forked from jacoco/jacoco
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
set the default phase for the report goal to "prepare-package"
added a coverage check mojo which attaches to the verify phase by default, closes issue jacoco#6
- Loading branch information
Showing
4 changed files
with
344 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
jacoco-maven-plugin/src/org/jacoco/maven/CheckConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2012 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 | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.maven; | ||
|
||
import org.jacoco.core.analysis.ICoverageNode.CounterEntity; | ||
|
||
/** | ||
* Used in the configuration of the "check" goal for specifying minimum rates of | ||
* coverage. | ||
*/ | ||
public class CheckConfiguration { | ||
|
||
private double instructionRate; | ||
private double branchRate; | ||
private double lineRate; | ||
private double complexityRate; | ||
private double methodRate; | ||
private double classRate; | ||
|
||
/** | ||
* Set the minimum allowed code coverage for instructions. | ||
* | ||
* @param instructionRate | ||
* percent of instructions covered | ||
*/ | ||
public void setInstructionRate(final double instructionRate) { | ||
this.instructionRate = instructionRate; | ||
} | ||
|
||
/** | ||
* Set the minimum allowed code coverage for branches. | ||
* | ||
* @param branchRate | ||
* percent of branches covered | ||
*/ | ||
public void setBranchRate(final double branchRate) { | ||
this.branchRate = branchRate; | ||
} | ||
|
||
/** | ||
* Set the minimum allowed code coverage for lines. | ||
* | ||
* @param lineRate | ||
* percent of lines covered | ||
*/ | ||
public void setLineRate(final double lineRate) { | ||
this.lineRate = lineRate; | ||
} | ||
|
||
/** | ||
* Set the minimum allowed code coverage for complexity. | ||
* | ||
* @param complexityRate | ||
* percent of complexities covered | ||
*/ | ||
public void setComplexityRate(final double complexityRate) { | ||
this.complexityRate = complexityRate; | ||
} | ||
|
||
/** | ||
* Set the minimum allowed code coverage for methods. | ||
* | ||
* @param methodRate | ||
* percent of methods covered | ||
*/ | ||
public void setMethodRate(final double methodRate) { | ||
this.methodRate = methodRate; | ||
} | ||
|
||
/** | ||
* Set the minimum allowed code coverage for classes. | ||
* | ||
* @param classRate | ||
* percent of classes covered | ||
*/ | ||
public void setClassRate(final double classRate) { | ||
this.classRate = classRate; | ||
} | ||
|
||
/** | ||
* Get the rate for the given CounterEntity | ||
* | ||
* @param entity | ||
* the counter type | ||
* @return minimum percent covered for given CounterEntity | ||
*/ | ||
public double getRate(final CounterEntity entity) { | ||
double rate = 0; | ||
|
||
if (CounterEntity.INSTRUCTION.equals(entity)) { | ||
rate = this.instructionRate; | ||
} else if (CounterEntity.BRANCH.equals(entity)) { | ||
rate = this.branchRate; | ||
} else if (CounterEntity.LINE.equals(entity)) { | ||
rate = this.lineRate; | ||
} else if (CounterEntity.COMPLEXITY.equals(entity)) { | ||
rate = this.complexityRate; | ||
} else if (CounterEntity.METHOD.equals(entity)) { | ||
rate = this.methodRate; | ||
} else if (CounterEntity.CLASS.equals(entity)) { | ||
rate = this.classRate; | ||
} | ||
return rate; | ||
} | ||
} |
214 changes: 214 additions & 0 deletions
214
jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2012 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 | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.maven; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.IOException; | ||
import java.util.List; | ||
|
||
import org.apache.maven.plugin.MojoExecutionException; | ||
import org.codehaus.plexus.util.FileUtils; | ||
import org.codehaus.plexus.util.StringUtils; | ||
import org.jacoco.core.analysis.Analyzer; | ||
import org.jacoco.core.analysis.CoverageBuilder; | ||
import org.jacoco.core.analysis.IBundleCoverage; | ||
import org.jacoco.core.analysis.ICounter; | ||
import org.jacoco.core.analysis.ICoverageNode; | ||
import org.jacoco.core.analysis.ICoverageNode.CounterEntity; | ||
import org.jacoco.core.data.ExecutionDataReader; | ||
import org.jacoco.core.data.ExecutionDataStore; | ||
import org.jacoco.core.data.SessionInfoStore; | ||
|
||
/** | ||
* Checks that the code coverage metrics are being met. | ||
* | ||
* @goal check | ||
* @phase verify | ||
* @requiresProject true | ||
* @threadSafe | ||
*/ | ||
public class CheckMojo extends AbstractJacocoMojo { | ||
|
||
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."; | ||
|
||
/** | ||
* Check configuration. Used to specify minimum coverage percentages that | ||
* must be met. | ||
* | ||
* @parameter | ||
* @required | ||
*/ | ||
private CheckConfiguration check; | ||
|
||
/** | ||
* Halt the build if any of the checks fail. | ||
* | ||
* @parameter expression="${jacoco.haltOnFailure}" default-value="true" | ||
* @required | ||
*/ | ||
private boolean haltOnFailure; | ||
|
||
/** | ||
* File with execution data. | ||
* | ||
* @parameter default-value="${project.build.directory}/jacoco.exec" | ||
*/ | ||
private File dataFile; | ||
|
||
private SessionInfoStore sessionInfoStore; | ||
|
||
private ExecutionDataStore executionDataStore; | ||
|
||
private boolean canCheckCoverage() { | ||
if (!dataFile.exists()) { | ||
getLog().info( | ||
"Skipping JaCoCo execution due to missing execution data file"); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public void executeMojo() throws MojoExecutionException, | ||
MojoExecutionException { | ||
if (!canCheckCoverage()) { | ||
return; | ||
} | ||
executeCheck(); | ||
} | ||
|
||
private void executeCheck() throws MojoExecutionException { | ||
try { | ||
loadExecutionData(); | ||
} catch (final IOException e) { | ||
throw new MojoExecutionException( | ||
"Unable to read execution data file " + dataFile + ": " | ||
+ e.getMessage(), e); | ||
} | ||
try { | ||
if (check()) { | ||
this.getLog().info(CHECK_SUCCESS); | ||
} else { | ||
this.handleFailure(); | ||
} | ||
} catch (final IOException e) { | ||
throw new MojoExecutionException( | ||
"Error while checking coverage: " + e.getMessage(), e); | ||
} | ||
} | ||
|
||
private void loadExecutionData() throws IOException { | ||
sessionInfoStore = new SessionInfoStore(); | ||
executionDataStore = new ExecutionDataStore(); | ||
FileInputStream in = null; | ||
try { | ||
in = new FileInputStream(dataFile); | ||
final ExecutionDataReader reader = new ExecutionDataReader(in); | ||
reader.setSessionInfoVisitor(sessionInfoStore); | ||
reader.setExecutionDataVisitor(executionDataStore); | ||
reader.read(); | ||
} finally { | ||
if (in != null) { | ||
in.close(); | ||
} | ||
} | ||
} | ||
|
||
private boolean check() | ||
throws IOException { | ||
final IBundleCoverage bundle = createBundle(); | ||
checkForMissingDebugInformation(bundle); | ||
|
||
boolean passed = true; | ||
|
||
for (final CounterEntity entity : CounterEntity.values()) { | ||
passed = this.checkCounter( | ||
entity, | ||
bundle.getCounter(entity), | ||
check.getRate(entity)) && passed; | ||
} | ||
|
||
return passed; | ||
} | ||
|
||
@SuppressWarnings("boxing") | ||
private boolean checkCounter(final CounterEntity entity, | ||
final ICounter counter, | ||
final double checkRate) { | ||
boolean passed = true; | ||
|
||
final double rate = counter.getCoveredRatio() * 100; | ||
|
||
if (rate < checkRate) { | ||
this.getLog() | ||
.warn(String.format(INSUFFICIENT_COVERAGE, entity.name(), | ||
rate, | ||
checkRate)); | ||
passed = false; | ||
} | ||
return passed; | ||
} | ||
|
||
private void handleFailure() throws MojoExecutionException { | ||
if (this.haltOnFailure) { | ||
throw new MojoExecutionException(CHECK_FAILED); | ||
} else { | ||
this.getLog().warn(CHECK_FAILED); | ||
} | ||
} | ||
|
||
private void checkForMissingDebugInformation(final ICoverageNode node) { | ||
if (node.getClassCounter().getTotalCount() > 0 | ||
&& node.getLineCounter().getTotalCount() == 0) { | ||
getLog().warn( | ||
"To enable source code annotation class files have to be compiled with debug information."); | ||
} | ||
} | ||
|
||
private IBundleCoverage createBundle() throws IOException { | ||
final CoverageBuilder builder = new CoverageBuilder(); | ||
final Analyzer analyzer = new Analyzer(executionDataStore, builder); | ||
final File classesDir = new File(getProject().getBuild() | ||
.getOutputDirectory()); | ||
|
||
final List<File> filesToAnalyze = getFilesToAnalyze(classesDir); | ||
|
||
for (final File file : filesToAnalyze) { | ||
analyzer.analyzeAll(file); | ||
} | ||
|
||
return builder.getBundle(getProject().getName()); | ||
} | ||
|
||
private List<File> getFilesToAnalyze(final File rootDir) throws IOException { | ||
final String includes; | ||
if (getIncludes() != null && !getIncludes().isEmpty()) { | ||
includes = StringUtils.join(getIncludes().iterator(), ","); | ||
} else { | ||
includes = "**"; | ||
} | ||
final String excludes; | ||
if (getExcludes() != null && !getExcludes().isEmpty()) { | ||
excludes = StringUtils.join(getExcludes().iterator(), ","); | ||
} else { | ||
excludes = ""; | ||
} | ||
@SuppressWarnings("unchecked") | ||
final List<File> files = FileUtils | ||
.getFiles(rootDir, includes, excludes); | ||
return files; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 comment
on commit 253c64d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also we are TDD addicts, which means that every new feature should be verified by test cases ( http://www.eclemma.org/jacoco/trunk/doc/conventions.html ). For Maven plugin we utilize integration tests with help of maven-invoker-plugin.
Seems that this commit is a mix of two: default phase for "report" goal and new "check" mojo. We definitely want second one, but not sure about first, which in any case should be discussed separately.