aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-halt/invoker.properties (renamed from jacoco-maven-plugin.test/it/it-check-fails-hault/invoker.properties)0
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-halt/pom.xml (renamed from jacoco-maven-plugin.test/it/it-check-fails-hault/pom.xml)23
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-halt/src/main/java/Example.java (renamed from jacoco-maven-plugin.test/it/it-check-fails-hault/src/main/java/Example.java)0
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-halt/src/test/java/ExampleTest.java (renamed from jacoco-maven-plugin.test/it/it-check-fails-hault/src/test/java/ExampleTest.java)0
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-halt/verify.bsh (renamed from jacoco-maven-plugin.test/it/it-check-fails-hault/verify.bsh)2
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-no-halt/pom.xml (renamed from jacoco-maven-plugin.test/it/it-check-fails-no-hault/pom.xml)20
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-no-halt/src/main/java/Example.java (renamed from jacoco-maven-plugin.test/it/it-check-fails-no-hault/src/main/java/Example.java)0
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-no-halt/src/test/java/ExampleTest.java (renamed from jacoco-maven-plugin.test/it/it-check-fails-no-hault/src/test/java/ExampleTest.java)0
-rw-r--r--jacoco-maven-plugin.test/it/it-check-fails-no-halt/verify.bsh (renamed from jacoco-maven-plugin.test/it/it-check-fails-no-hault/verify.bsh)2
-rw-r--r--jacoco-maven-plugin.test/it/it-check-passes/pom.xml25
-rw-r--r--jacoco-maven-plugin/src/org/jacoco/maven/CheckConfiguration.java108
-rw-r--r--jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java171
-rw-r--r--jacoco-maven-plugin/src/org/jacoco/maven/RuleConfiguration.java71
-rw-r--r--org.jacoco.ant.test/src/org/jacoco/ant/ReportTaskTest.xml68
-rw-r--r--org.jacoco.ant/META-INF/MANIFEST.MF9
-rw-r--r--org.jacoco.ant/src/org/jacoco/ant/ReportTask.java115
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/analysis/CounterComparatorTest.java9
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java21
-rw-r--r--org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java77
-rw-r--r--org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java30
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java17
-rw-r--r--org.jacoco.doc/docroot/doc/ant.html134
-rw-r--r--org.jacoco.doc/docroot/doc/changes.html10
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/JavaNamesTest.java7
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/check/BundleCheckerTest.java161
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/check/LimitTest.java331
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/check/RuleTest.java86
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/check/RulesCheckerTest.java103
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/csv/CSVFormatterTest.java4
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/csv/ClassRowWriterTest.java5
-rw-r--r--org.jacoco.report.test/src/org/jacoco/report/html/HTMLFormatterTest.java4
-rw-r--r--org.jacoco.report/META-INF/MANIFEST.MF2
-rw-r--r--org.jacoco.report/src/org/jacoco/report/ILanguageNames.java16
-rw-r--r--org.jacoco.report/src/org/jacoco/report/JavaNames.java19
-rw-r--r--org.jacoco.report/src/org/jacoco/report/check/BundleChecker.java150
-rw-r--r--org.jacoco.report/src/org/jacoco/report/check/IViolationsOutput.java36
-rw-r--r--org.jacoco.report/src/org/jacoco/report/check/Limit.java194
-rw-r--r--org.jacoco.report/src/org/jacoco/report/check/Rule.java122
-rw-r--r--org.jacoco.report/src/org/jacoco/report/check/RulesChecker.java98
-rw-r--r--org.jacoco.report/src/org/jacoco/report/check/package-info.java16
40 files changed, 1991 insertions, 275 deletions
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-hault/invoker.properties b/jacoco-maven-plugin.test/it/it-check-fails-halt/invoker.properties
index 324b5fb9..324b5fb9 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-hault/invoker.properties
+++ b/jacoco-maven-plugin.test/it/it-check-fails-halt/invoker.properties
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-hault/pom.xml b/jacoco-maven-plugin.test/it/it-check-fails-halt/pom.xml
index bf1af894..d2bf960f 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-hault/pom.xml
+++ b/jacoco-maven-plugin.test/it/it-check-fails-halt/pom.xml
@@ -39,14 +39,21 @@
<goal>check</goal>
</goals>
<configuration>
- <check>
- <classRatio>100</classRatio>
- <instructionRatio>100</instructionRatio>
- <methodRatio>100</methodRatio>
- <branchRatio>100</branchRatio>
- <complexityRatio>100</complexityRatio>
- <lineRatio>100</lineRatio>
- </check>
+ <rules>
+ <rule>
+ <element>CLASS</element>
+ <includes>
+ <include>Example</include>
+ </includes>
+ <limits>
+ <limit>
+ <counter>METHOD</counter>
+ <value>MISSEDCOUNT</value>
+ <maximum>0</maximum>
+ </limit>
+ </limits>
+ </rule>
+ </rules>
</configuration>
</execution>
</executions>
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-hault/src/main/java/Example.java b/jacoco-maven-plugin.test/it/it-check-fails-halt/src/main/java/Example.java
index 79220852..79220852 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-hault/src/main/java/Example.java
+++ b/jacoco-maven-plugin.test/it/it-check-fails-halt/src/main/java/Example.java
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-hault/src/test/java/ExampleTest.java b/jacoco-maven-plugin.test/it/it-check-fails-halt/src/test/java/ExampleTest.java
index 1bd3e5fa..1bd3e5fa 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-hault/src/test/java/ExampleTest.java
+++ b/jacoco-maven-plugin.test/it/it-check-fails-halt/src/test/java/ExampleTest.java
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-hault/verify.bsh b/jacoco-maven-plugin.test/it/it-check-fails-halt/verify.bsh
index ed4fce20..9854a863 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-hault/verify.bsh
+++ b/jacoco-maven-plugin.test/it/it-check-fails-halt/verify.bsh
@@ -18,6 +18,6 @@ if ( buildLog.indexOf( "Coverage checks have not been met." ) < 0 ) {
throw new RuntimeException( "Coverage checks should not have been met." );
}
-if ( buildLog.indexOf( "Insufficient code coverage for" ) < 0 ) {
+if ( buildLog.indexOf( "methods missed count is 1, but expected maximum is 0" ) < 0 ) {
throw new RuntimeException( "Should have displayed insufficient code coverage messages." );
}
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/pom.xml b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/pom.xml
index 4255e614..58f9b065 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/pom.xml
+++ b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/pom.xml
@@ -40,14 +40,18 @@
</goals>
<configuration>
<haltOnFailure>false</haltOnFailure>
- <check>
- <classRatio>100</classRatio>
- <instructionRatio>100</instructionRatio>
- <methodRatio>100</methodRatio>
- <branchRatio>100</branchRatio>
- <complexityRatio>100</complexityRatio>
- <lineRatio>100</lineRatio>
- </check>
+ <rules>
+ <rule>
+ <element>BUNDLE</element>
+ <limits>
+ <limit>
+ <counter>METHOD</counter>
+ <value>MISSEDCOUNT</value>
+ <maximum>0</maximum>
+ </limit>
+ </limits>
+ </rule>
+ </rules>
</configuration>
</execution>
</executions>
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/src/main/java/Example.java b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/src/main/java/Example.java
index 79220852..79220852 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/src/main/java/Example.java
+++ b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/src/main/java/Example.java
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/src/test/java/ExampleTest.java b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/src/test/java/ExampleTest.java
index 1bd3e5fa..1bd3e5fa 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/src/test/java/ExampleTest.java
+++ b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/src/test/java/ExampleTest.java
diff --git a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/verify.bsh b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/verify.bsh
index f78d267d..672922cd 100644
--- a/jacoco-maven-plugin.test/it/it-check-fails-no-hault/verify.bsh
+++ b/jacoco-maven-plugin.test/it/it-check-fails-no-halt/verify.bsh
@@ -18,6 +18,6 @@ if ( buildLog.indexOf( "Coverage checks have not been met." ) < 0 ) {
throw new RuntimeException( "Coverage checks should not have been met." );
}
-if ( buildLog.indexOf( "Insufficient code coverage for" ) < 0 ) {
+if ( buildLog.indexOf( "methods missed count is 1, but expected maximum is 0" ) < 0 ) {
throw new RuntimeException( "Should have displayed insufficient code coverage messages." );
}
diff --git a/jacoco-maven-plugin.test/it/it-check-passes/pom.xml b/jacoco-maven-plugin.test/it/it-check-passes/pom.xml
index cef3b178..c3beaaec 100644
--- a/jacoco-maven-plugin.test/it/it-check-passes/pom.xml
+++ b/jacoco-maven-plugin.test/it/it-check-passes/pom.xml
@@ -39,14 +39,23 @@
<goal>check</goal>
</goals>
<configuration>
- <check>
- <classRatio>100</classRatio>
- <instructionRatio>100</instructionRatio>
- <methodRatio>100</methodRatio>
- <branchRatio>100</branchRatio>
- <complexityRatio>100</complexityRatio>
- <lineRatio>100</lineRatio>
- </check>
+ <rules>
+ <rule>
+ <element>BUNDLE</element>
+ <limits>
+ <limit>
+ <counter>INSTRUCTION</counter>
+ <value>COVEREDRATIO</value>
+ <minimum>0.90</minimum>
+ </limit>
+ <limit>
+ <counter>CLASS</counter>
+ <value>MISSEDCOUNT</value>
+ <maximum>0</maximum>
+ </limit>
+ </limits>
+ </rule>
+ </rules>
</configuration>
</execution>
</executions>
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/CheckConfiguration.java b/jacoco-maven-plugin/src/org/jacoco/maven/CheckConfiguration.java
deleted file mode 100644
index e6eb9657..00000000
--- a/jacoco-maven-plugin/src/org/jacoco/maven/CheckConfiguration.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Evgeny Mandrikov - initial API and implementation
- * Kyle Lieber - implementation of CheckMojo
- *
- *******************************************************************************/
-package org.jacoco.maven;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
-
-/**
- * Used in the configuration of the "check" goal for specifying minimum ratios
- * of coverage.
- */
-public class CheckConfiguration {
-
- private static final double DEFAULT_RATIO = 0;
-
- private final Map<CounterEntity, Double> configuration;
-
- /**
- * Construct a new CheckConfiguration instance.
- */
- public CheckConfiguration() {
- this.configuration = new HashMap<CounterEntity, Double>();
- }
-
- /**
- * Set the minimum allowed code coverage for instructions.
- *
- * @param ratio
- * percent of instructions covered
- */
- public void setInstructionRatio(final Double ratio) {
- this.configuration.put(CounterEntity.INSTRUCTION, ratio);
- }
-
- /**
- * Set the minimum allowed code coverage for branches.
- *
- * @param ratio
- * percent of branches covered
- */
- public void setBranchRatio(final Double ratio) {
- this.configuration.put(CounterEntity.BRANCH, ratio);
- }
-
- /**
- * Set the minimum allowed code coverage for lines.
- *
- * @param ratio
- * percent of lines covered
- */
- public void setLineRatio(final Double ratio) {
- this.configuration.put(CounterEntity.LINE, ratio);
- }
-
- /**
- * Set the minimum allowed code coverage for complexity.
- *
- * @param ratio
- * percent of complexities covered
- */
- public void setComplexityRatio(final Double ratio) {
- this.configuration.put(CounterEntity.COMPLEXITY, ratio);
- }
-
- /**
- * Set the minimum allowed code coverage for methods.
- *
- * @param ratio
- * percent of methods covered
- */
- public void setMethodRatio(final Double ratio) {
- this.configuration.put(CounterEntity.METHOD, ratio);
- }
-
- /**
- * Set the minimum allowed code coverage for classes.
- *
- * @param ratio
- * percent of classes covered
- */
- public void setClassRatio(final Double ratio) {
- this.configuration.put(CounterEntity.CLASS, ratio);
- }
-
- /**
- * Get the ratio for the given CounterEntity
- *
- * @param entity
- * the counter type
- * @return minimum percent covered for given CounterEntity
- */
- public double getRatio(final CounterEntity entity) {
- final Double ratio = this.configuration.get(entity);
- return ratio == null ? DEFAULT_RATIO : ratio.doubleValue();
- }
-}
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java
index ecf20c96..d964734b 100644
--- a/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java
+++ b/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java
@@ -8,20 +8,26 @@
* Contributors:
* Evgeny Mandrikov - initial API and implementation
* Kyle Lieber - implementation of CheckMojo
+ * Marc Hoffmann - redesign using report APIs
*
*******************************************************************************/
package org.jacoco.maven;
import java.io.File;
import java.io.IOException;
-import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.maven.plugin.MojoExecutionException;
import org.jacoco.core.analysis.IBundleCoverage;
-import org.jacoco.core.analysis.ICounter;
-import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
+import org.jacoco.core.analysis.ICoverageNode;
import org.jacoco.core.data.ExecFileLoader;
import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.report.IReportVisitor;
+import org.jacoco.report.check.IViolationsOutput;
+import org.jacoco.report.check.Limit;
+import org.jacoco.report.check.Rule;
+import org.jacoco.report.check.RulesChecker;
/**
* Checks that the code coverage metrics are being met.
@@ -31,44 +37,75 @@ import org.jacoco.core.data.ExecutionDataStore;
* @requiresProject true
* @threadSafe
*/
-public class CheckMojo extends AbstractJacocoMojo {
+public class CheckMojo extends AbstractJacocoMojo implements IViolationsOutput {
private static final String MSG_SKIPPING = "Skipping JaCoCo execution due to missing execution data file";
-
- private static final String ERROR_UNABLE_TO_READ = "Unable to read execution data file %s: %s";
- private static final String ERROR_CHECKING_COVERAGE = "Error while checking coverage: %s";
-
- private static final String INSUFFICIENT_COVERAGE = "Insufficient code coverage for %s: %2$.2f%% < %3$.2f%%";
- private static final String CHECK_FAILED = "Coverage checks have not been met. See report for details.";
private static final String CHECK_SUCCESS = "All coverage checks have been met.";
+ private static final String CHECK_FAILED = "Coverage checks have not been met. See log for details.";
/**
* <p>
- * Check configuration. Used to specify minimum coverage percentages that
- * must be met. Defaults to 0% if a percentage ratio is not specified.
+ * Check configuration used to specify rules on element types (BUNDLE,
+ * PACKAGE, CLASS, SOURCEFILE or METHOD) with a list of limits. Each limit
+ * applies to a certain counter (INSTRUCTION, LINE, BRANCH, COMPLEXITY,
+ * METHOD, CLASS) and defines a minimum or maximum for the corresponding
+ * value (TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO, MISSEDRATIO).
+ * </p>
+ *
+ * <p>
+ * This example requires an overall instruction coverage of 80% and no class
+ * must be missed:
* </p>
*
+ * <pre>
+ * {@code
+ * <rules>
+ * <rule>
+ * <element>BUNDLE</element>
+ * <limits>
+ * <limit>
+ * <counter>INSTRUCTION</counter>
+ * <value>COVEREDRATIO</value>
+ * <minimum>0.80</minimum>
+ * </limit>
+ * <limit>
+ * <counter>CLASS</counter>
+ * <value>MISSEDCOUNT</value>
+ * <maximum>0</maximum>
+ * </limit>
+ * </limits>
+ * </rule>
+ * </rules>}
+ * </pre>
+ *
* <p>
- * Example requiring 100% coverage for class, instruction, method, branch,
- * complexity, and line:
+ * This example requires a line coverage minimum of 50% for every class
+ * except test classes:
* </p>
*
* <pre>
* {@code
- * <check>
- * <classRatio>100</classRatio>
- * <instructionRatio>100</instructionRatio>
- * <methodRatio>100</methodRatio>
- * <branchRatio>100</branchRatio>
- * <complexityRatio>100</complexityRatio>
- * <lineRatio>100</lineRatio>
- * </check>}
+ * <rules>
+ * <rule>
+ * <element>CLASS</element>
+ * <excludes>
+ * <exclude>*Test</exclude>
+ * </excludes>
+ * <limits>
+ * <limit>
+ * <entity>LINE</entity>
+ * <value>COVEREDRATIO</value>
+ * <minimum>0.50</minimum>
+ * </limit>
+ * </limits>
+ * </rule>
+ * </rules>}
* </pre>
*
* @parameter
* @required
*/
- private CheckConfiguration check;
+ private List<RuleConfiguration> rules;
/**
* Halt the build if any of the checks fail.
@@ -85,7 +122,7 @@ public class CheckMojo extends AbstractJacocoMojo {
*/
private File dataFile;
- private ExecutionDataStore executionDataStore;
+ private boolean violations;
private boolean canCheckCoverage() {
if (!dataFile.exists()) {
@@ -105,72 +142,58 @@ public class CheckMojo extends AbstractJacocoMojo {
}
private void executeCheck() throws MojoExecutionException {
+ final IBundleCoverage bundle = loadBundle();
+ violations = false;
+
+ final RulesChecker checker = new RulesChecker();
+ final List<Rule> checkerrules = new ArrayList<Rule>();
+ for (final RuleConfiguration r : rules) {
+ checkerrules.add(r.rule);
+ }
+ checker.setRules(checkerrules);
+
+ final IReportVisitor visitor = checker.createVisitor(this);
try {
- loadExecutionData();
+ visitor.visitBundle(bundle, null);
} catch (final IOException e) {
- throw new MojoExecutionException(String.format(
- ERROR_UNABLE_TO_READ, dataFile, e.getMessage()), e);
+ throw new MojoExecutionException(
+ "Error while checking code coverage: " + e.getMessage(), e);
}
- try {
- if (check()) {
- this.getLog().info(CHECK_SUCCESS);
+ if (violations) {
+ if (this.haltOnFailure) {
+ throw new MojoExecutionException(CHECK_FAILED);
} else {
- this.handleFailure();
+ this.getLog().warn(CHECK_FAILED);
}
- } catch (final IOException e) {
- throw new MojoExecutionException(String.format(
- ERROR_CHECKING_COVERAGE, e.getMessage()), e);
+ } else {
+ this.getLog().info(CHECK_SUCCESS);
}
}
- private void loadExecutionData() throws IOException {
- final ExecFileLoader loader = new ExecFileLoader();
- loader.load(dataFile);
- executionDataStore = loader.getExecutionDataStore();
- }
-
- private boolean check() throws IOException {
+ private IBundleCoverage loadBundle() throws MojoExecutionException {
final FileFilter fileFilter = new FileFilter(this.getIncludes(),
this.getExcludes());
- final BundleCreator creator = new BundleCreator(this.getProject(),
+ final BundleCreator creator = new BundleCreator(getProject(),
fileFilter);
- final IBundleCoverage bundle = creator.createBundle(executionDataStore);
-
- boolean passed = true;
-
- for (final CounterEntity entity : CounterEntity.values()) {
- passed = this.checkCounter(entity, bundle.getCounter(entity),
- check.getRatio(entity))
- && passed;
+ try {
+ final ExecutionDataStore executionData = loadExecutionData();
+ return creator.createBundle(executionData);
+ } catch (final IOException e) {
+ throw new MojoExecutionException(
+ "Error while reading code coverage: " + e.getMessage(), e);
}
-
- return passed;
}
- private boolean checkCounter(final CounterEntity entity,
- final ICounter counter, final double checkRatio) {
- boolean passed = true;
-
- final double ratio = counter.getCoveredRatio() * 100;
-
- if (ratio < checkRatio) {
- this.getLog().warn(
- String.format(INSUFFICIENT_COVERAGE, entity.name(),
- truncate(ratio), truncate(checkRatio)));
- passed = false;
- }
- return passed;
+ private ExecutionDataStore loadExecutionData() throws IOException {
+ final ExecFileLoader loader = new ExecFileLoader();
+ loader.load(dataFile);
+ return loader.getExecutionDataStore();
}
- private BigDecimal truncate(final double value) {
- return new BigDecimal(value).setScale(2, BigDecimal.ROUND_FLOOR);
+ public void onViolation(final ICoverageNode node, final Rule rule,
+ final Limit limit, final String message) {
+ this.getLog().warn(message);
+ violations = true;
}
- private void handleFailure() throws MojoExecutionException {
- if (this.haltOnFailure) {
- throw new MojoExecutionException(CHECK_FAILED);
- } else {
- this.getLog().warn(CHECK_FAILED);
- }
- }
}
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/RuleConfiguration.java b/jacoco-maven-plugin/src/org/jacoco/maven/RuleConfiguration.java
new file mode 100644
index 00000000..5654e05c
--- /dev/null
+++ b/jacoco-maven-plugin/src/org/jacoco/maven/RuleConfiguration.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Evgeny Mandrikov - initial API and implementation
+ * Kyle Lieber - implementation of CheckMojo
+ * Marc Hoffmann - redesign using report APIs
+ *
+ *******************************************************************************/
+package org.jacoco.maven;
+
+import java.util.List;
+
+import org.codehaus.plexus.util.StringUtils;
+import org.jacoco.core.analysis.ICoverageNode.ElementType;
+import org.jacoco.report.check.Limit;
+import org.jacoco.report.check.Rule;
+
+/**
+ * Wrapper for {@link Rule} objects to allow Maven style includes/excludes lists
+ *
+ */
+public class RuleConfiguration {
+
+ final Rule rule;
+
+ /**
+ * Create a new configuration instance.
+ */
+ public RuleConfiguration() {
+ rule = new Rule();
+ }
+
+ /**
+ * @param element
+ * element type this rule applies to
+ */
+ public void setElement(final ElementType element) {
+ rule.setElement(element);
+ }
+
+ /**
+ * @param includes
+ * includes patterns
+ */
+ public void setIncludes(final List<String> includes) {
+ rule.setIncludes(StringUtils.join(includes.iterator(), ":"));
+ }
+
+ /**
+ *
+ * @param excludes
+ * excludes patterns
+ */
+ public void setExcludes(final List<String> excludes) {
+ rule.setExcludes(StringUtils.join(excludes.iterator(), ":"));
+ }
+
+ /**
+ * @param limits
+ * list of {@link Limit}s configured for this rule
+ */
+ public void setLimits(final List<Limit> limits) {
+ rule.setLimits(limits);
+ }
+
+}
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/ReportTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/ReportTaskTest.xml
index 998358e8..b718c5b2 100644
--- a/org.jacoco.ant.test/src/org/jacoco/ant/ReportTaskTest.xml
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/ReportTaskTest.xml
@@ -411,6 +411,74 @@
<contains string="${testReportXmlEncoding.content}" substring="encoding=&quot;UTF-16&quot;"/>
</au:assertTrue>
</target>
+
+ <!-- Coverage Check -->
+
+ <target name="testReportCheckOk">
+ <jacoco:report>
+ <structure name="Test">
+ <classfiles>
+ <fileset dir="${org.jacoco.ant.reportTaskTest.classes.dir}" includes="**/*.class"/>
+ </classfiles>
+ <sourcefiles encoding="UTF-8">
+ <fileset dir="${org.jacoco.ant.reportTaskTest.sources.dir}" />
+ </sourcefiles>
+ </structure>
+ <check>
+ <rule element="CLASS">
+ <limit counter="METHOD" value="MISSEDCOUNT" maximum="100"/>
+ </rule>
+ </check>
+ </jacoco:report>
+ </target>
+
+ <target name="testReportCheckFailed">
+ <au:expectfailure expectedMessage="Coverage check failed due to violated rules.">
+ <jacoco:report>
+ <structure name="Test">
+ <classfiles>
+ <fileset dir="${org.jacoco.ant.reportTaskTest.classes.dir}" includes="**/*.class"/>
+ </classfiles>
+ <sourcefiles encoding="UTF-8">
+ <fileset dir="${org.jacoco.ant.reportTaskTest.sources.dir}" />
+ </sourcefiles>
+ </structure>
+ <check>
+ <rule element="BUNDLE">
+ <limit counter="INSTRUCTION" value="COVEREDRATIO" minimum="0.90"/>
+ </rule>
+ </check>
+ </jacoco:report>
+ </au:expectfailure>
+ <au:assertLogContains level="error" text="instructions covered ratio is 0.00, but expected minimum is 0.90"/>
+ </target>
+
+ <target name="testReportCheckSetPropertyOnly">
+ <jacoco:report>
+ <structure name="Test">
+ <classfiles>
+ <fileset dir="${org.jacoco.ant.reportTaskTest.classes.dir}" includes="**/*.class"/>
+ </classfiles>
+ <sourcefiles encoding="UTF-8">
+ <fileset dir="${org.jacoco.ant.reportTaskTest.sources.dir}" />
+ </sourcefiles>
+ </structure>
+ <check failonviolation="false" violationsproperty="violation">
+ <rule element="BUNDLE">
+ <limit counter="METHOD" value="COVEREDRATIO" minimum="0.50"/>
+ <limit counter="INSTRUCTION" value="COVEREDRATIO" minimum="0.90"/>
+ </rule>
+ </check>
+ </jacoco:report>
+ <au:assertLogContains level="error" text="methods covered ratio is 0.00, but expected minimum is 0.50"/>
+ <au:assertLogContains level="error" text="instructions covered ratio is 0.00, but expected minimum is 0.90"/>
+ <au:assertTrue message="Property is not set">
+ <contains string="${violation}" substring="methods covered ratio is 0.00, but expected minimum is 0.50"/>
+ </au:assertTrue>
+ <au:assertTrue message="Property is not set">
+ <contains string="${violation}" substring="instructions covered ratio is 0.00, but expected minimum is 0.90"/>
+ </au:assertTrue>
+ </target>
</project> \ No newline at end of file
diff --git a/org.jacoco.ant/META-INF/MANIFEST.MF b/org.jacoco.ant/META-INF/MANIFEST.MF
index 6d9e725b..d81bc54d 100644
--- a/org.jacoco.ant/META-INF/MANIFEST.MF
+++ b/org.jacoco.ant/META-INF/MANIFEST.MF
@@ -6,13 +6,14 @@ Bundle-Version: 0.6.3.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-Vendor: Mountainminds GmbH & Co. KG and Contributors
Require-Bundle: org.apache.ant;bundle-version="[1.7.0,2.0.0)"
-Import-Package: org.objectweb.asm;version="[4.1.0,4.2.0)",
- org.jacoco.agent;bundle-version="[0.6.3,0.6.4)",
+Import-Package: org.jacoco.agent;bundle-version="[0.6.3,0.6.4)",
org.jacoco.core.analysis;bundle-version="[0.6.3,0.6.4)",
org.jacoco.core.data;bundle-version="[0.6.3,0.6.4)",
- org.jacoco.core.instr;version="[0.6.3,0.6.4)",
+ org.jacoco.core.instr;bundle-version="[0.6.3,0.6.4)",
org.jacoco.core.runtime;bundle-version="[0.6.3,0.6.4)",
org.jacoco.report;bundle-version="[0.6.3,0.6.4)",
+ org.jacoco.report.check;bundle-version="[0.6.3,0.6.4)",
org.jacoco.report.csv;bundle-version="[0.6.3,0.6.4)",
org.jacoco.report.html;bundle-version="[0.6.3,0.6.4)",
- org.jacoco.report.xml;bundle-version="[0.6.3,0.6.4)"
+ org.jacoco.report.xml;bundle-version="[0.6.3,0.6.4)",
+ org.objectweb.asm;version="[4.1.0,4.2.0)"
diff --git a/org.jacoco.ant/src/org/jacoco/ant/ReportTask.java b/org.jacoco.ant/src/org/jacoco/ant/ReportTask.java
index bc413de4..329914ff 100644
--- a/org.jacoco.ant/src/org/jacoco/ant/ReportTask.java
+++ b/org.jacoco.ant/src/org/jacoco/ant/ReportTask.java
@@ -42,6 +42,10 @@ import org.jacoco.report.IReportGroupVisitor;
import org.jacoco.report.IReportVisitor;
import org.jacoco.report.MultiReportVisitor;
import org.jacoco.report.ZipMultiReportOutput;
+import org.jacoco.report.check.IViolationsOutput;
+import org.jacoco.report.check.Limit;
+import org.jacoco.report.check.Rule;
+import org.jacoco.report.check.RulesChecker;
import org.jacoco.report.csv.CSVFormatter;
import org.jacoco.report.html.HTMLFormatter;
import org.jacoco.report.xml.XMLFormatter;
@@ -145,16 +149,18 @@ public class ReportTask extends Task {
/**
* Interface for child elements that define formatters.
*/
- private interface IFormatterElement {
+ private abstract class FormatterElement {
- IReportVisitor createVisitor() throws IOException;
+ abstract IReportVisitor createVisitor() throws IOException;
+ void finish() {
+ }
}
/**
- * Formatter Element for HTML reports.
+ * Formatter element for HTML reports.
*/
- public class HTMLFormatterElement implements IFormatterElement {
+ public class HTMLFormatterElement extends FormatterElement {
private File destdir;
@@ -218,6 +224,7 @@ public class ReportTask extends Task {
this.locale = locale;
}
+ @Override
public IReportVisitor createVisitor() throws IOException {
final IMultiReportOutput output;
if (destfile != null) {
@@ -247,9 +254,9 @@ public class ReportTask extends Task {
}
/**
- * Formatter Element for CSV reports.
+ * Formatter element for CSV reports.
*/
- public class CSVFormatterElement implements IFormatterElement {
+ public class CSVFormatterElement extends FormatterElement {
private File destfile;
@@ -265,6 +272,7 @@ public class ReportTask extends Task {
this.destfile = destfile;
}
+ @Override
public IReportVisitor createVisitor() throws IOException {
if (destfile == null) {
throw new BuildException(
@@ -289,9 +297,9 @@ public class ReportTask extends Task {
}
/**
- * Formatter Element for XML reports.
+ * Formatter element for XML reports.
*/
- public class XMLFormatterElement implements IFormatterElement {
+ public class XMLFormatterElement extends FormatterElement {
private File destfile;
@@ -317,6 +325,7 @@ public class ReportTask extends Task {
this.encoding = encoding;
}
+ @Override
public IReportVisitor createVisitor() throws IOException {
if (destfile == null) {
throw new BuildException(
@@ -330,6 +339,78 @@ public class ReportTask extends Task {
}
+ /**
+ * Formatter element for coverage checks.
+ */
+ public class CheckFormatterElement extends FormatterElement implements
+ IViolationsOutput {
+
+ private final List<Rule> rules = new ArrayList<Rule>();
+ private boolean violations = false;
+ private boolean failOnViolation = true;
+ private String violationsPropery = null;
+
+ /**
+ * Creates and adds a new rule.
+ *
+ * @return new rule
+ */
+ public Rule createRule() {
+ final Rule rule = new Rule();
+ rules.add(rule);
+ return rule;
+ }
+
+ /**
+ * Sets whether the build should fail in case of a violation. Default is
+ * <code>true</code>.
+ *
+ * @param flag
+ * if <code>true</code> the build fails on violation
+ */
+ public void setFailOnViolation(final boolean flag) {
+ this.failOnViolation = flag;
+ }
+
+ /**
+ * Sets the name of a property to append the violation messages to.
+ *
+ * @param property
+ * name of a property
+ */
+ public void setViolationsProperty(final String property) {
+ this.violationsPropery = property;
+ }
+
+ @Override
+ public IReportVisitor createVisitor() throws IOException {
+ final RulesChecker formatter = new RulesChecker();
+ formatter.setRules(rules);
+ return formatter.createVisitor(this);
+ }
+
+ public void onViolation(final ICoverageNode node, final Rule rule,
+ final Limit limit, final String message) {
+ log(message, Project.MSG_ERR);
+ violations = true;
+ if (violationsPropery != null) {
+ final String old = getProject().getProperty(violationsPropery);
+ final String value = old == null ? message : String.format(
+ "%s\n%s", old, message);
+ getProject().setProperty(violationsPropery, value);
+ }
+ }
+
+ @Override
+ void finish() {
+ if (violations && failOnViolation) {
+ throw new BuildException(
+ "Coverage check failed due to violated rules.",
+ getLocation());
+ }
+ }
+ }
+
private final Union executiondataElement = new Union();
private SessionInfoStore sessionInfoStore;
@@ -338,7 +419,7 @@ public class ReportTask extends Task {
private final GroupElement structure = new GroupElement();
- private final List<IFormatterElement> formatters = new ArrayList<IFormatterElement>();
+ private final List<FormatterElement> formatters = new ArrayList<FormatterElement>();
/**
* Returns the nested resource collection for execution data files.
@@ -381,6 +462,17 @@ public class ReportTask extends Task {
}
/**
+ * Creates a new coverage check element.
+ *
+ * @return coverage check element
+ */
+ public CheckFormatterElement createCheck() {
+ final CheckFormatterElement element = new CheckFormatterElement();
+ formatters.add(element);
+ return element;
+ }
+
+ /**
* Creates a new XML report element.
*
* @return CSV report element
@@ -400,6 +492,9 @@ public class ReportTask extends Task {
executionDataStore.getContents());
createReport(visitor, structure);
visitor.visitEnd();
+ for (final FormatterElement f : formatters) {
+ f.finish();
+ }
} catch (final IOException e) {
throw new BuildException("Error while creating report", e,
getLocation());
@@ -429,7 +524,7 @@ public class ReportTask extends Task {
private IReportVisitor createVisitor() throws IOException {
final List<IReportVisitor> visitors = new ArrayList<IReportVisitor>();
- for (final IFormatterElement f : formatters) {
+ for (final FormatterElement f : formatters) {
visitors.add(f.createVisitor());
}
return new MultiReportVisitor(visitors);
diff --git a/org.jacoco.core.test/src/org/jacoco/core/analysis/CounterComparatorTest.java b/org.jacoco.core.test/src/org/jacoco/core/analysis/CounterComparatorTest.java
index 152bf8fd..06c3f9c1 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/analysis/CounterComparatorTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/analysis/CounterComparatorTest.java
@@ -75,6 +75,15 @@ public class CounterComparatorTest {
}
@Test
+ public void testReverseReverseComparator() {
+ final Comparator<ICounter> cmp = CounterComparator.TOTALITEMS.reverse()
+ .reverse();
+ assertCmpGreater(cmp, 21, 5, 19, 6);
+ assertCmpEquals(cmp, 20, 5, 19, 6);
+ assertCmpLess(cmp, 19, 5, 19, 6);
+ }
+
+ @Test
public void testNodeComparator1() {
ICoverageNode d1 = new MockNode(18);
ICoverageNode d2 = new MockNode(15);
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java
index 79053e65..11b1057b 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java
@@ -15,9 +15,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
import org.jacoco.core.analysis.ICounter;
+import org.jacoco.core.analysis.ICounter.CounterValue;
import org.junit.Test;
/**
@@ -29,16 +29,22 @@ public class CounterImplTest {
public void testGetInstance1() {
ICounter c = CounterImpl.getInstance(0, 0);
assertEquals(0, c.getTotalCount());
+ assertEquals(0.0, c.getValue(CounterValue.TOTALCOUNT), 0.0);
assertEquals(0, c.getMissedCount());
+ assertEquals(0.0, c.getValue(CounterValue.MISSEDCOUNT), 0.0);
assertEquals(0, c.getCoveredCount());
+ assertEquals(0.0, c.getValue(CounterValue.COVEREDCOUNT), 0.0);
}
@Test
public void testGetInstance2() {
ICounter c = CounterImpl.getInstance(33, 15);
assertEquals(48, c.getTotalCount());
+ assertEquals(48.0, c.getValue(CounterValue.TOTALCOUNT), 0.0);
assertEquals(33, c.getMissedCount());
+ assertEquals(33.0, c.getValue(CounterValue.MISSEDCOUNT), 0.0);
assertEquals(15, c.getCoveredCount());
+ assertEquals(15.0, c.getValue(CounterValue.COVEREDCOUNT), 0.0);
}
@Test
@@ -46,8 +52,11 @@ public class CounterImplTest {
ICounter c = CounterImpl.getInstance(15, 12);
ICounter copy = CounterImpl.getInstance(c);
assertEquals(27, copy.getTotalCount());
+ assertEquals(27.0, c.getValue(CounterValue.TOTALCOUNT), 0.0);
assertEquals(15, copy.getMissedCount());
+ assertEquals(15.0, c.getValue(CounterValue.MISSEDCOUNT), 0.0);
assertEquals(12, copy.getCoveredCount());
+ assertEquals(12.0, c.getValue(CounterValue.COVEREDCOUNT), 0.0);
}
@Test
@@ -84,36 +93,42 @@ public class CounterImplTest {
public void testGetCoveredRatio1() {
ICounter c = CounterImpl.getInstance(30, 10);
assertEquals(0.25, c.getCoveredRatio(), 0.0);
+ assertEquals(0.25, c.getValue(CounterValue.COVEREDRATIO), 0.0);
}
@Test
public void testGetCoveredRatio2() {
ICounter c = CounterImpl.getInstance(20, 0);
assertEquals(0.0, c.getCoveredRatio(), 0.0);
+ assertEquals(0.0, c.getValue(CounterValue.COVEREDRATIO), 0.0);
}
@Test
public void testGetCoveredRatio3() {
ICounter c = CounterImpl.getInstance(0, 0);
- assertTrue(Double.isNaN(c.getCoveredRatio()));
+ assertEquals(Double.NaN, c.getCoveredRatio(), 0.0);
+ assertEquals(Double.NaN, c.getValue(CounterValue.COVEREDRATIO), 0.0);
}
@Test
public void testGetMissedRatio1() {
ICounter c = CounterImpl.getInstance(10, 30);
assertEquals(0.25, c.getMissedRatio(), 0.0);
+ assertEquals(0.25, c.getValue(CounterValue.MISSEDRATIO), 0.0);
}
@Test
public void testGetMissedRatio2() {
ICounter c = CounterImpl.getInstance(0, 20);
assertEquals(0.0, c.getMissedRatio(), 0.0);
+ assertEquals(0.0, c.getValue(CounterValue.MISSEDRATIO), 0.0);
}
@Test
public void testGetMissedRatio3() {
ICounter c = CounterImpl.getInstance(0, 0);
- assertTrue(Double.isNaN(c.getMissedRatio()));
+ assertEquals(Double.NaN, c.getMissedRatio(), 0.0);
+ assertEquals(Double.NaN, c.getValue(CounterValue.MISSEDRATIO), 0.0);
}
@Test
diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java b/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java
index 56420b07..03764314 100644
--- a/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java
+++ b/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java
@@ -14,76 +14,63 @@ package org.jacoco.core.analysis;
import java.io.Serializable;
import java.util.Comparator;
+import org.jacoco.core.analysis.ICounter.CounterValue;
import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
/**
* Collection of comparators to compare {@link ICounter} objects by different
* criteria.
*/
-public abstract class CounterComparator implements Comparator<ICounter>,
- Serializable {
+public class CounterComparator implements Comparator<ICounter>, Serializable {
private static final long serialVersionUID = -3777463066252746748L;
/**
* Compares the absolute number of total items.
*/
- public static final CounterComparator TOTALITEMS = new CounterComparator() {
-
- private static final long serialVersionUID = 8824120489765405662L;
-
- public int compare(final ICounter c1, final ICounter c2) {
- return c1.getTotalCount() - c2.getTotalCount();
- }
- };
+ public static final CounterComparator TOTALITEMS = new CounterComparator(
+ CounterValue.TOTALCOUNT);
/**
* Compares the absolute number of covered items.
*/
- public static final CounterComparator COVEREDITEMS = new CounterComparator() {
-
- private static final long serialVersionUID = 1L;
-
- public int compare(final ICounter c1, final ICounter c2) {
- return c1.getCoveredCount() - c2.getCoveredCount();
- }
- };
+ public static final CounterComparator COVEREDITEMS = new CounterComparator(
+ CounterValue.COVEREDCOUNT);
/**
* Compares the absolute number of missed items.
*/
- public static final CounterComparator MISSEDITEMS = new CounterComparator() {
-
- private static final long serialVersionUID = -2991039557556551206L;
-
- public int compare(final ICounter c1, final ICounter c2) {
- return c1.getMissedCount() - c2.getMissedCount();
- }
- };
+ public static final CounterComparator MISSEDITEMS = new CounterComparator(
+ CounterValue.MISSEDCOUNT);
/**
* Compares the ratio of covered items.
*/
- public static final CounterComparator COVEREDRATIO = new CounterComparator() {
-
- private static final long serialVersionUID = 7897690710299613918L;
-
- public int compare(final ICounter c1, final ICounter c2) {
- return Double.compare(c1.getCoveredRatio(), c2.getCoveredRatio());
- }
- };
+ public static final CounterComparator COVEREDRATIO = new CounterComparator(
+ CounterValue.COVEREDRATIO);
/**
* Compares the ratio of missed items.
*/
- public static final CounterComparator MISSEDRATIO = new CounterComparator() {
+ public static final CounterComparator MISSEDRATIO = new CounterComparator(
+ CounterValue.MISSEDRATIO);
- private static final long serialVersionUID = -5014193668057469357L;
+ private final CounterValue value;
+ private final boolean reverse;
- public int compare(final ICounter c1, final ICounter c2) {
- return Double.compare(c1.getMissedRatio(), c2.getMissedRatio());
- }
- };
+ private CounterComparator(final CounterValue value) {
+ this(value, false);
+ }
+
+ private CounterComparator(final CounterValue value, final boolean reverse) {
+ this.value = value;
+ this.reverse = reverse;
+ }
+
+ public int compare(final ICounter c1, final ICounter c2) {
+ final int cmp = Double.compare(c1.getValue(value), c2.getValue(value));
+ return reverse ? -cmp : cmp;
+ }
/**
* Creates a new version of this comparator that sorts in reverse order.
@@ -91,15 +78,7 @@ public abstract class CounterComparator implements Comparator<ICounter>,
* @return reverse comparator
*/
public CounterComparator reverse() {
- final CounterComparator original = this;
- return new CounterComparator() {
-
- private static final long serialVersionUID = 7703349549732801967L;
-
- public int compare(final ICounter o1, final ICounter o2) {
- return original.compare(o2, o1);
- }
- };
+ return new CounterComparator(value, !reverse);
}
/**
diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java b/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java
index 9d2dbabc..6806789a 100644
--- a/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java
+++ b/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java
@@ -18,6 +18,27 @@ package org.jacoco.core.analysis;
public interface ICounter {
/**
+ * Different values provided by a counter.
+ */
+ public enum CounterValue {
+
+ /** Total number of items */
+ TOTALCOUNT,
+
+ /** Number of missed items */
+ MISSEDCOUNT,
+
+ /** Number of covered items */
+ COVEREDCOUNT,
+
+ /** Ratio of missed to total items */
+ MISSEDRATIO,
+
+ /** Ratio of covered to total items */
+ COVEREDRATIO
+ }
+
+ /**
* Status flag for no items (value is 0x00).
*/
public static final int EMPTY = 0x00;
@@ -38,6 +59,15 @@ public interface ICounter {
public static final int PARTLY_COVERED = NOT_COVERED | FULLY_COVERED;
/**
+ * Returns the counter value of the given type.
+ *
+ * @param value
+ * value type to return
+ * @return counter value
+ */
+ public double getValue(CounterValue value);
+
+ /**
* Returns the total count of items.
*
* @return total count of items
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java
index c5cc1901..8fdd0c7b 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java
@@ -147,6 +147,23 @@ public abstract class CounterImpl implements ICounter {
// === ICounter implementation ===
+ public double getValue(final CounterValue value) {
+ switch (value) {
+ case TOTALCOUNT:
+ return getTotalCount();
+ case MISSEDCOUNT:
+ return getMissedCount();
+ case COVEREDCOUNT:
+ return getCoveredCount();
+ case MISSEDRATIO:
+ return getMissedRatio();
+ case COVEREDRATIO:
+ return getCoveredRatio();
+ default:
+ throw new AssertionError(value);
+ }
+ }
+
public int getTotalCount() {
return missed + covered;
}
diff --git a/org.jacoco.doc/docroot/doc/ant.html b/org.jacoco.doc/docroot/doc/ant.html
index d2fc5e02..09f013d8 100644
--- a/org.jacoco.doc/docroot/doc/ant.html
+++ b/org.jacoco.doc/docroot/doc/ant.html
@@ -704,6 +704,140 @@
</tbody>
</table>
+<h3>Element <code>check</code></h3>
+
+<p>
+ This report type does not actually create a report. It checks coverage
+ counters and reports violations of configured rules. Every rule is applied to
+ elements of a given type (class, package, bundle, etc.) and has a list of
+ limits which are checked for every element. The following example checks that
+ for every package the line coverage is at least 80% and no class is missed:
+</p>
+
+<pre class="source lang-xml linenums">
+&lt;check&gt;
+ &lt;rule elementtype="PACKAGE"&gt;
+ &lt;limit entity="LINE" value="COVEREDRATIO" minimum="0.80"/&gt;
+ &lt;limit entity="CLASS" value="MISSEDCOUNT" maximum="0"/&gt;
+ &lt;/rule&gt;
+&lt;/check&gt;
+</pre>
+
+<p>
+ The <code>check</code> element has the following attributes:
+</p>
+
+<table class="coverage">
+ <thead>
+ <tr>
+ <td>Attribute</td>
+ <td>Description</td>
+ <td>Default</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>rules</code></td>
+ <td>List of rules to check.</td>
+ <td><i>none</i></td>
+ </tr>
+ <tr>
+ <td><code>failonviolation</code></td>
+ <td>Specifies whether build should fail in case of rule violations.</td>
+ <td><code>true</code></td>
+ </tr>
+ <tr>
+ <td><code>violationsproperty</code></td>
+ <td>The name of an Ant property which is filled with the violation
+ messages.</code></td>
+ <td><i>none</i></td>
+ </tr>
+ </tbody>
+</table>
+
+<p>
+ Within the <code>check</code> element any number of <code>rule</code> elements
+ can be nested:
+</p>
+
+<table class="coverage">
+ <thead>
+ <tr>
+ <td>Attribute</td>
+ <td>Description</td>
+ <td>Default</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>element</code></td>
+ <td>The elements this rule applies to. Possible values are
+ <code>BUNLDE</code>, <code>PACKAGE</code>, <code>CLASS</code>,
+ <code>SOURCEFILE</code> and <code>METHOD</code>.</td>
+ <td><code>BUNLDE</code></td>
+ </tr>
+ <tr>
+ <td><code>includes</code></td>
+ <td>A list of element names that should be checked. The list entries are
+ separated by a colon (:) and may use wildcard characters (* and ?).</td>
+ <td><code>*</code></td>
+ </tr>
+ <tr>
+ <td><code>excludes</code></td>
+ <td>A list of element names that should not be checked. The list entries
+ are separated by a colon (:) and may use wildcard characters (* and ?).</td>
+ <td><i>empty (no excludes)</i></td>
+ </tr>
+ <tr>
+ <td><code>limits</code></td>
+ <td>List of limits to check.</code></td>
+ <td><i>none</i></td>
+ </tr>
+ </tbody>
+</table>
+
+<p>
+ Within the <code>rule</code> element any number of <code>limit</code> elements
+ can be nested:
+</p>
+
+<table class="coverage">
+ <thead>
+ <tr>
+ <td>Attribute</td>
+ <td>Description</td>
+ <td>Default</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>counter</code></td>
+ <td>The <a href="counters.html">counter</a> which should be checked.
+ Possible options are <code>INSTRUCTION</code>, <code>LINE</code>,
+ <code>BRANCH</code>, <code>COMPLEXITY</code>, <code>METHOD</code> and
+ <code>CLASS</code>.</td>
+ <td><code>INSTRUCTION</code></td>
+ </tr>
+ <tr>
+ <td><code>value</code></td>
+ <td>The counter value that should be checked. Possible options are
+ <code>TOTALCOUNT</code>, <code>MISSEDCOUNT</code>,
+ <code>COVEREDCOUNT</code>, <code>MISSEDRATIO</code> and
+ <code>COVEREDRATIO</code>.
+ <td><code>COVEREDRATIO</code></td>
+ </tr>
+ <tr>
+ <td><code>minimum</code></td>
+ <td>Expected minmimum value.</code></td>
+ <td><i>none</i></td>
+ </tr>
+ <tr>
+ <td><code>maximum</code></td>
+ <td>Expected maximum value.</code></td>
+ <td><i>none</i></td>
+ </tr>
+ </tbody>
+</table>
<h2><a name="instrument">Task <code>instrument</code></a></h2>
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 0d124774..661f6fc9 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -23,6 +23,9 @@
<h3>New Features</h3>
<ul>
<li>Support for archives in Pack200 format (GitHub #91).</li>
+ <li>The coverage check has been reworked to allow checks on all counter values
+ on all element types (GitHub #106).</li>
+ <li>Coverage checks are now also available in Ant (GitHub #106).</li>
</ul>
<h3>Fixed Bugs</h3>
@@ -31,6 +34,13 @@
by certain tools like ProGuard (GitHub #85).</li>
</ul>
+<h3>API Changes</h3>
+<ul>
+ <li>The configuration of the Maven check goal has been reworked to support
+ checks on any element type (GitHub #106).</li>
+</ul>
+
+
<h2>Release 0.6.2 (2013/02/03)</h2>
<h3>New Features</h3>
diff --git a/org.jacoco.report.test/src/org/jacoco/report/JavaNamesTest.java b/org.jacoco.report.test/src/org/jacoco/report/JavaNamesTest.java
index ce6b3dc6..141325e7 100644
--- a/org.jacoco.report.test/src/org/jacoco/report/JavaNamesTest.java
+++ b/org.jacoco.report.test/src/org/jacoco/report/JavaNamesTest.java
@@ -153,4 +153,11 @@ public class JavaNamesTest {
"<init>", "()V", null));
}
+ @Test
+ public void testGetQualifiedMethodName() {
+ assertEquals("java.util.List.add(int, java.lang.Object)",
+ names.getQualifiedMethodName("java/util/List", "add",
+ "(ILjava/lang/Object;)V", null));
+ }
+
}
diff --git a/org.jacoco.report.test/src/org/jacoco/report/check/BundleCheckerTest.java b/org.jacoco.report.test/src/org/jacoco/report/check/BundleCheckerTest.java
new file mode 100644
index 00000000..d90f86c6
--- /dev/null
+++ b/org.jacoco.report.test/src/org/jacoco/report/check/BundleCheckerTest.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.jacoco.core.analysis.IBundleCoverage;
+import org.jacoco.core.analysis.IClassCoverage;
+import org.jacoco.core.analysis.ICoverageNode;
+import org.jacoco.core.analysis.ICoverageNode.ElementType;
+import org.jacoco.core.analysis.IPackageCoverage;
+import org.jacoco.core.analysis.ISourceFileCoverage;
+import org.jacoco.core.internal.analysis.BundleCoverageImpl;
+import org.jacoco.core.internal.analysis.ClassCoverageImpl;
+import org.jacoco.core.internal.analysis.CounterImpl;
+import org.jacoco.core.internal.analysis.MethodCoverageImpl;
+import org.jacoco.core.internal.analysis.PackageCoverageImpl;
+import org.jacoco.core.internal.analysis.SourceFileCoverageImpl;
+import org.jacoco.report.ILanguageNames;
+import org.jacoco.report.JavaNames;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link BundleChecker}.
+ */
+public class BundleCheckerTest implements IViolationsOutput {
+
+ private List<Rule> rules;
+ private ILanguageNames names;
+ private List<String> messages;
+
+ @Before
+ public void setup() {
+ rules = new ArrayList<Rule>();
+ names = new JavaNames();
+ messages = new ArrayList<String>();
+ }
+
+ @Test
+ public void testBundleLimit() {
+ addRule(ElementType.BUNDLE);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertMessage("Rule violated for bundle Test: instructions covered ratio is 0.50, but expected minimum is 0.75");
+ }
+
+ @Test
+ public void testPackageLimit() {
+ addRule(ElementType.PACKAGE);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertMessage("Rule violated for package org.jacoco.example: instructions covered ratio is 0.50, but expected minimum is 0.75");
+ }
+
+ @Test
+ public void testSourceFileLimit() {
+ addRule(ElementType.SOURCEFILE);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertMessage("Rule violated for source file org/jacoco/example/FooClass.java: instructions covered ratio is 0.50, but expected minimum is 0.75");
+ }
+
+ @Test
+ public void testClassLimit() {
+ addRule(ElementType.CLASS);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertMessage("Rule violated for class org.jacoco.example.FooClass: instructions covered ratio is 0.50, but expected minimum is 0.75");
+ }
+
+ @Test
+ public void testMethodLimit() {
+ addRule(ElementType.METHOD);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertMessage("Rule violated for method org.jacoco.example.FooClass.fooMethod(): instructions covered ratio is 0.50, but expected minimum is 0.75");
+ }
+
+ @Test
+ public void testGroupLimitNotSupported() {
+ addRule(ElementType.GROUP);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertEquals(Collections.emptyList(), messages);
+ }
+
+ @Test
+ public void testLimitOk() {
+ final Rule rule = new Rule();
+ rule.setElement(ElementType.BUNDLE);
+ final Limit limit = rule.createLimit();
+ limit.setMinimum("0.25");
+ rules.add(rule);
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertEquals(Collections.emptyList(), messages);
+ }
+
+ @Test
+ public void testBundleNoMatch() {
+ addRule(ElementType.BUNDLE).setExcludes("*");
+ final BundleChecker checker = new BundleChecker(rules, names, this);
+ checker.checkBundle(createBundle());
+ assertEquals(Collections.emptyList(), messages);
+ }
+
+ private Rule addRule(ElementType elementType) {
+ final Rule rule = new Rule();
+ rule.setElement(elementType);
+ final Limit limit = rule.createLimit();
+ limit.setMinimum("0.75");
+ rules.add(rule);
+ return rule;
+ }
+
+ private IBundleCoverage createBundle() {
+ final MethodCoverageImpl m = new MethodCoverageImpl("fooMethod", "()V",
+ null);
+ m.increment(CounterImpl.getInstance(5, 5), CounterImpl.COUNTER_0_0, 1);
+ m.incrementMethodCounter();
+
+ final ClassCoverageImpl c = new ClassCoverageImpl(
+ "org/jacoco/example/FooClass", 1001, null, "java/lang/Object",
+ new String[0]);
+ c.setSourceFileName("FooClass.java");
+ c.addMethod(m);
+
+ final SourceFileCoverageImpl s = new SourceFileCoverageImpl(
+ "FooClass.java", "org/jacoco/example");
+ s.increment(c);
+
+ IPackageCoverage p = new PackageCoverageImpl("org/jacoco/example",
+ Collections.singleton((IClassCoverage) c),
+ Collections.singleton((ISourceFileCoverage) s));
+ return new BundleCoverageImpl("Test", Collections.singleton(p));
+ }
+
+ private void assertMessage(String expected) {
+ assertEquals(Collections.singletonList(expected), messages);
+ }
+
+ public void onViolation(ICoverageNode node, Rule rule, Limit limit,
+ String message) {
+ messages.add(message);
+ }
+
+}
diff --git a/org.jacoco.report.test/src/org/jacoco/report/check/LimitTest.java b/org.jacoco.report.test/src/org/jacoco/report/check/LimitTest.java
new file mode 100644
index 00000000..dd23e3d2
--- /dev/null
+++ b/org.jacoco.report.test/src/org/jacoco/report/check/LimitTest.java
@@ -0,0 +1,331 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.math.BigDecimal;
+
+import org.jacoco.core.analysis.CoverageNodeImpl;
+import org.jacoco.core.analysis.ICounter.CounterValue;
+import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
+import org.jacoco.core.internal.analysis.CounterImpl;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link Limit}.
+ */
+public class LimitTest {
+
+ private Limit limit;
+
+ @Before
+ public void setup() {
+ limit = new Limit();
+ }
+
+ @Test
+ public void testDefaults() {
+ assertNull(limit.getMinimum());
+ assertNull(limit.getMaximum());
+ assertEquals(CounterEntity.INSTRUCTION, limit.getEntity());
+ assertEquals(CounterValue.COVEREDRATIO, limit.getValue());
+ }
+
+ @Test
+ public void testTotalCount() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setMaximum("-1");
+ assertEquals(CounterValue.TOTALCOUNT, limit.getValue());
+ assertEquals(
+ "instructions total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testMissedCount() {
+ limit.setValue(CounterValue.MISSEDCOUNT);
+ limit.setMaximum("-1");
+ assertEquals(CounterValue.MISSEDCOUNT, limit.getValue());
+ assertEquals(
+ "instructions missed count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testCoveredCount() {
+ limit.setValue(CounterValue.COVEREDCOUNT);
+ limit.setMaximum("-1");
+ assertEquals(CounterValue.COVEREDCOUNT, limit.getValue());
+ assertEquals(
+ "instructions covered count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testMissedRatio() {
+ limit.setValue(CounterValue.MISSEDRATIO);
+ limit.setMaximum("-1");
+ assertEquals(CounterValue.MISSEDRATIO, limit.getValue());
+ assertEquals(
+ "instructions missed ratio is 0, but expected maximum is -1",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.COUNTER_0_1;
+ }
+ }));
+ }
+
+ @Test
+ public void testCoveredRatio() {
+ limit.setValue(CounterValue.COVEREDRATIO);
+ limit.setMaximum("-1");
+ assertEquals(CounterValue.COVEREDRATIO, limit.getValue());
+ assertEquals(
+ "instructions covered ratio is 0, but expected maximum is -1",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.COUNTER_1_0;
+ }
+ }));
+ }
+
+ @Test
+ public void testInstruction() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setCounter(CounterEntity.INSTRUCTION);
+ limit.setMaximum("-1");
+ assertEquals(CounterEntity.INSTRUCTION, limit.getEntity());
+ assertEquals(
+ "instructions total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testBranch() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setCounter(CounterEntity.BRANCH);
+ limit.setMaximum("-1");
+ assertEquals(CounterEntity.BRANCH, limit.getEntity());
+ assertEquals("branches total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testLine() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setCounter(CounterEntity.LINE);
+ limit.setMaximum("-1");
+ assertEquals(CounterEntity.LINE, limit.getEntity());
+ assertEquals("lines total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testComlexity() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setCounter(CounterEntity.COMPLEXITY);
+ limit.setMaximum("-1");
+ assertEquals(CounterEntity.COMPLEXITY, limit.getEntity());
+ assertEquals("complexity total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testClass() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setCounter(CounterEntity.CLASS);
+ limit.setMaximum("-1");
+ assertEquals(CounterEntity.CLASS, limit.getEntity());
+ assertEquals("classes total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testMethod() {
+ limit.setValue(CounterValue.TOTALCOUNT);
+ limit.setCounter(CounterEntity.METHOD);
+ limit.setMaximum("-1");
+ assertEquals(CounterEntity.METHOD, limit.getEntity());
+ assertEquals("methods total count is 0, but expected maximum is -1",
+ limit.check(new TestNode()));
+ }
+
+ @Test
+ public void testNoRatio() {
+ assertNull(limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.COUNTER_0_0;
+ }
+ }));
+ }
+
+ @Test
+ public void testNoLimits() {
+ assertNull(limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(1000, 0);
+ }
+ }));
+ }
+
+ @Test
+ public void testMin0() {
+ limit.setMinimum("0");
+ limit.setMinimum((String) null);
+ assertNull(limit.getMinimum());
+ }
+
+ @Test
+ public void testMin1() {
+ limit.setMinimum("0.35");
+ assertEquals(new BigDecimal("0.35"), limit.getMinimum());
+ assertNull(limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(65, 35);
+ }
+ }));
+ }
+
+ @Test
+ public void testMin2() {
+ limit.setMinimum("0.35");
+ assertEquals(new BigDecimal("0.35"), limit.getMinimum());
+ assertNull(limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(64, 36);
+ }
+ }));
+ }
+
+ @Test
+ public void testMin3() {
+ limit.setMinimum("0.3500");
+ assertEquals(new BigDecimal("0.3500"), limit.getMinimum());
+ assertEquals(
+ "instructions covered ratio is 0.3400, but expected minimum is 0.3500",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(66, 34);
+ }
+ }));
+ }
+
+ @Test
+ public void testMin4() {
+ limit.setMinimum("0.35");
+ assertEquals(new BigDecimal("0.35"), limit.getMinimum());
+ assertEquals(
+ "instructions covered ratio is 0.34, but expected minimum is 0.35",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(65001,
+ 34999);
+ }
+ }));
+ }
+
+ @Test
+ public void testMin5() {
+ limit.setMinimum("10000");
+ limit.setValue(CounterValue.MISSEDCOUNT);
+ assertEquals(new BigDecimal("10000"), limit.getMinimum());
+ assertEquals(
+ "instructions missed count is 9990, but expected minimum is 10000",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(9990, 0);
+ }
+ }));
+ }
+
+ @Test
+ public void testMin6() {
+ limit.setMinimum("12345");
+ assertEquals(new BigDecimal("12345"), limit.getMinimum());
+ assertEquals(
+ "instructions covered ratio is 0, but expected minimum is 12345",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(1, 999);
+ }
+ }));
+ }
+
+ @Test
+ public void testMax0() {
+ limit.setMaximum("0");
+ limit.setMaximum((String) null);
+ assertNull(limit.getMaximum());
+ }
+
+ @Test
+ public void testMax1() {
+ limit.setMaximum("12345678");
+ limit.setValue(CounterValue.MISSEDCOUNT);
+ assertEquals(new BigDecimal("12345678"), limit.getMaximum());
+ assertNull(limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(12345678, 0);
+ }
+ }));
+ }
+
+ @Test
+ public void testMax2() {
+ limit.setMaximum("0.999");
+ assertEquals(new BigDecimal("0.999"), limit.getMaximum());
+ assertNull(limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(1, 99);
+ }
+ }));
+ }
+
+ @Test
+ public void testMax3() {
+ limit.setMaximum("0.999");
+ assertEquals(new BigDecimal("0.999"), limit.getMaximum());
+ assertEquals(
+ "instructions covered ratio is 1.000, but expected maximum is 0.999",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(0, 1);
+ }
+ }));
+ }
+
+ @Test
+ public void testMax4() {
+ limit.setMaximum("0.999");
+ assertEquals(new BigDecimal("0.999"), limit.getMaximum());
+ assertEquals(
+ "instructions covered ratio is 1.000, but expected maximum is 0.999",
+ limit.check(new TestNode() {
+ {
+ instructionCounter = CounterImpl.getInstance(999,
+ 999001);
+ }
+ }));
+ }
+
+ private static class TestNode extends CoverageNodeImpl {
+
+ public TestNode() {
+ super(ElementType.CLASS, "Foo");
+ }
+
+ }
+
+}
diff --git a/org.jacoco.report.test/src/org/jacoco/report/check/RuleTest.java b/org.jacoco.report.test/src/org/jacoco/report/check/RuleTest.java
new file mode 100644
index 00000000..f1e4568d
--- /dev/null
+++ b/org.jacoco.report.test/src/org/jacoco/report/check/RuleTest.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.jacoco.core.analysis.ICoverageNode.ElementType;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link Rule}.
+ */
+public class RuleTest {
+
+ private Rule rule;
+
+ @Before
+ public void setup() {
+ rule = new Rule();
+ }
+
+ @Test
+ public void testDefaults() {
+ assertEquals(ElementType.BUNDLE, rule.getElement());
+ assertEquals(Collections.emptyList(), rule.getLimits());
+ assertEquals("*", rule.getIncludes());
+ assertEquals("", rule.getExcludes());
+ }
+
+ @Test
+ public void testSetElement() {
+ rule.setElement(ElementType.PACKAGE);
+ assertEquals(ElementType.PACKAGE, rule.getElement());
+ }
+
+ @Test
+ public void testSetLimits() {
+ Limit l1 = new Limit();
+ Limit l2 = new Limit();
+ Limit l3 = new Limit();
+ rule.setLimits(Arrays.asList(l1, l2, l3));
+ assertEquals(Arrays.asList(l1, l2, l3), rule.getLimits());
+ }
+
+ @Test
+ public void testCreateLimit() {
+ Limit l1 = new Limit();
+ Limit l2 = new Limit();
+ rule.setLimits(new ArrayList<Limit>(Arrays.asList(l1, l2)));
+ Limit l3 = rule.createLimit();
+ assertEquals(Arrays.asList(l1, l2, l3), rule.getLimits());
+ }
+
+ @Test
+ public void testSetIncludes() {
+ rule.setIncludes("Foo*");
+ assertEquals("Foo*", rule.getIncludes());
+ assertTrue(rule.matches("FooBar"));
+ assertFalse(rule.matches("Other"));
+ }
+
+ @Test
+ public void testSetExcludes() {
+ rule.setExcludes("Foo*");
+ assertEquals("Foo*", rule.getExcludes());
+ assertTrue(rule.matches("Other"));
+ assertFalse(rule.matches("FooBar"));
+ }
+
+}
diff --git a/org.jacoco.report.test/src/org/jacoco/report/check/RulesCheckerTest.java b/org.jacoco.report.test/src/org/jacoco/report/check/RulesCheckerTest.java
new file mode 100644
index 00000000..debebb37
--- /dev/null
+++ b/org.jacoco.report.test/src/org/jacoco/report/check/RulesCheckerTest.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.jacoco.core.analysis.ICounter.CounterValue;
+import org.jacoco.core.analysis.ICoverageNode;
+import org.jacoco.core.analysis.ICoverageNode.ElementType;
+import org.jacoco.report.ILanguageNames;
+import org.jacoco.report.ReportStructureTestDriver;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link Limit}.
+ */
+public class RulesCheckerTest implements IViolationsOutput {
+
+ private RulesChecker checker;
+ private ReportStructureTestDriver driver;
+ private List<String> messages;
+
+ @Before
+ public void setup() {
+ checker = new RulesChecker();
+ driver = new ReportStructureTestDriver();
+ messages = new ArrayList<String>();
+ }
+
+ @Test
+ public void testSetRules() throws IOException {
+ Rule rule = new Rule();
+ Limit limit = rule.createLimit();
+ limit.setValue(CounterValue.MISSEDCOUNT);
+ limit.setMaximum("5");
+ checker.setRules(Arrays.asList(rule));
+ driver.sendGroup(checker.createVisitor(this));
+ assertEquals(
+ Arrays.asList("Rule violated for bundle bundle: instructions missed count is 10, but expected maximum is 5"),
+ messages);
+ }
+
+ @Test
+ public void testSetLanguageNames() throws IOException {
+ Rule rule = new Rule();
+ rule.setElement(ElementType.CLASS);
+ Limit limit = rule.createLimit();
+ limit.setValue(CounterValue.MISSEDCOUNT);
+ limit.setMaximum("5");
+ checker.setRules(Arrays.asList(rule));
+
+ checker.setLanguageNames(new ILanguageNames() {
+ public String getQualifiedClassName(String vmname) {
+ return "MyClass";
+ }
+
+ public String getQualifiedMethodName(String vmclassname,
+ String vmmethodname, String vmdesc, String vmsignature) {
+ return null;
+ }
+
+ public String getPackageName(String vmname) {
+ return null;
+ }
+
+ public String getMethodName(String vmclassname,
+ String vmmethodname, String vmdesc, String vmsignature) {
+ return null;
+ }
+
+ public String getClassName(String vmname, String vmsignature,
+ String vmsuperclass, String[] vminterfaces) {
+ return null;
+ }
+ });
+
+ driver.sendGroup(checker.createVisitor(this));
+ assertEquals(
+ Arrays.asList("Rule violated for class MyClass: instructions missed count is 10, but expected maximum is 5"),
+ messages);
+ }
+
+ public void onViolation(ICoverageNode node, Rule rule, Limit limit,
+ String message) {
+ messages.add(message);
+ }
+
+}
diff --git a/org.jacoco.report.test/src/org/jacoco/report/csv/CSVFormatterTest.java b/org.jacoco.report.test/src/org/jacoco/report/csv/CSVFormatterTest.java
index 95b274c9..ce08c816 100644
--- a/org.jacoco.report.test/src/org/jacoco/report/csv/CSVFormatterTest.java
+++ b/org.jacoco.report.test/src/org/jacoco/report/csv/CSVFormatterTest.java
@@ -119,6 +119,10 @@ public class CSVFormatterTest {
return null;
}
+ public String getQualifiedMethodName(String vmclassname,
+ String vmmethodname, String vmdesc, String vmsignature) {
+ return null;
+ }
};
formatter.setLanguageNames(names);
assertSame(names, formatter.getLanguageNames());
diff --git a/org.jacoco.report.test/src/org/jacoco/report/csv/ClassRowWriterTest.java b/org.jacoco.report.test/src/org/jacoco/report/csv/ClassRowWriterTest.java
index 581445f4..10b540d6 100644
--- a/org.jacoco.report.test/src/org/jacoco/report/csv/ClassRowWriterTest.java
+++ b/org.jacoco.report.test/src/org/jacoco/report/csv/ClassRowWriterTest.java
@@ -53,6 +53,11 @@ public class ClassRowWriterTest {
String vmmethodname, String vmdesc, String vmsignature) {
throw new AssertionError();
}
+
+ public String getQualifiedMethodName(String vmclassname,
+ String vmmethodname, String vmdesc, String vmsignature) {
+ throw new AssertionError();
+ }
};
result = new StringWriter();
writer = new ClassRowWriter(new DelimitedWriter(result), names);
diff --git a/org.jacoco.report.test/src/org/jacoco/report/html/HTMLFormatterTest.java b/org.jacoco.report.test/src/org/jacoco/report/html/HTMLFormatterTest.java
index 1abc2257..dabe637b 100644
--- a/org.jacoco.report.test/src/org/jacoco/report/html/HTMLFormatterTest.java
+++ b/org.jacoco.report.test/src/org/jacoco/report/html/HTMLFormatterTest.java
@@ -119,6 +119,10 @@ public class HTMLFormatterTest {
return null;
}
+ public String getQualifiedMethodName(String vmclassname,
+ String vmmethodname, String vmdesc, String vmsignature) {
+ return null;
+ }
};
formatter.setLanguageNames(names);
assertSame(names, formatter.getLanguageNames());
diff --git a/org.jacoco.report/META-INF/MANIFEST.MF b/org.jacoco.report/META-INF/MANIFEST.MF
index 0f13146e..b67fba28 100644
--- a/org.jacoco.report/META-INF/MANIFEST.MF
+++ b/org.jacoco.report/META-INF/MANIFEST.MF
@@ -6,10 +6,12 @@ Bundle-Version: 0.6.3.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-Vendor: Mountainminds GmbH & Co. KG
Export-Package: org.jacoco.report;version="0.6.3",
+ org.jacoco.report.check;version="0.6.3",
org.jacoco.report.csv;version="0.6.3",
org.jacoco.report.html;version="0.6.3",
org.jacoco.report.xml;version="0.6.3"
Import-Package: org.jacoco.core;bundle-version="[0.6.3,0.6.4)",
org.jacoco.core.analysis;bundle-version="[0.6.3,0.6.4)",
org.jacoco.core.data;bundle-version="[0.6.3,0.6.4)",
+ org.jacoco.core.runtime;bundle-version="[0.6.3,0.6.4)",
org.objectweb.asm;version="[4.1.0,4.2.0)"
diff --git a/org.jacoco.report/src/org/jacoco/report/ILanguageNames.java b/org.jacoco.report/src/org/jacoco/report/ILanguageNames.java
index fe682696..f45ed644 100644
--- a/org.jacoco.report/src/org/jacoco/report/ILanguageNames.java
+++ b/org.jacoco.report/src/org/jacoco/report/ILanguageNames.java
@@ -67,4 +67,20 @@ public interface ILanguageNames {
public String getMethodName(String vmclassname, String vmmethodname,
String vmdesc, String vmsignature);
+ /**
+ * Calculates the language specific fully qualified name of a method.
+ *
+ * @param vmclassname
+ * vm name of a containing class
+ * @param vmmethodname
+ * vm name of the method
+ * @param vmdesc
+ * vm parameter description of the method
+ * @param vmsignature
+ * vm signature of the method (may be <code>null</code>)
+ * @return language specific notation for the method
+ */
+ public String getQualifiedMethodName(String vmclassname,
+ String vmmethodname, String vmdesc, String vmsignature);
+
}
diff --git a/org.jacoco.report/src/org/jacoco/report/JavaNames.java b/org.jacoco.report/src/org/jacoco/report/JavaNames.java
index 34c33963..e2ec69e1 100644
--- a/org.jacoco.report/src/org/jacoco/report/JavaNames.java
+++ b/org.jacoco.report/src/org/jacoco/report/JavaNames.java
@@ -77,6 +77,19 @@ public class JavaNames implements ILanguageNames {
public String getMethodName(final String vmclassname,
final String vmmethodname, final String vmdesc,
final String vmsignature) {
+ return getMethodName(vmclassname, vmmethodname, vmdesc, false);
+ }
+
+ public String getQualifiedMethodName(final String vmclassname,
+ final String vmmethodname, final String vmdesc,
+ final String vmsignature) {
+ return getQualifiedClassName(vmclassname) + "."
+ + getMethodName(vmclassname, vmmethodname, vmdesc, true);
+ }
+
+ private String getMethodName(final String vmclassname,
+ final String vmmethodname, final String vmdesc,
+ final boolean qualifiedParams) {
if ("<clinit>".equals(vmmethodname)) {
return "static {...}";
}
@@ -99,7 +112,11 @@ public class JavaNames implements ILanguageNames {
} else {
comma = true;
}
- result.append(getShortTypeName(arg));
+ if (qualifiedParams) {
+ result.append(getQualifiedClassName(arg.getClassName()));
+ } else {
+ result.append(getShortTypeName(arg));
+ }
}
result.append(')');
return result.toString();
diff --git a/org.jacoco.report/src/org/jacoco/report/check/BundleChecker.java b/org.jacoco.report/src/org/jacoco/report/check/BundleChecker.java
new file mode 100644
index 00000000..7f2cfc8f
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/check/BundleChecker.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.jacoco.core.analysis.IBundleCoverage;
+import org.jacoco.core.analysis.IClassCoverage;
+import org.jacoco.core.analysis.ICoverageNode;
+import org.jacoco.core.analysis.IMethodCoverage;
+import org.jacoco.core.analysis.IPackageCoverage;
+import org.jacoco.core.analysis.ISourceFileCoverage;
+import org.jacoco.report.ILanguageNames;
+
+/**
+ * Internal class to check a list of rules against a {@link IBundleCoverage}
+ * instance.
+ */
+class BundleChecker {
+
+ private final ILanguageNames names;
+ private final IViolationsOutput output;
+
+ private final Collection<Rule> bundleRules;
+ private final Collection<Rule> packageRules;
+ private final Collection<Rule> classRules;
+ private final ArrayList<Rule> sourceFileRules;
+ private final Collection<Rule> methodRules;
+
+ private final boolean traversePackages;
+ private final boolean traverseClasses;
+ private final boolean traverseSourceFiles;
+ private final boolean traverseMethods;
+
+ public BundleChecker(final Collection<Rule> rules,
+ final ILanguageNames names, final IViolationsOutput output) {
+ this.names = names;
+ this.output = output;
+ this.bundleRules = new ArrayList<Rule>();
+ this.packageRules = new ArrayList<Rule>();
+ this.classRules = new ArrayList<Rule>();
+ this.sourceFileRules = new ArrayList<Rule>();
+ this.methodRules = new ArrayList<Rule>();
+ for (final Rule rule : rules) {
+ switch (rule.getElement()) {
+ case BUNDLE:
+ bundleRules.add(rule);
+ break;
+ case PACKAGE:
+ packageRules.add(rule);
+ break;
+ case CLASS:
+ classRules.add(rule);
+ break;
+ case SOURCEFILE:
+ sourceFileRules.add(rule);
+ break;
+ case METHOD:
+ methodRules.add(rule);
+ break;
+ }
+ }
+ traverseMethods = !methodRules.isEmpty();
+ traverseClasses = !classRules.isEmpty() || traverseMethods;
+ traverseSourceFiles = !sourceFileRules.isEmpty();
+ traversePackages = !packageRules.isEmpty() || traverseClasses
+ || traverseSourceFiles;
+ }
+
+ public void checkBundle(final IBundleCoverage bundleCoverage) {
+ final String name = bundleCoverage.getName();
+ checkRules(bundleCoverage, bundleRules, "bundle", name);
+ if (traversePackages) {
+ for (final IPackageCoverage p : bundleCoverage.getPackages()) {
+ check(p);
+ }
+ }
+ }
+
+ private void check(final IPackageCoverage packageCoverage) {
+ final String name = names.getPackageName(packageCoverage.getName());
+ checkRules(packageCoverage, packageRules, "package", name);
+ if (traverseClasses) {
+ for (final IClassCoverage c : packageCoverage.getClasses()) {
+ check(c);
+ }
+ }
+ if (traverseSourceFiles) {
+ for (final ISourceFileCoverage s : packageCoverage.getSourceFiles()) {
+ check(s);
+ }
+ }
+ }
+
+ private void check(final IClassCoverage classCoverage) {
+ final String name = names
+ .getQualifiedClassName(classCoverage.getName());
+ checkRules(classCoverage, classRules, "class", name);
+ if (traverseMethods) {
+ for (final IMethodCoverage m : classCoverage.getMethods()) {
+ check(m, classCoverage.getName());
+ }
+ }
+ }
+
+ private void check(final ISourceFileCoverage sourceFile) {
+ final String name = sourceFile.getPackageName() + "/"
+ + sourceFile.getName();
+ checkRules(sourceFile, sourceFileRules, "source file", name);
+ }
+
+ private void check(final IMethodCoverage method, final String className) {
+ final String name = names.getQualifiedMethodName(className,
+ method.getName(), method.getDesc(), method.getSignature());
+ checkRules(method, methodRules, "method", name);
+ }
+
+ private void checkRules(final ICoverageNode node,
+ final Collection<Rule> rules, final String typename,
+ final String elementname) {
+ for (final Rule rule : rules) {
+ if (rule.matches(elementname)) {
+ for (final Limit limit : rule.getLimits()) {
+ checkLimit(node, typename, elementname, rule, limit);
+ }
+ }
+ }
+ }
+
+ private void checkLimit(final ICoverageNode node, final String elementtype,
+ final String typename, final Rule rule, final Limit limit) {
+ final String message = limit.check(node);
+ if (message != null) {
+ output.onViolation(node, rule, limit, String.format(
+ "Rule violated for %s %s: %s", elementtype, typename,
+ message));
+ }
+ }
+
+}
diff --git a/org.jacoco.report/src/org/jacoco/report/check/IViolationsOutput.java b/org.jacoco.report/src/org/jacoco/report/check/IViolationsOutput.java
new file mode 100644
index 00000000..9167ccc7
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/check/IViolationsOutput.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import org.jacoco.core.analysis.ICoverageNode;
+
+/**
+ * Call-back interface which is used to report rule violations to.
+ *
+ */
+public interface IViolationsOutput {
+
+ /**
+ * Called for every rule violation.
+ *
+ * @param node
+ * node which violates a rule
+ * @param rule
+ * rule which is violated
+ * @param limit
+ * limit which is violated
+ * @param message
+ * readable message describing this violation
+ */
+ void onViolation(ICoverageNode node, Rule rule, Limit limit, String message);
+
+}
diff --git a/org.jacoco.report/src/org/jacoco/report/check/Limit.java b/org.jacoco.report/src/org/jacoco/report/check/Limit.java
new file mode 100644
index 00000000..8aec0ded
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/check/Limit.java
@@ -0,0 +1,194 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jacoco.core.analysis.ICounter.CounterValue;
+import org.jacoco.core.analysis.ICoverageNode;
+import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
+
+/**
+ * Descriptor for a limit which is given by a {@link Rule}.
+ */
+public class Limit {
+
+ private static final Map<CounterValue, String> VALUE_NAMES;
+ private static final Map<CounterEntity, String> ENTITY_NAMES;
+
+ static {
+ final Map<CounterValue, String> values = new HashMap<CounterValue, String>();
+ values.put(CounterValue.TOTALCOUNT, "total count");
+ values.put(CounterValue.MISSEDCOUNT, "missed count");
+ values.put(CounterValue.COVEREDCOUNT, "covered count");
+ values.put(CounterValue.MISSEDRATIO, "missed ratio");
+ values.put(CounterValue.COVEREDRATIO, "covered ratio");
+ VALUE_NAMES = Collections.unmodifiableMap(values);
+
+ final Map<CounterEntity, String> entities = new HashMap<CounterEntity, String>();
+ entities.put(CounterEntity.INSTRUCTION, "instructions");
+ entities.put(CounterEntity.BRANCH, "branches");
+ entities.put(CounterEntity.COMPLEXITY, "complexity");
+ entities.put(CounterEntity.LINE, "lines");
+ entities.put(CounterEntity.METHOD, "methods");
+ entities.put(CounterEntity.CLASS, "classes");
+ ENTITY_NAMES = Collections.unmodifiableMap(entities);
+ }
+
+ private CounterEntity entity;
+
+ private CounterValue value;
+
+ private BigDecimal minimum;
+
+ private BigDecimal maximum;
+
+ /**
+ * Creates a new instance with the following defaults:
+ * <ul>
+ * <li>counter entity: {@link CounterEntity#INSTRUCTION}
+ * <li>counter value: {@link CounterValue#COVEREDRATIO}
+ * <li>minimum: no limit
+ * <li>maximum: no limit
+ * </ul>
+ */
+ public Limit() {
+ this.entity = CounterEntity.INSTRUCTION;
+ this.value = CounterValue.COVEREDRATIO;
+ }
+
+ /**
+ * @return the configured counter entity to check
+ */
+ public CounterEntity getEntity() {
+ return entity;
+ }
+
+ /**
+ * Sets the counter entity to check.
+ *
+ * @param entity
+ * counter entity to check
+ */
+ public void setCounter(final CounterEntity entity) {
+ this.entity = entity;
+ }
+
+ /**
+ * @return the configured value to check
+ */
+ public CounterValue getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value to check.
+ *
+ * @param value
+ * value to check
+ */
+ public void setValue(final CounterValue value) {
+ this.value = value;
+ }
+
+ /**
+ * @return configured minimum value, or <code>null</code> if no minimum is
+ * given
+ */
+ public BigDecimal getMinimum() {
+ return minimum;
+ }
+
+ /**
+ * Sets allowed minimum value. Coverage ratios are given in the range from
+ * 0.0 to 1.0.
+ *
+ * @param minimum
+ * allowed minimum or <code>null</code>, if no minimum should be
+ * checked
+ */
+ public void setMinimum(final BigDecimal minimum) {
+ this.minimum = minimum;
+ }
+
+ /**
+ * Sets allowed minimum value as String representation.
+ *
+ * @param minimum
+ * allowed minimum or <code>null</code>, if no minimum should be
+ * checked
+ * @see Limit#setMinimum(BigDecimal)
+ */
+ public void setMinimum(final String minimum) {
+ setMinimum(minimum == null ? null : new BigDecimal(minimum));
+ }
+
+ /**
+ * @return configured maximum value, or <code>null</code> if no maximum is
+ * given
+ */
+ public BigDecimal getMaximum() {
+ return maximum;
+ }
+
+ /**
+ * Sets allowed maximum value as String representation.
+ *
+ * @param maximum
+ * allowed maximum or <code>null</code>, if no maximum should be
+ * checked
+ * @see #setMaximum(BigDecimal)
+ */
+ public void setMaximum(final String maximum) {
+ setMaximum(maximum == null ? null : new BigDecimal(maximum));
+ }
+
+ /**
+ * Sets allowed maximum value. Coverage ratios are given in the range from
+ * 0.0 to 1.0.
+ *
+ * @param maximum
+ * allowed maximum or <code>null</code>, if no maximum should be
+ * checked
+ */
+ public void setMaximum(final BigDecimal maximum) {
+ this.maximum = maximum;
+ }
+
+ String check(final ICoverageNode node) {
+ final double d = node.getCounter(entity).getValue(value);
+ if (Double.isNaN(d)) {
+ return null;
+ }
+ final BigDecimal bd = BigDecimal.valueOf(d);
+ if (minimum != null && minimum.compareTo(bd) > 0) {
+ return message("minimum", bd, minimum, RoundingMode.FLOOR);
+ }
+ if (maximum != null && maximum.compareTo(bd) < 0) {
+ return message("maximum", bd, maximum, RoundingMode.CEILING);
+ }
+ return null;
+ }
+
+ private String message(final String minmax, final BigDecimal v,
+ final BigDecimal ref, final RoundingMode mode) {
+ final BigDecimal rounded = v.setScale(ref.scale(), mode);
+ return String.format("%s %s is %s, but expected %s is %s",
+ ENTITY_NAMES.get(entity), VALUE_NAMES.get(value),
+ rounded.toPlainString(), minmax, ref.toPlainString());
+ }
+
+}
diff --git a/org.jacoco.report/src/org/jacoco/report/check/Rule.java b/org.jacoco.report/src/org/jacoco/report/check/Rule.java
new file mode 100644
index 00000000..3dea01e3
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/check/Rule.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jacoco.core.analysis.ICoverageNode.ElementType;
+import org.jacoco.core.runtime.WildcardMatcher;
+
+/**
+ * A rule applies for a certain element type and can define any number of limits
+ * for all elements of this type.
+ */
+public final class Rule {
+
+ private ElementType element;
+ private String includes;
+ private String excludes;
+ private List<Limit> limits;
+
+ private WildcardMatcher includesMatcher;
+ private WildcardMatcher excludesMatcher;
+
+ /**
+ * Creates a new Rule without limits.
+ */
+ public Rule() {
+ this.element = ElementType.BUNDLE;
+ this.limits = new ArrayList<Limit>();
+ this.setIncludes("*");
+ this.setExcludes("");
+ }
+
+ /**
+ * @return element type this rule applies to
+ */
+ public ElementType getElement() {
+ return element;
+ }
+
+ /**
+ * @param elementType
+ * element type this rule applies to
+ */
+ public void setElement(final ElementType elementType) {
+ this.element = elementType;
+ }
+
+ /**
+ * @return includes pattern
+ */
+ public String getIncludes() {
+ return includes;
+ }
+
+ /**
+ * @param includes
+ * includes pattern
+ */
+ public void setIncludes(final String includes) {
+ this.includes = includes;
+ this.includesMatcher = new WildcardMatcher(includes);
+ }
+
+ /**
+ * @return excludes pattern
+ */
+ public String getExcludes() {
+ return excludes;
+ }
+
+ /**
+ *
+ * @param excludes
+ * excludes patterns
+ */
+ public void setExcludes(final String excludes) {
+ this.excludes = excludes;
+ this.excludesMatcher = new WildcardMatcher(excludes);
+ }
+
+ /**
+ * @return list of {@link Limit}s configured for this rule
+ */
+ public List<Limit> getLimits() {
+ return limits;
+ }
+
+ /**
+ * @param limits
+ * list of {@link Limit}s configured for this rule
+ */
+ public void setLimits(final List<Limit> limits) {
+ this.limits = limits;
+ }
+
+ /**
+ * Creates and adds a new {@link Limit}.
+ *
+ * @return creates {@link Limit}
+ */
+ public Limit createLimit() {
+ final Limit limit = new Limit();
+ this.limits.add(limit);
+ return limit;
+ }
+
+ boolean matches(final String name) {
+ return includesMatcher.matches(name) && !excludesMatcher.matches(name);
+ }
+
+}
diff --git a/org.jacoco.report/src/org/jacoco/report/check/RulesChecker.java b/org.jacoco.report/src/org/jacoco/report/check/RulesChecker.java
new file mode 100644
index 00000000..ddcb7c75
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/check/RulesChecker.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.report.check;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.jacoco.core.analysis.IBundleCoverage;
+import org.jacoco.core.data.ExecutionData;
+import org.jacoco.core.data.SessionInfo;
+import org.jacoco.report.ILanguageNames;
+import org.jacoco.report.IReportGroupVisitor;
+import org.jacoco.report.IReportVisitor;
+import org.jacoco.report.ISourceFileLocator;
+import org.jacoco.report.JavaNames;
+
+/**
+ * Formatter which checks a set of given rules and reports violations to a
+ * {@link IViolationsOutput} instance.
+ */
+public class RulesChecker {
+
+ private List<Rule> rules;
+ private ILanguageNames languageNames;
+
+ /**
+ * New formatter instance.
+ */
+ public RulesChecker() {
+ this.rules = new ArrayList<Rule>();
+ this.setLanguageNames(new JavaNames());
+ }
+
+ /**
+ * Sets the rules to check by this formatter.
+ *
+ * @param rules
+ * rules to check
+ */
+ public void setRules(final List<Rule> rules) {
+ this.rules = rules;
+ }
+
+ /**
+ * Sets the implementation for language name display for message formatting.
+ * Java language names are defined by default.
+ *
+ * @param languageNames
+ * converter for language specific names
+ */
+ public void setLanguageNames(final ILanguageNames languageNames) {
+ this.languageNames = languageNames;
+ }
+
+ /**
+ * Creates a new visitor to process the configured checks.
+ *
+ * @param output
+ * call-back to report violations to
+ * @return visitor to emit the report data to
+ */
+ public IReportVisitor createVisitor(final IViolationsOutput output) {
+ final BundleChecker bundleChecker = new BundleChecker(rules,
+ languageNames, output);
+ return new IReportVisitor() {
+
+ public IReportGroupVisitor visitGroup(final String name)
+ throws IOException {
+ return this;
+ }
+
+ public void visitBundle(final IBundleCoverage bundle,
+ final ISourceFileLocator locator) throws IOException {
+ bundleChecker.checkBundle(bundle);
+ }
+
+ public void visitInfo(final List<SessionInfo> sessionInfos,
+ final Collection<ExecutionData> executionData)
+ throws IOException {
+ }
+
+ public void visitEnd() throws IOException {
+ }
+ };
+ }
+
+}
diff --git a/org.jacoco.report/src/org/jacoco/report/check/package-info.java b/org.jacoco.report/src/org/jacoco/report/check/package-info.java
new file mode 100644
index 00000000..e0fe96a8
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/check/package-info.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+
+/**
+ * Rules check implementation.
+ */
+package org.jacoco.report.check; \ No newline at end of file