From 822011820998bc9bcae2914657f1c8949074d2f4 Mon Sep 17 00:00:00 2001 From: Ivan Luzyanin Date: Thu, 29 Feb 2024 12:59:36 -0800 Subject: [PATCH 1/2] feature: Implemented 'exclude' filter functionality for Scala 3 --- .../scala/scoverage/ScoverageSbtPlugin.scala | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scoverage/ScoverageSbtPlugin.scala b/src/main/scala/scoverage/ScoverageSbtPlugin.scala index a7a22add..ba10d33c 100644 --- a/src/main/scala/scoverage/ScoverageSbtPlugin.scala +++ b/src/main/scala/scoverage/ScoverageSbtPlugin.scala @@ -7,6 +7,7 @@ import sbt.plugins.JvmPlugin import scoverage.reporter.CoberturaXmlWriter import scoverage.domain.Constants import scoverage.domain.Coverage +import scoverage.domain.Statement import scoverage.reporter.CoverageAggregator import scoverage.reporter.IOUtils import scoverage.reporter.ScoverageHtmlWriter @@ -14,6 +15,8 @@ import scoverage.reporter.ScoverageXmlWriter import scoverage.serialize.Serializer import java.time.Instant +import scala.util.Try +import scala.util.matching.Regex object ScoverageSbtPlugin extends AutoPlugin { @@ -225,10 +228,19 @@ object ScoverageSbtPlugin extends AutoPlugin { coverageSourceRoot.value.getAbsoluteFile() ) match { case Some(cov) => + val coverage = if (isScala3SupportingScoverage(scalaVersion.value)) { + filterCoverage( + cov, + coverageExcludedFiles.value, + coverageExcludedPackages.value, + log + ) + } else cov + writeReports( target, (Compile / sourceDirectories).value, - cov, + coverage, coverageOutputCobertura.value, coverageOutputXML.value, coverageOutputHTML.value, @@ -256,10 +268,19 @@ object ScoverageSbtPlugin extends AutoPlugin { CoverageAggregator.aggregate(dataDirs, coverageSourceRoot.value) match { case Some(cov) => + val coverage = if (isScala3SupportingScoverage(scalaVersion.value)) { + filterCoverage( + cov, + coverageExcludedFiles.value, + coverageExcludedPackages.value, + log + ) + } else cov + writeReports( coverageDataDir.value, sourceDirectories.all(aggregateFilter).value.flatten, - cov, + coverage, coverageOutputCobertura.value, coverageOutputXML.value, coverageOutputHTML.value, @@ -278,6 +299,73 @@ object ScoverageSbtPlugin extends AutoPlugin { } } + private def compileRegex(regexString: String): Option[Regex] = + Try(regexString.r).toOption + + private def asRegexList(strSetting: String) = strSetting + .split(';') + .map(_.trim) + .filter(_.nonEmpty) + .flatMap(compileRegex) + + private def filterStatements( + statements: Iterable[Statement], + excludedFiles: Array[Regex], + excludedPackages: Array[Regex], + log: Logger + ) = statements + .groupBy { statement => + (statement.location.sourcePath, statement.location.fullClassName) + } + .filterKeys { case t @ (sourcePath, fullClassName) => + val preserveSource = excludedFiles.isEmpty || excludedFiles.forall( + _.findFirstMatchIn(sourcePath).isEmpty + ) + val preservePackage = excludedPackages.isEmpty || excludedPackages + .forall(_.findFirstMatchIn(fullClassName).isEmpty) + if (!preserveSource) log.info(s"Excluded file from report: $sourcePath") + if (!preservePackage) + log.info(s"Excluded package from report: $fullClassName") + preserveSource && preservePackage + } + .values + .flatten + + private def filterCoverage( + originalCoverage: Coverage, + excludedFilesSetting: String, + excludedPackagesSetting: String, + log: Logger + ): Coverage = { + + val excludedFiles: Array[Regex] = asRegexList(excludedFilesSetting) + val excludedPackages: Array[Regex] = asRegexList(excludedPackagesSetting) + + if (excludedFiles.isEmpty && excludedPackages.isEmpty) { + log.debug("Skipping filter") + originalCoverage + } else { + log.debug("Applying filter") + val updatedCoverage = Coverage() + + filterStatements( + originalCoverage.statements, + excludedFiles, + excludedPackages, + log + ).foreach(updatedCoverage.add) + + filterStatements( + originalCoverage.ignoredStatements, + excludedFiles, + excludedPackages, + log + ).foreach(updatedCoverage.addIgnoredStatement) + + updatedCoverage + } + } + private def writeReports( crossTarget: File, compileSourceDirectories: Seq[File], From 02f292f5520d627573f202c836153353512d231f Mon Sep 17 00:00:00 2001 From: Ivan Luzyanin Date: Thu, 29 Feb 2024 15:33:56 -0800 Subject: [PATCH 2/2] fixed coverage minimum and summary --- src/main/scala/scoverage/ScoverageSbtPlugin.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/scoverage/ScoverageSbtPlugin.scala b/src/main/scala/scoverage/ScoverageSbtPlugin.scala index ba10d33c..d4f35122 100644 --- a/src/main/scala/scoverage/ScoverageSbtPlugin.scala +++ b/src/main/scala/scoverage/ScoverageSbtPlugin.scala @@ -251,7 +251,7 @@ object ScoverageSbtPlugin extends AutoPlugin { ) CoverageMinimum.all.value - .checkCoverage(cov, coverageFailOnMinimum.value) + .checkCoverage(coverage, coverageFailOnMinimum.value) case None => log.warn("No coverage data, skipping reports") } } @@ -289,11 +289,11 @@ object ScoverageSbtPlugin extends AutoPlugin { sourceEncoding((Compile / scalacOptions).value), log ) - val cfmt = cov.statementCoverageFormatted + val cfmt = coverage.statementCoverageFormatted log.info(s"Aggregation complete. Coverage was [$cfmt]") CoverageMinimum.all.value - .checkCoverage(cov, coverageFailOnMinimum.value) + .checkCoverage(coverage, coverageFailOnMinimum.value) case None => log.info("No subproject data to aggregate, skipping reports") }