1 /******************************************************************************* 2 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors 3 * This program and the accompanying materials are made available under 4 * the terms of the Eclipse Public License 2.0 which is available at 5 * http://www.eclipse.org/legal/epl-2.0 6 * 7 * SPDX-License-Identifier: EPL-2.0 8 * 9 * Contributors: 10 * Evgeny Mandrikov - initial API and implementation 11 * Kyle Lieber - implementation of CheckMojo 12 * 13 *******************************************************************************/ 14 package org.jacoco.maven; 15 16 import static java.lang.String.format; 17 18 import java.io.File; 19 import java.io.FileInputStream; 20 import java.io.IOException; 21 import java.io.InputStreamReader; 22 import java.io.Reader; 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.List; 26 27 import org.apache.maven.plugin.logging.Log; 28 import org.apache.maven.project.MavenProject; 29 import org.jacoco.core.analysis.Analyzer; 30 import org.jacoco.core.analysis.CoverageBuilder; 31 import org.jacoco.core.analysis.IBundleCoverage; 32 import org.jacoco.core.analysis.IClassCoverage; 33 import org.jacoco.core.tools.ExecFileLoader; 34 import org.jacoco.report.IReportGroupVisitor; 35 import org.jacoco.report.IReportVisitor; 36 import org.jacoco.report.ISourceFileLocator; 37 import org.jacoco.report.MultiReportVisitor; 38 import org.jacoco.report.check.IViolationsOutput; 39 import org.jacoco.report.check.Rule; 40 import org.jacoco.report.check.RulesChecker; 41 42 /** 43 * Encapsulates the tasks to create reports for Maven projects. Instances are 44 * supposed to be used in the following sequence: 45 * 46 * <ol> 47 * <li>Create an instance</li> 48 * <li>Load one or multiple exec files with 49 * <code>loadExecutionData()</code></li> 50 * <li>Add one or multiple formatters with <code>addXXX()</code> methods</li> 51 * <li>Create the root visitor with <code>initRootVisitor()</code></li> 52 * <li>Process one or multiple projects with <code>processProject()</code></li> 53 * </ol> 54 */ 55 final class ReportSupport { 56 57 private final Log log; 58 private final ExecFileLoader loader; 59 private final List<IReportVisitor> formatters; 60 61 /** 62 * Construct a new instance with the given log output. 63 * 64 * @param log 65 * for log output 66 */ ReportSupport(final Log log)67 public ReportSupport(final Log log) { 68 this.log = log; 69 this.loader = new ExecFileLoader(); 70 this.formatters = new ArrayList<IReportVisitor>(); 71 } 72 73 /** 74 * Loads the given execution data file. 75 * 76 * @param execFile 77 * execution data file to load 78 * @throws IOException 79 * if the file can't be loaded 80 */ loadExecutionData(final File execFile)81 public void loadExecutionData(final File execFile) throws IOException { 82 log.info("Loading execution data file " + execFile); 83 loader.load(execFile); 84 } 85 addVisitor(final IReportVisitor visitor)86 public void addVisitor(final IReportVisitor visitor) { 87 formatters.add(visitor); 88 } 89 addRulesChecker(final List<Rule> rules, final IViolationsOutput output)90 public void addRulesChecker(final List<Rule> rules, 91 final IViolationsOutput output) { 92 final RulesChecker checker = new RulesChecker(); 93 checker.setRules(rules); 94 formatters.add(checker.createVisitor(output)); 95 } 96 initRootVisitor()97 public IReportVisitor initRootVisitor() throws IOException { 98 final IReportVisitor visitor = new MultiReportVisitor(formatters); 99 visitor.visitInfo(loader.getSessionInfoStore().getInfos(), 100 loader.getExecutionDataStore().getContents()); 101 return visitor; 102 } 103 104 /** 105 * Calculates coverage for the given project and emits it to the report 106 * group without source references 107 * 108 * @param visitor 109 * group visitor to emit the project's coverage to 110 * @param project 111 * the MavenProject 112 * @param includes 113 * list of includes patterns 114 * @param excludes 115 * list of excludes patterns 116 * @throws IOException 117 * if class files can't be read 118 */ processProject(final IReportGroupVisitor visitor, final MavenProject project, final List<String> includes, final List<String> excludes)119 public void processProject(final IReportGroupVisitor visitor, 120 final MavenProject project, final List<String> includes, 121 final List<String> excludes) throws IOException { 122 processProject(visitor, project.getArtifactId(), project, includes, 123 excludes, new NoSourceLocator()); 124 } 125 126 /** 127 * Calculates coverage for the given project and emits it to the report 128 * group including source references 129 * 130 * @param visitor 131 * group visitor to emit the project's coverage to 132 * @param bundleName 133 * name for this project in the report 134 * @param project 135 * the MavenProject 136 * @param includes 137 * list of includes patterns 138 * @param excludes 139 * list of excludes patterns 140 * @param srcEncoding 141 * encoding of the source files within this project 142 * @throws IOException 143 * if class files can't be read 144 */ processProject(final IReportGroupVisitor visitor, final String bundleName, final MavenProject project, final List<String> includes, final List<String> excludes, final String srcEncoding)145 public void processProject(final IReportGroupVisitor visitor, 146 final String bundleName, final MavenProject project, 147 final List<String> includes, final List<String> excludes, 148 final String srcEncoding) throws IOException { 149 processProject(visitor, bundleName, project, includes, excludes, 150 new SourceFileCollection(project, srcEncoding)); 151 } 152 processProject(final IReportGroupVisitor visitor, final String bundleName, final MavenProject project, final List<String> includes, final List<String> excludes, final ISourceFileLocator locator)153 private void processProject(final IReportGroupVisitor visitor, 154 final String bundleName, final MavenProject project, 155 final List<String> includes, final List<String> excludes, 156 final ISourceFileLocator locator) throws IOException { 157 final CoverageBuilder builder = new CoverageBuilder(); 158 final File classesDir = new File( 159 project.getBuild().getOutputDirectory()); 160 161 if (classesDir.isDirectory()) { 162 final Analyzer analyzer = new Analyzer( 163 loader.getExecutionDataStore(), builder); 164 final FileFilter filter = new FileFilter(includes, excludes); 165 for (final File file : filter.getFiles(classesDir)) { 166 analyzer.analyzeAll(file); 167 } 168 } 169 170 final IBundleCoverage bundle = builder.getBundle(bundleName); 171 logBundleInfo(bundle, builder.getNoMatchClasses()); 172 173 visitor.visitBundle(bundle, locator); 174 } 175 logBundleInfo(final IBundleCoverage bundle, final Collection<IClassCoverage> nomatch)176 private void logBundleInfo(final IBundleCoverage bundle, 177 final Collection<IClassCoverage> nomatch) { 178 log.info(format("Analyzed bundle '%s' with %s classes", 179 bundle.getName(), 180 Integer.valueOf(bundle.getClassCounter().getTotalCount()))); 181 if (!nomatch.isEmpty()) { 182 log.warn(format( 183 "Classes in bundle '%s' do not match with execution data. " 184 + "For report generation the same class files must be used as at runtime.", 185 bundle.getName())); 186 for (final IClassCoverage c : nomatch) { 187 log.warn(format("Execution data for class %s does not match.", 188 c.getName())); 189 } 190 } 191 if (bundle.containsCode() 192 && bundle.getLineCounter().getTotalCount() == 0) { 193 log.warn( 194 "To enable source code annotation class files have to be compiled with debug information."); 195 } 196 } 197 198 private class NoSourceLocator implements ISourceFileLocator { 199 getSourceFile(final String packageName, final String fileName)200 public Reader getSourceFile(final String packageName, 201 final String fileName) { 202 return null; 203 } 204 getTabWidth()205 public int getTabWidth() { 206 return 0; 207 } 208 } 209 210 private class SourceFileCollection implements ISourceFileLocator { 211 212 private final List<File> sourceRoots; 213 private final String encoding; 214 SourceFileCollection(final MavenProject project, final String encoding)215 public SourceFileCollection(final MavenProject project, 216 final String encoding) { 217 this.sourceRoots = getCompileSourceRoots(project); 218 this.encoding = encoding; 219 } 220 getSourceFile(final String packageName, final String fileName)221 public Reader getSourceFile(final String packageName, 222 final String fileName) throws IOException { 223 final String r; 224 if (packageName.length() > 0) { 225 r = packageName + '/' + fileName; 226 } else { 227 r = fileName; 228 } 229 for (final File sourceRoot : sourceRoots) { 230 final File file = new File(sourceRoot, r); 231 if (file.exists() && file.isFile()) { 232 return new InputStreamReader(new FileInputStream(file), 233 encoding); 234 } 235 } 236 return null; 237 } 238 getTabWidth()239 public int getTabWidth() { 240 return 4; 241 } 242 } 243 getCompileSourceRoots( final MavenProject project)244 private static List<File> getCompileSourceRoots( 245 final MavenProject project) { 246 final List<File> result = new ArrayList<File>(); 247 for (final Object path : project.getCompileSourceRoots()) { 248 result.add(resolvePath(project, (String) path)); 249 } 250 return result; 251 } 252 resolvePath(final MavenProject project, final String path)253 private static File resolvePath(final MavenProject project, 254 final String path) { 255 File file = new File(path); 256 if (!file.isAbsolute()) { 257 file = new File(project.getBasedir(), path); 258 } 259 return file; 260 } 261 262 } 263