Home | History | Annotate | Download | only in maven
      1 /*******************************************************************************
      2  * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
      3  * All rights reserved. This program and the accompanying materials
      4  * are made available under the terms of the Eclipse Public License v1.0
      5  * which accompanies this distribution, and is available at
      6  * http://www.eclipse.org/legal/epl-v10.html
      7  *
      8  * Contributors:
      9  *    Evgeny Mandrikov - initial API and implementation
     10  *    Kyle Lieber - implementation of CheckMojo
     11  *    Marc Hoffmann - redesign using report APIs
     12  *
     13  *******************************************************************************/
     14 package org.jacoco.maven;
     15 
     16 import java.io.File;
     17 import java.io.IOException;
     18 import java.util.ArrayList;
     19 import java.util.List;
     20 
     21 import org.apache.maven.plugin.MojoExecutionException;
     22 import org.apache.maven.plugins.annotations.LifecyclePhase;
     23 import org.apache.maven.plugins.annotations.Mojo;
     24 import org.apache.maven.plugins.annotations.Parameter;
     25 import org.jacoco.core.analysis.ICoverageNode;
     26 import org.jacoco.report.IReportVisitor;
     27 import org.jacoco.report.check.IViolationsOutput;
     28 import org.jacoco.report.check.Limit;
     29 import org.jacoco.report.check.Rule;
     30 
     31 /**
     32  * Checks that the code coverage metrics are being met.
     33  *
     34  * @since 0.6.1
     35  */
     36 @Mojo(name = "check", defaultPhase = LifecyclePhase.VERIFY, threadSafe = true)
     37 public class CheckMojo extends AbstractJacocoMojo implements IViolationsOutput {
     38 
     39 	private static final String MSG_SKIPPING = "Skipping JaCoCo execution due to missing execution data file:";
     40 	private static final String CHECK_SUCCESS = "All coverage checks have been met.";
     41 	private static final String CHECK_FAILED = "Coverage checks have not been met. See log for details.";
     42 
     43 	/**
     44 	 * <p>
     45 	 * Check configuration used to specify rules on element types (BUNDLE,
     46 	 * PACKAGE, CLASS, SOURCEFILE or METHOD) with a list of limits. Each limit
     47 	 * applies to a certain counter (INSTRUCTION, LINE, BRANCH, COMPLEXITY,
     48 	 * METHOD, CLASS) and defines a minimum or maximum for the corresponding
     49 	 * value (TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO, MISSEDRATIO).
     50 	 * If a limit refers to a ratio the range is from 0.0 to 1.0 where the
     51 	 * number of decimal places will also determine the precision in error
     52 	 * messages.
     53 	 * </p>
     54 	 *
     55 	 * <p>
     56 	 * If not specified the following defaults are assumed:
     57 	 * </p>
     58 	 *
     59 	 * <ul>
     60 	 * <li>rule element: BUNDLE</li>
     61 	 * <li>limit counter: INSTRUCTION</li>
     62 	 * <li>limit value: COVEREDRATIO</li>
     63 	 * </ul>
     64 	 *
     65 	 * <p>
     66 	 * Note that you <b>must</b> use <tt>implementation</tt> hints for
     67 	 * <tt>rule</tt> and <tt>limit</tt> when using Maven 2, with Maven 3 you do
     68 	 * not need to specify the attributes.
     69 	 * </p>
     70 	 *
     71 	 * <p>
     72 	 * This example requires an overall instruction coverage of 80% and no class
     73 	 * must be missed:
     74 	 * </p>
     75 	 *
     76 	 * <pre>
     77 	 * {@code
     78 	 * <rules>
     79 	 *   <rule implementation="org.jacoco.maven.RuleConfiguration">
     80 	 *     <element>BUNDLE</element>
     81 	 *     <limits>
     82 	 *       <limit implementation="org.jacoco.report.check.Limit">
     83 	 *         <counter>INSTRUCTION</counter>
     84 	 *         <value>COVEREDRATIO</value>
     85 	 *         <minimum>0.80</minimum>
     86 	 *       </limit>
     87 	 *       <limit implementation="org.jacoco.report.check.Limit">
     88 	 *         <counter>CLASS</counter>
     89 	 *         <value>MISSEDCOUNT</value>
     90 	 *         <maximum>0</maximum>
     91 	 *       </limit>
     92 	 *     </limits>
     93 	 *   </rule>
     94 	 * </rules>}
     95 	 * </pre>
     96 	 *
     97 	 * <p>
     98 	 * This example requires a line coverage minimum of 50% for every class
     99 	 * except test classes:
    100 	 * </p>
    101 	 *
    102 	 * <pre>
    103 	 * {@code
    104 	 * <rules>
    105 	 *   <rule>
    106 	 *     <element>CLASS</element>
    107 	 *     <excludes>
    108 	 *       <exclude>*Test</exclude>
    109 	 *     </excludes>
    110 	 *     <limits>
    111 	 *       <limit>
    112 	 *         <counter>LINE</counter>
    113 	 *         <value>COVEREDRATIO</value>
    114 	 *         <minimum>0.50</minimum>
    115 	 *       </limit>
    116 	 *     </limits>
    117 	 *   </rule>
    118 	 * </rules>}
    119 	 * </pre>
    120 	 */
    121 	@Parameter(required = true)
    122 	private List<RuleConfiguration> rules;
    123 
    124 	/**
    125 	 * Halt the build if any of the checks fail.
    126 	 */
    127 	@Parameter(property = "jacoco.haltOnFailure", defaultValue = "true", required = true)
    128 	private boolean haltOnFailure;
    129 
    130 	/**
    131 	 * File with execution data.
    132 	 */
    133 	@Parameter(defaultValue = "${project.build.directory}/jacoco.exec")
    134 	private File dataFile;
    135 
    136 	private boolean violations;
    137 
    138 	private boolean canCheckCoverage() {
    139 		if (!dataFile.exists()) {
    140 			getLog().info(MSG_SKIPPING + dataFile);
    141 			return false;
    142 		}
    143 		final File classesDirectory = new File(getProject().getBuild()
    144 				.getOutputDirectory());
    145 		if (!classesDirectory.exists()) {
    146 			getLog().info(
    147 					"Skipping JaCoCo execution due to missing classes directory:"
    148 							+ classesDirectory);
    149 			return false;
    150 		}
    151 		return true;
    152 	}
    153 
    154 	@Override
    155 	public void executeMojo() throws MojoExecutionException,
    156 			MojoExecutionException {
    157 		if (!canCheckCoverage()) {
    158 			return;
    159 		}
    160 		executeCheck();
    161 	}
    162 
    163 	private void executeCheck() throws MojoExecutionException {
    164 		violations = false;
    165 
    166 		final ReportSupport support = new ReportSupport(getLog());
    167 
    168 		final List<Rule> checkerrules = new ArrayList<Rule>();
    169 		for (final RuleConfiguration r : rules) {
    170 			checkerrules.add(r.rule);
    171 		}
    172 		support.addRulesChecker(checkerrules, this);
    173 
    174 		try {
    175 			final IReportVisitor visitor = support.initRootVisitor();
    176 			support.loadExecutionData(dataFile);
    177 			support.processProject(visitor, getProject(), this.getIncludes(),
    178 					this.getExcludes());
    179 			visitor.visitEnd();
    180 		} catch (final IOException e) {
    181 			throw new MojoExecutionException(
    182 					"Error while checking code coverage: " + e.getMessage(), e);
    183 		}
    184 		if (violations) {
    185 			if (this.haltOnFailure) {
    186 				throw new MojoExecutionException(CHECK_FAILED);
    187 			} else {
    188 				this.getLog().warn(CHECK_FAILED);
    189 			}
    190 		} else {
    191 			this.getLog().info(CHECK_SUCCESS);
    192 		}
    193 	}
    194 
    195 	public void onViolation(final ICoverageNode node, final Rule rule,
    196 			final Limit limit, final String message) {
    197 		this.getLog().warn(message);
    198 		violations = true;
    199 	}
    200 
    201 }
    202