
com.github.htfv.maven.plugins.testing.MavenTest Maven / Gradle / Ivy
package com.github.htfv.maven.plugins.testing;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.maven.Maven;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
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.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import com.github.htfv.maven.plugins.testing.internal.MavenTestClassLoader;
import com.github.htfv.maven.plugins.testing.internal.MavenTestContext;
import com.github.htfv.maven.plugins.testing.internal.PlexusContainer;
import com.github.htfv.maven.plugins.testing.internal.ProjectWorkspaceReader;
/**
* Base class for a Maven integration test.
*
* If the test class is annotated with the {@link ExecuteMaven} annotation, its
* parameters are used to execute a Maven project. There is maximum one project
* instance per test class.
*
* @author htfv (Aliaksei Lahachou)
*/
public abstract class MavenTest
{
/**
* Specifies that a Maven project shall be executed before the test class.
*
* @author htfv (Aliaksei Lahachou)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ExecuteMaven
{
/**
* The directory containing the project.
*/
String directory();
/**
* The file name of the project, default is {@code pom.xml}.
*/
String file() default "pom.xml";
/**
* The goals to execute, default is {@code validate}.
*/
String[] goals() default { "validate" };
/**
* The profiles to activate.
*/
String[] profiles() default { };
}
/**
* Holds the {@link MavenTestContext}.
*/
private static final ThreadLocal CONTEXT =
new ThreadLocal();
/**
* Holds the previous thread context {@link ClassLoader}.
*/
private static final ThreadLocal OLD_CLASS_LOADER =
new ThreadLocal();
/**
* Tears down the {@link MavenTestContext} when all tests have been
* executed.
*
* @throws Exception
* if the {@link PlexusContainer} failed to shut down.
*/
@AfterClass
public static void tearDownContext() throws Exception
{
MavenTestContext context = CONTEXT.get();
if (context != null)
{
CONTEXT.set(null);
context.tearDown();
}
}
/**
* Executes a Maven project if {@link ExecuteMaven} annotation is present.
*
* @throws Exception
* if the Maven project could not be executed.
*/
private void executeMaven() throws Exception
{
//
// Get execution parameters.
//
ExecuteMaven executeMaven = this.getClass().getAnnotation(ExecuteMaven.class);
if (executeMaven == null)
{
return;
}
File directory = new File(executeMaven.directory());
File file = new File(directory, executeMaven.file());
List goals = Arrays.asList(executeMaven.goals());
List profiles = Arrays.asList(executeMaven.profiles());
//
// Pass versions of the workspace artifacts as properties.
//
Properties userProperties = new Properties();
List workspaceArtifacts = getWorkspaceArtifacts();
for (Artifact workspaceArtifact : workspaceArtifacts)
{
if ("pom".equals(workspaceArtifact.getExtension()))
{
continue;
}
String propertyName = ""
+ formatId(workspaceArtifact.getGroupId()) + "."
+ formatId(workspaceArtifact.getArtifactId()) + "."
+ "version";
userProperties.setProperty(propertyName, workspaceArtifact.getVersion());
}
//
// Create execution request.
//
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setActiveProfiles(profiles);
request.setGoals(goals);
request.setPom(file);
request.setUserProperties(userProperties);
request.setWorkspaceReader(new ProjectWorkspaceReader(workspaceArtifacts));
//
// Execute Maven.
//
Maven maven = getPlexusContainer().lookup(Maven.class);
MavenExecutionResult result = maven.execute(request);
for (Throwable e : result.getExceptions())
{
throw (Exception) e;
}
//
// Save project reference in the context.
//
MavenTestContext context = getContext();
context.setBasedir(directory);
context.setMavenProject(result.getProject());
}
/**
* Replaces all non-alpha-numeric characters with dashes.
*
* @param unformattedId
* the unformatted string.
*
* @return The formatted string.
*/
private String formatId(final String unformattedId)
{
return unformattedId.replaceAll("[^0-9A-Za-z]+", "-");
}
/**
* Gets the base directory of the tested project.
*
* @return The base directory of the tested project.
*/
protected File getBasedir()
{
return getContext().getBasedir();
}
/**
* Gets the {@link MavenTestContext}.
*
* @return The {@link MavenTestContext}.
*/
private MavenTestContext getContext()
{
//
// Check to see whether context was already created.
//
MavenTestContext context = CONTEXT.get();
if (context != null)
{
return context;
}
//
// Initialize new context.
//
context = new MavenTestContext();
CONTEXT.set(context);
return context;
}
/**
* Finds the workspace JAR artifact.
*
* @param mavenProject
* the loaded {@link MavenProject}, may be used to determine
* groupId, artifactId, and output directory of the project, if
* they cannot be determined from other sources.
*
* @return The workspace JAR artifact.
*/
private Artifact getJarArtifact(final MavenProject mavenProject)
{
//
// Look for plugin.xml or components.xml to determine the output
// directory. This will guarantee that we find the right directory even
// when some plugin like Cobertura calls us from its own directory.
//
ClassLoader classLoader = OLD_CLASS_LOADER.get();
if (classLoader == null)
{
classLoader = MavenTest.class.getClassLoader();
}
//
// Try META-INF/maven/plugin.xml first.
//
Artifact jarArtifact = getJarArtifactFromPluginXml(classLoader);
if (jarArtifact != null)
{
return jarArtifact;
}
//
// Try META-INF/plexus/components.xml
//
jarArtifact = getJarArtifactFromComponentsXml(classLoader, mavenProject);
if (jarArtifact != null)
{
return jarArtifact;
}
return null;
}
/**
* Finds the {@code META-INF/plexus/components.xml} file in this project and
* creates the JAR artifact from it.
*
* @param classLoader
* the {@link ClassLoader} used to load {@code components.xml}.
* @param mavenProject
* the {@link MavenProject} used to read artifact coordinates.
*
* @return The workspace JAR artifact.
*/
private Artifact getJarArtifactFromComponentsXml(final ClassLoader classLoader,
final MavenProject mavenProject)
{
try
{
Enumeration componentsXmlUrls =
classLoader.getResources("META-INF/plexus/components.xml");
String basedir = new File(".").getCanonicalPath();
while (componentsXmlUrls.hasMoreElements())
{
URL componentsXmlUrl = componentsXmlUrls.nextElement();
File componentsXmlFile = urlToFile(componentsXmlUrl);
if (componentsXmlFile == null
|| !componentsXmlFile.getAbsolutePath().startsWith(basedir))
{
continue;
}
String groupId = mavenProject.getGroupId();
String artifactId = mavenProject.getArtifactId();
String version = mavenProject.getVersion();
return new DefaultArtifact(groupId, artifactId, null, "jar", version)
.setFile(componentsXmlFile.getParentFile().getParentFile().getParentFile());
}
return null;
}
catch (IOException e)
{
return null;
}
}
/**
* Finds the {@code META-INF/maven/plugin.xml} file in this project and
* creates the JAR artifact from it.
*
* @param classLoader
* the {@link ClassLoader} used to load {@code plugin.xml}.
*
* @return The workspace JAR artifact.
*/
private Artifact getJarArtifactFromPluginXml(final ClassLoader classLoader)
{
try
{
Enumeration pluginXmlUrls =
classLoader.getResources("META-INF/maven/plugin.xml");
String basedir = new File(".").getCanonicalPath();
while (pluginXmlUrls.hasMoreElements())
{
URL pluginXmlUrl = pluginXmlUrls.nextElement();
File pluginXmlFile = urlToFile(pluginXmlUrl);
if (pluginXmlFile == null || !pluginXmlFile.getAbsolutePath().startsWith(basedir))
{
continue;
}
Artifact jarArtifact = getJarArtifactFromPluginXml(pluginXmlFile);
if (jarArtifact == null)
{
continue;
}
return jarArtifact;
}
return null;
}
catch (IOException e)
{
return null;
}
}
/**
* Parses the {@code plugin.xml} file and creates the JAR artifact from it.
*
* @param pluginXmlFile
* the {@code plugin.xml} file.
*
* @return The workspace JAR artifact.
*/
private Artifact getJarArtifactFromPluginXml(final File pluginXmlFile)
{
FileInputStream pluginXmlStream = null;
try
{
pluginXmlStream = new FileInputStream(pluginXmlFile);
Xpp3Dom plugin = Xpp3DomBuilder.build(pluginXmlStream, "UTF-8");
String groupId = plugin.getChild("groupId").getValue();
String artifactId = plugin.getChild("artifactId").getValue();
String version = plugin.getChild("version").getValue();
return new DefaultArtifact(groupId, artifactId, null, "jar", version)
.setFile(pluginXmlFile.getParentFile().getParentFile().getParentFile());
}
catch (IOException e)
{
return null;
}
catch (XmlPullParserException e)
{
return null;
}
finally
{
IOUtil.close(pluginXmlStream);
}
}
/**
* Gets the {@link MavenProject}.
*
* @return The {@link MavenProject}.
*/
protected MavenProject getMavenProject()
{
return getContext().getMavenProject();
}
/**
* Gets the {@link PlexusContainer}.
*
* @return The {@link PlexusContainer}.
*/
private PlexusContainer getPlexusContainer()
{
MavenTestContext context = getContext();
PlexusContainer plexusContainer = context.getPlexusContainer();
if (plexusContainer != null)
{
return plexusContainer;
}
plexusContainer = new PlexusContainer();
context.setPlexusContainer(plexusContainer);
return plexusContainer;
}
/**
* Gets a {@link Plugin} of the configured {@link MavenProject}.
*
* @param groupId
* the groupId.
* @param artifactId
* the artifactId.
*
* @return The {@link Plugin}.
*
* @throws Exception
* if {@link BuildConfigurator#configure} failed.
*/
protected Plugin getProjectPlugin(final String groupId, final String artifactId)
throws Exception
{
for (Plugin plugin : getMavenProject().getBuildPlugins())
{
if (ObjectUtils.equals(plugin.getGroupId(), groupId)
&& ObjectUtils.equals(plugin.getArtifactId(), artifactId))
{
return plugin;
}
}
return null;
}
/**
* Gets a {@link PluginExecution} of the configured {@link Plugin}.
*
* @param groupId
* the groupId.
* @param artifactId
* the artifactId.
* @param executionId
* the execution identifier.
*
* @return The {@link PluginExecution}.
*
* @throws Exception
* if {@link BuildConfigurator#configure} failed.
*/
protected PluginExecution getProjectPluginExecution(final String groupId,
final String artifactId, final String executionId) throws Exception
{
Plugin plugin = getProjectPlugin(groupId, artifactId);
for (PluginExecution execution : plugin.getExecutions())
{
if (ObjectUtils.equals(execution.getId(), executionId))
{
return execution;
}
}
return null;
}
/**
* Finds the workspace artifacts.
*
* @return The workspace artifacts.
*/
private List getWorkspaceArtifacts()
{
MavenTestContext context = getContext();
List workspaceArtifacts = context.getWorkspaceArtifacts();
if (workspaceArtifacts != null)
{
return workspaceArtifacts;
}
workspaceArtifacts = new ArrayList();
//
// Add the POM artifact.
//
File pomFile = new File("pom.xml");
MavenProject mavenProject = loadMavenProject(pomFile);
if (mavenProject != null)
{
String groupId = mavenProject.getGroupId();
String artifactId = mavenProject.getArtifactId();
String version = mavenProject.getVersion();
workspaceArtifacts.add(
new DefaultArtifact(groupId, artifactId, "pom", version)
.setFile(pomFile));
}
//
// Add the JAR artifact.
//
Artifact jarArtifact = getJarArtifact(mavenProject);
if (jarArtifact != null)
{
workspaceArtifacts.add(jarArtifact);
}
context.setWorkspaceArtifacts(workspaceArtifacts);
return workspaceArtifacts;
}
/**
* Initializes the {@link MavenTest}.
*
* @throws Exception
* if the test could not be initialized.
*/
@Before
public final void initializeMavenTest() throws Exception
{
setTestClassLoader();
executeMaven();
}
/**
* Loads a {@link MavenProject}.
*
* @param pomFile
* the POM file.
*
* @return The {@link MavenProject}.
*/
private MavenProject loadMavenProject(final File pomFile)
{
if (!pomFile.exists())
{
return null;
}
try
{
PlexusContainer plexusContainer = getPlexusContainer();
MavenExecutionRequest mavenExecutionRequest =
new DefaultMavenExecutionRequest();
MavenExecutionRequestPopulator mavenExecutionRequestPopulator =
plexusContainer.lookup(MavenExecutionRequestPopulator.class);
mavenExecutionRequestPopulator.populateDefaults(mavenExecutionRequest);
ProjectBuildingRequest projectBuildingRequest =
mavenExecutionRequest.getProjectBuildingRequest();
ProjectBuilder projectBuilder =
plexusContainer.lookup(ProjectBuilder.class);
ProjectBuildingResult projectBuildingResult =
projectBuilder.build(pomFile, projectBuildingRequest);
return projectBuildingResult.getProject();
}
catch (Exception e)
{
return null;
}
}
/**
* Restores the previous thread context {@link ClassLoader}.
*/
@After
public final void restoreClassLoader()
{
Thread.currentThread().setContextClassLoader(OLD_CLASS_LOADER.get());
}
/**
* Sets the {@link MavenTestClassLoader}.
*/
private void setTestClassLoader()
{
final Thread currentThread = Thread.currentThread();
final ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
OLD_CLASS_LOADER.set(threadContextClassLoader);
final ClassLoader parentClassLoader = ObjectUtils.defaultIfNull(
threadContextClassLoader, getClass().getClassLoader());
AccessController.doPrivileged(new PrivilegedAction()
{
@Override
public Boolean run()
{
currentThread.setContextClassLoader(
new MavenTestClassLoader(parentClassLoader));
return Boolean.TRUE;
}
});
}
/**
* Converts {@link URL} to {@link File}.
*
* @param url
* the {@link URL} to convert.
*
* @return The {@link File} or {@code null} if {@link URL} could not be
* converted.
*/
private File urlToFile(final URL url)
{
if (!"file".equals(url.getProtocol()))
{
return null;
}
try
{
return new File(url.toURI());
}
catch (URISyntaxException e)
{
return new File(url.getPath());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy