This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/additional-classpath-via-maven-gav in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit bc0a99cfc63ad79ff6ed3829608e540e083a7c44 Author: Konrad Windszus <[email protected]> AuthorDate: Fri Jun 23 14:11:54 2023 +0200 [SUREFIRE-2179] Support adding Maven artifacts to the test classpath --- .../plugin/surefire/AbstractSurefireMojo.java | 84 +++++++++++++++++++++- .../surefire/SurefireDependencyResolver.java | 34 ++++++--- .../maven/plugin/surefire/TestClassPath.java | 4 +- .../plugin/surefire/AbstractSurefireMojoTest.java | 4 +- .../site/apt/examples/configuring-classpath.apt.vm | 30 +++++++- 5 files changed, 138 insertions(+), 18 deletions(-) diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java index 8237a9f4e..595b09304 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java @@ -26,6 +26,7 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; @@ -41,6 +42,8 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.ZipFile; import org.apache.maven.artifact.Artifact; @@ -118,6 +121,14 @@ import org.codehaus.plexus.languages.java.jpms.ResolvePathResult; import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest; import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult; import org.codehaus.plexus.logging.Logger; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyFilter; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyResolutionException; +import org.eclipse.aether.util.artifact.JavaScopes; +import org.eclipse.aether.util.filter.DependencyFilterUtils; import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; @@ -281,6 +292,17 @@ public abstract class AbstractSurefireMojo extends AbstractMojo implements Suref @Parameter(property = "maven.test.additionalClasspath") private String[] additionalClasspathElements; + /** + * Maven coordinates in the format {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>} of additional artifacts. + * Those artifacts are automatically resolved from the repository (including their transitive dependencies). + * Afterwards they are appended including their transitive dependencies to the classpath + * (after the ones from {@link #additionalClasspathElements}). + * + * @since 3.2 + */ + @Parameter(property = "maven.test.additionalClasspathArtifacts") + private String[] additionalClasspathArtifacts; + /** * The test source directory containing test class sources. * Important <b>only</b> for TestNG HTML reports. @@ -2526,8 +2548,9 @@ public abstract class AbstractSurefireMojo extends AbstractMojo implements Suref * Generates the test classpath. * * @return the classpath elements + * @throws MojoFailureException */ - private TestClassPath generateTestClasspath() { + private TestClassPath generateTestClasspath() throws MojoFailureException { Set<Artifact> classpathArtifacts = getProject().getArtifacts(); if (getClasspathDependencyScopeExclude() != null @@ -2542,8 +2565,57 @@ public abstract class AbstractSurefireMojo extends AbstractMojo implements Suref classpathArtifacts = filterArtifacts(classpathArtifacts, dependencyFilter); } + Set<String> additionalClasspathElements = new HashSet<>(); + if (getAdditionalClasspathElements() != null) { + Arrays.stream(getAdditionalClasspathElements()).forEach(additionalClasspathElements::add); + } + if (getAdditionalClasspathArtifacts() != null) { + resolveDependencies(Arrays.stream(getAdditionalClasspathArtifacts())).stream() + .map(File::getAbsolutePath) + .forEach(additionalClasspathElements::add); + } return new TestClassPath( - classpathArtifacts, getMainBuildPath(), getTestClassesDirectory(), getAdditionalClasspathElements()); + classpathArtifacts, getMainBuildPath(), getTestClassesDirectory(), additionalClasspathElements); + } + + Set<File> resolveDependencies(Stream<String> mavenCoordinates) throws MojoFailureException { + Set<File> files = new HashSet<>(); + try { + mavenCoordinates.map(this::resolveArtifact).forEach(files::addAll); + } catch (IllegalStateException e) { + throw new MojoFailureException(e.getMessage(), e.getCause()); + } + return files; + } + + /** Resolves the artifact and its transitive runtime dependencies + * @param mavenCoordinates + * @return a collection of file paths (pointing to the local repository) + * @throws IllegalStateException in case resolving fails + */ + private Collection<File> resolveArtifact(String mavenCoordinates) throws IllegalStateException { + org.eclipse.aether.artifact.Artifact resolverArtifact = + new org.eclipse.aether.artifact.DefaultArtifact(mavenCoordinates); + getConsoleLogger().debug("Resolving artifact " + mavenCoordinates); + DependencyFilter filter = DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME); + List<ArtifactResult> results; + try { + results = surefireDependencyResolver.resolveDependencies( + repoSession, + project.getRemoteProjectRepositories(), + new Dependency(resolverArtifact, null), + filter); + } catch (DependencyResolutionException e) { + throw new IllegalStateException(e); + } + Collection<File> files = results.stream() + .map(ArtifactResult::getArtifact) + .map(org.eclipse.aether.artifact.Artifact::getFile) + .collect(Collectors.toSet()); + getConsoleLogger() + .debug("Resolved artifact " + mavenCoordinates + " added the following files to the classpath: " + + files); + return files; } /** @@ -3474,6 +3546,14 @@ public abstract class AbstractSurefireMojo extends AbstractMojo implements Suref this.additionalClasspathElements = additionalClasspathElements; } + public String[] getAdditionalClasspathArtifacts() { + return additionalClasspathArtifacts; + } + + public void setAdditionalClasspathArtifacts(String[] additionalClasspathArtifacts) { + this.additionalClasspathArtifacts = additionalClasspathArtifacts; + } + public String[] getClasspathDependencyExcludes() { return classpathDependencyExcludes; } diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java index b581a88bb..af89a6baa 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireDependencyResolver.java @@ -45,6 +45,7 @@ import org.apache.maven.plugin.MojoExecutionException; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.resolution.DependencyRequest; @@ -143,17 +144,9 @@ class SurefireDependencyResolver { throws MojoExecutionException { try { - - CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(dependency); - collectRequest.setRepositories(repositories); - - DependencyRequest request = new DependencyRequest(); - request.setCollectRequest(collectRequest); - request.setFilter(DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME)); - - DependencyResult dependencyResult = repositorySystem.resolveDependencies(session, request); - return dependencyResult.getArtifactResults().stream() + List<ArtifactResult> results = resolveDependencies( + session, repositories, dependency, DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME)); + return results.stream() .map(ArtifactResult::getArtifact) .map(RepositoryUtils::toArtifact) .collect(Collectors.toSet()); @@ -163,6 +156,25 @@ class SurefireDependencyResolver { } } + public List<ArtifactResult> resolveDependencies( + RepositorySystemSession session, + List<RemoteRepository> repositories, + org.eclipse.aether.graph.Dependency dependency, + DependencyFilter dependencyFilter) + throws DependencyResolutionException { + + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRoot(dependency); + collectRequest.setRepositories(repositories); + + DependencyRequest request = new DependencyRequest(); + request.setCollectRequest(collectRequest); + request.setFilter(dependencyFilter); + + DependencyResult dependencyResult = repositorySystem.resolveDependencies(session, request); + return dependencyResult.getArtifactResults(); + } + @Nonnull Set<Artifact> getProviderClasspath( RepositorySystemSession session, diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/TestClassPath.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/TestClassPath.java index f3379564f..d86910342 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/TestClassPath.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/TestClassPath.java @@ -34,13 +34,13 @@ final class TestClassPath { private final Iterable<Artifact> artifacts; private final File classesDirectory; private final File testClassesDirectory; - private final String[] additionalClasspathElements; + private final Iterable<String> additionalClasspathElements; TestClassPath( Iterable<Artifact> artifacts, File classesDirectory, File testClassesDirectory, - String[] additionalClasspathElements) { + Iterable<String> additionalClasspathElements) { this.artifacts = artifacts; this.classesDirectory = classesDirectory; this.testClassesDirectory = testClassesDirectory; diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java index 0fc3ccded..ea193d098 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java @@ -606,7 +606,7 @@ public class AbstractSurefireMojoTest { File classesDir = mockFile("classes"); File testClassesDir = mockFile("test-classes"); TestClassPath testClassPath = - new TestClassPath(new ArrayList<Artifact>(), classesDir, testClassesDir, new String[0]); + new TestClassPath(new ArrayList<Artifact>(), classesDir, testClassesDir, Collections.emptyList()); Artifact common = new DefaultArtifact( "org.apache.maven.surefire", @@ -711,7 +711,7 @@ public class AbstractSurefireMojoTest { File testClassesDirectory = new File(baseDir, "mock-dir"); mojo.setTestClassesDirectory(testClassesDirectory); TestClassPath testClassPath = new TestClassPath( - Collections.<Artifact>emptySet(), classesDirectory, testClassesDirectory, new String[0]); + Collections.<Artifact>emptySet(), classesDirectory, testClassesDirectory, Collections.emptyList()); ProviderInfo providerInfo = mock(ProviderInfo.class); when(providerInfo.getProviderName()).thenReturn("provider mock"); diff --git a/maven-surefire-plugin/src/site/apt/examples/configuring-classpath.apt.vm b/maven-surefire-plugin/src/site/apt/examples/configuring-classpath.apt.vm index 76d9b8776..f4e56fce1 100644 --- a/maven-surefire-plugin/src/site/apt/examples/configuring-classpath.apt.vm +++ b/maven-surefire-plugin/src/site/apt/examples/configuring-classpath.apt.vm @@ -86,11 +86,39 @@ Additional Classpath Elements </project> +---+ + Since version 3.2.0 the <<<additionalClasspathArtifacts>>> parameter can be used to add arbitrary artifacts to your test execution classpath (via their regular Maven coordinates). + Those are resolved from the repository like regular Maven dependencies and afterwards added as additional classpath elements to the end of the classpath, so you cannot use these to + override project dependencies or resources (except those which are filtered with <<<classpathDependencyExclude>>>). + Note that even transitive dependencies (both <<<compile>>> + <<<runtime>>> scope) are added implicitly. + ++---+ +<project> + [...] + <build> + <plugins> + <plugin> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${project.version}</version> + <configuration> + <additionalClasspathArtifacts> + <additionalClasspathArtifact>myGroupId:myArtifactId:1.0.0</additionalClasspathArtifact> + <additionalClasspathArtifact>myGroupId:myOtherArtifactId:1.2.0</additionalClasspathArtifact> + </additionalClasspathArtifacts> + </configuration> + </plugin> + </plugins> + </build> + [...] +</project> ++---+ Removing Dependency Classpath Elements Dependencies can be removed from the test classpath using the parameters <<<classpathDependencyExcludes>>> and <<<classpathDependencyScopeExclude>>>. A list of specific dependencies can be removed from the - classpath by specifying the <<<groupId:artifactId>>> to be removed. + classpath by specifying the <<<groupId:artifactId>>> to be removed. Details of the pattern matching mechanism + are outlined in the goal parameter description for <<<classpathDependencyScopeExcludes>>>. + It is important to note that this filtering is only applied to the effective project dependencies (this includes transitive project dependencies). +---+ <project>
