diff --git a/README.md b/README.md
index 25394433..65daaa6d 100644
--- a/README.md
+++ b/README.md
@@ -335,7 +335,12 @@ Read [SBT SCoverage Plugin documentation](https://github.com/scoverage/sbt-scove
scoverage-maven-plugin
${scoverage.plugin.version}
- 80
+ 95
+ 90
+ 90
+ 85
+ 85
+ 80
true
diff --git a/src/it/test_coverage_minima/invoker.properties b/src/it/test_coverage_minima/invoker.properties
new file mode 100644
index 00000000..f4f2d3bc
--- /dev/null
+++ b/src/it/test_coverage_minima/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=clean scoverage:check site -e -ntp
diff --git a/src/it/test_coverage_minima/module01/pom.xml b/src/it/test_coverage_minima/module01/pom.xml
new file mode 100644
index 00000000..eb09e4a1
--- /dev/null
+++ b/src/it/test_coverage_minima/module01/pom.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ it.scoverage-maven-plugin
+ test_coverage_minima
+ 1.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+ module01
+ Test SCoverage Coverage Minima : Module 1
+
+
diff --git a/src/it/test_coverage_minima/module01/src/main/scala/pkg01/HelloService1.scala b/src/it/test_coverage_minima/module01/src/main/scala/pkg01/HelloService1.scala
new file mode 100644
index 00000000..9d7c7380
--- /dev/null
+++ b/src/it/test_coverage_minima/module01/src/main/scala/pkg01/HelloService1.scala
@@ -0,0 +1,12 @@
+package pkg01
+
+class HelloService1
+{
+ def hello =
+ {
+ "Hello from module 1"
+ }
+
+}
+
+object HelloService1 extends HelloService1
diff --git a/src/it/test_coverage_minima/module01/src/test/scala/pkg01/HelloServiceTest.scala b/src/it/test_coverage_minima/module01/src/test/scala/pkg01/HelloServiceTest.scala
new file mode 100644
index 00000000..9cac7025
--- /dev/null
+++ b/src/it/test_coverage_minima/module01/src/test/scala/pkg01/HelloServiceTest.scala
@@ -0,0 +1,14 @@
+package pkg01
+
+import org.junit.Test;
+import org.junit.Assert.assertEquals
+
+class HelloServiceTest
+{
+ @Test
+ def test1()
+ {
+ assertEquals("Hello from module 1", HelloService1.hello)
+ }
+
+}
diff --git a/src/it/test_coverage_minima/module02/pom.xml b/src/it/test_coverage_minima/module02/pom.xml
new file mode 100644
index 00000000..9875282b
--- /dev/null
+++ b/src/it/test_coverage_minima/module02/pom.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ it.scoverage-maven-plugin
+ test_coverage_minima
+ 1.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+ module02
+ Test SCoverage Coverage Minima : Module 2
+
+
diff --git a/src/it/test_coverage_minima/module02/src/main/scala/pkg01/HelloService1.scala b/src/it/test_coverage_minima/module02/src/main/scala/pkg01/HelloService1.scala
new file mode 100644
index 00000000..1d3c8ca0
--- /dev/null
+++ b/src/it/test_coverage_minima/module02/src/main/scala/pkg01/HelloService1.scala
@@ -0,0 +1,12 @@
+package pkg01
+
+class HelloService1
+{
+ def hello =
+ {
+ "Hello from module 2"
+ }
+
+}
+
+object HelloService1 extends HelloService1
diff --git a/src/it/test_coverage_minima/module02/src/main/scala/pkg02/HelloService1.scala b/src/it/test_coverage_minima/module02/src/main/scala/pkg02/HelloService1.scala
new file mode 100644
index 00000000..b5edc85b
--- /dev/null
+++ b/src/it/test_coverage_minima/module02/src/main/scala/pkg02/HelloService1.scala
@@ -0,0 +1,12 @@
+package pkg02
+
+class HelloService1
+{
+ def hello =
+ {
+ "Hello from module 2"
+ }
+
+}
+
+object HelloService1 extends HelloService1
diff --git a/src/it/test_coverage_minima/module02/src/test/scala/pkg01/HelloServiceTest.scala b/src/it/test_coverage_minima/module02/src/test/scala/pkg01/HelloServiceTest.scala
new file mode 100644
index 00000000..8dacc428
--- /dev/null
+++ b/src/it/test_coverage_minima/module02/src/test/scala/pkg01/HelloServiceTest.scala
@@ -0,0 +1,14 @@
+package pkg01
+
+import org.junit.Test;
+import org.junit.Assert.assertEquals
+
+class HelloServiceTest
+{
+ @Test
+ def test1()
+ {
+ assertEquals("Hello from module 2", HelloService1.hello)
+ }
+
+}
diff --git a/src/it/test_coverage_minima/module03/pom.xml b/src/it/test_coverage_minima/module03/pom.xml
new file mode 100644
index 00000000..a6d61023
--- /dev/null
+++ b/src/it/test_coverage_minima/module03/pom.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+ it.scoverage-maven-plugin
+ test_coverage_minima
+ 1.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+ module03
+ Test SCoverage Coverage Minima : Module 3
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+
+ true
+ 100
+ 100
+ 100
+ 100
+ 100
+ 100
+
+
+
+
+ check
+
+
+
+
+
+
+
+
diff --git a/src/it/test_coverage_minima/module03/src/main/scala/pkg01/HelloService1.scala b/src/it/test_coverage_minima/module03/src/main/scala/pkg01/HelloService1.scala
new file mode 100644
index 00000000..478fbada
--- /dev/null
+++ b/src/it/test_coverage_minima/module03/src/main/scala/pkg01/HelloService1.scala
@@ -0,0 +1,12 @@
+package pkg01
+
+class HelloService1
+{
+ def hello =
+ {
+ "Hello from module 3"
+ }
+
+}
+
+object HelloService1 extends HelloService1
diff --git a/src/it/test_coverage_minima/module03/src/test/scala/pkg01/HelloServiceTest.scala b/src/it/test_coverage_minima/module03/src/test/scala/pkg01/HelloServiceTest.scala
new file mode 100644
index 00000000..ffe06705
--- /dev/null
+++ b/src/it/test_coverage_minima/module03/src/test/scala/pkg01/HelloServiceTest.scala
@@ -0,0 +1,14 @@
+package pkg01
+
+import org.junit.Test;
+import org.junit.Assert.assertEquals
+
+class HelloServiceTest
+{
+ @Test
+ def test1()
+ {
+ assertEquals("Hello from module 3", HelloService1.hello)
+ }
+
+}
diff --git a/src/it/test_coverage_minima/pom.xml b/src/it/test_coverage_minima/pom.xml
new file mode 100644
index 00000000..0d6ffc03
--- /dev/null
+++ b/src/it/test_coverage_minima/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+ 4.0.0
+
+
+ it.scoverage-maven-plugin
+ integration_tests_parent
+ 1.0-SNAPSHOT
+ ../integration_tests_parent/pom.xml
+
+
+ test_coverage_minima
+ 1.0-SNAPSHOT
+ pom
+ Test Scoverage coverage minima
+ Test Scoverage coverage minima
+
+
+ 2.13
+ 12
+
+
+
+ module01
+ module02
+ module03
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ @project.groupId@
+ @project.artifactId@
+
+ true
+ 95
+ 90
+ 90
+ 85
+ 85
+ 80
+ false
+
+
+
+
+ check
+
+
+
+
+
+
+
diff --git a/src/it/test_coverage_minima/validate.groovy b/src/it/test_coverage_minima/validate.groovy
new file mode 100644
index 00000000..4de80432
--- /dev/null
+++ b/src/it/test_coverage_minima/validate.groovy
@@ -0,0 +1,44 @@
+def checkModule(logText, module, coverageLog) {
+ assert new File(basedir, module + "/target/scoverage.xml").exists()
+ assert new File(basedir, module + "/target/site/scoverage/index.html").exists()
+ def entry = logText.find {
+ it.startsWith("scoverage") &&
+ it.contains(":check (default-cli) @ " + module + " ---\n")
+ }
+ assert entry != null
+ assert entry.endsWith(" @ " + module + " ---" + coverageLog.replaceAll("\r\n", "\n") + "[INFO] ")
+}
+
+try {
+ // check coverage minima
+ def logText = (new File(basedir, "build.log")).text.replaceAll("\r\n", "\n").split(/\n\[INFO\] \-\-\- /)
+
+ checkModule(logText, "module01",
+ """
+ |[INFO] Coverage is 100%: Statement:Total!
+ |[INFO] Coverage is 100%: Branch:Total!
+ |""".stripMargin()
+ )
+ checkModule(logText, "module02",
+ """
+ |[ERROR] Coverage is below minimum [50.00% < 95.00%]: Statement:Total
+ |[INFO] Coverage is 100%: Branch:Total!
+ |[ERROR] Coverage is below minimum [0.00% < 90.00%]: Statement:Package:pkg02
+ |[ERROR] Coverage is below minimum [0.00% < 85.00%]: Branch:Package:pkg02
+ |[ERROR] Coverage is below minimum [0.00% < 85.00%]: Statement:File:HelloService1.scala
+ |[ERROR] Coverage is below minimum [0.00% < 80.00%]: Branch:File:HelloService1.scala
+ |""".stripMargin()
+ )
+ checkModule(logText, "module03",
+ """
+ |[INFO] Coverage is 100%: Statement:Total!
+ |[INFO] Coverage is 100%: Branch:Total!
+ |""".stripMargin()
+ )
+
+ return true
+
+} catch (Throwable e) {
+ e.printStackTrace()
+ return false
+}
diff --git a/src/main/java/org/scoverage/plugin/SCoverageCheckMojo.java b/src/main/java/org/scoverage/plugin/SCoverageCheckMojo.java
index 42d19af2..18940379 100644
--- a/src/main/java/org/scoverage/plugin/SCoverageCheckMojo.java
+++ b/src/main/java/org/scoverage/plugin/SCoverageCheckMojo.java
@@ -20,9 +20,12 @@
import java.io.File;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
@@ -33,6 +36,10 @@
import scala.jdk.javaapi.CollectionConverters;
import scoverage.domain.Coverage;
+import scoverage.domain.CoverageMetrics;
+import scoverage.domain.DoubleFormat;
+import scoverage.domain.MeasuredFile;
+import scoverage.domain.MeasuredPackage;
import scoverage.reporter.IOUtils;
import scoverage.serialize.Serializer;
@@ -70,7 +77,7 @@ public class SCoverageCheckMojo
private File dataDirectory;
/**
- * Required minimum coverage.
+ * Required minimum total statement coverage.
*
*
* See https://github.com/scoverage/sbt-scoverage#minimum-coverage for additional documentation.
@@ -81,6 +88,66 @@ public class SCoverageCheckMojo
@Parameter( property = "scoverage.minimumCoverage", defaultValue = "0" )
private Double minimumCoverage;
+ /**
+ * Required minimum total branch coverage.
+ *
+ *
+ * See https://github.com/scoverage/sbt-scoverage#minimum-coverage for additional documentation.
+ *
+ *
+ * @since 2.0.1
+ */
+ @Parameter( property = "scoverage.minimumCoverageBranchTotal", defaultValue = "0" )
+ private Double minimumCoverageBranchTotal;
+
+ /**
+ * Required minimum per-package statement coverage.
+ *
+ *
+ * See https://github.com/scoverage/sbt-scoverage#minimum-coverage for additional documentation.
+ *
+ *
+ * @since 2.0.1
+ */
+ @Parameter( property = "scoverage.minimumCoverageStmtPerPackage", defaultValue = "0" )
+ private Double minimumCoverageStmtPerPackage;
+
+ /**
+ * Required minimum per-package branch coverage.
+ *
+ *
+ * See https://github.com/scoverage/sbt-scoverage#minimum-coverage for additional documentation.
+ *
+ *
+ * @since 2.0.1
+ */
+ @Parameter( property = "scoverage.minimumCoverageBranchPerPackage", defaultValue = "0" )
+ private Double minimumCoverageBranchPerPackage;
+
+ /**
+ * Required minimum per-file statement coverage.
+ *
+ *
+ * See https://github.com/scoverage/sbt-scoverage#minimum-coverage for additional documentation.
+ *
+ *
+ * @since 2.0.1
+ */
+ @Parameter( property = "scoverage.minimumCoverageStmtPerFile", defaultValue = "0" )
+ private Double minimumCoverageStmtPerFile;
+
+ /**
+ * Required minimum per-file branch coverage.
+ *
+ *
+ * See https://github.com/scoverage/sbt-scoverage#minimum-coverage for additional documentation.
+ *
+ *
+ * @since 2.0.1
+ */
+ @Parameter( property = "scoverage.minimumCoverageBranchPerFile", defaultValue = "0" )
+ private Double minimumCoverageBranchPerFile;
+
/**
* Fail the build if minimum coverage was not reached.
*
@@ -168,31 +235,19 @@ public void execute() throws MojoFailureException
int invokedBranchesCount = coverage.invokedBranchesCount();
int invokedStatementCount = coverage.invokedStatementCount();
- getLog().info( String.format( "Statement coverage.: %s%%", coverage.statementCoverageFormatted() ) );
- getLog().info( String.format( "Branch coverage....: %s%%", coverage.branchCoverageFormatted() ) );
getLog().debug( String.format( "invokedBranchesCount:%d / branchCount:%d, invokedStatementCount:%d / statementCount:%d",
invokedBranchesCount, branchCount, invokedStatementCount, statementCount ) );
- if ( minimumCoverage > 0.0 )
+
+ boolean ok = checkCoverage( getLog(), "Total", coverage,
+ minimumCoverage, minimumCoverageBranchTotal, true );
+ ok = checkCoverage( getLog(), "Package:", coverage.packages(), MeasuredPackage::name,
+ minimumCoverageStmtPerPackage, minimumCoverageBranchPerPackage ) && ok;
+ ok = checkCoverage( getLog(), "File:", coverage.files(), MeasuredFile::filename,
+ minimumCoverageStmtPerFile, minimumCoverageBranchPerFile ) && ok;
+
+ if ( !ok && failOnMinimumCoverage )
{
- String minimumCoverageFormatted = scoverage.domain.DoubleFormat.twoFractionDigits( minimumCoverage );
- if ( is100( minimumCoverage ) && is100( coverage.statementCoveragePercent() ) )
- {
- getLog().info( "100% Coverage !" );
- }
- else if ( coverage.statementCoveragePercent() < minimumCoverage )
- {
- getLog().error( String.format( "Coverage is below minimum [%s%% < %s%%]",
- coverage.statementCoverageFormatted(), minimumCoverageFormatted ) );
- if ( failOnMinimumCoverage )
- {
- throw new MojoFailureException( "Coverage minimum was not reached" );
- }
- }
- else
- {
- getLog().info( String.format( "Coverage is above minimum [%s%% >= %s%%]",
- coverage.statementCoverageFormatted(), minimumCoverageFormatted ) );
- }
+ throw new MojoFailureException( "Coverage minimum was not reached" );
}
long te = System.currentTimeMillis();
@@ -201,9 +256,86 @@ else if ( coverage.statementCoveragePercent() < minimumCoverage )
// Private utility methods
- private boolean is100( Double d )
+ private static boolean is100( Double d )
{
return Math.abs( 100 - d ) <= 0.00001d;
}
-}
\ No newline at end of file
+ private static
+ boolean checkCoverage( Log logger, String metricPrefix,
+ scala.collection.Iterable< T > metrics,
+ Function< T, String > toName,
+ double minStmt, double minBranch )
+ {
+ return minStmt <= 0 && minBranch <= 0 || checkAll(metrics, cov ->
+ checkCoverage(logger, metricPrefix + toName.apply(cov), cov, minStmt, minBranch, false)
+ );
+ }
+
+ private static boolean checkCoverage( Log logger, String metric, CoverageMetrics metrics,
+ double minStmt, double minBranch, boolean logSuccessInfo )
+ {
+ boolean stmt = checkCoverage( logger, "Statement:" + metric,
+ minStmt, metrics.statementCoveragePercent(), logSuccessInfo );
+ boolean branch = checkCoverage( logger, "Branch:" + metric,
+ minBranch, metrics.branchCoveragePercent(), logSuccessInfo );
+ return stmt && branch;
+ }
+
+ private static boolean checkCoverage( Log logger, String metric,
+ double minimum, double actual, boolean logSuccessInfo )
+ {
+ if ( minimum <= 0 )
+ {
+ return true;
+ }
+
+ if ( is100( actual ) )
+ {
+ logSuccess( logger, String.format( "Coverage is 100%%: %s!", metric ), logSuccessInfo );
+ return true;
+ }
+
+ String minimumFormatted = DoubleFormat.twoFractionDigits( minimum );
+ String actualFormatted = DoubleFormat.twoFractionDigits( actual );
+ boolean ok = minimum <= actual;
+
+ if ( ok )
+ {
+ String message = String.format( "Coverage is above minimum [%s%% >= %s%%]: %s",
+ actualFormatted, minimumFormatted, metric );
+ logSuccess( logger, message, logSuccessInfo );
+ }
+ else
+ {
+ String message = String.format( "Coverage is below minimum [%s%% < %s%%]: %s",
+ actualFormatted, minimumFormatted, metric );
+ logger.error( message );
+ }
+
+ return ok;
+ }
+
+ private static void logSuccess( Log logger, String message, boolean logSuccessInfo )
+ {
+ if ( logSuccessInfo )
+ {
+ logger.info( message );
+ }
+ else
+ {
+ logger.debug( message );
+ }
+ }
+
+ private static boolean checkAll( scala.collection.Iterable iterable, Predicate predicate )
+ {
+ boolean ok = true;
+ for ( T elem : CollectionConverters.asJava( iterable ) )
+ {
+ ok = predicate.test( elem ) && ok;
+ }
+ return ok;
+ }
+
+}