org.wisdom.maven.mojos.UnitTestMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wisdom-maven-plugin Show documentation
Show all versions of wisdom-maven-plugin Show documentation
The Maven Wisdom Plugin allows building applications for Wisdom.
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);
}
}