com.atlassian.maven.plugin.clover.internal.AbstractCloverInstrumentMojo Maven / Gradle / Ivy
package com.atlassian.maven.plugin.clover.internal;
import clover.org.apache.commons.lang3.StringUtils;
import com.atlassian.clover.util.IOStreamUtils;
import com.atlassian.maven.plugin.clover.DistributedCoverage;
import com.atlassian.maven.plugin.clover.MethodWithMetricsContext;
import com.atlassian.maven.plugin.clover.TestSources;
import com.atlassian.maven.plugin.clover.internal.lifecycle.BuildLifecycleAnalyzer;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
/**
* Common settings for clover:instr / clover:setup MOJOs.
*/
public abstract class AbstractCloverInstrumentMojo extends AbstractCloverMojo implements CompilerConfiguration {
/**
* The difference (in milliseconds) that a -clover classified artifact can have to a non-clover classified artifact.
* If the -clover classified artifact is more than cloveredArtifactExpiryInMillis older than the non-clover classified
* artifact, then the non-classified artifact will be used.
* This setting defaults to 2000.
*/
@Parameter(property = "maven.clover.cloveredArtifactExpiryInMillis", defaultValue = "2000")
protected long cloveredArtifactExpiryInMillis;
/**
* If set, then the clover-maven-plugin will not copy files that were excluded, across to the target/clover directory.
* This is useful if the build is also using plugins such as the maven-gwt-plugin, that scans for resources, and
* skips a step if none are found. Otherwise, setting this to false could well cause build failures.
*/
@Parameter(property = "maven.clover.copyExcludedFiles", defaultValue = "true")
protected boolean copyExcludedFiles = true;
/**
* The configuration for distributed coverage collection by Clover.
* If present, default values will be used and coverage will be collected across JVMs.
* Optional nested elements (and their defaults) of distributedCoverage are:
*
* - host - the host name of the JVM running the tests. default: localhost
* - port - the port that Clover can bind to in the host JVM. default: 1198
* - numClients - the number of clients expected to attach to the Test JVM. The test JVM will wait until numClients
* have connected before continuing. default: 0
* - timeout - the amount of time to wait for a response from a remote JVM before shunning it. default: 5000
* - retryPeriod - the amount of time a client should wait between reconnect attempts. default: 1000
*
*/
@Parameter
protected DistributedCoverage distributedCoverage;
/**
* The character encoding to use when parsing source files.
*/
@Parameter(property = "maven.clover.encoding")
protected String encoding;
/**
* The list of file to exclude from the instrumentation. Patterns are resolved against source roots.
*/
@Parameter
protected Set excludes = new HashSet();
/**
* The comma seperated list of file to exclude from the instrumentation. Patterns are resolved against source roots.
*/
@Parameter(property = "maven.clover.excludesList")
protected String excludesList = null;
/**
* The file containing a list of file paths, separated by new line, to exclude from the instrumentation. Patterns are resolved against source roots.
* See also {@link #excludes} and {@link #excludesList}
*/
@Parameter(property = "maven.clover.excludesFile")
protected String excludesFile = null;
/**
* The Clover flush policy to use.
* Valid values are directed
, interval
and threaded
.
*/
@Parameter(property = "maven.clover.flushPolicy", defaultValue = "threaded")
protected String flushPolicy;
/**
* By default, Clover Maven Plugin generates the ${java.io.tmpdir}/grover*.jar
file during setup,
* which is next being added as the dependent artifact to the build. As the file has generated, unique
* name and the jar is not being removed at the end of the build, these files can litter the temporary
* directory.
* By setting this parameter you can:
* a) specify constant file name for generated artifact,
* b) choose location different than ${java.io.tmpdir}.
* However, you must ensure that:
* a) grover.jar will not be deleted till end of the build (for example don't put into ./target directory
* and next run mvn clover:setup clean
)
* b) grover.jar will not be shared among builds with different Clover Maven Plugin versions used (for
* example if ProjectA uses Clover v 3.1.8 and ProjectB uses Clover v 3.1.9 then they shall have different
* groverJar
locations defined)
*
* @since 3.1.8
*/
@Parameter(property = "maven.clover.groverJar")
protected File groverJar;
/**
* The list of file to include in the instrumentation. Patterns are resolved against source roots.
* Defaults are '**/*.java, **/*.groovy' which are overwritten if <includes> is set by the user
*/
@Parameter
protected Set includes = new HashSet(Arrays.asList(new String[]{"**/*.java", "**/*.groovy"}));
/**
* The comma seperated list of files to include in the instrumentation. Patterns are resolved against source roots.
* Defaults are **.java which are overwritten if <includes> is set by the user
*/
@Parameter(property = "maven.clover.includesList")
protected String includesList = null;
/**
* The file containing a list of file paths, separated by new line, to include in the instrumentation. Patterns are resolved against source roots.
* See also {@link #includes} and {@link #includesList}
*/
@Parameter(property = "maven.clover.includesFile")
protected String includesFile = null;
/**
* Till 3.1.11: whether the Clover plugin should instrument all source roots (for example
* src/main/java, src/main/groovy, target/generated-sources
, so including the generated sources)
* or whether it should only instrument the main source root (usually src/main/java
).
* Since 3.1.12: whether the Clover plugin should instrument all source roots (for example
* src/main/java, src/main/groovy, target/generated-sources
, so including the generated sources)
* or whether it should instrument non-generated source roots (i.e. all roots except target/generated-sources/*
)
*/
@Parameter(property = "maven.clover.includesAllSourceRoots", defaultValue = "false")
protected boolean includesAllSourceRoots;
/**
* Whether the Clover plugin should instrument test source roots.
*/
@Parameter(property = "maven.clover.includesTestSourceRoots", defaultValue = "true")
protected boolean includesTestSourceRoots;
/**
* The level to instrument to. Valid values are 'method' or 'statement'. Default is 'statement'.
* Setting this to 'method' greatly reduces the overhead of enabling Clover, however limited or no reporting is
* available. The current use of setting this to method is for Test Optimization only.
*/
@Parameter(property = "maven.clover.instrumentation", defaultValue = "statement")
protected String instrumentation;
/**
* Define whether lambda functions shall be instrumented: Valid values are:
*
* - none - do not instrument lambda functions (note: statements inside lambdas will become a part of a parent function)
* - expression - instrument only expression-like lambdas, e.g.
(a,b) -> a + b
* - block - instrument block lambdas, e.g.
() -> { foo(); }
* - all_but_reference - instrument lambdas written in any form except method references, e.g.
Math::abs
* - all - instrument all forms of lambda functions
*
* Default is 'all' for 3.2.2-4.0.2 and 'none' since 4.0.3.
* IMPORTANT: Due to Clover's restrictions related with code instrumentation and javac compiler's type inference
* capabilities, you may get compilation errors when expression-like lambda functions are passed to generic methods
* or types. In such case disable instrumentation of expression-like form (i.e. use the 'none' or 'block' setting).
* See the
* Java 8 code instrumented by Clover fails to compile Knowledge Base article for more details.
*
*
* @since 3.2.2
*/
@Parameter(property = "maven.clover.instrumentLambda", defaultValue = "none")
private String instrumentLambda;
/**
* Which Java language level Clover shall use to parse sources. Valid values are:
*
* - 1.7 (String in switch, try with resources, binary literals, underscores in literals)
* - 1.8 (lambda expressions, default methods in interfaces)
* - 9 / 1.9 (module-info.java)
*
* By default Clover instruments using the highest language level supported.
*/
@Parameter(property = "maven.clover.jdk")
protected String jdk;
/**
* Specifies the custom method contexts to use for filtering specific methods from Clover reports.
* e.g. <main>public static void main\(String args\[\]\).*</main>
* will define the context called 'main' which will match all public static void main methods.
*/
@Parameter
protected Map methodContexts = new HashMap();
/**
* Specifies the custom method contexts to use for filtering specific methods from Clover reports.
* This is more detailed format compared to methodContexts, which allows to set also code metrics to be
* matched. Example:
*
* <methodWithMetricsContexts>
* <methodWithMetricsContext>
* <name>simpleGetter</name> <!-- (mandatory) -->
* <regexp>public .* get.*\(\)</regexp> <!-- (mandatory) -->
* <maxComplexity>1</maxComplexity> <!-- at most 1 cycle (optional) -->
* <maxStatements>1</maxStatements> <!-- at most 1 statement (optional) -->
* <maxAggregatedComplexity>2</maxAggregatedComplexity> <!-- no more than 2 cycles including inline classes (optional) -->
* <maxAggregatedStatements>10</maxAggregatedStatements> <!-- no more than 10 statements including inline classes (optional) -->
* </methodWithMetricsContext>
* <!-- can add more methodWithMetricsContext -->
* </methodWithMetricsContexts>
*
* will define a context called 'simpleGetter' which matches all public getXyz() methods containing at most one
* statement; this statement may contain more complex logic (an anonymous inline class) but not bigger than 9
* statements.
*/
@Parameter
protected Set methodWithMetricsContexts = new HashSet();
/**
* If set to 'false', test results will not be recorded; instead, results can be added via the
* <testResults> fileset at report time. Useful when a test uses a custom
* Rule expecting an exception, which OpenClover cannot recognize.
*/
@Parameter(property = "maven.clover.recordTestResults", defaultValue = "true")
protected boolean recordTestResults = true;
/**
* Try to protect your build from installing instrumented artifacts into local ~/.m2 cache
* or deploying them to a binaries repository. If this option is enabled, Clover will fail a build whenever
* it detects that 'install' or 'deploy' phase is about to be called. It will also fail a build if
* it detects that an artifact having multiple classifiers (e.g. "-clover-tests.jar"), which are not supported by
* Maven, is about to be installed under original name (e.g. "-tests.jar").
* Please note that this flag may not protect from all possible cases.
*
* @since 4.0.4
*/
@Parameter(property = "maven.clover.repositoryPollutionProtection", defaultValue = "false")
protected boolean repositoryPollutionProtection;
/**
* When creating the clover.jar dependency, what scope to use.
* This may be one of: compile, test, provided etc. If not specified - provided will be used.
*/
@Parameter(property = "maven.clover.scope")
protected String scope;
/**
* If set to true
, Clover will add several properties to the build configuration which
* disable a build failure for following plugins:
*
* - maven-surefire-plugin (maven.test.failure.ignore=true)
* - maven-failsafe-plugin (maven.test.failure.ignore=true)
* - maven-checkstyle-plugin (checkstyle.failOnViolation=false)
* - maven-pmd-plugin (pmd.failOnViolation=false)
*
* Thanks to this, build continues despite test failures or code validation failures and thus
* it is possible to generate a Clover coverage report for failed tests at the end of the build.
* Note: before version 3.1.9 the testFailureIgnore property was set to true for
* the forked Clover lifecycle ('instrument' goal) for 'test' and 'integration-test' phases. Since
* 3.1.9 it is no longer set.
*
* @since 3.1.9
*/
@Parameter(property = "maven.clover.setTestFailureIgnore", defaultValue = "false")
protected boolean setTestFailureIgnore;
/**
* By default, Clover Maven Plugin generates the ${java.io.tmpdir}/grover*.jar
file during setup,
* which is next being added as the dependent artifact to the build. As the file has generated, unique
* name and the jar is not being removed at the end of the build, these files can litter the temporary
* directory.
* In case when there is no Groovy code in the project, this parameter can be set to true
in order
* to disable generation of grover.jar artifact.
*
* @since 3.1.8
*/
@Parameter(property = "maven.clover.skipGroverJar", defaultValue = "false")
protected boolean skipGroverJar = false;
/**
* Specifies the custom statement contexts to use for filtering specific statements from Clover reports.
* e.g.<log>^LOG\..*</log>
* defines a statement context called "log" which matches all LOG statements.
*/
@Parameter
protected Map statementContexts = new HashMap();
/**
* Sets the granularity in milliseconds of the last modification date for testing whether a source needs reinstrumentation.
*/
@Parameter(property = "maven.clover.staleMillis", defaultValue = "0")
protected int staleMillis;
/**
* Specifies a custom test detector configuration. Useful in case your tests are not following JUnit/TestNG
* naming convention. Example:
*
*
* <testSources>
* <includes>
* <include>**/*</include>
* <include>*WebTest.java</include>
* <include>**/*IT.java</include>
* </includes>
* <excludes>
* <exclude>deprecated/**</exclude>
* </excludes>
* <testClasses>
* <testClass> <!-- 0..N occurrences -->
* <name>.*Test</name>
* <super>WebTest</super>
* <annotation>@Repeat</annotation>
* <package>org\.openclover\..*</package>
* <tag>@chrome</tag>
* <testMethods> <!-- 0..N occurrences -->
* <testMethod>
* <name>check.*</name>
* <annotation>@Test</annotation>
* <tag>@web</tag>
* <returnType>void</returnType>
* </testMethod>
* </testMethods>
* </testClass>
* </testClasses>
* </testSources>
*
*
* Note: every tag is optional.
*
* @since 4.4.0
*/
@Parameter
protected TestSources testSources;
/**
* Whether or not to include the -clover classifier on artifacts.
*/
@Parameter(property = "maven.clover.useCloverClassifier", defaultValue = "true")
protected boolean useCloverClassifier;
/**
* Use the fully qualified package name for java.lang.* classes.
*/
@Parameter(property = "maven.clover.useFullyQualifiedJavaLang", defaultValue = "true")
protected boolean useFullyQualifiedJavaLang;
///////////////////////////////////////////////////////////////////////////
/**
* Used to learn about lifecycles and phases
*/
@Component
private LifecycleExecutor lifecycleExecutor;
/**
* Used to learn about current build session.
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession mavenSession;
/**
*/
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject mavenProject;
///////////////////////////////////////////////////////////////////////////
@Override
public void execute() throws MojoExecutionException {
super.execute();
if (repositoryPollutionProtection) {
final BuildLifecycleAnalyzer lifecycleAnalyzer = new BuildLifecycleAnalyzer(
getLog(), lifecycleExecutor, mavenProject, mavenSession);
failIfDeployPhaseIsPresent(lifecycleAnalyzer);
failIfInstallPhaseIsPresent(lifecycleAnalyzer);
failIfCustomClassifierIsPresent();
}
}
protected abstract boolean shouldRedirectArtifacts();
protected abstract boolean shouldRedirectOutputDirectories();
@Override
public boolean isCopyExcludedFiles() {
return copyExcludedFiles;
}
@Override
public String getEncoding() {
return encoding;
}
@Override
public DistributedCoverage getDistributedCoverage() {
return distributedCoverage;
}
@Override
public Set getExcludes() {
if (excludesList == null && excludesFile == null) {
return excludes;
} else if (excludesFile != null) {
try {
return readPathPatternsFromFile(excludesFile);
} catch (IOException e) {
getLog().error("Could not read excludesFile: " + excludesFile, e);
return Collections.emptySet();
}
} else {
excludes.addAll(Arrays.asList(excludesList.split(",")));
return excludes;
}
}
@Override
public String getFlushPolicy() {
return this.flushPolicy;
}
@Override
public Set getIncludes() {
if (includesList == null && includesFile == null) {
return this.includes;
} else if (includesFile != null) {
try {
return readPathPatternsFromFile(includesFile);
} catch (IOException e) {
getLog().error("Could not read includesFile: " + includesFile, e);
return Collections.emptySet();
}
} else {
return new HashSet(Arrays.asList(includesList.split(",")));
}
}
@Override
public String getInstrumentation() {
return instrumentation;
}
@Override
public String getInstrumentLambda() {
return instrumentLambda;
}
@Override
public String getJdk() {
return this.jdk;
}
@Override
public Map getMethodContexts() {
return methodContexts;
}
@Override
public Set getMethodWithMetricsContexts() {
return methodWithMetricsContexts;
}
@Override
public Map getStatementContexts() {
return statementContexts;
}
@Override
public int getStaleMillis() {
return staleMillis;
}
@Override
public boolean isIncludesAllSourceRoots() {
return this.includesAllSourceRoots;
}
@Override
public boolean isUseFullyQualifiedJavaLang() {
return useFullyQualifiedJavaLang;
}
@Override
public TestSources getTestSources() {
return testSources;
}
@Override
public boolean isRecordTestResults() {
return recordTestResults;
}
private static final String PROTECTION_ENABLED_MSG = "Clover's repository pollution protection is enabled. ";
private static final String DISABLING_PROTECTION_MSG =
"You can also disable repository pollution protection (-Dmaven.clover.repositoryPollutionProtection=false) if this is intentional.";
/**
* Read list of file paths to exclude/include from file
*
* @param file path to external file with list of files to exclude/include separated by new line
* @return set of files to include/exclude
* @throws IOException if can't read external file
*/
private Set readPathPatternsFromFile(final String file) throws IOException {
Set files = new HashSet();
BufferedReader br = null;
try {
String line;
br = new BufferedReader(new FileReader(file));
while ((line = br.readLine()) != null) {
files.add(line);
}
} finally {
IOStreamUtils.close(br);
}
return files;
}
/**
* Check if the build life cycle contains the 'install' phase.
*
* @param lifecycleAnalyzer analyser
* @throws org.apache.maven.plugin.MojoExecutionException if 'install' phase is present
*/
protected void failIfInstallPhaseIsPresent(final BuildLifecycleAnalyzer lifecycleAnalyzer) throws MojoExecutionException {
if (lifecycleAnalyzer.isInstallPresent() && (!useCloverClassifier || !shouldRedirectArtifacts())) {
throw new MojoExecutionException(PROTECTION_ENABLED_MSG
+ "Your build runs 'install' phase which can put instrumented JARs into ~/.m2 local cache. "
+ "In order to fix this: \n"
+ " - run a build till the 'verify' phase (the latest)\n"
+ " - check if some build plug-in does not fork a parallel build cycle which runs till the 'install' phase\n"
+ DISABLING_PROTECTION_MSG);
}
}
/**
* Check if the build life cycle contains the 'deploy' phase.
*
* @param lifecycleAnalyzer analyser
* @throws org.apache.maven.plugin.MojoExecutionException if 'deploy' phase is present
*/
protected void failIfDeployPhaseIsPresent(final BuildLifecycleAnalyzer lifecycleAnalyzer) throws MojoExecutionException {
if (lifecycleAnalyzer.isDeployPresent() && (!useCloverClassifier || !shouldRedirectArtifacts())) {
throw new MojoExecutionException(PROTECTION_ENABLED_MSG
+ "Your build runs 'deploy' phase which can upload instrumented JARs into your repository. "
+ "In order to fix this: \n"
+ " - run a build till the 'verify' phase (the latest)\n"
+ " - check if some build plug-in does not fork a parallel build cycle which runs till the 'deploy' phase\n"
+ DISABLING_PROTECTION_MSG);
}
}
/**
* Check if an artifact has a custom classifier (except the 'javadoc' and 'sources' ones).
* If a custom classifier is present then adding a second 'clover' classifier may not work correctly
* as Maven does not support multiple classifiers.
*
* @throws org.apache.maven.plugin.MojoExecutionException if custom classifier is present
*/
protected void failIfCustomClassifierIsPresent() throws MojoExecutionException {
final String classifier = getProject().getArtifact().getClassifier();
final boolean customClassifierUsed = StringUtils.isNotEmpty(classifier)
&& !"javadoc".equals(classifier)
&& !"sources".equals(classifier);
if (customClassifierUsed && useCloverClassifier && shouldRedirectArtifacts()) {
throw new MojoExecutionException(PROTECTION_ENABLED_MSG
+ "Your build produces an artifact (" + getProject().getArtifact() + ") with a custom classifier. "
+ "As Maven does not support multiple "
+ "classifiers for an artifact, appending second 'clover' classifier may not be handled correctly. "
+ "You can: \n - remove a custom classifier or\n - configure Clover to not append the '-clover' classifier \n"
+ "to fix it. You can also disable pollution protection "
+ "(-Dmaven.clover.repositoryPollutionProtection=false) if you know "
+ "that it doesn't affect your build. ");
}
}
}