From 716b18e6a51dfa3edf29e3561ff88cb038f7e860 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Sun, 23 Sep 2018 10:31:58 +0200 Subject: [PATCH 1/3] Remove redundant `scoverage.Statement.source` field It's value is always equal to `scoverage.Location.sourcePath`. Replace it with delegating method with the same name. --- .../src/main/scala/scoverage/Serializer.scala | 2 +- .../src/main/scala/scoverage/coverage.scala | 4 ++-- .../src/main/scala/scoverage/plugin.scala | 1 - .../scoverage/report/ScoverageXmlReader.scala | 1 - .../scoverage/CoberturaXmlWriterTest.scala | 23 +++++++++---------- .../scoverage/CoverageAggregatorTest.scala | 8 +++---- .../scala/scoverage/CoverageMetricsTest.scala | 4 ++-- .../test/scala/scoverage/CoverageTest.scala | 10 ++++---- .../scoverage/ScoverageHtmlWriterTest.scala | 8 +++---- .../scoverage/ScoverageXmlReaderTest.scala | 4 ++-- .../test/scala/scoverage/SerializerTest.scala | 6 ++--- 11 files changed, 33 insertions(+), 38 deletions(-) diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala index 4d0d4067..fc7f4712 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala @@ -108,7 +108,7 @@ object Serializer { case "Object" => ClassType.Object case _ => ClassType.Class } - Statement(source, + Statement( Location(_package, _class, fullClassName, classType, method, path), id, start, diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala index 6591c99e..dfe65258 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/coverage.scala @@ -109,8 +109,7 @@ case class MeasuredFile(source: String, statements: Iterable[Statement]) override def ignoredStatements: Iterable[Statement] = Seq() } -case class Statement(source: String, - location: Location, +case class Statement(location: Location, id: Int, start: Int, end: Int, @@ -121,6 +120,7 @@ case class Statement(source: String, branch: Boolean, var count: Int = 0, ignored: Boolean = false) extends java.io.Serializable { + def source = location.sourcePath def invoked(): Unit = count = count + 1 def isInvoked = count > 0 } diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala index 609dcebc..ff8e148b 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala @@ -202,7 +202,6 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt case Some(source) => val id = statementIds.incrementAndGet val statement = Statement( - source.path, location, id, safeStart(tree), diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlReader.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlReader.scala index 75dedaeb..b104695b 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlReader.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlReader.scala @@ -43,7 +43,6 @@ object ScoverageXmlReader { id = id + 1 coverage add Statement( - source.text, location, id, start.text.toInt, diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala index 6a21fac3..101c1999 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoberturaXmlWriterTest.scala @@ -32,29 +32,28 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan val coverage = scoverage.Coverage() coverage - .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")), + .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(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")), + .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(canonicalPath("b.scala"), Location("com.sksamuel.scoverage2", "B", "com.sksamuel.scoverage2.B", ClassType.Object, "retrieve", canonicalPath("b.scala")), + .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(canonicalPath("b.scala"), - Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")), + .add(Statement(Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")), 4, 2, 3, 9, "", "", "", false, 3)) coverage - .add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update", canonicalPath("c.scala")), + .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(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update2", canonicalPath("c.scala")), + .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(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete", canonicalPath("d.scala")), + .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(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete2", canonicalPath("d.scala")), + .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) @@ -87,13 +86,13 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan val coverage = Coverage() coverage - .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")), + .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")), 1, 2, 3, 12, "", "", "", false)) coverage - .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")), + .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")), 2, 2, 3, 16, "", "", "", true)) coverage - .add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create3", canonicalPath("a.scala")), + .add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create3", canonicalPath("a.scala")), 3, 3, 3, 20, "", "", "", true, 1)) val writer = new CoberturaXmlWriter(sourceRoot, dir) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala index 711be566..e3daac1b 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageAggregatorTest.scala @@ -24,20 +24,20 @@ class CoverageAggregatorTest extends FreeSpec with Matchers { source) val coverage1 = Coverage() - coverage1.add(Statement(source, location, 1, 155, 176, 4, "", "", "", true, 1)) - coverage1.add(Statement(source, location, 2, 200, 300, 5, "", "", "", false, 2)) + coverage1.add(Statement(location, 1, 155, 176, 4, "", "", "", true, 1)) + coverage1.add(Statement(location, 2, 200, 300, 5, "", "", "", false, 2)) val dir1 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir1.mkdir() new ScoverageXmlWriter(sourceRoot, dir1, false).write(coverage1) val coverage2 = Coverage() - coverage2.add(Statement(source, location, 1, 95, 105, 19, "", "", "", false, 0)) + coverage2.add(Statement(location, 1, 95, 105, 19, "", "", "", false, 0)) val dir2 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir2.mkdir() new ScoverageXmlWriter(sourceRoot, dir2, false).write(coverage2) val coverage3 = Coverage() - coverage3.add(Statement(source, location, 2, 14, 1515, 544, "", "", "", false, 1)) + coverage3.add(Statement(location, 2, 14, 1515, 544, "", "", "", false, 1)) val dir3 = new File(IOUtils.getTempPath, UUID.randomUUID.toString) dir3.mkdir() new ScoverageXmlWriter(sourceRoot, dir3, false).write(coverage3) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala index b37ec30a..2992ae1b 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageMetricsTest.scala @@ -6,7 +6,7 @@ class CoverageMetricsTest extends FreeSpec with Matchers { "no branches with at least one invoked statement should have 100% branch coverage" in { val metrics = new CoverageMetrics { - override def statements: Iterable[Statement] = Seq(Statement(null, + override def statements: Iterable[Statement] = Seq(Statement( null, 0, 0, @@ -26,7 +26,7 @@ class CoverageMetricsTest extends FreeSpec with Matchers { "no branches with no invoked statements should have 0% branch coverage" in { val metrics = new CoverageMetrics { - override def statements: Iterable[Statement] = Seq(Statement(null, + override def statements: Iterable[Statement] = Seq(Statement( null, 0, 0, diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala index 4bf5e0af..50f9ee7c 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/CoverageTest.scala @@ -12,16 +12,16 @@ class CoverageTest extends FunSuite with BeforeAndAfter with OneInstancePerTest test("coverage for no invoked statements is 0") { val coverage = Coverage() - coverage.add(Statement("", Location("", "","", ClassType.Object, "", ""), 1, 2, 3, 4, "", "", "", false, 0)) + coverage.add(Statement(Location("", "","", ClassType.Object, "", ""), 1, 2, 3, 4, "", "", "", false, 0)) assert(0 === coverage.statementCoverage) } test("coverage for invoked statements") { val coverage = Coverage() - coverage.add(Statement("", Location("", "","", ClassType.Object, "", ""), 1, 2, 3, 4, "", "", "", false, 3)) - coverage.add(Statement("", Location("", "", "", ClassType.Object, "", ""), 2, 2, 3, 4, "", "", "", false, 0)) - coverage.add(Statement("", Location("", "", "", ClassType.Object, "", ""), 3, 2, 3, 4, "", "", "", false, 0)) - coverage.add(Statement("", Location("", "", "", ClassType.Object, "", ""), 4, 2, 3, 4, "", "", "", false, 0)) + coverage.add(Statement(Location("", "","", ClassType.Object, "", ""), 1, 2, 3, 4, "", "", "", false, 3)) + coverage.add(Statement(Location("", "", "", ClassType.Object, "", ""), 2, 2, 3, 4, "", "", "", false, 0)) + coverage.add(Statement(Location("", "", "", ClassType.Object, "", ""), 3, 2, 3, 4, "", "", "", false, 0)) + coverage.add(Statement(Location("", "", "", ClassType.Object, "", ""), 4, 2, 3, 4, "", "", "", false, 0)) assert(0.25 === coverage.statementCoverage) } } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala index a7076876..cf3f4e09 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageHtmlWriterTest.scala @@ -21,15 +21,15 @@ class ScoverageHtmlWriterTest extends FunSuite { val pathToClassInSubDir = pathToClassFile("subdir/ClassInSubDir.scala") val pathToClassInMainDir = pathToClassFile("ClassInMainDir.scala") - val statementForClassContainingHtml = Statement(pathToClassContainingHtml, - Location("coverage.sample", "ClassContainingHtml", "ClassContainingHtml", ClassType.Class, "some_html", pathToClassInSubDir), + val statementForClassContainingHtml = Statement( + Location("coverage.sample", "ClassContainingHtml", "ClassContainingHtml", ClassType.Class, "some_html", pathToClassContainingHtml), 3, 74, 97, 4, "
HTML content
", "scala.Predef.println", "Apply", false, 0) - val statementForClassInSubDir = Statement(pathToClassInSubDir, + val statementForClassInSubDir = Statement( Location("coverage.sample", "ClassInSubDir", "ClassInSubDir", ClassType.Class, "msg_test", pathToClassInSubDir), 2, 64, 84, 4, "scala.this.Predef.println(\"test code\")", "scala.Predef.println", "Apply", false, 0) - val statementForClassInMainDir = Statement(pathToClassInMainDir, + val statementForClassInMainDir = Statement( Location("coverage.sample", "ClassInMainDir", "ClassInMainDir", ClassType.Class, "msg_coverage", pathToClassInMainDir), 1, 69, 104, 4, "scala.this.Predef.println(\"measure coverage of code\")", "scala.Predef.println", "Apply", false, 0) diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala index d2033e39..84406d73 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageXmlReaderTest.scala @@ -17,7 +17,7 @@ class ScoverageXmlReaderTest extends FreeSpec with Matchers { val coverage = Coverage() - coverage.add(Statement(canonicalPath("com/scoverage/class.scala"), + coverage.add(Statement( Location("com.scoverage", "Test", "com.scoverage.TopLevel.Test", @@ -34,7 +34,7 @@ class ScoverageXmlReaderTest extends FreeSpec with Matchers { true, 2)) - coverage.add(Statement(canonicalPath("com/scoverage/foo/class.scala"), + coverage.add(Statement( Location("com.scoverage.foo", "ServiceState", "com.scoverage.foo.Service.ServiceState", diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala index 14e1abaf..40924651 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala @@ -13,14 +13,13 @@ class SerializerTest extends FunSuite with MockitoSugar with OneInstancePerTest val coverage = Coverage() coverage.add( Statement( - "mysource", Location("org.scoverage", "test", "org.scoverage.test", ClassType.Trait, "mymethod", "mypath"), 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 32 ) ) val expected = - mysource org.scoverage test Trait org.scoverage.test mymethod mypath 14 100 200 4 def test : String test DefDef true 32 false + mypath org.scoverage test Trait org.scoverage.test mymethod mypath 14 100 200 4 def test : String test DefDef true 32 false val writer = new StringWriter() @@ -31,11 +30,10 @@ class SerializerTest extends FunSuite with MockitoSugar with OneInstancePerTest test("coverage should be deserializable from xml") { val input = - mysource org.scoverage test Trait org.scoverage.test mymethod mypath 14 100 200 4 def test : String test DefDef true 32 false + mypath org.scoverage test Trait org.scoverage.test mymethod mypath 14 100 200 4 def test : String test DefDef true 32 false val statements = List(Statement( - "mysource", Location("org.scoverage", "test", "org.scoverage.test", ClassType.Trait, "mymethod", "mypath"), 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 32 )) From 4140954affb3eb701e25e82d37c918d1feca2141 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Sun, 23 Sep 2018 10:46:46 +0200 Subject: [PATCH 2/3] Change internal format from XML to plain text Change format of `scoverage.coverage.xml` file (containing information about instrumented statements) from XML to plain test (and file name to `scoverage.coverage`). In Scala version 2.13.0-M4 the dependency `scala-compiler` -> `scala-xml` was removed and since Scalac plugin cannot add its own dependencies, one of the solutions is to resign from XML format. The code generating reports will still use `scala-xml` dependency. It's not a problem because it's executes in the context of the build tool (SBT, Maven or Gradle), not Scalac. The side effect of this change is increased performance because writing and reading plain test files is much faster than XML files, especially when these files are large. --- .../src/main/scala/scoverage/Constants.scala | 2 +- .../src/main/scala/scoverage/Serializer.scala | 221 ++++++++---------- .../scoverage/report/ScoverageXmlWriter.scala | 30 ++- .../test/scala/scoverage/SerializerTest.scala | 102 ++++++-- 4 files changed, 202 insertions(+), 153 deletions(-) diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/Constants.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/Constants.scala index 3259e3b2..e1b1e57e 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/Constants.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/Constants.scala @@ -2,7 +2,7 @@ package scoverage object Constants { // the file that contains the statement mappings - val CoverageFileName = "scoverage.coverage.xml" + val CoverageFileName = "scoverage.coverage" // the final scoverage report val XMLReportFilename = "scoverage.xml" val XMLReportFilenameWithDebug = "scoverage-debug.xml" diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala index fc7f4712..0354ecae 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala @@ -1,9 +1,8 @@ package scoverage -import java.io._ +import java.io.{BufferedWriter, File, FileOutputStream, OutputStreamWriter, Writer} -import scala.io.Source -import scala.xml.{Utility, XML} +import scala.io.{Codec, Source} object Serializer { @@ -12,147 +11,109 @@ object Serializer { // Write out coverage data to given file. def serialize(coverage: Coverage, file: File): Unit = { - val writer = new BufferedWriter(new FileWriter(file)) - serialize(coverage, writer) - writer.close() + val writer: Writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Codec.UTF8.name)) + try { + serialize(coverage, writer) + } + finally { + writer.flush() + writer.close() + } } def serialize(coverage: Coverage, writer: Writer): Unit = { + def writeHeader(writer: Writer): Unit = { + writer.write(s"""# Coverage data, format version: 2.0 + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |""".stripMargin) + } + def writeStatement(stmt: Statement, writer: Writer): Unit = { - writer.write { - val xml = - - {stmt.source} - - - {stmt.location.packageName} - - - {stmt.location.className} - - - {stmt.location.classType.toString} - - - {stmt.location.fullClassName} - - - {stmt.location.method} - - - {stmt.location.sourcePath} - - - {stmt.id.toString} - - - {stmt.start.toString} - - - {stmt.end.toString} - - - {stmt.line.toString} - - - {escape(stmt.desc)} - - - {escape(stmt.symbolName)} - - - {escape(stmt.treeName)} - - - {stmt.branch.toString} - - - {stmt.count.toString} - - - {stmt.ignored.toString} - - - Utility.trim(xml) + "\n" - } + writer.write(s"""${stmt.id} + |${stmt.location.sourcePath} + |${stmt.location.packageName} + |${stmt.location.className} + |${stmt.location.classType} + |${stmt.location.fullClassName} + |${stmt.location.method} + |${stmt.start} + |${stmt.end} + |${stmt.line} + |${stmt.symbolName} + |${stmt.treeName} + |${stmt.branch} + |${stmt.count} + |${stmt.ignored} + |${stmt.desc} + |\f + |""".stripMargin) } - writer.write("\n") + + writeHeader(writer) coverage.statements.foreach(stmt => writeStatement(stmt, writer)) - writer.write("") } def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath) def coverageFile(dataDir: String): File = new File(dataDir, Constants.CoverageFileName) - def deserialize(str: String): Coverage = { - val xml = XML.loadString(str) - val statements = xml \ "statement" map (node => { - val source = (node \ "source").text - val count = (node \ "count").text.toInt - val ignored = (node \ "ignored").text.toBoolean - val branch = (node \ "branch").text.toBoolean - val _package = (node \ "package").text - val _class = (node \ "class").text - val fullClassName = (node \ "fullClassName").text - val method = (node \ "method").text - val path = (node \ "path").text - val treeName = (node \ "treeName").text - val symbolName = (node \ "symbolName").text - val id = (node \ "id").text.toInt - val line = (node \ "line").text.toInt - val desc = (node \ "description").text - val start = (node \ "start").text.toInt - val end = (node \ "end").text.toInt - val classType = (node \ "classType").text match { - case "Trait" => ClassType.Trait - case "Object" => ClassType.Object - case _ => ClassType.Class - } - Statement( - Location(_package, _class, fullClassName, classType, method, path), - id, - start, - end, - line, - desc, - symbolName, - treeName, branch, count, ignored) - }) - - val coverage = Coverage() - for ( statement <- statements ) - if (statement.ignored) coverage.addIgnoredStatement(statement) - else coverage.add(statement) - coverage - } - def deserialize(file: File): Coverage = { - val str = Source.fromFile(file).mkString - deserialize(str) + deserialize(Source.fromFile(file)(Codec.UTF8).getLines) } - /** - * This method ensures that the output String has only - * valid XML unicode characters as specified by the - * XML 1.0 standard. For reference, please see - * the - * standard. This method will return an empty - * String if the input is null or empty. - * - * @param in The String whose non-valid characters we want to remove. - * @return The in String, stripped of non-valid characters. - * @see http://blog.mark-mclaren.info/2007/02/invalid-xml-characters-when-valid-utf8_5873.html - * - */ - def escape(in: String): String = { - val out = new StringBuilder() - for ( current <- Option(in).getOrElse("").toCharArray ) { - if ((current == 0x9) || (current == 0xA) || (current == 0xD) || - ((current >= 0x20) && (current <= 0xD7FF)) || - ((current >= 0xE000) && (current <= 0xFFFD)) || - ((current >= 0x10000) && (current <= 0x10FFFF))) - out.append(current) + def deserialize(lines: Iterator[String]): Coverage = { + def toStatement(lines: Iterator[String]): Statement = { + val id: Int = lines.next.toInt + val sourcePath = lines.next + val packageName = lines.next + val className = lines.next + val classType = lines.next + val fullClassName = lines.next + val method = lines.next + val loc = Location(packageName, className, fullClassName, ClassType.fromString(classType), method, sourcePath) + val start: Int = lines.next.toInt + val end: Int = lines.next.toInt + val lineNo: Int = lines.next.toInt + val symbolName: String = lines.next + val treeName: String = lines.next + val branch: Boolean = lines.next.toBoolean + val count: Int = lines.next.toInt + val ignored: Boolean = lines.next.toBoolean + val desc = lines.toList.mkString("\n") + Statement(loc, id, start, end, lineNo, desc, symbolName, treeName, branch, count, ignored) + } + + val headerFirstLine = lines.next + require(headerFirstLine == "# Coverage data, format version: 2.0", "Wrong file format") + + val linesWithoutHeader = lines.dropWhile(_.startsWith("#")) + val coverage = Coverage() + while (!linesWithoutHeader.isEmpty) { + val oneStatementLines = linesWithoutHeader.takeWhile(_ != "\f") + val statement = toStatement(oneStatementLines) + if (statement.ignored) + coverage.addIgnoredStatement(statement) + else + coverage.add(statement) } - out.mkString + coverage } + } 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 7d3b8f90..33d63e10 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/report/ScoverageXmlWriter.scala @@ -43,12 +43,12 @@ class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: B start={stmt.start.toString} end={stmt.end.toString} line={stmt.line.toString} - symbol={Serializer.escape(stmt.symbolName)} - tree={Serializer.escape(stmt.treeName)} + symbol={escape(stmt.symbolName)} + tree={escape(stmt.treeName)} branch={stmt.branch.toString} invocation-count={stmt.count.toString} ignored={stmt.ignored.toString}> - {Serializer.escape(stmt.desc)} + {escape(stmt.desc)} case false => } + /** + * This method ensures that the output String has only + * valid XML unicode characters as specified by the + * XML 1.0 standard. For reference, please see + * the + * standard. This method will return an empty + * String if the input is null or empty. + * + * @param in The String whose non-valid characters we want to remove. + * @return The in String, stripped of non-valid characters. + * @see http://blog.mark-mclaren.info/2007/02/invalid-xml-characters-when-valid-utf8_5873.html + * + */ + def escape(in: String): String = { + val out = new StringBuilder() + for ( current <- Option(in).getOrElse("").toCharArray ) { + if ((current == 0x9) || (current == 0xA) || (current == 0xD) || + ((current >= 0x20) && (current <= 0xD7FF)) || + ((current >= 0xE000) && (current <= 0xFFFD)) || + ((current >= 0x10000) && (current <= 0x10FFFF))) + out.append(current) + } + out.mkString + } } diff --git a/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala b/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala index 40924651..098f2eeb 100644 --- a/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala +++ b/scalac-scoverage-plugin/src/test/scala/scoverage/SerializerTest.scala @@ -5,39 +5,103 @@ import java.io.StringWriter import org.scalatest.{OneInstancePerTest, FunSuite} import org.scalatest.mockito.MockitoSugar -import scala.xml.Utility - class SerializerTest extends FunSuite with MockitoSugar with OneInstancePerTest { - test("coverage should be serializable into xml") { + test("coverage should be serializable into plain text") { val coverage = Coverage() coverage.add( Statement( Location("org.scoverage", "test", "org.scoverage.test", ClassType.Trait, "mymethod", "mypath"), - 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 32 + 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 1 ) ) - val expected = - - mypath org.scoverage test Trait org.scoverage.test mymethod mypath 14 100 200 4 def test : String test DefDef true 32 false - - - val writer = new StringWriter() + val expected = s"""# Coverage data, format version: 2.0 + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |14 + |mypath + |org.scoverage + |test + |Trait + |org.scoverage.test + |mymethod + |100 + |200 + |4 + |test + |DefDef + |true + |1 + |false + |def test : String + |\f + |""".stripMargin + val writer = new StringWriter()//TODO-use UTF-8 val actual = Serializer.serialize(coverage, writer) - assert(Utility.trim(expected) === Utility.trim(xml.XML.loadString(writer.toString))) + assert(expected === writer.toString) } - test("coverage should be deserializable from xml") { - val input = - - mypath org.scoverage test Trait org.scoverage.test mymethod mypath 14 100 200 4 def test : String test DefDef true 32 false - - + test("coverage should be deserializable from plain text") { + val input = s"""# Coverage data, format version: 2.0 + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |14 + |mypath + |org.scoverage + |test + |Trait + |org.scoverage.test + |mymethod + |100 + |200 + |4 + |test + |DefDef + |true + |1 + |false + |def test : String + |\f + |""".stripMargin.split("\n").toIterator val statements = List(Statement( Location("org.scoverage", "test", "org.scoverage.test", ClassType.Trait, "mymethod", "mypath"), - 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 32 + 14, 100, 200, 4, "def test : String", "test", "DefDef", true, 1 )) - val coverage = Serializer.deserialize(input.toString()) + val coverage = Serializer.deserialize(input) assert(statements === coverage.statements.toList) } } From 4d5c7f1471e27f88bf3cb2487065356c48028d44 Mon Sep 17 00:00:00 2001 From: Grzegorz Slowikowski Date: Sun, 23 Sep 2018 10:49:33 +0200 Subject: [PATCH 3/3] Sort statements by id It improves file readability. --- .../src/main/scala/scoverage/Serializer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala index 0354ecae..39e7da80 100644 --- a/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala +++ b/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala @@ -68,7 +68,7 @@ object Serializer { } writeHeader(writer) - coverage.statements.foreach(stmt => writeStatement(stmt, writer)) + coverage.statements.toSeq.sortBy(_.id).foreach(stmt => writeStatement(stmt, writer)) } def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath)