Skip to content

Commit a3f67fd

Browse files
committed
Make coverage support incremental compilation
1 parent c0f7c2a commit a3f67fd

File tree

2 files changed

+89
-5
lines changed

2 files changed

+89
-5
lines changed

compiler/src/dotty/tools/dotc/coverage/Serializer.scala

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
package dotty.tools.dotc
22
package coverage
33

4+
import java.nio.charset.StandardCharsets.UTF_8
45
import java.nio.file.{Path, Paths, Files}
56
import java.io.Writer
67
import scala.collection.mutable.StringBuilder
8+
import scala.io.Source
79

810
/**
911
* Serializes scoverage data.
10-
* @see https://github.com/scoverage/scalac-scoverage-plugin/blob/main/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala
12+
* @see https://github.com/scoverage/scalac-scoverage-plugin/blob/main/serializer/src/main/scala/scoverage/serialize/Serializer.scala
1113
*/
1214
object Serializer:
1315

1416
private val CoverageFileName = "scoverage.coverage"
1517
private val CoverageDataFormatVersion = "3.0"
1618

19+
def coverageFilePath(dataDir: String) =
20+
Paths.get(dataDir, CoverageFileName).toAbsolutePath
21+
1722
/** Write out coverage data to the given data directory, using the default coverage filename */
1823
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit =
19-
serialize(coverage, Paths.get(dataDir, CoverageFileName).toAbsolutePath, Paths.get(sourceRoot).toAbsolutePath)
24+
serialize(coverage, coverageFilePath(dataDir), Paths.get(sourceRoot).toAbsolutePath)
2025

2126
/** Write out coverage data to a file. */
2227
def serialize(coverage: Coverage, file: Path, sourceRoot: Path): Unit =
@@ -85,6 +90,64 @@ object Serializer:
8590
.sortBy(_.id)
8691
.foreach(stmt => writeStatement(stmt, writer))
8792

93+
def deserialize(file: Path): Coverage =
94+
val source = Source.fromFile(file.toFile(), UTF_8.name())
95+
try deserialize(source.getLines())
96+
finally source.close()
97+
98+
def deserialize(lines: Iterator[String]): Coverage =
99+
def toStatement(lines: Iterator[String]): Statement =
100+
val id: Int = lines.next().toInt
101+
val sourcePath = lines.next()
102+
val packageName = lines.next()
103+
val className = lines.next()
104+
val classType = lines.next()
105+
val fullClassName = lines.next()
106+
val method = lines.next()
107+
val loc = Location(
108+
packageName,
109+
className,
110+
fullClassName,
111+
classType,
112+
method,
113+
Paths.get(sourcePath)
114+
)
115+
val start: Int = lines.next().toInt
116+
val end: Int = lines.next().toInt
117+
val lineNo: Int = lines.next().toInt
118+
val symbolName: String = lines.next()
119+
val treeName: String = lines.next()
120+
val branch: Boolean = lines.next().toBoolean
121+
val count: Int = lines.next().toInt
122+
val ignored: Boolean = lines.next().toBoolean
123+
val desc = lines.toList.mkString("\n")
124+
Statement(
125+
loc,
126+
id,
127+
start,
128+
end,
129+
lineNo,
130+
desc,
131+
symbolName,
132+
treeName,
133+
branch,
134+
ignored
135+
)
136+
137+
val headerFirstLine = lines.next()
138+
require(
139+
headerFirstLine == s"# Coverage data, format version: $CoverageDataFormatVersion",
140+
"Wrong file format"
141+
)
142+
143+
val linesWithoutHeader = lines.dropWhile(_.startsWith("#"))
144+
val coverage = Coverage()
145+
while !linesWithoutHeader.isEmpty do
146+
val oneStatementLines = linesWithoutHeader.takeWhile(_ != "\f")
147+
coverage.addStatement(toStatement(oneStatementLines))
148+
end while
149+
coverage
150+
88151
/** Makes a String suitable for output in the coverage statement data as a single line.
89152
* Escaped characters: '\\' (backslash), '\n', '\r', '\f'
90153
*/

compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dotty.tools.dotc
22
package transform
33

44
import java.io.File
5+
import java.nio.file.Files
56

67
import ast.tpd.*
78
import collection.mutable
@@ -41,6 +42,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
4142

4243
private var coverageExcludeClasslikePatterns: List[Pattern] = Nil
4344
private var coverageExcludeFilePatterns: List[Pattern] = Nil
45+
private var lastCompiledFiles: Set[String] = Set.empty
4446

4547
override def run(using ctx: Context): Unit =
4648
val outputPath = ctx.settings.coverageOutputDir.value
@@ -50,12 +52,18 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
5052
val newlyCreated = dataDir.mkdirs()
5153

5254
if !newlyCreated then
53-
// If the directory existed before, let's clean it up.
55+
// If the directory existed before, clean measurement files.
5456
dataDir.listFiles
55-
.filter(_.getName.startsWith("scoverage"))
57+
.filter(_.getName.startsWith("scoverage.measurements."))
5658
.foreach(_.delete())
5759
end if
5860

61+
val coverageFilePath = Serializer.coverageFilePath(outputPath)
62+
val previousCoverage =
63+
if Files.exists(coverageFilePath) then
64+
Serializer.deserialize(coverageFilePath)
65+
else Coverage()
66+
5967
// Initialise a coverage object if it does not exist yet
6068
if ctx.base.coverage == null then
6169
ctx.base.coverage = Coverage()
@@ -66,7 +74,19 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6674
ctx.base.coverage.nn.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath)
6775
super.run
6876

69-
Serializer.serialize(ctx.base.coverage.nn, outputPath, ctx.settings.sourceroot.value)
77+
val mergedCoverage = Coverage()
78+
79+
previousCoverage.statements
80+
.filterNot(stmt =>
81+
val source = stmt.location.sourcePath
82+
lastCompiledFiles.contains(source.toString) || !Files.exists(source)
83+
)
84+
.foreach { stmt =>
85+
mergedCoverage.addStatement(stmt)
86+
}
87+
ctx.base.coverage.nn.statements.foreach(stmt => mergedCoverage.addStatement(stmt))
88+
89+
Serializer.serialize(mergedCoverage, outputPath, ctx.settings.sourceroot.value)
7090

7191
private def isClassIncluded(sym: Symbol)(using Context): Boolean =
7292
val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show
@@ -253,6 +273,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
253273
InstrumentedParts.singleExprTree(coverageCall, transformed)
254274

255275
override def transform(tree: Tree)(using Context): Tree =
276+
lastCompiledFiles += tree.sourcePos.source.file.absolute.jpath.toString
256277
inContext(transformCtx(tree)) { // necessary to position inlined code properly
257278
tree match
258279
// simple cases

0 commit comments

Comments
 (0)