Skip to content

Commit

Permalink
same as scm, but only for the tests class
Browse files Browse the repository at this point in the history
  • Loading branch information
sbuisson committed Apr 2, 2018
1 parent cbf48e8 commit eb4586e
Showing 1 changed file with 316 additions and 0 deletions.
316 changes: 316 additions & 0 deletions pitest-maven/src/main/java/org/pitest/maven/ScmTestMojo.java
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);
}

}

0 comments on commit eb4586e

Please sign in to comment.