-
Notifications
You must be signed in to change notification settings - Fork 354
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
same as scm, but only for the tests class
- Loading branch information
Showing
1 changed file
with
316 additions
and
0 deletions.
There are no files selected for viewing
316 changes: 316 additions & 0 deletions
316
pitest-maven/src/main/java/org/pitest/maven/ScmTestMojo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
package org.pitest.maven; | ||
|
||
|
||
import java.io.File; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.LinkedHashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.function.Function; | ||
import java.util.function.Predicate; | ||
|
||
import org.apache.maven.artifact.Artifact; | ||
import org.apache.maven.plugin.MojoExecutionException; | ||
import org.apache.maven.plugins.annotations.Component; | ||
import org.apache.maven.plugins.annotations.LifecyclePhase; | ||
import org.apache.maven.plugins.annotations.Mojo; | ||
import org.apache.maven.plugins.annotations.Parameter; | ||
import org.apache.maven.plugins.annotations.ResolutionScope; | ||
import org.apache.maven.project.MavenProject; | ||
import org.apache.maven.scm.ChangeFile; | ||
import org.apache.maven.scm.ChangeSet; | ||
import org.apache.maven.scm.ScmBranch; | ||
import org.apache.maven.scm.ScmException; | ||
import org.apache.maven.scm.ScmFile; | ||
import org.apache.maven.scm.ScmFileSet; | ||
import org.apache.maven.scm.ScmFileStatus; | ||
import org.apache.maven.scm.command.changelog.ChangeLogScmRequest; | ||
import org.apache.maven.scm.command.changelog.ChangeLogScmResult; | ||
import org.apache.maven.scm.command.status.StatusScmResult; | ||
import org.apache.maven.scm.manager.ScmManager; | ||
import org.apache.maven.scm.repository.ScmRepository; | ||
import org.codehaus.plexus.util.StringUtils; | ||
import org.pitest.functional.FCollection; | ||
import java.util.Optional; | ||
import org.pitest.mutationtest.config.PluginServices; | ||
import org.pitest.mutationtest.config.ReportOptions; | ||
import org.pitest.mutationtest.tooling.CombinedStatistics; | ||
|
||
/** | ||
* Goal which runs a coverage mutation report only for test files that have been | ||
* modified or introduced locally based on the source control configured in | ||
* maven. | ||
*/ | ||
@Mojo(name = "scmTestMutationCoverage", | ||
defaultPhase = LifecyclePhase.VERIFY, | ||
requiresDependencyResolution = ResolutionScope.TEST, | ||
threadSafe = true) | ||
public class ScmTestMojo extends AbstractPitMojo { | ||
|
||
private static final int NO_LIMIT = -1; | ||
|
||
@Component | ||
private ScmManager manager; | ||
|
||
/** | ||
* List of scm status to include. Names match those defined by the maven scm | ||
* plugin. | ||
* | ||
* Common values include ADDED,MODIFIED (the defaults) & UNKNOWN. | ||
*/ | ||
@Parameter(property = "include") | ||
private HashSet<String> include; | ||
|
||
/** | ||
* Analyze last commit. If set to true analyzes last commited change set. | ||
*/ | ||
@Parameter(defaultValue = "false", property = "analyseLastCommit") | ||
private boolean analyseLastCommit; | ||
|
||
|
||
@Parameter(property = "originBranch") | ||
private String originBranch; | ||
|
||
@Parameter(property = "destinationBranch", defaultValue = "master") | ||
private String destinationBranch; | ||
|
||
/** | ||
* Connection type to use when querying scm for changed files. Can either be | ||
* "connection" or "developerConnection". | ||
*/ | ||
@Parameter(property = "connectionType", defaultValue = "connection") | ||
private String connectionType; | ||
|
||
/** | ||
* Project basedir | ||
*/ | ||
@Parameter(property = "basedir", required = true) | ||
private File basedir; | ||
|
||
/** | ||
* Base of scm root. For a multi module project this is probably the parent | ||
* project. | ||
*/ | ||
private File scmRootDir; | ||
|
||
public ScmTestMojo(final RunPitStrategy executionStrategy, | ||
final ScmManager manager, Predicate<Artifact> filter, | ||
PluginServices plugins, boolean analyseLastCommit, Predicate<MavenProject> nonEmptyProjectCheck) { | ||
super(executionStrategy, filter, plugins, nonEmptyProjectCheck); | ||
this.manager = manager; | ||
this.analyseLastCommit = analyseLastCommit; | ||
} | ||
|
||
public ScmTestMojo() { | ||
|
||
} | ||
|
||
@Override | ||
protected Optional<CombinedStatistics> analyse() throws MojoExecutionException { | ||
|
||
this.scmRootDir = findScmRootDir(); | ||
this.targetTests = makeConcreteList(findModifiedTestClassNames()); | ||
|
||
if (this.targetTests.isEmpty()) { | ||
this.getLog().info( | ||
"No modified test files found - nothing to mutation test, analyseLastCommit=" + this.analyseLastCommit); | ||
return Optional.empty(); | ||
} | ||
|
||
logClassNames(); | ||
defaultTargetTestsToGroupNameIfNoValueSet(); | ||
final ReportOptions data = new MojoToReportOptionsConverter(this, | ||
new SurefireConfigConverter(), filter).convert(); | ||
data.setFailWhenNoMutations(false); | ||
|
||
return Optional.ofNullable(this.goalStrategy.execute(detectBaseDir(), data, | ||
plugins, new HashMap<String, String>())); | ||
|
||
} | ||
|
||
private File findScmRootDir() { | ||
MavenProject rootProject = this.project; | ||
while (rootProject.hasParent() && rootProject.getParent().getBasedir() != null) | ||
rootProject = rootProject.getParent(); | ||
return rootProject.getBasedir(); | ||
} | ||
|
||
private void defaultTargetTestsToGroupNameIfNoValueSet() { | ||
if (this.getTargetTests() == null || this.getTargetTests().isEmpty()) { | ||
this.targetTests = makeConcreteList(Collections.singletonList(this | ||
.getProject().getGroupId() + "*")); | ||
} | ||
} | ||
|
||
private void logClassNames() { | ||
for (final String each : this.targetTests) { | ||
this.getLog().info("Will mutate changed tests " + each); | ||
} | ||
} | ||
|
||
private List<String> findModifiedTestClassNames() throws MojoExecutionException { | ||
|
||
final File testRoot = new File(this.project.getBuild() | ||
.getTestSourceDirectory()); | ||
|
||
final List<String> modifiedPaths = FCollection.map(findModifiedPaths(), pathByScmDir()); | ||
return FCollection.flatMap(modifiedPaths, new PathToJavaClassConverter( | ||
testRoot.getAbsolutePath())); | ||
|
||
} | ||
|
||
private Function<String,String> pathByScmDir() { | ||
|
||
return new Function<String, String>() { | ||
|
||
@Override | ||
public String apply(final String a) { | ||
return scmRootDir.getAbsolutePath() + "/" + a; | ||
} | ||
|
||
}; | ||
} | ||
|
||
private Set<String> findModifiedPaths() throws MojoExecutionException { | ||
try { | ||
final ScmRepository repository = this.manager | ||
.makeScmRepository(getSCMConnection()); | ||
final File scmRoot = scmRoot(); | ||
this.getLog().info("Scm root dir is " + scmRoot); | ||
|
||
final Set<ScmFileStatus> statusToInclude = makeStatusSet(); | ||
final Set<String> modifiedPaths; | ||
if (analyseLastCommit) { | ||
modifiedPaths = lastCommitChanges(statusToInclude, repository, scmRoot); | ||
} else if (originBranch != null && destinationBranch != null) { | ||
modifiedPaths = changesBetweenBranchs(originBranch, destinationBranch, statusToInclude, repository, scmRoot); | ||
} else { | ||
modifiedPaths = localChanges(statusToInclude, repository, scmRoot); | ||
} | ||
return modifiedPaths; | ||
} catch (final ScmException e) { | ||
throw new MojoExecutionException("Error while querying scm", e); | ||
} | ||
|
||
} | ||
|
||
private Set<String> lastCommitChanges(Set<ScmFileStatus> statusToInclude, ScmRepository repository, File scmRoot) throws ScmException { | ||
ChangeLogScmRequest scmRequest = new ChangeLogScmRequest(repository, new ScmFileSet(scmRoot)); | ||
scmRequest.setLimit(1); | ||
return pathsAffectedByChange(scmRequest, statusToInclude, 1); | ||
} | ||
|
||
private Set<String> changesBetweenBranchs(String origine, String destination, Set<ScmFileStatus> statusToInclude, ScmRepository repository, File scmRoot) throws ScmException { | ||
ChangeLogScmRequest scmRequest = new ChangeLogScmRequest(repository, new ScmFileSet(scmRoot)); | ||
scmRequest.setScmBranch(new ScmBranch(destination + ".." + origine)); | ||
return pathsAffectedByChange(scmRequest, statusToInclude, NO_LIMIT); | ||
} | ||
|
||
private Set<String> pathsAffectedByChange(ChangeLogScmRequest scmRequest, Set<ScmFileStatus> statusToInclude, int limit) throws ScmException { | ||
Set<String> affected = new LinkedHashSet<>(); | ||
ChangeLogScmResult changeLogScmResult = this.manager.changeLog(scmRequest); | ||
if (changeLogScmResult.isSuccess()) { | ||
List<ChangeSet> changeSets = limit(changeLogScmResult.getChangeLog().getChangeSets(),limit); | ||
for (ChangeSet change : changeSets) { | ||
List<ChangeFile> files = change.getFiles(); | ||
for (final ChangeFile changeFile : files) { | ||
if (statusToInclude.contains(changeFile.getAction())) { | ||
affected.add(changeFile.getName()); | ||
} | ||
} | ||
} | ||
} | ||
return affected; | ||
} | ||
|
||
|
||
private Set<String> localChanges(Set<ScmFileStatus> statusToInclude, ScmRepository repository, File scmRoot) throws ScmException { | ||
final StatusScmResult status = this.manager.status(repository, | ||
new ScmFileSet(scmRoot)); | ||
|
||
Set<String> affected = new LinkedHashSet<>(); | ||
for (final ScmFile file : status.getChangedFiles()) { | ||
if (statusToInclude.contains(file.getStatus())) { | ||
affected.add(file.getPath()); | ||
} | ||
} | ||
return affected; | ||
} | ||
|
||
private List<ChangeSet> limit(List<ChangeSet> changeSets, int limit) { | ||
if (limit < 0) { | ||
return changeSets; | ||
} | ||
return changeSets.subList(0, limit); | ||
} | ||
|
||
|
||
private Set<ScmFileStatus> makeStatusSet() { | ||
if ((this.include == null) || this.include.isEmpty()) { | ||
return new HashSet<>(Arrays.asList( | ||
ScmStatus.ADDED.getStatus(), ScmStatus.MODIFIED.getStatus())); | ||
} | ||
final Set<ScmFileStatus> s = new HashSet<>(); | ||
FCollection.mapTo(this.include, stringToMavenScmStatus(), s); | ||
return s; | ||
} | ||
|
||
private static Function<String, ScmFileStatus> stringToMavenScmStatus() { | ||
return new Function<String, ScmFileStatus>() { | ||
@Override | ||
public ScmFileStatus apply(final String a) { | ||
return ScmStatus.valueOf(a.toUpperCase()).getStatus(); | ||
} | ||
|
||
}; | ||
} | ||
|
||
private File scmRoot() { | ||
if (this.scmRootDir != null) { | ||
return this.scmRootDir; | ||
} | ||
return this.basedir; | ||
} | ||
|
||
private String getSCMConnection() throws MojoExecutionException { | ||
|
||
if (this.project.getScm() == null) { | ||
throw new MojoExecutionException("No SCM Connection configured."); | ||
} | ||
|
||
final String scmConnection = this.project.getScm().getConnection(); | ||
if ("connection".equalsIgnoreCase(this.connectionType) | ||
&& StringUtils.isNotEmpty(scmConnection)) { | ||
return scmConnection; | ||
} | ||
|
||
final String scmDeveloper = this.project.getScm().getDeveloperConnection(); | ||
if ("developerconnection".equalsIgnoreCase(this.connectionType) | ||
&& StringUtils.isNotEmpty(scmDeveloper)) { | ||
return scmDeveloper; | ||
} | ||
|
||
throw new MojoExecutionException("SCM Connection is not set."); | ||
|
||
} | ||
|
||
public void setConnectionType(final String connectionType) { | ||
this.connectionType = connectionType; | ||
} | ||
|
||
public void setScmRootDir(final File scmRootDir) { | ||
this.scmRootDir = scmRootDir; | ||
} | ||
|
||
/** | ||
* A bug in maven 2 requires that all list fields declare a concrete list type | ||
*/ | ||
private static ArrayList<String> makeConcreteList(List<String> list) { | ||
return new ArrayList<>(list); | ||
} | ||
|
||
} |