Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
pkskpro committed Jan 11, 2025
2 parents 69cc849 + 5176f9f commit 535aad4
Show file tree
Hide file tree
Showing 24 changed files with 379 additions and 32 deletions.
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import util.Store
import plugins.*
import java.util.concurrent.atomic.AtomicInteger
import java.nio.file.InvalidPathException
import dotty.tools.dotc.coverage.Coverage

object Contexts {

Expand Down Expand Up @@ -982,6 +983,11 @@ object Contexts {
/** Was best effort file used during compilation? */
private[core] var usedBestEffortTasty = false

/** If coverage option is used, it stores all instrumented statements (for InstrumentCoverage).
* We need this information to be persisted across different runs, so it's stored here.
*/
private[dotc] var coverage: Coverage | Null = null

// Types state
/** A table for hash consing unique types */
private[core] val uniques: Uniques = Uniques()
Expand Down
12 changes: 12 additions & 0 deletions compiler/src/dotty/tools/dotc/coverage/Coverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@ package dotty.tools.dotc
package coverage

import scala.collection.mutable
import java.nio.file.Path

/** Holds a list of statements to include in the coverage reports. */
class Coverage:
private val statementsById = new mutable.LongMap[Statement](256)

private var statementId: Int = 0

def nextStatementId(): Int =
statementId += 1
statementId - 1


def statements: Iterable[Statement] = statementsById.values

def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt

def removeStatementsFromFile(sourcePath: Path) =
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.
Expand Down
18 changes: 8 additions & 10 deletions compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
override def isEnabled(using ctx: Context) =
ctx.settings.coverageOutputDir.value.nonEmpty

// counter to assign a unique id to each statement
private var statementId = 0

// stores all instrumented statements
private val coverage = Coverage()

private var coverageExcludeClasslikePatterns: List[Pattern] = Nil
private var coverageExcludeFilePatterns: List[Pattern] = Nil

Expand All @@ -61,12 +55,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
.foreach(_.nn.delete())
end if

// Initialise a coverage object if it does not exist yet
if ctx.base.coverage == null then
ctx.base.coverage = Coverage()

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)
super.run

Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value)
Serializer.serialize(ctx.base.coverage.nn, outputPath, ctx.settings.sourceroot.value)

private def isClassIncluded(sym: Symbol)(using Context): Boolean =
val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show
Expand Down Expand Up @@ -110,8 +109,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
* @return the statement's id
*/
private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int =
val id = statementId
statementId += 1
val id = ctx.base.coverage.nn.nextStatementId()

val sourceFile = pos.source
val statement = Statement(
Expand All @@ -127,7 +125,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
treeName = tree.getClass.getSimpleName.nn,
branch
)
coverage.addStatement(statement)
ctx.base.coverage.nn.addStatement(statement)
id

/**
Expand Down
41 changes: 28 additions & 13 deletions compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,27 @@ class CoverageTests:
lines
end fixWindowsPaths

def runOnFile(p: Path): Boolean =
scalaFile.matches(p) &&
(Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains))
def runOnFileOrDir(p: Path): Boolean =
(scalaFile.matches(p) || Files.isDirectory(p))
&& (p != dir)
&& (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains))

Files.walk(dir, 1).filter(runOnFileOrDir).forEach(path => {
// measurement files only exist in the "run" category
// as these are generated at runtime by the scala.runtime.coverage.Invoker
val (targetDir, expectFile, expectMeasurementFile) =
if Files.isDirectory(path) then
val dirName = path.getFileName().toString
assert(!Files.walk(path).filter(scalaFile.matches(_)).toArray.isEmpty, s"No scala files found in test directory: ${path}")
val targetDir = computeCoverageInTmp(path, isDirectory = true, dir, run)
(targetDir, path.resolve(s"test.scoverage.check"), path.resolve(s"test.measurement.check"))
else
val fileName = path.getFileName.toString.stripSuffix(".scala")
val targetDir = computeCoverageInTmp(path, isDirectory = false, dir, run)
(targetDir, path.resolveSibling(s"${fileName}.scoverage.check"), path.resolveSibling(s"${fileName}.measurement.check"))

Files.walk(dir).filter(runOnFile).forEach(path => {
val fileName = path.getFileName.toString.stripSuffix(".scala")
val targetDir = computeCoverageInTmp(path, dir, run)
val targetFile = targetDir.resolve(s"scoverage.coverage")
val expectFile = path.resolveSibling(s"$fileName.scoverage.check")

if updateCheckFiles then
Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING)
else
Expand All @@ -72,9 +84,6 @@ class CoverageTests:
val instructions = FileDiff.diffMessage(expectFile.toString, targetFile.toString)
fail(s"Coverage report differs from expected data.\n$instructions")

// measurement files only exist in the "run" category
// as these are generated at runtime by the scala.runtime.coverage.Invoker
val expectMeasurementFile = path.resolveSibling(s"$fileName.measurement.check")
if run && Files.exists(expectMeasurementFile) then

// Note that this assumes that the test invoked was single threaded,
Expand All @@ -95,14 +104,20 @@ class CoverageTests:
})

/** Generates the coverage report for the given input file, in a temporary directory. */
def computeCoverageInTmp(inputFile: Path, sourceRoot: Path, run: Boolean)(using TestGroup): Path =
def computeCoverageInTmp(inputFile: Path, isDirectory: Boolean, sourceRoot: Path, run: Boolean)(using TestGroup): Path =
val target = Files.createTempDirectory("coverage")
val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-sourceroot", sourceRoot.toString)
if run then
val test = compileDir(inputFile.getParent.toString, options)
val path = if isDirectory then inputFile.toString else inputFile.getParent.toString
val test = compileDir(path, options)
test.checkFilePaths.foreach { checkFilePath =>
assert(checkFilePath.exists, s"Expected checkfile for $path $checkFilePath does not exist.")
}
test.checkRuns()
else
val test = compileFile(inputFile.toString, options)
val test =
if isDirectory then compileDir(inputFile.toString, options)
else compileFile(inputFile.toString, options)
test.checkCompile()
target

Expand Down
24 changes: 15 additions & 9 deletions compiler/test/dotty/tools/vulpix/ParallelTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
* For a given test source, returns a check file against which the result of the test run
* should be compared. Is used by implementations of this trait.
*/
final def checkFile(testSource: TestSource): Option[JFile] = (testSource match {
case ts: JointCompilationSource =>
ts.files.collectFirst {
case f if !f.isDirectory =>
new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check"))
}
case ts: SeparateCompilationSource =>
Option(new JFile(ts.dir.getPath + ".check"))
}).filter(_.exists)
final def checkFile(testSource: TestSource): Option[JFile] = (CompilationLogic.checkFilePath(testSource)).filter(_.exists)

/**
* Checks if the given actual lines are the same as the ones in the check file.
Expand Down Expand Up @@ -343,6 +335,18 @@ trait ParallelTesting extends RunnerOrchestration { self =>
}
}

object CompilationLogic {
private[ParallelTesting] def checkFilePath(testSource: TestSource) = testSource match {
case ts: JointCompilationSource =>
ts.files.collectFirst {
case f if !f.isDirectory =>
new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check"))
}
case ts: SeparateCompilationSource =>
Option(new JFile(ts.dir.getPath + ".check"))
}
}

/** Each `Test` takes the `testSources` and performs the compilation and assertions
* according to the implementing class "neg", "run" or "pos".
*/
Expand Down Expand Up @@ -1157,6 +1161,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
def this(targets: List[TestSource]) =
this(targets, 1, true, None, false, false)

def checkFilePaths: List[JFile] = targets.map(CompilationLogic.checkFilePath).flatten

def copy(targets: List[TestSource],
times: Int = times,
shouldDelete: Boolean = shouldDelete,
Expand Down
1 change: 1 addition & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ object Build {

// Avoid various sbt craziness involving classloaders and parallelism
run / fork := true,
run / connectInput := true,
Test / fork := true,
Test / parallelExecution := false,

Expand Down
13 changes: 13 additions & 0 deletions tests/coverage/pos/macro-late-suspend/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package example

sealed trait Test

object Test {
case object Foo extends Test

val visitorType = mkVisitorType[Test]

trait Visitor[A] {
type V[a] = visitorType.Out[a]
}
}
3 changes: 3 additions & 0 deletions tests/coverage/pos/macro-late-suspend/UsesTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package example

val _ = Test.Foo
12 changes: 12 additions & 0 deletions tests/coverage/pos/macro-late-suspend/VisitorMacros.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package example

import scala.quoted.*

private def mkVisitorTypeImpl[T: Type](using q: Quotes): Expr[VisitorType[T]] =
'{new VisitorType[T]{}}

transparent inline def mkVisitorType[T]: VisitorType[T] = ${ mkVisitorTypeImpl[T] }

trait VisitorType[T] {
type Out[A]
}
88 changes: 88 additions & 0 deletions tests/coverage/pos/macro-late-suspend/test.scoverage.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Coverage data, format version: 3.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)
# ' ' sign
# ------------------------------------------
1
macro-late-suspend/VisitorMacros.scala
example
VisitorMacros$package
Object
example.VisitorMacros$package
mkVisitorTypeImpl
124
144
6
unpickleExprV2
Apply
false
0
false
new VisitorType[T]{}

2
macro-late-suspend/VisitorMacros.scala
example
VisitorMacros$package
Object
example.VisitorMacros$package
mkVisitorTypeImpl
40
69
5
mkVisitorTypeImpl
DefDef
false
0
false
private def mkVisitorTypeImpl

3
macro-late-suspend/Test.scala
example
Test
Object
example.Test
<init>
102
121
8
<init>
Apply
false
0
false
mkVisitorType[Test]

4
macro-late-suspend/UsesTest.scala
example
UsesTest$package
Object
example.UsesTest$package
<init>
22
22
3
<none>
Literal
true
0
false


1 change: 1 addition & 0 deletions tests/coverage/run/i16940/i16940.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2
2 changes: 2 additions & 0 deletions tests/coverage/run/i18233-min/i182233-min.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
List()
List(abc, def)
1 change: 1 addition & 0 deletions tests/coverage/run/i18233/i18233.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Baz
File renamed without changes.
1 change: 1 addition & 0 deletions tests/coverage/run/macro-suspend/Macro.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
>>> hello <<<
8 changes: 8 additions & 0 deletions tests/coverage/run/macro-suspend/Macro.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.quoted.{Expr, Quotes}

object Macro:
inline def decorate(inline s: String): String = ${ decorateQuotes('s) }
def decorateQuotes(s: Expr[String])(using Quotes): Expr[String] = '{ ">>> " + $s + " <<<" }

object Greeting:
def greet() = "hello"
4 changes: 4 additions & 0 deletions tests/coverage/run/macro-suspend/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Test:
def main(args: Array[String]): Unit =
println(Macro.decorate(Greeting.greet()))

Loading

0 comments on commit 535aad4

Please sign in to comment.