1/******************************************************************************* 2 * Copyright (c) 2009, 2015 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 *******************************************************************************/ 14package org.jacoco.maven; 15 16import java.io.File; 17import java.io.IOException; 18import java.util.ArrayList; 19import java.util.List; 20 21import org.apache.maven.plugin.MojoExecutionException; 22import org.jacoco.core.analysis.IBundleCoverage; 23import org.jacoco.core.analysis.ICoverageNode; 24import org.jacoco.core.data.ExecutionDataStore; 25import org.jacoco.core.tools.ExecFileLoader; 26import org.jacoco.report.IReportVisitor; 27import org.jacoco.report.check.IViolationsOutput; 28import org.jacoco.report.check.Limit; 29import org.jacoco.report.check.Rule; 30import org.jacoco.report.check.RulesChecker; 31 32/** 33 * Checks that the code coverage metrics are being met. 34 * 35 * @goal check 36 * @phase verify 37 * @requiresProject true 38 * @threadSafe 39 * @since 0.6.1 40 */ 41public class CheckMojo extends AbstractJacocoMojo implements IViolationsOutput { 42 43 private static final String MSG_SKIPPING = "Skipping JaCoCo execution due to missing execution data file:"; 44 private static final String CHECK_SUCCESS = "All coverage checks have been met."; 45 private static final String CHECK_FAILED = "Coverage checks have not been met. See log for details."; 46 47 /** 48 * <p> 49 * Check configuration used to specify rules on element types (BUNDLE, 50 * PACKAGE, CLASS, SOURCEFILE or METHOD) with a list of limits. Each limit 51 * applies to a certain counter (INSTRUCTION, LINE, BRANCH, COMPLEXITY, 52 * METHOD, CLASS) and defines a minimum or maximum for the corresponding 53 * value (TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO, MISSEDRATIO). 54 * If a limit refers to a ratio the range is from 0.0 to 1.0 where the 55 * number of decimal places will also determine the precision in error 56 * messages. 57 * 58 * Note that you <b>must</b> use <tt>implementation</tt> hints for 59 * <tt>rule</tt> and <tt>limit</tt> when using Maven 2, with Maven 3 you do 60 * not need to specify the attributes. 61 * </p> 62 * 63 * <p> 64 * This example requires an overall instruction coverage of 80% and no class 65 * must be missed: 66 * </p> 67 * 68 * <pre> 69 * {@code 70 * <rules> 71 * <rule implementation="org.jacoco.maven.RuleConfiguration"> 72 * <element>BUNDLE</element> 73 * <limits> 74 * <limit implementation="org.jacoco.report.check.Limit"> 75 * <counter>INSTRUCTION</counter> 76 * <value>COVEREDRATIO</value> 77 * <minimum>0.80</minimum> 78 * </limit> 79 * <limit implementation="org.jacoco.report.check.Limit"> 80 * <counter>CLASS</counter> 81 * <value>MISSEDCOUNT</value> 82 * <maximum>0</maximum> 83 * </limit> 84 * </limits> 85 * </rule> 86 * </rules>} 87 * </pre> 88 * 89 * <p> 90 * This example requires a line coverage minimum of 50% for every class 91 * except test classes: 92 * </p> 93 * 94 * <pre> 95 * {@code 96 * <rules> 97 * <rule> 98 * <element>CLASS</element> 99 * <excludes> 100 * <exclude>*Test</exclude> 101 * </excludes> 102 * <limits> 103 * <limit> 104 * <counter>LINE</counter> 105 * <value>COVEREDRATIO</value> 106 * <minimum>0.50</minimum> 107 * </limit> 108 * </limits> 109 * </rule> 110 * </rules>} 111 * </pre> 112 * 113 * @parameter 114 * @required 115 */ 116 private List<RuleConfiguration> rules; 117 118 /** 119 * Halt the build if any of the checks fail. 120 * 121 * @parameter property="jacoco.haltOnFailure" default-value="true" 122 * @required 123 */ 124 private boolean haltOnFailure; 125 126 /** 127 * File with execution data. 128 * 129 * @parameter default-value="${project.build.directory}/jacoco.exec" 130 */ 131 private File dataFile; 132 133 private boolean violations; 134 135 private boolean canCheckCoverage() { 136 if (!dataFile.exists()) { 137 getLog().info(MSG_SKIPPING + dataFile); 138 return false; 139 } 140 final File classesDirectory = new File(getProject().getBuild() 141 .getOutputDirectory()); 142 if (!classesDirectory.exists()) { 143 getLog().info( 144 "Skipping JaCoCo execution due to missing classes directory:" 145 + classesDirectory); 146 return false; 147 } 148 return true; 149 } 150 151 @Override 152 public void executeMojo() throws MojoExecutionException, 153 MojoExecutionException { 154 if (!canCheckCoverage()) { 155 return; 156 } 157 executeCheck(); 158 } 159 160 private void executeCheck() throws MojoExecutionException { 161 final IBundleCoverage bundle = loadBundle(); 162 violations = false; 163 164 final RulesChecker checker = new RulesChecker(); 165 final List<Rule> checkerrules = new ArrayList<Rule>(); 166 for (final RuleConfiguration r : rules) { 167 checkerrules.add(r.rule); 168 } 169 checker.setRules(checkerrules); 170 171 final IReportVisitor visitor = checker.createVisitor(this); 172 try { 173 visitor.visitBundle(bundle, null); 174 } catch (final IOException e) { 175 throw new MojoExecutionException( 176 "Error while checking code coverage: " + e.getMessage(), e); 177 } 178 if (violations) { 179 if (this.haltOnFailure) { 180 throw new MojoExecutionException(CHECK_FAILED); 181 } else { 182 this.getLog().warn(CHECK_FAILED); 183 } 184 } else { 185 this.getLog().info(CHECK_SUCCESS); 186 } 187 } 188 189 private IBundleCoverage loadBundle() throws MojoExecutionException { 190 final FileFilter fileFilter = new FileFilter(this.getIncludes(), 191 this.getExcludes()); 192 final BundleCreator creator = new BundleCreator(getProject(), 193 fileFilter, getLog()); 194 try { 195 final ExecutionDataStore executionData = loadExecutionData(); 196 return creator.createBundle(executionData); 197 } catch (final IOException e) { 198 throw new MojoExecutionException( 199 "Error while reading code coverage: " + e.getMessage(), e); 200 } 201 } 202 203 private ExecutionDataStore loadExecutionData() throws IOException { 204 final ExecFileLoader loader = new ExecFileLoader(); 205 loader.load(dataFile); 206 return loader.getExecutionDataStore(); 207 } 208 209 public void onViolation(final ICoverageNode node, final Rule rule, 210 final Limit limit, final String message) { 211 this.getLog().warn(message); 212 violations = true; 213 } 214 215} 216