From 98355189928053635c48b6aa4c12222496fc3f19 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 18 May 2023 12:04:54 +0100 Subject: [PATCH] Auto add junit-platform-launcher to classpath 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. --- .../org/pitest/maven/AbstractPitMojo.java | 30 ++++++- .../maven/MojoToReportOptionsConverter.java | 86 +++++++++++++++++-- .../main/java/org/pitest/maven/PitMojo.java | 7 ++ .../main/java/org/pitest/maven/ScmMojo.java | 19 ++-- .../org/pitest/maven/BasePitMojoTest.java | 2 +- .../java/org/pitest/maven/ScmMojoTest.java | 2 +- 6 files changed, 129 insertions(+), 17 deletions(-) diff --git a/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java b/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java index d4c74f290..aa280f552 100644 --- a/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java +++ b/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java @@ -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; @@ -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; @@ -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 /** @@ -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 filter, - final PluginServices plugins, final Predicate emptyProjectCheck) { + public AbstractPitMojo(GoalStrategy strategy, Predicate filter, + PluginServices plugins, Predicate emptyProjectCheck, RepositorySystem repositorySystem) { this.goalStrategy = strategy; this.filter = filter; this.plugins = plugins; this.notEmptyProject = emptyProjectCheck; + this.repositorySystem = repositorySystem; } @Override @@ -740,6 +754,14 @@ public String getProjectBase() { return projectBase; } + public MavenSession session() { + return session; + } + + public RepositorySystem repositorySystem() { + return repositorySystem; + } + static class RunDecision { private List reasons = new ArrayList<>(4); diff --git a/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java b/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java index 83480365b..fc77fb78e 100644 --- a/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java +++ b/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java @@ -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; @@ -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; @@ -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. + *

+ * 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 classPath) { + List 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 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 findJUnitArtifact(List junitDependencies) { + Optional 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 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 classPath) { @@ -270,6 +341,11 @@ private void addOwnDependenciesToClassPath(final List 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> globStringsToPredicates( diff --git a/pitest-maven/src/main/java/org/pitest/maven/PitMojo.java b/pitest-maven/src/main/java/org/pitest/maven/PitMojo.java index 47438cb75..43611c395 100644 --- a/pitest-maven/src/main/java/org/pitest/maven/PitMojo.java +++ b/pitest-maven/src/main/java/org/pitest/maven/PitMojo.java @@ -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 @@ -13,4 +16,8 @@ threadSafe = true) public class PitMojo extends AbstractPitMojo { + @Inject + public PitMojo(RepositorySystem repositorySystem) { + super(repositorySystem); + } } diff --git a/pitest-maven/src/main/java/org/pitest/maven/ScmMojo.java b/pitest-maven/src/main/java/org/pitest/maven/ScmMojo.java index a828180ef..206f70af6 100644 --- a/pitest-maven/src/main/java/org/pitest/maven/ScmMojo.java +++ b/pitest-maven/src/main/java/org/pitest/maven/ScmMojo.java @@ -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 @@ -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 filter, - PluginServices plugins, boolean analyseLastCommit, Predicate nonEmptyProjectCheck) { - super(executionStrategy, filter, plugins, nonEmptyProjectCheck); + public ScmMojo(RunPitStrategy executionStrategy, + ScmManager manager, Predicate filter, + PluginServices plugins, + boolean analyseLastCommit, + Predicate 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 diff --git a/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java b/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java index 40eac21f2..51526fe2a 100644 --- a/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java +++ b/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java @@ -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; } diff --git a/pitest-maven/src/test/java/org/pitest/maven/ScmMojoTest.java b/pitest-maven/src/test/java/org/pitest/maven/ScmMojoTest.java index 6fab59413..6504b140d 100644 --- a/pitest-maven/src/test/java/org/pitest/maven/ScmMojoTest.java +++ b/pitest-maven/src/test/java/org/pitest/maven/ScmMojoTest.java @@ -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);