diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala index d09ad8ca..2cfa3e1f 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala @@ -71,13 +71,16 @@ object IOUtils { 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] = { - val acc = mutable.Set[Int]() + 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() ) { if (!line.isEmpty) { - acc += line.toInt + acc += (line.split(" ").toList match { + case List(idx, clazz) => (idx.toInt, clazz) + case List(idx) => (idx.toInt, "") + }) } } reader.close() diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala index dfe65258..0f5297a6 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala @@ -38,8 +38,8 @@ case class Coverage() // returns the classes by least coverage def risks(limit: Int) = classes.toSeq.sortBy(_.statementCount).reverse.sortBy(_.statementCoverage).take(limit) - def apply(ids: Iterable[Int]): Unit = ids foreach invoked - def invoked(id: Int): Unit = statementsById.get(id).foreach(_.invoked()) + def apply(ids: Iterable[(Int, String)]): Unit = ids foreach invoked + def invoked(id: (Int, String)): Unit = statementsById.get(id._1).foreach(_.invoked(id._2)) } trait MethodBuilders { @@ -119,9 +119,13 @@ case class Statement(location: Location, treeName: String, branch: Boolean, var count: Int = 0, - ignored: Boolean = false) extends java.io.Serializable { + ignored: Boolean = false, + tests: mutable.Set[String] = mutable.Set[String]()) extends java.io.Serializable { def source = location.sourcePath - def invoked(): Unit = count = count + 1 + def invoked(test: String): Unit = { + count = count + 1 + if(test != "") tests += test + } def isInvoked = count > 0 } diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala index 1aa04d13..11839535 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala @@ -35,6 +35,8 @@ class ScoveragePlugin(val global: Global) extends Plugin { options.dataDir = opt.substring("dataDir:".length) } else if (opt.startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:")) { // skip here, these flags are processed elsewhere + } else if (opt == "reportTestName") { + options.reportTestName = true } else { error("Unknown option: " + opt) } @@ -82,6 +84,7 @@ class ScoverageOptions { 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 dataDir: String = IOUtils.getTempPath + var reportTestName: Boolean = false } class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Option[String], extraBeforePhase: Option[String]) @@ -161,14 +164,17 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt ), newTermName("invoked") ), - List( - Literal( - Constant(id) - ), - Literal( - Constant(options.dataDir) - ) - ) + Literal( + Constant(id) + ) :: + Literal( + Constant(options.dataDir) + ) :: + (if(options.reportTestName) + List(Literal( + Constant(true) + )) + else Nil) ) } 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 be800229..fa859f2e 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/StatementWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/StatementWriter.scala @@ -24,6 +24,7 @@ class StatementWriter(mFile: MeasuredFile) { Pos Tree Symbol + Tests Code {mFile.statements.toSeq.sortBy(_.line).map(stmt => { @@ -44,6 +45,9 @@ class StatementWriter(mFile: MeasuredFile) { {stmt.symbolName} + + {stmt.tests.mkString(",")} + {stmt.desc} diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala index afe0fb26..c2159a64 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/IOUtilsTest.scala @@ -17,7 +17,7 @@ class IOUtilsTest extends AnyFreeSpec with OneInstancePerTest with Matchers { writer.write("1\n5\n9\n\n10\n") writer.close() val invoked = IOUtils.invoked(Seq(file)) - assert(invoked === Set(1, 5, 9, 10)) + assert(invoked === Set((1, ""), (5, ""), (9, ""), (10, ""))) file.delete() } @@ -38,7 +38,7 @@ 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-runtime/shared/src/main/scala/scoverage/Invoker.scala b/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala index dccf1ebb..8310c3d5 100644 --- a/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala +++ b/scalac-scoverage-runtime/shared/src/main/scala/scoverage/Invoker.scala @@ -31,7 +31,7 @@ object Invoker { * @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): Unit = { + 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 @@ -55,12 +55,19 @@ object Invoker { threadFiles.set(files) } val writer = files.getOrElseUpdate(dataDir, new FileWriter(measurementFile(dataDir), true)) - writer.append(Integer.toString(id)).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, ()) } } + def getCallingScalaTest: String = + Thread.currentThread.getStackTrace + .map(_.getClassName.toLowerCase) + .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)