diff options
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="UTF-16""/> </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"> +<check> + <rule elementtype="PACKAGE"> + <limit entity="LINE" value="COVEREDRATIO" minimum="0.80"/> + <limit entity="CLASS" value="MISSEDCOUNT" maximum="0"/> + </rule> +</check> +</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 |