All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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