From db86022e7a5784744d3d4bcb87c8c2a5383d29e4 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Thu, 12 Mar 2015 08:41:22 +0100 Subject: [PATCH] Manually merge pull request #109 from gslowikowski/issue104 Support multiple source roots in report generators (issue #104) --- .../src/main/scala/scoverage/IOUtils.scala | 18 ++++++++++ .../scoverage/report/CoberturaXmlWriter.scala | 34 ++++++++++++------- .../scoverage/report/CoverageAggregator.scala | 4 +++ .../report/ScoverageHtmlWriter.scala | 14 ++++++-- .../scoverage/report/ScoverageXmlWriter.scala | 13 +++++-- .../scoverage/CoberturaXmlWriterTest.scala | 32 +++++++++-------- .../scoverage/CoverageAggregatorTest.scala | 12 ++++--- .../scoverage/ScoverageXmlReaderTest.scala | 14 +++++--- 8 files changed, 101 insertions(+), 40 deletions(-) diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala index 68875c15..73da9a54 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala @@ -81,4 +81,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 _ => throw new RuntimeException(s"No source root found for '$src'"); //TODO Change exception class + } + } + } 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..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,17 +4,24 @@ import java.io.File import scoverage._ -import scala.xml.Node +import scala.xml.{Node, PrettyPrinter} /** @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"), - "\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 = { @@ -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.filter(_.isDirectory()).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/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) 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 f7cccb89..8f121378 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 @@ -531,5 +536,8 @@ class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) { {xml.Unparsed("""$(document).ready(function() {$(".tablesorter").tablesorter();});""")} } + + 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) + } 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