Skip to content

Commit db86022

Browse files
committed
Manually merge pull request #109 from gslowikowski/issue104
Support multiple source roots in report generators (issue #104)
1 parent 052b986 commit db86022

8 files changed

+101
-40
lines changed

scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala

+18
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,22 @@ object IOUtils {
8181
}
8282
acc
8383
}
84+
85+
/**
86+
* Converts absolute path to relative one if any of the source directories is it's parent.
87+
* If there is no parent directory, the path is returned unchanged (absolute).
88+
*
89+
* @param src absolute file path in canonical form
90+
* @param sourcePaths absolute source paths in canonical form WITH trailing file separators
91+
*/
92+
def relativeSource(src: String, sourcePaths: Seq[String]): String = {
93+
val sourceRoot: Option[String] = sourcePaths.find(
94+
sourcePath => src.startsWith(sourcePath)
95+
)
96+
sourceRoot match {
97+
case Some(path: String) => src.replace(path, "")
98+
case _ => throw new RuntimeException(s"No source root found for '$src'"); //TODO Change exception class
99+
}
100+
}
101+
84102
}

scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala

+22-12
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@ import java.io.File
44

55
import scoverage._
66

7-
import scala.xml.Node
7+
import scala.xml.{Node, PrettyPrinter}
88

99
/** @author Stephen Samuel */
10-
class CoberturaXmlWriter(baseDir: File, outputDir: File) {
10+
class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) {
1111

12+
def this (baseDir: File, outputDir: File) {
13+
this(Seq(baseDir), outputDir);
14+
}
15+
16+
// Source paths in canonical form WITH trailing file separator
17+
val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator )
18+
1219
def format(double: Double): String = "%.2f".format(double)
1320

1421
def write(coverage: Coverage): Unit = {
15-
IOUtils.writeToFile(new File(outputDir.getAbsolutePath + "/cobertura.xml"),
16-
"<?xml version=\"1.0\"?>\n<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n" +
17-
xml(coverage))
22+
val file = new File(outputDir, "cobertura.xml")
23+
IOUtils.writeToFile(file, "<?xml version=\"1.0\"?>\n<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n" +
24+
new PrettyPrinter(120, 4).format(xml(coverage)))
1825
}
1926

2027
def method(method: MeasuredMethod): Node = {
@@ -35,12 +42,7 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) {
3542

3643
def klass(klass: MeasuredClass): Node = {
3744
<class name={klass.name}
38-
filename={
39-
val absPath = baseDir.getAbsolutePath.last == File.separatorChar match {
40-
case true => baseDir.getAbsolutePath
41-
case false => baseDir.getAbsolutePath + File.separatorChar
42-
}
43-
klass.source.replace(absPath, "")}
45+
filename={relativeSource(klass.source).replace(File.separator, "/")}
4446
line-rate={format(klass.statementCoverage)}
4547
branch-rate={format(klass.branchCoverage)}
4648
complexity="0">
@@ -69,6 +71,10 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) {
6971
</package>
7072
}
7173

74+
def source(src: File): Node = {
75+
<source>{src.getCanonicalPath.replace(File.separator, "/")}</source>
76+
}
77+
7278
def xml(coverage: Coverage): Node = {
7379
<coverage line-rate={format(coverage.statementCoverage)}
7480
lines-covered={coverage.statementCount.toString}
@@ -80,11 +86,15 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) {
8086
version="1.0"
8187
timestamp={System.currentTimeMillis.toString}>
8288
<sources>
83-
<source>/src/main/scala</source>
89+
<source>--source</source>
90+
{sourceDirectories.filter(_.isDirectory()).map(source)}
8491
</sources>
8592
<packages>
8693
{coverage.packages.map(pack)}
8794
</packages>
8895
</coverage>
8996
}
97+
98+
private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths)
99+
90100
}

scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ object CoverageAggregator {
88

99
def aggregate(baseDir: File, clean: Boolean): Option[Coverage] = {
1010
val files = IOUtils.reportFileSearch(baseDir, IOUtils.isReportFile)
11+
aggregate(files, clean)
12+
}
13+
14+
def aggregate(files: Seq[File], clean: Boolean): Option[Coverage] = {
1115
println(s"[info] Found ${files.size} subproject report files [${files.mkString(",")}]")
1216
if (files.size > 1) {
1317
val coverage = aggregatedCoverage(files)

scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala

+11-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ import scoverage._
88
import scala.xml.Node
99

1010
/** @author Stephen Samuel */
11-
class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) {
11+
class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File) {
1212

13+
def this (sourceDirectory: File, outputDir: File) {
14+
this(Seq(sourceDirectory), outputDir);
15+
}
16+
17+
// Source paths in canonical form WITH trailing file separator
18+
val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator )
19+
1320
def write(coverage: Coverage): Unit = {
1421
val indexFile = new File(outputDir.getAbsolutePath + "/index.html")
1522
val packageFile = new File(outputDir.getAbsolutePath + "/packages.html")
@@ -23,8 +30,6 @@ class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) {
2330
coverage.packages.foreach(writePackage)
2431
}
2532

26-
private def relativeSource(src: String): String = src.replace(sourceDirectory.getCanonicalPath + File.separator, "")
27-
2833
private def writePackage(pkg: MeasuredPackage): Unit = {
2934
// package overview files are written out using a filename that respects the package name
3035
// that means package com.example declared in a class at src/main/scala/mystuff/MyClass.scala will be written
@@ -531,5 +536,8 @@ class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) {
531536
{xml.Unparsed("""$(document).ready(function() {$(".tablesorter").tablesorter();});""")}
532537
</script>
533538
}
539+
540+
private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths)
541+
534542
}
535543

scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ import _root_.scoverage._
77
import scala.xml.{Node, PrettyPrinter}
88

99
/** @author Stephen Samuel */
10-
class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) {
10+
class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: Boolean) {
1111

12+
def this (sourceDir: File, outputDir: File, debug: Boolean) {
13+
this(Seq(sourceDir), outputDir, debug);
14+
}
15+
16+
// Source paths in canonical form WITH trailing file separator
17+
val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator )
18+
1219
def write(coverage: Coverage): Unit = {
1320
val file = IOUtils.reportFile(outputDir, debug)
1421
IOUtils.writeToFile(file, new PrettyPrinter(120, 4).format(xml(coverage)))
@@ -74,7 +81,7 @@ class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) {
7481

7582
private def klass(klass: MeasuredClass): Node = {
7683
<class name={klass.name}
77-
filename={klass.source.replace(sourceDir.getAbsolutePath, "")}
84+
filename={relativeSource(klass.source)}
7885
statement-count={klass.statementCount.toString}
7986
statements-invoked={klass.invokedStatementCount.toString}
8087
statement-rate={klass.statementCoverageFormatted}
@@ -96,5 +103,7 @@ class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) {
96103
</package>
97104
}
98105

106+
private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths)
107+
99108
}
100109

scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala

+18-14
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,42 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan
2222

2323
def fileIn(dir: File) = new File(dir, "cobertura.xml")
2424

25+
// Let current directory be our source root
26+
private val sourceRoot = new File(".")
27+
private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath
28+
2529
test("cobertura output validates") {
2630

2731
val dir = tempDir()
2832

2933
val coverage = scoverage.Coverage()
3034
coverage
31-
.add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", ""),
35+
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", canonicalPath("a.scala")),
3236
1, 2, 3, 12, "", "", "", false, 3))
3337
coverage
34-
.add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", ""),
38+
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", canonicalPath("a.scala")),
3539
2, 2, 3, 16, "", "", "", false, 3))
3640
coverage
37-
.add(Statement("b.scala", Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve", ""),
41+
.add(Statement(canonicalPath("b.scala"), Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve", canonicalPath("b.scala")),
3842
3, 2, 3, 21, "", "", "", false, 0))
3943
coverage
40-
.add(Statement("b.scala",
41-
Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", ""),
44+
.add(Statement(canonicalPath("b.scala"),
45+
Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")),
4246
4, 2, 3, 9, "", "", "", false, 3))
4347
coverage
44-
.add(Statement("c.scala", Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update", ""),
48+
.add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update", canonicalPath("c.scala")),
4549
5, 2, 3, 66, "", "", "", true, 3))
4650
coverage
47-
.add(Statement("c.scala", Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update2", ""),
51+
.add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update2", canonicalPath("c.scala")),
4852
6, 2, 3, 6, "", "", "", true, 3))
4953
coverage
50-
.add(Statement("d.scala", Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete", ""),
54+
.add(Statement(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete", canonicalPath("d.scala")),
5155
7, 2, 3, 4, "", "", "", false, 0))
5256
coverage
53-
.add(Statement("d.scala", Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete2", ""),
57+
.add(Statement(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete2", canonicalPath("d.scala")),
5458
8, 2, 3, 14, "", "", "", false, 0))
5559

56-
val writer = new CoberturaXmlWriter(new File(""), dir)
60+
val writer = new CoberturaXmlWriter(sourceRoot, dir)
5761
writer.write(coverage)
5862

5963
val domFactory = DocumentBuilderFactory.newInstance()
@@ -83,16 +87,16 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan
8387

8488
val coverage = Coverage()
8589
coverage
86-
.add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", ""),
90+
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", canonicalPath("a.scala")),
8791
1, 2, 3, 12, "", "", "", false))
8892
coverage
89-
.add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", ""),
93+
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", canonicalPath("a.scala")),
9094
2, 2, 3, 16, "", "", "", true))
9195
coverage
92-
.add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create3", ""),
96+
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create3", canonicalPath("a.scala")),
9397
3, 3, 3, 20, "", "", "", true, 1))
9498

95-
val writer = new CoberturaXmlWriter(new File(""), dir)
99+
val writer = new CoberturaXmlWriter(sourceRoot, dir)
96100
writer.write(coverage)
97101

98102
val xml = XML.loadFile(fileIn(dir))

scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala

+8-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import scoverage.report.{CoverageAggregator, ScoverageXmlWriter}
88

99
class CoverageAggregatorTest extends FreeSpec with Matchers {
1010

11+
// Let current directory be our source root
12+
private val sourceRoot = new File(".")
13+
private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath
14+
1115
"coverage aggregator" - {
1216
"should merge coverage objects with same id" in {
1317

14-
val source = "/home/sam/src/main/scala/com/scoverage/class.scala"
18+
val source = canonicalPath("com/scoverage/class.scala")
1519
val location = Location("com.scoverage.foo",
1620
"ServiceState",
1721
"Service",
@@ -24,19 +28,19 @@ class CoverageAggregatorTest extends FreeSpec with Matchers {
2428
coverage1.add(Statement(source, location, 2, 200, 300, 5, "", "", "", false, 2))
2529
val dir1 = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
2630
dir1.mkdir()
27-
new ScoverageXmlWriter(new File("/home/sam"), dir1, false).write(coverage1)
31+
new ScoverageXmlWriter(sourceRoot, dir1, false).write(coverage1)
2832

2933
val coverage2 = Coverage()
3034
coverage2.add(Statement(source, location, 1, 95, 105, 19, "", "", "", false, 0))
3135
val dir2 = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
3236
dir2.mkdir()
33-
new ScoverageXmlWriter(new File("/home/sam"), dir2, false).write(coverage2)
37+
new ScoverageXmlWriter(sourceRoot, dir2, false).write(coverage2)
3438

3539
val coverage3 = Coverage()
3640
coverage3.add(Statement(source, location, 2, 14, 1515, 544, "", "", "", false, 1))
3741
val dir3 = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
3842
dir3.mkdir()
39-
new ScoverageXmlWriter(new File("/home/sam"), dir3, false).write(coverage3)
43+
new ScoverageXmlWriter(sourceRoot, dir3, false).write(coverage3)
4044

4145
val aggregated = CoverageAggregator.aggregatedCoverage(
4246
Seq(IOUtils.reportFile(dir1, debug = false),

scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala

+9-5
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ import scoverage.report.{ScoverageXmlReader, ScoverageXmlWriter}
88

99
class ScoverageXmlReaderTest extends FreeSpec with Matchers {
1010

11+
// Let current directory be our source root
12+
private val sourceRoot = new File(".")
13+
private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath
14+
1115
"scoverage xml reader" - {
1216
"should read output from ScoverageXmlWriter" in {
1317

1418
val coverage = Coverage()
1519

16-
coverage.add(Statement("/home/sam/src/main/scala/com/scoverage/class.scala",
20+
coverage.add(Statement(canonicalPath("com/scoverage/class.scala"),
1721
Location("com.scoverage",
1822
"Test",
1923
"TopLevel",
2024
ClassType.Object,
2125
"somemeth",
22-
"/home/sam/src/main/scala/com/scoverage/class.scala"),
26+
canonicalPath("com/scoverage/class.scala")),
2327
14,
2428
155,
2529
176,
@@ -30,13 +34,13 @@ class ScoverageXmlReaderTest extends FreeSpec with Matchers {
3034
true,
3135
2))
3236

33-
coverage.add(Statement("/home/sam/src/main/scala/com/scoverage/foo/class.scala",
37+
coverage.add(Statement(canonicalPath("com/scoverage/foo/class.scala"),
3438
Location("com.scoverage.foo",
3539
"ServiceState",
3640
"Service",
3741
ClassType.Trait,
3842
"methlab",
39-
"/home/sam/src/main/scala/com/scoverage/foo/class.scala"),
43+
canonicalPath("com/scoverage/foo/class.scala")),
4044
16,
4145
95,
4246
105,
@@ -50,7 +54,7 @@ class ScoverageXmlReaderTest extends FreeSpec with Matchers {
5054
val temp = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
5155
temp.mkdir()
5256
temp.deleteOnExit()
53-
new ScoverageXmlWriter(new File("/home/sam"), temp, false).write(coverage)
57+
new ScoverageXmlWriter(sourceRoot, temp, false).write(coverage)
5458

5559
val actual = ScoverageXmlReader.read(IOUtils.reportFile(temp, debug = false))
5660
// we don't care about the statement ids as the will change on reading back in

0 commit comments

Comments
 (0)