Skip to content

Commit f9068db

Browse files
authored
Merge pull request #233 from gslowikowski/plain-text-internal-format
Use plain text as internal format (not XML)
2 parents 3518d2f + 4d5c7f1 commit f9068db

13 files changed

+233
-189
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package scoverage
22

33
object Constants {
44
// the file that contains the statement mappings
5-
val CoverageFileName = "scoverage.coverage.xml"
5+
val CoverageFileName = "scoverage.coverage"
66
// the final scoverage report
77
val XMLReportFilename = "scoverage.xml"
88
val XMLReportFilenameWithDebug = "scoverage-debug.xml"
Lines changed: 92 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package scoverage
22

3-
import java.io._
3+
import java.io.{BufferedWriter, File, FileOutputStream, OutputStreamWriter, Writer}
44

5-
import scala.io.Source
6-
import scala.xml.{Utility, XML}
5+
import scala.io.{Codec, Source}
76

87
object Serializer {
98

@@ -12,147 +11,109 @@ object Serializer {
1211

1312
// Write out coverage data to given file.
1413
def serialize(coverage: Coverage, file: File): Unit = {
15-
val writer = new BufferedWriter(new FileWriter(file))
16-
serialize(coverage, writer)
17-
writer.close()
14+
val writer: Writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Codec.UTF8.name))
15+
try {
16+
serialize(coverage, writer)
17+
}
18+
finally {
19+
writer.flush()
20+
writer.close()
21+
}
1822
}
1923

2024
def serialize(coverage: Coverage, writer: Writer): Unit = {
25+
def writeHeader(writer: Writer): Unit = {
26+
writer.write(s"""# Coverage data, format version: 2.0
27+
|# Statement data:
28+
|# - id
29+
|# - source path
30+
|# - package name
31+
|# - class name
32+
|# - class type (Class, Object or Trait)
33+
|# - full class name
34+
|# - method name
35+
|# - start offset
36+
|# - end offset
37+
|# - line number
38+
|# - symbol name
39+
|# - tree name
40+
|# - is branch
41+
|# - invocations count
42+
|# - is ignored
43+
|# - description (can be multi-line)
44+
|# '\f' sign
45+
|# ------------------------------------------
46+
|""".stripMargin)
47+
}
48+
2149
def writeStatement(stmt: Statement, writer: Writer): Unit = {
22-
writer.write {
23-
val xml = <statement>
24-
<source>
25-
{stmt.source}
26-
</source>
27-
<package>
28-
{stmt.location.packageName}
29-
</package>
30-
<class>
31-
{stmt.location.className}
32-
</class>
33-
<classType>
34-
{stmt.location.classType.toString}
35-
</classType>
36-
<fullClassName>
37-
{stmt.location.fullClassName}
38-
</fullClassName>
39-
<method>
40-
{stmt.location.method}
41-
</method>
42-
<path>
43-
{stmt.location.sourcePath}
44-
</path>
45-
<id>
46-
{stmt.id.toString}
47-
</id>
48-
<start>
49-
{stmt.start.toString}
50-
</start>
51-
<end>
52-
{stmt.end.toString}
53-
</end>
54-
<line>
55-
{stmt.line.toString}
56-
</line>
57-
<description>
58-
{escape(stmt.desc)}
59-
</description>
60-
<symbolName>
61-
{escape(stmt.symbolName)}
62-
</symbolName>
63-
<treeName>
64-
{escape(stmt.treeName)}
65-
</treeName>
66-
<branch>
67-
{stmt.branch.toString}
68-
</branch>
69-
<count>
70-
{stmt.count.toString}
71-
</count>
72-
<ignored>
73-
{stmt.ignored.toString}
74-
</ignored>
75-
</statement>
76-
Utility.trim(xml) + "\n"
77-
}
50+
writer.write(s"""${stmt.id}
51+
|${stmt.location.sourcePath}
52+
|${stmt.location.packageName}
53+
|${stmt.location.className}
54+
|${stmt.location.classType}
55+
|${stmt.location.fullClassName}
56+
|${stmt.location.method}
57+
|${stmt.start}
58+
|${stmt.end}
59+
|${stmt.line}
60+
|${stmt.symbolName}
61+
|${stmt.treeName}
62+
|${stmt.branch}
63+
|${stmt.count}
64+
|${stmt.ignored}
65+
|${stmt.desc}
66+
|\f
67+
|""".stripMargin)
7868
}
79-
writer.write("<statements>\n")
80-
coverage.statements.foreach(stmt => writeStatement(stmt, writer))
81-
writer.write("</statements>")
69+
70+
writeHeader(writer)
71+
coverage.statements.toSeq.sortBy(_.id).foreach(stmt => writeStatement(stmt, writer))
8272
}
8373

8474
def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath)
8575
def coverageFile(dataDir: String): File = new File(dataDir, Constants.CoverageFileName)
8676

87-
def deserialize(str: String): Coverage = {
88-
val xml = XML.loadString(str)
89-
val statements = xml \ "statement" map (node => {
90-
val source = (node \ "source").text
91-
val count = (node \ "count").text.toInt
92-
val ignored = (node \ "ignored").text.toBoolean
93-
val branch = (node \ "branch").text.toBoolean
94-
val _package = (node \ "package").text
95-
val _class = (node \ "class").text
96-
val fullClassName = (node \ "fullClassName").text
97-
val method = (node \ "method").text
98-
val path = (node \ "path").text
99-
val treeName = (node \ "treeName").text
100-
val symbolName = (node \ "symbolName").text
101-
val id = (node \ "id").text.toInt
102-
val line = (node \ "line").text.toInt
103-
val desc = (node \ "description").text
104-
val start = (node \ "start").text.toInt
105-
val end = (node \ "end").text.toInt
106-
val classType = (node \ "classType").text match {
107-
case "Trait" => ClassType.Trait
108-
case "Object" => ClassType.Object
109-
case _ => ClassType.Class
110-
}
111-
Statement(source,
112-
Location(_package, _class, fullClassName, classType, method, path),
113-
id,
114-
start,
115-
end,
116-
line,
117-
desc,
118-
symbolName,
119-
treeName, branch, count, ignored)
120-
})
121-
122-
val coverage = Coverage()
123-
for ( statement <- statements )
124-
if (statement.ignored) coverage.addIgnoredStatement(statement)
125-
else coverage.add(statement)
126-
coverage
127-
}
128-
12977
def deserialize(file: File): Coverage = {
130-
val str = Source.fromFile(file).mkString
131-
deserialize(str)
78+
deserialize(Source.fromFile(file)(Codec.UTF8).getLines)
13279
}
13380

134-
/**
135-
* This method ensures that the output String has only
136-
* valid XML unicode characters as specified by the
137-
* XML 1.0 standard. For reference, please see
138-
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
139-
* standard</a>. This method will return an empty
140-
* String if the input is null or empty.
141-
*
142-
* @param in The String whose non-valid characters we want to remove.
143-
* @return The in String, stripped of non-valid characters.
144-
* @see http://blog.mark-mclaren.info/2007/02/invalid-xml-characters-when-valid-utf8_5873.html
145-
*
146-
*/
147-
def escape(in: String): String = {
148-
val out = new StringBuilder()
149-
for ( current <- Option(in).getOrElse("").toCharArray ) {
150-
if ((current == 0x9) || (current == 0xA) || (current == 0xD) ||
151-
((current >= 0x20) && (current <= 0xD7FF)) ||
152-
((current >= 0xE000) && (current <= 0xFFFD)) ||
153-
((current >= 0x10000) && (current <= 0x10FFFF)))
154-
out.append(current)
81+
def deserialize(lines: Iterator[String]): Coverage = {
82+
def toStatement(lines: Iterator[String]): Statement = {
83+
val id: Int = lines.next.toInt
84+
val sourcePath = lines.next
85+
val packageName = lines.next
86+
val className = lines.next
87+
val classType = lines.next
88+
val fullClassName = lines.next
89+
val method = lines.next
90+
val loc = Location(packageName, className, fullClassName, ClassType.fromString(classType), method, sourcePath)
91+
val start: Int = lines.next.toInt
92+
val end: Int = lines.next.toInt
93+
val lineNo: Int = lines.next.toInt
94+
val symbolName: String = lines.next
95+
val treeName: String = lines.next
96+
val branch: Boolean = lines.next.toBoolean
97+
val count: Int = lines.next.toInt
98+
val ignored: Boolean = lines.next.toBoolean
99+
val desc = lines.toList.mkString("\n")
100+
Statement(loc, id, start, end, lineNo, desc, symbolName, treeName, branch, count, ignored)
101+
}
102+
103+
val headerFirstLine = lines.next
104+
require(headerFirstLine == "# Coverage data, format version: 2.0", "Wrong file format")
105+
106+
val linesWithoutHeader = lines.dropWhile(_.startsWith("#"))
107+
val coverage = Coverage()
108+
while (!linesWithoutHeader.isEmpty) {
109+
val oneStatementLines = linesWithoutHeader.takeWhile(_ != "\f")
110+
val statement = toStatement(oneStatementLines)
111+
if (statement.ignored)
112+
coverage.addIgnoredStatement(statement)
113+
else
114+
coverage.add(statement)
155115
}
156-
out.mkString
116+
coverage
157117
}
118+
158119
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ case class MeasuredFile(source: String, statements: Iterable[Statement])
109109
override def ignoredStatements: Iterable[Statement] = Seq()
110110
}
111111

112-
case class Statement(source: String,
113-
location: Location,
112+
case class Statement(location: Location,
114113
id: Int,
115114
start: Int,
116115
end: Int,
@@ -121,6 +120,7 @@ case class Statement(source: String,
121120
branch: Boolean,
122121
var count: Int = 0,
123122
ignored: Boolean = false) extends java.io.Serializable {
123+
def source = location.sourcePath
124124
def invoked(): Unit = count = count + 1
125125
def isInvoked = count > 0
126126
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
202202
case Some(source) =>
203203
val id = statementIds.incrementAndGet
204204
val statement = Statement(
205-
source.path,
206205
location,
207206
id,
208207
safeStart(tree),

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ object ScoverageXmlReader {
4343
id = id + 1
4444

4545
coverage add Statement(
46-
source.text,
4746
location,
4847
id,
4948
start.text.toInt,

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: B
4343
start={stmt.start.toString}
4444
end={stmt.end.toString}
4545
line={stmt.line.toString}
46-
symbol={Serializer.escape(stmt.symbolName)}
47-
tree={Serializer.escape(stmt.treeName)}
46+
symbol={escape(stmt.symbolName)}
47+
tree={escape(stmt.treeName)}
4848
branch={stmt.branch.toString}
4949
invocation-count={stmt.count.toString}
5050
ignored={stmt.ignored.toString}>
51-
{Serializer.escape(stmt.desc)}
51+
{escape(stmt.desc)}
5252
</statement>
5353
case false =>
5454
<statement package={stmt.location.packageName}
@@ -101,5 +101,29 @@ class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: B
101101
</classes>
102102
</package>
103103
}
104+
/**
105+
* This method ensures that the output String has only
106+
* valid XML unicode characters as specified by the
107+
* XML 1.0 standard. For reference, please see
108+
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
109+
* standard</a>. This method will return an empty
110+
* String if the input is null or empty.
111+
*
112+
* @param in The String whose non-valid characters we want to remove.
113+
* @return The in String, stripped of non-valid characters.
114+
* @see http://blog.mark-mclaren.info/2007/02/invalid-xml-characters-when-valid-utf8_5873.html
115+
*
116+
*/
117+
def escape(in: String): String = {
118+
val out = new StringBuilder()
119+
for ( current <- Option(in).getOrElse("").toCharArray ) {
120+
if ((current == 0x9) || (current == 0xA) || (current == 0xD) ||
121+
((current >= 0x20) && (current <= 0xD7FF)) ||
122+
((current >= 0xE000) && (current <= 0xFFFD)) ||
123+
((current >= 0x10000) && (current <= 0x10FFFF)))
124+
out.append(current)
125+
}
126+
out.mkString
127+
}
104128

105129
}

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,28 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan
3232

3333
val coverage = scoverage.Coverage()
3434
coverage
35-
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")),
35+
.add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")),
3636
1, 2, 3, 12, "", "", "", false, 3))
3737
coverage
38-
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")),
38+
.add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")),
3939
2, 2, 3, 16, "", "", "", false, 3))
4040
coverage
41-
.add(Statement(canonicalPath("b.scala"), Location("com.sksamuel.scoverage2", "B", "com.sksamuel.scoverage2.B", ClassType.Object, "retrieve", canonicalPath("b.scala")),
41+
.add(Statement(Location("com.sksamuel.scoverage2", "B", "com.sksamuel.scoverage2.B", ClassType.Object, "retrieve", canonicalPath("b.scala")),
4242
3, 2, 3, 21, "", "", "", false, 0))
4343
coverage
44-
.add(Statement(canonicalPath("b.scala"),
45-
Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")),
44+
.add(Statement(Location("com.sksamuel.scoverage2", "B", "B", ClassType.Object, "retrieve2", canonicalPath("b.scala")),
4645
4, 2, 3, 9, "", "", "", false, 3))
4746
coverage
48-
.add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update", canonicalPath("c.scala")),
47+
.add(Statement(Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update", canonicalPath("c.scala")),
4948
5, 2, 3, 66, "", "", "", true, 3))
5049
coverage
51-
.add(Statement(canonicalPath("c.scala"), Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update2", canonicalPath("c.scala")),
50+
.add(Statement(Location("com.sksamuel.scoverage3", "C", "com.sksamuel.scoverage3.C", ClassType.Object, "update2", canonicalPath("c.scala")),
5251
6, 2, 3, 6, "", "", "", true, 3))
5352
coverage
54-
.add(Statement(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete", canonicalPath("d.scala")),
53+
.add(Statement(Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete", canonicalPath("d.scala")),
5554
7, 2, 3, 4, "", "", "", false, 0))
5655
coverage
57-
.add(Statement(canonicalPath("d.scala"), Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete2", canonicalPath("d.scala")),
56+
.add(Statement(Location("com.sksamuel.scoverage4", "D", "com.sksamuel.scoverage4.D", ClassType.Object, "delete2", canonicalPath("d.scala")),
5857
8, 2, 3, 14, "", "", "", false, 0))
5958

6059
val writer = new CoberturaXmlWriter(sourceRoot, dir)
@@ -87,13 +86,13 @@ class CoberturaXmlWriterTest extends FunSuite with BeforeAndAfter with OneInstan
8786

8887
val coverage = Coverage()
8988
coverage
90-
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")),
89+
.add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create", canonicalPath("a.scala")),
9190
1, 2, 3, 12, "", "", "", false))
9291
coverage
93-
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")),
92+
.add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create2", canonicalPath("a.scala")),
9493
2, 2, 3, 16, "", "", "", true))
9594
coverage
96-
.add(Statement(canonicalPath("a.scala"), Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create3", canonicalPath("a.scala")),
95+
.add(Statement(Location("com.sksamuel.scoverage", "A", "com.sksamuel.scoverage.A", ClassType.Object, "create3", canonicalPath("a.scala")),
9796
3, 3, 3, 20, "", "", "", true, 1))
9897

9998
val writer = new CoberturaXmlWriter(sourceRoot, dir)

0 commit comments

Comments
 (0)