My plugin mojo test class leverages maven-plugin-test-harness
to build the complete maven environment with all pom config, plexus container context and repo access.
The following should all actually work:
pom.xml
in the plugin project's test resources directoryBecause of the lack of concrete working examples I've been trying many different fixes using what I've collected from SO, and other blogs or articles online.
I am currently struggling to get the maven to resolve the artifacts. While I've got the dependency list from the maven project object, the artifact list is empty.
This is what I've built up by dissecting AbstractMojoTestCase
.
I can't use MojoRule
because JUnit5 doesn't use @Rules
anymore.
Plus, some of the maven API calls are deprecated, but I couldn't find a new implementation. I think it won't come until mvn4. See the quote below.
@BeforeEach
protected void setUp() throws Exception {
super.setUp();
cleanUp();
ClassLoader classLoader = getClass().getClassLoader();
URL url = classLoader.getResource(TEST_POM);
if (url == null) {
throw new MojoExecutionException(String.format(
"Cannot locate %s", TEST_POM));
}
File pom = new File(url.getFile());
//noinspection deprecation - wait on maven-plugin-testing-harness update
MavenSettingsBuilder mavenSettingsBuilder = (MavenSettingsBuilder)
getContainer().lookup(MavenSettingsBuilder.ROLE);
Settings settings = mavenSettingsBuilder.buildSettings();
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setPom(pom);
request.setLocalRepositoryPath(settings.getLocalRepository());
MavenExecutionRequestPopulator populator =
getContainer().lookup(MavenExecutionRequestPopulator.class);
populator.populateDefaults(request);
DefaultMaven maven = (DefaultMaven) getContainer().lookup(Maven.class);
DefaultRepositorySystemSession repoSession =
(DefaultRepositorySystemSession)
maven.newRepositorySession(request);
LocalRepository localRepository = new LocalRepository(
request.getLocalRepository().getBasedir());
SimpleLocalRepositoryManagerFactory factory =
new SimpleLocalRepositoryManagerFactory();
LocalRepositoryManager localRepositoryManager =
factory.newInstance(repoSession, localRepository);
repoSession.setLocalRepositoryManager(localRepositoryManager);
ProjectBuildingRequest buildingRequest =
request.getProjectBuildingRequest()
.setRepositorySession(repoSession)
.setResolveDependencies(true);
ProjectBuilder projectBuilder = lookup(ProjectBuilder.class);
MavenProject project =
projectBuilder.build(pom, buildingRequest).getProject();
//noinspection deprecation - wait on maven-plugin-testing-harness update
MavenSession session = new MavenSession(getContainer(), repoSession,
request, new DefaultMavenExecutionResult());
session.setCurrentProject(project);
session.setProjects(Collections.singletonList(project));
request.setSystemProperties(System.getProperties());
testMojo = (GenerateConfig) lookupConfiguredMojo(session,
newMojoExecution("configure"));
copyTestProjectResourcesToTarget(getContainer(), project, session);
}
[UPDATE 2017-07-27]: actually this now solves most of my problems.
Only a couple of minor issues now:
@Deprecated
so I assume there is a better way of doing it (using the MavenSettingsBuilder.buildSettings()
)[UPDATE 2017-08-01]: test now needs to access some property files which would be on the classpath in a live environment in the target/classes
dir.
Logically they are test resources in my maven-plugin project, so I have included them under the same directory as the test pom.xml
in src/test/resources/my-test-project
dir.
That didn't work, so I also tried src/test/resources/my-test-project/src/main/resources
but that's also not good.
I am having a hard time establishing what exactly is on the plugin's classpath during the test, or working out how to cause it to be constructed correctly.
[UPDATE 2017-08-02]: although I've answered my own question (as opposed to extending this question), this whole thing isn't finished yet so I'm not marking this as answered quite yet.
And just for the record, these are the dependencies required:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>4.12.0-M4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-testing</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>
<version>3.3.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-compat</artifactId>
<version>3.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.twdata.maven</groupId>
<artifactId>mojo-executor</artifactId>
<version>2.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<scope>test</scope>
</dependency>
[UPDATE 2017-08-09]:
I have to add some more functionality and discovered that the test was fine if the dependency it wanted to unpack was in the local repo already, but if not, it won't fetch it.
I now need to determine how to instruct maven to fetch the dependency from the remote repo.
I tried launching the dependency plugin and invoking resolve
in the test setup, but it dies badly, I think there must be a simpler way.
I found the solution to fetching dependencies from the remote repository.
Working with the maven internals like this and judging from the deprecated classes and the amount of duplicated functionality, it gives me the strong impression that maven v4 will make this redundant.
One glitch with this setup routine is that it creates a local repository directory tree in the maven project directory. This is obviously not desirable but will need some more tweaking to solve.
@BeforeEach
public void setUp() throws Exception {
super.setUp();
ClassLoader classLoader = getClass().getClassLoader();
URL url = classLoader.getResource(TEST_POM);
if (url == null) {
throw new MojoExecutionException(String.format(
"Cannot locate %s", TEST_POM));
}
File pom = new File(url.getFile());
Settings settings = getMavenSettings();
if (settings.getLocalRepository() == null) {
settings.setLocalRepository(
org.apache.maven.repository.RepositorySystem
.defaultUserLocalRepository.getAbsolutePath());
}
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setPom(pom);
ArtifactRepository artifactRepository =
new org.apache.maven.artifact.repository.
DefaultArtifactRepository(
"id", settings.getLocalRepository(),
new DefaultRepositoryLayout());
request.setLocalRepository(artifactRepository);
MavenExecutionRequestPopulator populator =
getContainer().lookup(MavenExecutionRequestPopulator.class);
populator.populateFromSettings(request, settings);
DefaultMaven maven = (DefaultMaven)
getContainer().lookup(Maven.class);
DefaultRepositorySystemSession repositorySystemSession =
(DefaultRepositorySystemSession)
maven.newRepositorySession(request);
SimpleLocalRepositoryManagerFactory factory =
new SimpleLocalRepositoryManagerFactory();
LocalRepositoryManager localRepositoryManager =
factory.newInstance(repositorySystemSession,
new LocalRepository(settings.getLocalRepository()));
repositorySystemSession.setLocalRepositoryManager(
localRepositoryManager);
ProjectBuildingRequest buildingRequest =
request.getProjectBuildingRequest()
.setRepositorySession(repositorySystemSession)
.setResolveDependencies(true);
ProjectBuilder projectBuilder = lookup(ProjectBuilder.class);
ProjectBuildingResult projectBuildingResult =
projectBuilder.build(pom, buildingRequest);
MavenProject project = projectBuildingResult.getProject();
MavenSession session = new MavenSession(getContainer(),
repositorySystemSession, request,
new DefaultMavenExecutionResult());
session.setCurrentProject(project);
session.setProjects(Collections.singletonList(project));
request.setSystemProperties(System.getProperties());
testMojo = (GenerateConfig) lookupConfiguredMojo(session,
newMojoExecution("configure"));
testMojo.getLog().debug(String.format("localRepo = %s",
request.getLocalRepository()));
copyTestProjectResourcesToTarget(getContainer(), project, session);
resolveConfigurationFromRepo(repositorySystemSession, project);
}
private Settings getMavenSettings()
throws ComponentLookupException,
IOException,
XmlPullParserException {
org.apache.maven.settings.MavenSettingsBuilder mavenSettingsBuilder
= (org.apache.maven.settings.MavenSettingsBuilder)
getContainer().lookup(
org.apache.maven.settings.MavenSettingsBuilder.ROLE);
return mavenSettingsBuilder.buildSettings();
}
/**
* This is ugly but there seems to be no other way to accomplish it. The
* artifact that the mojo finds on its own will not resolve to a jar file
* on its own in the test harness. So we use aether to resolve it, by
* cloning the maven default artifact into an aether artifact and feeding
* an artifact request to the repo system obtained by the aether service
* locator.
*/
private void resolveConfigurationFromRepo(
DefaultRepositorySystemSession repositorySystemSession,
MavenProject project)
throws ArtifactResolutionException, MojoExecutionException {
org.apache.maven.artifact.Artifact defaultArtifact =
testMojo.getConfigArtifact();
Artifact artifact = new DefaultArtifact(
defaultArtifact.getGroupId(),
defaultArtifact.getArtifactId(),
null,
defaultArtifact.getType(),
defaultArtifact.getVersion());
List<RemoteRepository> remoteArtifactRepositories =
project.getRemoteProjectRepositories();
DefaultServiceLocator locator =
MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class,
BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class,
FileTransporterFactory.class);
locator.addService(TransporterFactory.class,
HttpTransporterFactory.class);
RepositorySystem repositorySystem = locator.getService(
RepositorySystem.class);
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.setArtifact(artifact);
artifactRequest.setRepositories(remoteArtifactRepositories);
ArtifactResult result = repositorySystem.resolveArtifact(
repositorySystemSession, artifactRequest);
defaultArtifact.setFile(result.getArtifact().getFile());
testMojo.getLog().debug( "Resolved artifact " + artifact + " to " +
result.getArtifact().getFile() + " from "
+ result.getRepository() );
}
/**
* Need manual copy of resources because only parts of the maven lifecycle
* happen automatically with this test harness.
*/
private void copyTestProjectResourcesToTarget(PlexusContainer container,
MavenProject project,
MavenSession session)
throws ComponentLookupException, MojoExecutionException {
Optional<Dependency> resourcesPluginDepOpt =
project.getDependencies().stream()
.filter(d -> Objects.equals(d.getArtifactId(),
MAVEN_RESOURCES_ARTIFACT_ID))
.findFirst();
// don't want to define the version here so we read it from what we have
if (!resourcesPluginDepOpt.isPresent()) {
throw new MojoExecutionException("Require " +
MAVEN_RESOURCES_ARTIFACT_ID);
}
Plugin resourcePlugin = MojoExecutor.plugin(
MojoExecutor.groupId(MAVEN_PLUGINS_GROUP_ID),
MojoExecutor.artifactId(MAVEN_RESOURCES_ARTIFACT_ID),
MojoExecutor.version(resourcesPluginDepOpt.get().getVersion()));
MojoExecutor.executeMojo(resourcePlugin,
MojoExecutor.goal("resources"),
MojoExecutor.configuration(),
MojoExecutor.executionEnvironment(
project, session,
container.lookup(BuildPluginManager.class)));
}
and here are the packages used, quite important to use the classes from the right package but easily confused:
import org.apache.maven.DefaultMaven;
import org.apache.maven.Maven;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;