Skip to content

Commit 2017109

Browse files
committed
Add tests and fix implementation
1 parent 28325c6 commit 2017109

File tree

4 files changed

+80
-12
lines changed

4 files changed

+80
-12
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import java.nio.file.Path
88
class Coverage:
99
private val statementsById = new mutable.LongMap[Statement](256)
1010

11-
private var statementId: Int = 0
12-
13-
def nextStatementId(): Int =
14-
statementId += 1
15-
statementId - 1
11+
private var _nextStatementId: Int = 1
1612

13+
def nextStatementId(): Int = _nextStatementId
14+
def setNextStatementId(id: Int): Unit = _nextStatementId = id
1715

1816
def statements: Iterable[Statement] = statementsById.values
1917

20-
def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt
18+
def addStatement(stmt: Statement): Unit =
19+
if stmt.id >= _nextStatementId then _nextStatementId = stmt.id + 1
20+
statementsById(stmt.id) = stmt
2121

2222
def removeStatementsFromFile(sourcePath: Path | Null) =
2323
val removedIds = statements.filter(_.location.sourcePath == sourcePath).map(_.id.toLong)

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ object Serializer:
1616
private val CoverageFileName = "scoverage.coverage"
1717
private val CoverageDataFormatVersion = "3.0"
1818

19-
def coverageFilePath(dataDir: String) =
19+
def coverageFilePath(dataDir: String): Path =
2020
Paths.get(dataDir, CoverageFileName).toAbsolutePath
2121

2222
/** Write out coverage data to the given data directory, using the default coverage filename */
@@ -90,12 +90,12 @@ object Serializer:
9090
.sortBy(_.id)
9191
.foreach(stmt => writeStatement(stmt, writer))
9292

93-
def deserialize(file: Path): Coverage =
93+
def deserialize(file: Path, sourceRoot: String): Coverage =
9494
val source = Source.fromFile(file.toFile(), UTF_8.name())
95-
try deserialize(source.getLines())
95+
try deserialize(source.getLines(), Paths.get(sourceRoot).toAbsolutePath)
9696
finally source.close()
9797

98-
def deserialize(lines: Iterator[String]): Coverage =
98+
def deserialize(lines: Iterator[String], sourceRoot: Path): Coverage =
9999
def toStatement(lines: Iterator[String]): Statement =
100100
val id: Int = lines.next().toInt
101101
val sourcePath = lines.next()
@@ -110,7 +110,7 @@ object Serializer:
110110
fullClassName,
111111
classType,
112112
method,
113-
Paths.get(sourcePath)
113+
sourceRoot.resolve(sourcePath)
114114
)
115115
val start: Int = lines.next().toInt
116116
val end: Int = lines.next().toInt

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6161
val coverageFilePath = Serializer.coverageFilePath(outputPath)
6262
val previousCoverage =
6363
if Files.exists(coverageFilePath) then
64-
Serializer.deserialize(coverageFilePath)
64+
Serializer.deserialize(coverageFilePath, ctx.settings.sourceroot.value)
6565
else Coverage()
6666

6767
// Initialise a coverage object if it does not exist yet
@@ -72,6 +72,8 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
7272
coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern)
7373

7474
ctx.base.coverage.nn.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath)
75+
ctx.base.coverage.nn.setNextStatementId(previousCoverage.nextStatementId())
76+
7577
super.run
7678

7779
val mergedCoverage = Coverage()

compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import scala.language.unsafeNulls
1818
import scala.collection.mutable.Buffer
1919
import dotty.tools.dotc.util.DiffUtil
2020

21+
import java.nio.charset.StandardCharsets
2122
import java.util.stream.Collectors
2223

2324
@Category(Array(classOf[BootstrappedOnlyTests]))
@@ -127,6 +128,71 @@ class CoverageTests:
127128
)
128129
}
129130

131+
@Test
132+
def checkIncrementalCoverage(): Unit =
133+
val target = Files.createTempDirectory("coverage")
134+
val sourceRoot = target.resolve("src")
135+
Files.createDirectory(sourceRoot)
136+
val sourceFile1 = sourceRoot.resolve("file1.scala")
137+
Files.write(sourceFile1, "def file1() = 1".getBytes(StandardCharsets.UTF_8))
138+
139+
val coverageOut = target.resolve("coverage-out")
140+
Files.createDirectory(coverageOut)
141+
val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", coverageOut.toString, "-sourceroot", sourceRoot.toString)
142+
compileFile(sourceFile1.toString, options).checkCompile()
143+
144+
val scoverageFile = coverageOut.resolve("scoverage.coverage")
145+
assert(Files.exists(scoverageFile), s"Expected scoverage file to exist at $scoverageFile")
146+
147+
locally {
148+
val coverage = Serializer.deserialize(scoverageFile, sourceRoot.toString())
149+
val filesWithCoverage = coverage.statements.map(_.location.sourcePath.getFileName.toString).toSet
150+
assertEquals(Set("file1.scala"), filesWithCoverage)
151+
}
152+
153+
val sourceFile2 = sourceRoot.resolve("file2.scala")
154+
Files.write(sourceFile2, "def file2() = 2".getBytes(StandardCharsets.UTF_8))
155+
156+
compileFile(sourceFile2.toString, options).checkCompile()
157+
locally {
158+
val coverage = Serializer.deserialize(scoverageFile, sourceRoot.toString())
159+
val filesWithCoverage = coverage.statements.map(_.location.sourcePath.getFileName.toString).toSet
160+
assertEquals(Set("file1.scala", "file2.scala"), filesWithCoverage)
161+
}
162+
163+
@Test
164+
def `deleted source files should not be kept in incremental coverage`(): Unit =
165+
val target = Files.createTempDirectory("coverage")
166+
val sourceRoot = target.resolve("src")
167+
Files.createDirectory(sourceRoot)
168+
val sourceFile1 = sourceRoot.resolve("file1.scala")
169+
Files.write(sourceFile1, "def file1() = 1".getBytes(StandardCharsets.UTF_8))
170+
171+
val coverageOut = target.resolve("coverage-out")
172+
Files.createDirectory(coverageOut)
173+
val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", coverageOut.toString, "-sourceroot", sourceRoot.toString)
174+
compileFile(sourceFile1.toString, options).checkCompile()
175+
176+
val scoverageFile = coverageOut.resolve("scoverage.coverage")
177+
assert(Files.exists(scoverageFile), s"Expected scoverage file to exist at $scoverageFile")
178+
179+
locally {
180+
val coverage = Serializer.deserialize(scoverageFile, sourceRoot.toString())
181+
val filesWithCoverage = coverage.statements.map(_.location.sourcePath.getFileName.toString).toSet
182+
assertEquals(Set("file1.scala"), filesWithCoverage)
183+
}
184+
185+
val sourceFile2 = sourceRoot.resolve("file2.scala")
186+
Files.write(sourceFile2, "def file2() = 2".getBytes(StandardCharsets.UTF_8))
187+
188+
Files.delete(sourceFile1)
189+
190+
compileFile(sourceFile2.toString, options).checkCompile()
191+
locally {
192+
val coverage = Serializer.deserialize(scoverageFile, sourceRoot.toString())
193+
val filesWithCoverage = coverage.statements.map(_.location.sourcePath.getFileName.toString).toSet
194+
assertEquals(Set("file2.scala"), filesWithCoverage)
195+
}
130196

131197
object CoverageTests extends ParallelTesting:
132198
import scala.concurrent.duration.*

0 commit comments

Comments
 (0)