PageRenderTime 73ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/IntegrationTestMojo.java

https://bitbucket.org/atlassian/amps
Java | 425 lines | 298 code | 50 blank | 77 comment | 60 complexity | 6f2363b054790e495b3ad459095380f6 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. package com.atlassian.maven.plugins.amps;
  2. import com.atlassian.maven.plugins.amps.product.AmpsDefaults;
  3. import com.google.common.annotations.VisibleForTesting;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
  6. import org.apache.maven.plugin.MojoExecutionException;
  7. import org.apache.maven.plugins.annotations.Component;
  8. import org.apache.maven.plugins.annotations.Mojo;
  9. import org.apache.maven.plugins.annotations.Parameter;
  10. import org.apache.maven.plugins.annotations.ResolutionScope;
  11. import org.apache.maven.project.MavenProject;
  12. import java.io.File;
  13. import java.util.Collection;
  14. import java.util.HashMap;
  15. import java.util.HashSet;
  16. import java.util.List;
  17. import java.util.Map;
  18. import static com.atlassian.maven.plugins.amps.MavenGoals.getReportsDirectory;
  19. import static com.atlassian.maven.plugins.amps.util.DebugUtils.setNodeDebugPorts;
  20. import static java.lang.String.format;
  21. import static java.util.Collections.emptyList;
  22. import static java.util.Collections.emptyMap;
  23. import static java.util.Collections.singleton;
  24. import static java.util.Collections.singletonList;
  25. import static java.util.stream.Collectors.joining;
  26. /**
  27. * Run the integration tests against the webapp.
  28. */
  29. @Mojo(name = "integration-test", requiresDependencyResolution = ResolutionScope.TEST)
  30. public class IntegrationTestMojo extends AbstractTestGroupsHandlerMojo {
  31. /**
  32. * Pattern for to use to find integration tests. Only used if no test groups are defined.
  33. */
  34. @VisibleForTesting
  35. @Parameter(property = "functional.test.pattern")
  36. String functionalTestPattern = MavenGoals.REGEX_INTEGRATION_TESTS;
  37. /**
  38. * The directory containing generated test classes of the project being tested.
  39. */
  40. @Parameter(property = "project.build.testOutputDirectory", required = true)
  41. private File testClassesDirectory;
  42. /**
  43. * A comma separated list of test groups to run. If not specified, all test groups are run.
  44. */
  45. @Parameter(property = "testGroups")
  46. private String configuredTestGroupsToRun;
  47. /**
  48. * If true, no products will be started.
  49. */
  50. @Parameter(property = "no.webapp", defaultValue = "false")
  51. private boolean noWebapp;
  52. @Component
  53. private ArtifactHandlerManager artifactHandlerManager;
  54. /**
  55. * Whether to skip the integration tests along with any product startups (one of three synonymous flags).
  56. */
  57. @Parameter(property = "maven.test.skip", defaultValue = "false")
  58. private boolean testsSkip;
  59. /**
  60. * Whether to skip the integration tests along with any product startups (one of three synonymous flags).
  61. */
  62. @Parameter(property = "skipTests", defaultValue = "false")
  63. private boolean skipTests;
  64. /**
  65. * Whether to skip the integration tests along with any product startups (one of three synonymous flags).
  66. */
  67. @Parameter(property = "skipITs", defaultValue = "false")
  68. private boolean skipITs;
  69. /**
  70. * The debug port for the first product started. Any other products will be assigned a random free port.
  71. */
  72. @Parameter(property = "jvm.debug.port", defaultValue = "0")
  73. protected int jvmDebugPort;
  74. /**
  75. * Whether to suspend the started products' JVMs until a remote debugger is attached.
  76. */
  77. @Parameter(property = "jvm.debug.suspend")
  78. protected boolean jvmDebugSuspend;
  79. /**
  80. * Passed as-is to the {@code failsafe:integration-test} goal as its {@code debugForkedProcess} parameter.
  81. */
  82. @Parameter(property = "maven.failsafe.debug")
  83. protected String mavenFailsafeDebug;
  84. /**
  85. * The test category as defined by the surefire/failsafe notion of groups. In JUnit4, this affects tests annotated
  86. * with the {@link org.junit.experimental.categories.Category Category} annotation.
  87. */
  88. @Parameter
  89. protected String category;
  90. /**
  91. * By default, this goal performs both failsafe:integration-test and failsafe:verify goals. Set this parameter to
  92. * {@code true} if you want to defer {@code failsafe:verify} to a later lifecycle phase (e.g. {@code verify}), in
  93. * order to perform cleanup in the {@code post-integration-test} phase. Please note that you will have to set the
  94. * execution(s) for failsafe:verify yourself in the pom.xml file, including the {@code reportsDirectory}
  95. * configuration for the {@code verify} goal.
  96. */
  97. @Parameter(property = "skip.IT.verification")
  98. protected boolean skipITVerification;
  99. protected void doExecute() throws MojoExecutionException {
  100. // Should this check for ITs respect the `functionalTestPattern` parameter?
  101. if (!new File(testClassesDirectory, "it").exists()) {
  102. getLog().info("No integration tests found");
  103. return;
  104. }
  105. if (skipTests || testsSkip || skipITs) {
  106. getLog().info("Integration tests skipped");
  107. return;
  108. }
  109. final MavenProject project = getMavenContext().getProject();
  110. // Workaround for MNG-1682/MNG-2426: force Maven to install artifacts using the "jar" handler
  111. project.getArtifact().setArtifactHandler(artifactHandlerManager.getArtifactHandler("jar"));
  112. final MavenGoals goals = getMavenGoals();
  113. final String pluginJar = targetDirectory.getAbsolutePath() + "/" + finalName + ".jar";
  114. for (String testGroupId : getTestGroupsToRun()) {
  115. runTestsForTestGroup(testGroupId, goals, pluginJar, copy(systemPropertyVariables));
  116. }
  117. }
  118. private Collection<String> getTestGroupsToRun() {
  119. final Collection<String> superclassTestGroupIds = getTestGroupIds();
  120. if (superclassTestGroupIds.isEmpty()) {
  121. return singleton(NO_TEST_GROUP);
  122. } else if (configuredTestGroupsToRun == null) {
  123. // No test groups configured for this goal, use those configured for the superclass
  124. return superclassTestGroupIds;
  125. } else {
  126. // Find the test groups configured for this goal that are valid according to the superclass
  127. final Collection<String> testGroupIdsInCommonWithSuperclass = new HashSet<>();
  128. for (String testGroupId : configuredTestGroupsToRun.split(",")) {
  129. if (superclassTestGroupIds.contains(testGroupId)) {
  130. testGroupIdsInCommonWithSuperclass.add(testGroupId);
  131. } else {
  132. getLog().warn("Test group '" + testGroupId + "' does not exist");
  133. }
  134. }
  135. return testGroupIdsInCommonWithSuperclass;
  136. }
  137. }
  138. private static Map<String, Object> copy(final Map<String, Object> mapIn) {
  139. return new HashMap<>(mapIn);
  140. }
  141. /**
  142. * Returns product-specific properties to pass to the container during
  143. * integration testing. Default implementation does nothing.
  144. *
  145. * @param product the {@code Product} object to use
  146. * @return a {@code Map} of properties to add to the system properties passed
  147. * to the container
  148. */
  149. protected Map<String, String> getProductFunctionalTestProperties(final Product product) {
  150. return emptyMap();
  151. }
  152. private boolean debuggingEnabled() {
  153. return jvmDebugPort > 0;
  154. }
  155. private void runTestsForTestGroup(final String testGroupId, final MavenGoals goals, final String pluginJar,
  156. final Map<String, Object> systemProperties) throws MojoExecutionException {
  157. final List<String> includes = getIncludesForTestGroup(testGroupId);
  158. final List<String> excludes = getExcludesForTestGroup(testGroupId);
  159. final List<Product> products = getProductsForTestGroup(testGroupId);
  160. setParallelMode(products);
  161. if (debuggingEnabled()) {
  162. setNodeDebugPorts(products, jvmDebugPort);
  163. }
  164. final Map<Integer, Product> productsByNodeWebPort = start(products);
  165. populateNonProductProperties(systemProperties, testGroupId, pluginJar, products);
  166. populateProductProperties(systemProperties, productsByNodeWebPort);
  167. if (!noWebapp) {
  168. waitForProducts(products, true);
  169. }
  170. MojoExecutionException thrown = null;
  171. try {
  172. doRunTests(testGroupId, goals, systemProperties, includes, excludes);
  173. } catch (MojoExecutionException e) {
  174. // If any tests fail an exception will be thrown. We need to catch that and hold onto it, because
  175. // even if tests fail any running products still need to be stopped
  176. thrown = e;
  177. } finally {
  178. if (!noWebapp) {
  179. try {
  180. // Shut down all products
  181. stopProducts(products);
  182. } catch (MojoExecutionException e) {
  183. if (thrown == null) {
  184. // If no exception was thrown during the tests, propagate the failure to stop
  185. thrown = e;
  186. } else {
  187. // Otherwise, suppress the stop failure and focus on the test failure
  188. thrown.addSuppressed(e);
  189. }
  190. }
  191. }
  192. }
  193. if (thrown != null) {
  194. // If tests failed, or if any products could not be stopped, propagate the exception
  195. throw thrown;
  196. }
  197. }
  198. private Map<Integer, Product> start(final Iterable<Product> products) throws MojoExecutionException {
  199. final Map<Integer, Product> productsByNodeWebPort = new HashMap<>();
  200. for (final Product product : products) {
  201. if (product.isInstallPlugin() == null) {
  202. product.setInstallPlugin(installPlugin);
  203. }
  204. if (shouldBuildTestPlugin()) {
  205. product.addBundledArtifacts(getTestFrameworkPlugins());
  206. }
  207. final List<Node> nodes = startIfNecessary(product);
  208. // when running with -Dno.webapp, then actualHttpPort may not reflect real conditions
  209. for (final Node node : nodes) {
  210. final int webPort = node.getWebPort();
  211. if (productsByNodeWebPort.put(webPort, product) != null) {
  212. throw new MojoExecutionException(format("HTTP server port %d was already occupied", webPort));
  213. }
  214. }
  215. }
  216. return productsByNodeWebPort;
  217. }
  218. private List<Node> startIfNecessary(final Product product) throws MojoExecutionException {
  219. final List<Node> nodes;
  220. if (noWebapp) {
  221. nodes = product.getNodes();
  222. validateWebPortsSet(nodes, product.getProtocol());
  223. } else {
  224. if (debuggingEnabled()) {
  225. product.defaultJvmArgs(jvmArgs);
  226. product.setNodeDebugArgs(jvmDebugSuspend, getLog());
  227. }
  228. nodes = getProductHandler(product.getId()).start(product);
  229. }
  230. return nodes;
  231. }
  232. private void doRunTests(
  233. final String testGroupId, final MavenGoals goals, final Map<String, Object> systemProperties,
  234. final List<String> includes, final List<String> excludes)
  235. throws MojoExecutionException {
  236. final String reportsDirectory =
  237. getReportsDirectory(targetDirectory, "group-" + testGroupId, getClassifier(testGroupId));
  238. goals.runIntegrationTests(
  239. reportsDirectory, includes, excludes, systemProperties, category, mavenFailsafeDebug);
  240. if (skipITVerification) {
  241. getLog().info("Skipping failsafe IT failure verification.");
  242. } else {
  243. goals.runVerify(reportsDirectory);
  244. }
  245. }
  246. private void validateWebPortsSet(final Collection<Node> nodes, final String protocol) throws MojoExecutionException {
  247. for (final Node node : nodes) {
  248. if (node.getWebPort() <= 0) {
  249. final String portsWord = nodes.size() == 1 ? "port" : "ports";
  250. throw new MojoExecutionException(
  251. format("%s %s must be set when using the no.webapp flag.", protocol, portsWord));
  252. }
  253. }
  254. }
  255. @VisibleForTesting
  256. void populateProductProperties(
  257. final Map<String, Object> systemProperties, final Map<Integer, Product> productsByNodeWebPort) {
  258. for (final Map.Entry<Integer, Product> entry : productsByNodeWebPort.entrySet()) {
  259. final int nodeWebPort = entry.getKey();
  260. final Product product = entry.getValue();
  261. if (productsByNodeWebPort.size() == 1) {
  262. putIfNotOverridden(systemProperties, "http.port", String.valueOf(nodeWebPort));
  263. putIfNotOverridden(systemProperties, "context.path", product.getContextPath());
  264. }
  265. final String baseUrl = product.getBaseUrlForPort(nodeWebPort);
  266. // hard coded system properties...
  267. putIfNotOverridden(systemProperties,
  268. "http." + product.getInstanceId() + ".port", String.valueOf(nodeWebPort));
  269. putIfNotOverridden(systemProperties,
  270. "context." + product.getInstanceId() + ".path", product.getContextPath());
  271. putIfNotOverridden(systemProperties, "http." + product.getInstanceId() + ".url", baseUrl);
  272. putIfNotOverridden(systemProperties,
  273. "http." + product.getInstanceId() + ".protocol", product.getProtocol());
  274. putIfNotOverridden(systemProperties, "baseurl." + product.getInstanceId(), baseUrl);
  275. // yes, this means you only get one base url if multiple products, but that is what selenium would expect
  276. putIfNotOverridden(systemProperties, "baseurl", baseUrl);
  277. putHomeDirProperties(product, systemProperties);
  278. putIfNotOverridden(systemProperties, "product." + product.getInstanceId() + ".id", product.getId());
  279. putIfNotOverridden(systemProperties,
  280. "product." + product.getInstanceId() + ".version", product.getVersion());
  281. systemProperties.putAll(getProductFunctionalTestProperties(product));
  282. }
  283. }
  284. private void populateNonProductProperties(final Map<String, Object> systemProperties, final String testGroupId,
  285. final String pluginJar, final Collection<Product> products) {
  286. final String instanceIds = products.stream()
  287. .map(Product::getInstanceId)
  288. .collect(joining(","));
  289. putIfNotOverridden(systemProperties, "plugin.jar", pluginJar);
  290. putIfNotOverridden(systemProperties, "testGroup", testGroupId);
  291. putIfNotOverridden(systemProperties, "testGroup.instanceIds", instanceIds);
  292. systemProperties.putAll(getTestGroupSystemProperties(testGroupId));
  293. }
  294. private void putHomeDirProperties(final Product product, final Map<String, Object> properties) {
  295. final List<File> homeDirectories = getProductHandler(product.getId()).getHomeDirectories(product);
  296. for (int i = 0; i < homeDirectories.size(); i++) {
  297. final String homeDirectory = homeDirectories.get(i).getAbsolutePath();
  298. if (i == 0) {
  299. // For backward compat with single-node operation, we don't suffix the properties for the first node
  300. putIfNotOverridden(properties, "homedir", homeDirectory);
  301. putIfNotOverridden(properties, "homedir." + product.getInstanceId(), homeDirectory);
  302. }
  303. final String propertySuffix = "." + i;
  304. putIfNotOverridden(properties, "homedir" + propertySuffix, homeDirectory);
  305. putIfNotOverridden(properties, "homedir." + product.getInstanceId() + propertySuffix, homeDirectory);
  306. }
  307. }
  308. /**
  309. * Adds the property to the map if such property is not overridden in system properties passed to
  310. * maven executing this mojo.
  311. *
  312. * @param map the properties map
  313. * @param key the key to be added
  314. * @param value the value to be set. Will be overridden by an existing system property, if one exists
  315. */
  316. private static void putIfNotOverridden(final Map<String, Object> map, final String key, final Object value) {
  317. map.computeIfAbsent(key, aKey -> System.getProperty(aKey, String.valueOf(value)));
  318. }
  319. /**
  320. * Returns the classifier of the test group. Unless specified, this is "tomcat85x", the default container.
  321. */
  322. private String getClassifier(String testGroupId) {
  323. for (TestGroup group : getTestGroups()) {
  324. if (StringUtils.equals(group.getId(), testGroupId)) {
  325. if (group.getClassifier() != null) {
  326. return group.getClassifier();
  327. } else {
  328. return AmpsDefaults.DEFAULT_CONTAINER;
  329. }
  330. }
  331. }
  332. return AmpsDefaults.DEFAULT_CONTAINER;
  333. }
  334. private Map<String, String> getTestGroupSystemProperties(String testGroupId) {
  335. if (NO_TEST_GROUP.equals(testGroupId)) {
  336. return emptyMap();
  337. }
  338. for (TestGroup group : getTestGroups()) {
  339. if (StringUtils.equals(group.getId(), testGroupId)) {
  340. return group.getSystemProperties();
  341. }
  342. }
  343. return emptyMap();
  344. }
  345. @VisibleForTesting
  346. List<String> getIncludesForTestGroup(final String testGroupId) {
  347. if (NO_TEST_GROUP.equals(testGroupId)) {
  348. return singletonList(functionalTestPattern);
  349. } else {
  350. for (TestGroup group : getTestGroups()) {
  351. if (StringUtils.equals(group.getId(), testGroupId)) {
  352. final List<String> groupIncludes = group.getIncludes();
  353. if (groupIncludes.isEmpty()) {
  354. return singletonList(functionalTestPattern);
  355. } else {
  356. return groupIncludes;
  357. }
  358. }
  359. }
  360. }
  361. return singletonList(functionalTestPattern);
  362. }
  363. private List<String> getExcludesForTestGroup(String testGroupId) {
  364. if (NO_TEST_GROUP.equals(testGroupId)) {
  365. return emptyList();
  366. } else {
  367. for (TestGroup group : getTestGroups()) {
  368. if (StringUtils.equals(group.getId(), testGroupId)) {
  369. return group.getExcludes();
  370. }
  371. }
  372. }
  373. return emptyList();
  374. }
  375. }