Skip to content

Commit

Permalink
Manually merge pull request scoverage#109 from gslowikowski/issue104
Browse files Browse the repository at this point in the history
Support multiple source roots in report generators (issue scoverage#104)
  • Loading branch information
gslowikowski committed Mar 12, 2015
1 parent 052b986 commit db86022
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 40 deletions.
18 changes: 18 additions & 0 deletions scalac-scoverage-plugin/src/main/scala/scoverage/IOUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
"<?xml version=\"1.0\"?>\n<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n" +
xml(coverage))
val file = new File(outputDir, "cobertura.xml")
IOUtils.writeToFile(file, "<?xml version=\"1.0\"?>\n<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n" +
new PrettyPrinter(120, 4).format(xml(coverage)))
}

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

def klass(klass: MeasuredClass): Node = {
<class name={klass.name}
filename={
val absPath = baseDir.getAbsolutePath.last == File.separatorChar match {
case true => 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">
Expand Down Expand Up @@ -69,6 +71,10 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) {
</package>
}

def source(src: File): Node = {
<source>{src.getCanonicalPath.replace(File.separator, "/")}</source>
}

def xml(coverage: Coverage): Node = {
<coverage line-rate={format(coverage.statementCoverage)}
lines-covered={coverage.statementCount.toString}
Expand All @@ -80,11 +86,15 @@ class CoberturaXmlWriter(baseDir: File, outputDir: File) {
version="1.0"
timestamp={System.currentTimeMillis.toString}>
<sources>
<source>/src/main/scala</source>
<source>--source</source>
{sourceDirectories.filter(_.isDirectory()).map(source)}
</sources>
<packages>
{coverage.packages.map(pack)}
</packages>
</coverage>
}

private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths)

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -531,5 +536,8 @@ class ScoverageHtmlWriter(sourceDirectory: File, outputDir: File) {
{xml.Unparsed("""$(document).ready(function() {$(".tablesorter").tablesorter();});""")}
</script>
}

private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths)

}

Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down Expand Up @@ -74,7 +81,7 @@ class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) {

private def klass(klass: MeasuredClass): Node = {
<class name={klass.name}
filename={klass.source.replace(sourceDir.getAbsolutePath, "")}
filename={relativeSource(klass.source)}
statement-count={klass.statementCount.toString}
statements-invoked={klass.invokedStatementCount.toString}
statement-rate={klass.statementCoverageFormatted}
Expand All @@ -96,5 +103,7 @@ class ScoverageXmlWriter(sourceDir: File, outputDir: File, debug: Boolean) {
</package>
}

private def relativeSource(src: String): String = IOUtils.relativeSource(src, formattedSourcePaths)

}

Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down

0 comments on commit db86022

Please sign in to comment.