From 87d5bb688dd63410d4794045440e5333f84709a2 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Tue, 3 Mar 2015 14:46:42 +0100 Subject: [PATCH 1/6] Changes: IOUtils - added "relativeSource" method finding relative path based multiple source root directories, ScoverageHtmlWriter, ScoverageXmlWriter, CoberturaXmlWriter - multiple source root directories supported, CoberturaXmlWriter - section filled properly. --- .../src/main/scala/scoverage/IOUtils.scala | 18 ++++++++++++ .../scoverage/report/CoberturaXmlWriter.scala | 28 +++++++++++++------ .../report/ScoverageHtmlWriter.scala | 14 ++++++++-- .../scoverage/report/ScoverageXmlWriter.scala | 13 +++++++-- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala index 9d98238c..f23ce9f7 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala @@ -80,4 +80,22 @@ object IOUtils { } acc } + + /** + * 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 + * @param sourcePaths absolute source paths in canonical form WITH trailing file separators + */ + def relativeSource(src: String, sourcePaths: Seq[String]): String = { + val sourceRoot: Option[String] = sourcePaths.find( + sourcePath => src.startsWith(sourcePath) + ) + sourceRoot match { + case Some(path: String) => src.replace(path, "") + case _ => src + } + } + } 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 6a4ebad4..07ec7c73 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala @@ -7,12 +7,19 @@ import scoverage._ import scala.xml.Node /** @author Stephen Samuel */ -class CoberturaXmlWriter(baseDir: File, outputDir: File) { +class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) { + def this (baseDir: File, outputDir: File) { + this(Seq(baseDir), outputDir); + } + + // Source paths in canonical form WITH trailing file separator + val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator ) + def format(double: Double): String = "%.2f".format(double) def write(coverage: Coverage): Unit = { - IOUtils.writeToFile(new File(outputDir.getAbsolutePath + "/cobertura.xml"), + IOUtils.writeToFile(new File(outputDir, "cobertura.xml"), "\n\n" + xml(coverage)) } @@ -35,12 +42,7 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) { def klass(klass: MeasuredClass): Node = { baseDir.getAbsolutePath - case false => baseDir.getAbsolutePath + File.separatorChar - } - klass.source.replace(absPath, "")} + filename={relativeSource(klass.source).replace(File.separator, "/")} line-rate={format(klass.statementCoverage)} branch-rate={format(klass.branchCoverage)} complexity="0"> @@ -69,6 +71,10 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) { } + def source(src: File): Node = { + {src.getCanonicalPath.replace(File.separator, "/")} + } + def xml(coverage: Coverage): Node = { - /src/main/scala + --source + {sourceDirectories.map(source)} {coverage.packages.map(pack)} } + + private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths) + } 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 8506014b..43b74cdc 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageHtmlWriter.scala @@ -8,8 +8,15 @@ import scoverage._ import scala.xml.Node /** @author Stephen Samuel */ -class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) { +class ScoverageHtmlWriter(sourceDirectories: Seq[File], outputDir: File) { + def this (sourceDirectory: File, outputDir: File) { + this(Seq(sourceDirectory), outputDir); + } + + // Source paths in canonical form WITH trailing file separator + val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator ) + def write(coverage: Coverage): Unit = { val indexFile = new File(outputDir.getAbsolutePath + "/index.html") val packageFile = new File(outputDir.getAbsolutePath + "/packages.html") @@ -23,8 +30,6 @@ class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) { coverage.packages.foreach(writePackage) } - private def relativeSource(src: String): String = src.replace(sourceDirectory.getCanonicalPath + File.separator, "") - private def writePackage(pkg: MeasuredPackage): Unit = { // package overview files are written out using a filename that respects the package name // that means package com.example declared in a class at src/main/scala/mystuff/MyClass.scala will be written @@ -535,5 +540,8 @@ class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) { } + + private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths) + } 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 33063332..6bbb78b2 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala @@ -7,8 +7,15 @@ import _root_.scoverage._ import scala.xml.{Node, PrettyPrinter} /** @author Stephen Samuel */ -class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) { +class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: Boolean) { + def this (sourceDir: File, outputDir: File, debug: Boolean) { + this(Seq(sourceDir), outputDir, debug); + } + + // Source paths in canonical form WITH trailing file separator + val formattedSourcePaths: Seq[String] = sourceDirectories filter ( _.isDirectory ) map ( _.getCanonicalPath + File.separator ) + def write(coverage: Coverage): Unit = { val file = IOUtils.reportFile(outputDir, debug) IOUtils.writeToFile(file, new PrettyPrinter(120, 4).format(xml(coverage))) @@ -74,7 +81,7 @@ class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) { private def klass(klass: MeasuredClass): Node = { } + private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths) + } From d8bbb356bab9c6da4b45d984be536a458b66df01 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Tue, 3 Mar 2015 15:26:44 +0100 Subject: [PATCH 2/6] CoverageAggregator - added "aggregate" method with XML report files (Seq[File]) as input parameter. --- .../src/main/scala/scoverage/report/CoverageAggregator.scala | 4 ++++ 1 file changed, 4 insertions(+) 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 50466d77..62108cca 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoverageAggregator.scala @@ -8,6 +8,10 @@ object CoverageAggregator { def aggregate(baseDir: File, clean: Boolean): Option[Coverage] = { val files = IOUtils.reportFileSearch(baseDir, IOUtils.isReportFile) + aggregate(files, clean) + } + + def aggregate(files: Seq[File], clean: Boolean): Option[Coverage] = { println(s"[info] Found ${files.size} subproject report files [${files.mkString(",")}]") if (files.size > 1) { val coverage = aggregatedCoverage(files) From f3a6489333869e8fbbacd3ff467e5e9ae9734f5c Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Tue, 3 Mar 2015 15:35:00 +0100 Subject: [PATCH 3/6] IOUtils - exception raised if relative path cannot be computed (no source root matches), this means there are some configuration problems and failing fast is better solution than generating inproper file paths (causing IOExceptions later). --- scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala index f23ce9f7..4d2f428e 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala @@ -94,7 +94,7 @@ object IOUtils { ) sourceRoot match { case Some(path: String) => src.replace(path, "") - case _ => src + case _ => throw new RuntimeException(s"No source root found for '$src'"); //TODO Change exception class } } From 82c811857103b9ea762cc7abb12a4fb22760edf3 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Tue, 3 Mar 2015 16:15:17 +0100 Subject: [PATCH 4/6] CoberturaXmlWriter - only existing source root directories written to section. --- .../src/main/scala/scoverage/report/CoberturaXmlWriter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 07ec7c73..114b4bc0 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala @@ -87,7 +87,7 @@ class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) { timestamp={System.currentTimeMillis.toString}> --source - {sourceDirectories.map(source)} + {sourceDirectories.filter(_.isDirectory()).map(source)} {coverage.packages.map(pack)} From 88c35e294e032914cfba53a32c1dac67864a6e19 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Tue, 3 Mar 2015 16:37:26 +0100 Subject: [PATCH 5/6] PrettyPrinter used to format Cobertura report (just like in ScoverageXmlReportWriter). --- .../main/scala/scoverage/report/CoberturaXmlWriter.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 114b4bc0..3967a5df 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/CoberturaXmlWriter.scala @@ -4,7 +4,7 @@ import java.io.File import scoverage._ -import scala.xml.Node +import scala.xml.{Node, PrettyPrinter} /** @author Stephen Samuel */ class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) { @@ -19,9 +19,9 @@ class CoberturaXmlWriter(sourceDirectories: Seq[File], outputDir: File) { def format(double: Double): String = "%.2f".format(double) def write(coverage: Coverage): Unit = { - IOUtils.writeToFile(new File(outputDir, "cobertura.xml"), - "\n\n" + - xml(coverage)) + val file = new File(outputDir, "cobertura.xml") + IOUtils.writeToFile(file, "\n\n" + + new PrettyPrinter(120, 4).format(xml(coverage))) } def method(method: MeasuredMethod): Node = { From 9af6fce76a820adf4661e73d2f1485abffd6f079 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Tue, 3 Mar 2015 21:22:22 +0100 Subject: [PATCH 6/6] Fixed unit tests - proper paths used. --- .../scoverage/CoberturaXmlWriterTest.scala | 32 +++++++++++-------- .../scoverage/CoverageAggregatorTest.scala | 12 ++++--- .../scoverage/ScoverageXmlReaderTest.scala | 14 +++++--- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala index f5fb2bb6..2a4a9346 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala @@ -22,38 +22,42 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan def fileIn(dir: File) = new File(dir, "cobertura.xml") + // Let current directory be our source root + private val sourceRoot = new File(".") + private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath + test("cobertura output validates") { val dir = tempDir() val coverage = scoverage.Coverage() coverage - .add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", ""), + .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", canonicalPath("a.scala")), 1, 2, 3, 12, "", "", "", false, 3)) coverage - .add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", ""), + .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", canonicalPath("a.scala")), 2, 2, 3, 16, "", "", "", false, 3)) coverage - .add(Statement("b.scala", Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve", ""), + .add(Statement(canonicalPath("b.scala"), Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve", canonicalPath("b.scala")), 3, 2, 3, 21, "", "", "", false, 0)) coverage - .add(Statement("b.scala", - Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", ""), + .add(Statement(canonicalPath("b.scala"), + Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")), 4, 2, 3, 9, "", "", "", false, 3)) coverage - .add(Statement("c.scala", Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update", ""), + .add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update", canonicalPath("c.scala")), 5, 2, 3, 66, "", "", "", true, 3)) coverage - .add(Statement("c.scala", Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update2", ""), + .add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "C", ClassType.Object, "update2", canonicalPath("c.scala")), 6, 2, 3, 6, "", "", "", true, 3)) coverage - .add(Statement("d.scala", Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete", ""), + .add(Statement(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete", canonicalPath("d.scala")), 7, 2, 3, 4, "", "", "", false, 0)) coverage - .add(Statement("d.scala", Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete2", ""), + .add(Statement(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "D", ClassType.Object, "delete2", canonicalPath("d.scala")), 8, 2, 3, 14, "", "", "", false, 0)) - val writer = new CoberturaXmlWriter(new File(""), dir) + val writer = new CoberturaXmlWriter(sourceRoot, dir) writer.write(coverage) val domFactory = DocumentBuilderFactory.newInstance() @@ -83,16 +87,16 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan val coverage = Coverage() coverage - .add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", ""), + .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create", canonicalPath("a.scala")), 1, 2, 3, 12, "", "", "", false)) coverage - .add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", ""), + .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create2", canonicalPath("a.scala")), 2, 2, 3, 16, "", "", "", true)) coverage - .add(Statement("a.scala", Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create3", ""), + .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "A", ClassType.Object, "create3", canonicalPath("a.scala")), 3, 3, 3, 20, "", "", "", true, 1)) - val writer = new CoberturaXmlWriter(new File(""), dir) + val writer = new CoberturaXmlWriter(sourceRoot, dir) writer.write(coverage) val xml = XML.loadFile(fileIn(dir)) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala index e587b7ca..e99cb2dc 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala @@ -8,10 +8,14 @@ import scoverage.report.{CoverageAggregator, ScoverageXmlWriter} class CoverageAggregatorTest extends FreeSpec with Matchers { + // Let current directory be our source root + private val sourceRoot = new File(".") + private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath + "coverage aggregator" - { "should merge coverage objects with same id" in { - val source = "/home/sam/src/main/scala/com/scoverage/class.scala" + val source = canonicalPath("com/scoverage/class.scala") val location = Location("com.scoverage.foo", "ServiceState", "Service", @@ -24,19 +28,19 @@ class CoverageAggregatorTest extends FreeSpec with Matchers { coverage1.add(Statement(source, location, 2, 200, 300, 5, "", "", "", false, 2)) val dir1 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir1.mkdir() - new ScoverageXmlWriter(new File("/home/sam"), dir1, false).write(coverage1) + new ScoverageXmlWriter(sourceRoot, dir1, false).write(coverage1) val coverage2 = Coverage() coverage2.add(Statement(source, location, 1, 95, 105, 19, "", "", "", false, 0)) val dir2 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir2.mkdir() - new ScoverageXmlWriter(new File("/home/sam"), dir2, false).write(coverage2) + new ScoverageXmlWriter(sourceRoot, dir2, false).write(coverage2) val coverage3 = Coverage() coverage3.add(Statement(source, location, 2, 14, 1515, 544, "", "", "", false, 1)) val dir3 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir3.mkdir() - new ScoverageXmlWriter(new File("/home/sam"), dir3, false).write(coverage3) + new ScoverageXmlWriter(sourceRoot, dir3, false).write(coverage3) val aggregated = CoverageAggregator.aggregatedCoverage( Seq(IOUtils.reportFile(dir1, debug = false), diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala index 1b0251ae..ebda28c6 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala @@ -8,18 +8,22 @@ import scoverage.report.{ScoverageXmlReader, ScoverageXmlWriter} class ScoverageXmlReaderTest extends FreeSpec with Matchers { + // Let current directory be our source root + private val sourceRoot = new File(".") + private def canonicalPath(fileName: String) = new File(sourceRoot, fileName).getCanonicalPath + "scoverage xml reader" - { "should read output from ScoverageXmlWriter" in { val coverage = Coverage() - coverage.add(Statement("/home/sam/src/main/scala/com/scoverage/class.scala", + coverage.add(Statement(canonicalPath("com/scoverage/class.scala"), Location("com.scoverage", "Test", "TopLevel", ClassType.Object, "somemeth", - "/home/sam/src/main/scala/com/scoverage/class.scala"), + canonicalPath("com/scoverage/class.scala")), 14, 155, 176, @@ -30,13 +34,13 @@ class ScoverageXmlReaderTest extends FreeSpec with Matchers { true, 2)) - coverage.add(Statement("/home/sam/src/main/scala/com/scoverage/foo/class.scala", + coverage.add(Statement(canonicalPath("com/scoverage/foo/class.scala"), Location("com.scoverage.foo", "ServiceState", "Service", ClassType.Trait, "methlab", - "/home/sam/src/main/scala/com/scoverage/foo/class.scala"), + canonicalPath("com/scoverage/foo/class.scala")), 16, 95, 105, @@ -50,7 +54,7 @@ class ScoverageXmlReaderTest extends FreeSpec with Matchers { val temp = new File(IOUtils.getTempPath, UUID.randomUUID.toString) temp.mkdir() temp.deleteOnExit() - new ScoverageXmlWriter(new File("/home/sam"), temp, false).write(coverage) + new ScoverageXmlWriter(sourceRoot, temp, false).write(coverage) val actual = ScoverageXmlReader.read(IOUtils.reportFile(temp, debug = false)) // we don't care about the statement ids as the will change on reading back in