• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *
11  *******************************************************************************/
12 package org.jacoco.maven;
13 
14 import java.io.File;
15 import java.io.FileInputStream;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.Reader;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Locale;
23 
24 import org.apache.maven.doxia.siterenderer.Renderer;
25 import org.apache.maven.plugin.MojoExecutionException;
26 import org.apache.maven.project.MavenProject;
27 import org.apache.maven.reporting.AbstractMavenReport;
28 import org.apache.maven.reporting.MavenReportException;
29 import org.jacoco.core.analysis.IBundleCoverage;
30 import org.jacoco.core.analysis.ICoverageNode;
31 import org.jacoco.core.data.ExecutionDataStore;
32 import org.jacoco.core.data.SessionInfoStore;
33 import org.jacoco.core.tools.ExecFileLoader;
34 import org.jacoco.report.FileMultiReportOutput;
35 import org.jacoco.report.IReportGroupVisitor;
36 import org.jacoco.report.IReportVisitor;
37 import org.jacoco.report.ISourceFileLocator;
38 import org.jacoco.report.MultiReportVisitor;
39 import org.jacoco.report.csv.CSVFormatter;
40 import org.jacoco.report.html.HTMLFormatter;
41 import org.jacoco.report.xml.XMLFormatter;
42 
43 /**
44  * Base class for creating a code coverage report for tests of a single project
45  * in multiple formats (HTML, XML, and CSV).
46  */
47 public abstract class AbstractReportMojo extends AbstractMavenReport {
48 
49 	/**
50 	 * Encoding of the generated reports.
51 	 *
52 	 * @parameter property="project.reporting.outputEncoding"
53 	 *            default-value="UTF-8"
54 	 */
55 	String outputEncoding;
56 	/**
57 	 * Encoding of the source files.
58 	 *
59 	 * @parameter property="project.build.sourceEncoding"
60 	 *            default-value="UTF-8"
61 	 */
62 	String sourceEncoding;
63 	/**
64 	 * A list of class files to include in the report. May use wildcard
65 	 * characters (* and ?). When not specified everything will be included.
66 	 *
67 	 * @parameter
68 	 */
69 	List<String> includes;
70 	/**
71 	 * A list of class files to exclude from the report. May use wildcard
72 	 * characters (* and ?). When not specified nothing will be excluded.
73 	 *
74 	 * @parameter
75 	 */
76 	List<String> excludes;
77 	/**
78 	 * Flag used to suppress execution.
79 	 *
80 	 * @parameter property="jacoco.skip" default-value="false"
81 	 */
82 	boolean skip;
83 	/**
84 	 * Maven project.
85 	 *
86 	 * @parameter property="project"
87 	 * @readonly
88 	 */
89 	MavenProject project;
90 	/**
91 	 * Doxia Site Renderer.
92 	 *
93 	 * @component
94 	 */
95 	Renderer siteRenderer;
96 	SessionInfoStore sessionInfoStore;
97 	ExecutionDataStore executionDataStore;
98 
getOutputName()99 	public abstract String getOutputName();
100 
getName(final Locale locale)101 	public abstract String getName(final Locale locale);
102 
getDescription(final Locale locale)103 	public String getDescription(final Locale locale) {
104 		return getName(locale) + " Coverage Report.";
105 	}
106 
107 	@Override
isExternalReport()108 	public boolean isExternalReport() {
109 		return true;
110 	}
111 
112 	@Override
getProject()113 	protected MavenProject getProject() {
114 		return project;
115 	}
116 
117 	@Override
getSiteRenderer()118 	protected Renderer getSiteRenderer() {
119 		return siteRenderer;
120 	}
121 
122 	/**
123 	 * Returns the list of class files to include in the report.
124 	 *
125 	 * @return class files to include, may contain wildcard characters
126 	 */
getIncludes()127 	List<String> getIncludes() {
128 		return includes;
129 	}
130 
131 	/**
132 	 * Returns the list of class files to exclude from the report.
133 	 *
134 	 * @return class files to exclude, may contain wildcard characters
135 	 */
getExcludes()136 	List<String> getExcludes() {
137 		return excludes;
138 	}
139 
140 	@Override
setReportOutputDirectory( final File reportOutputDirectory)141 	public abstract void setReportOutputDirectory(
142 			final File reportOutputDirectory);
143 
144 	@Override
canGenerateReport()145 	public boolean canGenerateReport() {
146 		if (skip) {
147 			getLog().info(
148 					"Skipping JaCoCo execution because property jacoco.skip is set.");
149 			return false;
150 		}
151 		if (!getDataFile().exists()) {
152 			getLog().info(
153 					"Skipping JaCoCo execution due to missing execution data file:"
154 							+ getDataFile());
155 			return false;
156 		}
157 		final File classesDirectory = new File(getProject().getBuild()
158 				.getOutputDirectory());
159 		if (!classesDirectory.exists()) {
160 			getLog().info(
161 					"Skipping JaCoCo execution due to missing classes directory:"
162 							+ classesDirectory);
163 			return false;
164 		}
165 		return true;
166 	}
167 
168 	/**
169 	 * This method is called when the report generation is invoked directly as a
170 	 * standalone Mojo.
171 	 */
172 	@Override
execute()173 	public void execute() throws MojoExecutionException {
174 		if (!canGenerateReport()) {
175 			return;
176 		}
177 		try {
178 			executeReport(Locale.getDefault());
179 		} catch (final MavenReportException e) {
180 			throw new MojoExecutionException("An error has occurred in "
181 					+ getName(Locale.ENGLISH) + " report generation.", e);
182 		}
183 	}
184 
185 	@Override
executeReport(final Locale locale)186 	protected void executeReport(final Locale locale)
187 			throws MavenReportException {
188 		loadExecutionData();
189 		try {
190 			final IReportVisitor visitor = createVisitor(locale);
191 			visitor.visitInfo(sessionInfoStore.getInfos(),
192 					executionDataStore.getContents());
193 			createReport(visitor);
194 			visitor.visitEnd();
195 		} catch (final IOException e) {
196 			throw new MavenReportException("Error while creating report: "
197 					+ e.getMessage(), e);
198 		}
199 	}
200 
loadExecutionData()201 	void loadExecutionData() throws MavenReportException {
202 		final ExecFileLoader loader = new ExecFileLoader();
203 		try {
204 			loader.load(getDataFile());
205 		} catch (final IOException e) {
206 			throw new MavenReportException(
207 					"Unable to read execution data file " + getDataFile()
208 							+ ": " + e.getMessage(), e);
209 		}
210 		sessionInfoStore = loader.getSessionInfoStore();
211 		executionDataStore = loader.getExecutionDataStore();
212 	}
213 
createReport(final IReportGroupVisitor visitor)214 	void createReport(final IReportGroupVisitor visitor) throws IOException {
215 		final FileFilter fileFilter = new FileFilter(this.getIncludes(),
216 				this.getExcludes());
217 		final BundleCreator creator = new BundleCreator(this.getProject(),
218 				fileFilter, getLog());
219 		final IBundleCoverage bundle = creator.createBundle(executionDataStore);
220 		final SourceFileCollection locator = new SourceFileCollection(
221 				getCompileSourceRoots(), sourceEncoding);
222 		checkForMissingDebugInformation(bundle);
223 		visitor.visitBundle(bundle, locator);
224 	}
225 
checkForMissingDebugInformation(final ICoverageNode node)226 	void checkForMissingDebugInformation(final ICoverageNode node) {
227 		if (node.getClassCounter().getTotalCount() > 0
228 				&& node.getLineCounter().getTotalCount() == 0) {
229 			getLog().warn(
230 					"To enable source code annotation class files have to be compiled with debug information.");
231 		}
232 	}
233 
createVisitor(final Locale locale)234 	IReportVisitor createVisitor(final Locale locale) throws IOException {
235 		final List<IReportVisitor> visitors = new ArrayList<IReportVisitor>();
236 		getOutputDirectoryFile().mkdirs();
237 		final XMLFormatter xmlFormatter = new XMLFormatter();
238 		xmlFormatter.setOutputEncoding(outputEncoding);
239 		visitors.add(xmlFormatter.createVisitor(new FileOutputStream(new File(
240 				getOutputDirectoryFile(), "jacoco.xml"))));
241 		final CSVFormatter csvFormatter = new CSVFormatter();
242 		csvFormatter.setOutputEncoding(outputEncoding);
243 		visitors.add(csvFormatter.createVisitor(new FileOutputStream(new File(
244 				getOutputDirectoryFile(), "jacoco.csv"))));
245 		final HTMLFormatter htmlFormatter = new HTMLFormatter();
246 		htmlFormatter.setOutputEncoding(outputEncoding);
247 		htmlFormatter.setLocale(locale);
248 		visitors.add(htmlFormatter.createVisitor(new FileMultiReportOutput(
249 				getOutputDirectoryFile())));
250 		return new MultiReportVisitor(visitors);
251 	}
252 
resolvePath(final String path)253 	File resolvePath(final String path) {
254 		File file = new File(path);
255 		if (!file.isAbsolute()) {
256 			file = new File(getProject().getBasedir(), path);
257 		}
258 		return file;
259 	}
260 
getCompileSourceRoots()261 	List<File> getCompileSourceRoots() {
262 		final List<File> result = new ArrayList<File>();
263 		for (final Object path : getProject().getCompileSourceRoots()) {
264 			result.add(resolvePath((String) path));
265 		}
266 		return result;
267 	}
268 
269 	private static class SourceFileCollection implements ISourceFileLocator {
270 
271 		private final List<File> sourceRoots;
272 		private final String encoding;
273 
SourceFileCollection(final List<File> sourceRoots, final String encoding)274 		public SourceFileCollection(final List<File> sourceRoots,
275 				final String encoding) {
276 			this.sourceRoots = sourceRoots;
277 			this.encoding = encoding;
278 		}
279 
getSourceFile(final String packageName, final String fileName)280 		public Reader getSourceFile(final String packageName,
281 				final String fileName) throws IOException {
282 			final String r;
283 			if (packageName.length() > 0) {
284 				r = packageName + '/' + fileName;
285 			} else {
286 				r = fileName;
287 			}
288 			for (final File sourceRoot : sourceRoots) {
289 				final File file = new File(sourceRoot, r);
290 				if (file.exists() && file.isFile()) {
291 					return new InputStreamReader(new FileInputStream(file),
292 							encoding);
293 				}
294 			}
295 			return null;
296 		}
297 
getTabWidth()298 		public int getTabWidth() {
299 			return 4;
300 		}
301 	}
302 
getDataFile()303 	abstract File getDataFile();
304 
getOutputDirectoryFile()305 	abstract File getOutputDirectoryFile();
306 
307 }
308