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>

Reply via email to