diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index dde8adcf8659..3c5297334a3b 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -19,11 +19,6 @@ class Coverage: if stmt.id >= _nextStatementId then _nextStatementId = stmt.id + 1 statementsById(stmt.id) = stmt - def removeStatementsFromFile(sourcePath: Path | Null) = - val removedIds = statements.filter(_.location.sourcePath == sourcePath).map(_.id.toLong) - removedIds.foreach(statementsById.remove) - - /** * A statement that can be invoked, and thus counted as "covered" by code coverage tools. * diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index e0878f34ca22..7528f5c01f57 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc package transform import java.io.File -import java.nio.file.Files +import java.nio.file.{Files, Path} import ast.tpd.* import collection.mutable @@ -42,54 +42,61 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: private var coverageExcludeClasslikePatterns: List[Pattern] = Nil private var coverageExcludeFilePatterns: List[Pattern] = Nil - private var lastCompiledFiles: Set[String] = Set.empty - override def run(using ctx: Context): Unit = + override def runOn(units: List[CompilationUnit])(using ctx: Context): List[CompilationUnit] = val outputPath = ctx.settings.coverageOutputDir.value - // Ensure the dir exists + // Ensure the dir exists (once per batch, not per unit) val dataDir = File(outputPath) val newlyCreated = dataDir.mkdirs() if !newlyCreated then // If the directory existed before, clean measurement files. - dataDir.listFiles - .filter(_.getName.startsWith("scoverage.measurements.")) - .foreach(_.delete()) + val files = dataDir.listFiles + if files != null then + files + .filter(_.getName.startsWith("scoverage.measurements.")) + .foreach(_.delete()) end if + // Deserialize previous coverage once at the start val coverageFilePath = Serializer.coverageFilePath(outputPath) val previousCoverage = if Files.exists(coverageFilePath) then Serializer.deserialize(coverageFilePath, ctx.settings.sourceroot.value) else Coverage() - // Initialise a coverage object if it does not exist yet - if ctx.base.coverage == null then - ctx.base.coverage = Coverage() - + // Initialize coverage patterns once coverageExcludeClasslikePatterns = ctx.settings.coverageExcludeClasslikes.value.map(_.r.pattern) coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern) - ctx.base.coverage.nn.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath) + // Initialize coverage object + ctx.base.coverage = Coverage() ctx.base.coverage.nn.setNextStatementId(previousCoverage.nextStatementId()) - super.run + // Run the transformation on all units + val result = super.runOn(units) + // Serialize once at the end with merged coverage val mergedCoverage = Coverage() + val currentFiles = units.map(_.source.file.absolute.jpath) + // Add statements from previous coverage that aren't from recompiled files + // and whose source files still exist previousCoverage.statements .filterNot(stmt => val source = stmt.location.sourcePath - lastCompiledFiles.contains(source.toString) || !Files.exists(source) + currentFiles.contains(source) || !Files.exists(source) ) - .foreach { stmt => - mergedCoverage.addStatement(stmt) - } - ctx.base.coverage.nn.statements.foreach(stmt => mergedCoverage.addStatement(stmt)) + .foreach(mergedCoverage.addStatement) + + // Add all new statements from this compilation + ctx.base.coverage.nn.statements.foreach(mergedCoverage.addStatement) Serializer.serialize(mergedCoverage, outputPath, ctx.settings.sourceroot.value) + result + private def isClassIncluded(sym: Symbol)(using Context): Boolean = val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show coverageExcludeClasslikePatterns.isEmpty || !coverageExcludeClasslikePatterns.exists( @@ -275,9 +282,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: InstrumentedParts.singleExprTree(coverageCall, transformed) override def transform(tree: Tree)(using Context): Tree = - val path = tree.sourcePos.source.file.absolute.jpath - if path != null then lastCompiledFiles += path.toString - inContext(transformCtx(tree)) { // necessary to position inlined code properly tree match // simple cases