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