diff --git a/README.md b/README.md index 2fda42cc..de07dabd 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Search for scalac-scoverage-plugin. #### Excluding code from coverage stats You can exclude whole classes or packages by name. Pass a semicolon separated -list of regexes to the 'excludedPackages' option. +list of regexes to the `excludedPackages` option. For example: @@ -98,6 +98,15 @@ The regular expressions are matched against the fully qualified class name, and Any matched classes will not be instrumented or included in the coverage report. +You can also exclude files from being considered for instrumentation. + + -P:scoverage:excludedFiles:.*\/two\/GoodCoverage;.*\/three\/.* + +Note: The `.scala` file extension needs to be omitted from the filename, if one is given. + +Note: These two options only work for Scala2. Right now Scala3 does not support +a way to exclude packages or files from being instrumented. + You can also mark sections of code with comments like: // $COVERAGE-OFF$ diff --git a/domain/src/main/scala/scoverage/domain/coverage.scala b/domain/src/main/scala/scoverage/domain/Coverage.scala similarity index 97% rename from domain/src/main/scala/scoverage/domain/coverage.scala rename to domain/src/main/scala/scoverage/domain/Coverage.scala index a6b8764c..1bc60b6e 100644 --- a/domain/src/main/scala/scoverage/domain/coverage.scala +++ b/domain/src/main/scala/scoverage/domain/Coverage.scala @@ -15,6 +15,7 @@ case class Coverage() private val statementsById = mutable.Map[Int, Statement]() override def statements = statementsById.values def add(stmt: Statement): Unit = statementsById.put(stmt.id, stmt) + def remove(id: Int): Unit = statementsById.remove(id) private val ignoredStatementsById = mutable.Map[Int, Statement]() override def ignoredStatements = ignoredStatementsById.values diff --git a/plugin/src/main/scala/scoverage/CoverageFilter.scala b/plugin/src/main/scala/scoverage/CoverageFilter.scala index 189720ad..a6db1108 100644 --- a/plugin/src/main/scala/scoverage/CoverageFilter.scala +++ b/plugin/src/main/scala/scoverage/CoverageFilter.scala @@ -3,6 +3,7 @@ package scoverage import scala.collection.mutable import scala.reflect.internal.util.Position import scala.reflect.internal.util.SourceFile +import scala.tools.nsc.reporters.Reporter import scala.util.matching.Regex /** Methods related to filtering the instrumentation and coverage. @@ -11,7 +12,7 @@ import scala.util.matching.Regex */ trait CoverageFilter { def isClassIncluded(className: String): Boolean - def isFileIncluded(file: SourceFile): Boolean + def isFileIncluded(path: String): Boolean def isLineIncluded(position: Position): Boolean def isSymbolIncluded(symbolName: String): Boolean def getExcludedLineNumbers(sourceFile: SourceFile): List[Range] @@ -21,15 +22,22 @@ object AllCoverageFilter extends CoverageFilter { override def getExcludedLineNumbers(sourceFile: SourceFile): List[Range] = Nil override def isLineIncluded(position: Position): Boolean = true override def isClassIncluded(className: String): Boolean = true - override def isFileIncluded(file: SourceFile): Boolean = true + override def isFileIncluded(path: String): Boolean = true override def isSymbolIncluded(symbolName: String): Boolean = true } class RegexCoverageFilter( excludedPackages: Seq[String], excludedFiles: Seq[String], - excludedSymbols: Seq[String] + excludedSymbols: Seq[String], + reporter: Reporter ) extends CoverageFilter { + if (excludedPackages.nonEmpty) + reporter.echo(s"scoverage excludedPackages: ${excludedPackages}") + if (excludedFiles.nonEmpty) + reporter.echo(s"scoverage excludedFiles: ${excludedFiles}") + if (excludedSymbols.nonEmpty) + reporter.echo(s"scoverage excludedSymbols: ${excludedSymbols}") val excludedClassNamePatterns = excludedPackages.map(_.r.pattern) val excludedFilePatterns = excludedFiles.map(_.r.pattern) @@ -54,11 +62,11 @@ class RegexCoverageFilter( ) } - override def isFileIncluded(file: SourceFile): Boolean = { - def isFileMatch(file: SourceFile) = excludedFilePatterns.exists( - _.matcher(file.path.replace(".scala", "")).matches + override def isFileIncluded(path: String): Boolean = { + def isFileMatch(path: String) = excludedFilePatterns.exists( + _.matcher(path.replace(".scala", "")).matches ) - excludedFilePatterns.isEmpty || !isFileMatch(file) + excludedFilePatterns.isEmpty || !isFileMatch(path) } /** True if the line containing `position` has not been excluded by a magic comment. diff --git a/plugin/src/main/scala/scoverage/ScoveragePlugin.scala b/plugin/src/main/scala/scoverage/ScoveragePlugin.scala index 90235d7e..dc8bceb3 100644 --- a/plugin/src/main/scala/scoverage/ScoveragePlugin.scala +++ b/plugin/src/main/scala/scoverage/ScoveragePlugin.scala @@ -112,7 +112,8 @@ class ScoverageInstrumentationComponent( coverageFilter = new RegexCoverageFilter( options.excludedPackages, options.excludedFiles, - options.excludedSymbols + options.excludedSymbols, + reporter ) new File(options.dataDir).mkdirs() // ensure data directory is created } @@ -230,8 +231,9 @@ class ScoverageInstrumentationComponent( ): Tree = { safeSource(tree) match { case None => - reporter.echo( - s"[warn] Could not instrument [${tree.getClass.getSimpleName}/${tree.symbol}]. No pos." + reporter.warning( + NoPosition, + s"Could not instrument [${tree.getClass.getSimpleName}/${tree.symbol}]." ) tree case Some(source) => @@ -351,7 +353,7 @@ class ScoverageInstrumentationComponent( def isClassIncluded(symbol: Symbol): Boolean = coverageFilter.isClassIncluded(symbol.fullNameString) def isFileIncluded(source: SourceFile): Boolean = - coverageFilter.isFileIncluded(source) + coverageFilter.isFileIncluded(source.path) def isStatementIncluded(pos: Position): Boolean = coverageFilter.isLineIncluded(pos) def isSymbolIncluded(symbol: Symbol): Boolean = @@ -361,7 +363,7 @@ class ScoverageInstrumentationComponent( Location.fromGlobal(global)(t) match { case Some(loc) => this.location = loc case _ => - reporter.warning(t.pos, s"[warn] Cannot update location for $t") + reporter.warning(t.pos, s"Cannot update location for $t") } } diff --git a/plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala b/plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala index 781df1e0..6fcc4f46 100644 --- a/plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala +++ b/plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala @@ -4,50 +4,56 @@ import scala.reflect.internal.util.BatchSourceFile import scala.reflect.internal.util.NoFile import scala.reflect.internal.util.SourceFile import scala.reflect.io.VirtualFile +import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.nsc.Settings import munit.FunSuite class RegexCoverageFilterTest extends FunSuite { + val reporter = new ConsoleReporter(new Settings()) + test("isClassIncluded should return true for empty excludes") { - assert(new RegexCoverageFilter(Nil, Nil, Nil).isClassIncluded("x")) + assert( + new RegexCoverageFilter(Nil, Nil, Nil, reporter).isClassIncluded("x") + ) } test("should not crash for empty input") { - assert(new RegexCoverageFilter(Nil, Nil, Nil).isClassIncluded("")) + assert(new RegexCoverageFilter(Nil, Nil, Nil, reporter).isClassIncluded("")) } test("should exclude scoverage -> scoverage") { assert( - !new RegexCoverageFilter(Seq("scoverage"), Nil, Nil) + !new RegexCoverageFilter(Seq("scoverage"), Nil, Nil, reporter) .isClassIncluded("scoverage") ) } test("should include scoverage -> scoverageeee") { assert( - new RegexCoverageFilter(Seq("scoverage"), Nil, Nil) + new RegexCoverageFilter(Seq("scoverage"), Nil, Nil, reporter) .isClassIncluded("scoverageeee") ) } test("should exclude scoverage* -> scoverageeee") { assert( - !new RegexCoverageFilter(Seq("scoverage*"), Nil, Nil) + !new RegexCoverageFilter(Seq("scoverage*"), Nil, Nil, reporter) .isClassIncluded("scoverageeee") ) } test("should include eee -> scoverageeee") { assert( - new RegexCoverageFilter(Seq("eee"), Nil, Nil) + new RegexCoverageFilter(Seq("eee"), Nil, Nil, reporter) .isClassIncluded("scoverageeee") ) } test("should exclude .*eee -> scoverageeee") { assert( - !new RegexCoverageFilter(Seq(".*eee"), Nil, Nil) + !new RegexCoverageFilter(Seq(".*eee"), Nil, Nil, reporter) .isClassIncluded("scoverageeee") ) } @@ -56,91 +62,100 @@ class RegexCoverageFilterTest extends FunSuite { test("isFileIncluded should return true for empty excludes") { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) - assert(new RegexCoverageFilter(Nil, Nil, Nil).isFileIncluded(file)) + assert( + new RegexCoverageFilter(Nil, Nil, Nil, reporter) + .isFileIncluded(file.path) + ) } test("should exclude by filename") { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) assert( - !new RegexCoverageFilter(Nil, Seq("sammy"), Nil) - .isFileIncluded(file) + !new RegexCoverageFilter(Nil, Seq("sammy"), Nil, reporter) + .isFileIncluded(file.path) ) } test("should exclude by regex wildcard") { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) assert( - !new RegexCoverageFilter(Nil, Seq("sam.*"), Nil) - .isFileIncluded(file) + !new RegexCoverageFilter(Nil, Seq("sam.*"), Nil, reporter) + .isFileIncluded(file.path) ) } test("should not exclude non matching regex") { val file = new BatchSourceFile(abstractFile, Array.emptyCharArray) assert( - new RegexCoverageFilter(Nil, Seq("qweqeqwe"), Nil) - .isFileIncluded(file) + new RegexCoverageFilter(Nil, Seq("qweqeqwe"), Nil, reporter) + .isFileIncluded(file.path) ) } val options = ScoverageOptions.default() test("isSymbolIncluded should return true for empty excludes") { - assert(new RegexCoverageFilter(Nil, Nil, Nil).isSymbolIncluded("x")) + assert( + new RegexCoverageFilter(Nil, Nil, Nil, reporter) + .isSymbolIncluded("x") + ) } test("should not crash for empty input") { - assert(new RegexCoverageFilter(Nil, Nil, Nil).isSymbolIncluded("")) + assert( + new RegexCoverageFilter(Nil, Nil, Nil, reporter) + .isSymbolIncluded("") + ) } test("should exclude scoverage -> scoverage") { assert( - !new RegexCoverageFilter(Nil, Nil, Seq("scoverage")) + !new RegexCoverageFilter(Nil, Nil, Seq("scoverage"), reporter) .isSymbolIncluded("scoverage") ) } test("should include scoverage -> scoverageeee") { assert( - new RegexCoverageFilter(Nil, Nil, Seq("scoverage")) + new RegexCoverageFilter(Nil, Nil, Seq("scoverage"), reporter) .isSymbolIncluded("scoverageeee") ) } test("should exclude scoverage* -> scoverageeee") { assert( - !new RegexCoverageFilter(Nil, Nil, Seq("scoverage*")) + !new RegexCoverageFilter(Nil, Nil, Seq("scoverage*"), reporter) .isSymbolIncluded("scoverageeee") ) } test("should include eee -> scoverageeee") { assert( - new RegexCoverageFilter(Nil, Nil, Seq("eee")) + new RegexCoverageFilter(Nil, Nil, Seq("eee"), reporter) .isSymbolIncluded("scoverageeee") ) } test("should exclude .*eee -> scoverageeee") { assert( - !new RegexCoverageFilter(Nil, Nil, Seq(".*eee")) + !new RegexCoverageFilter(Nil, Nil, Seq(".*eee"), reporter) .isSymbolIncluded("scoverageeee") ) } test("should exclude scala.reflect.api.Exprs.Expr") { assert( - !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols) + !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols, reporter) .isSymbolIncluded("scala.reflect.api.Exprs.Expr") ) } test("should exclude scala.reflect.macros.Universe.Tree") { assert( - !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols) + !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols, reporter) .isSymbolIncluded("scala.reflect.macros.Universe.Tree") ) } test("should exclude scala.reflect.api.Trees.Tree") { assert( - !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols) + !new RegexCoverageFilter(Nil, Nil, options.excludedSymbols, reporter) .isSymbolIncluded("scala.reflect.api.Trees.Tree") ) } @@ -158,7 +173,7 @@ class RegexCoverageFilterTest extends FunSuite { |8 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil, reporter) .getExcludedLineNumbers(mockSourceFile(file)) assertEquals(numbers, List.empty) } @@ -182,7 +197,7 @@ class RegexCoverageFilterTest extends FunSuite { |16 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil, reporter) .getExcludedLineNumbers(mockSourceFile(file)) assertEquals(numbers, List(Range(4, 9), Range(12, 14))) } @@ -205,7 +220,7 @@ class RegexCoverageFilterTest extends FunSuite { |15 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil, reporter) .getExcludedLineNumbers(mockSourceFile(file)) assertEquals(numbers, List(Range(4, 9), Range(12, 16))) } @@ -228,7 +243,7 @@ class RegexCoverageFilterTest extends FunSuite { |15 """.stripMargin - val numbers = new RegexCoverageFilter(Nil, Nil, Nil) + val numbers = new RegexCoverageFilter(Nil, Nil, Nil, reporter) .getExcludedLineNumbers(mockSourceFile(file)) assertEquals(numbers, List(Range(4, 9), Range(12, 16))) } diff --git a/reporter/src/test/scala/scoverage/reporter/IOUtilsTest.scala b/reporter/src/test/scala/scoverage/reporter/IOUtilsTest.scala index 8f3bcfe2..a106988b 100644 --- a/reporter/src/test/scala/scoverage/reporter/IOUtilsTest.scala +++ b/reporter/src/test/scala/scoverage/reporter/IOUtilsTest.scala @@ -6,7 +6,6 @@ import java.util.UUID import munit.FunSuite import scoverage.domain.Constants -import scoverage.reporter.IOUtils /** @author Stephen Samuel */ class IOUtilsTest extends FunSuite {