Home | History | Annotate | Download | only in maven
      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 
     99 	public abstract String getOutputName();
    100 
    101 	public abstract String getName(final Locale locale);
    102 
    103 	public String getDescription(final Locale locale) {
    104 		return getName(locale) + " Coverage Report.";
    105 	}
    106 
    107 	@Override
    108 	public boolean isExternalReport() {
    109 		return true;
    110 	}
    111 
    112 	@Override
    113 	protected MavenProject getProject() {
    114 		return project;
    115 	}
    116 
    117 	@Override
    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 	 */
    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 	 */
    136 	List<String> getExcludes() {
    137 		return excludes;
    138 	}
    139 
    140 	@Override
    141 	public abstract void setReportOutputDirectory(
    142 			final File reportOutputDirectory);
    143 
    144 	@Override
    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
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    274 		public SourceFileCollection(final List<File> sourceRoots,
    275 				final String encoding) {
    276 			this.sourceRoots = sourceRoots;
    277 			this.encoding = encoding;
    278 		}
    279 
    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 
    298 		public int getTabWidth() {
    299 			return 4;
    300 		}
    301 	}
    302 
    303 	abstract File getDataFile();
    304 
    305 	abstract File getOutputDirectoryFile();
    306 
    307 }
    308