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

org.wisdom.maven.mojos.UnitTestMojo Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * Wisdom-Framework
 * %%
 * Copyright (C) 2013 - 2014 Wisdom Framework
 * %%
 * Licensed 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.
 * #L%
 */
package org.wisdom.maven.mojos;

import com.google.common.collect.ImmutableList;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.surefire.report.ReportTestCase;
import org.apache.maven.plugins.surefire.report.ReportTestSuite;
import org.apache.maven.plugins.surefire.report.SurefireReportParser;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.wisdom.maven.WatchingException;
import org.wisdom.maven.utils.PluginExtractor;
import org.wisdom.maven.utils.WatcherUtils;

import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static org.twdata.maven.mojoexecutor.MojoExecutor.*;

/**
 * A Mojo executing Surefire (unit test execution) and bridging it with the Wisdom's watch mode.
 */
@Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, threadSafe = true,
        requiresDependencyResolution = ResolutionScope.TEST)
public class UnitTestMojo extends AbstractWisdomWatcherMojo {

    /**
     * The surefire maven plugin name.
     */
    public static final String MAVEN_SUREFIRE_PLUGIN = "maven-surefire-plugin";

    /**
     * The default version of Surefire.
     */
    public static final String DEFAULT_VERSION = "2.18";

    /**
     * The groupId of the Surefire maven plugin.
     */
    public static final String GROUP_ID = "org.apache.maven.plugins";

    /**
     * The goal used to execute the tests.
     */
    public static final String TEST_GOAL = "test";

    /**
     * If set to {@literal true}, skip the test execution.
     */
    @Parameter(property = "skipTests", defaultValue = "false")
    protected boolean skipTests;

    /**
     * Sets the test selection strategy in watch mode. By default, all test are executed. The {@literal SELECTIVE}
     * policy executes only the test related to the current change.
     */
    @Parameter(defaultValue = "ALL")
    protected TestSelectionPolicy testSelectionPolicy;

    /**
     * The directory where Surefire execution reports are stored.
     */
    private File reports;

    /**
     * Executes the unit tests.
     *
     * @throws MojoExecutionException the tests failed.
     */
    public void execute() throws MojoExecutionException {
        reports = new File(basedir, "target/surefire-reports");
        if (skipTests) {
            getLog().info("Test skipped");
            removeFromWatching();
            return;
        }
        execute(null);
    }

    /**
     * Executes the unit tests.
     *
     * @param testParameter an optional parameter to select the test to execute. this parameter is used in the
     *                      {@literal SELECTIVE} policy.
     * @throws MojoExecutionException the tests failed
     */
    public void execute(String testParameter) throws MojoExecutionException {
        String version = PluginExtractor.getBuildPluginVersion(this, MAVEN_SUREFIRE_PLUGIN);
        Xpp3Dom configuration = PluginExtractor.getBuildPluginConfiguration(this, MAVEN_SUREFIRE_PLUGIN, TEST_GOAL);

        if (version == null) {
            version = DEFAULT_VERSION;
        }

        if (configuration == null) {
            configuration = configuration(
                    element(name("testSourceDirectory"), "${project.build.testSourceDirectory}")
            );
        } else {
            getLog().debug("Loading maven-surefire-plugin configuration:");
            getLog().debug(configuration.toString());
        }

        // Surefire detects the already executed configurations, to avoid re-executing tests.
        // Obviously in our case, this is annoying. So we inject a system property to hack the Sha1 computation used
        // to detect already executed configurations.
        Xpp3Dom sys = configuration.getChild("systemPropertyVariables");
        if (sys == null) {
            sys = new Xpp3Dom("systemPropertyVariables");
            configuration.addChild(sys);
        }

        Xpp3Dom hashHack = new Xpp3Dom("__surefire_hash_hack__");
        hashHack.setValue(String.valueOf(System.currentTimeMillis()));
        sys.addChild(hashHack);

        // The test parameter argument is used to select the test to execute. We inject this parameter here:
        if (testParameter != null) {
            Xpp3Dom test = new Xpp3Dom("test");
            test.setValue(testParameter);
            configuration.addChild(test);

            // Also disables the 'fail if no test'
            Xpp3Dom failIfNotTest = configuration.getChild("failIfNoTests");
            if (failIfNotTest == null) {
                failIfNotTest = new Xpp3Dom("failIfNoTests");
                configuration.addChild(failIfNotTest);
            }
            failIfNotTest.setValue("false");

            // To get the right summary, we need to delete existing surefire-report
            FileUtils.deleteQuietly(reports);
        }

        // Execute the tests.
        executeMojo(
                plugin(
                        groupId(GROUP_ID),
                        artifactId(MAVEN_SUREFIRE_PLUGIN),
                        version(version)
                ),
                goal(TEST_GOAL),
                configuration,
                executionEnvironment(
                        project,
                        session,
                        pluginManager
                )
        );
    }

    /**
     * Do we have to execute some tests when the given file is changed.
     *
     * @param file is the file.
     * @return {@literal true} if the file is a java file from either {@literal src/main/java} or from {@literal
     * src/test/java}, {@literal false} otherwise.
     */
    @Override
    public boolean accept(File file) {
        return
                WatcherUtils.isInDirectory(file, WatcherUtils.getJavaSource(basedir))
                        || WatcherUtils.isInDirectory(file, new File(basedir, "src/test/java"));
    }

    /**
     * Notifies the watcher that a new file is created. It selects and executes the test. Failures and errors are
     * reported in the thrown {@link org.wisdom.maven.WatchingException}.
     *
     * @param file is the file.
     * @return return {@code true}
     * @throws org.wisdom.maven.WatchingException if the test execution failed.
     */
    @Override
    public boolean fileCreated(File file) throws WatchingException {
        // Check selection policy
        String testParameter = null;
        if (testSelectionPolicy == TestSelectionPolicy.SELECTIVE) {
            // The test selection is done using the -Dtest parameter from surefire
            // We should also take care that the changed file is not a 'test', in that case, we run only this one.
            final String filename = file.getName().substring(0, file.getName().lastIndexOf("."));
            if (filename.startsWith("Test")  || filename.endsWith("Test")  || filename.endsWith("TestCase")) {
                testParameter = filename;
            } else {
                // It's a business class
                // Be aware of #365, the selection must select only unit tests, so the expression should be
                // TestFileName*,*FileNameTest*
                // The *FileNameTestCase case can be ignored (included by the second expression)
                testParameter = "Test" + filename + "*,*" + filename + "Test*";
            }
        }

        try {
            execute(testParameter);
            return true;
        } catch (MojoExecutionException e) {
            getLog().debug("An error occurred while executing Surefire", e);
            // Compute the Watching Exception content.
            StringBuilder message = new StringBuilder();
            SurefireReportParser parser = new SurefireReportParser(ImmutableList.of(reports), Locale.ENGLISH);
            try {
                computeTestFailureMessageFromReports(message, parser);
                throw new WatchingException("Unit Test Failures", message.toString(), file, e);
            } catch (MavenReportException reportException) {
                // Cannot read the reports.
                throw new WatchingException("Unit Test Failures", file, reportException);
            }}
    }

    private static void computeTestFailureMessageFromReports(StringBuilder message, SurefireReportParser parser)
            throws MavenReportException {
        List suites = parser.parseXMLReportFiles();
        Map summary = parser.getSummary(suites);
        message
                .append(summary.get("totalTests"))
                .append(" tests, ")
                .append(summary.get("totalErrors"))
                .append(" errors, ")
                .append(summary.get("totalFailures"))
                .append(" failures, ")
                .append(summary.get("totalSkipped"))
                .append(" skipped ")
                .append("(executed in ")
                .append(summary.get("totalElapsedTime"))
                .append("s)
    "); for (ReportTestSuite suite : suites) { if (suite.getNumberOfErrors() > 0 || suite.getNumberOfFailures() > 0) { for (ReportTestCase tc : suite.getTestCases()) { if (tc.getFailure() != null && !"skipped".equalsIgnoreCase((String) tc.getFailure().get("message"))) { message .append("
  • ") .append(tc.getFullName()) .append(" failed: ") .append(tc.getFailure().get("message")) .append("
  • "); } } } } message.append("
"); } /** * Notifies the watcher that a file has been modified. Just calls {@link #fileCreated(java.io.File)} * * @param file is the file. * @return {@literal false} if the pipeline processing must be interrupted for this event. Most watchers should * returns {@literal true} to let other watchers to be notified. * @throws org.wisdom.maven.WatchingException if the watcher failed to process the given file. */ @Override public boolean fileUpdated(File file) throws WatchingException { return fileCreated(file); } /** * Notifies the watcher that a file was deleted. Just calls {@link #fileCreated(java.io.File)} * * @param file the file * @return {@literal false} if the pipeline processing must be interrupted for this event. Most watchers should * return {@literal true} to let other watchers be notified. * @throws org.wisdom.maven.WatchingException if the watcher failed to process the given file. */ @Override public boolean fileDeleted(File file) throws WatchingException { return fileCreated(file); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy