diff --git a/maven-plugin-testing-harness/pom.xml b/maven-plugin-testing-harness/pom.xml index bb972f95..49420cdb 100644 --- a/maven-plugin-testing-harness/pom.xml +++ b/maven-plugin-testing-harness/pom.xml @@ -31,7 +31,8 @@ under the License. The Maven Plugin Testing Harness provides mechanisms to manage tests on Mojo. - 5.13.0 + 3.9.12 + 1.9.25 3.5.3 @@ -61,6 +62,12 @@ under the License. ${mavenVersion} provided + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${version.maven-plugin-tools} + test + javax.inject javax.inject @@ -110,6 +117,27 @@ under the License. true + + + org.apache.maven.resolver + maven-resolver-connector-basic + ${resloverVersion} + test + + + org.apache.maven.resolver + maven-resolver-transport-file + ${resloverVersion} + test + + + org.apache.maven.resolver + maven-resolver-transport-http + ${resloverVersion} + test + + + org.codehaus.plexus plexus-testing diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java index 6aa59ffd..6853301c 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java @@ -35,6 +35,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -50,9 +51,14 @@ import com.google.inject.Binder; import com.google.inject.Module; import com.google.inject.internal.ProviderMethodsModule; +import org.apache.maven.RepositoryUtils; import org.apache.maven.api.di.Provides; +import org.apache.maven.execution.DefaultMavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionRequestPopulator; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.scope.internal.MojoExecutionScope; +import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory; import org.apache.maven.lifecycle.internal.MojoDescriptorCreator; import org.apache.maven.plugin.Mojo; import org.apache.maven.plugin.MojoExecution; @@ -81,6 +87,7 @@ import org.codehaus.plexus.util.xml.XmlStreamReader; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.eclipse.aether.RepositorySystemSession; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; @@ -93,6 +100,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mockingDetails; +import static org.mockito.Mockito.spy; /** * JUnit Jupiter extension that provides support for testing Maven plugins (Mojos). @@ -223,6 +231,9 @@ public void beforeEach(ExtensionContext context) throws Exception { MojoExecution mojoExecution = addMock(plexusContainer, MojoExecution.class, this::mockMojoExecution); MavenSession mavenSession = addMock(plexusContainer, MavenSession.class, this::mockMavenSession); + // prepare MavenExecutionRequest to be available in BeforeEach methods in test classes + createMavenExecutionRequest(context); + SessionScope sessionScope = plexusContainer.lookup(SessionScope.class); sessionScope.enter(); sessionScope.seed(MavenSession.class, mavenSession); @@ -388,6 +399,11 @@ protected Mojo lookupMojo( ExtensionContext extensionContext, String[] coord, Xpp3Dom pluginConfiguration, PluginDescriptor descriptor) throws Exception { PlexusContainer plexusContainer = getContainer(extensionContext); + + MavenExecutionRequest request = setupMavenExecutionRequest(extensionContext); + plexusContainer.lookup(MavenExecutionRequestPopulator.class).populateDefaults(request); + setupRepositorySession(extensionContext, request); + // pluginkey = groupId : artifactId : version : goal Mojo mojo = plexusContainer.lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]); @@ -405,15 +421,18 @@ protected Mojo lookupMojo( MojoExecution mojoExecution = plexusContainer.lookup(MojoExecution.class); if (mockingDetails(session).isMock()) { - lenient().when(session.getCurrentProject()).thenReturn(mavenProject); + lenient().doReturn(mavenProject).when(session).getCurrentProject(); } if (mockingDetails(mavenProject).isMock()) { - lenient().when(mavenProject.getBasedir()).thenReturn(new File(getTestBasedir(extensionContext))); + lenient() + .doReturn(new File(getTestBasedir(extensionContext))) + .when(mavenProject) + .getBasedir(); } if (mojoDescriptor.isPresent() && mockingDetails(mojoExecution).isMock()) { - lenient().when(mojoExecution.getMojoDescriptor()).thenReturn(mojoDescriptor.get()); + lenient().doReturn(mojoDescriptor.get()).when(mojoExecution).getMojoDescriptor(); } if (pluginConfiguration != null) { @@ -445,6 +464,100 @@ protected Mojo lookupMojo( return mojo; } + private boolean isRealRepositorySessionNotRequired(ExtensionContext context) { + return !AnnotationSupport.findAnnotation(context.getTestClass(), MojoTest.class) + .map(MojoTest::realRepositorySession) + .orElse(false); + } + + /** + * Create a MavenExecutionRequest if not already present in the MavenSession + */ + private void createMavenExecutionRequest(ExtensionContext context) throws ComponentLookupException { + PlexusContainer container = getContainer(context); + MavenSession session = container.lookup(MavenSession.class); + MavenExecutionRequest request = session.getRequest(); + + if (request == null && mockingDetails(session).isMock()) { + lenient() + .doReturn(spy(new DefaultMavenExecutionRequest())) + .when(session) + .getRequest(); + } + } + + private MavenExecutionRequest setupMavenExecutionRequest(ExtensionContext context) throws ComponentLookupException { + PlexusContainer container = getContainer(context); + MavenSession session = container.lookup(MavenSession.class); + MavenExecutionRequest request = session.getRequest(); + + if (request == null) { + // user can provide own MavenSession instance without a request + request = new DefaultMavenExecutionRequest(); + } + + if (request.getStartTime() == null) { + request.setStartTime(new Date()); + } + + if (request.getUserProperties().isEmpty()) { + request.setUserProperties(session.getUserProperties()); + } + + if (request.getSystemProperties().isEmpty()) { + request.setSystemProperties(session.getSystemProperties()); + } + + // set a default local repository path if none is set + if (request.getLocalRepositoryPath() == null && request.getLocalRepository() == null) { + request.setLocalRepositoryPath(getTestBasedir(context) + "/target/local-repo"); + } + + if (request.getBaseDirectory() == null) { + request.setBaseDirectory(new File(getTestBasedir(context))); + } + + return request; + } + + private void setupRepositorySession(ExtensionContext context, MavenExecutionRequest request) + throws ComponentLookupException { + + if (isRealRepositorySessionNotRequired(context)) { + return; + } + + PlexusContainer container = getContainer(context); + + MavenProject mavenProject = container.lookup(MavenProject.class); + if (mockingDetails(mavenProject).isMock()) { + lenient() + .doReturn(request.getRemoteRepositories()) + .when(mavenProject) + .getRemoteArtifactRepositories(); + lenient() + .doReturn(request.getPluginArtifactRepositories()) + .when(mavenProject) + .getPluginArtifactRepositories(); + lenient() + .doReturn(RepositoryUtils.toRepos(request.getRemoteRepositories())) + .when(mavenProject) + .getRemoteProjectRepositories(); + lenient() + .doReturn(RepositoryUtils.toRepos(request.getPluginArtifactRepositories())) + .when(mavenProject) + .getRemotePluginRepositories(); + } + + RepositorySystemSession repositorySystemSession = + container.lookup(DefaultRepositorySystemSessionFactory.class).newRepositorySession(request); + + MavenSession session = container.lookup(MavenSession.class); + if (mockingDetails(session).isMock()) { + lenient().doReturn(repositorySystemSession).when(session).getRepositorySession(); + } + } + private Xpp3Dom finalizeConfig(Xpp3Dom config, MojoDescriptor mojoDescriptor) { List children = new ArrayList<>(); if (mojoDescriptor != null && mojoDescriptor.getParameters() != null) { diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java index 4aa9bbfc..063c1fa3 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java @@ -83,4 +83,12 @@ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(MojoExtension.class) @Target(ElementType.TYPE) -public @interface MojoTest {} +public @interface MojoTest { + /** + * Indicates whether to use a real repository session for the test. + *
+ * When set to {@code true}, the test will utilize a real repository session, + * allowing for artifact resolution and repository interactions. + */ + boolean realRepositorySession() default false; +} diff --git a/maven-plugin-testing-harness/src/site/markdown/examples/repositories.md b/maven-plugin-testing-harness/src/site/markdown/examples/repositories.md index 1098a4a8..05246e46 100644 --- a/maven-plugin-testing-harness/src/site/markdown/examples/repositories.md +++ b/maven-plugin-testing-harness/src/site/markdown/examples/repositories.md @@ -18,129 +18,38 @@ date: February 2008 -## Testing Using Repositories - -### NOTE - -`JUnit 3` based tests are deprecated since `3.4.0`. - -Use JUnit 5 annotations, consult [javadocs](../apidocs/org/apache/maven/api/plugin/testing/package-summary.html) for examples. - - **Note**: This example improves the [cookbook](../getting-started/index.html) for testing repositories. - - - When developing a Maven plugin you often need to play with repositories. Suppose that the MyMojo needs to download artifacts into your local repository, i.e.: - - -``` -public class MyMojo - extends AbstractMojo -{ - /** - * Used for resolving artifacts - */ - @Component - private ArtifactResolver resolver; - - /** - * Factory for creating artifact objects - */ - @Component - private ArtifactFactory factory; - - /** - * Local Repository. - */ - @Parameter( defaultValue = "${localRepository}", readonly = true, required = true ) - private ArtifactRepository localRepository; - - public void execute() - throws MojoExecutionException - { - ... - - Artifact artifact = factory.createArtifact( "junit", "junit", "3.8.1", "compile", "jar" ); - try - { - resolver.resolve( artifact, project.getRemoteArtifactRepositories(), localRepository ); - } - catch ( ArtifactResolutionException e ) - { - throw new MojoExecutionException( "Unable to resolve artifact:" + artifact, e ); - } - catch ( ArtifactNotFoundException e ) - { - throw new MojoExecutionException( "Unable to find artifact:" + artifact, e ); - } - - ... - } -} -``` - -### Create Stubs +## Testing Using Repositories +**Note**: This example improves the [cookbook](../getting-started/index.html) for testing repositories. + +When developing a Maven plugin you often need to play with repositories. +Suppose that the Mojo needs to download artifacts into your local repository. - Stub for the test project: +You need annotate unit test with `@MojoTest(realRepositorySession = true)` to enable real repository session. +Then provided mock for `MavenSession` will have a real repository session with local repository configured. +Mock for `MavenProject` will also have mocked methods `getRemote*Repositories`. -``` -public class MyProjectStub - extends MavenProjectStub -{ - /** - * Default constructor - */ - public MyProjectStub() - { - ... - } +### Project dependencies for test - /** {@inheritDoc} */ - public List getRemoteArtifactRepositories() - { - ArtifactRepository repository = new DefaultArtifactRepository( "central", "http://repo.maven.apache.org/maven2", - new DefaultRepositoryLayout() ); +For real repository session you need to add resolver transport dependency in test scope to your `pom.xml`: - return Collections.singletonList( repository ); - } -} -``` + +### Example Mojo to test -### Configure `project-to-test` pom + +### Unit test + -``` - - ... - - - - maven-my-plugin - - - ${basedir}/target/test-harness/project-to-test - - - ${localRepository} - - - - - - - -``` #### Execute test - - Calling `mvn test` will create `$\{basedir\}/target/local-repo/junitjunit/3.8.1/junit-3.8.1.jar` file. + Calling `mvn test` will create `/org/apache/commons/commons-lang3/3.20.0/commons-lang3-3.20.0.jar` file. diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojo.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojo.java new file mode 100644 index 00000000..c9e2a16e --- /dev/null +++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojo.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.plugin.testing; + +// START SNIPPET: resolve-mojo +import javax.inject.Inject; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; + +/** + * Simple mojo for resolving artifacts. + */ +@Mojo(name = "simple-resolve") +public class SimpleResolveMojo extends AbstractMojo { + + private final MavenProject project; + private final MavenSession session; + private final RepositorySystem repositorySystem; + + @Parameter + private String artifact; + + @Inject + SimpleResolveMojo(MavenProject project, MavenSession session, RepositorySystem repositorySystem) { + this.project = project; + this.session = session; + this.repositorySystem = repositorySystem; + } + + @Override + public void execute() throws MojoExecutionException { + + ArtifactRequest request = + new ArtifactRequest(new DefaultArtifact(artifact), project.getRemoteProjectRepositories(), null); + + try { + ArtifactResult result = repositorySystem.resolveArtifact(session.getRepositorySession(), request); + getLog().info("Resolved artifact to " + result.getArtifact().getFile()); + } catch (ArtifactResolutionException e) { + throw new MojoExecutionException("Failed to resolve artifact", e); + } + } +} +// END SNIPPET: resolve-mojo diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojoTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojoTest.java new file mode 100644 index 00000000..9f697579 --- /dev/null +++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleResolveMojoTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.plugin.testing; + +// START SNIPPET: resolve-mojo-test +import javax.inject.Inject; + +import java.io.File; + +import org.apache.maven.api.plugin.testing.InjectMojo; +import org.apache.maven.api.plugin.testing.MojoParameter; +import org.apache.maven.api.plugin.testing.MojoTest; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecutionException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Test class for simple artifact resolution mojo with repository system injection. + */ +@MojoTest(realRepositorySession = true) +class SimpleResolveMojoTest { + + @TempDir + private File tempDir; + + // inject the Maven session to customize it for tests + @Inject + private MavenSession session; + + @BeforeEach + void beforeEach() { + // optionally customize the session to use a temporary local repository + // default would be basedir/target/local-repo + session.getRequest().setLocalRepositoryPath(tempDir); + + // other customizations can be done here, if needed + // session.getRequest().setRemoteRepositories(customRemoteRepositories()); + // session.getRequest().setOffline(true); + // session.getUserProperties().setProperty("maven.repo.local.tail", "your local repository"); + } + + @Test + @InjectMojo(goal = "simple-resolve") + @MojoParameter(name = "artifact", value = "org.apache.commons:commons-lang3:3.20.0") + void artifactShouldBeResolved(SimpleResolveMojo mojo) throws MojoExecutionException { + assertDoesNotThrow(mojo::execute); + } +} +// END SNIPPET: resolve-mojo-test diff --git a/maven-plugin-testing-harness/src/test/resources/META-INF/maven/plugin.xml b/maven-plugin-testing-harness/src/test/resources/META-INF/maven/plugin.xml index 7a17964c..c8d4ca8c 100644 --- a/maven-plugin-testing-harness/src/test/resources/META-INF/maven/plugin.xml +++ b/maven-plugin-testing-harness/src/test/resources/META-INF/maven/plugin.xml @@ -93,6 +93,28 @@ under the License. once-per-session false + + simple-resolve + false + true + false + false + true + true + org.apache.maven.plugin.testing.SimpleResolveMojo + java + per-lookup + once-per-session + false + + + artifact + java.lang.String + true + true + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index fa47ba8d..9979f2e6 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,6 @@ under the License. - 3.9.12 plugin-testing-archives/LATEST 8 2025-10-28T21:36:24Z