Skip to content

Use plain text as internal format (not XML) #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
223 changes: 92 additions & 131 deletions scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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 = <statement>
<source>
{stmt.source}
</source>
<package>
{stmt.location.packageName}
</package>
<class>
{stmt.location.className}
</class>
<classType>
{stmt.location.classType.toString}
</classType>
<fullClassName>
{stmt.location.fullClassName}
</fullClassName>
<method>
{stmt.location.method}
</method>
<path>
{stmt.location.sourcePath}
</path>
<id>
{stmt.id.toString}
</id>
<start>
{stmt.start.toString}
</start>
<end>
{stmt.end.toString}
</end>
<line>
{stmt.line.toString}
</line>
<description>
{escape(stmt.desc)}
</description>
<symbolName>
{escape(stmt.symbolName)}
</symbolName>
<treeName>
{escape(stmt.treeName)}
</treeName>
<branch>
{stmt.branch.toString}
</branch>
<count>
{stmt.count.toString}
</count>
<ignored>
{stmt.ignored.toString}
</ignored>
</statement>
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("<statements>\n")
coverage.statements.foreach(stmt => writeStatement(stmt, writer))
writer.write("</statements>")

writeHeader(writer)
coverage.statements.toSeq.sortBy(_.id).foreach(stmt => writeStatement(stmt, writer))
}

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(source,
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
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
* standard</a>. 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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ object ScoverageXmlReader {
id = id + 1

coverage add Statement(
source.text,
location,
id,
start.text.toInt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
</statement>
case false =>
<statement package={stmt.location.packageName}
Expand Down Expand Up @@ -101,5 +101,29 @@ class ScoverageXmlWriter(sourceDirectories: Seq[File], outputDir: File, debug: B
</classes>
</package>
}
/**
* 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
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
* standard</a>. 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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading