diff options
author | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2016-04-19 13:22:01 +0200 |
---|---|---|
committer | Evgeny Mandrikov <Godin@users.noreply.github.com> | 2016-04-19 13:22:01 +0200 |
commit | c181f60ce08ec9b0a6f59a2391c33c41bca8f1c0 (patch) | |
tree | 714c1ffa8b66fde4037131aade0269f63c8a40b6 /jacoco-maven-plugin/src/org/jacoco/maven | |
parent | bac679b3ae6363f1d610e705139f0e1964b319ea (diff) | |
download | jacoco-c181f60ce08ec9b0a6f59a2391c33c41bca8f1c0.tar.gz |
GitHub #388: New Maven goal "report-aggregate" to create reports for multi-module projects
Diffstat (limited to 'jacoco-maven-plugin/src/org/jacoco/maven')
8 files changed, 606 insertions, 305 deletions
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/AbstractReportMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/AbstractReportMojo.java index 5d9d7c1c..0db69d6e 100644 --- a/jacoco-maven-plugin/src/org/jacoco/maven/AbstractReportMojo.java +++ b/jacoco-maven-plugin/src/org/jacoco/maven/AbstractReportMojo.java @@ -11,13 +11,7 @@ *******************************************************************************/ package org.jacoco.maven; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -26,19 +20,8 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.apache.maven.reporting.AbstractMavenReport; import org.apache.maven.reporting.MavenReportException; -import org.jacoco.core.analysis.IBundleCoverage; -import org.jacoco.core.analysis.ICoverageNode; -import org.jacoco.core.data.ExecutionDataStore; -import org.jacoco.core.data.SessionInfoStore; -import org.jacoco.core.tools.ExecFileLoader; -import org.jacoco.report.FileMultiReportOutput; import org.jacoco.report.IReportGroupVisitor; import org.jacoco.report.IReportVisitor; -import org.jacoco.report.ISourceFileLocator; -import org.jacoco.report.MultiReportVisitor; -import org.jacoco.report.csv.CSVFormatter; -import org.jacoco.report.html.HTMLFormatter; -import org.jacoco.report.xml.XMLFormatter; /** * Base class for creating a code coverage report for tests of a single project @@ -53,13 +36,30 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { * default-value="UTF-8" */ String outputEncoding; + + /** + * Name of the root node HTML report pages. + * + * @parameter default-value="${project.name}" + * @since 0.7.7 + */ + String title; + + /** + * Footer text used in HTML report pages. + * + * @parameter + * @since 0.7.7 + */ + String footer; + /** * Encoding of the source files. * - * @parameter property="project.build.sourceEncoding" - * default-value="UTF-8" + * @parameter property="project.build.sourceEncoding" default-value="UTF-8" */ String sourceEncoding; + /** * A list of class files to include in the report. May use wildcard * characters (* and ?). When not specified everything will be included. @@ -67,6 +67,7 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { * @parameter */ List<String> includes; + /** * A list of class files to exclude from the report. May use wildcard * characters (* and ?). When not specified nothing will be excluded. @@ -74,12 +75,14 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { * @parameter */ List<String> excludes; + /** * Flag used to suppress execution. * * @parameter property="jacoco.skip" default-value="false" */ boolean skip; + /** * Maven project. * @@ -87,18 +90,13 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { * @readonly */ MavenProject project; + /** * Doxia Site Renderer. * * @component */ Renderer siteRenderer; - SessionInfoStore sessionInfoStore; - ExecutionDataStore executionDataStore; - - public abstract String getOutputName(); - - public abstract String getName(final Locale locale); public String getDescription(final Locale locale) { return getName(locale) + " Coverage Report."; @@ -138,33 +136,29 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { } @Override - public abstract void setReportOutputDirectory( - final File reportOutputDirectory); - - @Override public boolean canGenerateReport() { if (skip) { getLog().info( "Skipping JaCoCo execution because property jacoco.skip is set."); return false; } - if (!getDataFile().exists()) { + if (!canGenerateReportRegardingDataFiles()) { getLog().info( - "Skipping JaCoCo execution due to missing execution data file:" - + getDataFile()); + "Skipping JaCoCo execution due to missing execution data file."); return false; } - final File classesDirectory = new File(getProject().getBuild() - .getOutputDirectory()); - if (!classesDirectory.exists()) { + if (!canGenerateReportRegardingClassesDirectory()) { getLog().info( - "Skipping JaCoCo execution due to missing classes directory:" - + classesDirectory); + "Skipping JaCoCo execution due to missing classes directory."); return false; } return true; } + abstract boolean canGenerateReportRegardingDataFiles(); + + abstract boolean canGenerateReportRegardingClassesDirectory(); + /** * This method is called when the report generation is invoked directly as a * standalone Mojo. @@ -185,12 +179,12 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { @Override protected void executeReport(final Locale locale) throws MavenReportException { - loadExecutionData(); try { - final IReportVisitor visitor = createVisitor(locale); - visitor.visitInfo(sessionInfoStore.getInfos(), - executionDataStore.getContents()); - createReport(visitor); + final ReportSupport support = new ReportSupport(getLog()); + loadExecutionData(support); + addFormatters(support, locale); + final IReportVisitor visitor = support.initRootVisitor(); + createReport(visitor, support); visitor.visitEnd(); } catch (final IOException e) { throw new MavenReportException("Error while creating report: " @@ -198,110 +192,13 @@ public abstract class AbstractReportMojo extends AbstractMavenReport { } } - void loadExecutionData() throws MavenReportException { - final ExecFileLoader loader = new ExecFileLoader(); - try { - loader.load(getDataFile()); - } catch (final IOException e) { - throw new MavenReportException( - "Unable to read execution data file " + getDataFile() - + ": " + e.getMessage(), e); - } - sessionInfoStore = loader.getSessionInfoStore(); - executionDataStore = loader.getExecutionDataStore(); - } - - void createReport(final IReportGroupVisitor visitor) throws IOException { - final FileFilter fileFilter = new FileFilter(this.getIncludes(), - this.getExcludes()); - final BundleCreator creator = new BundleCreator(this.getProject(), - fileFilter, getLog()); - final IBundleCoverage bundle = creator.createBundle(executionDataStore); - final SourceFileCollection locator = new SourceFileCollection( - getCompileSourceRoots(), sourceEncoding); - checkForMissingDebugInformation(bundle); - visitor.visitBundle(bundle, locator); - } - - void checkForMissingDebugInformation(final ICoverageNode node) { - if (node.getClassCounter().getTotalCount() > 0 - && node.getLineCounter().getTotalCount() == 0) { - getLog().warn( - "To enable source code annotation class files have to be compiled with debug information."); - } - } - - IReportVisitor createVisitor(final Locale locale) throws IOException { - final List<IReportVisitor> visitors = new ArrayList<IReportVisitor>(); - getOutputDirectoryFile().mkdirs(); - final XMLFormatter xmlFormatter = new XMLFormatter(); - xmlFormatter.setOutputEncoding(outputEncoding); - visitors.add(xmlFormatter.createVisitor(new FileOutputStream(new File( - getOutputDirectoryFile(), "jacoco.xml")))); - final CSVFormatter csvFormatter = new CSVFormatter(); - csvFormatter.setOutputEncoding(outputEncoding); - visitors.add(csvFormatter.createVisitor(new FileOutputStream(new File( - getOutputDirectoryFile(), "jacoco.csv")))); - final HTMLFormatter htmlFormatter = new HTMLFormatter(); - htmlFormatter.setOutputEncoding(outputEncoding); - htmlFormatter.setLocale(locale); - visitors.add(htmlFormatter.createVisitor(new FileMultiReportOutput( - getOutputDirectoryFile()))); - return new MultiReportVisitor(visitors); - } - - File resolvePath(final String path) { - File file = new File(path); - if (!file.isAbsolute()) { - file = new File(getProject().getBasedir(), path); - } - return file; - } - - List<File> getCompileSourceRoots() { - final List<File> result = new ArrayList<File>(); - for (final Object path : getProject().getCompileSourceRoots()) { - result.add(resolvePath((String) path)); - } - return result; - } - - private static class SourceFileCollection implements ISourceFileLocator { - - private final List<File> sourceRoots; - private final String encoding; - - public SourceFileCollection(final List<File> sourceRoots, - final String encoding) { - this.sourceRoots = sourceRoots; - this.encoding = encoding; - } - - public Reader getSourceFile(final String packageName, - final String fileName) throws IOException { - final String r; - if (packageName.length() > 0) { - r = packageName + '/' + fileName; - } else { - r = fileName; - } - for (final File sourceRoot : sourceRoots) { - final File file = new File(sourceRoot, r); - if (file.exists() && file.isFile()) { - return new InputStreamReader(new FileInputStream(file), - encoding); - } - } - return null; - } - - public int getTabWidth() { - return 4; - } - } + abstract void loadExecutionData(final ReportSupport support) + throws IOException; - abstract File getDataFile(); + abstract void addFormatters(final ReportSupport support, final Locale locale) + throws IOException; - abstract File getOutputDirectoryFile(); + abstract void createReport(final IReportGroupVisitor visitor, + final ReportSupport support) throws IOException; } diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/BundleCreator.java b/jacoco-maven-plugin/src/org/jacoco/maven/BundleCreator.java deleted file mode 100644 index b4c8179f..00000000 --- a/jacoco-maven-plugin/src/org/jacoco/maven/BundleCreator.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2016 Mountainminds GmbH & Co. KG and Contributors - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Evgeny Mandrikov - initial API and implementation - * Kyle Lieber - implementation of CheckMojo - * - *******************************************************************************/ -package org.jacoco.maven; - -import static java.lang.String.format; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.List; - -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.util.FileUtils; -import org.jacoco.core.analysis.Analyzer; -import org.jacoco.core.analysis.CoverageBuilder; -import org.jacoco.core.analysis.IBundleCoverage; -import org.jacoco.core.analysis.IClassCoverage; -import org.jacoco.core.data.ExecutionDataStore; - -/** - * Creates an IBundleCoverage. - */ -public final class BundleCreator { - - private final MavenProject project; - private final FileFilter fileFilter; - private final Log log; - - /** - * Construct a new BundleCreator given the MavenProject and FileFilter. - * - * @param project - * the MavenProject - * @param fileFilter - * the FileFilter - * @param log - * for log output - */ - public BundleCreator(final MavenProject project, - final FileFilter fileFilter, final Log log) { - this.project = project; - this.fileFilter = fileFilter; - this.log = log; - } - - /** - * Create an IBundleCoverage for the given ExecutionDataStore. - * - * @param executionDataStore - * the execution data. - * @return the coverage data. - * @throws IOException - * if class files can't be read - */ - public IBundleCoverage createBundle( - final ExecutionDataStore executionDataStore) throws IOException { - final CoverageBuilder builder = new CoverageBuilder(); - final Analyzer analyzer = new Analyzer(executionDataStore, builder); - final File classesDir = new File(this.project.getBuild() - .getOutputDirectory()); - - @SuppressWarnings("unchecked") - final List<File> filesToAnalyze = FileUtils.getFiles(classesDir, - fileFilter.getIncludes(), fileFilter.getExcludes()); - - for (final File file : filesToAnalyze) { - analyzer.analyzeAll(file); - } - - final IBundleCoverage bundle = builder - .getBundle(this.project.getName()); - logBundleInfo(bundle, builder.getNoMatchClasses()); - - return bundle; - } - - private void logBundleInfo(final IBundleCoverage bundle, - final Collection<IClassCoverage> nomatch) { - log.info(format("Analyzed bundle '%s' with %s classes", - bundle.getName(), - Integer.valueOf(bundle.getClassCounter().getTotalCount()))); - if (!nomatch.isEmpty()) { - log.warn(format( - "Classes in bundle '%s' do no match with execution data. " - + "For report generation the same class files must be used as at runtime.", - bundle.getName())); - for (final IClassCoverage c : nomatch) { - log.warn(format("Execution data for class %s does not match.", - c.getName())); - } - } - } - -} diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java index 10ec9eb2..b61acee1 100644 --- a/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java +++ b/jacoco-maven-plugin/src/org/jacoco/maven/CheckMojo.java @@ -19,15 +19,11 @@ import java.util.ArrayList; import java.util.List; import org.apache.maven.plugin.MojoExecutionException; -import org.jacoco.core.analysis.IBundleCoverage; import org.jacoco.core.analysis.ICoverageNode; -import org.jacoco.core.data.ExecutionDataStore; -import org.jacoco.core.tools.ExecFileLoader; import org.jacoco.report.IReportVisitor; import org.jacoco.report.check.IViolationsOutput; import org.jacoco.report.check.Limit; import org.jacoco.report.check.Rule; -import org.jacoco.report.check.RulesChecker; /** * Checks that the code coverage metrics are being met. @@ -170,19 +166,22 @@ public class CheckMojo extends AbstractJacocoMojo implements IViolationsOutput { } private void executeCheck() throws MojoExecutionException { - final IBundleCoverage bundle = loadBundle(); violations = false; - final RulesChecker checker = new RulesChecker(); + final ReportSupport support = new ReportSupport(getLog()); + final List<Rule> checkerrules = new ArrayList<Rule>(); for (final RuleConfiguration r : rules) { checkerrules.add(r.rule); } - checker.setRules(checkerrules); + support.addRulesChecker(checkerrules, this); - final IReportVisitor visitor = checker.createVisitor(this); try { - visitor.visitBundle(bundle, null); + final IReportVisitor visitor = support.initRootVisitor(); + support.loadExecutionData(dataFile); + support.processProject(visitor, getProject(), this.getIncludes(), + this.getExcludes()); + visitor.visitEnd(); } catch (final IOException e) { throw new MojoExecutionException( "Error while checking code coverage: " + e.getMessage(), e); @@ -198,26 +197,6 @@ public class CheckMojo extends AbstractJacocoMojo implements IViolationsOutput { } } - private IBundleCoverage loadBundle() throws MojoExecutionException { - final FileFilter fileFilter = new FileFilter(this.getIncludes(), - this.getExcludes()); - final BundleCreator creator = new BundleCreator(getProject(), - fileFilter, getLog()); - try { - final ExecutionDataStore executionData = loadExecutionData(); - return creator.createBundle(executionData); - } catch (final IOException e) { - throw new MojoExecutionException( - "Error while reading code coverage: " + e.getMessage(), e); - } - } - - private ExecutionDataStore loadExecutionData() throws IOException { - final ExecFileLoader loader = new ExecFileLoader(); - loader.load(dataFile); - return loader.getExecutionDataStore(); - } - public void onViolation(final ICoverageNode node, final Rule rule, final Limit limit, final String message) { this.getLog().warn(message); diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/FileFilter.java b/jacoco-maven-plugin/src/org/jacoco/maven/FileFilter.java index d4be7426..3da49410 100644 --- a/jacoco-maven-plugin/src/org/jacoco/maven/FileFilter.java +++ b/jacoco-maven-plugin/src/org/jacoco/maven/FileFilter.java @@ -45,7 +45,7 @@ public class FileFilter { } /** - * Returns a list of files. + * Returns a list of file names. * * @param directory * the directory to scan @@ -60,6 +60,20 @@ public class FileFilter { } /** + * Returns a list of files. + * + * @param directory + * the directory to scan + * @return a list of files + * @throws IOException + * if file system access fails + */ + @SuppressWarnings("unchecked") + public List<File> getFiles(final File directory) throws IOException { + return FileUtils.getFiles(directory, getIncludes(), getExcludes()); + } + + /** * Get the includes pattern * * @return the pattern diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/ReportAggregateMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/ReportAggregateMojo.java new file mode 100644 index 00000000..e8087694 --- /dev/null +++ b/jacoco-maven-plugin/src/org/jacoco/maven/ReportAggregateMojo.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2009, 2016 Mountainminds GmbH & Co. KG and Contributors + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * John Oliver, Marc R. Hoffmann, Jan Wloka - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.maven; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; +import org.jacoco.report.IReportGroupVisitor; + +/** + * <p> + * Creates a structured code coverage report (HTML, XML, and CSV) from multiple + * projects within reactor. The report is created from all modules this project + * depends on. From those projects class and source files as well as JaCoCo + * execution data files will be collected. This also allows to create coverage + * reports when tests are in separate projects than the code under test, for + * example in case of integration tests. + * </p> + * + * <p> + * Using the dependency scope allows to distinguish projects which contribute + * execution data but should not be part of the report itself: + * </p> + * + * <ul> + * <li><code>compile</code>: Project source and execution data is included in + * the report.</li> + * <li><code>test</code>: Only execution data is considered for the report.</li> + * </ul> + * + * @goal report-aggregate + * @requiresProject true + * @threadSafe + * @since 0.7.7 + */ +public class ReportAggregateMojo extends AbstractReportMojo { + + /** + * A list of execution data files to include in the report from each + * project. May use wildcard characters (* and ?). When not specified all + * *.exec files from the target folder will be included. + * + * @parameter default-value="target/*.exec" + */ + List<String> dataFileIncludes; + + /** + * A list of execution data files to exclude from the report. May use + * wildcard characters (* and ?). When not specified nothing will be + * excluded. + * + * @parameter + */ + List<String> dataFileExcludes; + + /** + * Output directory for the reports. Note that this parameter is only + * relevant if the goal is run from the command line or from the default + * build lifecycle. If the goal is run indirectly as part of a site + * generation, the output directory configured in the Maven Site Plugin is + * used instead. + * + * @parameter + * default-value="${project.reporting.outputDirectory}/jacoco-aggregate" + */ + private File outputDirectory; + + /** + * The projects in the reactor. + * + * @parameter property="reactorProjects" + * @readonly + */ + private List<MavenProject> reactorProjects; + + @Override + boolean canGenerateReportRegardingDataFiles() { + return true; + } + + @Override + boolean canGenerateReportRegardingClassesDirectory() { + return true; + } + + @Override + void loadExecutionData(final ReportSupport support) throws IOException { + final FileFilter filter = new FileFilter(dataFileIncludes, + dataFileExcludes); + for (final MavenProject dependency : findDependencies( + Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST)) { + for (final File execFile : filter.getFiles(dependency.getBasedir())) { + support.loadExecutionData(execFile); + } + } + } + + @Override + void addFormatters(final ReportSupport support, final Locale locale) + throws IOException { + support.addAllFormatters(outputDirectory, outputEncoding, footer, + locale); + } + + @Override + void createReport(final IReportGroupVisitor visitor, + final ReportSupport support) throws IOException { + final IReportGroupVisitor group = visitor.visitGroup(title); + for (final MavenProject dependency : findDependencies(Artifact.SCOPE_COMPILE)) { + support.processProject(group, dependency.getArtifactId(), + dependency, getIncludes(), getExcludes(), sourceEncoding); + } + } + + @Override + protected String getOutputDirectory() { + return outputDirectory.getAbsolutePath(); + } + + @Override + public void setReportOutputDirectory(final File reportOutputDirectory) { + if (reportOutputDirectory != null + && !reportOutputDirectory.getAbsolutePath().endsWith( + "jacoco-aggregate")) { + outputDirectory = new File(reportOutputDirectory, + "jacoco-aggregate"); + } else { + outputDirectory = reportOutputDirectory; + } + } + + public String getOutputName() { + return "jacoco-aggregate/index"; + } + + public String getName(final Locale locale) { + return "JaCoCo Aggregate"; + } + + private List<MavenProject> findDependencies(final String... scopes) { + final List<MavenProject> result = new ArrayList<MavenProject>(); + final List<String> scopeList = Arrays.asList(scopes); + for (final Object dependencyObject : getProject().getDependencies()) { + final Dependency dependency = (Dependency) dependencyObject; + if (scopeList.contains(dependency.getScope())) { + final MavenProject project = findProjectFromReactor(dependency); + if (project != null) { + result.add(project); + } + } + } + return result; + } + + private MavenProject findProjectFromReactor(final Dependency d) { + for (final MavenProject p : reactorProjects) { + if (p.getGroupId().equals(d.getGroupId()) + && p.getArtifactId().equals(d.getArtifactId()) + && p.getVersion().equals(d.getVersion())) { + return p; + } + } + return null; + } + +} diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/ReportITMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/ReportITMojo.java index 9d8ef271..e0b0217c 100644 --- a/jacoco-maven-plugin/src/org/jacoco/maven/ReportITMojo.java +++ b/jacoco-maven-plugin/src/org/jacoco/maven/ReportITMojo.java @@ -13,8 +13,11 @@ package org.jacoco.maven; import java.io.File; +import java.io.IOException; import java.util.Locale; +import org.jacoco.report.IReportGroupVisitor; + /** * Same as <code>report</code>, but provides default values suitable for * integration-tests: @@ -50,6 +53,35 @@ public class ReportITMojo extends AbstractReportMojo { private File dataFile; @Override + boolean canGenerateReportRegardingDataFiles() { + return dataFile.exists(); + } + + @Override + boolean canGenerateReportRegardingClassesDirectory() { + return new File(getProject().getBuild().getOutputDirectory()).exists(); + } + + @Override + void loadExecutionData(final ReportSupport support) throws IOException { + support.loadExecutionData(dataFile); + } + + @Override + void addFormatters(final ReportSupport support, final Locale locale) + throws IOException { + support.addAllFormatters(outputDirectory, outputEncoding, footer, + locale); + } + + @Override + void createReport(final IReportGroupVisitor visitor, + final ReportSupport support) throws IOException { + support.processProject(visitor, title, getProject(), getIncludes(), + getExcludes(), sourceEncoding); + } + + @Override protected String getOutputDirectory() { return outputDirectory.getAbsolutePath(); } @@ -65,22 +97,10 @@ public class ReportITMojo extends AbstractReportMojo { } } - @Override - File getDataFile() { - return dataFile; - } - - @Override - File getOutputDirectoryFile() { - return outputDirectory; - } - - @Override public String getOutputName() { return "jacoco-it/index"; } - @Override public String getName(final Locale locale) { return "JaCoCo IT"; } diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java index f6abdaf2..23c80e4a 100644 --- a/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java +++ b/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java @@ -12,8 +12,11 @@ package org.jacoco.maven; import java.io.File; +import java.io.IOException; import java.util.Locale; +import org.jacoco.report.IReportGroupVisitor; + /** * Creates a code coverage report for tests of a single project in multiple * formats (HTML, XML, and CSV). @@ -45,6 +48,35 @@ public class ReportMojo extends AbstractReportMojo { private File dataFile; @Override + boolean canGenerateReportRegardingDataFiles() { + return dataFile.exists(); + } + + @Override + boolean canGenerateReportRegardingClassesDirectory() { + return new File(getProject().getBuild().getOutputDirectory()).exists(); + } + + @Override + void loadExecutionData(final ReportSupport support) throws IOException { + support.loadExecutionData(dataFile); + } + + @Override + void addFormatters(final ReportSupport support, final Locale locale) + throws IOException { + support.addAllFormatters(outputDirectory, outputEncoding, footer, + locale); + } + + @Override + void createReport(final IReportGroupVisitor visitor, + final ReportSupport support) throws IOException { + support.processProject(visitor, title, getProject(), getIncludes(), + getExcludes(), sourceEncoding); + } + + @Override protected String getOutputDirectory() { return outputDirectory.getAbsolutePath(); } @@ -59,23 +91,11 @@ public class ReportMojo extends AbstractReportMojo { } } - @Override - File getDataFile() { - return dataFile; - } - - @Override - File getOutputDirectoryFile() { - return outputDirectory; - } - - @Override public String getOutputName() { return "jacoco/index"; } - @Override public String getName(final Locale locale) { - return "JaCoCo Test"; + return "JaCoCo"; } } diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/ReportSupport.java b/jacoco-maven-plugin/src/org/jacoco/maven/ReportSupport.java new file mode 100644 index 00000000..62819778 --- /dev/null +++ b/jacoco-maven-plugin/src/org/jacoco/maven/ReportSupport.java @@ -0,0 +1,294 @@ +/******************************************************************************* + * Copyright (c) 2009, 2016 Mountainminds GmbH & Co. KG and Contributors + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Evgeny Mandrikov - initial API and implementation + * Kyle Lieber - implementation of CheckMojo + * + *******************************************************************************/ +package org.jacoco.maven; + +import static java.lang.String.format; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.jacoco.core.analysis.Analyzer; +import org.jacoco.core.analysis.CoverageBuilder; +import org.jacoco.core.analysis.IBundleCoverage; +import org.jacoco.core.analysis.IClassCoverage; +import org.jacoco.core.tools.ExecFileLoader; +import org.jacoco.report.FileMultiReportOutput; +import org.jacoco.report.IReportGroupVisitor; +import org.jacoco.report.IReportVisitor; +import org.jacoco.report.ISourceFileLocator; +import org.jacoco.report.MultiReportVisitor; +import org.jacoco.report.check.IViolationsOutput; +import org.jacoco.report.check.Rule; +import org.jacoco.report.check.RulesChecker; +import org.jacoco.report.csv.CSVFormatter; +import org.jacoco.report.html.HTMLFormatter; +import org.jacoco.report.xml.XMLFormatter; + +/** + * Encapsulates the tasks to create reports for Maven projects. Instances are + * supposed to be used in the following sequence: + * + * <ol> + * <li>Create an instance</li> + * <li>Load one or multiple exec files with <code>loadExecutionData()</code></li> + * <li>Add one ore multiple formatters with <code>addXXX()</code> methods</li> + * <li>Create the root visitor with <code>initRootVisitor()</code></li> + * <li>Process one or multiple projects with <code>processProject()</code></li> + * </ol> + */ +final class ReportSupport { + + private final Log log; + private final ExecFileLoader loader; + private final List<IReportVisitor> formatters; + + /** + * Construct a new instance with the given log output. + * + * @param log + * for log output + */ + public ReportSupport(final Log log) { + this.log = log; + this.loader = new ExecFileLoader(); + this.formatters = new ArrayList<IReportVisitor>(); + } + + /** + * Loads the given execution data file. + * + * @param execFile + * execution data file to load + * @throws IOException + * if the file can't be loaded + */ + public void loadExecutionData(final File execFile) throws IOException { + log.info("Loading execution data file " + execFile); + loader.load(execFile); + } + + public void addXmlFormatter(final File targetfile, final String encoding) + throws IOException { + final XMLFormatter xml = new XMLFormatter(); + xml.setOutputEncoding(encoding); + formatters.add(xml.createVisitor(new FileOutputStream(targetfile))); + } + + public void addCsvFormatter(final File targetfile, final String encoding) + throws IOException { + final CSVFormatter csv = new CSVFormatter(); + csv.setOutputEncoding(encoding); + formatters.add(csv.createVisitor(new FileOutputStream(targetfile))); + } + + public void addHtmlFormatter(final File targetdir, final String encoding, + final String footer, final Locale locale) throws IOException { + final HTMLFormatter htmlFormatter = new HTMLFormatter(); + htmlFormatter.setOutputEncoding(encoding); + htmlFormatter.setLocale(locale); + if (footer != null) { + htmlFormatter.setFooterText(footer); + } + formatters.add(htmlFormatter.createVisitor(new FileMultiReportOutput( + targetdir))); + } + + public void addAllFormatters(final File targetdir, final String encoding, + final String footer, final Locale locale) throws IOException { + targetdir.mkdirs(); + addXmlFormatter(new File(targetdir, "jacoco.xml"), encoding); + addCsvFormatter(new File(targetdir, "jacoco.csv"), encoding); + addHtmlFormatter(targetdir, encoding, footer, locale); + } + + public void addRulesChecker(final List<Rule> rules, + final IViolationsOutput output) { + final RulesChecker checker = new RulesChecker(); + checker.setRules(rules); + formatters.add(checker.createVisitor(output)); + } + + public IReportVisitor initRootVisitor() throws IOException { + final IReportVisitor visitor = new MultiReportVisitor(formatters); + visitor.visitInfo(loader.getSessionInfoStore().getInfos(), loader + .getExecutionDataStore().getContents()); + return visitor; + } + + /** + * Calculates coverage for the given project and emits it to the report + * group without source references + * + * @param visitor + * group visitor to emit the project's coverage to + * @param project + * the MavenProject + * @param includes + * list of includes patterns + * @param excludes + * list of excludes patterns + * @throws IOException + * if class files can't be read + */ + public void processProject(final IReportGroupVisitor visitor, + final MavenProject project, final List<String> includes, + final List<String> excludes) throws IOException { + processProject(visitor, project.getArtifactId(), project, includes, + excludes, new NoSourceLocator()); + } + + /** + * Calculates coverage for the given project and emits it to the report + * group including source references + * + * @param visitor + * group visitor to emit the project's coverage to + * @param bundeName + * name for this project in the report + * @param project + * the MavenProject + * @param includes + * list of includes patterns + * @param excludes + * list of excludes patterns + * @param srcEncoding + * encoding of the source files within this project + * @throws IOException + * if class files can't be read + */ + public void processProject(final IReportGroupVisitor visitor, + final String bundeName, final MavenProject project, + final List<String> includes, final List<String> excludes, + final String srcEncoding) throws IOException { + processProject(visitor, bundeName, project, includes, excludes, + new SourceFileCollection(project, srcEncoding)); + } + + private void processProject(final IReportGroupVisitor visitor, + final String bundeName, final MavenProject project, + final List<String> includes, final List<String> excludes, + final ISourceFileLocator locator) throws IOException { + final CoverageBuilder builder = new CoverageBuilder(); + final File classesDir = new File(project.getBuild() + .getOutputDirectory()); + + if (classesDir.isDirectory()) { + final Analyzer analyzer = new Analyzer( + loader.getExecutionDataStore(), builder); + final FileFilter filter = new FileFilter(includes, excludes); + for (final File file : filter.getFiles(classesDir)) { + analyzer.analyzeAll(file); + } + } + + final IBundleCoverage bundle = builder.getBundle(bundeName); + logBundleInfo(bundle, builder.getNoMatchClasses()); + + visitor.visitBundle(bundle, locator); + } + + private void logBundleInfo(final IBundleCoverage bundle, + final Collection<IClassCoverage> nomatch) { + log.info(format("Analyzed bundle '%s' with %s classes", + bundle.getName(), + Integer.valueOf(bundle.getClassCounter().getTotalCount()))); + if (!nomatch.isEmpty()) { + log.warn(format( + "Classes in bundle '%s' do no match with execution data. " + + "For report generation the same class files must be used as at runtime.", + bundle.getName())); + for (final IClassCoverage c : nomatch) { + log.warn(format("Execution data for class %s does not match.", + c.getName())); + } + } + if (bundle.getClassCounter().getTotalCount() > 0 + && bundle.getLineCounter().getTotalCount() == 0) { + log.warn("To enable source code annotation class files have to be compiled with debug information."); + } + } + + private class NoSourceLocator implements ISourceFileLocator { + + public Reader getSourceFile(final String packageName, + final String fileName) { + return null; + } + + public int getTabWidth() { + return 0; + } + } + + private class SourceFileCollection implements ISourceFileLocator { + + private final List<File> sourceRoots; + private final String encoding; + + public SourceFileCollection(final MavenProject project, + final String encoding) { + this.sourceRoots = getCompileSourceRoots(project); + this.encoding = encoding; + } + + public Reader getSourceFile(final String packageName, + final String fileName) throws IOException { + final String r; + if (packageName.length() > 0) { + r = packageName + '/' + fileName; + } else { + r = fileName; + } + for (final File sourceRoot : sourceRoots) { + final File file = new File(sourceRoot, r); + if (file.exists() && file.isFile()) { + return new InputStreamReader(new FileInputStream(file), + encoding); + } + } + return null; + } + + public int getTabWidth() { + return 4; + } + } + + private static List<File> getCompileSourceRoots(final MavenProject project) { + final List<File> result = new ArrayList<File>(); + for (final Object path : project.getCompileSourceRoots()) { + result.add(resolvePath(project, (String) path)); + } + return result; + } + + private static File resolvePath(final MavenProject project, + final String path) { + File file = new File(path); + if (!file.isAbsolute()) { + file = new File(project.getBasedir(), path); + } + return file; + } + +} |