Skip to content

Commit bdfd90e

Browse files
Albert MeltzerAlbert Meltzer
Albert Meltzer
authored and
Albert Meltzer
committed
Coverage minima: add more fine-grained control
Along with existing statement minimum, add branch minimum. Also, include this pair of control at the package and file level.
1 parent 183fbad commit bdfd90e

File tree

40 files changed

+450
-20
lines changed

40 files changed

+450
-20
lines changed

Diff for: README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,13 @@ Any code between two such comments will not be instrumented or included in the c
108108
Based on minimum coverage, you can fail the build with the following keys
109109

110110
```scala
111-
coverageMinimum := 80
111+
coverageMinimum := 95
112112
coverageFailOnMinimum := true
113+
coverageMinimumBranchTotal := 90
114+
coverageMinimumStmtPerPackage := 90
115+
coverageMinimumBranchPerPackage := 85
116+
coverageMinimumStmtPerFile := 85
117+
coverageMinimumBranchPerFile := 80
113118
```
114119

115120
These settings will be enforced when the reports are generated.

Diff for: src/main/scala/scoverage/ScoverageKeys.scala

+34-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ object ScoverageKeys {
88
lazy val coverageAggregate = taskKey[Unit]("aggregate reports from subprojects")
99
lazy val coverageExcludedPackages = settingKey[String]("regex for excluded packages")
1010
lazy val coverageExcludedFiles = settingKey[String]("regex for excluded file paths")
11-
lazy val coverageMinimum = settingKey[Double]("scoverage-minimum-coverage")
11+
lazy val coverageMinimum = settingKey[Double]("scoverage-minimum-coverage-stmt-total")
12+
lazy val coverageMinimumBranchTotal = settingKey[Double]("scoverage-minimum-coverage-branch-total")
13+
lazy val coverageMinimumStmtPerPackage = settingKey[Double]("scoverage-minimum-coverage-stmt-per-package")
14+
lazy val coverageMinimumBranchPerPackage = settingKey[Double]("scoverage-minimum-coverage-branch-per-package")
15+
lazy val coverageMinimumStmtPerFile = settingKey[Double]("scoverage-minimum-coverage-stmt-per-file")
16+
lazy val coverageMinimumBranchPerFile = settingKey[Double]("scoverage-minimum-coverage-branch-per-file")
1217
lazy val coverageFailOnMinimum = settingKey[Boolean]("if coverage is less than this value then fail build")
1318
lazy val coverageHighlighting = settingKey[Boolean]("enables range positioning for highlighting")
1419
lazy val coverageOutputCobertura = settingKey[Boolean]("enables cobertura XML report generation")
@@ -17,4 +22,32 @@ object ScoverageKeys {
1722
lazy val coverageOutputDebug = settingKey[Boolean]("turn on the debug report")
1823
lazy val coverageOutputTeamCity = settingKey[Boolean]("turn on teamcity reporting")
1924
lazy val coverageScalacPluginVersion = settingKey[String]("version of scalac-scoverage-plugin to use")
25+
26+
def coverageMinima = Def.setting {
27+
CoverageMinima(
28+
total = CoverageMinimum(
29+
statement = coverageMinimum.value,
30+
branch = coverageMinimumBranchTotal.value),
31+
perPackage = CoverageMinimum(
32+
statement = coverageMinimumStmtPerPackage.value,
33+
branch = coverageMinimumBranchPerPackage.value
34+
),
35+
perFile = CoverageMinimum(
36+
statement = coverageMinimumStmtPerFile.value,
37+
branch = coverageMinimumBranchPerFile.value
38+
)
39+
)
40+
}
41+
42+
case class CoverageMinimum(
43+
statement: Double,
44+
branch: Double
45+
)
46+
47+
case class CoverageMinima(
48+
total: CoverageMinimum,
49+
perPackage: CoverageMinimum,
50+
perFile: CoverageMinimum
51+
)
52+
2053
}

Diff for: src/main/scala/scoverage/ScoverageSbtPlugin.scala

+42-14
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ object ScoverageSbtPlugin extends AutoPlugin {
2828
coverageExcludedPackages := "",
2929
coverageExcludedFiles := "",
3030
coverageMinimum := 0, // default is no minimum
31+
coverageMinimumBranchTotal := 0,
32+
coverageMinimumStmtPerPackage := 0,
33+
coverageMinimumBranchPerPackage := 0,
34+
coverageMinimumStmtPerFile := 0,
35+
coverageMinimumBranchPerFile := 0,
3136
coverageFailOnMinimum := false,
3237
coverageHighlighting := true,
3338
coverageOutputXML := true,
@@ -122,7 +127,7 @@ object ScoverageSbtPlugin extends AutoPlugin {
122127
sourceEncoding((Compile / scalacOptions).value),
123128
log)
124129

125-
checkCoverage(cov, log, coverageMinimum.value, coverageFailOnMinimum.value)
130+
checkCoverage(cov, log, coverageMinima.value, coverageFailOnMinimum.value)
126131
case None => log.warn("No coverage data, skipping reports")
127132
}
128133
}
@@ -148,7 +153,7 @@ object ScoverageSbtPlugin extends AutoPlugin {
148153
val cfmt = cov.statementCoverageFormatted
149154
log.info(s"Aggregation complete. Coverage was [$cfmt]")
150155

151-
checkCoverage(cov, log, coverageMinimum.value, coverageFailOnMinimum.value)
156+
checkCoverage(cov, log, coverageMinima.value, coverageFailOnMinimum.value)
152157
case None =>
153158
log.info("No subproject data to aggregate, skipping reports")
154159
}
@@ -242,28 +247,51 @@ object ScoverageSbtPlugin extends AutoPlugin {
242247

243248
private def checkCoverage(coverage: Coverage,
244249
log: Logger,
245-
min: Double,
250+
min: CoverageMinima,
246251
failOnMin: Boolean): Unit = {
252+
val ok: Boolean = checkCoverage(coverage, "Total", log, min.total) &&
253+
coverage.packages.forall(x => checkCoverage(x, s"Package:${x.name}", log, min.perPackage)) &&
254+
coverage.files.forall(x => checkCoverage(x, s"File:${x.filename}", log, min.perFile))
255+
256+
if (!ok && failOnMin)
257+
throw new RuntimeException("Coverage minimum was not reached")
258+
259+
log.info(s"All done. Coverage was [${coverage.statementCoverageFormatted}%]")
260+
}
247261

248-
val cper = coverage.statementCoveragePercent
249-
val cfmt = coverage.statementCoverageFormatted
262+
private def checkCoverage(metrics: CoverageMetrics,
263+
metric: String,
264+
log: Logger,
265+
min: CoverageMinimum): Boolean = {
266+
checkCoverage(s"Branch:$metric", log, min.branch, metrics.branchCoveragePercent) &&
267+
checkCoverage(s"Stmt:$metric", log, min.statement, metrics.statementCoveragePercent)
268+
}
250269

270+
private def checkCoverage(metric: String,
271+
log: Logger,
272+
min: Double,
273+
cper: Double): Boolean = {
251274
// check for default minimum
252-
if (min > 0) {
275+
if (min <= 0) {
276+
true
277+
} else {
253278
def is100(d: Double) = Math.abs(100 - d) <= 0.00001
254279

255280
if (is100(min) && is100(cper)) {
256-
log.info(s"100% Coverage !")
257-
} else if (min > cper) {
258-
log.error(s"Coverage is below minimum [$cfmt% < $min%]")
259-
if (failOnMin)
260-
throw new RuntimeException("Coverage minimum was not reached")
281+
log.debug(s"100% Coverage: $metric")
282+
true
261283
} else {
262-
log.info(s"Coverage is above minimum [$cfmt% > $min%]")
284+
val ok: Boolean = min <= cper
285+
val minfmt = scoverage.DoubleFormat.twoFractionDigits(min)
286+
val cfmt = scoverage.DoubleFormat.twoFractionDigits(cper)
287+
if (ok) {
288+
log.debug(s"Coverage is above minimum [$cfmt% > $minfmt%]: $metric")
289+
} else {
290+
log.error(s"Coverage is below minimum [$cfmt% < $minfmt%]: $metric")
291+
}
292+
ok
263293
}
264294
}
265-
266-
log.info(s"All done. Coverage was [$cfmt%]")
267295
}
268296

269297
private def sourceEncoding(scalacOptions: Seq[String]): Option[String] = {
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version := "0.1"
2+
3+
scalaVersion := "2.10.4"
4+
5+
libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"
6+
7+
coverageMinimumBranchPerFile := 80
8+
9+
coverageFailOnMinimum := true
10+
11+
resolvers ++= {
12+
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
13+
else Seq.empty
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
val pluginVersion = sys.props.getOrElse(
2+
"plugin.version",
3+
throw new RuntimeException(
4+
"""|The system property 'plugin.version' is not defined.
5+
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin))
6+
7+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % pluginVersion)
8+
9+
resolvers ++= {
10+
if (pluginVersion.endsWith("-SNAPSHOT"))
11+
Seq(Resolver.sonatypeRepo("snapshots"))
12+
else
13+
Seq.empty
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package one
2+
3+
object BadCoverage {
4+
5+
def sum(num1: Int, num2: Int) = {
6+
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package two
2+
3+
object BadCoverage {
4+
5+
def sum(num1: Int, num2: Int) = {
6+
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import org.specs2.mutable._
2+
3+
/**
4+
* Created by tbarke001c on 7/8/14.
5+
*/
6+
class BadCoverageSpec extends Specification {
7+
8+
"BadCoverage" should {
9+
"sum two numbers" in {
10+
one.BadCoverage.sum(1, 2) mustEqual 3
11+
one.BadCoverage.sum(0, 3) mustEqual 3
12+
one.BadCoverage.sum(3, 0) mustEqual 3
13+
two.BadCoverage.sum(1, 2) mustEqual 3
14+
two.BadCoverage.sum(3, 0) mustEqual 3
15+
}
16+
}
17+
}

Diff for: src/sbt-test/scoverage/bad-coverage-file-branch/test

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# run scoverage
2+
> clean
3+
> coverage
4+
> test
5+
-> coverageReport
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version := "0.1"
2+
3+
scalaVersion := "2.10.4"
4+
5+
libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"
6+
7+
coverageMinimumStmtPerFile := 90
8+
9+
coverageFailOnMinimum := true
10+
11+
resolvers ++= {
12+
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
13+
else Seq.empty
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
val pluginVersion = sys.props.getOrElse(
2+
"plugin.version",
3+
throw new RuntimeException(
4+
"""|The system property 'plugin.version' is not defined.
5+
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin))
6+
7+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % pluginVersion)
8+
9+
resolvers ++= {
10+
if (pluginVersion.endsWith("-SNAPSHOT"))
11+
Seq(Resolver.sonatypeRepo("snapshots"))
12+
else
13+
Seq.empty
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package one
2+
3+
object BadCoverage {
4+
5+
def sum(num1: Int, num2: Int) = {
6+
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package two
2+
3+
object BadCoverage {
4+
5+
def sum(num1: Int, num2: Int) = {
6+
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import org.specs2.mutable._
2+
3+
/**
4+
* Created by tbarke001c on 7/8/14.
5+
*/
6+
class BadCoverageSpec extends Specification {
7+
8+
"BadCoverage" should {
9+
"sum two numbers" in {
10+
one.BadCoverage.sum(1, 2) mustEqual 3
11+
one.BadCoverage.sum(0, 3) mustEqual 3
12+
one.BadCoverage.sum(3, 0) mustEqual 3
13+
two.BadCoverage.sum(1, 2) mustEqual 3
14+
}
15+
}
16+
}

Diff for: src/sbt-test/scoverage/bad-coverage-file-stmt/test

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# run scoverage
2+
> clean
3+
> coverage
4+
> test
5+
-> coverageReport
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version := "0.1"
2+
3+
scalaVersion := "2.10.4"
4+
5+
libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"
6+
7+
coverageMinimumBranchPerPackage := 80
8+
9+
coverageFailOnMinimum := true
10+
11+
resolvers ++= {
12+
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
13+
else Seq.empty
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
val pluginVersion = sys.props.getOrElse(
2+
"plugin.version",
3+
throw new RuntimeException(
4+
"""|The system property 'plugin.version' is not defined.
5+
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin))
6+
7+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % pluginVersion)
8+
9+
resolvers ++= {
10+
if (pluginVersion.endsWith("-SNAPSHOT"))
11+
Seq(Resolver.sonatypeRepo("snapshots"))
12+
else
13+
Seq.empty
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package one
2+
3+
object BadCoverage {
4+
5+
def sum(num1: Int, num2: Int) = {
6+
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package two
2+
3+
object BadCoverage {
4+
5+
def sum(num1: Int, num2: Int) = {
6+
if (0 == num1) num2 else if (0 == num2) num1 else num1 + num2
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import org.specs2.mutable._
2+
3+
/**
4+
* Created by tbarke001c on 7/8/14.
5+
*/
6+
class BadCoverageSpec extends Specification {
7+
8+
"BadCoverage" should {
9+
"sum two numbers" in {
10+
one.BadCoverage.sum(1, 2) mustEqual 3
11+
one.BadCoverage.sum(0, 3) mustEqual 3
12+
one.BadCoverage.sum(3, 0) mustEqual 3
13+
two.BadCoverage.sum(1, 2) mustEqual 3
14+
two.BadCoverage.sum(0, 3) mustEqual 3
15+
}
16+
}
17+
}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# run scoverage
2+
> clean
3+
> coverage
4+
> test
5+
-> coverageReport
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version := "0.1"
2+
3+
scalaVersion := "2.10.4"
4+
5+
libraryDependencies += "org.specs2" %% "specs2" % "2.3.13" % "test"
6+
7+
coverageMinimumStmtPerPackage := 80
8+
9+
coverageFailOnMinimum := true
10+
11+
resolvers ++= {
12+
if (sys.props.get("plugin.version").map(_.endsWith("-SNAPSHOT")).getOrElse(false)) Seq(Resolver.sonatypeRepo("snapshots"))
13+
else Seq.empty
14+
}

0 commit comments

Comments
 (0)