diff --git a/.gitignore b/.gitignore
index 19961ed1..a95276af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,5 +20,12 @@ credentials.sbt
.idea_modules
*.iml
+# VSCode with Metals specific
+.bsp
+.bloop
+.metals
+.vscode
+metals.sbt
+
# npm specific
node_modules/
diff --git a/reporter/src/main/scala/scoverage/reporter/BaseReportWriter.scala b/reporter/src/main/scala/scoverage/reporter/BaseReportWriter.scala
index 6c89b2e4..753d93c4 100644
--- a/reporter/src/main/scala/scoverage/reporter/BaseReportWriter.scala
+++ b/reporter/src/main/scala/scoverage/reporter/BaseReportWriter.scala
@@ -2,39 +2,62 @@ package scoverage.reporter
import java.io.File
-class BaseReportWriter(
- sourceDirectories: Seq[File],
+/** Abstract report writer.
+ *
+ * @param sourceRoots list of source directories
+ * @param outputDir directory where to store the reports
+ * @param outputEncoding encoding to use when writing files
+ * @param recoverNoSourceRoot specifies how to handle source paths that are outside of the source roots.
+ */
+abstract class BaseReportWriter(
+ sourceRoots: Seq[File],
outputDir: File,
- sourceEncoding: Option[String]
+ outputEncoding: Option[String],
+ recoverNoSourceRoot: BaseReportWriter.PathRecoverer
) {
// Source paths in canonical form WITH trailing file separator
private val formattedSourcePaths: Seq[String] =
- sourceDirectories
+ sourceRoots
.filter(_.isDirectory)
- .map(_.getCanonicalPath + File.separator)
+ .map(_.getCanonicalPath + File.separatorChar)
- /** 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).
+ /** Converts an absolute path to a path relative to the reporter's source directories (aka "source roots").
+ * If the path is not in the source roots, returns None.
*
* @param src absolute file path in canonical form
+ * @return `Some(relativePath)` if `src` is in the source roots, else `None`
*/
- def relativeSource(src: String): String =
+ def relativeSource(src: String): Option[String] =
relativeSource(src, formattedSourcePaths)
- private def relativeSource(src: String, sourcePaths: Seq[String]): String = {
+ private def relativeSource(
+ src: String,
+ sourceRoots: Seq[String]
+ ): Option[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))
- 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)"
- );
- }
+ sourceRoots
+ .find(root => canonicalSrc.startsWith(root))
+ .map(root => canonicalSrc.substring(root.length))
+ .orElse(recoverNoSourceRoot(new File(canonicalSrc), formattedSourcePaths))
}
+}
+object BaseReportWriter {
+
+ /** Specifies how to handle source path that are outside of the source roots.
+ * Takes the source path (as a canonical File) and returns:
+ * - `None` to skip the element
+ * - `Some(newPath)` to use `newPath` instead
+ *
+ * The function may of course take additional actions, such as logging a warning,
+ * throwing an error, etc.
+ */
+ type PathRecoverer = (File, Seq[String]) => Option[String]
+ /** Throws an exception */
+ def failIfNoSourceRoot(f: File, roots: Seq[String]): Option[String] =
+ throw new RuntimeException(
+ s"No source root found for '${f.getPath}' (source roots: $roots)"
+ )
}
diff --git a/reporter/src/main/scala/scoverage/reporter/CoberturaXmlWriter.scala b/reporter/src/main/scala/scoverage/reporter/CoberturaXmlWriter.scala
index e71ec786..5faf6834 100644
--- a/reporter/src/main/scala/scoverage/reporter/CoberturaXmlWriter.scala
+++ b/reporter/src/main/scala/scoverage/reporter/CoberturaXmlWriter.scala
@@ -15,11 +15,22 @@ import scoverage.domain.MeasuredPackage
class CoberturaXmlWriter(
sourceDirectories: Seq[File],
outputDir: File,
- sourceEncoding: Option[String]
-) extends BaseReportWriter(sourceDirectories, outputDir, sourceEncoding) {
+ sourceEncoding: Option[String],
+ recoverNoSourceRoot: BaseReportWriter.PathRecoverer
+) extends BaseReportWriter(
+ sourceDirectories,
+ outputDir,
+ sourceEncoding,
+ recoverNoSourceRoot
+ ) {
- def this(baseDir: File, outputDir: File, sourceEncoding: Option[String]) = {
- this(Seq(baseDir), outputDir, sourceEncoding)
+ def this(
+ baseDir: File,
+ outputDir: File,
+ sourceEncoding: Option[String],
+ recoverNoSourceRoot: BaseReportWriter.PathRecoverer
+ ) = {
+ this(Seq(baseDir), outputDir, sourceEncoding, recoverNoSourceRoot)
}
def write(coverage: Coverage): Unit = {
@@ -49,24 +60,26 @@ class CoberturaXmlWriter(
}
- def klass(klass: MeasuredClass): Node = {
-
-
- {klass.methods.map(method)}
-
-
- {
- klass.statements.map(stmt => )
- }
-
-
+ def klass(klass: MeasuredClass): Option[Node] = {
+ relativeSource(klass.source).map(sourcePath => {
+
+
+ {klass.methods.map(method)}
+
+
+ {
+ klass.statements.map(stmt => )
+ }
+
+
+ })
}
def pack(pack: MeasuredPackage): Node = {
@@ -75,7 +88,7 @@ class CoberturaXmlWriter(
branch-rate={DoubleFormat.twoFractionDigits(pack.branchCoverage)}
complexity="0">
- {pack.classes.map(klass)}
+ {pack.classes.flatMap(klass)}
}
diff --git a/reporter/src/main/scala/scoverage/reporter/ScoverageHtmlWriter.scala b/reporter/src/main/scala/scoverage/reporter/ScoverageHtmlWriter.scala
index e93bf48e..f076bf78 100644
--- a/reporter/src/main/scala/scoverage/reporter/ScoverageHtmlWriter.scala
+++ b/reporter/src/main/scala/scoverage/reporter/ScoverageHtmlWriter.scala
@@ -15,8 +15,14 @@ import scoverage.domain.MeasuredPackage
class ScoverageHtmlWriter(
sourceDirectories: Seq[File],
outputDir: File,
- sourceEncoding: Option[String]
-) extends BaseReportWriter(sourceDirectories, outputDir, sourceEncoding) {
+ sourceEncoding: Option[String],
+ recoverNoSourceRoot: BaseReportWriter.PathRecoverer
+) extends BaseReportWriter(
+ sourceDirectories,
+ outputDir,
+ sourceEncoding,
+ recoverNoSourceRoot
+ ) {
// to be used by gradle-scoverage plugin
def this(
@@ -24,17 +30,34 @@ class ScoverageHtmlWriter(
outputDir: File,
sourceEncoding: Option[String]
) = {
- this(sourceDirectories.toSeq, outputDir, sourceEncoding)
+ this(
+ sourceDirectories.toSeq,
+ outputDir,
+ sourceEncoding,
+ BaseReportWriter.failIfNoSourceRoot
+ )
}
// for backward compatibility only
+ @deprecated
def this(sourceDirectories: Seq[File], outputDir: File) = {
- this(sourceDirectories, outputDir, None);
+ this(
+ sourceDirectories,
+ outputDir,
+ None,
+ BaseReportWriter.failIfNoSourceRoot
+ );
}
// for backward compatibility only
+ @deprecated
def this(sourceDirectory: File, outputDir: File) = {
- this(Seq(sourceDirectory), outputDir)
+ this(
+ Seq(sourceDirectory),
+ outputDir,
+ None,
+ BaseReportWriter.failIfNoSourceRoot
+ )
}
def write(coverage: Coverage): Unit = {
@@ -81,16 +104,25 @@ class ScoverageHtmlWriter(
private def writeFile(mfile: MeasuredFile): Unit = {
// each highlighted file is written out using the same structure as the original file.
- val file = new File(outputDir, relativeSource(mfile.source) + ".html")
+ val sourcePath = relativeSource(mfile.source).getOrElse(
+ throw new RuntimeException(
+ s"Expected the file $mfile to be in the source roots"
+ )
+ )
+ val htmlPath = sourcePath + ".html"
+ val file = new File(outputDir, htmlPath)
file.getParentFile.mkdirs()
- IOUtils.writeToFile(file, filePage(mfile).toString(), sourceEncoding)
+ IOUtils.writeToFile(
+ file,
+ filePage(mfile, htmlPath).toString(),
+ sourceEncoding
+ )
}
private def packageOverviewRelativePath(pkg: MeasuredPackage) =
pkg.name.replace("", "(empty)") + ".html"
- private def filePage(mfile: MeasuredFile): Node = {
- val filename = relativeSource(mfile.source) + ".html"
+ private def filePage(mfile: MeasuredFile, filename: String): Node = {
val css =
"table.codegrid { font-family: monospace; font-size: 12px; width: auto!important; }" +
"table.statementlist { width: auto!important; font-size: 13px; } " +
@@ -236,18 +268,18 @@ class ScoverageHtmlWriter(
- {classes.toSeq.sortBy(_.fullClassName) map classRow}
+ {classes.toSeq.sortBy(_.fullClassName).flatMap(classRow)}
}
- def classRow(klass: MeasuredClass): Node = {
+ def classRow(klass: MeasuredClass): Option[Node] = {
+ relativeSource(klass.source).map(path => classRow(klass, path))
+ }
+ def classRow(klass: MeasuredClass, relativeSourcePath: String): Node = {
val filename: String = {
-
- val fileRelativeToSource = new File(
- relativeSource(klass.source) + ".html"
- )
+ val fileRelativeToSource = new File(relativeSourcePath + ".html")
val path = fileRelativeToSource.getParent
val value = fileRelativeToSource.getName
diff --git a/reporter/src/main/scala/scoverage/reporter/ScoverageXmlWriter.scala b/reporter/src/main/scala/scoverage/reporter/ScoverageXmlWriter.scala
index ad09adae..3c552864 100644
--- a/reporter/src/main/scala/scoverage/reporter/ScoverageXmlWriter.scala
+++ b/reporter/src/main/scala/scoverage/reporter/ScoverageXmlWriter.scala
@@ -16,16 +16,23 @@ class ScoverageXmlWriter(
sourceDirectories: Seq[File],
outputDir: File,
debug: Boolean,
- sourceEncoding: Option[String]
-) extends BaseReportWriter(sourceDirectories, outputDir, sourceEncoding) {
+ sourceEncoding: Option[String],
+ recoverNoSourceRoot: BaseReportWriter.PathRecoverer
+) extends BaseReportWriter(
+ sourceDirectories,
+ outputDir,
+ sourceEncoding,
+ recoverNoSourceRoot
+ ) {
def this(
sourceDir: File,
outputDir: File,
debug: Boolean,
- sourceEncoding: Option[String]
+ sourceEncoding: Option[String],
+ recoverNoSourceRoot: BaseReportWriter.PathRecoverer
) = {
- this(Seq(sourceDir), outputDir, debug, sourceEncoding)
+ this(Seq(sourceDir), outputDir, debug, sourceEncoding, recoverNoSourceRoot)
}
def write(coverage: Coverage): Unit = {
@@ -97,17 +104,19 @@ class ScoverageXmlWriter(
}
- private def klass(klass: MeasuredClass): Node = {
-
-
- {klass.methods.map(method)}
-
-
+ private def klass(klass: MeasuredClass): Option[Node] = {
+ relativeSource(klass.source).map(sourcePath => {
+
+
+ {klass.methods.map(method)}
+
+
+ })
}
private def pack(pack: MeasuredPackage): Node = {
@@ -116,7 +125,7 @@ class ScoverageXmlWriter(
statements-invoked={pack.invokedStatementCount.toString}
statement-rate={pack.statementCoverageFormatted}>
- {pack.classes.map(klass)}
+ {pack.classes.flatMap(klass)}
}
diff --git a/reporter/src/test/scala/scoverage/reporter/CoberturaXmlWriterTest.scala b/reporter/src/test/scala/scoverage/reporter/CoberturaXmlWriterTest.scala
index d9dc1ef0..af17bb23 100644
--- a/reporter/src/test/scala/scoverage/reporter/CoberturaXmlWriterTest.scala
+++ b/reporter/src/test/scala/scoverage/reporter/CoberturaXmlWriterTest.scala
@@ -1,7 +1,8 @@
package scoverage.reporter
import java.io.File
-import java.util.UUID
+import java.nio.file.Files
+import java.nio.file.Path
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.SAXParserFactory
@@ -12,79 +13,35 @@ import scala.xml.factory.XMLLoader
import munit.FunSuite
import org.xml.sax.ErrorHandler
import org.xml.sax.SAXParseException
-import scoverage.domain.ClassType
import scoverage.domain.Coverage
-import scoverage.domain.Location
-import scoverage.domain.Statement
+
+import TestUtils._
+import BaseReportWriter.failIfNoSourceRoot
/** @author Stephen Samuel */
class CoberturaXmlWriterTest extends FunSuite {
- def tempDir(): File = {
- val dir = new File(IOUtils.getTempDirectory, UUID.randomUUID.toString)
- dir.mkdirs()
- dir.deleteOnExit()
- dir
- }
+ val xmlOutputPath = FunFixture[Path](
+ setup = test => {
+ val dir = Files.createTempDirectory("test-cobertura")
+ dir.resolve("cobertura.xml")
+ },
+ teardown = file => {
+ Files.deleteIfExists(file)
+ Files.deleteIfExists(file.getParent())
+ }
+ )
- def fileIn(dir: File) = new File(dir, "cobertura.xml")
+ // Let the current directory be our source root (any dir would do)
+ val sourceRoot = new File(".")
- // Let current directory be our source root
- private val sourceRoot = new File(".")
- private def canonicalPath(fileName: String) =
+ def canonicalPath(fileName: String) =
new File(sourceRoot, fileName).getCanonicalPath
- test("cobertura output has relative file path") {
-
- 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,
- 3
- )
- )
- coverage.add(
- Statement(
- Location(
- "com.sksamuel.scoverage.A",
- "B",
- "com.sksamuel.scoverage.A.B",
- ClassType.Object,
- "create",
- canonicalPath("a/b.scala")
- ),
- 2,
- 2,
- 3,
- 12,
- "",
- "",
- "",
- false,
- 3
- )
- )
-
- val writer = new CoberturaXmlWriter(sourceRoot, dir, None)
- writer.write(coverage)
+ def relativePath(fileName: String) =
+ new File(sourceRoot, fileName).getPath.replace("./", "")
+ def parseXML(file: Path): Elem = {
// Needed to acount for https://github.com/scala/scala-xml/pull/177
val customXML: XMLLoader[Elem] = XML.withSAXParser {
val factory = SAXParserFactory.newInstance()
@@ -94,316 +51,109 @@ class CoberturaXmlWriterTest extends FunSuite {
)
factory.newSAXParser()
}
+ customXML.loadFile(file.toFile())
+ }
- val xml = customXML.loadFile(fileIn(dir))
+ xmlOutputPath.test("cobertura output has relative file path") { xmlPath =>
+ val coverage = Coverage()
+ val outputDir = xmlPath.getParent().toFile()
+ coverage.add(
+ testStatement(
+ testLocation(canonicalPath("a.scala"))
+ )
+ )
+ coverage.add(
+ testStatement(
+ testLocation(canonicalPath("a/b.scala"))
+ )
+ )
+ val writer =
+ new CoberturaXmlWriter(sourceRoot, outputDir, None, failIfNoSourceRoot)
+ writer.write(coverage)
+
+ val xml = parseXML(xmlPath)
assertEquals(
((xml \\ "coverage" \ "packages" \ "package" \ "classes" \ "class")(
0
) \ "@filename").text,
- new File("a.scala").getPath()
+ relativePath("a.scala")
)
assertEquals(
((xml \\ "coverage" \ "packages" \ "package" \ "classes" \ "class")(
1
) \ "@filename").text,
- new File("a", "b.scala").getPath()
+ relativePath("a/b.scala")
)
}
- test("cobertura output validates") {
-
- val dir = tempDir()
-
+ xmlOutputPath.test("cobertura output validates") { xmlPath =>
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,
- 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
- )
- )
- coverage
- .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
- )
- )
- coverage
- .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
- )
- )
- coverage
- .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
- )
- )
-
- val writer = new CoberturaXmlWriter(sourceRoot, dir, None)
+ val outputDir = xmlPath.getParent().toFile()
+
+ val fakeSources = Seq("a.scala", "b.scala", "c.scala", "d.scala")
+ for {
+ s <- fakeSources
+ loc = testLocation(canonicalPath(s))
+ isBranch <- Seq(true, false)
+ invokeCount <- Seq(0, 3)
+ } coverage.add(testStatement(loc, isBranch, invokeCount))
+
+ val writer =
+ new CoberturaXmlWriter(sourceRoot, outputDir, None, failIfNoSourceRoot)
writer.write(coverage)
val domFactory = DocumentBuilderFactory.newInstance()
domFactory.setValidating(true)
val builder = domFactory.newDocumentBuilder()
builder.setErrorHandler(new ErrorHandler() {
- @Override
- def error(e: SAXParseException): Unit = {
+ override def error(e: SAXParseException): Unit = {
fail(e.getMessage(), e.getCause())
}
- @Override
- def fatalError(e: SAXParseException): Unit = {
+ override def fatalError(e: SAXParseException): Unit = {
fail(e.getMessage(), e.getCause())
}
-
- @Override
- def warning(e: SAXParseException): Unit = {
+ override def warning(e: SAXParseException): Unit = {
fail(e.getMessage(), e.getCause())
}
})
- builder.parse(fileIn(dir))
+ builder.parse(xmlPath.toFile())
}
- test(
+ xmlOutputPath.test(
"coverage rates are written as 2dp decimal values rather than percentage"
- ) {
-
- val dir = tempDir()
-
+ ) { xmlPath =>
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
- )
+ val outputDir = xmlPath.getParent().toFile()
+ val fakeSourcePath = canonicalPath("a.scala")
+ coverage.add(
+ testStatement(
+ testLocation(fakeSourcePath),
+ isBranch = false,
+ invokeCount = 0 // not covered
)
- coverage
- .add(
- Statement(
- Location(
- "com.sksamuel.scoverage",
- "A",
- "com.sksamuel.scoverage.A",
- ClassType.Object,
- "create2",
- canonicalPath("a.scala")
- ),
- 2,
- 2,
- 3,
- 16,
- "",
- "",
- "",
- true
- )
+ )
+ coverage.add(
+ testStatement(
+ testLocation(fakeSourcePath),
+ isBranch = true,
+ invokeCount = 0 // not covered
)
- coverage
- .add(
- Statement(
- Location(
- "com.sksamuel.scoverage",
- "A",
- "com.sksamuel.scoverage.A",
- ClassType.Object,
- "create3",
- canonicalPath("a.scala")
- ),
- 3,
- 3,
- 3,
- 20,
- "",
- "",
- "",
- true,
- 1
- )
+ )
+ coverage.add(
+ testStatement(
+ testLocation(fakeSourcePath),
+ isBranch = true,
+ invokeCount = 1 // covered
)
+ )
- val writer = new CoberturaXmlWriter(sourceRoot, dir, None)
+ val writer =
+ new CoberturaXmlWriter(sourceRoot, outputDir, None, failIfNoSourceRoot)
writer.write(coverage)
- // Needed to acount for https://github.com/scala/scala-xml/pull/177
- val customXML: XMLLoader[Elem] = XML.withSAXParser {
- val factory = SAXParserFactory.newInstance()
- factory.setFeature(
- "http://apache.org/xml/features/nonvalidating/load-external-dtd",
- false
- )
- factory.newSAXParser()
- }
-
- val xml = customXML.loadFile(fileIn(dir))
+ val xml = parseXML(xmlPath)
assertEquals((xml \\ "coverage" \ "@line-rate").text, "0.33", "line-rate")
assertEquals(
@@ -413,4 +163,67 @@ class CoberturaXmlWriterTest extends FunSuite {
)
}
+
+ def testPathRecovery(name: String, policy: BaseReportWriter.PathRecoverer)(checks: Elem => Unit)(implicit loc: munit.Location) = {
+ xmlOutputPath.test(name) { xmlPath =>
+ val outputDir = xmlPath.getParent().toFile()
+ val coverage = Coverage()
+
+ val notInRoot = "/*not*/in/root.scala"
+ val inRoot = "in-root.sc"
+
+ coverage.add(
+ testStatement(
+ testLocation(notInRoot, className = "A") // should be replaced
+ )
+ )
+ coverage.add(
+ testStatement(
+ testLocation(
+ canonicalPath(inRoot),
+ className = "B"
+ ) // should be unchanged
+ )
+ )
+ val writer = new CoberturaXmlWriter(
+ sourceRoot,
+ outputDir,
+ None,
+ policy
+ )
+ writer.write(coverage)
+
+ val xml = parseXML(xmlPath)
+ checks(xml)
+ }
+ }
+
+ testPathRecovery("path recovery replace", (f, roots) => Some("recovered/path")) { xml =>
+ val classes =
+ (xml \\ "coverage" \ "packages" \ "package" \ "classes" \ "class")
+
+ assertEquals(
+ (classes(0) \ "@filename").text,
+ "recovered/path"
+ )
+ assertEquals(
+ (classes(1) \ "@filename").text,
+ relativePath("in-root.sc")
+ )
+ }
+
+ testPathRecovery("path recovery: skip", (f, roots) => None) { xml =>
+ val classes =
+ (xml \\ "coverage" \ "packages" \ "package" \ "classes" \ "class")
+
+ println(classes)
+ assertEquals(
+ (classes(0) \ "@filename").text,
+ "in-root.sc"
+ )
+ assertEquals(
+ classes.length,
+ 1
+ )
+ }
}
diff --git a/reporter/src/test/scala/scoverage/reporter/TestUtils.scala b/reporter/src/test/scala/scoverage/reporter/TestUtils.scala
new file mode 100644
index 00000000..d78f758a
--- /dev/null
+++ b/reporter/src/test/scala/scoverage/reporter/TestUtils.scala
@@ -0,0 +1,43 @@
+package scoverage.reporter
+
+import scoverage.domain.Location
+import scoverage.domain.ClassType
+import scoverage.domain.Statement
+
+object TestUtils {
+ private var nextId = 0
+
+ def testLocation(
+ sourcePath: String,
+ className: String = s"T$nextId",
+ classType: ClassType = ClassType.Class
+ ): Location =
+ Location(
+ "scoverage.test",
+ className,
+ s"scoverage.test.$className",
+ classType,
+ "method",
+ sourcePath
+ )
+
+ def testStatement(
+ location: Location,
+ isBranch: Boolean = false,
+ invokeCount: Int = 0
+ ): Statement = {
+ nextId += 1
+ Statement(
+ location,
+ nextId,
+ 10 + nextId,
+ 50 + nextId,
+ nextId * 10,
+ nextId.toString,
+ "sym",
+ "",
+ isBranch,
+ invokeCount
+ )
+ }
+}