Skip to content

Commit

Permalink
set the default phase for the report goal to "prepare-package"
Browse files Browse the repository at this point in the history
added a coverage check mojo which attaches to the verify phase by default, closes issue jacoco#6
  • Loading branch information
klieber committed Sep 17, 2012
1 parent bf2b7de commit 253c64d
Show file tree
Hide file tree
Showing 4 changed files with 344 additions and 3 deletions.
16 changes: 14 additions & 2 deletions jacoco-maven-plugin/src/org/jacoco/maven/AbstractJacocoMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;

/**
Expand Down Expand Up @@ -54,7 +56,8 @@ public abstract class AbstractJacocoMojo extends AbstractMojo {
*/
private boolean skip;

public final void execute() {
public final void execute() throws MojoExecutionException,
MojoFailureException {
if ("pom".equals(project.getPackaging())) {
getLog().info(
"Skipping JaCoCo for project with packaging type 'pom'");
Expand All @@ -69,8 +72,17 @@ public final void execute() {

/**
* Executes Mojo.
*
* @throws MojoExecutionException
* if an unexpected problem occurs. Throwing this exception
* causes a "BUILD ERROR" message to be displayed.
* @throws MojoFailureException
* if an expected problem (such as a compilation failure)
* occurs. Throwing this exception causes a "BUILD FAILURE"
* message to be displayed.
*/
protected abstract void executeMojo();
protected abstract void executeMojo() throws MojoExecutionException,
MojoFailureException;

/**
* @return Maven project
Expand Down
114 changes: 114 additions & 0 deletions jacoco-maven-plugin/src/org/jacoco/maven/CheckConfiguration.java
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 jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java
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;
}

}
3 changes: 2 additions & 1 deletion jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
* (HTML, XML, and CSV).
*
* @goal report
* @phase prepare-package

This comment has been minimized.

Copy link
@Godin

Godin Sep 17, 2012

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.

* @requiresProject true
* @threadSafe
*/
Expand Down Expand Up @@ -203,7 +204,7 @@ public boolean canGenerateReport() {
}
if (!dataFile.exists()) {
getLog().info(
"Skipping JaCoCo execution due to missing execution data file");
"Skipping JaCoCo execution due to missing execution data file");
return false;
}
return true;
Expand Down

1 comment on commit 253c64d

@Godin
Copy link

@Godin Godin commented on 253c64d Sep 17, 2012

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.

Please sign in to comment.