Skip to content

Commit

Permalink
Coverage minima: add more fine-grained control
Browse files Browse the repository at this point in the history
Along with existing statement minimum, add branch minimum. Also, include
this pair of control at the package and file level.
  • Loading branch information
Albert Meltzer committed Feb 18, 2018
1 parent 10db28c commit cb5abca
Show file tree
Hide file tree
Showing 40 changed files with 450 additions and 20 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ Any code between two such comments will not be instrumented or included in the c
Based on minimum coverage, you can fail the build with the following keys

```scala
coverageMinimum := 80
coverageMinimum := 95
coverageFailOnMinimum := true
coverageMinimumBranchTotal := 90
coverageMinimumStmtPerPackage := 90
coverageMinimumBranchPerPackage := 85
coverageMinimumStmtPerFile := 85
coverageMinimumBranchPerFile := 80
```

These settings will be enforced when the reports are generated.
Expand Down
35 changes: 34 additions & 1 deletion src/main/scala/scoverage/ScoverageKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ object ScoverageKeys {
lazy val coverageAggregate = taskKey[Unit]("aggregate reports from subprojects")
lazy val coverageExcludedPackages = settingKey[String]("regex for excluded packages")
lazy val coverageExcludedFiles = settingKey[String]("regex for excluded file paths")
lazy val coverageMinimum = settingKey[Double]("scoverage-minimum-coverage")
lazy val coverageMinimum = settingKey[Double]("scoverage-minimum-coverage-stmt-total")
lazy val coverageMinimumBranchTotal = settingKey[Double]("scoverage-minimum-coverage-branch-total")
lazy val coverageMinimumStmtPerPackage = settingKey[Double]("scoverage-minimum-coverage-stmt-per-package")
lazy val coverageMinimumBranchPerPackage = settingKey[Double]("scoverage-minimum-coverage-branch-per-package")
lazy val coverageMinimumStmtPerFile = settingKey[Double]("scoverage-minimum-coverage-stmt-per-file")
lazy val coverageMinimumBranchPerFile = settingKey[Double]("scoverage-minimum-coverage-branch-per-file")
lazy val coverageFailOnMinimum = settingKey[Boolean]("if coverage is less than this value then fail build")
lazy val coverageHighlighting = settingKey[Boolean]("enables range positioning for highlighting")
lazy val coverageOutputCobertura = settingKey[Boolean]("enables cobertura XML report generation")
Expand All @@ -18,4 +23,32 @@ object ScoverageKeys {
lazy val coverageCleanSubprojectFiles = settingKey[Boolean]("removes subproject data after an aggregation")
lazy val coverageOutputTeamCity = settingKey[Boolean]("turn on teamcity reporting")
lazy val coverageScalacPluginVersion = settingKey[String]("version of scalac-scoverage-plugin to use")

def coverageMinima = Def.setting {
CoverageMinima(
total = CoverageMinimum(
statement = coverageMinimum.value,
branch = coverageMinimumBranchTotal.value),
perPackage = CoverageMinimum(
statement = coverageMinimumStmtPerPackage.value,
branch = coverageMinimumBranchPerPackage.value
),
perFile = CoverageMinimum(
statement = coverageMinimumStmtPerFile.value,
branch = coverageMinimumBranchPerFile.value
)
)
}

case class CoverageMinimum(
statement: Double,
branch: Double
)

case class CoverageMinima(
total: CoverageMinimum,
perPackage: CoverageMinimum,
perFile: CoverageMinimum
)

}
56 changes: 42 additions & 14 deletions src/main/scala/scoverage/ScoverageSbtPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ object ScoverageSbtPlugin extends AutoPlugin {
coverageExcludedPackages := "",
coverageExcludedFiles := "",
coverageMinimum := 0, // default is no minimum
coverageMinimumBranchTotal := 0,
coverageMinimumStmtPerPackage := 0,
coverageMinimumBranchPerPackage := 0,
coverageMinimumStmtPerFile := 0,
coverageMinimumBranchPerFile := 0,
coverageFailOnMinimum := false,
coverageHighlighting := true,
coverageOutputXML := true,
Expand Down Expand Up @@ -123,7 +128,7 @@ object ScoverageSbtPlugin extends AutoPlugin {
sourceEncoding((scalacOptions in (Compile)).value),
log)

checkCoverage(cov, log, coverageMinimum.value, coverageFailOnMinimum.value)
checkCoverage(cov, log, coverageMinima.value, coverageFailOnMinimum.value)
case None => log.warn("No coverage data, skipping reports")
}
}
Expand All @@ -150,7 +155,7 @@ object ScoverageSbtPlugin extends AutoPlugin {
val cfmt = cov.statementCoverageFormatted
log.info(s"Aggregation complete. Coverage was [$cfmt]")

checkCoverage(cov, log, coverageMinimum.value, coverageFailOnMinimum.value)
checkCoverage(cov, log, coverageMinima.value, coverageFailOnMinimum.value)
case None =>
log.info("No subproject data to aggregate, skipping reports")
}
Expand Down Expand Up @@ -244,28 +249,51 @@ object ScoverageSbtPlugin extends AutoPlugin {

private def checkCoverage(coverage: Coverage,
log: Logger,
min: Double,
min: CoverageMinima,
failOnMin: Boolean): Unit = {
val ok: Boolean = checkCoverage(coverage, "Total", log, min.total) &&
coverage.packages.forall(x => checkCoverage(x, s"Package:${x.name}", log, min.perPackage)) &&
coverage.files.forall(x => checkCoverage(x, s"File:${x.filename}", log, min.perFile))

if (!ok && failOnMin)
throw new RuntimeException("Coverage minimum was not reached")

log.info(s"All done. Coverage was [${coverage.statementCoverageFormatted}%]")
}

val cper = coverage.statementCoveragePercent
val cfmt = coverage.statementCoverageFormatted
private def checkCoverage(metrics: CoverageMetrics,
metric: String,
log: Logger,
min: CoverageMinimum): Boolean = {
checkCoverage(s"Branch:$metric", log, min.branch, metrics.branchCoveragePercent) &&
checkCoverage(s"Stmt:$metric", log, min.statement, metrics.statementCoveragePercent)
}

private def checkCoverage(metric: String,
log: Logger,
min: Double,
cper: Double): Boolean = {
// check for default minimum
if (min > 0) {
if (min <= 0) {
true
} else {
def is100(d: Double) = Math.abs(100 - d) <= 0.00001

if (is100(min) && is100(cper)) {
log.info(s"100% Coverage !")
} else if (min > cper) {
log.error(s"Coverage is below minimum [$cfmt% < $min%]")
if (failOnMin)
throw new RuntimeException("Coverage minimum was not reached")
log.info(s"100% Coverage: $metric")
true
} else {
log.info(s"Coverage is above minimum [$cfmt% > $min%]")
val ok: Boolean = min <= cper
val minfmt = scoverage.DoubleFormat.twoFractionDigits(min)
val cfmt = scoverage.DoubleFormat.twoFractionDigits(cper)
if (ok) {
log.info(s"Coverage is above minimum [$cfmt% > $minfmt%]: $metric")
} else {
log.error(s"Coverage is below minimum [$cfmt% < $minfmt%]: $metric")
}
ok
}
}

log.info(s"All done. Coverage was [$cfmt%]")
}

private def sourceEncoding(scalacOptions: Seq[String]): Option[String] = {
Expand Down
14 changes: 14 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-file-branch/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version := "0.1"

scalaVersion := "2.10.4"

libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"

coverageMinimumBranchPerFile := 80

coverageFailOnMinimum := true

resolvers ++= {
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
else Seq.empty
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
val pluginVersion = sys.props.getOrElse(
"plugin.version",
throw new RuntimeException(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin))

addSbtPlugin("org.scoverage" % "sbt-scoverage" % pluginVersion)

resolvers ++= {
if (pluginVersion.endsWith("-SNAPSHOT"))
Seq(Resolver.sonatypeRepo("snapshots"))
else
Seq.empty
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package one

object BadCoverage {

def sum(num1: Int, num2: Int) = {
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package two

object BadCoverage {

def sum(num1: Int, num2: Int) = {
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import org.specs2.mutable._

/**
* Created by tbarke001c on 7/8/14.
*/
class BadCoverageSpec extends Specification {

"BadCoverage" should {
"sum two numbers" in {
one.BadCoverage.sum(1, 2) mustEqual 3
one.BadCoverage.sum(0, 3) mustEqual 3
one.BadCoverage.sum(3, 0) mustEqual 3
two.BadCoverage.sum(1, 2) mustEqual 3
two.BadCoverage.sum(3, 0) mustEqual 3
}
}
}
5 changes: 5 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-file-branch/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# run scoverage
> clean
> coverage
> test
-> coverageReport
14 changes: 14 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-file-stmt/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version := "0.1"

scalaVersion := "2.10.4"

libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"

coverageMinimumStmtPerFile := 90

coverageFailOnMinimum := true

resolvers ++= {
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
else Seq.empty
}
14 changes: 14 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-file-stmt/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
val pluginVersion = sys.props.getOrElse(
"plugin.version",
throw new RuntimeException(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin))

addSbtPlugin("org.scoverage" % "sbt-scoverage" % pluginVersion)

resolvers ++= {
if (pluginVersion.endsWith("-SNAPSHOT"))
Seq(Resolver.sonatypeRepo("snapshots"))
else
Seq.empty
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package one

object BadCoverage {

def sum(num1: Int, num2: Int) = {
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package two

object BadCoverage {

def sum(num1: Int, num2: Int) = {
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import org.specs2.mutable._

/**
* Created by tbarke001c on 7/8/14.
*/
class BadCoverageSpec extends Specification {

"BadCoverage" should {
"sum two numbers" in {
one.BadCoverage.sum(1, 2) mustEqual 3
one.BadCoverage.sum(0, 3) mustEqual 3
one.BadCoverage.sum(3, 0) mustEqual 3
two.BadCoverage.sum(1, 2) mustEqual 3
}
}
}
5 changes: 5 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-file-stmt/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# run scoverage
> clean
> coverage
> test
-> coverageReport
14 changes: 14 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-package-branch/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version := "0.1"

scalaVersion := "2.10.4"

libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"

coverageMinimumBranchPerPackage := 80

coverageFailOnMinimum := true

resolvers ++= {
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
else Seq.empty
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
val pluginVersion = sys.props.getOrElse(
"plugin.version",
throw new RuntimeException(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin))

addSbtPlugin("org.scoverage" % "sbt-scoverage" % pluginVersion)

resolvers ++= {
if (pluginVersion.endsWith("-SNAPSHOT"))
Seq(Resolver.sonatypeRepo("snapshots"))
else
Seq.empty
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package one

object BadCoverage {

def sum(num1: Int, num2: Int) = {
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package two

object BadCoverage {

def sum(num1: Int, num2: Int) = {
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import org.specs2.mutable._

/**
* Created by tbarke001c on 7/8/14.
*/
class BadCoverageSpec extends Specification {

"BadCoverage" should {
"sum two numbers" in {
one.BadCoverage.sum(1, 2) mustEqual 3
one.BadCoverage.sum(0, 3) mustEqual 3
one.BadCoverage.sum(3, 0) mustEqual 3
two.BadCoverage.sum(1, 2) mustEqual 3
two.BadCoverage.sum(0, 3) mustEqual 3
}
}
}
5 changes: 5 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-package-branch/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# run scoverage
> clean
> coverage
> test
-> coverageReport
14 changes: 14 additions & 0 deletions src/sbt-test/scoverage/bad-coverage-package-stmt/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version := "0.1"

scalaVersion := "2.10.4"

libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"

coverageMinimumStmtPerPackage := 80

coverageFailOnMinimum := true

resolvers ++= {
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
else Seq.empty
}
Loading

0 comments on commit cb5abca

Please sign in to comment.