Skip to content

Instantly share code, notes, and snippets.

@KetothXupack
Created January 10, 2013 08:19
Show Gist options
  • Save KetothXupack/4500400 to your computer and use it in GitHub Desktop.
Save KetothXupack/4500400 to your computer and use it in GitHub Desktop.
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java
index 06ef04e..68834c8 100644
--- a/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java
+++ b/jacoco-maven-plugin/src/org/jacoco/maven/ReportMojo.java
@@ -7,21 +7,11 @@
*
* Contributors:
* Evgeny Mandrikov - initial API and implementation
- * Kyle Lieber - implementation of CheckMojo
- *
+ * Kyle Lieber - implementation of CheckMojo
+ * Ketoth Xupack - data files merging & report aggregation
*******************************************************************************/
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;
-
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
@@ -30,8 +20,6 @@ import org.apache.maven.reporting.MavenReportException;
import org.jacoco.core.analysis.IBundleCoverage;
import org.jacoco.core.analysis.ICoverageNode;
import org.jacoco.core.data.ExecFileLoader;
-import org.jacoco.core.data.ExecutionDataStore;
-import org.jacoco.core.data.SessionInfoStore;
import org.jacoco.report.FileMultiReportOutput;
import org.jacoco.report.IReportGroupVisitor;
import org.jacoco.report.IReportVisitor;
@@ -41,10 +29,21 @@ import org.jacoco.report.csv.CSVFormatter;
import org.jacoco.report.html.HTMLFormatter;
import org.jacoco.report.xml.XMLFormatter;
+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;
+
/**
* Creates a code coverage report for a single project in multiple formats
* (HTML, XML, and CSV).
- *
+ *
+ * @requiresDependencyResolution compile
* @goal report
* @requiresProject true
* @threadSafe
@@ -57,14 +56,14 @@ public class ReportMojo extends AbstractMavenReport {
* 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"
*/
private File outputDirectory;
/**
* Encoding of the generated reports.
- *
+ *
* @parameter expression="${project.reporting.outputEncoding}"
* default-value="UTF-8"
*/
@@ -72,7 +71,7 @@ public class ReportMojo extends AbstractMavenReport {
/**
* Encoding of the source files.
- *
+ *
* @parameter expression="${project.build.sourceEncoding}"
* default-value="UTF-8"
*/
@@ -80,15 +79,50 @@ public class ReportMojo extends AbstractMavenReport {
/**
* File with execution data.
- *
+ *
* @parameter default-value="${project.build.directory}/jacoco.exec"
*/
private File dataFile;
/**
+ * File with execution data.
+ *
+ * @parameter
+ */
+ private String[] dataFiles;
+
+ /**
+ * A list of data files to aggregate.
+ *
+ * @parameter default-value=""
+ */
+ private String postfix;
+
+ /**
+ * {@code true} to fail on missing data files in {@code dataFiles} list.
+ *
+ * @parameter
+ */
+ private boolean strict = false;
+
+ /**
+ * Do not produce report for each module in multimodule project.
+ *
+ * @parameter
+ */
+ private boolean skipModule = false;
+
+ /**
+ * Flg for aggregating each module report in multimodule environment.
+ *
+ * @parameter
+ */
+ private boolean aggregate = true;
+
+ /**
* A list of class files to include in the report. May use wildcard
* characters (* and ?). When not specified everything will be included.
- *
+ *
* @parameter
*/
private List<String> includes;
@@ -96,49 +130,66 @@ public class ReportMojo extends AbstractMavenReport {
/**
* A list of class files to exclude from the report. May use wildcard
* characters (* and ?). When not specified nothing will be excluded.
- *
+ *
* @parameter
*/
private List<String> excludes;
/**
* Flag used to suppress execution.
- *
+ *
* @parameter expression="${jacoco.skip}" default-value="false"
*/
private boolean skip;
/**
* Maven project.
- *
+ *
* @parameter expression="${project}"
* @readonly
*/
private MavenProject project;
/**
+ * The projects in the reactor for aggregation report.
+ *
+ * @parameter expression="${reactorProjects}"
+ * @readonly
+ */
+ private List<MavenProject> reactorProjects;
+
+ /**
* Doxia Site Renderer.
- *
+ *
* @component
*/
private Renderer siteRenderer;
- private SessionInfoStore sessionInfoStore;
-
- private ExecutionDataStore executionDataStore;
-
public String getOutputName() {
- return "jacoco/index";
+ return getRelative() + "/index";
+ }
+
+ private String getRelative() {
+ return new File(getProject().getReporting().getOutputDirectory())
+ .toURI().relativize(outputDirectory.toURI()).getPath();
}
public String getName(final Locale locale) {
- return "JaCoCo";
+ return doPostfix("JaCoCo");
}
public String getDescription(final Locale locale) {
return "JaCoCo Test Coverage Report.";
}
+ private String doPostfix(final String string) {
+ if (postfix == null || "".equals(postfix)) {
+ return string;
+ }
+
+ return string + " (" + postfix + ")";
+ }
+
@Override
public boolean isExternalReport() {
return true;
@@ -161,7 +212,7 @@ public class ReportMojo extends AbstractMavenReport {
/**
* Returns the list of class files to include in the report.
- *
+ *
* @return class files to include, may contain wildcard characters
*/
protected List<String> getIncludes() {
@@ -170,7 +221,7 @@ public class ReportMojo extends AbstractMavenReport {
/**
* Returns the list of class files to exclude from the report.
- *
+ *
* @return class files to exclude, may contain wildcard characters
*/
protected List<String> getExcludes() {
@@ -179,9 +230,8 @@ public class ReportMojo extends AbstractMavenReport {
@Override
public void setReportOutputDirectory(final File reportOutputDirectory) {
- if (reportOutputDirectory != null
- && !reportOutputDirectory.getAbsolutePath().endsWith("jacoco")) {
- outputDirectory = new File(reportOutputDirectory, "jacoco");
+ if (reportOutputDirectory != null) {
+ outputDirectory = new File(reportOutputDirectory, getRelative());
} else {
outputDirectory = reportOutputDirectory;
}
@@ -189,20 +239,23 @@ public class ReportMojo extends AbstractMavenReport {
@Override
public boolean canGenerateReport() {
- if ("pom".equals(project.getPackaging())) {
- getLog().info(
- "Skipping JaCoCo for project with packaging type 'pom'");
- return false;
- }
if (skip) {
getLog().info("Skipping JaCoCo execution");
return false;
}
- if (!dataFile.exists()) {
- getLog().info(
- "Skipping JaCoCo execution due to missing execution data file");
+
+ // fallback to old behavior
+ if (dataFiles == null) {
+ skipModule = false;
+ strict = true;
+ aggregate = true;
+ }
+
+ if (dataFiles == null && dataFile != null && !dataFile.exists()) {
+ getLog().warn("Skipping JaCoCo execution due to missing execution data file");
return false;
}
+
return true;
}
@@ -223,47 +276,129 @@ public class ReportMojo extends AbstractMavenReport {
}
}
+ private String[] getDataFiles() {
+ if (dataFiles == null && dataFile != null) {
+ return new String[] {dataFile.getPath()};
+ }
+
+ return dataFiles;
+ }
+
@Override
protected void executeReport(final Locale locale)
throws MavenReportException {
- loadExecutionData();
try {
- final IReportVisitor visitor = createVisitor(locale);
- visitor.visitInfo(sessionInfoStore.getInfos(),
- executionDataStore.getContents());
- createReport(visitor);
- visitor.visitEnd();
+ executeReport(locale, false);
+ if (getProject() != getLastProject()) {
+ executeReport(locale, true);
+ }
} catch (final IOException e) {
- throw new MavenReportException("Error while creating report: "
- + e.getMessage(), e);
+ throw new MavenReportException("Error while creating report: " + e.getMessage(), e);
}
}
- private void loadExecutionData() throws MavenReportException {
+ private void executeReport(final Locale locale, final boolean root)
+ throws IOException, MavenReportException {
+
+ final boolean exec = root ? aggregate : !skipModule;
+ if (!exec) {
+ return;
+ }
+
+ final IReportVisitor mainVisitor = createVisitor(locale, outputDirectory);
+ boolean visited = false;
+
+ final ExecFileLoader loader = loadExecutionData(root);
+ mainVisitor.visitInfo(loader.getSessionInfoStore().getInfos(),
+ loader.getExecutionDataStore().getContents());
+
+ if (root) {
+ final IReportGroupVisitor subProjectVisitor = mainVisitor.visitGroup(
+ getRootProject().getName());
+
+ for (final MavenProject child : reactorProjects) {
+ visited |= visitProject(loader, subProjectVisitor, child);
+ }
+ } else {
+ visited = visitProject(loader, mainVisitor, getProject());
+ }
+
+ if (visited) {
+ mainVisitor.visitEnd();
+ }
+ }
+
+ private MavenProject getRootProject() {
+ final MavenProject lastProject = reactorProjects.get(0);
+ if (!lastProject.isExecutionRoot()) {
+ throw new IllegalStateException("");
+ }
+
+ return lastProject;
+ }
+
+ private MavenProject getLastProject() {
+ final int size = reactorProjects.size();
+ return reactorProjects.get(size - 1);
+ }
+
+ private void loadExecFile(final ExecFileLoader loader, final MavenProject project)
+ throws IOException, MavenReportException {
+ for (final String dataFile : getDataFiles()) {
+ final File file = resolvePath(project, dataFile);
+ if (file.exists()) {
+ loader.load(file);
+ } else {
+ if (strict) {
+ throw new MavenReportException("File " + file +" exists");
+ }
+ }
+ }
+ }
+
+ private ExecFileLoader loadExecutionData(final boolean root)
+ throws MavenReportException {
final ExecFileLoader loader = new ExecFileLoader();
try {
- loader.load(dataFile);
+ if (root) {
+ for (final MavenProject project : reactorProjects) {
+ loadExecFile(loader, project);
+ }
+ } else {
+ loadExecFile(loader, getProject());
+ }
} catch (final IOException e) {
- throw new MavenReportException(
- "Unable to read execution data file " + dataFile + ": "
- + e.getMessage(), e);
+ throw new MavenReportException("Unable to read execution data file: " + e.getMessage(), e);
}
- sessionInfoStore = loader.getSessionInfoStore();
- executionDataStore = loader.getExecutionDataStore();
+
+ return loader;
}
- private void createReport(final IReportGroupVisitor visitor)
+ /**
+ * @return {@code true} if project was actually visited
+ * @throws IOException
+ */
+ private boolean visitProject(final ExecFileLoader loader, final IReportGroupVisitor visitor,
+ final MavenProject project)
throws IOException {
- final FileFilter fileFilter = new FileFilter(this.getIncludes(),
- this.getExcludes());
- final BundleCreator creator = new BundleCreator(this.getProject(),
- fileFilter);
- final IBundleCoverage bundle = creator.createBundle(executionDataStore);
+
+ // skip processing modules with "pom" packaging
+ if ("pom".equals(project.getPackaging())) {
+ return false;
+ }
+
+ final FileFilter fileFilter = new FileFilter(this.getIncludes(), this.getExcludes());
+ final BundleCreator creator = new BundleCreator(project, fileFilter);
+ final IBundleCoverage bundle = creator.createBundle(loader.getExecutionDataStore());
final SourceFileCollection locator = new SourceFileCollection(
- getCompileSourceRoots(), sourceEncoding);
+ getCompileSourceRoots(project),
+ sourceEncoding);
+
checkForMissingDebugInformation(bundle);
visitor.visitBundle(bundle, locator);
+
+ return true;
}
private void checkForMissingDebugInformation(final ICoverageNode node) {
@@ -274,7 +409,7 @@ public class ReportMojo extends AbstractMavenReport {
}
}
- private IReportVisitor createVisitor(final Locale locale)
+ private IReportVisitor createVisitor(final Locale locale, final File outputDirectory)
throws IOException {
final List<IReportVisitor> visitors = new ArrayList<IReportVisitor>();
@@ -313,7 +448,7 @@ public class ReportMojo extends AbstractMavenReport {
public Reader getSourceFile(final String packageName,
final String fileName) throws IOException {
final String r;
- if (packageName.length() > 0) {
+ if (!packageName.isEmpty()) {
r = packageName + '/' + fileName;
} else {
r = fileName;
@@ -333,18 +468,18 @@ public class ReportMojo extends AbstractMavenReport {
}
}
- private File resolvePath(final String path) {
+ private static File resolvePath(final MavenProject project, final String path) {
File file = new File(path);
if (!file.isAbsolute()) {
- file = new File(getProject().getBasedir(), path);
+ file = new File(project.getBasedir(), path);
}
return file;
}
- private List<File> getCompileSourceRoots() {
+ private static List<File> getCompileSourceRoots(final MavenProject project) {
final List<File> result = new ArrayList<File>();
- for (final Object path : getProject().getCompileSourceRoots()) {
- result.add(resolvePath((String) path));
+ for (final Object path : project.getCompileSourceRoots()) {
+ result.add(resolvePath(project, (String) path));
}
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment