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