Skip to content

Commit

Permalink
Auto add junit-platform-launcher to classpath
Browse files Browse the repository at this point in the history
The pitest minions require a version of the junit-platform-launcher
which matches the version of junit 5 used by the SUT. Previously
this was provided by the cludge of shading it into the junit5 plugin,
but this causes constant headaches with version mismatched.

This change pulls in a matching version of platform-launcher in the same
manner that surefire does. It also allows junit dependencies to be
explicitly included pitest config should this resolution give the wrong
results.
  • Loading branch information
hcoles committed May 18, 2023
1 parent 2c60cf1 commit 9835518
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 17 deletions.
30 changes: 26 additions & 4 deletions pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.pitest.maven;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.pitest.coverage.CoverageSummary;
import org.pitest.mutationtest.config.PluginServices;
import org.pitest.mutationtest.config.ReportOptions;
Expand All @@ -16,6 +18,7 @@
import org.slf4j.bridge.SLF4JBridgeHandler;
import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J;

import javax.inject.Inject;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
Expand All @@ -37,6 +40,13 @@ public class AbstractPitMojo extends AbstractMojo {

private final PluginServices plugins;

/**
* The current build session instance.
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;


// Concrete List types declared for all fields to work around maven 2 bug

/**
Expand Down Expand Up @@ -385,18 +395,22 @@ public class AbstractPitMojo extends AbstractMojo {

private final GoalStrategy goalStrategy;

public AbstractPitMojo() {
private final RepositorySystem repositorySystem;

@Inject
public AbstractPitMojo(RepositorySystem repositorySystem) {
this(new RunPitStrategy(), new DependencyFilter(PluginServices.makeForLoader(
AbstractPitMojo.class.getClassLoader())), PluginServices.makeForLoader(
AbstractPitMojo.class.getClassLoader()), new NonEmptyProjectCheck());
AbstractPitMojo.class.getClassLoader()), new NonEmptyProjectCheck(), repositorySystem);
}

public AbstractPitMojo(final GoalStrategy strategy, final Predicate<Artifact> filter,
final PluginServices plugins, final Predicate<MavenProject> emptyProjectCheck) {
public AbstractPitMojo(GoalStrategy strategy, Predicate<Artifact> filter,
PluginServices plugins, Predicate<MavenProject> emptyProjectCheck, RepositorySystem repositorySystem) {
this.goalStrategy = strategy;
this.filter = filter;
this.plugins = plugins;
this.notEmptyProject = emptyProjectCheck;
this.repositorySystem = repositorySystem;
}

@Override
Expand Down Expand Up @@ -740,6 +754,14 @@ public String getProjectBase() {
return projectBase;
}

public MavenSession session() {
return session;
}

public RepositorySystem repositorySystem() {
return repositorySystem;
}

static class RunDecision {
private List<String> reasons = new ArrayList<>(4);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.pitest.classinfo.ClassName;
import org.pitest.classpath.DirectoryClassPathRoot;
import org.pitest.functional.FCollection;
Expand All @@ -40,6 +44,7 @@
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
Expand Down Expand Up @@ -77,18 +82,84 @@ public ReportOptions convert() {

classPath.addAll(this.mojo.getAdditionalClasspathElements());

autoAddJUnitPlatform(classPath);
removeExcludedDependencies(classPath);

ReportOptions option = parseReportOptions(classPath);
return updateFromSurefire(option);

}

/**
* The junit 5 plugin needs junit-platform-launcher to run, but this will not be on the classpath
* of the project. We want to use the same version that surefire (and therefore the SUT) uses, not
* the one the plugin was built against.
* <p>
* It is not declared as a normal dependency, instead surefire picks the version to use based on
* other junit jars on the classpath. We're forced to do something similar here.
*
* @param classPath classpath to modify
*/
private void autoAddJUnitPlatform(List<String> classPath) {
List<Artifact> junitDependencies = this.mojo.getProject().getArtifacts().stream()
.filter(a -> a.getGroupId().equals("org.junit.platform"))
.collect(Collectors.toList());

// If the launcher has been manually added to the dependencies, there is nothing to do
if (junitDependencies.stream().anyMatch(a -> a.getArtifactId().equals("junit-platform-launcher"))) {
return;
}

Optional<Artifact> maybeJUnitPlatform = findJUnitArtifact(junitDependencies);
if (!maybeJUnitPlatform.isPresent()) {
this.log.debug("JUnit 5 not on classpath");
return;
}

// Look for platform engine or platform commons on classpath
Artifact toMatch = maybeJUnitPlatform.get();

// Assume that platform launcher has been released with same version number as engine and commons
DefaultArtifact platformLauncher = new DefaultArtifact(toMatch.getGroupId(), "junit-platform-launcher", "jar",
toMatch.getVersion());

try {
ArtifactRequest r = new ArtifactRequest();
r.setArtifact(platformLauncher);

r.setRepositories(this.mojo.getProject().getRemotePluginRepositories());
ArtifactResult resolved = this.mojo.repositorySystem().resolveArtifact(mojo.session().getRepositorySession(), r);

this.log.info("Auto adding " + resolved + " to classpath.");
classPath.add(resolved.getArtifact().getFile().getAbsolutePath());
} catch (ArtifactResolutionException e) {
this.log.error("Could not resolve " + platformLauncher);
throw new RuntimeException(e);
}

}

private static Optional<Artifact> findJUnitArtifact(List<Artifact> junitDependencies) {
Optional<Artifact> maybeEngine = junitDependencies.stream()
.filter(a -> a.getArtifactId().equals("junit-platform-engine"))
.findAny();
if (maybeEngine.isPresent()) {
return maybeEngine;
}

return junitDependencies.stream()
.filter(a -> a.getArtifactId().equals("junit-platform-commons"))
.findAny();
}

private void removeExcludedDependencies(List<String> classPath) {
for (Object artifact : this.mojo.getProject().getArtifacts()) {
final Artifact dependency = (Artifact) artifact;

if (this.mojo.getClasspathDependencyExcludes().contains(
dependency.getGroupId() + ":" + dependency.getArtifactId())) {
classPath.remove(dependency.getFile().getPath());
}
}

ReportOptions option = parseReportOptions(classPath);
return updateFromSurefire(option);

}

private ReportOptions parseReportOptions(final List<String> classPath) {
Expand Down Expand Up @@ -270,6 +341,11 @@ private void addOwnDependenciesToClassPath(final List<String> classPath) {
+ dependency.getArtifactId() + " to SUT classpath");
classPath.add(dependency.getFile().getAbsolutePath());
}

// If the user as explicitly added junit platform classes to the pitest classpath, trust that they
// know what they're doing and add them in
this.mojo.getPluginArtifactMap().values().stream().filter(a -> a.getGroupId().equals("org.junit.platform"))
.forEach(dependency -> classPath.add(dependency.getFile().getAbsolutePath()));
}

private Collection<Predicate<String>> globStringsToPredicates(
Expand Down
7 changes: 7 additions & 0 deletions pitest-maven/src/main/java/org/pitest/maven/PitMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.eclipse.aether.RepositorySystem;

import javax.inject.Inject;

/**
* Goal which runs a coverage mutation report
Expand All @@ -13,4 +16,8 @@
threadSafe = true)
public class PitMojo extends AbstractPitMojo {

@Inject
public PitMojo(RepositorySystem repositorySystem) {
super(repositorySystem);
}
}
19 changes: 13 additions & 6 deletions pitest-maven/src/main/java/org/pitest/maven/ScmMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@
import org.apache.maven.scm.manager.ScmManager;
import org.apache.maven.scm.repository.ScmRepository;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.RepositorySystem;
import org.pitest.functional.FCollection;
import org.pitest.mutationtest.config.PluginServices;
import org.pitest.mutationtest.config.ReportOptions;
import org.pitest.mutationtest.tooling.CombinedStatistics;

import javax.inject.Inject;

/**
* Goal which runs a coverage mutation report only for files that have been
* modified or introduced locally based on the source control configured in
Expand Down Expand Up @@ -96,16 +99,20 @@ public class ScmMojo extends AbstractPitMojo {
@Parameter(property = "scmRootDir", defaultValue = "${project.parent.basedir}")
private File scmRootDir;

public ScmMojo(final RunPitStrategy executionStrategy,
final ScmManager manager, Predicate<Artifact> filter,
PluginServices plugins, boolean analyseLastCommit, Predicate<MavenProject> nonEmptyProjectCheck) {
super(executionStrategy, filter, plugins, nonEmptyProjectCheck);
public ScmMojo(RunPitStrategy executionStrategy,
ScmManager manager, Predicate<Artifact> filter,
PluginServices plugins,
boolean analyseLastCommit,
Predicate<MavenProject> nonEmptyProjectCheck,
RepositorySystem repositorySystem) {
super(executionStrategy, filter, plugins, nonEmptyProjectCheck, repositorySystem);
this.manager = manager;
this.analyseLastCommit = analyseLastCommit;
}

public ScmMojo() {

@Inject
public ScmMojo(RepositorySystem repositorySystem) {
super(repositorySystem);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ protected String createPomWithConfiguration(final String config) {

protected AbstractPitMojo createPITMojo(final String config) throws Exception {
final AbstractPitMojo pitMojo = new AbstractPitMojo(this.executionStrategy, this.filter,
this.plugins, p -> true);
this.plugins, p -> true, null);
configurePitMojo(pitMojo, config);
return pitMojo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class ScmMojoTest extends BasePitMojoTest {
public void setUp() throws Exception {
super.setUp();
this.testee = new ScmMojo(this.executionStrategy, this.manager,
this.filter, this.plugins, false, i -> true);
this.filter, this.plugins, false, i -> true, null);
this.testee.setScmRootDir(new File("foo"));
when(this.project.getBuild()).thenReturn(this.build);
when(this.project.getParent()).thenReturn(null);
Expand Down

0 comments on commit 9835518

Please sign in to comment.