Skip to content

Commit 18be8d1

Browse files
Port coverage filter options for packages and files (#19727)
- Add `-coverage-exclude-packages` option that excludes packages and classes from generating coverage - Add `-coverage-exclude-files` option that excludes files from generating coverage
2 parents 7f410aa + 641f5db commit 18be8d1

13 files changed

+251
-6
lines changed

.gitignore

-4
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,3 @@ docs/_spec/.jekyll-metadata
9999

100100
# scaladoc related
101101
scaladoc/output/
102-
103-
#coverage
104-
coverage/
105-

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ trait CommonScalaSettings:
127127

128128
/* Coverage settings */
129129
val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out"))
130+
val coverageExcludeClasslikes: Setting[List[String]] = MultiStringSetting("-coverage-exclude-classlikes", "packages, classes and modules", "List of regexes for packages, classes and modules to exclude from coverage.", aliases = List("--coverage-exclude-classlikes"))
131+
val coverageExcludeFiles: Setting[List[String]] = MultiStringSetting("-coverage-exclude-files", "files", "List of regexes for files to exclude from coverage.", aliases = List("--coverage-exclude-files"))
130132

131133
/* Other settings */
132134
val encoding: Setting[String] = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding"))

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

+33-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import core.Constants.Constant
1313
import core.NameOps.isContextFunction
1414
import core.StdNames.nme
1515
import core.Types.*
16+
import core.Decorators.*
1617
import coverage.*
1718
import typer.LiftCoverage
1819
import util.{SourcePosition, SourceFile}
1920
import util.Spans.Span
2021
import localopt.StringInterpolatorOpt
2122
import inlines.Inlines
23+
import scala.util.matching.Regex
24+
import java.util.regex.Pattern
2225

2326
/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
2427
* ("instruments" the source code).
@@ -41,6 +44,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
4144
// stores all instrumented statements
4245
private val coverage = Coverage()
4346

47+
private var coverageExcludeClasslikePatterns: List[Pattern] = Nil
48+
private var coverageExcludeFilePatterns: List[Pattern] = Nil
49+
4450
override def run(using ctx: Context): Unit =
4551
val outputPath = ctx.settings.coverageOutputDir.value
4652

@@ -54,10 +60,26 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
5460
.filter(_.nn.getName.nn.startsWith("scoverage"))
5561
.foreach(_.nn.delete())
5662
end if
63+
64+
coverageExcludeClasslikePatterns = ctx.settings.coverageExcludeClasslikes.value.map(_.r.pattern)
65+
coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern)
66+
5767
super.run
5868

5969
Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value)
6070

71+
private def isClassIncluded(sym: Symbol)(using Context): Boolean =
72+
val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show
73+
coverageExcludeClasslikePatterns.isEmpty || !coverageExcludeClasslikePatterns.exists(
74+
_.matcher(fqn).nn.matches
75+
)
76+
77+
private def isFileIncluded(file: SourceFile)(using Context): Boolean =
78+
val normalizedPath = file.path.replace(".scala", "")
79+
coverageExcludeFilePatterns.isEmpty || !coverageExcludeFilePatterns.exists(
80+
_.matcher(normalizedPath).nn.matches
81+
)
82+
6183
override protected def newTransformer(using Context) =
6284
CoverageTransformer(ctx.settings.coverageOutputDir.value)
6385

@@ -269,8 +291,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
269291
transformDefDef(tree)
270292

271293
case tree: PackageDef =>
272-
// only transform the statements of the package
273-
cpy.PackageDef(tree)(tree.pid, transform(tree.stats))
294+
if isFileIncluded(tree.srcPos.sourcePos.source) && isClassIncluded(tree.symbol) then
295+
// only transform the statements of the package
296+
cpy.PackageDef(tree)(tree.pid, transform(tree.stats))
297+
else
298+
tree
299+
300+
case tree: TypeDef =>
301+
if isFileIncluded(tree.srcPos.sourcePos.source) && isClassIncluded(tree.symbol) then
302+
super.transform(tree)
303+
else
304+
tree
274305

275306
case tree: Assign =>
276307
// only transform the rhs

tests/coverage/pos/ExcludeClass.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//> using options -coverage-exclude-classlikes:covtest.Klass
2+
3+
package covtest
4+
5+
class Klass {
6+
def abs(i: Int) =
7+
if i > 0 then
8+
i
9+
else
10+
-i
11+
}
12+
13+
class Klass2 {
14+
def abs(i: Int) =
15+
if i > 0 then
16+
i
17+
else
18+
-i
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------
21+
0
22+
ExcludeClass.scala
23+
covtest
24+
Klass2
25+
Class
26+
covtest.Klass2
27+
abs
28+
219
29+
220
30+
16
31+
i
32+
Ident
33+
true
34+
0
35+
false
36+
i
37+
38+
1
39+
ExcludeClass.scala
40+
covtest
41+
Klass2
42+
Class
43+
covtest.Klass2
44+
abs
45+
236
46+
238
47+
18
48+
unary_-
49+
Select
50+
true
51+
0
52+
false
53+
-i
54+
55+
2
56+
ExcludeClass.scala
57+
covtest
58+
Klass2
59+
Class
60+
covtest.Klass2
61+
abs
62+
177
63+
184
64+
14
65+
abs
66+
DefDef
67+
false
68+
0
69+
false
70+
def abs
71+

tests/coverage/pos/ExcludeDef.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -coverage-exclude-classlikes:covtest\..*
2+
3+
package covtest
4+
5+
def abs(i: Int) =
6+
if i > 0 then
7+
i
8+
else
9+
-i
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------

tests/coverage/pos/ExcludeFile.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//> using options -coverage-exclude-files:.*ExcludeFile
2+
3+
package covtest
4+
5+
class Klass {
6+
def abs(i: Int) =
7+
if i > 0 then
8+
i
9+
else
10+
-i
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//> using options -coverage-exclude-classlikes:covtest.Oject,covtest.Tait
2+
3+
package covtest
4+
5+
object Oject {
6+
def abs(i: Int) =
7+
if i > 0 then
8+
i
9+
else
10+
-i
11+
}
12+
13+
trait Tait {
14+
def abs(i: Int): Int
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//> using options -coverage-exclude-classlikes:covtest
2+
3+
package covtest
4+
5+
class Klass {
6+
def abs(i: Int) =
7+
if i > 0 then
8+
i
9+
else
10+
-i
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------

0 commit comments

Comments
 (0)