diff --git a/NOTICE.md b/NOTICE.md index 64b5f9122db7..64ebae49efe5 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -88,6 +88,10 @@ major authors were omitted by oversight. docs/js/. Please refer to the license header of the concerned files for details. + * dotty.tools.dotc.coverage: Coverage instrumentation utilities have been + adapted from the scoverage plugin for scala 2 [5], which is under the + Apache 2.0 license. + * The Dotty codebase contains parts which are derived from the ScalaPB protobuf library [4], which is under the Apache 2.0 license. @@ -96,3 +100,4 @@ major authors were omitted by oversight. [2] https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt [3] https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt [4] https://github.com/lampepfl/dotty/pull/5783/files +[5] https://github.com/scoverage/scalac-scoverage-plugin diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 44e28bcdd446..51891beef79f 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -59,6 +59,7 @@ class Compiler { /** Phases dealing with the transformation from pickled trees to backend trees */ protected def transformPhases: List[List[Phase]] = + List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage-out is set) List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 0cb2ed4d7acc..117c5217f15e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1437,6 +1437,7 @@ object desugar { ValDef(param.name, param.tpt, selector(idx)) .withSpan(param.span) .withAttachment(UntupledParam, ()) + .withFlags(Synthetic) } Function(param :: Nil, Block(vdefs, body)) } @@ -1693,7 +1694,7 @@ object desugar { case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags) } RefinedTypeTree(polyFunctionTpt, List( - DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree) + DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree).withFlags(Synthetic) )) } else { diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index ea41984c5766..183854f3aede 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -105,12 +105,14 @@ object MainProxies { .filterNot(_.matches(defn.MainAnnot)) .map(annot => insertTypeSplices.transform(annot.tree)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) - .withFlags(JavaStatic) + .withFlags(JavaStatic | Synthetic) .withAnnotations(annots) val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) .withFlags(Final | Invisible) - if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil + + if (!ctx.reporter.hasErrors) + result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil } result } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index cb20a7c9da37..c3e826578906 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -610,7 +610,7 @@ object Trees { override def toString = s"InlineMatch($selector, $cases)" } - /** case pat if guard => body; only appears as child of a Match */ + /** case pat if guard => body */ case class CaseDef[-T >: Untyped] private[ast] (pat: Tree[T], guard: Tree[T], body: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] = CaseDef[T] @@ -1367,13 +1367,26 @@ object Trees { /** The context to use when mapping or accumulating over a tree */ def localCtx(tree: Tree)(using Context): Context + /** The context to use when transforming a tree. + * It ensures that the source is correct, and that the local context is used if + * that's necessary for transforming the whole tree. + * TODO: ensure transform is always called with the correct context as argument + * @see https://github.com/lampepfl/dotty/pull/13880#discussion_r836395977 + */ + def transformCtx(tree: Tree)(using Context): Context = + val sourced = + if tree.source.exists && tree.source != ctx.source + then ctx.withSource(tree.source) + else ctx + tree match + case t: (MemberDef | PackageDef | LambdaTypeTree | TermLambdaTypeTree) => + localCtx(t)(using sourced) + case _ => + sourced + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(using Context): Tree = { - inContext( - if tree.source != ctx.source && tree.source.exists - then ctx.withSource(tree.source) - else ctx - ){ + inContext(transformCtx(tree)) { Stats.record(s"TreeMap.transform/$getClass") if (skipTransform(tree)) tree else tree match { @@ -1430,13 +1443,9 @@ object Trees { case AppliedTypeTree(tpt, args) => cpy.AppliedTypeTree(tree)(transform(tpt), transform(args)) case LambdaTypeTree(tparams, body) => - inContext(localCtx(tree)) { - cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) - } + cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) case TermLambdaTypeTree(params, body) => - inContext(localCtx(tree)) { - cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) - } + cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) case MatchTypeTree(bound, selector, cases) => cpy.MatchTypeTree(tree)(transform(bound), transform(selector), transformSub(cases)) case ByNameTypeTree(result) => @@ -1452,19 +1461,13 @@ object Trees { case EmptyValDef => tree case tree @ ValDef(name, tpt, _) => - inContext(localCtx(tree)) { - val tpt1 = transform(tpt) - val rhs1 = transform(tree.rhs) - cpy.ValDef(tree)(name, tpt1, rhs1) - } + val tpt1 = transform(tpt) + val rhs1 = transform(tree.rhs) + cpy.ValDef(tree)(name, tpt1, rhs1) case tree @ DefDef(name, paramss, tpt, _) => - inContext(localCtx(tree)) { - cpy.DefDef(tree)(name, transformParamss(paramss), transform(tpt), transform(tree.rhs)) - } + cpy.DefDef(tree)(name, transformParamss(paramss), transform(tpt), transform(tree.rhs)) case tree @ TypeDef(name, rhs) => - inContext(localCtx(tree)) { - cpy.TypeDef(tree)(name, transform(rhs)) - } + cpy.TypeDef(tree)(name, transform(rhs)) case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty => cpy.Template(tree)(transformSub(constr), transform(tree.parents), Nil, transformSub(self), transformStats(tree.body, tree.symbol)) case Import(expr, selectors) => @@ -1472,10 +1475,7 @@ object Trees { case Export(expr, selectors) => cpy.Export(tree)(transform(expr), selectors) case PackageDef(pid, stats) => - val pid1 = transformSub(pid) - inContext(localCtx(tree)) { - cpy.PackageDef(tree)(pid1, transformStats(stats, ctx.owner)) - } + cpy.PackageDef(tree)(transformSub(pid), transformStats(stats, ctx.owner)) case Annotated(arg, annot) => cpy.Annotated(tree)(transform(arg), transform(annot)) case Thicket(trees) => diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8b163acb1fa9..7a00c1d5e3ed 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -117,6 +117,9 @@ trait CommonScalaSettings: val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) + /* Coverage settings */ + val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out")) + /* Other settings */ val encoding: Setting[String] = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) val usejavacp: Setting[Boolean] = BooleanSetting("-usejavacp", "Utilize the java.class.path in classpath resolution.", aliases = List("--use-java-class-path")) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dc672690702c..5eb1ccb0f957 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -13,6 +13,7 @@ import typer.ImportInfo.RootRef import Comments.CommentsContext import Comments.Comment import util.Spans.NoSpan +import Symbols.requiredModuleRef import scala.annotation.tailrec @@ -460,6 +461,9 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef + @tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker") + @tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked") + @tu lazy val ImplicitScrutineeTypeSym = newPermanentSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala new file mode 100644 index 000000000000..8ae249c1f5a3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -0,0 +1,27 @@ +package dotty.tools.dotc +package coverage + +import scala.collection.mutable + +/** Holds a list of statements to include in the coverage reports. */ +class Coverage: + private val statementsById = new mutable.LongMap[Statement](256) + + def statements: Iterable[Statement] = statementsById.values + + def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt + +/** A statement that can be invoked, and thus counted as "covered" by code coverage tools. */ +case class Statement( + source: String, + location: Location, + id: Int, + start: Int, + end: Int, + line: Int, + desc: String, + symbolName: String, + treeName: String, + branch: Boolean, + ignored: Boolean = false +) \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala new file mode 100644 index 000000000000..faf1e97d0c01 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -0,0 +1,47 @@ +package dotty.tools.dotc +package coverage + +import ast.tpd._ +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags.* +import java.nio.file.Path + +/** Information about the location of a coverable piece of code. + * + * @param packageName name of the enclosing package + * @param className name of the closest enclosing class + * @param fullClassName fully qualified name of the closest enclosing class + * @param classType "type" of the closest enclosing class: Class, Trait or Object + * @param method name of the closest enclosing method + * @param sourcePath absolute path of the source file + */ +final case class Location( + packageName: String, + className: String, + fullClassName: String, + classType: String, + method: String, + sourcePath: Path +) + +object Location: + /** Extracts the location info of a Tree. */ + def apply(tree: Tree)(using ctx: Context): Location = + + val enclosingClass = ctx.owner.denot.enclosingClass + val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString + val className = enclosingClass.name.toSimpleName.toString + + val classType: String = + if enclosingClass.is(Trait) then "Trait" + else if enclosingClass.is(ModuleClass) then "Object" + else "Class" + + Location( + packageName, + className, + s"$packageName.$className", + classType, + ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(), + ctx.source.file.absolute.jpath + ) diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala new file mode 100644 index 000000000000..4bd0d89a0b84 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -0,0 +1,84 @@ +package dotty.tools.dotc +package coverage + +import java.nio.file.{Path, Paths, Files} +import java.io.Writer +import scala.language.unsafeNulls + +/** + * Serializes scoverage data. + * @see https://github.com/scoverage/scalac-scoverage-plugin/blob/main/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala + */ +object Serializer: + + private val CoverageFileName = "scoverage.coverage" + private val CoverageDataFormatVersion = "3.0" + + /** Write out coverage data to the given data directory, using the default coverage filename */ + def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit = + serialize(coverage, Paths.get(dataDir, CoverageFileName).toAbsolutePath, Paths.get(sourceRoot).toAbsolutePath) + + /** Write out coverage data to a file. */ + def serialize(coverage: Coverage, file: Path, sourceRoot: Path): Unit = + val writer = Files.newBufferedWriter(file) + try + serialize(coverage, writer, sourceRoot) + finally + writer.close() + + /** Write out coverage data (info about each statement that can be covered) to a writer. + */ + def serialize(coverage: Coverage, writer: Writer, sourceRoot: Path): Unit = + + def getRelativePath(filePath: Path): String = + val relPath = sourceRoot.relativize(filePath) + relPath.toString + + def writeHeader(writer: Writer): Unit = + writer.write(s"""# Coverage data, format version: $CoverageDataFormatVersion + |# 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 = + // Note: we write 0 for the count because we have not measured the actual coverage at this point + writer.write(s"""${stmt.id} + |${getRelativePath(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} + |0 + |${stmt.ignored} + |${stmt.desc} + |\f + |""".stripMargin) + + writeHeader(writer) + coverage.statements.toSeq + .sortBy(_.id) + .foreach(stmt => writeStatement(stmt, writer)) diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala new file mode 100644 index 000000000000..442bf27d9f6b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -0,0 +1,333 @@ +package dotty.tools.dotc +package transform + +import java.io.File +import java.util.concurrent.atomic.AtomicInteger + +import collection.mutable +import core.Flags.* +import core.Contexts.{Context, ctx, inContext} +import core.DenotTransformers.IdentityDenotTransformer +import core.Symbols.{defn, Symbol} +import core.Decorators.{toTermName, i} +import core.Constants.Constant +import core.NameOps.isContextFunction +import core.Types.* +import typer.LiftCoverage +import util.{SourcePosition, Property} +import util.Spans.Span +import coverage.* + +/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker + * ("instruments" the source code). + * The result can then be consumed by the Scoverage tool. + */ +class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: + import ast.tpd._ + + override def phaseName = InstrumentCoverage.name + + override def description = InstrumentCoverage.description + + // Enabled by argument "-coverage-out OUTPUT_DIR" + 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() + + override def run(using ctx: Context): Unit = + val outputPath = ctx.settings.coverageOutputDir.value + + // Ensure the dir exists + val dataDir = new File(outputPath) + val newlyCreated = dataDir.mkdirs() + + if !newlyCreated then + // If the directory existed before, let's clean it up. + dataDir.listFiles.nn + .filter(_.nn.getName.nn.startsWith("scoverage")) + .foreach(_.nn.delete()) + end if + super.run + + Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value) + + override protected def newTransformer(using Context) = CoverageTransformer() + + /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */ + private class CoverageTransformer extends Transformer: + override def transform(tree: Tree)(using ctx: Context): Tree = + inContext(transformCtx(tree)) { // necessary to position inlined code properly + tree match + // simple cases + case tree: (Import | Export | Literal | This | Super | New) => tree + case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident, TypTree, ... + + // branches + case tree: If => + cpy.If(tree)( + cond = transform(tree.cond), + thenp = instrument(transform(tree.thenp), branch = true), + elsep = instrument(transform(tree.elsep), branch = true) + ) + case tree: Try => + cpy.Try(tree)( + expr = instrument(transform(tree.expr), branch = true), + cases = instrumentCases(tree.cases), + finalizer = instrument(transform(tree.finalizer), branch = true) + ) + + // a.f(args) + case tree @ Apply(fun: Select, args) => + // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` + val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) + if canInstrumentApply(tree) then + if needsLift(tree) then + val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted + instrumentLifted(transformed) + else + val transformed = transformApply(tree, transformedFun) + instrument(transformed) + else + transformApply(tree, transformedFun) + + // f(args) + case tree: Apply => + if canInstrumentApply(tree) then + if needsLift(tree) then + instrumentLifted(tree) + else + instrument(transformApply(tree)) + else + transformApply(tree) + + // (f(x))[args] + case TypeApply(fun: Apply, args) => + cpy.TypeApply(tree)(transform(fun), args) + + // a.b + case Select(qual, name) => + if qual.symbol.exists && qual.symbol.is(JavaDefined) then + //Java class can't be used as a value, we can't instrument the + //qualifier ({;System}.xyz() is not possible !) instrument it + //as it is + instrument(tree) + else + val transformed = cpy.Select(tree)(transform(qual), name) + if transformed.qualifier.isDef then + // instrument calls to methods without parameter list + instrument(transformed) + else + transformed + + case tree: CaseDef => instrumentCaseDef(tree) + case tree: ValDef => + // only transform the rhs + val rhs = transform(tree.rhs) + cpy.ValDef(tree)(rhs = rhs) + + case tree: DefDef => + // Only transform the params (for the default values) and the rhs. + val paramss = transformParamss(tree.paramss) + val rhs = transform(tree.rhs) + val finalRhs = + if canInstrumentDefDef(tree) then + // Ensure that the rhs is always instrumented, if possible + instrumentBody(tree, rhs) + else + rhs + cpy.DefDef(tree)(tree.name, paramss, tree.tpt, finalRhs) + + case tree: PackageDef => + // only transform the statements of the package + cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) + case tree: Assign => + // only transform the rhs + cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) + + // For everything else just recurse and transform + // Special care for Templates: it's important to set the owner of the `stats`, like super.transform + case _ => + super.transform(tree) + } + + /** Lifts and instruments an application. + * Note that if only one arg needs to be lifted, we just lift everything. + */ + private def instrumentLifted(tree: Apply)(using Context) = + // lifting + val buffer = mutable.ListBuffer[Tree]() + val liftedApply = LiftCoverage.liftForCoverage(buffer, tree) + + // instrumentation + val instrumentedArgs = buffer.toList.map(transform) + val instrumentedApply = instrument(liftedApply) + Block( + instrumentedArgs, + instrumentedApply + ) + + private inline def transformApply(tree: Apply)(using Context): Apply = + transformApply(tree, transform(tree.fun)) + + private inline def transformApply(tree: Apply, transformedFun: Tree)(using Context): Apply = + cpy.Apply(tree)(transformedFun, transform(tree.args)) + + private inline def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = + cases.map(instrumentCaseDef) + + private def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = + val pat = tree.pat + val guard = tree.guard + val friendlyEnd = if guard.span.exists then guard.span.end else pat.span.end + val pos = tree.sourcePos.withSpan(tree.span.withEnd(friendlyEnd)) // user-friendly span + // ensure that the body is always instrumented by inserting a call to Invoker.invoked at its beginning + val instrumentedBody = instrument(transform(tree.body), pos, false) + cpy.CaseDef(tree)(tree.pat, transform(tree.guard), instrumentedBody) + + /** Records information about a new coverable statement. Generates a unique id for it. + * @return the statement's id + */ + private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int = + val id = statementId + statementId += 1 + val statement = new Statement( + source = ctx.source.file.name, + location = Location(tree), + id = id, + start = pos.start, + end = pos.end, + line = pos.line, + desc = tree.source.content.slice(pos.start, pos.end).mkString, + symbolName = tree.symbol.name.toSimpleName.toString, + treeName = tree.getClass.getSimpleName.nn, + branch + ) + coverage.addStatement(statement) + id + + private inline def syntheticSpan(pos: SourcePosition): Span = pos.span.toSynthetic + + /** Shortcut for instrument(tree, tree.sourcePos, branch) */ + private inline def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = + instrument(tree, tree.sourcePos, branch) + + /** Instruments a statement, if it has a position. */ + private def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using Context): Tree = + if pos.exists && !pos.span.isZeroExtent then + val statementId = recordStatement(tree, pos, branch) + insertInvokeCall(tree, pos, statementId) + else + tree + + /** Instruments the body of a DefDef. Handles corner cases. */ + private def instrumentBody(parent: DefDef, body: Tree)(using Context): Tree = + /* recurse on closures, so that we insert the call at the leaf: + + def g: (a: Ta) ?=> (b: Tb) = { + // nothing here <-- not here! + def $anonfun(using a: Ta) = + Invoked.invoked(id, DIR) <-- here + + closure($anonfun) + } + */ + body match + case b @ Block((meth: DefDef) :: Nil, closure: Closure) + if meth.symbol == closure.meth.symbol && defn.isContextFunctionType(body.tpe) => + val instr = cpy.DefDef(meth)(rhs = instrumentBody(parent, meth.rhs)) + cpy.Block(b)(instr :: Nil, closure) + case _ => + // compute user-friendly position to highlight more text in the coverage UI + val namePos = parent.namePos + val pos = namePos.withSpan(namePos.span.withStart(parent.span.start)) + // record info and insert call to Invoker.invoked + val statementId = recordStatement(parent, pos, false) + insertInvokeCall(body, pos, statementId) + + /** Returns the tree, prepended by a call to Invoker.invoker */ + private def insertInvokeCall(tree: Tree, pos: SourcePosition, statementId: Int)(using Context): Tree = + val callSpan = syntheticSpan(pos) + Block(invokeCall(statementId, callSpan) :: Nil, tree).withSpan(callSpan.union(tree.span)) + + /** Generates Invoked.invoked(id, DIR) */ + private def invokeCall(id: Int, span: Span)(using Context): Tree = + val outputPath = ctx.settings.coverageOutputDir.value + ref(defn.InvokedMethodRef).withSpan(span) + .appliedToArgs( + List(Literal(Constant(id)), Literal(Constant(outputPath))) + ).withSpan(span) + + /** + * Checks if the apply needs a lift in the coverage phase. + * In case of a nested application, we have to lift all arguments + * Example: + * ``` + * def T(x:Int)(y:Int) + * T(f())(1) + * ``` + * should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} + */ + private def needsLift(tree: Apply)(using Context): Boolean = + def isBooleanOperator(fun: Tree) = + // We don't want to lift a || getB(), to avoid calling getB if a is true. + // Same idea with a && getB(): if a is false, getB shouldn't be called. + val sym = fun.symbol + sym.exists && + sym == defn.Boolean_&& || sym == defn.Boolean_|| + + def isContextual(fun: Apply): Boolean = + val args = fun.args + args.nonEmpty && args.head.symbol.isAllOf(Given | Implicit) + + val fun = tree.fun + val nestedApplyNeedsLift = fun match + case a: Apply => needsLift(a) + case _ => false + + nestedApplyNeedsLift || + !isBooleanOperator(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) + + /** Check if the body of a DefDef can be instrumented with instrumentBody. */ + private def canInstrumentDefDef(tree: DefDef)(using Context): Boolean = + // No need to force the instrumentation of synthetic definitions + // (it would work, but it looks better without). + !tree.symbol.isOneOf(Accessor | Synthetic | Artifact) && + !tree.rhs.isEmpty + + /** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */ + private def canInstrumentApply(tree: Apply)(using Context): Boolean = + !tree.symbol.isOneOf(Synthetic | Artifact) && // no need to instrument synthetic apply + (tree.typeOpt match + case AppliedType(tycon: NamedType, _) => + /* If the last expression in a block is a context function, we'll try to + summon its arguments at the current point, even if the expected type + is a function application. Therefore, this is not valid: + ``` + def f = (t: Exception) ?=> (c: String) ?=> result + + ({ + invoked() + f(using e) + })(using s) + ``` + */ + !tycon.name.isContextFunction + case m: MethodType => + /* def f(a: Ta)(b: Tb) + f(a)(b) + + Here, f(a)(b) cannot be rewritten to {invoked();f(a)}(b) + */ + false + case _ => + true + ) + +object InstrumentCoverage: + val name: String = "instrumentCoverage" + val description: String = "instrument code for coverage cheking" diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index b5881d88f5f1..4ab6a5c9a646 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -214,6 +214,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => initFlags = stat.symbol.flags | PrivateLocal ).installAfter(thisPhase) stat.symbol.enteredAfter(thisPhase) + case _ => } (scall, stats ::: inits, args) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index c0c6ede200ed..0d955651dbd9 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -11,6 +11,7 @@ import Symbols._ import Names._ import NameKinds.UniqueName import util.Spans._ +import util.Property import collection.mutable import Trees._ @@ -155,6 +156,27 @@ class LiftComplex extends Lifter { } object LiftComplex extends LiftComplex +/** Lift complex + lift the prefixes */ +object LiftCoverage extends LiftComplex { + + private val LiftEverything = new Property.Key[Boolean] + + private inline def liftEverything(using Context): Boolean = + ctx.property(LiftEverything).contains(true) + + private def liftEverythingContext(using Context): Context = + ctx.fresh.setProperty(LiftEverything, true) + + override def noLift(expr: tpd.Tree)(using Context) = + !liftEverything && super.noLift(expr) + + def liftForCoverage(defs: mutable.ListBuffer[tpd.Tree], tree: tpd.Apply)(using Context) = { + val liftedFun = liftApp(defs, tree.fun) + val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args)(using liftEverythingContext) + tpd.cpy.Apply(tree)(liftedFun, liftedArgs) + } +} + object LiftErased extends LiftComplex: override def isErased = true diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala new file mode 100644 index 000000000000..84e07003cf0d --- /dev/null +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -0,0 +1,82 @@ +package dotty.tools.dotc.coverage + +import org.junit.Test +import org.junit.AfterClass +import org.junit.Assert.* +import org.junit.experimental.categories.Category + +import dotty.{BootstrappedOnlyTests, Properties} +import dotty.tools.vulpix.* +import dotty.tools.vulpix.TestConfiguration.* +import dotty.tools.dotc.Main + +import java.nio.file.{Files, FileSystems, Path, Paths, StandardCopyOption} +import scala.jdk.CollectionConverters.* +import scala.util.Properties.userDir +import scala.language.unsafeNulls + +@Category(Array(classOf[BootstrappedOnlyTests])) +class CoverageTests: + import CoverageTests.{*, given} + + private val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") + private val rootSrc = Paths.get(userDir, "tests", "coverage") + + @Test + def checkCoverageStatements(): Unit = + checkCoverageIn(rootSrc.resolve("pos"), false) + + @Test + def checkInstrumentedRuns(): Unit = + checkCoverageIn(rootSrc.resolve("run"), true) + + def checkCoverageIn(dir: Path, run: Boolean)(using TestGroup): Unit = + Files.walk(dir).filter(scalaFile.matches).forEach(p => { + val path = p + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, dir, run) + val targetFile = targetDir.resolve(s"scoverage.coverage") + val expectFile = p.resolveSibling(s"$fileName.scoverage.check") + + if updateCheckFiles then + Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) + else + val expected = Files.readAllLines(expectFile).asScala + val obtained = Files.readAllLines(targetFile).asScala + if expected != obtained then + for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do + Console.err.println(s"wrong line ${i+1}:") + Console.err.println(s" expected: $exp") + Console.err.println(s" actual : $actual") + fail(s"$targetFile differs from expected $expectFile") + + }) + + /** Generates the coverage report for the given input file, in a temporary directory. */ + def computeCoverageInTmp(inputFile: Path, 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) + val test = compileFile(inputFile.toString, options) + if run then + test.checkRuns() + else + test.checkCompile() + target + +object CoverageTests extends ParallelTesting: + import scala.concurrent.duration.* + + def maxDuration = 30.seconds + def numberOfSlaves = 1 + + def safeMode = Properties.testsSafeMode + def testFilter = Properties.testsFilter + def isInteractive = SummaryReport.isInteractive + def updateCheckFiles = Properties.testsUpdateCheckfile + + given summaryReport: SummaryReporting = SummaryReport() + @AfterClass def tearDown(): Unit = + super.cleanup() + summaryReport.echoSummary() + + given TestGroup = TestGroup("instrumentCoverage") diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 6df8c734ad3e..cf051e5f01ad 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -666,7 +666,7 @@ trait ParallelTesting extends RunnerOrchestration { self => if (didFail) { reportFailed() failedTestSources.toSet.foreach(addFailedTest) - reproduceInstructions.iterator.foreach(addReproduceInstruction) + reproduceInstructions.foreach(addReproduceInstruction) } else reportPassed() } @@ -980,7 +980,7 @@ trait ParallelTesting extends RunnerOrchestration { self => cleanup() if (!shouldFail && test.didFail) { - fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }") + fail(s"Expected no errors when compiling, failed for the following reason(s):\n${reasonsForFailure(test)}\n") } else if (shouldFail && !test.didFail) { fail("Pos test should have failed, but didn't") @@ -1077,14 +1077,16 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Extract `Failure` set and render from `Test` */ private def reasonsForFailure(test: Test): String = { val failureReport = - if (test.failureCount == 0) "" - else s"\n - encountered ${test.failureCount} test failures(s)" + if test.failureCount == 0 then "" + else s"encountered ${test.failureCount} test failure(s):\n" failureReport + test.failureReasons.collect { case test.TimeoutFailure(title) => s" - test '$title' timed out" case test.JavaCompilationFailure(msg) => s" - java compilation failed with:\n${ msg.linesIterator.map(" " + _).mkString("\n") }" + case test.Generic => + " - generic failure (see test output)" }.mkString("\n") } diff --git a/docs/_docs/usage/coverage.md b/docs/_docs/usage/coverage.md new file mode 100644 index 000000000000..2e24b08ea29e --- /dev/null +++ b/docs/_docs/usage/coverage.md @@ -0,0 +1,61 @@ +--- +layout: doc-page +title: "Code Coverage for Scala 3" +--- + +## Instrument code for coverage analysis + +[PR#13880](https://github.com/lampepfl/dotty/pull/13880) has implemented code coverage support for Dotty. +In general, code coverage works in three steps: +1. The program is "instrumented" at compilation time: code is inserted to record which statement are called. This does not change the behavior of the program. Also, a list of all the coverable statements is produced. +2. The program is run, usually by unit tests, and the instrumentation code saves its measurements. +3. Some tool processes the data to generate a fancy coverage report, for instance a web page. + +In Scala 2, all these steps were performed by external tools. In particular, step 1 was implemented by a compiler plugin. + +In Scala 3, the compiler itself takes care of step 1. To use this feature, add the compile option `-coverage-out:DIR`, where `DIR` is the destination of the measurement files. + +You can also set `-sourceroot:PATHS_ROOT` to customize how the path of your source files are resolved. +Note that `-sourceroot` also sets the root path of the SemanticDB files. + +## How-to with sbt + +For now, the Scoverage sbt plugin doesn't apply the above options automatically. +However, you can easily do it yourself, and use the plugin to generate user-friendly reports. + +1. Add the scoverage sbt plugin by appending this line to your `project/plugins.sbt` +```scala +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0-M4") +``` + +2. Compile your project with +```scala +Compile/compile/scalacOptions += + s"-coverage-out:target/scala-${scalaVersion.value}/scoverage-data" +``` + +2. Run the tests: `sbt test` +3. Generate xml and html reports: `sbt coverageReport` + +## Details: how the code is instrumented + +When the `-coverage-out` option is set, a new phase `instrumentCoverage` runs, just before `firstTransform`. +For a carefully selected list of tree types, it adds a call to `scala.runtime.Invoker.invoked(statementId, DIR)`. + +For instance, this code: +``` +def method() = + println(f()) +``` + +with `-coverage-out:target/cov` be turned to +``` +def method() = + Invoker.invoked(2, "target/cov") + println({ + Invoker.invoked(1, "target/cov") + f() + }) +``` + +At the end of the phase, the list of all the instrumented statements is serialized to the file `DIR/scoverage.coverage`. diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala new file mode 100644 index 000000000000..ee37a477cbe6 --- /dev/null +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -0,0 +1,55 @@ +package scala.runtime.coverage + +import scala.collection.mutable.{BitSet, AnyRefMap} +import scala.collection.concurrent.TrieMap +import java.nio.file.Files +import java.io.FileWriter +import java.io.File +import scala.annotation.internal.sharable + +@sharable // avoids false positive by -Ycheck-reentrant +object Invoker { + private val runtimeUUID = java.util.UUID.randomUUID() + + private val MeasurementsPrefix = "scoverage.measurements." + private val threadFiles = new ThreadLocal[AnyRefMap[String, FileWriter]] + private val dataDirToSet = TrieMap.empty[String, BitSet] + + /** We record that the given id has been invoked by appending its id to the coverage data file. + * + * This will happen concurrently on as many threads as the application is using, so we use one + * file per thread, named for the thread id. + * + * This method is not thread-safe if the threads are in different JVMs, because the thread IDs + * may collide. You may not use `scoverage` on multiple processes in parallel without risking + * corruption of the measurement file. + * + * @param id + * the id of the statement that was invoked + * @param dataDir + * the directory where the measurement data is held + */ + def invoked(id: Int, dataDir: String): Unit = + val set = dataDirToSet.getOrElseUpdate(dataDir, BitSet.empty) + if !set.contains(id) then + val added = set.synchronized { + set.add(id) + } + if added then + var writers = threadFiles.get() + if writers == null then + writers = AnyRefMap.empty + threadFiles.set(writers) + val writer = writers.getOrElseUpdate( + dataDir, + FileWriter(measurementFile(dataDir), true) + ) + writer.write(Integer.toString(id)) + writer.write('\n') + writer.flush() + + def measurementFile(dataDir: String): File = new File( + dataDir, + MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.nn.getId + ) +} diff --git a/project/Build.scala b/project/Build.scala index 3a4480dd84e3..f3e8957fb258 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -572,8 +572,8 @@ object Build { ) }, - javaOptions += ( - s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}" + javaOptions ++= Seq( + s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}", ), testCompilation := Def.inputTaskDyn { @@ -583,7 +583,8 @@ object Build { s""" |usage: testCompilation [--help] [--from-tasty] [--update-checkfiles] [] | - |By default runs tests in dotty.tools.dotc.*CompilationTests excluding tests tagged with dotty.SlowTests. + |By default runs tests in dotty.tools.dotc.*CompilationTests and dotty.tools.dotc.coverage.*, + |excluding tests tagged with dotty.SlowTests. | | --help show this message | --from-tasty runs tests in dotty.tools.dotc.FromTastyTests @@ -598,7 +599,7 @@ object Build { val updateCheckfile = args.contains("--update-checkfiles") val fromTasty = args.contains("--from-tasty") val args1 = if (updateCheckfile | fromTasty) args.filter(x => x != "--update-checkfiles" && x != "--from-tasty") else args - val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests" + val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests dotty.tools.dotc.coverage.*" val cmd = s" $test -- --exclude-categories=dotty.SlowTests" + (if (updateCheckfile) " -Ddotty.tests.updateCheckfiles=TRUE" else "") + (if (args1.nonEmpty) " -Ddotty.tests.filter=" + args1.mkString(" ") else "") diff --git a/tests/coverage/pos/Constructor.scala b/tests/coverage/pos/Constructor.scala new file mode 100644 index 000000000000..251370ec8e6e --- /dev/null +++ b/tests/coverage/pos/Constructor.scala @@ -0,0 +1,11 @@ +package covtest + +class C: + def f(x: Int) = x + def x = 1 + f(x) + +object O: + def g(y: Int) = y + def y = 1 + g(y) diff --git a/tests/coverage/pos/Constructor.scoverage.check b/tests/coverage/pos/Constructor.scoverage.check new file mode 100644 index 000000000000..b6223734b1a8 --- /dev/null +++ b/tests/coverage/pos/Constructor.scoverage.check @@ -0,0 +1,122 @@ +# 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 +# ------------------------------------------ +0 +Constructor.scala +covtest +C +Class +covtest.C +f +28 +33 +3 +f +DefDef +false +0 +false +def f + +1 +Constructor.scala +covtest +C +Class +covtest.C +x +48 +53 +4 +x +DefDef +false +0 +false +def x + +2 +Constructor.scala +covtest +C +Class +covtest.C + +60 +64 +5 +f +Apply +false +0 +false +f(x) + +3 +Constructor.scala +covtest +O$ +Object +covtest.O$ +g +78 +83 +8 +g +DefDef +false +0 +false +def g + +4 +Constructor.scala +covtest +O$ +Object +covtest.O$ +y +98 +103 +9 +y +DefDef +false +0 +false +def y + +5 +Constructor.scala +covtest +O$ +Object +covtest.O$ + +110 +114 +10 +g +Apply +false +0 +false +g(y) + diff --git a/tests/coverage/pos/ContextFunctions.scala b/tests/coverage/pos/ContextFunctions.scala new file mode 100644 index 000000000000..04f5b6768c6c --- /dev/null +++ b/tests/coverage/pos/ContextFunctions.scala @@ -0,0 +1,14 @@ +package covtest + +class OnError(op: Exception => Any): + def onError(fallback: Any): Any = fallback + +class Imperative: + + def readName2 = (t: Exception) ?=> (c: String) ?=> { + "name" + } + + def readPerson(s: String) = + val e: Exception = null + OnError((e) => readName2(using e)(using s)).onError(None) diff --git a/tests/coverage/pos/ContextFunctions.scoverage.check b/tests/coverage/pos/ContextFunctions.scoverage.check new file mode 100644 index 000000000000..74a76df8bbf4 --- /dev/null +++ b/tests/coverage/pos/ContextFunctions.scoverage.check @@ -0,0 +1,190 @@ +# 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 +# ------------------------------------------ +0 +ContextFunctions.scala +covtest +OnError +Class +covtest.OnError +onError +56 +67 +3 +onError +DefDef +false +0 +false +def onError + +1 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readName2 +121 +134 +7 +readName2 +DefDef +false +0 +false +def readName2 + +2 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +apply +Apply +false +0 +false +readName2(using e)(using s) + +3 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 + +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +4 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +invoked +Apply +false +0 +false +readName2(using e)(using s) + +5 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +apply +Apply +false +0 +false +readName2(using e)(using s) + +6 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 +invoked +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +7 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 + +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +8 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +309 +13 +onError +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)).onError(None) + +9 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +192 +206 +11 +readPerson +DefDef +false +0 +false +def readPerson + diff --git a/tests/coverage/pos/Enum.scala b/tests/coverage/pos/Enum.scala new file mode 100644 index 000000000000..b5710432f6b6 --- /dev/null +++ b/tests/coverage/pos/Enum.scala @@ -0,0 +1,41 @@ +package covtest + +/** + * Enum Types: https://dotty.epfl.ch/docs/reference/enums/adts.html + * Taken from https://github.com/scala/scala3-example-project + */ +object EnumTypes: + + enum ListEnum[+A]: + case Cons(h: A, t: ListEnum[A]) + case Empty + + enum Planet(mass: Double, radius: Double): + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + case Mars extends Planet(6.421e+23, 3.3972e6) + case Jupiter extends Planet(1.9e+27, 7.1492e7) + case Saturn extends Planet(5.688e+26, 6.0268e7) + case Uranus extends Planet(8.686e+25, 2.5559e7) + case Neptune extends Planet(1.024e+26, 2.4746e7) + end Planet + + def test(): Unit = + val emptyList = ListEnum.Empty + val list = ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + println("Example 1: \n"+emptyList) + println(s"${list}\n") + + def calculateEarthWeightOnPlanets(earthWeight: Double) = + val mass = earthWeight/Planet.Earth.surfaceGravity + for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + + println("Example 2:") + calculateEarthWeightOnPlanets(80) + end test diff --git a/tests/coverage/pos/Enum.scoverage.check b/tests/coverage/pos/Enum.scoverage.check new file mode 100644 index 000000000000..d76c749275e7 --- /dev/null +++ b/tests/coverage/pos/Enum.scoverage.check @@ -0,0 +1,531 @@ +# 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 +# ------------------------------------------ +0 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +1 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +invoked +Apply +false +0 +false +G * mass + +2 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +3 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +371 +386 +14 +* +Apply +false +0 +false +radius * radius + +4 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +386 +14 +/ +Apply +false +0 +false +G * mass / (radius * radius + +5 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +338 +356 +14 +surfaceGravity +DefDef +false +0 +false +def surfaceGravity + +6 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +432 +458 +15 +* +Apply +false +0 +false +otherMass * surfaceGravity + +7 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +392 +409 +15 +surfaceWeight +DefDef +false +0 +false +def surfaceWeight + +8 +Enum.scala +covtest +$anon +Class +covtest.$anon + +485 +512 +17 + +Apply +false +0 +false +Planet(3.303e+23, 2.4397e6) + +9 +Enum.scala +covtest +$anon +Class +covtest.$anon + +538 +565 +18 + +Apply +false +0 +false +Planet(4.869e+24, 6.0518e6) + +10 +Enum.scala +covtest +$anon +Class +covtest.$anon + +591 +619 +19 + +Apply +false +0 +false +Planet(5.976e+24, 6.37814e6) + +11 +Enum.scala +covtest +$anon +Class +covtest.$anon + +645 +672 +20 + +Apply +false +0 +false +Planet(6.421e+23, 3.3972e6) + +12 +Enum.scala +covtest +$anon +Class +covtest.$anon + +698 +725 +21 + +Apply +false +0 +false +Planet(1.9e+27, 7.1492e7) + +13 +Enum.scala +covtest +$anon +Class +covtest.$anon + +751 +778 +22 + +Apply +false +0 +false +Planet(5.688e+26, 6.0268e7) + +14 +Enum.scala +covtest +$anon +Class +covtest.$anon + +804 +831 +23 + +Apply +false +0 +false +Planet(8.686e+25, 2.5559e7) + +15 +Enum.scala +covtest +$anon +Class +covtest.$anon + +857 +884 +24 + +Apply +false +0 +false +Planet(1.024e+26, 2.4746e7) + +16 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1051 +1076 +30 ++ +Apply +false +0 +false +"Example 1: \n"+emptyList + +17 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1043 +1077 +30 +println +Apply +false +0 +false +println("Example 1: \n"+emptyList) + +18 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1090 +1102 +31 +s +Apply +false +0 +false +s"${list}\n" + +19 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1082 +1103 +31 +println +Apply +false +0 +false +println(s"${list}\n") + +20 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1183 +1222 +34 +/ +Apply +false +0 +false +earthWeight/Planet.Earth.surfaceGravity + +21 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1238 +1251 +35 +refArrayOps +Apply +false +0 +false +Planet.values + +22 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +$anonfun +1296 +1317 +36 +surfaceWeight +Apply +false +0 +false +p.surfaceWeight(mass) + +23 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +$anonfun +1271 +1319 +36 +s +Apply +false +0 +false +s"Your weight on $p is ${p.surfaceWeight(mass)}" + +24 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +$anonfun +1263 +1320 +36 +println +Apply +false +0 +false +println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +25 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1229 +1320 +35 +foreach +Apply +false +0 +false +for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +26 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1109 +1142 +33 +calculateEarthWeightOnPlanets +DefDef +false +0 +false +def calculateEarthWeightOnPlanets + +27 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1326 +1347 +38 +println +Apply +false +0 +false +println("Example 2:") + +28 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1352 +1385 +39 +calculateEarthWeightOnPlanets +Apply +false +0 +false +calculateEarthWeightOnPlanets(80) + +29 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +901 +909 +27 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/Givens.scala b/tests/coverage/pos/Givens.scala new file mode 100644 index 000000000000..2f9cbdfd1169 --- /dev/null +++ b/tests/coverage/pos/Givens.scala @@ -0,0 +1,23 @@ +package covtest + +import scala.language.strictEquality + +class Context(val id: Int) + +class Givens: + + def test(): Unit = + given CanEqual[Int, String] = CanEqual.derived + println(3 == "3") + println(3 == 5.1) + + def printContext(msg: String)(using ctx: Context): Unit = + println(msg) + println(ctx.id) + + def getMessage(i: Int): String = i.toString + + def test2() = + given c: Context = Context(0) + printContext("test")(using c) + printContext(getMessage(123)) diff --git a/tests/coverage/pos/Givens.scoverage.check b/tests/coverage/pos/Givens.scoverage.check new file mode 100644 index 000000000000..389713521448 --- /dev/null +++ b/tests/coverage/pos/Givens.scoverage.check @@ -0,0 +1,275 @@ +# 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 +# ------------------------------------------ +0 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +182 +190 +10 +== +Apply +false +0 +false +3 == "3" + +1 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +174 +191 +10 +println +Apply +false +0 +false +println(3 == "3") + +2 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +204 +212 +11 +== +Apply +false +0 +false +3 == 5.1 + +3 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +196 +213 +11 +println +Apply +false +0 +false +println(3 == 5.1) + +4 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +100 +108 +8 +test +DefDef +false +0 +false +def test + +5 +Givens.scala +covtest +Givens +Class +covtest.Givens +printContext +279 +291 +14 +println +Apply +false +0 +false +println(msg) + +6 +Givens.scala +covtest +Givens +Class +covtest.Givens +printContext +296 +311 +15 +println +Apply +false +0 +false +println(ctx.id) + +7 +Givens.scala +covtest +Givens +Class +covtest.Givens +printContext +217 +233 +13 +printContext +DefDef +false +0 +false +def printContext + +8 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +348 +358 +17 +toString +Apply +false +0 +false +i.toString + +9 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +315 +329 +17 +getMessage +DefDef +false +0 +false +def getMessage + +10 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +399 +409 +20 + +Apply +false +0 +false +Context(0) + +11 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +414 +443 +21 +printContext +Apply +false +0 +false +printContext("test")(using c) + +12 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +461 +476 +22 +getMessage +Apply +false +0 +false +getMessage(123) + +13 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +448 +477 +22 +printContext +Apply +false +0 +false +printContext(getMessage(123)) + +14 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +362 +371 +19 +test2 +DefDef +false +0 +false +def test2 + diff --git a/tests/coverage/pos/Inlined.scala b/tests/coverage/pos/Inlined.scala new file mode 100644 index 000000000000..ebe0503716a7 --- /dev/null +++ b/tests/coverage/pos/Inlined.scala @@ -0,0 +1,8 @@ +package covtest + +// Checks that we use the new positions of the inlined code properly +def testInlined(): Unit = + val l = 1 + assert(l == 1) // scala.Predef.assert is inline in dotty + assert(l == List(l).length) + assert(List(l).length == 1) diff --git a/tests/coverage/pos/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check new file mode 100644 index 000000000000..f3cb3b5d9026 --- /dev/null +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -0,0 +1,275 @@ +# 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 +# ------------------------------------------ +0 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +133 +139 +5 +== +Apply +false +0 +false +l == 1 + +1 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false +scala.runtime.Scala3RunTime + +2 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +3 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +4 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +197 +204 +6 +apply +Apply +false +0 +false +List(l) + +5 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +192 +211 +6 +== +Apply +false +0 +false +l == List(l).length + +6 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false +scala.runtime.Scala3RunTime + +7 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +8 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +9 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +222 +229 +7 +apply +Apply +false +0 +false +List(l) + +10 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +222 +241 +7 +== +Apply +false +0 +false +List(l).length == 1 + +11 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false +scala.runtime.Scala3RunTime + +12 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +13 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +14 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +86 +101 +3 +testInlined +DefDef +false +0 +false +def testInlined + diff --git a/tests/coverage/pos/Literals.scala b/tests/coverage/pos/Literals.scala new file mode 100644 index 000000000000..4b61238b2015 --- /dev/null +++ b/tests/coverage/pos/Literals.scala @@ -0,0 +1,12 @@ +package covtest + +def block = + println("not this") // this literal should not be instrumented, only the println call + 12 + true + null + +def f(x: Int, y: Int, z: Int)(t: Int) = ??? + +def main: Unit = + f(0,1,2)(3) diff --git a/tests/coverage/pos/Literals.scoverage.check b/tests/coverage/pos/Literals.scoverage.check new file mode 100644 index 000000000000..bda71a49f3b1 --- /dev/null +++ b/tests/coverage/pos/Literals.scoverage.check @@ -0,0 +1,105 @@ +# 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 +# ------------------------------------------ +0 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +block +31 +50 +3 +println +Apply +false +0 +false +println("not this") + +1 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +block +17 +26 +2 +block +DefDef +false +0 +false +def block + +2 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +f +137 +142 +8 +f +DefDef +false +0 +false +def f + +3 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +main +201 +212 +11 +f +Apply +false +0 +false +f(0,1,2)(3) + +4 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +main +182 +190 +10 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/pos/MatchCaseClasses.scala b/tests/coverage/pos/MatchCaseClasses.scala new file mode 100644 index 000000000000..9e94be869a37 --- /dev/null +++ b/tests/coverage/pos/MatchCaseClasses.scala @@ -0,0 +1,15 @@ +package covtest + +case class Pat1(x: Int) +case class Pat2(x: Int, y: Any) + +object MatchCaseClasses: + def f(x: Any): Unit = x match + case Pat1(0) => println("a") + case Pat1(_) => println("b") + case p @ Pat2(1, -1) => println("c") + case Pat2(_, y: String) => + println(y) + println("d") + case p: Pat2 => println("e") + case _ => println("other") diff --git a/tests/coverage/pos/MatchCaseClasses.scoverage.check b/tests/coverage/pos/MatchCaseClasses.scoverage.check new file mode 100644 index 000000000000..0e469fc86b7c --- /dev/null +++ b/tests/coverage/pos/MatchCaseClasses.scoverage.check @@ -0,0 +1,258 @@ +# 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 +# ------------------------------------------ +0 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +151 +163 +7 +println +Apply +false +0 +false +println("a") + +1 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +135 +147 +7 + +Block +false +0 +false +case Pat1(0) + +2 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +184 +196 +8 +println +Apply +false +0 +false +println("b") + +3 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +168 +180 +8 + +Block +false +0 +false +case Pat1(_) + +4 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +225 +237 +9 +println +Apply +false +0 +false +println("c") + +5 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +201 +221 +9 + +Block +false +0 +false +case p @ Pat2(1, -1) + +6 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +275 +285 +11 +println +Apply +false +0 +false +println(y) + +7 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +292 +304 +12 +println +Apply +false +0 +false +println("d") + +8 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +242 +265 +10 + +Block +false +0 +false +case Pat2(_, y: String) + +9 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +325 +337 +13 +println +Apply +false +0 +false +println("e") + +10 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +309 +321 +13 + +Block +false +0 +false +case p: Pat2 + +11 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +352 +368 +14 +println +Apply +false +0 +false +println("other") + +12 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +342 +348 +14 + +Block +false +0 +false +case _ + +13 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +101 +106 +6 +f +DefDef +false +0 +false +def f + diff --git a/tests/coverage/pos/MatchNumbers.scala b/tests/coverage/pos/MatchNumbers.scala new file mode 100644 index 000000000000..f160ab883402 --- /dev/null +++ b/tests/coverage/pos/MatchNumbers.scala @@ -0,0 +1,12 @@ +package covtest + +object MatchNumbers: + type Integer = Int | Long + + def f(i: Integer): Int = i match + case x: Int if x < 0 => -1 + case x: Int => x + case y: Long => y.toInt + + f(0) + f(1L) diff --git a/tests/coverage/pos/MatchNumbers.scoverage.check b/tests/coverage/pos/MatchNumbers.scoverage.check new file mode 100644 index 000000000000..f16c11031784 --- /dev/null +++ b/tests/coverage/pos/MatchNumbers.scoverage.check @@ -0,0 +1,139 @@ +# 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 +# ------------------------------------------ +0 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +106 +126 +6 + +Block +false +0 +false +case x: Int if x < 0 + +1 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +121 +126 +6 +< +Apply +false +0 +false +x < 0 + +2 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +137 +148 +7 + +Block +false +0 +false +case x: Int + +3 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +158 +170 +8 + +Block +false +0 +false +case y: Long + +4 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +69 +74 +5 +f +DefDef +false +0 +false +def f + +5 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ + +185 +189 +10 +f +Apply +false +0 +false +f(0) + +6 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ + +192 +197 +11 +f +Apply +false +0 +false +f(1L) + diff --git a/tests/coverage/pos/Select.scala b/tests/coverage/pos/Select.scala new file mode 100644 index 000000000000..2c9e63552586 --- /dev/null +++ b/tests/coverage/pos/Select.scala @@ -0,0 +1,20 @@ +package covtest + +trait T: + def print(): Unit + +class A extends T: + def print() = println("A") + def instance = this + +class B extends A: + override def print() = + super.print() + println(this.instance) + +def test(): Unit = + val a = A() + val aNew = new A + + a.instance.print() // should instrument `a.instance` and `(a.instance).print()` + a.print() diff --git a/tests/coverage/pos/Select.scoverage.check b/tests/coverage/pos/Select.scoverage.check new file mode 100644 index 000000000000..ef2831ee1013 --- /dev/null +++ b/tests/coverage/pos/Select.scoverage.check @@ -0,0 +1,224 @@ +# 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 +# ------------------------------------------ +0 +Select.scala +covtest +A +Class +covtest.A +print +82 +94 +6 +println +Apply +false +0 +false +println("A") + +1 +Select.scala +covtest +A +Class +covtest.A +print +68 +77 +6 +print +DefDef +false +0 +false +def print + +2 +Select.scala +covtest +A +Class +covtest.A +instance +97 +109 +7 +instance +DefDef +false +0 +false +def instance + +3 +Select.scala +covtest +B +Class +covtest.B + +134 +135 +9 + +Apply +false +0 +false +A + +4 +Select.scala +covtest +B +Class +covtest.B +print +166 +179 +11 +print +Apply +false +0 +false +super.print() + +5 +Select.scala +covtest +B +Class +covtest.B +print +184 +206 +12 +println +Apply +false +0 +false +println(this.instance) + +6 +Select.scala +covtest +B +Class +covtest.B +print +139 +157 +10 +print +DefDef +false +0 +false +override def print + +7 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +237 +240 +15 + +Apply +false +0 +false +A() + +8 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +254 +259 +16 + +Apply +false +0 +false +new A + +9 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +263 +281 +18 +print +Apply +false +0 +false +a.instance.print() + +10 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +345 +354 +19 +print +Apply +false +0 +false +a.print() + +11 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +208 +216 +14 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/SimpleMethods.scala b/tests/coverage/pos/SimpleMethods.scala new file mode 100644 index 000000000000..510a86799b32 --- /dev/null +++ b/tests/coverage/pos/SimpleMethods.scala @@ -0,0 +1,23 @@ +package covtest + +class C: + def a: C = this + def b: Unit = return + def c: Unit = () + def d: Int = 12 + def e: Null = null + + def block: Int = + "literal" + 0 + + def cond: Boolean = + if false then true + else false + + def new1: C = new {} + + def tryCatch: Unit = + try () + catch + case e: Exception => 1 diff --git a/tests/coverage/pos/SimpleMethods.scoverage.check b/tests/coverage/pos/SimpleMethods.scoverage.check new file mode 100644 index 000000000000..f2d9d61a1cd4 --- /dev/null +++ b/tests/coverage/pos/SimpleMethods.scoverage.check @@ -0,0 +1,241 @@ +# 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 +# ------------------------------------------ +0 +SimpleMethods.scala +covtest +C +Class +covtest.C +a +28 +33 +3 +a +DefDef +false +0 +false +def a + +1 +SimpleMethods.scala +covtest +C +Class +covtest.C +b +46 +51 +4 +b +DefDef +false +0 +false +def b + +2 +SimpleMethods.scala +covtest +C +Class +covtest.C +c +69 +74 +5 +c +DefDef +false +0 +false +def c + +3 +SimpleMethods.scala +covtest +C +Class +covtest.C +d +88 +93 +6 +d +DefDef +false +0 +false +def d + +4 +SimpleMethods.scala +covtest +C +Class +covtest.C +e +106 +111 +7 +e +DefDef +false +0 +false +def e + +5 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +128 +137 +9 +block +DefDef +false +0 +false +def block + +6 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +206 +210 +14 + +Literal +true +0 +false +true + +7 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +220 +225 +15 + +Literal +true +0 +false +false + +8 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +168 +176 +13 +cond +DefDef +false +0 +false +def cond + +9 +SimpleMethods.scala +covtest +C +Class +covtest.C +new1 +229 +237 +17 +new1 +DefDef +false +0 +false +def new1 + +10 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +282 +284 +20 + +Literal +true +0 +false +() + +11 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +301 +318 +22 + +Block +false +0 +false +case e: Exception + +12 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +253 +265 +19 +tryCatch +DefDef +false +0 +false +def tryCatch + diff --git a/tests/coverage/pos/StructuralTypes.scala b/tests/coverage/pos/StructuralTypes.scala new file mode 100644 index 000000000000..f0472345b9d4 --- /dev/null +++ b/tests/coverage/pos/StructuralTypes.scala @@ -0,0 +1,14 @@ +package covtest + +object StructuralTypes: + + case class Record(elems: (String, Any)*) extends Selectable: + def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2 + + type Person = Record { + val name: String + } + + def test(): Unit = + val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person] + person.name diff --git a/tests/coverage/pos/StructuralTypes.scoverage.check b/tests/coverage/pos/StructuralTypes.scoverage.check new file mode 100644 index 000000000000..43ddfd7715b8 --- /dev/null +++ b/tests/coverage/pos/StructuralTypes.scoverage.check @@ -0,0 +1,139 @@ +# 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 +# ------------------------------------------ +0 +StructuralTypes.scala +covtest +Record +Class +covtest.Record +$anonfun +159 +171 +5 +== +Apply +false +0 +false +_._1 == name + +1 +StructuralTypes.scala +covtest +Record +Class +covtest.Record +selectDynamic +148 +172 +5 +find +Apply +false +0 +false +elems.find(_._1 == name) + +2 +StructuralTypes.scala +covtest +Record +Class +covtest.Record +selectDynamic +109 +126 +5 +selectDynamic +DefDef +false +0 +false +def selectDynamic + +3 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +277 +293 +12 +-> +Apply +false +0 +false +"name" -> "Emma" + +4 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +295 +306 +12 +-> +Apply +false +0 +false +"age" -> 42 + +5 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +333 +344 +13 +selectDynamic +Apply +false +0 +false +person.name + +6 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +234 +242 +11 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/TypeLambdas.scala b/tests/coverage/pos/TypeLambdas.scala new file mode 100644 index 000000000000..9c7641c025e2 --- /dev/null +++ b/tests/coverage/pos/TypeLambdas.scala @@ -0,0 +1,19 @@ +package covtest + +/** + * Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html + * Taken from https://github.com/scala/scala3-example-project + */ +object TypeLambdas: + + type M = [X, Y] =>> Map[Y, X] + + type Tuple = [X] =>> (X, X) + + def test(): Unit = + val m: M[String, Int] = Map(1 -> "1") + println(m) + + val tuple: Tuple[String] = ("a", "b") + println(tuple) + diff --git a/tests/coverage/pos/TypeLambdas.scoverage.check b/tests/coverage/pos/TypeLambdas.scoverage.check new file mode 100644 index 000000000000..f97d5285cf06 --- /dev/null +++ b/tests/coverage/pos/TypeLambdas.scoverage.check @@ -0,0 +1,105 @@ +# 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 +# ------------------------------------------ +0 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +310 +318 +13 +-> +Apply +false +0 +false +1 -> "1" + +1 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +306 +319 +13 +apply +Apply +false +0 +false +Map(1 -> "1") + +2 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +324 +334 +14 +println +Apply +false +0 +false +println(m) + +3 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +382 +396 +17 +println +Apply +false +0 +false +println(tuple) + +4 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +259 +267 +12 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/run/currying/test.check b/tests/coverage/run/currying/test.check new file mode 100644 index 000000000000..d21dbd35bc5c --- /dev/null +++ b/tests/coverage/run/currying/test.check @@ -0,0 +1,4 @@ +3 +3 +3 +3 \ No newline at end of file diff --git a/tests/coverage/run/currying/test.scala b/tests/coverage/run/currying/test.scala new file mode 100644 index 000000000000..4776b6bde855 --- /dev/null +++ b/tests/coverage/run/currying/test.scala @@ -0,0 +1,15 @@ +object Test: + def f1(a: Int)(b: Int)(c: Int) = a+b+c + def f2 = (a: Int) => (b: Int) => (c: Int) => + a+b+c + + def g1 = (a: Int) ?=> (b: Int) ?=> (c: Int) ?=> + a+b+c + + def g2(using a: Int)(using b: Int)(using c: Int) = a+b+c + + def main(args: Array[String]): Unit = + println(f1(0)(1)(2)) + println(f2(0)(1)(2)) + println(g1(using 0)(using 1)(using 2)) + println(g2(using 0)(using 1)(using 2)) diff --git a/tests/coverage/run/currying/test.scoverage.check b/tests/coverage/run/currying/test.scoverage.check new file mode 100644 index 000000000000..688c75b42933 --- /dev/null +++ b/tests/coverage/run/currying/test.scoverage.check @@ -0,0 +1,411 @@ +# 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 +# ------------------------------------------ +0 +currying/test.scala + +Test$ +Object +.Test$ +f1 +48 +51 +1 ++ +Apply +false +0 +false +a+b + +1 +currying/test.scala + +Test$ +Object +.Test$ +f1 +48 +53 +1 ++ +Apply +false +0 +false +a+b+c + +2 +currying/test.scala + +Test$ +Object +.Test$ +f1 +15 +21 +1 +f1 +DefDef +false +0 +false +def f1 + +3 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +105 +108 +3 ++ +Apply +false +0 +false +a+b + +4 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +105 +110 +3 ++ +Apply +false +0 +false +a+b+c + +5 +currying/test.scala + +Test$ +Object +.Test$ +f2 +56 +62 +2 +f2 +DefDef +false +0 +false +def f2 + +6 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +166 +169 +6 ++ +Apply +false +0 +false +a+b + +7 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +166 +171 +6 ++ +Apply +false +0 +false +a+b+c + +8 +currying/test.scala + +Test$ +Object +.Test$ +g1 +114 +120 +5 +g1 +DefDef +false +0 +false +def g1 + +9 +currying/test.scala + +Test$ +Object +.Test$ +g2 +226 +229 +8 ++ +Apply +false +0 +false +a+b + +10 +currying/test.scala + +Test$ +Object +.Test$ +g2 +226 +231 +8 ++ +Apply +false +0 +false +a+b+c + +11 +currying/test.scala + +Test$ +Object +.Test$ +g2 +175 +181 +8 +g2 +DefDef +false +0 +false +def g2 + +12 +currying/test.scala + +Test$ +Object +.Test$ +main +285 +296 +11 +f1 +Apply +false +0 +false +f1(0)(1)(2) + +13 +currying/test.scala + +Test$ +Object +.Test$ +main +277 +297 +11 +println +Apply +false +0 +false +println(f1(0)(1)(2)) + +14 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +315 +12 +apply +Apply +false +0 +false +f2(0) + +15 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +318 +12 +apply +Apply +false +0 +false +f2(0)(1) + +16 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +321 +12 +apply +Apply +false +0 +false +f2(0)(1)(2) + +17 +currying/test.scala + +Test$ +Object +.Test$ +main +302 +322 +12 +println +Apply +false +0 +false +println(f2(0)(1)(2)) + +18 +currying/test.scala + +Test$ +Object +.Test$ +main +335 +364 +13 +apply +Apply +false +0 +false +g1(using 0)(using 1)(using 2) + +19 +currying/test.scala + +Test$ +Object +.Test$ +main +327 +365 +13 +println +Apply +false +0 +false +println(g1(using 0)(using 1)(using 2)) + +20 +currying/test.scala + +Test$ +Object +.Test$ +main +378 +407 +14 +g2 +Apply +false +0 +false +g2(using 0)(using 1)(using 2) + +21 +currying/test.scala + +Test$ +Object +.Test$ +main +370 +408 +14 +println +Apply +false +0 +false +println(g2(using 0)(using 1)(using 2)) + +22 +currying/test.scala + +Test$ +Object +.Test$ +main +235 +243 +10 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/inheritance/test.check b/tests/coverage/run/inheritance/test.check new file mode 100644 index 000000000000..59fbc504fb8c --- /dev/null +++ b/tests/coverage/run/inheritance/test.check @@ -0,0 +1,3 @@ +block +1 +2 \ No newline at end of file diff --git a/tests/coverage/run/inheritance/test.scala b/tests/coverage/run/inheritance/test.scala new file mode 100644 index 000000000000..b5be83a378ea --- /dev/null +++ b/tests/coverage/run/inheritance/test.scala @@ -0,0 +1,10 @@ +class A(val x: Int, val y: Int) +class B(x: Int) extends A(x, 0) +class C1 extends B({println("block"); 1}) +class C2 extends B(A(2,2).x) + +@main +def Test: Unit = + println(C1().x) // block + // 1 + println(C2().x) // 2 diff --git a/tests/coverage/run/inheritance/test.scoverage.check b/tests/coverage/run/inheritance/test.scoverage.check new file mode 100644 index 000000000000..5744f4b5eb3b --- /dev/null +++ b/tests/coverage/run/inheritance/test.scoverage.check @@ -0,0 +1,191 @@ +# 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 +# ------------------------------------------ +0 +inheritance/test.scala + +B +Class +.B + +56 +63 +1 + +Apply +false +0 +false +A(x, 0) + +1 +inheritance/test.scala + +C1 +Class +.C1 + +84 +100 +2 +println +Apply +false +0 +false +println("block") + +2 +inheritance/test.scala + +C1 +Class +.C1 + +81 +105 +2 + +Apply +false +0 +false +B({println("block"); 1}) + +3 +inheritance/test.scala + +C2 +Class +.C2 + +125 +131 +3 + +Apply +false +0 +false +A(2,2) + +4 +inheritance/test.scala + +C2 +Class +.C2 + +123 +134 +3 + +Apply +false +0 +false +B(A(2,2).x) + +5 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +169 +173 +7 + +Apply +false +0 +false +C1() + +6 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +161 +176 +7 +println +Apply +false +0 +false +println(C1().x) + +7 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +219 +223 +9 + +Apply +false +0 +false +C2() + +8 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +211 +226 +9 +println +Apply +false +0 +false +println(C2().x) + +9 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +136 +150 +6 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/coverage/run/interpolation/test.check b/tests/coverage/run/interpolation/test.check new file mode 100644 index 000000000000..9ce4b367db49 --- /dev/null +++ b/tests/coverage/run/interpolation/test.check @@ -0,0 +1,5 @@ +0: d +1: o +2: t +3: t +4: y diff --git a/tests/coverage/run/interpolation/test.scala b/tests/coverage/run/interpolation/test.scala new file mode 100644 index 000000000000..3745367b593d --- /dev/null +++ b/tests/coverage/run/interpolation/test.scala @@ -0,0 +1,9 @@ +object Test: + + def simple(a: Int, b: String): String = + s"$a, ${b.length}" + + def main(args: Array[String]): Unit = + val xs: List[String] = List("d", "o", "t", "t", "y") + + xs.zipWithIndex.map((s, i) => println(s"$i: $s")) diff --git a/tests/coverage/run/interpolation/test.scoverage.check b/tests/coverage/run/interpolation/test.scoverage.check new file mode 100644 index 000000000000..9c93d58f182c --- /dev/null +++ b/tests/coverage/run/interpolation/test.scoverage.check @@ -0,0 +1,156 @@ +# 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 +# ------------------------------------------ +0 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +68 +76 +3 +length +Apply +false +0 +false +b.length + +1 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +60 +78 +3 +s +Apply +false +0 +false +s"$a, ${b.length}" + +2 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +16 +26 +2 +simple +DefDef +false +0 +false +def simple + +3 +interpolation/test.scala + +Test$ +Object +.Test$ +main +147 +176 +6 +apply +Apply +false +0 +false +List("d", "o", "t", "t", "y") + +4 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +220 +229 +8 +s +Apply +false +0 +false +s"$i: $s" + +5 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +212 +230 +8 +println +Apply +false +0 +false +println(s"$i: $s") + +6 +interpolation/test.scala + +Test$ +Object +.Test$ +main +182 +231 +8 +map +Apply +false +0 +false +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) + +7 +interpolation/test.scala + +Test$ +Object +.Test$ +main +82 +90 +5 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/lifting/test.check b/tests/coverage/run/lifting/test.check new file mode 100644 index 000000000000..fe4a8088d523 --- /dev/null +++ b/tests/coverage/run/lifting/test.check @@ -0,0 +1,3 @@ +string123.0.0 +string123.0.0 +string-1.0.123 diff --git a/tests/coverage/run/lifting/test.scala b/tests/coverage/run/lifting/test.scala new file mode 100644 index 000000000000..2b44168e3aed --- /dev/null +++ b/tests/coverage/run/lifting/test.scala @@ -0,0 +1,20 @@ +class Vals: + val l = List(1) + val ll = l :: List(1,2,3) + +class A: + def msg(a: Int, b: Int, c: Int) = "string" + a + "." + b + "." + c + def integer: Int = 0 + def ex: this.type = this + +@main +def Test: Unit = + val a = A() + val i = 123 + def f() = -1 + var x = a.msg(i, 0, a.integer) + println(x) + x = a.ex.msg(i, 0, a.ex.integer) + println(x) + x = a.msg(f(), 0, i) + println(x) diff --git a/tests/coverage/run/lifting/test.scoverage.check b/tests/coverage/run/lifting/test.scoverage.check new file mode 100644 index 000000000000..7729e7515734 --- /dev/null +++ b/tests/coverage/run/lifting/test.scoverage.check @@ -0,0 +1,378 @@ +# 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 +# ------------------------------------------ +0 +lifting/test.scala + +Vals +Class +.Vals + +22 +29 +1 +apply +Apply +false +0 +false +List(1) + +1 +lifting/test.scala + +Vals +Class +.Vals + +46 +57 +2 +apply +Apply +false +0 +false +List(1,2,3) + +2 +lifting/test.scala + +Vals +Class +.Vals + +41 +57 +2 +:: +Apply +false +0 +false +l :: List(1,2,3) + +3 +lifting/test.scala + +A +Class +.A +msg +104 +116 +5 ++ +Apply +false +0 +false +"string" + a + +4 +lifting/test.scala + +A +Class +.A +msg +104 +122 +5 ++ +Apply +false +0 +false +"string" + a + "." + +5 +lifting/test.scala + +A +Class +.A +msg +104 +126 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + +6 +lifting/test.scala + +A +Class +.A +msg +104 +132 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + "." + +7 +lifting/test.scala + +A +Class +.A +msg +104 +136 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + "." + c + +8 +lifting/test.scala + +A +Class +.A +msg +70 +77 +5 +msg +DefDef +false +0 +false +def msg + +9 +lifting/test.scala + +A +Class +.A +integer +139 +150 +6 +integer +DefDef +false +0 +false +def integer + +10 +lifting/test.scala + +A +Class +.A +ex +162 +168 +7 +ex +DefDef +false +0 +false +def ex + +11 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +221 +224 +11 + +Apply +false +0 +false +A() + +12 +lifting/test.scala + +test$package$ +Object +.test$package$ +f +241 +246 +13 +f +DefDef +false +0 +false +def f + +13 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +264 +286 +14 +msg +Apply +false +0 +false +a.msg(i, 0, a.integer) + +14 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +289 +299 +15 +println +Apply +false +0 +false +println(x) + +15 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +306 +334 +16 +msg +Apply +false +0 +false +a.ex.msg(i, 0, a.ex.integer) + +16 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +337 +347 +17 +println +Apply +false +0 +false +println(x) + +17 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +360 +363 +18 +f +Apply +false +0 +false +f() + +18 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +354 +370 +18 +msg +Apply +false +0 +false +a.msg(f(), 0, i) + +19 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +373 +383 +19 +println +Apply +false +0 +false +println(x) + +20 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +188 +202 +10 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/coverage/run/trait/test.check b/tests/coverage/run/trait/test.check new file mode 100644 index 000000000000..8c9ca6a4ce24 --- /dev/null +++ b/tests/coverage/run/trait/test.check @@ -0,0 +1,3 @@ +0 +test +test diff --git a/tests/coverage/run/trait/test.scala b/tests/coverage/run/trait/test.scala new file mode 100644 index 000000000000..54166d77c17e --- /dev/null +++ b/tests/coverage/run/trait/test.scala @@ -0,0 +1,14 @@ +trait T1: + def x = 0 + +class Impl1 extends T1 + +trait T2(val p: String) +class Impl2 extends T2("test") with T1 +class Impl3 extends T2(Impl2().p) + +@main +def Test: Unit = + println(Impl1().x) // 0 + println(Impl2().p) // test + println(Impl3().p) // test diff --git a/tests/coverage/run/trait/test.scoverage.check b/tests/coverage/run/trait/test.scoverage.check new file mode 100644 index 000000000000..dcc93e2a7809 --- /dev/null +++ b/tests/coverage/run/trait/test.scoverage.check @@ -0,0 +1,208 @@ +# 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 +# ------------------------------------------ +0 +trait/test.scala + +T1 +Trait +.T1 +x +12 +17 +1 +x +DefDef +false +0 +false +def x + +1 +trait/test.scala + +Impl2 +Class +.Impl2 + +91 +101 +6 + +Apply +false +0 +false +T2("test") + +2 +trait/test.scala + +Impl3 +Class +.Impl3 + +133 +140 +7 + +Apply +false +0 +false +Impl2() + +3 +trait/test.scala + +Impl3 +Class +.Impl3 + +130 +143 +7 + +Apply +false +0 +false +T2(Impl2().p) + +4 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +178 +185 +11 + +Apply +false +0 +false +Impl1() + +5 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +170 +188 +11 +println +Apply +false +0 +false +println(Impl1().x) + +6 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +204 +211 +12 + +Apply +false +0 +false +Impl2() + +7 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +196 +214 +12 +println +Apply +false +0 +false +println(Impl2().p) + +8 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +233 +240 +13 + +Apply +false +0 +false +Impl3() + +9 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +225 +243 +13 +println +Apply +false +0 +false +println(Impl3().p) + +10 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +145 +159 +10 +Test +DefDef +false +0 +false +@main +def Test +