diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb275872..b0ee9612 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,12 +11,12 @@ on: pull_request: jobs: - scala: + test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - java: [ '1.8', '1.11' ] + java: [ '8', '11' ] scala: [ { version: '2.12.13' }, { version: '2.12.12' }, @@ -37,10 +37,29 @@ jobs: with: fetch-depth: 0 - - name: Set up Scala env - uses: olafurpg/setup-scala@v10 + - name: Set up JVM + uses: actions/setup-java@v2 with: - java-version: adopt@${{ matrix.java }} + distribution: 'adopt' + java-version: ${{ matrix.java }} - name: run tests run: sbt ++${{ matrix.scala.version }} test + + style-check: + runs-on: ubuntu-latest + + steps: + - name: checkout the repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up JVM + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '8' + + - name: styleCheck + run: sbt styleCheck diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 00000000..5ae6c1d3 --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1,12 @@ +rules = [ + OrganizeImports +] + +OrganizeImports.groupedImports = Explode +OrganizeImports.expandRelative = true +OrganizeImports.removeUnused = true +OrganizeImports.groups = [ + "re:javax?\\." + "scala." + "*" +] diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..eb30fc05 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,4 @@ +version = "2.7.5" +project.git = true +assumeStandardLibraryStripMargin = true +xmlLiterals.assumeFormatted = true diff --git a/build.sbt b/build.sbt index 0a639ebb..35cc0d5d 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,10 @@ import sbtcrossproject.CrossProject import sbtcrossproject.CrossType val scalatestVersion = "3.2.8" - +val defaultScala213 = "2.13.5" val bin212 = Seq("2.12.13", "2.12.12", "2.12.11", "2.12.10", "2.12.9", "2.12.8") -val bin213 = Seq("2.13.5", "2.13.4", "2.13.3", "2.13.2", "2.13.1", "2.13.0") +val bin213 = + Seq(defaultScala213, "2.13.4", "2.13.3", "2.13.2", "2.13.1", "2.13.0") inThisBuild( List( @@ -27,12 +28,12 @@ inThisBuild( licenses := Seq( "Apache-2.0" -> url("http://www.apache.org/license/LICENSE-2.0") ), - scalaVersion := bin213.head, - crossScalaVersions := bin212 ++ bin213, + scalaVersion := defaultScala213, versionScheme := Some("early-semver"), Test / fork := false, Test / publishArtifact := false, Test / parallelExecution := false, + Global / concurrentRestrictions += Tags.limit(Tags.Test, 1), scalacOptions := Seq( "-unchecked", "-deprecation", @@ -40,10 +41,24 @@ inThisBuild( "-encoding", "utf8" ), - Global / concurrentRestrictions += Tags.limit(Tags.Test, 1) + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.5.0", + semanticdbEnabled := true, + semanticdbVersion := scalafixSemanticdb.revision, + scalafixScalaBinaryVersion := scalaBinaryVersion.value ) ) +lazy val sharedSettings = List( + scalacOptions := { + if (scalaVersion.value == defaultScala213) { + scalacOptions.value :+ "-Wunused:imports" + } else { + scalacOptions.value + } + }, + crossScalaVersions := bin212 ++ bin213 +) + lazy val root = Project("scalac-scoverage", file(".")) .settings( name := "scalac-scoverage", @@ -63,7 +78,8 @@ lazy val runtime = CrossProject( crossTarget := target.value / s"scala-${scalaVersion.value}", libraryDependencies ++= Seq( "org.scalatest" %%% "scalatest" % scalatestVersion % Test - ) + ), + sharedSettings ) .jvmSettings( Test / fork := true @@ -86,8 +102,19 @@ lazy val plugin = "org.scala-lang.modules" %% "scala-xml" % "1.3.0", "org.scalatest" %% "scalatest" % scalatestVersion % Test, "org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided - ) + ), + sharedSettings ) .settings( (Test / unmanagedSourceDirectories) += (Test / sourceDirectory).value / "scala-2.12+" ) + +addCommandAlias( + "styleFix", + "scalafixAll ; scalafmtAll ; scalafmtSbt" +) + +addCommandAlias( + "styleCheck", + "scalafmtCheckAll ; scalafmtSbtCheck ; scalafix --check" +) diff --git a/project/plugins.sbt b/project/plugins.sbt index a56379c4..f957831e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,7 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") + +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") + +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/CoverageFilter.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/CoverageFilter.scala index ea5206fd..189720ad 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/CoverageFilter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/CoverageFilter.scala @@ -1,14 +1,14 @@ package scoverage import scala.collection.mutable -import scala.reflect.internal.util.{Position, SourceFile} +import scala.reflect.internal.util.Position +import scala.reflect.internal.util.SourceFile import scala.util.matching.Regex -/** - * Methods related to filtering the instrumentation and coverage. - * - * @author Stephen Samuel - */ +/** Methods related to filtering the instrumentation and coverage. + * + * @author Stephen Samuel + */ trait CoverageFilter { def isClassIncluded(className: String): Boolean def isFileIncluded(file: SourceFile): Boolean @@ -25,40 +25,44 @@ object AllCoverageFilter extends CoverageFilter { override def isSymbolIncluded(symbolName: String): Boolean = true } -class RegexCoverageFilter(excludedPackages: Seq[String], - excludedFiles: Seq[String], - excludedSymbols: Seq[String]) extends CoverageFilter { +class RegexCoverageFilter( + excludedPackages: Seq[String], + excludedFiles: Seq[String], + excludedSymbols: Seq[String] +) extends CoverageFilter { val excludedClassNamePatterns = excludedPackages.map(_.r.pattern) val excludedFilePatterns = excludedFiles.map(_.r.pattern) val excludedSymbolPatterns = excludedSymbols.map(_.r.pattern) - /** - * We cache the excluded ranges to avoid scanning the source code files - * repeatedly. For a large project there might be a lot of source code - * data, so we only hold a weak reference. - */ - val linesExcludedByScoverageCommentsCache: mutable.Map[SourceFile, List[Range]] = mutable.WeakHashMap.empty + /** We cache the excluded ranges to avoid scanning the source code files + * repeatedly. For a large project there might be a lot of source code + * data, so we only hold a weak reference. + */ + val linesExcludedByScoverageCommentsCache + : mutable.Map[SourceFile, List[Range]] = mutable.WeakHashMap.empty final val scoverageExclusionCommentsRegex = """(?ms)^\s*//\s*(\$COVERAGE-OFF\$).*?(^\s*//\s*\$COVERAGE-ON\$|\Z)""".r - /** - * True if the given className has not been excluded by the - * `excludedPackages` option. - */ + /** True if the given className has not been excluded by the + * `excludedPackages` option. + */ override def isClassIncluded(className: String): Boolean = { - excludedClassNamePatterns.isEmpty || !excludedClassNamePatterns.exists(_.matcher(className).matches) + excludedClassNamePatterns.isEmpty || !excludedClassNamePatterns.exists( + _.matcher(className).matches + ) } override def isFileIncluded(file: SourceFile): Boolean = { - def isFileMatch(file: SourceFile) = excludedFilePatterns.exists(_.matcher(file.path.replace(".scala", "")).matches) + def isFileMatch(file: SourceFile) = excludedFilePatterns.exists( + _.matcher(file.path.replace(".scala", "")).matches + ) excludedFilePatterns.isEmpty || !isFileMatch(file) } - /** - * True if the line containing `position` has not been excluded by a magic comment. - */ + /** True if the line containing `position` has not been excluded by a magic comment. + */ def isLineIncluded(position: Position): Boolean = { if (position.isDefined) { val excludedLineNumbers = getExcludedLineNumbers(position.source) @@ -70,27 +74,34 @@ class RegexCoverageFilter(excludedPackages: Seq[String], } override def isSymbolIncluded(symbolName: String): Boolean = { - excludedSymbolPatterns.isEmpty || !excludedSymbolPatterns.exists(_.matcher(symbolName).matches) + excludedSymbolPatterns.isEmpty || !excludedSymbolPatterns.exists( + _.matcher(symbolName).matches + ) } - /** - * Provides overloads to paper over 2.12.13+ SourceFile incompatibility - */ - def compatFindAllIn(regexp: Regex, pattern: Array[Char]): Regex.MatchIterator = regexp.findAllIn(new String(pattern)) - def compatFindAllIn(regexp: Regex, pattern: String): Regex.MatchIterator = regexp.findAllIn(pattern) + /** Provides overloads to paper over 2.12.13+ SourceFile incompatibility + */ + def compatFindAllIn( + regexp: Regex, + pattern: Array[Char] + ): Regex.MatchIterator = regexp.findAllIn(new String(pattern)) + def compatFindAllIn(regexp: Regex, pattern: String): Regex.MatchIterator = + regexp.findAllIn(pattern) - /** - * Checks the given sourceFile for any magic comments which exclude lines - * from coverage. Returns a list of Ranges of lines that should be excluded. - * - * The line numbers returned are conventional 1-based line numbers (i.e. the - * first line is line number 1) - */ + /** Checks the given sourceFile for any magic comments which exclude lines + * from coverage. Returns a list of Ranges of lines that should be excluded. + * + * The line numbers returned are conventional 1-based line numbers (i.e. the + * first line is line number 1) + */ def getExcludedLineNumbers(sourceFile: SourceFile): List[Range] = { linesExcludedByScoverageCommentsCache.get(sourceFile) match { case Some(lineNumbers) => lineNumbers case None => - val lineNumbers = compatFindAllIn(scoverageExclusionCommentsRegex, sourceFile.content).matchData.map { m => + val lineNumbers = compatFindAllIn( + scoverageExclusionCommentsRegex, + sourceFile.content + ).matchData.map { m => // Asking a SourceFile for the line number of the char after // the end of the file gives an exception val endChar = math.min(m.end(2), sourceFile.content.length - 1) @@ -100,7 +111,8 @@ class RegexCoverageFilter(excludedPackages: Seq[String], // line numbers Range( 1 + sourceFile.offsetToLine(m.start(1)), - 1 + sourceFile.offsetToLine(endChar)) + 1 + sourceFile.offsetToLine(endChar) + ) }.toList linesExcludedByScoverageCommentsCache.put(sourceFile, lineNumbers) lineNumbers diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/DoubleFormat.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/DoubleFormat.scala index 0b689eb8..e470672a 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/DoubleFormat.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/DoubleFormat.scala @@ -1,6 +1,7 @@ package scoverage -import java.text.{DecimalFormat, DecimalFormatSymbols} +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols import java.util.Locale object DoubleFormat { diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala index 91a1caa3..0877fdca 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala @@ -2,7 +2,8 @@ package scoverage import java.io._ -import scala.collection.{Set, mutable} +import scala.collection.Set +import scala.collection.mutable import scala.io.Source /** @author Stephen Samuel */ @@ -11,7 +12,8 @@ object IOUtils { def getTempDirectory: File = new File(getTempPath) def getTempPath: String = System.getProperty("java.io.tmpdir") - def readStreamAsString(in: InputStream): String = Source.fromInputStream(in).mkString + def readStreamAsString(in: InputStream): String = + Source.fromInputStream(in).mkString private val UnixSeperator: Char = '/' private val WindowsSeperator: Char = '\\' @@ -27,15 +29,18 @@ object IOUtils { } def reportFile(outputDir: File, debug: Boolean = false): File = debug match { - case true => new File(outputDir, Constants.XMLReportFilenameWithDebug) + case true => new File(outputDir, Constants.XMLReportFilenameWithDebug) case false => new File(outputDir, Constants.XMLReportFilename) } - def clean(dataDir: File): Unit = findMeasurementFiles(dataDir).foreach(_.delete) + def clean(dataDir: File): Unit = + findMeasurementFiles(dataDir).foreach(_.delete) def clean(dataDir: String): Unit = clean(new File(dataDir)) def writeToFile(file: File, str: String) = { - val writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), UTF8Encoding)) + val writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), UTF8Encoding) + ) try { writer.write(str) } finally { @@ -43,43 +48,53 @@ object IOUtils { } } - /** - * Returns the measurement file for the current thread. - */ - def measurementFile(dataDir: File): File = measurementFile(dataDir.getAbsolutePath) - def measurementFile(dataDir: String): File = new File(dataDir, Constants.MeasurementsPrefix + Thread.currentThread.getId) - - def findMeasurementFiles(dataDir: String): Array[File] = findMeasurementFiles(new File(dataDir)) - def findMeasurementFiles(dataDir: File): Array[File] = dataDir.listFiles(new FileFilter { - override def accept(pathname: File): Boolean = pathname.getName.startsWith(Constants.MeasurementsPrefix) - }) + /** Returns the measurement file for the current thread. + */ + def measurementFile(dataDir: File): File = measurementFile( + dataDir.getAbsolutePath + ) + def measurementFile(dataDir: String): File = + new File(dataDir, Constants.MeasurementsPrefix + Thread.currentThread.getId) + + def findMeasurementFiles(dataDir: String): Array[File] = findMeasurementFiles( + new File(dataDir) + ) + def findMeasurementFiles(dataDir: File): Array[File] = + dataDir.listFiles(new FileFilter { + override def accept(pathname: File): Boolean = + pathname.getName.startsWith(Constants.MeasurementsPrefix) + }) def scoverageDataDirsSearch(baseDir: File): Seq[File] = { def directoryFilter = new FileFilter { override def accept(pathname: File): Boolean = pathname.isDirectory } def search(file: File): Seq[File] = file match { - case dir if dir.isDirectory && dir.getName == Constants.DataDir => Seq(dir) - case dir if dir.isDirectory => dir.listFiles(directoryFilter).toSeq.flatMap(search) + case dir if dir.isDirectory && dir.getName == Constants.DataDir => + Seq(dir) + case dir if dir.isDirectory => + dir.listFiles(directoryFilter).toSeq.flatMap(search) case _ => Nil } search(baseDir) } - val isMeasurementFile = (file: File) => file.getName.startsWith(Constants.MeasurementsPrefix) + val isMeasurementFile = (file: File) => + file.getName.startsWith(Constants.MeasurementsPrefix) val isReportFile = (file: File) => file.getName == Constants.XMLReportFilename - val isDebugReportFile = (file: File) => file.getName == Constants.XMLReportFilenameWithDebug + val isDebugReportFile = (file: File) => + file.getName == Constants.XMLReportFilenameWithDebug // loads all the invoked statement ids from the given files def invoked(files: Seq[File]): Set[(Int, String)] = { val acc = mutable.Set[(Int, String)]() files.foreach { file => val reader = Source.fromFile(file) - for ( line <- reader.getLines() ) { + for (line <- reader.getLines()) { if (!line.isEmpty) { acc += (line.split(" ").toList match { case List(idx, clazz) => (idx.toInt, clazz) - case List(idx) => (idx.toInt, "") + case List(idx) => (idx.toInt, "") // This should never really happen but to avoid a match error we'll default to a 0 // index here since we start with 1 anyways. case _ => (0, "") diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/Location.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/Location.scala index 7845811a..94a6fcef 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/Location.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/Location.scala @@ -2,29 +2,30 @@ package scoverage import scala.tools.nsc.Global -/** - * @param packageName the name of the enclosing package - * @param className the name of the closest enclosing class - * @param fullClassName the fully qualified name of the closest enclosing class - */ -case class Location(packageName: String, - className: String, - fullClassName: String, - classType: ClassType, - method: String, - sourcePath: String) extends java.io.Serializable +/** @param packageName the name of the enclosing package + * @param className the name of the closest enclosing class + * @param fullClassName the fully qualified name of the closest enclosing class + */ +case class Location( + packageName: String, + className: String, + fullClassName: String, + classType: ClassType, + method: String, + sourcePath: String +) extends java.io.Serializable object Location { def apply(global: Global): global.Tree => Option[Location] = { tree => - def packageName(s: global.Symbol): String = { s.enclosingPackage.fullName } def className(s: global.Symbol): String = { // anon functions are enclosed in proper classes. - if (s.enclClass.isAnonymousFunction || s.enclClass.isAnonymousClass) className(s.owner) + if (s.enclClass.isAnonymousFunction || s.enclClass.isAnonymousClass) + className(s.owner) else s.enclClass.nameString } @@ -36,13 +37,14 @@ object Location { def fullClassName(s: global.Symbol): String = { // anon functions are enclosed in proper classes. - if (s.enclClass.isAnonymousFunction || s.enclClass.isAnonymousClass) fullClassName(s.owner) + if (s.enclClass.isAnonymousFunction || s.enclClass.isAnonymousClass) + fullClassName(s.owner) else s.enclClass.fullNameString } def enclosingMethod(s: global.Symbol): String = { // check if we are in a proper method and return that, otherwise traverse up - if (s.enclClass.isAnonymousFunction ) enclosingMethod(s.owner) + if (s.enclClass.isAnonymousFunction) enclosingMethod(s.owner) else if (s.enclMethod.isPrimaryConstructor) "" else Option(s.enclMethod.nameString).getOrElse("") } @@ -51,15 +53,15 @@ object Location { Option(symbol.sourceFile).map(_.canonicalPath).getOrElse("") } - Option(tree.symbol) map { - symbol => - Location( - packageName(symbol), - className(symbol), - fullClassName(symbol), - classType(symbol), - enclosingMethod(symbol), - sourcePath(symbol)) + Option(tree.symbol) map { symbol => + Location( + packageName(symbol), + className(symbol), + fullClassName(symbol), + classType(symbol), + enclosingMethod(symbol), + sourcePath(symbol) + ) } } -} \ No newline at end of file +} diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala index d9e4ebfb..98667dd0 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala @@ -1,21 +1,28 @@ package scoverage -import java.io.{BufferedWriter, File, FileOutputStream, OutputStreamWriter, Writer} +import java.io.BufferedWriter +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter +import java.io.Writer -import scala.io.{Codec, Source} +import scala.io.Codec +import scala.io.Source object Serializer { // Write out coverage data to the given data directory, using the default coverage filename - def serialize(coverage: Coverage, dataDir: String): Unit = serialize(coverage, coverageFile(dataDir)) + def serialize(coverage: Coverage, dataDir: String): Unit = + serialize(coverage, coverageFile(dataDir)) // Write out coverage data to given file. def serialize(coverage: Coverage, file: File): Unit = { - val writer: Writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Codec.UTF8.name)) + val writer: Writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), Codec.UTF8.name) + ) try { serialize(coverage, writer) - } - finally { + } finally { writer.flush() writer.close() } @@ -24,55 +31,58 @@ object Serializer { def serialize(coverage: Coverage, writer: Writer): Unit = { def writeHeader(writer: Writer): Unit = { writer.write(s"""# Coverage data, format version: 2.0 - |# Statement data: - |# - id - |# - source path - |# - package name - |# - class name - |# - class type (Class, Object or Trait) - |# - full class name - |# - method name - |# - start offset - |# - end offset - |# - line number - |# - symbol name - |# - tree name - |# - is branch - |# - invocations count - |# - is ignored - |# - description (can be multi-line) - |# '\f' sign - |# ------------------------------------------ - |""".stripMargin) + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |""".stripMargin) } def writeStatement(stmt: Statement, writer: Writer): Unit = { writer.write(s"""${stmt.id} - |${stmt.location.sourcePath} - |${stmt.location.packageName} - |${stmt.location.className} - |${stmt.location.classType} - |${stmt.location.fullClassName} - |${stmt.location.method} - |${stmt.start} - |${stmt.end} - |${stmt.line} - |${stmt.symbolName} - |${stmt.treeName} - |${stmt.branch} - |${stmt.count} - |${stmt.ignored} - |${stmt.desc} - |\f - |""".stripMargin) + |${stmt.location.sourcePath} + |${stmt.location.packageName} + |${stmt.location.className} + |${stmt.location.classType} + |${stmt.location.fullClassName} + |${stmt.location.method} + |${stmt.start} + |${stmt.end} + |${stmt.line} + |${stmt.symbolName} + |${stmt.treeName} + |${stmt.branch} + |${stmt.count} + |${stmt.ignored} + |${stmt.desc} + |\f + |""".stripMargin) } writeHeader(writer) - coverage.statements.toSeq.sortBy(_.id).foreach(stmt => writeStatement(stmt, writer)) + coverage.statements.toSeq + .sortBy(_.id) + .foreach(stmt => writeStatement(stmt, writer)) } def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath) - def coverageFile(dataDir: String): File = new File(dataDir, Constants.CoverageFileName) + def coverageFile(dataDir: String): File = + new File(dataDir, Constants.CoverageFileName) def deserialize(file: File): Coverage = { deserialize(Source.fromFile(file)(Codec.UTF8).getLines()) @@ -87,7 +97,14 @@ object Serializer { val classType = lines.next() val fullClassName = lines.next() val method = lines.next() - val loc = Location(packageName, className, fullClassName, ClassType.fromString(classType), method, sourcePath) + val loc = Location( + packageName, + className, + fullClassName, + ClassType.fromString(classType), + method, + sourcePath + ) val start: Int = lines.next().toInt val end: Int = lines.next().toInt val lineNo: Int = lines.next().toInt @@ -97,11 +114,26 @@ object Serializer { val count: Int = lines.next().toInt val ignored: Boolean = lines.next().toBoolean val desc = lines.toList.mkString("\n") - Statement(loc, id, start, end, lineNo, desc, symbolName, treeName, branch, count, ignored) + Statement( + loc, + id, + start, + end, + lineNo, + desc, + symbolName, + treeName, + branch, + count, + ignored + ) } val headerFirstLine = lines.next() - require(headerFirstLine == "# Coverage data, format version: 2.0", "Wrong file format") + require( + headerFirstLine == "# Coverage data, format version: 2.0", + "Wrong file format" + ) val linesWithoutHeader = lines.dropWhile(_.startsWith("#")) val coverage = Coverage() diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala index 0f5297a6..aaad4cb2 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala @@ -2,19 +2,19 @@ package scoverage import java.io.File -import scoverage.DoubleFormat.twoFractionDigits - import scala.collection.mutable -/** - * @author Stephen Samuel */ +import scoverage.DoubleFormat.twoFractionDigits + +/** @author Stephen Samuel + */ case class Coverage() - extends CoverageMetrics - with MethodBuilders - with java.io.Serializable - with ClassBuilders - with PackageBuilders - with FileBuilders { + extends CoverageMetrics + with MethodBuilders + with java.io.Serializable + with ClassBuilders + with PackageBuilders + with FileBuilders { private val statementsById = mutable.Map[Int, Statement]() override def statements = statementsById.values @@ -22,30 +22,42 @@ case class Coverage() private val ignoredStatementsById = mutable.Map[Int, Statement]() override def ignoredStatements = ignoredStatementsById.values - def addIgnoredStatement(stmt: Statement): Unit = ignoredStatementsById.put(stmt.id, stmt) - + def addIgnoredStatement(stmt: Statement): Unit = + ignoredStatementsById.put(stmt.id, stmt) def avgClassesPerPackage = classCount / packageCount.toDouble - def avgClassesPerPackageFormatted: String = twoFractionDigits(avgClassesPerPackage) + def avgClassesPerPackageFormatted: String = twoFractionDigits( + avgClassesPerPackage + ) def avgMethodsPerClass = methodCount / classCount.toDouble - def avgMethodsPerClassFormatted: String = twoFractionDigits(avgMethodsPerClass) + def avgMethodsPerClassFormatted: String = twoFractionDigits( + avgMethodsPerClass + ) def loc = files.map(_.loc).sum def linesPerFile = loc / fileCount.toDouble def linesPerFileFormatted: String = twoFractionDigits(linesPerFile) // returns the classes by least coverage - def risks(limit: Int) = classes.toSeq.sortBy(_.statementCount).reverse.sortBy(_.statementCoverage).take(limit) + def risks(limit: Int) = classes.toSeq + .sortBy(_.statementCount) + .reverse + .sortBy(_.statementCoverage) + .take(limit) def apply(ids: Iterable[(Int, String)]): Unit = ids foreach invoked - def invoked(id: (Int, String)): Unit = statementsById.get(id._1).foreach(_.invoked(id._2)) + def invoked(id: (Int, String)): Unit = + statementsById.get(id._1).foreach(_.invoked(id._2)) } trait MethodBuilders { def statements: Iterable[Statement] def methods: Seq[MeasuredMethod] = { - statements.groupBy(stmt => stmt.location.packageName + "/" + stmt.location.className + "/" + stmt.location.method) + statements + .groupBy(stmt => + stmt.location.packageName + "/" + stmt.location.className + "/" + stmt.location.method + ) .map(arg => MeasuredMethod(arg._1, arg._2)) .toSeq } @@ -56,75 +68,93 @@ trait PackageBuilders { def statements: Iterable[Statement] def packageCount = packages.size def packages: Seq[MeasuredPackage] = { - statements.groupBy(_.location.packageName).map(arg => MeasuredPackage(arg._1, arg._2)).toSeq.sortBy(_.name) + statements + .groupBy(_.location.packageName) + .map(arg => MeasuredPackage(arg._1, arg._2)) + .toSeq + .sortBy(_.name) } } trait ClassBuilders { def statements: Iterable[Statement] - def classes = statements.groupBy(_.location.fullClassName).map(arg => MeasuredClass(arg._1, arg._2)) + def classes = statements + .groupBy(_.location.fullClassName) + .map(arg => MeasuredClass(arg._1, arg._2)) def classCount: Int = classes.size } trait FileBuilders { def statements: Iterable[Statement] - def files: Iterable[MeasuredFile] = statements.groupBy(_.source).map(arg => MeasuredFile(arg._1, arg._2)) + def files: Iterable[MeasuredFile] = + statements.groupBy(_.source).map(arg => MeasuredFile(arg._1, arg._2)) def fileCount: Int = files.size } -case class MeasuredMethod(name: String, statements: Iterable[Statement]) extends CoverageMetrics { +case class MeasuredMethod(name: String, statements: Iterable[Statement]) + extends CoverageMetrics { override def ignoredStatements: Iterable[Statement] = Seq() } case class MeasuredClass(fullClassName: String, statements: Iterable[Statement]) - extends CoverageMetrics with MethodBuilders { + extends CoverageMetrics + with MethodBuilders { def source: String = statements.head.source def loc = statements.map(_.line).max - /** - * The class name for display is the FQN minus the package, - * for example "com.a.Foo.Bar.Baz" should display as "Foo.Bar.Baz" - * and "com.a.Foo" should display as "Foo". - * - * This is used in the class lists in the package and overview pages. - */ - def displayClassName = statements.headOption.map(_.location).map { location => - location.fullClassName.stripPrefix(location.packageName + ".") - }.getOrElse(fullClassName) + /** The class name for display is the FQN minus the package, + * for example "com.a.Foo.Bar.Baz" should display as "Foo.Bar.Baz" + * and "com.a.Foo" should display as "Foo". + * + * This is used in the class lists in the package and overview pages. + */ + def displayClassName = statements.headOption + .map(_.location) + .map { location => + location.fullClassName.stripPrefix(location.packageName + ".") + } + .getOrElse(fullClassName) override def ignoredStatements: Iterable[Statement] = Seq() } case class MeasuredPackage(name: String, statements: Iterable[Statement]) - extends CoverageMetrics with ClassCoverage with ClassBuilders with FileBuilders { + extends CoverageMetrics + with ClassCoverage + with ClassBuilders + with FileBuilders { override def ignoredStatements: Iterable[Statement] = Seq() } case class MeasuredFile(source: String, statements: Iterable[Statement]) - extends CoverageMetrics with ClassCoverage with ClassBuilders { + extends CoverageMetrics + with ClassCoverage + with ClassBuilders { def filename = new File(source).getName def loc = statements.map(_.line).max override def ignoredStatements: Iterable[Statement] = Seq() } -case class Statement(location: Location, - id: Int, - start: Int, - end: Int, - line: Int, - desc: String, - symbolName: String, - treeName: String, - branch: Boolean, - var count: Int = 0, - ignored: Boolean = false, - tests: mutable.Set[String] = mutable.Set[String]()) extends java.io.Serializable { +case class Statement( + location: Location, + id: Int, + start: Int, + end: Int, + line: Int, + desc: String, + symbolName: String, + treeName: String, + branch: Boolean, + var count: Int = 0, + ignored: Boolean = false, + tests: mutable.Set[String] = mutable.Set[String]() +) extends java.io.Serializable { def source = location.sourcePath def invoked(test: String): Unit = { count = count + 1 - if(test != "") tests += test + if (test != "") tests += test } def isInvoked = count > 0 } @@ -137,8 +167,8 @@ object ClassType { def fromString(str: String): ClassType = { str.toLowerCase match { case "object" => Object - case "trait" => Trait - case _ => Class + case "trait" => Trait + case _ => Class } } } @@ -150,7 +180,9 @@ case class ClassRef(name: String) { object ClassRef { def fromFilepath(path: String) = ClassRef(path.replace('/', '.')) - def apply(_package: String, className: String): ClassRef = ClassRef(_package.replace('/', '.') + "." + className) + def apply(_package: String, className: String): ClassRef = ClassRef( + _package.replace('/', '.') + "." + className + ) } trait CoverageMetrics { @@ -162,18 +194,20 @@ trait CoverageMetrics { def invokedStatements: Iterable[Statement] = statements.filter(_.count > 0) def invokedStatementCount = invokedStatements.size - def statementCoverage: Double = if (statementCount == 0) 1 else invokedStatementCount / statementCount.toDouble + def statementCoverage: Double = if (statementCount == 0) 1 + else invokedStatementCount / statementCount.toDouble def statementCoveragePercent = statementCoverage * 100 - def statementCoverageFormatted: String = twoFractionDigits(statementCoveragePercent) + def statementCoverageFormatted: String = twoFractionDigits( + statementCoveragePercent + ) def branches: Iterable[Statement] = statements.filter(_.branch) def branchCount: Int = branches.size def branchCoveragePercent = branchCoverage * 100 def invokedBranches: Iterable[Statement] = branches.filter(_.count > 0) def invokedBranchesCount = invokedBranches.size - /** - * @see http://stackoverflow.com/questions/25184716/scoverage-ambiguous-measurement-from-branch-coverage - */ + /** @see http://stackoverflow.com/questions/25184716/scoverage-ambiguous-measurement-from-branch-coverage + */ def branchCoverage: Double = { // if there are zero branches, then we have a single line of execution. // in that case, if there is at least some coverage, we have covered the branch. diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala index 4d4d0343..02b6c557 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala @@ -6,20 +6,38 @@ import java.util.concurrent.atomic.AtomicInteger import scala.reflect.internal.ModifierFlags import scala.reflect.internal.util.SourceFile import scala.tools.nsc.Global -import scala.tools.nsc.plugins.{PluginComponent, Plugin} -import scala.tools.nsc.transform.{Transform, TypingTransformers} +import scala.tools.nsc.plugins.Plugin +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.transform.Transform +import scala.tools.nsc.transform.TypingTransformers /** @author Stephen Samuel */ class ScoveragePlugin(val global: Global) extends Plugin { override val name: String = "scoverage" override val description: String = "scoverage code coverage compiler plugin" - private val (extraAfterPhase, extraBeforePhase) = processPhaseOptions(pluginOptions) - val instrumentationComponent = new ScoverageInstrumentationComponent(global, extraAfterPhase, extraBeforePhase) - override val components: List[PluginComponent] = List(instrumentationComponent) - - private def parseExclusionEntry(entryName: String, inOption: String): Seq[String] = - inOption.substring(entryName.length).split(";").map(_.trim).toIndexedSeq.filterNot(_.isEmpty) + private val (extraAfterPhase, extraBeforePhase) = processPhaseOptions( + pluginOptions + ) + val instrumentationComponent = new ScoverageInstrumentationComponent( + global, + extraAfterPhase, + extraBeforePhase + ) + override val components: List[PluginComponent] = List( + instrumentationComponent + ) + + private def parseExclusionEntry( + entryName: String, + inOption: String + ): Seq[String] = + inOption + .substring(entryName.length) + .split(";") + .map(_.trim) + .toIndexedSeq + .filterNot(_.isEmpty) override def init(opts: List[String], error: String => Unit): Boolean = { val options = new ScoverageOptions @@ -33,7 +51,10 @@ class ScoveragePlugin(val global: Global) extends Plugin { options.excludedSymbols = parseExclusionEntry("excludedSymbols:", opt) } else if (opt.startsWith("dataDir:")) { options.dataDir = opt.substring("dataDir:".length) - } else if (opt.startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:")) { + } else if ( + opt + .startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:") + ) { // skip here, these flags are processed elsewhere } else if (opt == "reportTestName") { options.reportTestName = true @@ -42,21 +63,25 @@ class ScoveragePlugin(val global: Global) extends Plugin { } } if (!opts.exists(_.startsWith("dataDir:"))) - throw new RuntimeException("Cannot invoke plugin without specifying ") + throw new RuntimeException( + "Cannot invoke plugin without specifying " + ) instrumentationComponent.setOptions(options) true } - override val optionsHelp: Option[String] = Some(Seq( - "-P:scoverage:dataDir: where the coverage files should be written\n", - "-P:scoverage:excludedPackages:; semicolon separated list of regexs for packages to exclude", - "-P:scoverage:excludedFiles:; semicolon separated list of regexs for paths to exclude", - "-P:scoverage:excludedSymbols:; semicolon separated list of regexs for symbols to exclude", - "-P:scoverage:extraAfterPhase: phase after which scoverage phase runs (must be after typer phase)", - "-P:scoverage:extraBeforePhase: phase before which scoverage phase runs (must be before patmat phase)", - " Any classes whose fully qualified name matches the regex will", - " be excluded from coverage." - ).mkString("\n")) + override val optionsHelp: Option[String] = Some( + Seq( + "-P:scoverage:dataDir: where the coverage files should be written\n", + "-P:scoverage:excludedPackages:; semicolon separated list of regexs for packages to exclude", + "-P:scoverage:excludedFiles:; semicolon separated list of regexs for paths to exclude", + "-P:scoverage:excludedSymbols:; semicolon separated list of regexs for symbols to exclude", + "-P:scoverage:extraAfterPhase: phase after which scoverage phase runs (must be after typer phase)", + "-P:scoverage:extraBeforePhase: phase before which scoverage phase runs (must be before patmat phase)", + " Any classes whose fully qualified name matches the regex will", + " be excluded from coverage." + ).mkString("\n") + ) // copied from scala 2.11 private def pluginOptions: List[String] = { @@ -65,7 +90,9 @@ class ScoveragePlugin(val global: Global) extends Plugin { global.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec) } - private def processPhaseOptions(opts: List[String]): (Option[String], Option[String]) = { + private def processPhaseOptions( + opts: List[String] + ): (Option[String], Option[String]) = { var afterPhase: Option[String] = None var beforePhase: Option[String] = None for (opt <- opts) { @@ -83,13 +110,20 @@ class ScoveragePlugin(val global: Global) extends Plugin { class ScoverageOptions { var excludedPackages: Seq[String] = Nil var excludedFiles: Seq[String] = Nil - var excludedSymbols: Seq[String] = Seq("scala.reflect.api.Exprs.Expr", "scala.reflect.api.Trees.Tree", "scala.reflect.macros.Universe.Tree") + var excludedSymbols: Seq[String] = Seq( + "scala.reflect.api.Exprs.Expr", + "scala.reflect.api.Trees.Tree", + "scala.reflect.macros.Universe.Tree" + ) var dataDir: String = IOUtils.getTempPath var reportTestName: Boolean = false } -class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Option[String], extraBeforePhase: Option[String]) - extends PluginComponent +class ScoverageInstrumentationComponent( + val global: Global, + extraAfterPhase: Option[String], + extraBeforePhase: Option[String] +) extends PluginComponent with TypingTransformers with Transform { @@ -99,11 +133,12 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt val coverage = new Coverage override val phaseName: String = "scoverage-instrumentation" - override val runsAfter: List[String] = List("typer") ::: extraAfterPhase.toList - override val runsBefore: List[String] = List("patmat") ::: extraBeforePhase.toList + override val runsAfter: List[String] = + List("typer") ::: extraAfterPhase.toList + override val runsBefore: List[String] = + List("patmat") ::: extraBeforePhase.toList - /** - * Our options are not provided at construction time, but shortly after, + /** Our options are not provided at construction time, but shortly after, * so they start as None. * You must call "setOptions" before running any commands that rely on * the options. @@ -121,7 +156,11 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt def setOptions(options: ScoverageOptions): Unit = { this.options = options - coverageFilter = new RegexCoverageFilter(options.excludedPackages, options.excludedFiles, options.excludedSymbols) + coverageFilter = new RegexCoverageFilter( + options.excludedPackages, + options.excludedFiles, + options.excludedSymbols + ) new File(options.dataDir).mkdirs() // ensure data directory is created } @@ -136,33 +175,41 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt reporter.echo("Beginning coverage instrumentation") super.run() - reporter.echo(s"Instrumentation completed [${coverage.statements.size} statements]") + reporter.echo( + s"Instrumentation completed [${coverage.statements.size} statements]" + ) Serializer.serialize(coverage, Serializer.coverageFile(options.dataDir)) - reporter.echo(s"Wrote instrumentation file [${Serializer.coverageFile(options.dataDir)}]") + reporter.echo( + s"Wrote instrumentation file [${Serializer.coverageFile(options.dataDir)}]" + ) reporter.echo(s"Will write measurement data to [${options.dataDir}]") } } - protected def newTransformer(unit: CompilationUnit): Transformer = new Transformer(unit) + protected def newTransformer(unit: CompilationUnit): Transformer = + new Transformer(unit) - class Transformer(unit: global.CompilationUnit) extends TypingTransformer(unit) { + class Transformer(unit: global.CompilationUnit) + extends TypingTransformer(unit) { import global._ // contains the location of the last node var location: Location = _ - /** - * The 'start' of the position, if it is available, else -1 + /** The 'start' of the position, if it is available, else -1 * We cannot use 'isDefined' to test whether pos.start will work, as some * classes (e.g. scala.reflect.internal.util.OffsetPosition have * isDefined true, but throw on `start` */ - def safeStart(tree: Tree): Int = scala.util.Try(tree.pos.start).getOrElse(-1) + def safeStart(tree: Tree): Int = + scala.util.Try(tree.pos.start).getOrElse(-1) def safeEnd(tree: Tree): Int = scala.util.Try(tree.pos.end).getOrElse(-1) - def safeLine(tree: Tree): Int = if (tree.pos.isDefined) tree.pos.line else -1 - def safeSource(tree: Tree): Option[SourceFile] = if (tree.pos.isDefined) Some(tree.pos.source) else None + def safeLine(tree: Tree): Int = + if (tree.pos.isDefined) tree.pos.line else -1 + def safeSource(tree: Tree): Option[SourceFile] = + if (tree.pos.isDefined) Some(tree.pos.source) else None def invokeCall(id: Int): Tree = { Apply( @@ -176,14 +223,16 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt Literal( Constant(id) ) :: - Literal( - Constant(options.dataDir) - ) :: - (if(options.reportTestName) - List(Literal( - Constant(true) - )) - else Nil) + Literal( + Constant(options.dataDir) + ) :: + (if (options.reportTestName) + List( + Literal( + Constant(true) + ) + ) + else Nil) ) } @@ -193,26 +242,40 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt def transformForCases(cases: List[CaseDef]): List[CaseDef] = { // we don't instrument the synthetic case _ => false clause - cases.dropRight(1).map(c => { - treeCopy.CaseDef( - // in a for-loop we don't care about instrumenting the guards, as they are synthetically generated - c, c.pat, process(c.guard), process(c.body) - ) - }) ++ cases.takeRight(1) + cases + .dropRight(1) + .map(c => { + treeCopy.CaseDef( + // in a for-loop we don't care about instrumenting the guards, as they are synthetically generated + c, + c.pat, + process(c.guard), + process(c.body) + ) + }) ++ cases.takeRight(1) } def transformCases(cases: List[CaseDef]): List[CaseDef] = { cases.map(c => { treeCopy.CaseDef( - c, c.pat, process(c.guard), process(c.body) + c, + c.pat, + process(c.guard), + process(c.body) ) }) } - def instrument(tree: Tree, original: Tree, branch: Boolean = false): Tree = { + def instrument( + tree: Tree, + original: Tree, + branch: Boolean = false + ): Tree = { safeSource(tree) match { case None => - reporter.echo(s"[warn] Could not instrument [${tree.getClass.getSimpleName}/${tree.symbol}]. No pos.") + reporter.echo( + s"[warn] Could not instrument [${tree.getClass.getSimpleName}/${tree.symbol}]. No pos." + ) tree case Some(source) => val id = statementIds.incrementAndGet @@ -251,7 +314,7 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt isJSCtorDefaultParam(sym) } else { sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) && - isJSType(sym.owner) && { + isJSType(sym.owner) && { /* If this is a default parameter accessor on a * non-native JS class, we need to know if the method for which we * are the default parameter is exposed or not. @@ -272,14 +335,18 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt } } } - - private lazy val JSTypeAnnot = rootMirror.getRequiredClass("scala.scalajs.js.annotation.internal.JSType") - private lazy val ExposedJSMemberAnnot = rootMirror.getRequiredClass("scala.scalajs.js.annotation.internal.ExposedJSMember") - private lazy val JSNativeAnnotation = rootMirror.getRequiredClass("scala.scalajs.js.native") + + private lazy val JSTypeAnnot = + rootMirror.getRequiredClass("scala.scalajs.js.annotation.internal.JSType") + private lazy val ExposedJSMemberAnnot = rootMirror.getRequiredClass( + "scala.scalajs.js.annotation.internal.ExposedJSMember" + ) + private lazy val JSNativeAnnotation = + rootMirror.getRequiredClass("scala.scalajs.js.native") private def isJSType(sym: Symbol): Boolean = sym.hasAnnotation(JSTypeAnnot) - + def isNonNativeJSClass(sym: Symbol): Boolean = !sym.isTrait && isJSType(sym) && !sym.hasAnnotation(JSNativeAnnotation) @@ -291,10 +358,10 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt sym.hasAnnotation(ExposedJSMemberAnnot) } } - + private def isJSCtorDefaultParam(sym: Symbol) = { isCtorDefaultParam(sym) && - isJSType(patchedLinkedClassOfClass(sym.owner)) + isJSType(patchedLinkedClassOfClass(sym.owner)) } private def patchedLinkedClassOfClass(sym: Symbol): Symbol = { @@ -317,42 +384,63 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt private def isCtorDefaultParam(sym: Symbol) = { sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) && - sym.owner.isModuleClass && - nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR - } - + sym.owner.isModuleClass && + nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR + } + def isUndefinedParameterInScalaJs(sym: Symbol): Boolean = { isScalaJsEnabled && sym != null && isJSDefaultParam(sym) } - def isClassIncluded(symbol: Symbol): Boolean = coverageFilter.isClassIncluded(symbol.fullNameString) - def isFileIncluded(source: SourceFile): Boolean = coverageFilter.isFileIncluded(source) - def isStatementIncluded(pos: Position): Boolean = coverageFilter.isLineIncluded(pos) - def isSymbolIncluded(symbol: Symbol): Boolean = coverageFilter.isSymbolIncluded(symbol.fullNameString) + def isClassIncluded(symbol: Symbol): Boolean = + coverageFilter.isClassIncluded(symbol.fullNameString) + def isFileIncluded(source: SourceFile): Boolean = + coverageFilter.isFileIncluded(source) + def isStatementIncluded(pos: Position): Boolean = + coverageFilter.isLineIncluded(pos) + def isSymbolIncluded(symbol: Symbol): Boolean = + coverageFilter.isSymbolIncluded(symbol.fullNameString) def updateLocation(t: Tree): Unit = { Location(global)(t) match { case Some(loc) => this.location = loc - case _ => reporter.warning(t.pos, s"[warn] Cannot update location for $t") + case _ => + reporter.warning(t.pos, s"[warn] Cannot update location for $t") } } def transformPartial(c: ClassDef): ClassDef = { treeCopy.ClassDef( - c, c.mods, c.name, c.tparams, + c, + c.mods, + c.name, + c.tparams, treeCopy.Template( - c.impl, c.impl.parents, c.impl.self, c.impl.body.map { + c.impl, + c.impl.parents, + c.impl.self, + c.impl.body.map { case d: DefDef if d.name.toString == "applyOrElse" => d.rhs match { case Match(selector, cases) => treeCopy.DefDef( - d, d.mods, d.name, d.tparams, d.vparamss, d.tpt, + d, + d.mods, + d.name, + d.tparams, + d.vparamss, + d.tpt, treeCopy.Match( // note: do not transform last case as that is the default handling - d.rhs, selector, transformCases(cases.init) :+ cases.last + d.rhs, + selector, + transformCases(cases.init) :+ cases.last ) ) case _ => - reporter.error(c.pos, "Cannot instrument partial function apply. Please file bug report") + reporter.error( + c.pos, + "Cannot instrument partial function apply. Please file bug report" + ) d } case other => other @@ -363,28 +451,51 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt def debug(t: Tree): Unit = { import scala.reflect.runtime.{universe => u} - reporter.echo(t.getClass.getSimpleName + ": LINE " + safeLine(t) + ": " + u.showRaw(t)) + reporter.echo( + t.getClass.getSimpleName + ": LINE " + safeLine(t) + ": " + u.showRaw(t) + ) } def traverseApplication(t: Tree): Tree = { t match { - case a: ApplyToImplicitArgs => treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args)) - case Apply(Select(_, name), List(fun@Function(params, body))) - if name.toString == "withFilter" && fun.symbol.isSynthetic && fun.toString.contains("check$ifrefutable$1") => t - case a: Apply => treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args)) - case a: TypeApply => treeCopy.TypeApply(a, traverseApplication(a.fun), transformStatements(a.args)) - case s: Select => treeCopy.Select(s, traverseApplication(s.qualifier), s.name) + case a: ApplyToImplicitArgs => + treeCopy.Apply( + a, + traverseApplication(a.fun), + transformStatements(a.args) + ) + case Apply(Select(_, name), List(fun @ Function(params, body))) + if name.toString == "withFilter" && fun.symbol.isSynthetic && fun.toString + .contains("check$ifrefutable$1") => + t + case a: Apply => + treeCopy.Apply( + a, + traverseApplication(a.fun), + transformStatements(a.args) + ) + case a: TypeApply => + treeCopy.TypeApply( + a, + traverseApplication(a.fun), + transformStatements(a.args) + ) + case s: Select => + treeCopy.Select(s, traverseApplication(s.qualifier), s.name) case i: Ident => i - case t: This => t - case other => process(other) + case t: This => t + case other => process(other) } } - private def isSynthetic(t: Tree): Boolean = Option(t.symbol).fold(false)(_.isSynthetic) + private def isSynthetic(t: Tree): Boolean = + Option(t.symbol).fold(false)(_.isSynthetic) private def isNonSynthetic(t: Tree): Boolean = !isSynthetic(t) - private def containsNonSynthetic(t: Tree): Boolean = isNonSynthetic(t) || t.children.exists(containsNonSynthetic) + private def containsNonSynthetic(t: Tree): Boolean = + isNonSynthetic(t) || t.children.exists(containsNonSynthetic) - def allConstArgs(args: List[Tree]) = args.forall(arg => arg.isInstanceOf[Literal] || arg.isInstanceOf[Ident]) + def allConstArgs(args: List[Tree]) = + args.forall(arg => arg.isInstanceOf[Literal] || arg.isInstanceOf[Ident]) def process(tree: Tree): Tree = { tree match { @@ -393,7 +504,11 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // case t if !t.pos.isRange => super.transform(t) // ignore macro expanded code, do not send to super as we don't want any children to be instrumented - case t if t.attachments.all.toString().contains("MacroExpansionAttachment") => t + case t + if t.attachments.all + .toString() + .contains("MacroExpansionAttachment") => + t // /** // * Object creation from new. @@ -402,35 +517,55 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // case a: GenericApply if a.symbol.isConstructor && a.symbol.enclClass.isAnonymousFunction => tree // case a: GenericApply if a.symbol.isConstructor => instrument(a) - /** - * When an apply has no parameters, or is an application of purely literals or idents + /** When an apply has no parameters, or is an application of purely literals or idents * then we can simply instrument the outer call. Ie, we can treat it all as one single statement * for the purposes of code coverage. * This will include calls to case apply. */ case a: GenericApply if allConstArgs(a.args) => instrument(a, a) - /** - * Applications of methods with non trivial args means the args themselves + /** Applications of methods with non trivial args means the args themselves * must also be instrumented */ //todo remove once scala merges into Apply proper case a: ApplyToImplicitArgs => - instrument(treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args)), a) + instrument( + treeCopy.Apply( + a, + traverseApplication(a.fun), + transformStatements(a.args) + ), + a + ) // handle 'new' keywords, instrumenting parameter lists - case a@Apply(s@Select(New(tpt), name), args) => + case a @ Apply(s @ Select(New(tpt), name), args) => instrument(treeCopy.Apply(a, s, transformStatements(args)), a) case a: Apply => - instrument(treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args)), a) + instrument( + treeCopy.Apply( + a, + traverseApplication(a.fun), + transformStatements(a.args) + ), + a + ) case a: TypeApply => - instrument(treeCopy.TypeApply(a, traverseApplication(a.fun), transformStatements(a.args)), a) + instrument( + treeCopy.TypeApply( + a, + traverseApplication(a.fun), + transformStatements(a.args) + ), + a + ) /** pattern match with syntax `Assign(lhs, rhs)`. * This AST node corresponds to the following Scala code: * lhs = rhs */ - case assign: Assign => treeCopy.Assign(assign, assign.lhs, process(assign.rhs)) + case assign: Assign => + treeCopy.Assign(assign, assign.lhs, process(assign.rhs)) /** pattern match with syntax `Block(stats, expr)`. * This AST node corresponds to the following Scala code: @@ -441,8 +576,11 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt treeCopy.Block(b, transformStatements(b.stats), transform(b.expr)) // special support to handle partial functions - case c: ClassDef if c.symbol.isAnonymousFunction && - c.symbol.enclClass.superClass.nameString.contains("AbstractPartialFunction") => + case c: ClassDef + if c.symbol.isAnonymousFunction && + c.symbol.enclClass.superClass.nameString.contains( + "AbstractPartialFunction" + ) => if (isClassIncluded(c.symbol)) { transformPartial(c) } else { @@ -451,7 +589,8 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // scalac generated classes, we just instrument the enclosed methods/statements // the location would stay as the source class - case c: ClassDef if c.symbol.isAnonymousClass || c.symbol.isAnonymousFunction => + case c: ClassDef + if c.symbol.isAnonymousClass || c.symbol.isAnonymousFunction => if (isFileIncluded(c.pos.source) && isClassIncluded(c.symbol)) super.transform(tree) else { @@ -471,9 +610,10 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // this will catch methods defined as macros, eg def test = macro testImpl // it will not catch macro implementations - case d: DefDef if d.symbol != null - && d.symbol.annotations.nonEmpty - && d.symbol.annotations.toString() == "macroImpl" => + case d: DefDef + if d.symbol != null + && d.symbol.annotations.nonEmpty + && d.symbol.annotations.toString() == "macroImpl" => tree // will catch macro implementations, as they must end with Expr, however will catch @@ -484,8 +624,7 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // we can ignore primary constructors because they are just empty at this stage, the body is added later. case d: DefDef if d.symbol.isPrimaryConstructor => tree - /** - * Case class accessors for vals + /** Case class accessors for vals * EG for case class CreditReject(req: MarketOrderRequest, client: ActorRef) * def req: com.sksamuel.scoverage.samples.MarketOrderRequest * def client: akka.actor.ActorRef @@ -495,15 +634,22 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // Compiler generated case apply and unapply. Ignore these case d: DefDef if d.symbol.isCaseApplyOrUnapply => tree - /** - * Lazy stable DefDefs are generated as the impl for lazy vals. + /** Lazy stable DefDefs are generated as the impl for lazy vals. */ - case d: DefDef if d.symbol.isStable && d.symbol.isGetter && d.symbol.isLazy => + case d: DefDef + if d.symbol.isStable && d.symbol.isGetter && d.symbol.isLazy => updateLocation(d) - treeCopy.DefDef(d, d.mods, d.name, d.tparams, d.vparamss, d.tpt, process(d.rhs)) + treeCopy.DefDef( + d, + d.mods, + d.name, + d.tparams, + d.vparamss, + d.tpt, + process(d.rhs) + ) - /** - * Stable getters are methods generated for access to a top level val. + /** Stable getters are methods generated for access to a top level val. * Should be ignored as this is compiler generated code. * * Eg @@ -537,7 +683,15 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt */ case d: DefDef => updateLocation(d) - treeCopy.DefDef(d, d.mods, d.name, d.tparams, d.vparamss, d.tpt, process(d.rhs)) + treeCopy.DefDef( + d, + d.mods, + d.name, + d.tparams, + d.vparamss, + d.tpt, + process(d.rhs) + ) case EmptyTree => tree @@ -552,10 +706,12 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // The two procedures (then and else) are instrumented separately to determine if we entered // both branches. case i: If => - treeCopy.If(i, + treeCopy.If( + i, process(i.cond), instrument(process(i.thenp), i.thenp, branch = true), - instrument(process(i.elsep), i.elsep, branch = true)) + instrument(process(i.elsep), i.elsep, branch = true) + ) case _: Import => tree @@ -567,11 +723,13 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt case l: Literal => instrument(l, l) // pattern match clauses will be instrumented per case - case m@Match(selector: Tree, cases: List[CaseDef]) => + case m @ Match(selector: Tree, cases: List[CaseDef]) => // we can be fairly sure this was generated as part of a for loop - if (selector.toString.contains("check$") + if ( + selector.toString.contains("check$") && selector.tpe.annotations.mkString == "unchecked" - && m.cases.last.toString == "case _ => false") { + && m.cases.last.toString == "case _ => false" + ) { treeCopy.Match(tree, process(selector), transformForCases(cases)) } else { // if the selector was added by compiler, we don't want to instrument it.... @@ -579,7 +737,7 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt if (Option(selector.symbol).exists(_.isSynthetic)) treeCopy.Match(tree, selector, transformCases(cases)) else - // .. but we will if it was a user match + // .. but we will if it was a user match treeCopy.Match(tree, process(selector), transformCases(cases)) } @@ -597,8 +755,7 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt m } - /** - * match with syntax `New(tpt)`. + /** match with syntax `New(tpt)`. * This AST node corresponds to the following Scala code: * * `new` T @@ -620,15 +777,15 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt * TypeTree(typeOf[Int])), * List(Literal(Constant(2)))), * List(Literal(Constant(3)))) - * */ case n: New => n - case s@Select(n@New(tpt), name) => + case s @ Select(n @ New(tpt), name) => instrument(treeCopy.Select(s, n, name), s) case p: PackageDef => - if (isClassIncluded(p.symbol)) treeCopy.PackageDef(p, p.pid, transformStatements(p.stats)) + if (isClassIncluded(p.symbol)) + treeCopy.PackageDef(p, p.pid, transformStatements(p.stats)) else p // This AST node corresponds to the following Scala code: `return` expr @@ -648,13 +805,16 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt */ case s: Select if location == null => tree - /** - * I think lazy selects are the LHS of a lazy assign. + /** I think lazy selects are the LHS of a lazy assign. * todo confirm we can ignore */ case s: Select if s.symbol.isLazy => tree - case s: Select => instrument(treeCopy.Select(s, traverseApplication(s.qualifier), s.name), s) + case s: Select => + instrument( + treeCopy.Select(s, traverseApplication(s.qualifier), s.name), + s + ) case s: Super => tree @@ -669,43 +829,53 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt // instrument trys, catches and finally as separate blocks case Try(t: Tree, cases: List[CaseDef], f: Tree) => - treeCopy.Try(tree, + treeCopy.Try( + tree, instrument(process(t), t, branch = true), transformCases(cases), - if (f.isEmpty) f else instrument(process(f), f, branch = true)) + if (f.isEmpty) f else instrument(process(f), f, branch = true) + ) // type aliases, type parameters, abstract types case t: TypeDef => super.transform(tree) case t: Template => updateLocation(t) - treeCopy.Template(tree, t.parents, t.self, transformStatements(t.body)) + treeCopy.Template( + tree, + t.parents, + t.self, + transformStatements(t.body) + ) case _: TypeTree => super.transform(tree) - /** - * We can ignore lazy val defs as they are implemented by a generated defdef + /** We can ignore lazy val defs as they are implemented by a generated defdef */ case v: ValDef if v.symbol.isLazy => tree - /** - * val default: A1 => B1 = + /** val default: A1 => B1 = * val x1: Any = _ */ case v: ValDef if v.symbol.isSynthetic => tree - /** - * Vals declared in case constructors + /** Vals declared in case constructors */ - case v: ValDef if v.symbol.isParamAccessor && v.symbol.isCaseAccessor => tree + case v: ValDef if v.symbol.isParamAccessor && v.symbol.isCaseAccessor => + tree // we need to remove the final mod so that we keep the code in order to check its invoked case v: ValDef if v.mods.isFinal => updateLocation(v) - treeCopy.ValDef(v, v.mods.&~(ModifierFlags.FINAL), v.name, v.tpt, process(v.rhs)) + treeCopy.ValDef( + v, + v.mods.&~(ModifierFlags.FINAL), + v.name, + v.tpt, + process(v.rhs) + ) - /** - * This AST node corresponds to any of the following Scala code: + /** This AST node corresponds to any of the following Scala code: * * mods `val` name: tpt = rhs * mods `var` name: tpt = rhs @@ -721,10 +891,12 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt treeCopy.ValDef(tree, v.mods, v.name, v.tpt, process(v.rhs)) case _ => - reporter.warning(tree.pos, "BUG: Unexpected construct: " + tree.getClass + " " + tree.symbol) + reporter.warning( + tree.pos, + "BUG: Unexpected construct: " + tree.getClass + " " + tree.symbol + ) super.transform(tree) } } } } - diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/BaseReportWriter.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/BaseReportWriter.scala index 294bc0f7..158c3e51 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/BaseReportWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/BaseReportWriter.scala @@ -5,27 +5,29 @@ import java.io.File class BaseReportWriter(sourceDirectories: Seq[File], outputDir: File) { // Source paths in canonical form WITH trailing file separator - private val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator ) + private val formattedSourcePaths: Seq[String] = + sourceDirectories filter (_.isDirectory) map (_.getCanonicalPath + File.separator) - /** - * Converts absolute path to relative one if any of the source directories is it's parent. - * If there is no parent directory, the path is returned unchanged (absolute). - * - * @param src absolute file path in canonical form - */ - def relativeSource(src: String): String = relativeSource(src, formattedSourcePaths) + /** Converts absolute path to relative one if any of the source directories is it's parent. + * If there is no parent directory, the path is returned unchanged (absolute). + * + * @param src absolute file path in canonical form + */ + def relativeSource(src: String): String = + relativeSource(src, formattedSourcePaths) private def relativeSource(src: String, sourcePaths: Seq[String]): String = { // We need the canonical path for the given src because our formattedSourcePaths are canonical val canonicalSrc = new File(src).getCanonicalPath - val sourceRoot: Option[String] = sourcePaths.find( - sourcePath => canonicalSrc.startsWith(sourcePath) - ) + val sourceRoot: Option[String] = + sourcePaths.find(sourcePath => canonicalSrc.startsWith(sourcePath)) sourceRoot match { case Some(path: String) => canonicalSrc.replace(path, "") case _ => val fmtSourcePaths: String = sourcePaths.mkString("'", "', '", "'") - throw new RuntimeException(s"No source root found for '$canonicalSrc' (source roots: $fmtSourcePaths)"); + throw new RuntimeException( + s"No source root found for '$canonicalSrc' (source roots: $fmtSourcePaths)" + ); } } diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala index 27a5df63..4a6a5286 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala @@ -2,22 +2,27 @@ package scoverage.report import java.io.File +import scala.xml.Node +import scala.xml.PrettyPrinter + import scoverage.DoubleFormat.twoFractionDigits import scoverage._ -import scala.xml.{Node, PrettyPrinter} - /** @author Stephen Samuel */ -class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) extends BaseReportWriter(sourceDirectories, outputDir) { +class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) + extends BaseReportWriter(sourceDirectories, outputDir) { - def this (baseDir: File, outputDir: File) = { + def this(baseDir: File, outputDir: File) = { this(Seq(baseDir), outputDir) } def write(coverage: Coverage): Unit = { val file = new File(outputDir, "cobertura.xml") - IOUtils.writeToFile(file, "\n\n" + - new PrettyPrinter(120, 4).format(xml(coverage))) + IOUtils.writeToFile( + file, + "\n\n" + + new PrettyPrinter(120, 4).format(xml(coverage)) + ) } def method(method: MeasuredMethod): Node = { @@ -27,12 +32,12 @@ class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) extends branch-rate={twoFractionDigits(method.branchCoverage)} complexity="0"> - {method.statements.map(stmt => - - )} + branch={stmt.branch.toString}/>) + } } @@ -47,12 +52,12 @@ class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) extends {klass.methods.map(method)} - {klass.statements.map(stmt => - - )} + branch={stmt.branch.toString}/>) + } } diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CodeGrid.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CodeGrid.scala index 72966a69..e730f86c 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CodeGrid.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CodeGrid.scala @@ -1,14 +1,14 @@ package scoverage.report -import _root_.scoverage.MeasuredFile - import scala.io.Source +import _root_.scoverage.MeasuredFile + /** @author Stephen Samuel */ class CodeGrid(mFile: MeasuredFile, sourceEncoding: Option[String]) { // for backward compatibility only - def this (mFile: MeasuredFile) = { + def this(mFile: MeasuredFile) = { this(mFile, None); } @@ -19,14 +19,16 @@ class CodeGrid(mFile: MeasuredFile, sourceEncoding: Option[String]) { // Array of lines, each line is an array of cells, where a cell is a character + coverage info for that position // All cells default to NoData until the highlighted information is applied // note: we must re-include the line sep to keep source positions correct. - private val lines = source(mFile).split(lineBreak).map(line => (line.toCharArray ++ lineBreak).map(Cell(_, NoData))) + private val lines = source(mFile) + .split(lineBreak) + .map(line => (line.toCharArray ++ lineBreak).map(Cell(_, NoData))) // useful to have a single array to write into the cells private val cells = lines.flatten // apply the instrumentation data to the cells updating their coverage info mFile.statements.foreach(stmt => { - for ( k <- stmt.start until stmt.end ) { + for (k <- stmt.start until stmt.end) { if (k < cells.size) { // if the cell is set to Invoked, then it be changed to NotInvoked, as an inner statement will override // outer containing statements. If a cell is NotInvoked then it can not be changed further. @@ -59,11 +61,11 @@ class CodeGrid(mFile: MeasuredFile, sourceEncoding: Option[String]) { } // escape xml characters cell.char match { - case '<' => sb.append("<") - case '>' => sb.append(">") - case '&' => sb.append("&") - case '"' => sb.append(""") - case c => sb.append(c) + case '<' => sb.append("<") + case '>' => sb.append(">") + case '&' => sb.append("&") + case '"' => sb.append(""") + case c => sb.append(c) } }) sb append "" @@ -74,22 +76,22 @@ class CodeGrid(mFile: MeasuredFile, sourceEncoding: Option[String]) { private def source(mfile: MeasuredFile): String = { val src = sourceEncoding match { - case Some(enc) => Source.fromFile(mfile.source, enc) - case None => Source.fromFile(mfile.source) + case Some(enc) => Source.fromFile(mfile.source, enc) + case None => Source.fromFile(mfile.source) } src.mkString } - private def spanStart(status: StatementStatus): String = s"" + private def spanStart(status: StatementStatus): String = + s"" private def cellStyle(status: StatementStatus): String = { val GREEN = "#AEF1AE" val RED = "#F0ADAD" status match { - case Invoked => s"background: $GREEN" + case Invoked => s"background: $GREEN" case NotInvoked => s"background: $RED" - case NoData => "" + case NoData => "" } } } - diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala index cc9a0f50..3e23a990 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala @@ -2,7 +2,9 @@ package scoverage.report import java.io.File -import scoverage.{Coverage, IOUtils, Serializer} +import scoverage.Coverage +import scoverage.IOUtils +import scoverage.Serializer object CoverageAggregator { @@ -12,10 +14,14 @@ object CoverageAggregator { } // to be used by gradle-scoverage plugin - def aggregate(dataDirs: Array[File]): Option[Coverage] = aggregate(dataDirs.toSeq) + def aggregate(dataDirs: Array[File]): Option[Coverage] = aggregate( + dataDirs.toSeq + ) def aggregate(dataDirs: Seq[File]): Option[Coverage] = { - println(s"[info] Found ${dataDirs.size} subproject scoverage data directories [${dataDirs.mkString(",")}]") + println( + s"[info] Found ${dataDirs.size} subproject scoverage data directories [${dataDirs.mkString(",")}]" + ) if (dataDirs.size > 0) { Some(aggregatedCoverage(dataDirs)) } else { @@ -30,7 +36,8 @@ object CoverageAggregator { val coverageFile: File = Serializer.coverageFile(dataDir) if (coverageFile.exists) { val subcoverage: Coverage = Serializer.deserialize(coverageFile) - val measurementFiles: Array[File] = IOUtils.findMeasurementFiles(dataDir) + val measurementFiles: Array[File] = + IOUtils.findMeasurementFiles(dataDir) val measurements = IOUtils.invoked(measurementFiles.toIndexedSeq) subcoverage.apply(measurements) subcoverage.statements foreach { stmt => diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala index d41064a0..791d397c 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala @@ -3,36 +3,48 @@ package scoverage.report import java.io.File import java.util.Date -import scoverage._ - import scala.xml.Node +import scoverage._ + /** @author Stephen Samuel */ -class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceEncoding: Option[String]) extends BaseReportWriter(sourceDirectories, outputDir) { +class ScoverageHtmlWriter( + sourceDirectories: Seq[File], + outputDir: File, + sourceEncoding: Option[String] +) extends BaseReportWriter(sourceDirectories, outputDir) { // to be used by gradle-scoverage plugin - def this (sourceDirectories: Array[File], outputDir: File, sourceEncoding: Option[String]) = { - this (sourceDirectories.toSeq, outputDir, sourceEncoding) + def this( + sourceDirectories: Array[File], + outputDir: File, + sourceEncoding: Option[String] + ) = { + this(sourceDirectories.toSeq, outputDir, sourceEncoding) } // for backward compatibility only - def this (sourceDirectories: Seq[File], outputDir: File) = { + def this(sourceDirectories: Seq[File], outputDir: File) = { this(sourceDirectories, outputDir, None); } - + // for backward compatibility only - def this (sourceDirectory: File, outputDir: File) = { + def this(sourceDirectory: File, outputDir: File) = { this(Seq(sourceDirectory), outputDir) } - + def write(coverage: Coverage): Unit = { val indexFile = new File(outputDir.getAbsolutePath + "/index.html") val cssFile = new File(outputDir.getAbsolutePath + "/pure-min.css") val packageFile = new File(outputDir.getAbsolutePath + "/packages.html") val overviewFile = new File(outputDir.getAbsolutePath + "/overview.html") - val index = IOUtils.readStreamAsString(getClass.getResourceAsStream("/scoverage/index.html")) - val css = IOUtils.readStreamAsString(getClass.getResourceAsStream("/scoverage/pure-min.css")) + val index = IOUtils.readStreamAsString( + getClass.getResourceAsStream("/scoverage/index.html") + ) + val css = IOUtils.readStreamAsString( + getClass.getResourceAsStream("/scoverage/pure-min.css") + ) IOUtils.writeToFile(indexFile, index) IOUtils.writeToFile(cssFile, css) IOUtils.writeToFile(packageFile, packageList(coverage).toString()) @@ -58,7 +70,8 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE IOUtils.writeToFile(file, filePage(mfile).toString()) } - private def packageOverviewRelativePath(pkg: MeasuredPackage) = pkg.name.replace("", "(empty)") + ".html" + private def packageOverviewRelativePath(pkg: MeasuredPackage) = + pkg.name.replace("", "(empty)") + ".html" private def filePage(mfile: MeasuredFile): Node = { val filename = relativeSource(mfile.source) + ".html" @@ -216,7 +229,9 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE val filename: String = { - val fileRelativeToSource = new File(relativeSource(klass.source) + ".html") + val fileRelativeToSource = new File( + relativeSource(klass.source) + ".html" + ) val path = fileRelativeToSource.getParent val value = fileRelativeToSource.getName @@ -238,7 +253,11 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE - {klass.statements.headOption.map(_.source.split(File.separatorChar).last).getOrElse("")} + { + klass.statements.headOption + .map(_.source.split(File.separatorChar).last) + .getOrElse("") + } {klass.loc.toString} @@ -299,14 +318,18 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE - {coverage.packages.map(arg => - + { + coverage.packages.map(arg => + - {arg.name} + { + arg.name + } {arg.statementCoverageFormatted}% - )} + ) + } @@ -341,8 +364,10 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE - {coverage.risks(limit).map(klass => - + { + coverage + .risks(limit) + .map(klass => {klass.displayClassName} @@ -366,7 +391,8 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE {klass.branchCoverageFormatted} % - )} + ) + } } @@ -424,10 +450,10 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE def stats(coverage: Coverage): Node = { - val statement0f = Math.round(coverage.statementCoveragePercent).toInt.toString + val statement0f = + Math.round(coverage.statementCoveragePercent).toInt.toString val branch0f = Math.round(coverage.branchCoveragePercent).toInt.toString -
@@ -549,7 +575,7 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE
} - + def plugins = { @@ -557,7 +583,11 @@ class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File, sourceE } diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala index 0dd5dc8e..7f1f8208 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala @@ -2,14 +2,19 @@ package scoverage.report import java.io.File -import scoverage._ +import scala.xml.Node +import scala.xml.PrettyPrinter -import scala.xml.{Node, PrettyPrinter} +import scoverage._ /** @author Stephen Samuel */ -class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: Boolean) extends BaseReportWriter(sourceDirectories, outputDir) { +class ScoverageXmlWriter( + sourceDirectories: Seq[File], + outputDir: File, + debug: Boolean +) extends BaseReportWriter(sourceDirectories, outputDir) { - def this (sourceDir: File, outputDir: File, debug: Boolean) = { + def this(sourceDir: File, outputDir: File, debug: Boolean) = { this(Seq(sourceDir), outputDir, debug) } @@ -51,7 +56,7 @@ class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: B {escape(stmt.desc)} case false => - } - /** - * This method ensures that the output String has only + + /** This method ensures that the output String has only * valid XML unicode characters as specified by the * XML 1.0 standard. For reference, please see * the @@ -112,15 +117,16 @@ class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: B * @param in The String whose non-valid characters we want to remove. * @return The in String, stripped of non-valid characters. * @see http://blog.mark-mclaren.info/2007/02/invalid-xml-characters-when-valid-utf8_5873.html - * */ def escape(in: String): String = { val out = new StringBuilder() - for ( current <- Option(in).getOrElse("").toCharArray ) { - if ((current == 0x9) || (current == 0xA) || (current == 0xD) || - ((current >= 0x20) && (current <= 0xD7FF)) || - ((current >= 0xE000) && (current <= 0xFFFD)) || - ((current >= 0x10000) && (current <= 0x10FFFF))) + for (current <- Option(in).getOrElse("").toCharArray) { + if ( + (current == 0x9) || (current == 0xa) || (current == 0xd) || + ((current >= 0x20) && (current <= 0xd7ff)) || + ((current >= 0xe000) && (current <= 0xfffd)) || + ((current >= 0x10000) && (current <= 0x10ffff)) + ) out.append(current) } out.mkString diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/StatementWriter.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/StatementWriter.scala index fa859f2e..4c0f0ddd 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/StatementWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/StatementWriter.scala @@ -1,9 +1,9 @@ package scoverage.report -import _root_.scoverage.MeasuredFile - import scala.xml.Node +import _root_.scoverage.MeasuredFile + /** @author Stephen Samuel */ class StatementWriter(mFile: MeasuredFile) { @@ -13,7 +13,7 @@ class StatementWriter(mFile: MeasuredFile) { def output: Node = { def cellStyle(invoked: Boolean): String = invoked match { - case true => s"background: $GREEN" + case true => s"background: $GREEN" case false => s"background: $RED" } @@ -26,7 +26,10 @@ class StatementWriter(mFile: MeasuredFile) { Symbol Tests Code - {mFile.statements.toSeq.sortBy(_.line).map(stmt => { + { + mFile.statements.toSeq + .sortBy(_.line) + .map(stmt => { {stmt.line} @@ -52,7 +55,8 @@ class StatementWriter(mFile: MeasuredFile) { {stmt.desc} - })} + }) + } } } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala index acc67d80..3cd39cb1 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala @@ -4,15 +4,20 @@ import java.io.File import java.util.UUID import javax.xml.parsers.DocumentBuilderFactory -import org.scalatest.{BeforeAndAfter, OneInstancePerTest} -import org.xml.sax.{ErrorHandler, SAXParseException} -import scoverage.report.CoberturaXmlWriter - import scala.xml.XML + +import org.scalatest.BeforeAndAfter +import org.scalatest.OneInstancePerTest import org.scalatest.funsuite.AnyFunSuite +import org.xml.sax.ErrorHandler +import org.xml.sax.SAXParseException +import scoverage.report.CoberturaXmlWriter /** @author Stephen Samuel */ -class CoberturaXmlWriterTest extends AnyFunSuite with BeforeAndAfter with OneInstancePerTest { +class CoberturaXmlWriterTest + extends AnyFunSuite + with BeforeAndAfter + with OneInstancePerTest { def tempDir(): File = { val dir = new File(IOUtils.getTempDirectory, UUID.randomUUID.toString) @@ -25,7 +30,8 @@ class CoberturaXmlWriterTest extends AnyFunSuite with BeforeAndAfter with OneIns // Let current directory be our source root private val sourceRoot = new File(".") - private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath + private def canonicalPath(fileName: String) = + new File(sourceRoot, fileName).getCanonicalPath test("cobertura output validates") { @@ -33,29 +39,181 @@ class CoberturaXmlWriterTest extends AnyFunSuite with BeforeAndAfter with OneIns val coverage = scoverage.Coverage() coverage - .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")), - 1, 2, 3, 12, "", "", "", false, 3)) + .add( + Statement( + Location( + "com.sksamuel.scoverage", + "A", + "com.sksamuel.scoverage.A", + ClassType.Object, + "create", + canonicalPath("a.scala") + ), + 1, + 2, + 3, + 12, + "", + "", + "", + false, + 3 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")), - 2, 2, 3, 16, "", "", "", false, 3)) + .add( + Statement( + Location( + "com.sksamuel.scoverage", + "A", + "com.sksamuel.scoverage.A", + ClassType.Object, + "create2", + canonicalPath("a.scala") + ), + 2, + 2, + 3, + 16, + "", + "", + "", + false, + 3 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage2", "B", "com.sksamuel.scoverage2.B", ClassType.Object, "retrieve", canonicalPath("b.scala")), - 3, 2, 3, 21, "", "", "", false, 0)) + .add( + Statement( + Location( + "com.sksamuel.scoverage2", + "B", + "com.sksamuel.scoverage2.B", + ClassType.Object, + "retrieve", + canonicalPath("b.scala") + ), + 3, + 2, + 3, + 21, + "", + "", + "", + false, + 0 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")), - 4, 2, 3, 9, "", "", "", false, 3)) + .add( + Statement( + Location( + "com.sksamuel.scoverage2", + "B", + "B", + ClassType.Object, + "retrieve2", + canonicalPath("b.scala") + ), + 4, + 2, + 3, + 9, + "", + "", + "", + false, + 3 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update", canonicalPath("c.scala")), - 5, 2, 3, 66, "", "", "", true, 3)) + .add( + Statement( + Location( + "com.sksamuel.scoverage3", + "C", + "com.sksamuel.scoverage3.C", + ClassType.Object, + "update", + canonicalPath("c.scala") + ), + 5, + 2, + 3, + 66, + "", + "", + "", + true, + 3 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update2", canonicalPath("c.scala")), - 6, 2, 3, 6, "", "", "", true, 3)) + .add( + Statement( + Location( + "com.sksamuel.scoverage3", + "C", + "com.sksamuel.scoverage3.C", + ClassType.Object, + "update2", + canonicalPath("c.scala") + ), + 6, + 2, + 3, + 6, + "", + "", + "", + true, + 3 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete", canonicalPath("d.scala")), - 7, 2, 3, 4, "", "", "", false, 0)) + .add( + Statement( + Location( + "com.sksamuel.scoverage4", + "D", + "com.sksamuel.scoverage4.D", + ClassType.Object, + "delete", + canonicalPath("d.scala") + ), + 7, + 2, + 3, + 4, + "", + "", + "", + false, + 0 + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete2", canonicalPath("d.scala")), - 8, 2, 3, 14, "", "", "", false, 0)) + .add( + Statement( + Location( + "com.sksamuel.scoverage4", + "D", + "com.sksamuel.scoverage4.D", + ClassType.Object, + "delete2", + canonicalPath("d.scala") + ), + 8, + 2, + 3, + 14, + "", + "", + "", + false, + 0 + ) + ) val writer = new CoberturaXmlWriter(sourceRoot, dir) writer.write(coverage) @@ -81,20 +239,77 @@ class CoberturaXmlWriterTest extends AnyFunSuite with BeforeAndAfter with OneIns builder.parse(fileIn(dir)) } - test("coverage rates are written as 2dp decimal values rather than percentage") { + test( + "coverage rates are written as 2dp decimal values rather than percentage" + ) { val dir = tempDir() val coverage = Coverage() coverage - .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")), - 1, 2, 3, 12, "", "", "", false)) + .add( + Statement( + Location( + "com.sksamuel.scoverage", + "A", + "com.sksamuel.scoverage.A", + ClassType.Object, + "create", + canonicalPath("a.scala") + ), + 1, + 2, + 3, + 12, + "", + "", + "", + false + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")), - 2, 2, 3, 16, "", "", "", true)) + .add( + Statement( + Location( + "com.sksamuel.scoverage", + "A", + "com.sksamuel.scoverage.A", + ClassType.Object, + "create2", + canonicalPath("a.scala") + ), + 2, + 2, + 3, + 16, + "", + "", + "", + true + ) + ) coverage - .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create3", canonicalPath("a.scala")), - 3, 3, 3, 20, "", "", "", true, 1)) + .add( + Statement( + Location( + "com.sksamuel.scoverage", + "A", + "com.sksamuel.scoverage.A", + ClassType.Object, + "create3", + canonicalPath("a.scala") + ), + 3, + 3, + 3, + 20, + "", + "", + "", + true, + 1 + ) + ) val writer = new CoberturaXmlWriter(sourceRoot, dir) writer.write(coverage) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala index 6267b9ea..3f9dc1f9 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala @@ -1,28 +1,32 @@ package scoverage -import java.io.{File, FileWriter} +import java.io.File +import java.io.FileWriter import java.util.UUID -import scoverage.report.CoverageAggregator import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers +import scoverage.report.CoverageAggregator class CoverageAggregatorTest extends AnyFreeSpec with Matchers { // Let current directory be our source root private val sourceRoot = new File(".") - private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath + private def canonicalPath(fileName: String) = + new File(sourceRoot, fileName).getCanonicalPath "coverage aggregator" - { "should merge coverage objects with same id" in { val source = canonicalPath("com/scoverage/class.scala") - val location = Location("com.scoverage.foo", + val location = Location( + "com.scoverage.foo", "ServiceState", "com.scoverage.foo.Service.ServiceState", ClassType.Trait, "methlab", - source) + source + ) val cov1Stmt1 = Statement(location, 1, 155, 176, 4, "", "", "", true, 1) val cov1Stmt2 = Statement(location, 2, 200, 300, 5, "", "", "", false, 1) @@ -32,7 +36,8 @@ class CoverageAggregatorTest extends AnyFreeSpec with Matchers { val dir1 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir1.mkdir() Serializer.serialize(coverage1, Serializer.coverageFile(dir1)) - val measurementsFile1 = new File(dir1, s"${Constants.MeasurementsPrefix}1") + val measurementsFile1 = + new File(dir1, s"${Constants.MeasurementsPrefix}1") val measurementsFile1Writer = new FileWriter(measurementsFile1) measurementsFile1Writer.write("1\n2\n") measurementsFile1Writer.close() @@ -44,18 +49,21 @@ class CoverageAggregatorTest extends AnyFreeSpec with Matchers { dir2.mkdir() Serializer.serialize(coverage2, Serializer.coverageFile(dir2)) - val cov3Stmt1 = Statement(location, 2, 14, 1515, 544, "", "", "", false, 1) + val cov3Stmt1 = + Statement(location, 2, 14, 1515, 544, "", "", "", false, 1) val coverage3 = Coverage() coverage3.add(cov3Stmt1.copy(count = 0)) val dir3 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir3.mkdir() Serializer.serialize(coverage3, Serializer.coverageFile(dir3)) - val measurementsFile3 = new File(dir3, s"${Constants.MeasurementsPrefix}1") + val measurementsFile3 = + new File(dir3, s"${Constants.MeasurementsPrefix}1") val measurementsFile3Writer = new FileWriter(measurementsFile3) measurementsFile3Writer.write("2\n") measurementsFile3Writer.close() - val aggregated = CoverageAggregator.aggregatedCoverage(Seq(dir1, dir2, dir3)) + val aggregated = + CoverageAggregator.aggregatedCoverage(Seq(dir1, dir2, dir3)) aggregated.statements.toSet.size shouldBe 4 aggregated.statements.map(_.copy(id = 0)).toSet shouldBe Set(cov1Stmt1, cov1Stmt2, cov2Stmt1, cov3Stmt1).map(_.copy(id = 0)) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala index 043bb77f..287f32fb 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala @@ -7,17 +7,8 @@ class CoverageMetricsTest extends AnyFreeSpec with Matchers { "no branches with at least one invoked statement should have 100% branch coverage" in { val metrics = new CoverageMetrics { - override def statements: Iterable[Statement] = Seq(Statement( - null, - 0, - 0, - 0, - 0, - null, - null, - null, - false, - 1)) + override def statements: Iterable[Statement] = + Seq(Statement(null, 0, 0, 0, 0, null, null, null, false, 1)) override def ignoredStatements: Iterable[Statement] = Seq() } @@ -27,17 +18,8 @@ class CoverageMetricsTest extends AnyFreeSpec with Matchers { "no branches with no invoked statements should have 0% branch coverage" in { val metrics = new CoverageMetrics { - override def statements: Iterable[Statement] = Seq(Statement( - null, - 0, - 0, - 0, - 0, - null, - null, - null, - false, - 0)) + override def statements: Iterable[Statement] = + Seq(Statement(null, 0, 0, 0, 0, null, null, null, false, 0)) override def ignoredStatements: Iterable[Statement] = Seq() } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala index d78549be..df40a432 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala @@ -1,10 +1,14 @@ package scoverage -import org.scalatest.{BeforeAndAfter, OneInstancePerTest} +import org.scalatest.BeforeAndAfter +import org.scalatest.OneInstancePerTest import org.scalatest.funsuite.AnyFunSuite /** @author Stephen Samuel */ -class CoverageTest extends AnyFunSuite with BeforeAndAfter with OneInstancePerTest { +class CoverageTest + extends AnyFunSuite + with BeforeAndAfter + with OneInstancePerTest { test("coverage for no statements is 1") { val coverage = Coverage() @@ -13,16 +17,81 @@ class CoverageTest extends AnyFunSuite with BeforeAndAfter with OneInstancePerTe test("coverage for no invoked statements is 0") { val coverage = Coverage() - coverage.add(Statement(Location("", "","", ClassType.Object, "", ""), 1, 2, 3, 4, "", "", "", false, 0)) + coverage.add( + Statement( + Location("", "", "", ClassType.Object, "", ""), + 1, + 2, + 3, + 4, + "", + "", + "", + false, + 0 + ) + ) assert(0 === coverage.statementCoverage) } test("coverage for invoked statements") { val coverage = Coverage() - coverage.add(Statement(Location("", "","", ClassType.Object, "", ""), 1, 2, 3, 4, "", "", "", false, 3)) - coverage.add(Statement(Location("", "", "", ClassType.Object, "", ""), 2, 2, 3, 4, "", "", "", false, 0)) - coverage.add(Statement(Location("", "", "", ClassType.Object, "", ""), 3, 2, 3, 4, "", "", "", false, 0)) - coverage.add(Statement(Location("", "", "", ClassType.Object, "", ""), 4, 2, 3, 4, "", "", "", false, 0)) + coverage.add( + Statement( + Location("", "", "", ClassType.Object, "", ""), + 1, + 2, + 3, + 4, + "", + "", + "", + false, + 3 + ) + ) + coverage.add( + Statement( + Location("", "", "", ClassType.Object, "", ""), + 2, + 2, + 3, + 4, + "", + "", + "", + false, + 0 + ) + ) + coverage.add( + Statement( + Location("", "", "", ClassType.Object, "", ""), + 3, + 2, + 3, + 4, + "", + "", + "", + false, + 0 + ) + ) + coverage.add( + Statement( + Location("", "", "", ClassType.Object, "", ""), + 4, + 2, + 3, + 4, + "", + "", + "", + false, + 0 + ) + ) assert(0.25 === coverage.statementCoverage) } } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala index c2159a64..ab79d9e3 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala @@ -1,6 +1,7 @@ package scoverage -import java.io.{File, FileWriter} +import java.io.File +import java.io.FileWriter import java.util.UUID import org.scalatest.OneInstancePerTest @@ -23,7 +24,7 @@ class IOUtilsTest extends AnyFreeSpec with OneInstancePerTest with Matchers { } "should parse multiple measurement files" in { // clean up any existing measurement files - for ( file <- IOUtils.findMeasurementFiles(IOUtils.getTempDirectory) ) + for (file <- IOUtils.findMeasurementFiles(IOUtils.getTempDirectory)) file.delete() val file1 = File.createTempFile("scoverage.measurements.1", "txt") @@ -38,7 +39,17 @@ class IOUtilsTest extends AnyFreeSpec with OneInstancePerTest with Matchers { val files = IOUtils.findMeasurementFiles(file1.getParent) val invoked = IOUtils.invoked(files.toIndexedSeq) - assert(invoked === Set((1, ""), (2, ""), (5, ""), (7, ""), (9, ""), (10, ""), (14, ""))) + assert( + invoked === Set( + (1, ""), + (2, ""), + (5, ""), + (7, ""), + (9, ""), + (10, ""), + (14, "") + ) + ) file1.delete() file2.delete() diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/LocationCompiler.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/LocationCompiler.scala index 0aede017..45803a4f 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/LocationCompiler.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/LocationCompiler.scala @@ -4,17 +4,21 @@ import java.io.File import scala.tools.nsc.Global import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.transform.{Transform, TypingTransformers} +import scala.tools.nsc.transform.Transform +import scala.tools.nsc.transform.TypingTransformers -class LocationCompiler(settings: scala.tools.nsc.Settings, reporter: scala.tools.nsc.reporters.Reporter) - extends scala.tools.nsc.Global(settings, reporter) { +class LocationCompiler( + settings: scala.tools.nsc.Settings, + reporter: scala.tools.nsc.reporters.Reporter +) extends scala.tools.nsc.Global(settings, reporter) { val locations = List.newBuilder[(String, Location)] private val locationSetter = new LocationSetter(this) def compile(code: String): Unit = { val files = writeCodeSnippetToTempFile(code) - val command = new scala.tools.nsc.CompilerCommand(List(files.getAbsolutePath), settings) + val command = + new scala.tools.nsc.CompilerCommand(List(files.getAbsolutePath), settings) new Run().compile(command.files) } @@ -25,17 +29,23 @@ class LocationCompiler(settings: scala.tools.nsc.Settings, reporter: scala.tools file } - class LocationSetter(val global: Global) extends PluginComponent with TypingTransformers with Transform { + class LocationSetter(val global: Global) + extends PluginComponent + with TypingTransformers + with Transform { override val phaseName = "location-setter" override val runsAfter = List("typer") override val runsBefore = List("patmat") - override protected def newTransformer(unit: global.CompilationUnit): global.Transformer = new Transformer(unit) - class Transformer(unit: global.CompilationUnit) extends TypingTransformer(unit) { + override protected def newTransformer( + unit: global.CompilationUnit + ): global.Transformer = new Transformer(unit) + class Transformer(unit: global.CompilationUnit) + extends TypingTransformer(unit) { override def transform(tree: global.Tree) = { - for ( location <- Location(global)(tree) ) { + for (location <- Location(global)(tree)) { locations += (tree.getClass.getSimpleName -> location) } super.transform(tree) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/LocationTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/LocationTest.scala index c8b2c13f..a59a27da 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/LocationTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/LocationTest.scala @@ -20,7 +20,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for objects" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.test\nobject Bammy { def foo = Symbol(\"boo\") } ") + compiler.compile( + "package com.test\nobject Bammy { def foo = Symbol(\"boo\") } " + ) val loc = compiler.locations.result().find(_._1 == "Template").get._2 loc.packageName shouldBe "com.test" loc.className shouldBe "Bammy" @@ -31,7 +33,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for traits" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.test\ntrait Gammy { def goo = Symbol(\"hoo\") } ") + compiler.compile( + "package com.test\ntrait Gammy { def goo = Symbol(\"hoo\") } " + ) val loc = compiler.locations.result().find(_._1 == "Template").get._2 loc.packageName shouldBe "com.test" loc.className shouldBe "Gammy" @@ -43,7 +47,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "should correctly process methods" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.methodtest \n class Hammy { def foo = Symbol(\"boo\") } ") + compiler.compile( + "package com.methodtest \n class Hammy { def foo = Symbol(\"boo\") } " + ) val loc = compiler.locations.result().find(_._2.method == "foo").get._2 loc.packageName shouldBe "com.methodtest" loc.className shouldBe "Hammy" @@ -53,7 +59,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "should correctly process nested methods" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.methodtest \n class Hammy { def foo = { def goo = { getClass; 3 }; goo } } ") + compiler.compile( + "package com.methodtest \n class Hammy { def foo = { def goo = { getClass; 3 }; goo } } " + ) val loc = compiler.locations.result().find(_._2.method == "goo").get._2 loc.packageName shouldBe "com.methodtest" loc.className shouldBe "Hammy" @@ -63,7 +71,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "should process anon functions as inside the enclosing method" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.methodtest \n class Jammy { def moo = { Option(\"bat\").map(_.length) } } ") + compiler.compile( + "package com.methodtest \n class Jammy { def moo = { Option(\"bat\").map(_.length) } } " + ) val loc = compiler.locations.result().find(_._1 == "Function").get._2 loc.packageName shouldBe "com.methodtest" loc.className shouldBe "Jammy" @@ -75,8 +85,11 @@ class LocationTest extends AnyFreeSpec with Matchers { "should use outer package" - { "for nested classes" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.methodtest \n class Jammy { class Pammy } ") - val loc = compiler.locations.result().find(_._2.className == "Pammy").get._2 + compiler.compile( + "package com.methodtest \n class Jammy { class Pammy } " + ) + val loc = + compiler.locations.result().find(_._2.className == "Pammy").get._2 loc.packageName shouldBe "com.methodtest" loc.className shouldBe "Pammy" loc.fullClassName shouldBe "com.methodtest.Jammy.Pammy" @@ -86,8 +99,11 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for nested objects" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.methodtest \n class Jammy { object Zammy } ") - val loc = compiler.locations.result().find(_._2.className == "Zammy").get._2 + compiler.compile( + "package com.methodtest \n class Jammy { object Zammy } " + ) + val loc = + compiler.locations.result().find(_._2.className == "Zammy").get._2 loc.packageName shouldBe "com.methodtest" loc.className shouldBe "Zammy" loc.fullClassName shouldBe "com.methodtest.Jammy.Zammy" @@ -97,8 +113,11 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for nested traits" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.methodtest \n class Jammy { trait Mammy } ") - val loc = compiler.locations.result().find(_._2.className == "Mammy").get._2 + compiler.compile( + "package com.methodtest \n class Jammy { trait Mammy } " + ) + val loc = + compiler.locations.result().find(_._2.className == "Mammy").get._2 loc.packageName shouldBe "com.methodtest" loc.className shouldBe "Mammy" loc.fullClassName shouldBe "com.methodtest.Jammy.Mammy" @@ -110,9 +129,11 @@ class LocationTest extends AnyFreeSpec with Matchers { "should support nested packages" - { "for classes" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.a \n " + - "package b \n" + - "class Kammy ") + compiler.compile( + "package com.a \n " + + "package b \n" + + "class Kammy " + ) val loc = compiler.locations.result().find(_._1 == "Template").get._2 loc.packageName shouldBe "com.a.b" loc.className shouldBe "Kammy" @@ -123,9 +144,11 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for objects" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.a \n " + - "package b \n" + - "object Kammy ") + compiler.compile( + "package com.a \n " + + "package b \n" + + "object Kammy " + ) val loc = compiler.locations.result().find(_._1 == "Template").get._2 loc.packageName shouldBe "com.a.b" loc.className shouldBe "Kammy" @@ -136,9 +159,11 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for traits" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.a \n " + - "package b \n" + - "trait Kammy ") + compiler.compile( + "package com.a \n " + + "package b \n" + + "trait Kammy " + ) val loc = compiler.locations.result().find(_._1 == "Template").get._2 loc.packageName shouldBe "com.a.b" loc.className shouldBe "Kammy" @@ -151,7 +176,9 @@ class LocationTest extends AnyFreeSpec with Matchers { "should use method name" - { "for class constructor body" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.b \n class Tammy { val name = Symbol(\"sam\") } ") + compiler.compile( + "package com.b \n class Tammy { val name = Symbol(\"sam\") } " + ) val loc = compiler.locations.result().find(_._1 == "ValDef").get._2 loc.packageName shouldBe "com.b" loc.className shouldBe "Tammy" @@ -162,7 +189,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for object constructor body" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.b \n object Yammy { val name = Symbol(\"sam\") } ") + compiler.compile( + "package com.b \n object Yammy { val name = Symbol(\"sam\") } " + ) val loc = compiler.locations.result().find(_._1 == "ValDef").get._2 loc.packageName shouldBe "com.b" loc.className shouldBe "Yammy" @@ -173,7 +202,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "for trait constructor body" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.b \n trait Wammy { val name = Symbol(\"sam\") } ") + compiler.compile( + "package com.b \n trait Wammy { val name = Symbol(\"sam\") } " + ) val loc = compiler.locations.result().find(_._1 == "ValDef").get._2 loc.packageName shouldBe "com.b" loc.className shouldBe "Wammy" @@ -187,7 +218,8 @@ class LocationTest extends AnyFreeSpec with Matchers { val compiler = ScoverageCompiler.locationCompiler compiler .compile( - "package com.a; object A { def foo(b : B) : Unit = b.invoke }; trait B { def invoke : Unit }; class C { A.foo(new B { def invoke = () }) }") + "package com.a; object A { def foo(b : B) : Unit = b.invoke }; trait B { def invoke : Unit }; class C { A.foo(new B { def invoke = () }) }" + ) val loc = compiler.locations.result().filter(_._1 == "Template").last._2 loc.packageName shouldBe "com.a" loc.className shouldBe "C" @@ -199,7 +231,8 @@ class LocationTest extends AnyFreeSpec with Matchers { "anon class implemented method should report enclosing method" in { val compiler = ScoverageCompiler.locationCompiler compiler.compile( - "package com.a; object A { def foo(b : B) : Unit = b.invoke }; trait B { def invoke : Unit }; class C { A.foo(new B { def invoke = () }) }") + "package com.a; object A { def foo(b : B) : Unit = b.invoke }; trait B { def invoke : Unit }; class C { A.foo(new B { def invoke = () }) }" + ) val loc = compiler.locations.result().filter(_._1 == "DefDef").last._2 loc.packageName shouldBe "com.a" loc.className shouldBe "C" @@ -210,7 +243,9 @@ class LocationTest extends AnyFreeSpec with Matchers { } "doubly nested classes should report correct fullClassName" in { val compiler = ScoverageCompiler.locationCompiler - compiler.compile("package com.a \n object Foo { object Boo { object Moo { val name = Symbol(\"sam\") } } }") + compiler.compile( + "package com.a \n object Foo { object Boo { object Moo { val name = Symbol(\"sam\") } } }" + ) val loc = compiler.locations.result().find(_._1 == "ValDef").get._2 loc.packageName shouldBe "com.a" loc.className shouldBe "Moo" diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/MacroSupport.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/MacroSupport.scala index 70fa4641..f79d7707 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/MacroSupport.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/MacroSupport.scala @@ -4,15 +4,17 @@ import java.io.File trait MacroSupport { - val macroContextPackageName: String = if (ScoverageCompiler.ShortScalaVersion == "2.10") { - "scala.reflect.macros" - } - else { - "scala.reflect.macros.blackbox" - } + val macroContextPackageName: String = + if (ScoverageCompiler.ShortScalaVersion == "2.10") { + "scala.reflect.macros" + } else { + "scala.reflect.macros.blackbox" + } val macroSupportDeps = Seq(testClasses) - private def testClasses: File = new File(s"./scalac-scoverage-plugin/target/scala-${ScoverageCompiler.ScalaVersion}/test-classes") + private def testClasses: File = new File( + s"./scalac-scoverage-plugin/target/scala-${ScoverageCompiler.ScalaVersion}/test-classes" + ) } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/PluginASTSupportTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/PluginASTSupportTest.scala index 6e52f9b9..ad8ed477 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/PluginASTSupportTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/PluginASTSupportTest.scala @@ -5,10 +5,10 @@ import org.scalatest.funsuite.AnyFunSuite /** @author Stephen Samuel */ class PluginASTSupportTest - extends AnyFunSuite - with OneInstancePerTest - with BeforeAndAfterEachTestData - with MacroSupport { + extends AnyFunSuite + with OneInstancePerTest + with BeforeAndAfterEachTestData + with MacroSupport { override protected def afterEach(testData: TestData): Unit = { val compiler = ScoverageCompiler.default @@ -18,127 +18,123 @@ class PluginASTSupportTest // https://github.com/scoverage/sbt-scoverage/issues/203 test("should support final val literals in traits") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( - """ - |trait TraitWithFinalVal { - | final val FOO = "Bar" - |} """.stripMargin) + compiler.compileCodeSnippet(""" + |trait TraitWithFinalVal { + | final val FOO = "Bar" + |} """.stripMargin) compiler.assertNoErrors() compiler.assertNMeasuredStatements(0) } test("should support final val literals in objects") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( - """ - |object TraitWithFinalVal { - | final val FOO = "Bar" - |} """.stripMargin) + compiler.compileCodeSnippet(""" + |object TraitWithFinalVal { + | final val FOO = "Bar" + |} """.stripMargin) compiler.assertNoErrors() compiler.assertNMeasuredStatements(0) } test("should support final val literals in classes") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( - """ - |class TraitWithFinalVal { - | final val FOO = "Bar" - |} """.stripMargin) + compiler.compileCodeSnippet(""" + |class TraitWithFinalVal { + | final val FOO = "Bar" + |} """.stripMargin) compiler.assertNoErrors() compiler.assertNMeasuredStatements(0) } test("should support final val blocks in traits") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( - """ - |trait TraitWithFinalVal { - | final val FOO = { println("boo"); "Bar" } - |} """.stripMargin) + compiler.compileCodeSnippet(""" + |trait TraitWithFinalVal { + | final val FOO = { println("boo"); "Bar" } + |} """.stripMargin) compiler.assertNoErrors() compiler.assertNMeasuredStatements(2) } test("should support final val blocks in objects") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( - """ - |object TraitWithFinalVal { - | final val FOO = { println("boo"); "Bar" } - |} """.stripMargin) + compiler.compileCodeSnippet(""" + |object TraitWithFinalVal { + | final val FOO = { println("boo"); "Bar" } + |} """.stripMargin) compiler.assertNoErrors() compiler.assertNMeasuredStatements(2) } test("should support final val blocks in classes") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( - """ - |class TraitWithFinalVal { - | final val FOO = { println("boo"); "Bar" } - |} """.stripMargin) + compiler.compileCodeSnippet(""" + |class TraitWithFinalVal { + | final val FOO = { println("boo"); "Bar" } + |} """.stripMargin) compiler.assertNoErrors() compiler.assertNMeasuredStatements(2) } test("scoverage component should ignore basic macros") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( s""" - | object MyMacro { - | import scala.language.experimental.macros - | import ${macroContextPackageName}.Context - | def test: Unit = macro testImpl - | def testImpl(c: Context): c.Expr[Unit] = { - | import c.universe._ - | reify { - | println("macro test") - | } - | } - |} """.stripMargin) + compiler.compileCodeSnippet(s""" + | object MyMacro { + | import scala.language.experimental.macros + | import ${macroContextPackageName}.Context + | def test: Unit = macro testImpl + | def testImpl(c: Context): c.Expr[Unit] = { + | import c.universe._ + | reify { + | println("macro test") + | } + | } + |} """.stripMargin) assert(!compiler.reporter.hasErrors) } test("scoverage component should ignore complex macros #11") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( s""" object ComplexMacro { - | - | import scala.language.experimental.macros - | import ${macroContextPackageName}.Context - | - | def debug(params: Any*): Unit = macro debugImpl - | - | def debugImpl(c: Context)(params: c.Expr[Any]*) = { - | import c.universe._ - | - | val trees = params map {param => (param.tree match { - | case Literal(Constant(_)) => reify { print(param.splice) } - | case _ => reify { - | val variable = c.Expr[String](Literal(Constant(show(param.tree)))).splice - | print(s"$$variable = $${param.splice}") - | } - | }).tree - | } - | - | val separators = (1 until trees.size).map(_ => (reify { print(", ") }).tree) :+ (reify { println() }).tree - | val treesWithSeparators = trees zip separators flatMap {p => List(p._1, p._2)} - | - | c.Expr[Unit](Block(treesWithSeparators.toList, Literal(Constant(())))) - | } - |} """.stripMargin) + compiler.compileCodeSnippet(s""" object ComplexMacro { + | + | import scala.language.experimental.macros + | import ${macroContextPackageName}.Context + | + | def debug(params: Any*): Unit = macro debugImpl + | + | def debugImpl(c: Context)(params: c.Expr[Any]*) = { + | import c.universe._ + | + | val trees = params map {param => (param.tree match { + | case Literal(Constant(_)) => reify { print(param.splice) } + | case _ => reify { + | val variable = c.Expr[String](Literal(Constant(show(param.tree)))).splice + | print(s"$$variable = $${param.splice}") + | } + | }).tree + | } + | + | val separators = (1 until trees.size).map(_ => (reify { print(", ") }).tree) :+ (reify { println() }).tree + | val treesWithSeparators = trees zip separators flatMap {p => List(p._1, p._2)} + | + | c.Expr[Unit](Block(treesWithSeparators.toList, Literal(Constant(())))) + | } + |} """.stripMargin) assert(!compiler.reporter.hasErrors) } // https://github.com/scoverage/scalac-scoverage-plugin/issues/32 test("exhaustive warnings should not be generated for @unchecked") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """object PartialMatchObject { - | def partialMatchExample(s: Option[String]): Unit = { - | (s: @unchecked) match { - | case Some(str) => println(str) - | } - | } - |} """.stripMargin) + compiler.compileCodeSnippet( + """object PartialMatchObject { + | def partialMatchExample(s: Option[String]): Unit = { + | (s: @unchecked) match { + | case Some(str) => println(str) + | } + | } + |} """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) } @@ -147,11 +143,11 @@ class PluginASTSupportTest ignore("macro range positions should not break plugin") { val compiler = ScoverageCompiler.default macroSupportDeps.foreach(compiler.addToClassPath(_)) - compiler.compileCodeSnippet( s"""import scoverage.macrosupport.Tester - | - |object MacroTest { - | Tester.test - |} """.stripMargin) + compiler.compileCodeSnippet(s"""import scoverage.macrosupport.Tester + | + |object MacroTest { + | Tester.test + |} """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) } @@ -159,26 +155,27 @@ class PluginASTSupportTest // https://github.com/scoverage/scalac-scoverage-plugin/issues/45 test("compile final vals in annotations") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """object Foo { - | final val foo = 1L - |} - |@SerialVersionUID(Foo.foo) - |case class Bar() - |""".stripMargin) + compiler.compileCodeSnippet("""object Foo { + | final val foo = 1L + |} + |@SerialVersionUID(Foo.foo) + |case class Bar() + |""".stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) } test("type param with default arg supported") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """class TypeTreeObjects { - | class Container { - | def typeParamAndDefaultArg[C](name: String = "sammy"): String = name - | } - | new Container().typeParamAndDefaultArg[Any]() - |} """.stripMargin) + compiler.compileCodeSnippet( + """class TypeTreeObjects { + | class Container { + | def typeParamAndDefaultArg[C](name: String = "sammy"): String = name + | } + | new Container().typeParamAndDefaultArg[Any]() + |} """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) } } - diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageScalaJsTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageScalaJsTest.scala index de04a6f8..bd601bca 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageScalaJsTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageScalaJsTest.scala @@ -1,16 +1,16 @@ package scoverage +import org.scalatest.BeforeAndAfterEachTestData +import org.scalatest.OneInstancePerTest import org.scalatest.funsuite.AnyFunSuite -import org.scalatest.{BeforeAndAfterEachTestData, OneInstancePerTest} -/** - * https://github.com/scoverage/scalac-scoverage-plugin/issues/196 - */ +/** https://github.com/scoverage/scalac-scoverage-plugin/issues/196 + */ class PluginCoverageScalaJsTest - extends AnyFunSuite - with OneInstancePerTest - with BeforeAndAfterEachTestData - with MacroSupport { + extends AnyFunSuite + with OneInstancePerTest + with BeforeAndAfterEachTestData + with MacroSupport { ignore("scoverage should ignore default undefined parameter") { val compiler = ScoverageCompiler.default @@ -19,7 +19,8 @@ class PluginCoverageScalaJsTest | |object JSONHelper { | def toJson(value: String): String = js.JSON.stringify(value) - |}""".stripMargin) + |}""".stripMargin + ) assert(!compiler.reporter.hasErrors) compiler.assertNMeasuredStatements(4) } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageTest.scala index de4ac3b2..481747f4 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageTest.scala @@ -1,23 +1,26 @@ package scoverage -import org.scalatest.{BeforeAndAfterEachTestData, OneInstancePerTest} +import org.scalatest.BeforeAndAfterEachTestData +import org.scalatest.OneInstancePerTest import org.scalatest.funsuite.AnyFunSuite /** @author Stephen Samuel */ class PluginCoverageTest - extends AnyFunSuite - with OneInstancePerTest - with BeforeAndAfterEachTestData - with MacroSupport { + extends AnyFunSuite + with OneInstancePerTest + with BeforeAndAfterEachTestData + with MacroSupport { test("scoverage should instrument default arguments with methods") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ object DefaultArgumentsObject { - | val defaultName = "world" - | def makeGreeting(name: String = defaultName): String = { - | "Hello, " + name - | } - |} """.stripMargin) + compiler.compileCodeSnippet( + """ object DefaultArgumentsObject { + | val defaultName = "world" + | def makeGreeting(name: String = defaultName): String = { + | "Hello, " + name + | } + |} """.stripMargin + ) assert(!compiler.reporter.hasErrors) // we expect: // instrumenting the default-param which becomes a method call invocation @@ -27,8 +30,9 @@ class PluginCoverageTest test("scoverage should skip macros") { val compiler = ScoverageCompiler.default - val code = if (ScoverageCompiler.ShortScalaVersion == "2.10") - """ + val code = + if (ScoverageCompiler.ShortScalaVersion == "2.10") + """ import scala.language.experimental.macros import scala.reflect.macros.Context object Impl { @@ -38,8 +42,8 @@ class PluginCoverageTest object Macros { def poly[T]: String = macro Impl.poly[T] }""" - else - s""" + else + s""" import scala.language.experimental.macros import scala.reflect.macros.blackbox.Context class Impl(val c: Context) { @@ -56,15 +60,15 @@ class PluginCoverageTest test("scoverage should instrument final vals") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ object FinalVals { - | final val name = { - | val name = "sammy" - | if (System.currentTimeMillis() > 0) { - | println(name) - | } - | } - | println(name) - |} """.stripMargin) + compiler.compileCodeSnippet(""" object FinalVals { + | final val name = { + | val name = "sammy" + | if (System.currentTimeMillis() > 0) { + | println(name) + | } + | } + | println(name) + |} """.stripMargin) assert(!compiler.reporter.hasErrors) // we should have 3 statements - initialising the val, executing println, and executing the parameter compiler.assertNMeasuredStatements(8) @@ -72,11 +76,11 @@ class PluginCoverageTest test("scoverage should not instrument the match as a statement") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ object A { - | System.currentTimeMillis() match { - | case x => println(x) - | } - |} """.stripMargin) + compiler.compileCodeSnippet(""" object A { + | System.currentTimeMillis() match { + | case x => println(x) + | } + |} """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) @@ -87,13 +91,13 @@ class PluginCoverageTest } test("scoverage should instrument match guards") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ object A { - | System.currentTimeMillis() match { - | case l if l < 1000 => println("a") - | case l if l > 1000 => println("b") - | case _ => println("c") - | } - |} """.stripMargin) + compiler.compileCodeSnippet(""" object A { + | System.currentTimeMillis() match { + | case l if l < 1000 => println("a") + | case l if l > 1000 => println("b") + | case _ => println("c") + | } + |} """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) @@ -105,12 +109,12 @@ class PluginCoverageTest test("scoverage should instrument non basic selector") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ trait A { - | def someValue = "sammy" - | def foo(a:String) = someValue match { - | case any => "yes" - | } - |} """.stripMargin) + compiler.compileCodeSnippet(""" trait A { + | def someValue = "sammy" + | def foo(a:String) = someValue match { + | case any => "yes" + | } + |} """.stripMargin) assert(!compiler.reporter.hasErrors) // should instrument: // the someValue method entry @@ -121,11 +125,13 @@ class PluginCoverageTest test("scoverage should instrument conditional selectors in a match") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ trait A { - | def foo(a:String) = (if (a == "hello") 1 else 2) match { - | case any => "yes" - | } - |} """.stripMargin) + compiler.compileCodeSnippet( + """ trait A { + | def foo(a:String) = (if (a == "hello") 1 else 2) match { + | case any => "yes" + | } + |} """.stripMargin + ) assert(!compiler.reporter.hasErrors) // should instrument: // the if clause, @@ -138,11 +144,15 @@ class PluginCoverageTest } // https://github.com/scoverage/sbt-scoverage/issues/16 - test("scoverage should instrument for-loops but not the generated scaffolding") { + test( + "scoverage should instrument for-loops but not the generated scaffolding" + ) { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ trait A { - | def print1(list: List[String]) = for (string: String <- list) println(string) - |} """.stripMargin) + compiler.compileCodeSnippet( + """ trait A { + | def print1(list: List[String]) = for (string: String <- list) println(string) + |} """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) // should instrument: @@ -155,10 +165,12 @@ class PluginCoverageTest test("scoverage should instrument for-loop guards") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """object A { - | def foo(list: List[String]) = for (string: String <- list if string.length > 5) - | println(string) - |} """.stripMargin) + compiler.compileCodeSnippet( + """object A { + | def foo(list: List[String]) = for (string: String <- list if string.length > 5) + | println(string) + |} """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) // should instrument: @@ -168,22 +180,26 @@ class PluginCoverageTest compiler.assertNMeasuredStatements(3) } - test("scoverage should correctly handle new with args (apply with list of args)") { + test( + "scoverage should correctly handle new with args (apply with list of args)" + ) { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ object A { - | new String(new String(new String)) - | } """.stripMargin) + compiler.compileCodeSnippet(""" object A { + | new String(new String(new String)) + | } """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) // should have 3 statements, one for each of the nested strings compiler.assertNMeasuredStatements(3) } - test("scoverage should correctly handle no args new (apply, empty list of args)") { + test( + "scoverage should correctly handle no args new (apply, empty list of args)" + ) { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ object A { - | new String - | } """.stripMargin) + compiler.compileCodeSnippet(""" object A { + | new String + | } """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) // should have 1. the apply that wraps the select. @@ -192,10 +208,12 @@ class PluginCoverageTest test("scoverage should correctly handle new that invokes nested statements") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ - | object A { - | val value = new java.util.concurrent.CountDownLatch(if (System.currentTimeMillis > 1) 5 else 10) - | } """.stripMargin) + compiler.compileCodeSnippet( + """ + | object A { + | val value = new java.util.concurrent.CountDownLatch(if (System.currentTimeMillis > 1) 5 else 10) + | } """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) // should have 6 statements - the apply/new statement, two literals, the if cond, if elsep, if thenp @@ -204,9 +222,9 @@ class PluginCoverageTest test("scoverage should instrument val RHS") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """object A { - | val name = BigDecimal(50.0) - |} """.stripMargin) + compiler.compileCodeSnippet("""object A { + | val name = BigDecimal(50.0) + |} """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) compiler.assertNMeasuredStatements(1) @@ -227,7 +245,8 @@ class PluginCoverageTest | case (_, _) => false | } | } - """.stripMargin) + """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) @@ -238,13 +257,13 @@ class PluginCoverageTest test("scoverage should instrument all case statements in an explicit match") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ trait A { - | def foo(name: Any) = name match { - | case i : Int => 1 - | case b : Boolean => println("boo") - | case _ => 3 - | } - |} """.stripMargin) + compiler.compileCodeSnippet(""" trait A { + | def foo(name: Any) = name match { + | case i : Int => 1 + | case b : Boolean => println("boo") + | case _ => 3 + | } + |} """.stripMargin) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) // should have one statement for each case body @@ -254,23 +273,26 @@ class PluginCoverageTest test("plugin should support yields") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( """ - | object Yielder { - | val holidays = for ( name <- Seq("sammy", "clint", "lee"); - | place <- Seq("london", "philly", "iowa") ) yield { - | name + " has been to " + place - | } - | }""".stripMargin) + compiler.compileCodeSnippet( + """ + | object Yielder { + | val holidays = for ( name <- Seq("sammy", "clint", "lee"); + | place <- Seq("london", "philly", "iowa") ) yield { + | name + " has been to " + place + | } + | }""".stripMargin + ) assert(!compiler.reporter.hasErrors) // 2 statements for the two applies in Seq, one for each literal which is 6, one for the operation passed to yield. // Depending on the collections api version, there can be additional implicit canBuildFrom statements. - val expectedStatementsCount = if (ScoverageCompiler.ShortScalaVersion < "2.13") 11 else 9 + val expectedStatementsCount = + if (ScoverageCompiler.ShortScalaVersion < "2.13") 11 else 9 compiler.assertNMeasuredStatements(expectedStatementsCount) } test("plugin should not instrument local macro implementation") { val compiler = ScoverageCompiler.default - compiler.compileCodeSnippet( s""" + compiler.compileCodeSnippet(s""" | object MyMacro { | import scala.language.experimental.macros | import ${macroContextPackageName}.Context @@ -286,10 +308,12 @@ class PluginCoverageTest compiler.assertNoCoverage() } - ignore("plugin should not instrument expanded macro code http://github.com/skinny-framework/skinny-framework/issues/97") { + ignore( + "plugin should not instrument expanded macro code http://github.com/skinny-framework/skinny-framework/issues/97" + ) { val compiler = ScoverageCompiler.default macroSupportDeps.foreach(compiler.addToClassPath(_)) - compiler.compileCodeSnippet( s"""import scoverage.macrosupport.Tester + compiler.compileCodeSnippet(s"""import scoverage.macrosupport.Tester | |class MacroTest { | Tester.test @@ -299,7 +323,9 @@ class PluginCoverageTest compiler.assertNoCoverage() } - ignore("plugin should handle return inside catch github.com/scoverage/scalac-scoverage-plugin/issues/93") { + ignore( + "plugin should handle return inside catch github.com/scoverage/scalac-scoverage-plugin/issues/93" + ) { val compiler = ScoverageCompiler.default compiler.compileCodeSnippet( """ @@ -318,7 +344,8 @@ class PluginCoverageTest | } | def recover(it: Boolean): Boolean = it | } - """.stripMargin) + """.stripMargin + ) assert(!compiler.reporter.hasErrors) assert(!compiler.reporter.hasWarnings) compiler.assertNMeasuredStatements(11) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala index d371a3ad..a7b24af5 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala @@ -1,8 +1,10 @@ package scoverage - -import scala.reflect.internal.util.{BatchSourceFile, NoFile, SourceFile} +import scala.reflect.internal.util.BatchSourceFile +import scala.reflect.internal.util.NoFile +import scala.reflect.internal.util.SourceFile import scala.reflect.io.VirtualFile + import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers @@ -19,23 +21,38 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { } "should exclude scoverage -> scoverage" in { - assert(!new RegexCoverageFilter(Seq("scoverage"), Nil, Nil).isClassIncluded("scoverage")) + assert( + !new RegexCoverageFilter(Seq("scoverage"), Nil, Nil) + .isClassIncluded("scoverage") + ) } "should include scoverage -> scoverageeee" in { - assert(new RegexCoverageFilter(Seq("scoverage"), Nil, Nil).isClassIncluded("scoverageeee")) + assert( + new RegexCoverageFilter(Seq("scoverage"), Nil, Nil) + .isClassIncluded("scoverageeee") + ) } "should exclude scoverage* -> scoverageeee" in { - assert(!new RegexCoverageFilter(Seq("scoverage*"), Nil, Nil).isClassIncluded("scoverageeee")) + assert( + !new RegexCoverageFilter(Seq("scoverage*"), Nil, Nil) + .isClassIncluded("scoverageeee") + ) } "should include eee -> scoverageeee" in { - assert(new RegexCoverageFilter(Seq("eee"), Nil, Nil).isClassIncluded("scoverageeee")) + assert( + new RegexCoverageFilter(Seq("eee"), Nil, Nil) + .isClassIncluded("scoverageeee") + ) } "should exclude .*eee -> scoverageeee" in { - assert(!new RegexCoverageFilter(Seq(".*eee"), Nil, Nil).isClassIncluded("scoverageeee")) + assert( + !new RegexCoverageFilter(Seq(".*eee"), Nil, Nil) + .isClassIncluded("scoverageeee") + ) } } "isFileIncluded" - { @@ -46,15 +63,18 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { } "should exclude by filename" in { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) - new RegexCoverageFilter(Nil, Seq("sammy"), Nil).isFileIncluded(file) shouldBe false + new RegexCoverageFilter(Nil, Seq("sammy"), Nil) + .isFileIncluded(file) shouldBe false } "should exclude by regex wildcard" in { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) - new RegexCoverageFilter(Nil, Seq("sam.*"), Nil).isFileIncluded(file) shouldBe false + new RegexCoverageFilter(Nil, Seq("sam.*"), Nil) + .isFileIncluded(file) shouldBe false } "should not exclude non matching regex" in { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) - new RegexCoverageFilter(Nil, Seq("qweqeqwe"), Nil).isFileIncluded(file) shouldBe true + new RegexCoverageFilter(Nil, Seq("qweqeqwe"), Nil) + .isFileIncluded(file) shouldBe true } } "isSymbolIncluded" - { @@ -68,31 +88,55 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { } "should exclude scoverage -> scoverage" in { - assert(!new RegexCoverageFilter(Nil, Nil, Seq("scoverage")).isSymbolIncluded("scoverage")) + assert( + !new RegexCoverageFilter(Nil, Nil, Seq("scoverage")) + .isSymbolIncluded("scoverage") + ) } "should include scoverage -> scoverageeee" in { - assert(new RegexCoverageFilter(Nil, Nil, Seq("scoverage")).isSymbolIncluded("scoverageeee")) + assert( + new RegexCoverageFilter(Nil, Nil, Seq("scoverage")) + .isSymbolIncluded("scoverageeee") + ) } "should exclude scoverage* -> scoverageeee" in { - assert(!new RegexCoverageFilter(Nil, Nil, Seq("scoverage*")).isSymbolIncluded("scoverageeee")) + assert( + !new RegexCoverageFilter(Nil, Nil, Seq("scoverage*")) + .isSymbolIncluded("scoverageeee") + ) } "should include eee -> scoverageeee" in { - assert(new RegexCoverageFilter(Nil, Nil, Seq("eee")).isSymbolIncluded("scoverageeee")) + assert( + new RegexCoverageFilter(Nil, Nil, Seq("eee")) + .isSymbolIncluded("scoverageeee") + ) } "should exclude .*eee -> scoverageeee" in { - assert(!new RegexCoverageFilter(Nil, Nil, Seq(".*eee")).isSymbolIncluded("scoverageeee")) + assert( + !new RegexCoverageFilter(Nil, Nil, Seq(".*eee")) + .isSymbolIncluded("scoverageeee") + ) } "should exclude scala.reflect.api.Exprs.Expr" in { - assert(!new RegexCoverageFilter(Nil, Nil, options.excludedSymbols).isSymbolIncluded("scala.reflect.api.Exprs.Expr")) + assert( + !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols) + .isSymbolIncluded("scala.reflect.api.Exprs.Expr") + ) } "should exclude scala.reflect.macros.Universe.Tree" in { - assert(!new RegexCoverageFilter(Nil, Nil, options.excludedSymbols).isSymbolIncluded("scala.reflect.macros.Universe.Tree")) + assert( + !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols) + .isSymbolIncluded("scala.reflect.macros.Universe.Tree") + ) } "should exclude scala.reflect.api.Trees.Tree" in { - assert(!new RegexCoverageFilter(Nil, Nil, options.excludedSymbols).isSymbolIncluded("scala.reflect.api.Trees.Tree")) + assert( + !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols) + .isSymbolIncluded("scala.reflect.api.Trees.Tree") + ) } } "getExcludedLineNumbers" - { @@ -108,7 +152,8 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { |8 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil).getExcludedLineNumbers(mockSourceFile(file)) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + .getExcludedLineNumbers(mockSourceFile(file)) numbers === List.empty } "should exclude lines between magic comments" in { @@ -131,7 +176,8 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { |16 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil).getExcludedLineNumbers(mockSourceFile(file)) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + .getExcludedLineNumbers(mockSourceFile(file)) numbers === List(Range(4, 9), Range(12, 14)) } "should exclude all lines after an upaired magic comment" in { @@ -153,7 +199,8 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { |15 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil).getExcludedLineNumbers(mockSourceFile(file)) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + .getExcludedLineNumbers(mockSourceFile(file)) numbers === List(Range(4, 9), Range(12, 16)) } "should allow text comments on the same line as the markers" in { @@ -175,7 +222,8 @@ class RegexCoverageFilterTest extends AnyFreeSpec with Matchers { |15 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil).getExcludedLineNumbers(mockSourceFile(file)) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + .getExcludedLineNumbers(mockSourceFile(file)) numbers === List(Range(4, 9), Range(12, 16)) } } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala index df19aa4a..f1e0fe0b 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala @@ -1,23 +1,28 @@ package scoverage -import java.io.{File, FileNotFoundException} +import java.io.File +import java.io.FileNotFoundException import java.net.URL import scala.collection.mutable.ListBuffer -import scala.tools.nsc.{Global, Settings} +import scala.tools.nsc.Global +import scala.tools.nsc.Settings import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.transform.{Transform, TypingTransformers} +import scala.tools.nsc.transform.Transform +import scala.tools.nsc.transform.TypingTransformers object ScoverageCompiler { val ScalaVersion: String = scala.util.Properties.versionNumberString val ShortScalaVersion: String = (ScalaVersion split "[.]").toList match { case init :+ last if last forall (_.isDigit) => init mkString "." - case _ => ScalaVersion + case _ => ScalaVersion } def classPath: Seq[String] = - getScalaJars.map(_.getAbsolutePath) :+ sbtCompileDir.getAbsolutePath :+ runtimeClasses.getAbsolutePath + getScalaJars.map( + _.getAbsolutePath + ) :+ sbtCompileDir.getAbsolutePath :+ runtimeClasses.getAbsolutePath def settings: Settings = { val s = new scala.tools.nsc.Settings @@ -27,7 +32,8 @@ object ScoverageCompiler { s.Yposdebug.value = true s.classpath.value = classPath.mkString(File.pathSeparator) - val path = s"./scalac-scoverage-plugin/target/scala-$ScalaVersion/test-generated-classes" + val path = + s"./scalac-scoverage-plugin/target/scala-$ScalaVersion/test-generated-classes" new File(path).mkdirs() s.outdir.value = path s @@ -49,52 +55,76 @@ object ScoverageCompiler { } private def sbtCompileDir: File = { - val dir = new File(s"./scalac-scoverage-plugin/target/scala-$ScalaVersion/classes") + val dir = new File( + s"./scalac-scoverage-plugin/target/scala-$ScalaVersion/classes" + ) if (!dir.exists) - throw new FileNotFoundException(s"Could not locate SBT compile directory for plugin files [$dir]") + throw new FileNotFoundException( + s"Could not locate SBT compile directory for plugin files [$dir]" + ) dir } - private def runtimeClasses: File = new File(s"./scalac-scoverage-runtime/jvm/target/scala-$ScalaVersion/classes") + private def runtimeClasses: File = new File( + s"./scalac-scoverage-runtime/jvm/target/scala-$ScalaVersion/classes" + ) private def findScalaJar(artifactId: String): File = findIvyJar("org.scala-lang", artifactId, ScalaVersion) .orElse(findCoursierJar(artifactId, ScalaVersion)) .getOrElse { - throw new FileNotFoundException(s"Could not locate $artifactId/$ScalaVersion") + throw new FileNotFoundException( + s"Could not locate $artifactId/$ScalaVersion" + ) } - private def findCoursierJar(artifactId: String, version: String): Option[File] = { + private def findCoursierJar( + artifactId: String, + version: String + ): Option[File] = { val userHome = System.getProperty("user.home") val jarPaths = Seq( s"$userHome/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/$artifactId/$version/$artifactId-$version.jar", - s"$userHome/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/$artifactId/$version/$artifactId-$version.jar", + s"$userHome/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/$artifactId/$version/$artifactId-$version.jar" ) jarPaths.map(new File(_)).find(_.exists()) } - private def findIvyJar(groupId: String, artifactId: String, version: String, packaging: String = "jar"): Option[File] = { + private def findIvyJar( + groupId: String, + artifactId: String, + version: String, + packaging: String = "jar" + ): Option[File] = { val userHome = System.getProperty("user.home") - val jarPath = s"$userHome/.ivy2/cache/$groupId/$artifactId/${packaging}s/$artifactId-$version.jar" + val jarPath = + s"$userHome/.ivy2/cache/$groupId/$artifactId/${packaging}s/$artifactId-$version.jar" val file = new File(jarPath) if (file.exists()) Some(file) else None } } -class ScoverageCompiler(settings: scala.tools.nsc.Settings, rep: scala.tools.nsc.reporters.Reporter) - extends scala.tools.nsc.Global(settings, rep) { +class ScoverageCompiler( + settings: scala.tools.nsc.Settings, + rep: scala.tools.nsc.reporters.Reporter +) extends scala.tools.nsc.Global(settings, rep) { def addToClassPath(file: File): Unit = { - settings.classpath.value = settings.classpath.value + File.pathSeparator + file.getAbsolutePath + settings.classpath.value = + settings.classpath.value + File.pathSeparator + file.getAbsolutePath } - val instrumentationComponent = new ScoverageInstrumentationComponent(this, None, None) + val instrumentationComponent = + new ScoverageInstrumentationComponent(this, None, None) instrumentationComponent.setOptions(new ScoverageOptions()) val testStore = new ScoverageTestStoreComponent(this) val validator = new PositionValidator(this) def compileSourceFiles(files: File*): ScoverageCompiler = { - val command = new scala.tools.nsc.CompilerCommand(files.map(_.getAbsolutePath).toList, settings) + val command = new scala.tools.nsc.CompilerCommand( + files.map(_.getAbsolutePath).toList, + settings + ) new Run().compile(command.files) this } @@ -106,33 +136,52 @@ class ScoverageCompiler(settings: scala.tools.nsc.Settings, rep: scala.tools.nsc file } - def compileCodeSnippet(code: String): ScoverageCompiler = compileSourceFiles(writeCodeSnippetToTempFile(code)) + def compileCodeSnippet(code: String): ScoverageCompiler = compileSourceFiles( + writeCodeSnippetToTempFile(code) + ) def compileSourceResources(urls: URL*): ScoverageCompiler = { compileSourceFiles(urls.map(_.getFile).map(new File(_)): _*) } - def assertNoErrors() = assert(!reporter.hasErrors, "There are compilation errors") + def assertNoErrors() = + assert(!reporter.hasErrors, "There are compilation errors") - def assertNoCoverage() = assert(!testStore.sources.mkString(" ").contains(s"scoverage.Invoker.invoked"), - "There are scoverage.Invoker.invoked instructions added to the code") + def assertNoCoverage() = assert( + !testStore.sources.mkString(" ").contains(s"scoverage.Invoker.invoked"), + "There are scoverage.Invoker.invoked instructions added to the code" + ) def assertNMeasuredStatements(n: Int): Unit = { for (k <- 1 to n) { - assert(testStore.sources.mkString(" ").contains(s"scoverage.Invoker.invoked($k,"), - s"Should be $n invoked statements but missing #$k") + assert( + testStore.sources + .mkString(" ") + .contains(s"scoverage.Invoker.invoked($k,"), + s"Should be $n invoked statements but missing #$k" + ) } - assert(!testStore.sources.mkString(" ").contains(s"scoverage.Invoker.invoked(${n + 1},"), - s"Found statement ${n + 1} but only expected $n") + assert( + !testStore.sources + .mkString(" ") + .contains(s"scoverage.Invoker.invoked(${n + 1},"), + s"Found statement ${n + 1} but only expected $n" + ) } - class PositionValidator(val global: Global) extends PluginComponent with TypingTransformers with Transform { + class PositionValidator(val global: Global) + extends PluginComponent + with TypingTransformers + with Transform { override val phaseName = "scoverage-validator" override val runsAfter = List("typer") override val runsBefore = List("scoverage-instrumentation") - override protected def newTransformer(unit: global.CompilationUnit): global.Transformer = new Transformer(unit) - class Transformer(unit: global.CompilationUnit) extends TypingTransformer(unit) { + override protected def newTransformer( + unit: global.CompilationUnit + ): global.Transformer = new Transformer(unit) + class Transformer(unit: global.CompilationUnit) + extends TypingTransformer(unit) { override def transform(tree: global.Tree) = { global.validatePositions(tree) @@ -141,7 +190,10 @@ class ScoverageCompiler(settings: scala.tools.nsc.Settings, rep: scala.tools.nsc } } - class ScoverageTestStoreComponent(val global: Global) extends PluginComponent with TypingTransformers with Transform { + class ScoverageTestStoreComponent(val global: Global) + extends PluginComponent + with TypingTransformers + with Transform { val sources = new ListBuffer[String] @@ -149,8 +201,11 @@ class ScoverageCompiler(settings: scala.tools.nsc.Settings, rep: scala.tools.nsc override val runsAfter = List("jvm") override val runsBefore = List("terminal") - override protected def newTransformer(unit: global.CompilationUnit): global.Transformer = new Transformer(unit) - class Transformer(unit: global.CompilationUnit) extends TypingTransformer(unit) { + override protected def newTransformer( + unit: global.CompilationUnit + ): global.Transformer = new Transformer(unit) + class Transformer(unit: global.CompilationUnit) + extends TypingTransformer(unit) { override def transform(tree: global.Tree) = { sources += tree.toString @@ -162,9 +217,10 @@ class ScoverageCompiler(settings: scala.tools.nsc.Settings, rep: scala.tools.nsc override def computeInternalPhases(): Unit = { super.computeInternalPhases() addToPhasesSet(validator, "scoverage validator") - addToPhasesSet(instrumentationComponent, "scoverage instrumentationComponent") + addToPhasesSet( + instrumentationComponent, + "scoverage instrumentationComponent" + ) addToPhasesSet(testStore, "scoverage teststore") } } - - diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala index e8f839b8..3c0e57aa 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala @@ -2,17 +2,18 @@ package scoverage import java.io._ import java.util.UUID -import scala.io.Source +import scala.io.Source import scala.xml.XML -import scoverage.report.ScoverageHtmlWriter - import org.scalatest.funsuite.AnyFunSuite +import scoverage.report.ScoverageHtmlWriter class ScoverageHtmlWriterTest extends AnyFunSuite { - val rootDirForClasses = new File(getClass.getResource("forHtmlWriter/src/main/scala/").getFile) + val rootDirForClasses = new File( + getClass.getResource("forHtmlWriter/src/main/scala/").getFile + ) def pathToClassFile(classLocation: String): String = new File(rootDirForClasses, classLocation).getCanonicalPath @@ -22,17 +23,62 @@ class ScoverageHtmlWriterTest extends AnyFunSuite { val pathToClassInMainDir = pathToClassFile("ClassInMainDir.scala") val statementForClassContainingHtml = Statement( - Location("coverage.sample", "ClassContainingHtml", "ClassContainingHtml", ClassType.Class, "some_html", pathToClassContainingHtml), - 3, 74, 97, 4, "
HTML content
", - "scala.Predef.println", "Apply", false, 0) + Location( + "coverage.sample", + "ClassContainingHtml", + "ClassContainingHtml", + ClassType.Class, + "some_html", + pathToClassContainingHtml + ), + 3, + 74, + 97, + 4, + "
HTML content
", + "scala.Predef.println", + "Apply", + false, + 0 + ) val statementForClassInSubDir = Statement( - Location("coverage.sample", "ClassInSubDir", "ClassInSubDir", ClassType.Class, "msg_test", pathToClassInSubDir), - 2, 64, 84, 4, "scala.this.Predef.println(\"test code\")", - "scala.Predef.println", "Apply", false, 0) + Location( + "coverage.sample", + "ClassInSubDir", + "ClassInSubDir", + ClassType.Class, + "msg_test", + pathToClassInSubDir + ), + 2, + 64, + 84, + 4, + "scala.this.Predef.println(\"test code\")", + "scala.Predef.println", + "Apply", + false, + 0 + ) val statementForClassInMainDir = Statement( - Location("coverage.sample", "ClassInMainDir", "ClassInMainDir", ClassType.Class, "msg_coverage", pathToClassInMainDir), - 1, 69, 104, 4, "scala.this.Predef.println(\"measure coverage of code\")", - "scala.Predef.println", "Apply", false, 0) + Location( + "coverage.sample", + "ClassInMainDir", + "ClassInMainDir", + ClassType.Class, + "msg_coverage", + pathToClassInMainDir + ), + 1, + 69, + 104, + 4, + "scala.this.Predef.println(\"measure coverage of code\")", + "scala.Predef.println", + "Apply", + false, + 0 + ) def createTemporaryDir(): File = { val dir = new File(IOUtils.getTempDirectory, UUID.randomUUID.toString) @@ -59,15 +105,22 @@ class ScoverageHtmlWriterTest extends AnyFunSuite { val htmls = List("overview.html", "coverage.sample.html") for (html <- htmls) { - val xml = XML.loadString(Source.fromFile(new File(outputDir, html)).getLines().mkString) + val xml = XML.loadString( + Source.fromFile(new File(outputDir, html)).getLines().mkString + ) val links = for (node <- xml \\ "a") yield { node.attribute("href") match { case Some(url) => url.toString - case None => fail() + case None => fail() } } - - assert( links.toSet == Set("ClassInMainDir.scala.html", "subdir/ClassInSubDir.scala.html") ) + + assert( + links.toSet == Set( + "ClassInMainDir.scala.html", + "subdir/ClassInSubDir.scala.html" + ) + ) } } @@ -77,8 +130,15 @@ class ScoverageHtmlWriterTest extends AnyFunSuite { coverage.add(statementForClassContainingHtml) val outputDir = writeCoverageToTemporaryDir(coverage) - val contentsOfFileWithEmbeddedHtml = Source.fromFile(new File(outputDir, "ClassContainingHtml.scala.html")).getLines().mkString - assert( !contentsOfFileWithEmbeddedHtml.contains("
HTML content
") ) - assert( contentsOfFileWithEmbeddedHtml.contains("<div>HTML content</div>") ) + val contentsOfFileWithEmbeddedHtml = Source + .fromFile(new File(outputDir, "ClassContainingHtml.scala.html")) + .getLines() + .mkString + assert(!contentsOfFileWithEmbeddedHtml.contains("
HTML content
")) + assert( + contentsOfFileWithEmbeddedHtml.contains( + "<div>HTML content</div>" + ) + ) } } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala index 3c98a920..ea010c8f 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala @@ -11,96 +11,128 @@ class SerializerTest extends AnyFunSuite with OneInstancePerTest { val coverage = Coverage() coverage.add( Statement( - Location("org.scoverage", "test", "org.scoverage.test", ClassType.Trait, "mymethod", "mypath"), - 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 1 + Location( + "org.scoverage", + "test", + "org.scoverage.test", + ClassType.Trait, + "mymethod", + "mypath" + ), + 14, + 100, + 200, + 4, + "def test : String", + "test", + "DefDef", + true, + 1 ) ) val expected = s"""# Coverage data, format version: 2.0 - |# Statement data: - |# - id - |# - source path - |# - package name - |# - class name - |# - class type (Class, Object or Trait) - |# - full class name - |# - method name - |# - start offset - |# - end offset - |# - line number - |# - symbol name - |# - tree name - |# - is branch - |# - invocations count - |# - is ignored - |# - description (can be multi-line) - |# '\f' sign - |# ------------------------------------------ - |14 - |mypath - |org.scoverage - |test - |Trait - |org.scoverage.test - |mymethod - |100 - |200 - |4 - |test - |DefDef - |true - |1 - |false - |def test : String - |\f - |""".stripMargin - val writer = new StringWriter()//TODO-use UTF-8 + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |14 + |mypath + |org.scoverage + |test + |Trait + |org.scoverage.test + |mymethod + |100 + |200 + |4 + |test + |DefDef + |true + |1 + |false + |def test : String + |\f + |""".stripMargin + val writer = new StringWriter() //TODO-use UTF-8 val actual = Serializer.serialize(coverage, writer) assert(expected === writer.toString) } test("coverage should be deserializable from plain text") { val input = s"""# Coverage data, format version: 2.0 - |# Statement data: - |# - id - |# - source path - |# - package name - |# - class name - |# - class type (Class, Object or Trait) - |# - full class name - |# - method name - |# - start offset - |# - end offset - |# - line number - |# - symbol name - |# - tree name - |# - is branch - |# - invocations count - |# - is ignored - |# - description (can be multi-line) - |# '\f' sign - |# ------------------------------------------ - |14 - |mypath - |org.scoverage - |test - |Trait - |org.scoverage.test - |mymethod - |100 - |200 - |4 - |test - |DefDef - |true - |1 - |false - |def test : String - |\f - |""".stripMargin.split("\n").iterator - val statements = List(Statement( - Location("org.scoverage", "test", "org.scoverage.test", ClassType.Trait, "mymethod", "mypath"), - 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 1 - )) + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |14 + |mypath + |org.scoverage + |test + |Trait + |org.scoverage.test + |mymethod + |100 + |200 + |4 + |test + |DefDef + |true + |1 + |false + |def test : String + |\f + |""".stripMargin.split("\n").iterator + val statements = List( + Statement( + Location( + "org.scoverage", + "test", + "org.scoverage.test", + ClassType.Trait, + "mymethod", + "mypath" + ), + 14, + 100, + 200, + 4, + "def test : String", + "test", + "DefDef", + true, + 1 + ) + ) val coverage = Serializer.deserialize(input) assert(statements === coverage.statements.toList) } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/macrosupport/Tester.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/macrosupport/Tester.scala index 78386a68..e4e79c5b 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/macrosupport/Tester.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/macrosupport/Tester.scala @@ -1,7 +1,5 @@ package scoverage.macrosupport -import scala.language.experimental.macros - object Tester { // def test: Unit = macro TesterMacro.test diff --git a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/File.scala b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/File.scala index 23c3d1b8..b6eddabe 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/File.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/File.scala @@ -2,10 +2,9 @@ package scalajssupport import scala.scalajs.js -/** - * This wraps RhinoFile, NodeFile, or PhantomFile depending on which javascript - * environment is being used, and emulates a subset of the java.io.File API. - */ +/** This wraps RhinoFile, NodeFile, or PhantomFile depending on which javascript + * environment is being used, and emulates a subset of the java.io.File API. + */ class File(path: String) { import File._ @@ -67,12 +66,13 @@ object File { } } - val jsFile: JsFileObject = if (globalObject.hasOwnProperty("Packages").asInstanceOf[Boolean]) - RhinoFile - else if (globalObject.hasOwnProperty("callPhantom").asInstanceOf[Boolean]) - PhantomFile - else - NodeFile + val jsFile: JsFileObject = + if (globalObject.hasOwnProperty("Packages").asInstanceOf[Boolean]) + RhinoFile + else if (globalObject.hasOwnProperty("callPhantom").asInstanceOf[Boolean]) + PhantomFile + else + NodeFile // Factorize this def pathJoin(path: String, child: String): String = @@ -80,4 +80,4 @@ object File { def write(path: String, data: String, mode: String = "a") = jsFile.write(path, data, mode) -} \ No newline at end of file +} diff --git a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/FileWriter.scala b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/FileWriter.scala index dc01ec6f..04dde5a7 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/FileWriter.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/FileWriter.scala @@ -1,8 +1,7 @@ package scalajssupport -/** - * Emulates a subset of the java.io.FileWriter API required for scoverage to work. - */ +/** Emulates a subset of the java.io.FileWriter API required for scoverage to work. + */ class FileWriter(file: File, append: Boolean) { def this(file: File) = this(file, false) def this(file: String) = this(new File(file), false) diff --git a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/NodeFile.scala b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/NodeFile.scala index 7fb186ab..467e1aaf 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/NodeFile.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/NodeFile.scala @@ -34,15 +34,17 @@ class NodeFile(path: String) extends JsFile { } def mkdirs(): Unit = { - path.split("/").foldLeft("")((acc: String, x: String) => { - val new_acc = NodeFile.nodePath.join(acc, x) - try { - NodeFile.fs.mkdirSync(new_acc) - } catch { - case e: Exception => - } - new_acc - }) + path + .split("/") + .foldLeft("")((acc: String, x: String) => { + val new_acc = NodeFile.nodePath.join(acc, x) + try { + NodeFile.fs.mkdirSync(new_acc) + } catch { + case e: Exception => + } + new_acc + }) } def listFiles(): Array[File] = { @@ -76,7 +78,11 @@ trait FS extends js.Object { def readFileSync(path: String, options: js.Dynamic): String = js.native def rmdirSync(path: String): Unit = js.native def unlinkSync(path: String): Unit = js.native - def writeFileSync(path: String, data: String, options: js.Dynamic = js.Dynamic.literal()): Unit = js.native + def writeFileSync( + path: String, + data: String, + options: js.Dynamic = js.Dynamic.literal() + ): Unit = js.native } @js.native @@ -87,7 +93,8 @@ trait NodePath extends js.Object { private[scalajssupport] object NodeFile extends JsFileObject { val fs: FS = js.Dynamic.global.require("fs").asInstanceOf[FS] - val nodePath: NodePath = js.Dynamic.global.require("path").asInstanceOf[NodePath] + val nodePath: NodePath = + js.Dynamic.global.require("path").asInstanceOf[NodePath] def write(path: String, data: String, mode: String = "a") = { fs.writeFileSync(path, data, js.Dynamic.literal(flag = mode)) } @@ -99,4 +106,4 @@ private[scalajssupport] object NodeFile extends JsFileObject { def apply(path: String) = { new NodeFile(path) } -} \ No newline at end of file +} diff --git a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/PhantomFile.scala b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/PhantomFile.scala index 1fe92baf..7663aa2f 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/PhantomFile.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/PhantomFile.scala @@ -50,11 +50,13 @@ class PhantomFile(path: String) extends JsFile { private[scalajssupport] object PhantomFile extends JsFileObject { def fsCallArray(method: String, args: js.Array[js.Any]): js.Dynamic = { - val d = js.Dynamic.global.callPhantom(js.Dynamic.literal( - action = "require.fs", - method = method, - args = args - )) + val d = js.Dynamic.global.callPhantom( + js.Dynamic.literal( + action = "require.fs", + method = method, + args = args + ) + ) JSON.parse(d.asInstanceOf[String]) } @@ -63,18 +65,25 @@ private[scalajssupport] object PhantomFile extends JsFileObject { } - def absolute(path: String): String = fsCall("absolute", path).asInstanceOf[String] - def isDirectory(path: String): Boolean = fsCall("isDirectory", path).asInstanceOf[Boolean] - def list(path: String): js.Array[String] = fsCall("list", path).asInstanceOf[js.Array[String]] - def makeTree(path: String): Boolean = fsCall("makeTree", path).asInstanceOf[Boolean] + def absolute(path: String): String = + fsCall("absolute", path).asInstanceOf[String] + def isDirectory(path: String): Boolean = + fsCall("isDirectory", path).asInstanceOf[Boolean] + def list(path: String): js.Array[String] = + fsCall("list", path).asInstanceOf[js.Array[String]] + def makeTree(path: String): Boolean = + fsCall("makeTree", path).asInstanceOf[Boolean] def read(path: String): String = fsCall("read", path).asInstanceOf[String] - def remove(path: String): Boolean = fsCall("remove", path).asInstanceOf[Boolean] - def removeDirectory(path: String): Boolean = fsCall("removeDirectory", path).asInstanceOf[Boolean] + def remove(path: String): Boolean = + fsCall("remove", path).asInstanceOf[Boolean] + def removeDirectory(path: String): Boolean = + fsCall("removeDirectory", path).asInstanceOf[Boolean] val separator: String = fsCall("separator").asInstanceOf[String] - def write(path: String, content: String, mode: String): Unit = fsCallArray("write", js.Array(path, content, mode)) + def write(path: String, content: String, mode: String): Unit = + fsCallArray("write", js.Array(path, content, mode)) def pathJoin(path: String, child: String): String = path + separator + child def apply(path: String) = { new PhantomFile(path) } -} \ No newline at end of file +} diff --git a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/RhinoFile.scala b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/RhinoFile.scala index 5207a376..1d2d5f77 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/RhinoFile.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/RhinoFile.scala @@ -1,10 +1,10 @@ package scalajssupport import scala.scalajs.js +import scala.scalajs.js.Dynamic.{global => g} +import scala.scalajs.js.Dynamic.{newInstance => jsnew} import scala.scalajs.js.annotation.JSGlobal -import js.Dynamic.{ global => g, newInstance => jsnew } - @JSGlobal("Packages.java.io.File") @js.native class NativeRhinoFile(path: String, child: String) extends js.Object { @@ -41,7 +41,8 @@ class RhinoFile(_file: NativeRhinoFile) extends JsFile { def getName(): String = "" + _file.getName() def getPath(): String = { - "" + _file.getPath() // Rhino bug: doesn't seem to actually returns a string, we have to convert it ourselves + "" + _file + .getPath() // Rhino bug: doesn't seem to actually returns a string, we have to convert it ourselves } def isDirectory(): Boolean = _file.isDirectory() @@ -60,7 +61,8 @@ class RhinoFile(_file: NativeRhinoFile) extends JsFile { def readFile(): String = { val fis = jsnew(g.Packages.java.io.FileInputStream)(_file) val data = g.Packages.java.lang.reflect.Array.newInstance( - g.Packages.java.lang.Byte.TYPE, _file.length() + g.Packages.java.lang.Byte.TYPE, + _file.length() ) fis.read(data) fis.close() @@ -70,7 +72,8 @@ class RhinoFile(_file: NativeRhinoFile) extends JsFile { private[scalajssupport] object RhinoFile extends JsFileObject { def write(path: String, data: String, mode: String) = { - val outputstream = jsnew(g.Packages.java.io.FileOutputStream)(path, mode == "a") + val outputstream = + jsnew(g.Packages.java.io.FileOutputStream)(path, mode == "a") val jString = jsnew(g.Packages.java.lang.String)(data) outputstream.write(jString.getBytes()) } @@ -82,4 +85,4 @@ private[scalajssupport] object RhinoFile extends JsFileObject { def apply(path: String) = { new RhinoFile(path) } -} \ No newline at end of file +} diff --git a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/Source.scala b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/Source.scala index f0dbdbaf..13f018e6 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/Source.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scalajssupport/Source.scala @@ -1,11 +1,10 @@ package scalajssupport -import scala.io.{ Source => OrigSource } +import scala.io.{Source => OrigSource} -/** -* This implementation of Source loads the whole file in memory, which is not really efficient, but -* it is not a problem for scoverage operations. -*/ +/** This implementation of Source loads the whole file in memory, which is not really efficient, but + * it is not a problem for scoverage operations. + */ object Source { def fromFile(file: File) = { new OrigSource { diff --git a/scalac-scoverage-runtime/js/src/main/scala/scoverage/Platform.scala b/scalac-scoverage-runtime/js/src/main/scala/scoverage/Platform.scala index e59ced2c..a7deb165 100644 --- a/scalac-scoverage-runtime/js/src/main/scala/scoverage/Platform.scala +++ b/scalac-scoverage-runtime/js/src/main/scala/scoverage/Platform.scala @@ -1,12 +1,11 @@ package scoverage import scala.collection.mutable.HashMap -import scalajssupport.{ - File => SupportFile, - FileWriter => SupportFileWriter, - FileFilter => SupportFileFilter, - Source => SupportSource -} + +import scalajssupport.{File => SupportFile} +import scalajssupport.{FileFilter => SupportFileFilter} +import scalajssupport.{FileWriter => SupportFileWriter} +import scalajssupport.{Source => SupportSource} object Platform { type ThreadSafeMap[A, B] = HashMap[A, B] @@ -18,4 +17,4 @@ object Platform { lazy val Source = SupportSource -} \ No newline at end of file +} diff --git a/scalac-scoverage-runtime/jvm/src/main/scala/scoverage/Platform.scala b/scalac-scoverage-runtime/jvm/src/main/scala/scoverage/Platform.scala index 452f6311..35c4ebdb 100644 --- a/scalac-scoverage-runtime/jvm/src/main/scala/scoverage/Platform.scala +++ b/scalac-scoverage-runtime/jvm/src/main/scala/scoverage/Platform.scala @@ -1,12 +1,11 @@ package scoverage +import java.io.{File => SupportFile} +import java.io.{FileFilter => SupportFileFilter} +import java.io.{FileWriter => SupportFileWriter} + import scala.collection.concurrent.TrieMap -import java.io.{ - File => SupportFile, - FileWriter => SupportFileWriter, - FileFilter => SupportFileFilter -} -import scala.io.{ Source => SupportSource } +import scala.io.{Source => SupportSource} object Platform { type ThreadSafeMap[A, B] = TrieMap[A, B] @@ -17,4 +16,4 @@ object Platform { type FileFilter = SupportFileFilter lazy val Source = SupportSource -} \ No newline at end of file +} diff --git a/scalac-scoverage-runtime/jvm/src/test/scala/scoverage/InvokerConcurrencyTest.scala b/scalac-scoverage-runtime/jvm/src/test/scala/scoverage/InvokerConcurrencyTest.scala index 090590c5..2adb3c1f 100644 --- a/scalac-scoverage-runtime/jvm/src/test/scala/scoverage/InvokerConcurrencyTest.scala +++ b/scalac-scoverage-runtime/jvm/src/test/scala/scoverage/InvokerConcurrencyTest.scala @@ -3,18 +3,18 @@ package scoverage import java.io.File import java.util.concurrent.Executors -import org.scalatest.BeforeAndAfter - import scala.concurrent._ import scala.concurrent.duration._ + +import org.scalatest.BeforeAndAfter import org.scalatest.funsuite.AnyFunSuite -/** - * Verify that [[Invoker.invoked()]] is thread-safe - */ +/** Verify that [[Invoker.invoked()]] is thread-safe + */ class InvokerConcurrencyTest extends AnyFunSuite with BeforeAndAfter { - implicit val executor = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(8)) + implicit val executor = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(8)) val measurementDir = new File("target/invoker-test.measurement") @@ -23,7 +23,9 @@ class InvokerConcurrencyTest extends AnyFunSuite with BeforeAndAfter { measurementDir.mkdirs() } - test("calling Invoker.invoked on multiple threads does not corrupt the measurement file") { + test( + "calling Invoker.invoked on multiple threads does not corrupt the measurement file" + ) { val testIds: Set[Int] = (1 to 1000).toSet diff --git a/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala b/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala index cfb93761..482749a0 100644 --- a/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala +++ b/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala @@ -1,6 +1,8 @@ package scoverage -import scala.collection.{mutable, Set} +import scala.collection.Set +import scala.collection.mutable + import scoverage.Platform._ /** @author Stephen Samuel */ @@ -14,24 +16,28 @@ object Invoker { // For each data directory we maintain a thread-safe set tracking the ids that we've already // seen and recorded. We're using a map as a set, so we only care about its keys and can ignore // its values. - private val dataDirToIds = ThreadSafeMap.empty[String, ThreadSafeMap[Int, Any]] + private val dataDirToIds = + ThreadSafeMap.empty[String, ThreadSafeMap[Int, Any]] - /** - * We record that the given id has been invoked by appending its id to the coverage - * data file. - * - * This will happen concurrently on as many threads as the application is using, - * so we use one file per thread, named for the thread id. - * - * This method is not thread-safe if the threads are in different JVMs, because - * the thread IDs may collide. - * You may not use `scoverage` on multiple processes in parallel without risking - * corruption of the measurement file. - * - * @param id the id of the statement that was invoked - * @param dataDir the directory where the measurement data is held - */ - def invoked(id: Int, dataDir: String, reportTestName: Boolean = false): Unit = { + /** We record that the given id has been invoked by appending its id to the coverage + * data file. + * + * This will happen concurrently on as many threads as the application is using, + * so we use one file per thread, named for the thread id. + * + * This method is not thread-safe if the threads are in different JVMs, because + * the thread IDs may collide. + * You may not use `scoverage` on multiple processes in parallel without risking + * corruption of the measurement file. + * + * @param id the id of the statement that was invoked + * @param dataDir the directory where the measurement data is held + */ + def invoked( + id: Int, + dataDir: String, + reportTestName: Boolean = false + ): Unit = { // [sam] we can do this simple check to save writing out to a file. // This won't work across JVMs but since there's no harm in writing out the same id multiple // times since for coverage we only care about 1 or more, (it just slows things down to @@ -54,9 +60,18 @@ object Invoker { files = mutable.HashMap.empty[String, FileWriter] threadFiles.set(files) } - val writer = files.getOrElseUpdate(dataDir, new FileWriter(measurementFile(dataDir), true)) + val writer = files.getOrElseUpdate( + dataDir, + new FileWriter(measurementFile(dataDir), true) + ) - if(reportTestName) writer.append(Integer.toString(id)).append(" ").append(getCallingScalaTest).append("\n").flush() + if (reportTestName) + writer + .append(Integer.toString(id)) + .append(" ") + .append(getCallingScalaTest) + .append("\n") + .flush() else writer.append(Integer.toString(id)).append("\n").flush() ids.put(id, ()) } @@ -65,16 +80,27 @@ object Invoker { def getCallingScalaTest: String = Thread.currentThread.getStackTrace .map(_.getClassName.toLowerCase) - .find(name => name.endsWith("suite") || name.endsWith("spec") || name.endsWith("test")) + .find(name => + name.endsWith("suite") || name.endsWith("spec") || name.endsWith("test") + ) .getOrElse("") - def measurementFile(dataDir: File): File = measurementFile(dataDir.getAbsolutePath()) - def measurementFile(dataDir: String): File = new File(dataDir, MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.getId) + def measurementFile(dataDir: File): File = measurementFile( + dataDir.getAbsolutePath() + ) + def measurementFile(dataDir: String): File = new File( + dataDir, + MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.getId + ) - def findMeasurementFiles(dataDir: String): Array[File] = findMeasurementFiles(new File(dataDir)) - def findMeasurementFiles(dataDir: File): Array[File] = dataDir.listFiles(new FileFilter { - override def accept(pathname: File): Boolean = pathname.getName().startsWith(MeasurementsPrefix) - }) + def findMeasurementFiles(dataDir: String): Array[File] = findMeasurementFiles( + new File(dataDir) + ) + def findMeasurementFiles(dataDir: File): Array[File] = + dataDir.listFiles(new FileFilter { + override def accept(pathname: File): Boolean = + pathname.getName().startsWith(MeasurementsPrefix) + }) // loads all the invoked statement ids from the given files def invoked(files: Seq[File]): Set[Int] = { diff --git a/scalac-scoverage-runtime/shared/src/test/scala/scoverage/InvokerMultiModuleTest.scala b/scalac-scoverage-runtime/shared/src/test/scala/scoverage/InvokerMultiModuleTest.scala index 296749f4..fcdce722 100644 --- a/scalac-scoverage-runtime/shared/src/test/scala/scoverage/InvokerMultiModuleTest.scala +++ b/scalac-scoverage-runtime/shared/src/test/scala/scoverage/InvokerMultiModuleTest.scala @@ -1,13 +1,11 @@ package scoverage -import scoverage.Platform.File - import org.scalatest.BeforeAndAfter import org.scalatest.funsuite.AnyFunSuite +import scoverage.Platform.File -/** - * Verify that [[Invoker.invoked()]] can handle a multi-module project - */ +/** Verify that [[Invoker.invoked()]] can handle a multi-module project + */ class InvokerMultiModuleTest extends AnyFunSuite with BeforeAndAfter { val measurementDir = Array( @@ -20,7 +18,9 @@ class InvokerMultiModuleTest extends AnyFunSuite with BeforeAndAfter { measurementDir.foreach(_.mkdirs()) } - test("calling Invoker.invoked on with different directories puts measurements in different directories") { + test( + "calling Invoker.invoked on with different directories puts measurements in different directories" + ) { val testIds: Set[Int] = (1 to 10).toSet