From 78f340bf8d57138454f85594a1269a770896a19d Mon Sep 17 00:00:00 2001 From: ckipp01 Date: Tue, 11 May 2021 16:59:35 +0200 Subject: [PATCH] Add in scalafmt and scalafix. I was going to wait to do this, but I was starting on something else in here and I kept hitting format by habit since I'm honestly just used to always having it. I think it's safe to say that most Scala developers will be used to the default style that scalafmt gives you, so I'd like to propose that we add this in along with scalafix to organize imports. One huge concern is always that changes like this make it hard to get a good `git blame`, however with modern git, this really isn't an issue anymore. You can read more about his here: https://www.moxio.com/blog/43/ignoring-bulk-change-commits-with-git-blame I recently used the strategy outlined in that article in the sbt-scoverage plugin: https://github.com/scoverage/sbt-scoverage/pull/351 and it seems to work quite well. --- .github/workflows/ci.yml | 29 +- .scalafix.conf | 12 + .scalafmt.conf | 4 + build.sbt | 41 +- project/plugins.sbt | 4 + .../main/scala/scoverage/CoverageFilter.scala | 90 ++-- .../main/scala/scoverage/DoubleFormat.scala | 3 +- .../src/main/scala/scoverage/IOUtils.scala | 57 ++- .../src/main/scala/scoverage/Location.scala | 52 +- .../src/main/scala/scoverage/Serializer.scala | 128 +++-- .../src/main/scala/scoverage/coverage.scala | 144 +++--- .../src/main/scala/scoverage/plugin.scala | 458 ++++++++++++------ .../scoverage/report/BaseReportWriter.scala | 26 +- .../scoverage/report/CoberturaXmlWriter.scala | 33 +- .../scala/scoverage/report/CodeGrid.scala | 34 +- .../scoverage/report/CoverageAggregator.scala | 15 +- .../report/ScoverageHtmlWriter.scala | 80 ++- .../scoverage/report/ScoverageXmlWriter.scala | 32 +- .../scoverage/report/StatementWriter.scala | 14 +- .../scoverage/CoberturaXmlWriterTest.scala | 273 +++++++++-- .../scoverage/CoverageAggregatorTest.scala | 26 +- .../scala/scoverage/CoverageMetricsTest.scala | 26 +- .../test/scala/scoverage/CoverageTest.scala | 83 +++- .../test/scala/scoverage/IOUtilsTest.scala | 17 +- .../scala/scoverage/LocationCompiler.scala | 26 +- .../test/scala/scoverage/LocationTest.scala | 87 +++- .../test/scala/scoverage/MacroSupport.scala | 16 +- .../scoverage/PluginASTSupportTest.scala | 189 ++++---- .../scoverage/PluginCoverageScalaJsTest.scala | 19 +- .../scala/scoverage/PluginCoverageTest.scala | 209 ++++---- .../scoverage/RegexCoverageFilterTest.scala | 92 +++- .../scala/scoverage/ScoverageCompiler.scala | 128 +++-- .../scoverage/ScoverageHtmlWriterTest.scala | 100 +++- .../test/scala/scoverage/SerializerTest.scala | 194 ++++---- .../scala/scoverage/macrosupport/Tester.scala | 2 - .../src/main/scala/scalajssupport/File.scala | 22 +- .../scala/scalajssupport/FileWriter.scala | 5 +- .../main/scala/scalajssupport/NodeFile.scala | 31 +- .../scala/scalajssupport/PhantomFile.scala | 35 +- .../main/scala/scalajssupport/RhinoFile.scala | 15 +- .../main/scala/scalajssupport/Source.scala | 9 +- .../src/main/scala/scoverage/Platform.scala | 13 +- .../src/main/scala/scoverage/Platform.scala | 13 +- .../scoverage/InvokerConcurrencyTest.scala | 16 +- .../src/main/scala/scoverage/Invoker.scala | 80 +-- .../scoverage/InvokerMultiModuleTest.scala | 12 +- 46 files changed, 1989 insertions(+), 1005 deletions(-) create mode 100644 .scalafix.conf create mode 100644 .scalafmt.conf 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