/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/IntegrationTestMojo.java
Java | 425 lines | 298 code | 50 blank | 77 comment | 60 complexity | 6f2363b054790e495b3ad459095380f6 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
- package com.atlassian.maven.plugins.amps;
- import com.atlassian.maven.plugins.amps.product.AmpsDefaults;
- import com.google.common.annotations.VisibleForTesting;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
- import org.apache.maven.plugin.MojoExecutionException;
- import org.apache.maven.plugins.annotations.Component;
- 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.project.MavenProject;
- import java.io.File;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import static com.atlassian.maven.plugins.amps.MavenGoals.getReportsDirectory;
- import static com.atlassian.maven.plugins.amps.util.DebugUtils.setNodeDebugPorts;
- import static java.lang.String.format;
- import static java.util.Collections.emptyList;
- import static java.util.Collections.emptyMap;
- import static java.util.Collections.singleton;
- import static java.util.Collections.singletonList;
- import static java.util.stream.Collectors.joining;
- /**
- * Run the integration tests against the webapp.
- */
- @Mojo(name = "integration-test", requiresDependencyResolution = ResolutionScope.TEST)
- public class IntegrationTestMojo extends AbstractTestGroupsHandlerMojo {
- /**
- * Pattern for to use to find integration tests. Only used if no test groups are defined.
- */
- @VisibleForTesting
- @Parameter(property = "functional.test.pattern")
- String functionalTestPattern = MavenGoals.REGEX_INTEGRATION_TESTS;
- /**
- * The directory containing generated test classes of the project being tested.
- */
- @Parameter(property = "project.build.testOutputDirectory", required = true)
- private File testClassesDirectory;
- /**
- * A comma separated list of test groups to run. If not specified, all test groups are run.
- */
- @Parameter(property = "testGroups")
- private String configuredTestGroupsToRun;
- /**
- * If true, no products will be started.
- */
- @Parameter(property = "no.webapp", defaultValue = "false")
- private boolean noWebapp;
- @Component
- private ArtifactHandlerManager artifactHandlerManager;
- /**
- * Whether to skip the integration tests along with any product startups (one of three synonymous flags).
- */
- @Parameter(property = "maven.test.skip", defaultValue = "false")
- private boolean testsSkip;
- /**
- * Whether to skip the integration tests along with any product startups (one of three synonymous flags).
- */
- @Parameter(property = "skipTests", defaultValue = "false")
- private boolean skipTests;
- /**
- * Whether to skip the integration tests along with any product startups (one of three synonymous flags).
- */
- @Parameter(property = "skipITs", defaultValue = "false")
- private boolean skipITs;
- /**
- * The debug port for the first product started. Any other products will be assigned a random free port.
- */
- @Parameter(property = "jvm.debug.port", defaultValue = "0")
- protected int jvmDebugPort;
- /**
- * Whether to suspend the started products' JVMs until a remote debugger is attached.
- */
- @Parameter(property = "jvm.debug.suspend")
- protected boolean jvmDebugSuspend;
- /**
- * Passed as-is to the {@code failsafe:integration-test} goal as its {@code debugForkedProcess} parameter.
- */
- @Parameter(property = "maven.failsafe.debug")
- protected String mavenFailsafeDebug;
- /**
- * The test category as defined by the surefire/failsafe notion of groups. In JUnit4, this affects tests annotated
- * with the {@link org.junit.experimental.categories.Category Category} annotation.
- */
- @Parameter
- protected String category;
- /**
- * By default, this goal performs both failsafe:integration-test and failsafe:verify goals. Set this parameter to
- * {@code true} if you want to defer {@code failsafe:verify} to a later lifecycle phase (e.g. {@code verify}), in
- * order to perform cleanup in the {@code post-integration-test} phase. Please note that you will have to set the
- * execution(s) for failsafe:verify yourself in the pom.xml file, including the {@code reportsDirectory}
- * configuration for the {@code verify} goal.
- */
- @Parameter(property = "skip.IT.verification")
- protected boolean skipITVerification;
- protected void doExecute() throws MojoExecutionException {
- // Should this check for ITs respect the `functionalTestPattern` parameter?
- if (!new File(testClassesDirectory, "it").exists()) {
- getLog().info("No integration tests found");
- return;
- }
- if (skipTests || testsSkip || skipITs) {
- getLog().info("Integration tests skipped");
- return;
- }
- final MavenProject project = getMavenContext().getProject();
- // Workaround for MNG-1682/MNG-2426: force Maven to install artifacts using the "jar" handler
- project.getArtifact().setArtifactHandler(artifactHandlerManager.getArtifactHandler("jar"));
- final MavenGoals goals = getMavenGoals();
- final String pluginJar = targetDirectory.getAbsolutePath() + "/" + finalName + ".jar";
- for (String testGroupId : getTestGroupsToRun()) {
- runTestsForTestGroup(testGroupId, goals, pluginJar, copy(systemPropertyVariables));
- }
- }
- private Collection<String> getTestGroupsToRun() {
- final Collection<String> superclassTestGroupIds = getTestGroupIds();
- if (superclassTestGroupIds.isEmpty()) {
- return singleton(NO_TEST_GROUP);
- } else if (configuredTestGroupsToRun == null) {
- // No test groups configured for this goal, use those configured for the superclass
- return superclassTestGroupIds;
- } else {
- // Find the test groups configured for this goal that are valid according to the superclass
- final Collection<String> testGroupIdsInCommonWithSuperclass = new HashSet<>();
- for (String testGroupId : configuredTestGroupsToRun.split(",")) {
- if (superclassTestGroupIds.contains(testGroupId)) {
- testGroupIdsInCommonWithSuperclass.add(testGroupId);
- } else {
- getLog().warn("Test group '" + testGroupId + "' does not exist");
- }
- }
- return testGroupIdsInCommonWithSuperclass;
- }
- }
- private static Map<String, Object> copy(final Map<String, Object> mapIn) {
- return new HashMap<>(mapIn);
- }
- /**
- * Returns product-specific properties to pass to the container during
- * integration testing. Default implementation does nothing.
- *
- * @param product the {@code Product} object to use
- * @return a {@code Map} of properties to add to the system properties passed
- * to the container
- */
- protected Map<String, String> getProductFunctionalTestProperties(final Product product) {
- return emptyMap();
- }
- private boolean debuggingEnabled() {
- return jvmDebugPort > 0;
- }
- private void runTestsForTestGroup(final String testGroupId, final MavenGoals goals, final String pluginJar,
- final Map<String, Object> systemProperties) throws MojoExecutionException {
- final List<String> includes = getIncludesForTestGroup(testGroupId);
- final List<String> excludes = getExcludesForTestGroup(testGroupId);
- final List<Product> products = getProductsForTestGroup(testGroupId);
- setParallelMode(products);
- if (debuggingEnabled()) {
- setNodeDebugPorts(products, jvmDebugPort);
- }
- final Map<Integer, Product> productsByNodeWebPort = start(products);
- populateNonProductProperties(systemProperties, testGroupId, pluginJar, products);
- populateProductProperties(systemProperties, productsByNodeWebPort);
- if (!noWebapp) {
- waitForProducts(products, true);
- }
- MojoExecutionException thrown = null;
- try {
- doRunTests(testGroupId, goals, systemProperties, includes, excludes);
- } catch (MojoExecutionException e) {
- // If any tests fail an exception will be thrown. We need to catch that and hold onto it, because
- // even if tests fail any running products still need to be stopped
- thrown = e;
- } finally {
- if (!noWebapp) {
- try {
- // Shut down all products
- stopProducts(products);
- } catch (MojoExecutionException e) {
- if (thrown == null) {
- // If no exception was thrown during the tests, propagate the failure to stop
- thrown = e;
- } else {
- // Otherwise, suppress the stop failure and focus on the test failure
- thrown.addSuppressed(e);
- }
- }
- }
- }
- if (thrown != null) {
- // If tests failed, or if any products could not be stopped, propagate the exception
- throw thrown;
- }
- }
- private Map<Integer, Product> start(final Iterable<Product> products) throws MojoExecutionException {
- final Map<Integer, Product> productsByNodeWebPort = new HashMap<>();
- for (final Product product : products) {
- if (product.isInstallPlugin() == null) {
- product.setInstallPlugin(installPlugin);
- }
- if (shouldBuildTestPlugin()) {
- product.addBundledArtifacts(getTestFrameworkPlugins());
- }
- final List<Node> nodes = startIfNecessary(product);
- // when running with -Dno.webapp, then actualHttpPort may not reflect real conditions
- for (final Node node : nodes) {
- final int webPort = node.getWebPort();
- if (productsByNodeWebPort.put(webPort, product) != null) {
- throw new MojoExecutionException(format("HTTP server port %d was already occupied", webPort));
- }
- }
- }
- return productsByNodeWebPort;
- }
- private List<Node> startIfNecessary(final Product product) throws MojoExecutionException {
- final List<Node> nodes;
- if (noWebapp) {
- nodes = product.getNodes();
- validateWebPortsSet(nodes, product.getProtocol());
- } else {
- if (debuggingEnabled()) {
- product.defaultJvmArgs(jvmArgs);
- product.setNodeDebugArgs(jvmDebugSuspend, getLog());
- }
- nodes = getProductHandler(product.getId()).start(product);
- }
- return nodes;
- }
- private void doRunTests(
- final String testGroupId, final MavenGoals goals, final Map<String, Object> systemProperties,
- final List<String> includes, final List<String> excludes)
- throws MojoExecutionException {
- final String reportsDirectory =
- getReportsDirectory(targetDirectory, "group-" + testGroupId, getClassifier(testGroupId));
- goals.runIntegrationTests(
- reportsDirectory, includes, excludes, systemProperties, category, mavenFailsafeDebug);
- if (skipITVerification) {
- getLog().info("Skipping failsafe IT failure verification.");
- } else {
- goals.runVerify(reportsDirectory);
- }
- }
- private void validateWebPortsSet(final Collection<Node> nodes, final String protocol) throws MojoExecutionException {
- for (final Node node : nodes) {
- if (node.getWebPort() <= 0) {
- final String portsWord = nodes.size() == 1 ? "port" : "ports";
- throw new MojoExecutionException(
- format("%s %s must be set when using the no.webapp flag.", protocol, portsWord));
- }
- }
- }
- @VisibleForTesting
- void populateProductProperties(
- final Map<String, Object> systemProperties, final Map<Integer, Product> productsByNodeWebPort) {
- for (final Map.Entry<Integer, Product> entry : productsByNodeWebPort.entrySet()) {
- final int nodeWebPort = entry.getKey();
- final Product product = entry.getValue();
- if (productsByNodeWebPort.size() == 1) {
- putIfNotOverridden(systemProperties, "http.port", String.valueOf(nodeWebPort));
- putIfNotOverridden(systemProperties, "context.path", product.getContextPath());
- }
- final String baseUrl = product.getBaseUrlForPort(nodeWebPort);
- // hard coded system properties...
- putIfNotOverridden(systemProperties,
- "http." + product.getInstanceId() + ".port", String.valueOf(nodeWebPort));
- putIfNotOverridden(systemProperties,
- "context." + product.getInstanceId() + ".path", product.getContextPath());
- putIfNotOverridden(systemProperties, "http." + product.getInstanceId() + ".url", baseUrl);
- putIfNotOverridden(systemProperties,
- "http." + product.getInstanceId() + ".protocol", product.getProtocol());
- putIfNotOverridden(systemProperties, "baseurl." + product.getInstanceId(), baseUrl);
- // yes, this means you only get one base url if multiple products, but that is what selenium would expect
- putIfNotOverridden(systemProperties, "baseurl", baseUrl);
- putHomeDirProperties(product, systemProperties);
- putIfNotOverridden(systemProperties, "product." + product.getInstanceId() + ".id", product.getId());
- putIfNotOverridden(systemProperties,
- "product." + product.getInstanceId() + ".version", product.getVersion());
- systemProperties.putAll(getProductFunctionalTestProperties(product));
- }
- }
- private void populateNonProductProperties(final Map<String, Object> systemProperties, final String testGroupId,
- final String pluginJar, final Collection<Product> products) {
- final String instanceIds = products.stream()
- .map(Product::getInstanceId)
- .collect(joining(","));
- putIfNotOverridden(systemProperties, "plugin.jar", pluginJar);
- putIfNotOverridden(systemProperties, "testGroup", testGroupId);
- putIfNotOverridden(systemProperties, "testGroup.instanceIds", instanceIds);
- systemProperties.putAll(getTestGroupSystemProperties(testGroupId));
- }
- private void putHomeDirProperties(final Product product, final Map<String, Object> properties) {
- final List<File> homeDirectories = getProductHandler(product.getId()).getHomeDirectories(product);
- for (int i = 0; i < homeDirectories.size(); i++) {
- final String homeDirectory = homeDirectories.get(i).getAbsolutePath();
- if (i == 0) {
- // For backward compat with single-node operation, we don't suffix the properties for the first node
- putIfNotOverridden(properties, "homedir", homeDirectory);
- putIfNotOverridden(properties, "homedir." + product.getInstanceId(), homeDirectory);
- }
- final String propertySuffix = "." + i;
- putIfNotOverridden(properties, "homedir" + propertySuffix, homeDirectory);
- putIfNotOverridden(properties, "homedir." + product.getInstanceId() + propertySuffix, homeDirectory);
- }
- }
- /**
- * Adds the property to the map if such property is not overridden in system properties passed to
- * maven executing this mojo.
- *
- * @param map the properties map
- * @param key the key to be added
- * @param value the value to be set. Will be overridden by an existing system property, if one exists
- */
- private static void putIfNotOverridden(final Map<String, Object> map, final String key, final Object value) {
- map.computeIfAbsent(key, aKey -> System.getProperty(aKey, String.valueOf(value)));
- }
- /**
- * Returns the classifier of the test group. Unless specified, this is "tomcat85x", the default container.
- */
- private String getClassifier(String testGroupId) {
- for (TestGroup group : getTestGroups()) {
- if (StringUtils.equals(group.getId(), testGroupId)) {
- if (group.getClassifier() != null) {
- return group.getClassifier();
- } else {
- return AmpsDefaults.DEFAULT_CONTAINER;
- }
- }
- }
- return AmpsDefaults.DEFAULT_CONTAINER;
- }
- private Map<String, String> getTestGroupSystemProperties(String testGroupId) {
- if (NO_TEST_GROUP.equals(testGroupId)) {
- return emptyMap();
- }
- for (TestGroup group : getTestGroups()) {
- if (StringUtils.equals(group.getId(), testGroupId)) {
- return group.getSystemProperties();
- }
- }
- return emptyMap();
- }
- @VisibleForTesting
- List<String> getIncludesForTestGroup(final String testGroupId) {
- if (NO_TEST_GROUP.equals(testGroupId)) {
- return singletonList(functionalTestPattern);
- } else {
- for (TestGroup group : getTestGroups()) {
- if (StringUtils.equals(group.getId(), testGroupId)) {
- final List<String> groupIncludes = group.getIncludes();
- if (groupIncludes.isEmpty()) {
- return singletonList(functionalTestPattern);
- } else {
- return groupIncludes;
- }
- }
- }
- }
- return singletonList(functionalTestPattern);
- }
- private List<String> getExcludesForTestGroup(String testGroupId) {
- if (NO_TEST_GROUP.equals(testGroupId)) {
- return emptyList();
- } else {
- for (TestGroup group : getTestGroups()) {
- if (StringUtils.equals(group.getId(), testGroupId)) {
- return group.getExcludes();
- }
- }
- }
- return emptyList();
- }
- }