1   package com.atlassian.maven.plugins.amps;
2   
3   import static com.google.common.collect.Iterables.transform;
4   
5   import java.io.File;
6   import java.util.Collections;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.concurrent.Callable;
13  import java.util.concurrent.Executor;
14  import java.util.concurrent.ExecutorService;
15  import java.util.concurrent.Executors;
16  
17  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
18  import org.apache.maven.plugin.MojoExecutionException;
19  import org.apache.maven.project.MavenProject;
20  import org.jfrog.maven.annomojo.annotations.MojoComponent;
21  import org.jfrog.maven.annomojo.annotations.MojoGoal;
22  import org.jfrog.maven.annomojo.annotations.MojoParameter;
23  import org.jfrog.maven.annomojo.annotations.MojoRequiresDependencyResolution;
24  
25  import com.atlassian.maven.plugins.amps.product.ProductHandler;
26  import com.atlassian.util.concurrent.AsyncCompleter;
27  import com.atlassian.util.concurrent.ExceptionPolicy;
28  import com.google.common.base.Function;
29  
30  /**
31   * Run the integration tests against the webapp
32   */
33  @MojoGoal("integration-test")
34  @MojoRequiresDependencyResolution("test")
35  public class IntegrationTestMojo extends AbstractTestGroupsHandlerMojo
36  {
37      /**
38       * Pattern for to use to find integration tests.  Only used if no test groups are defined.
39       */
40      @MojoParameter(expression = "${functional.test.pattern}")
41      private String functionalTestPattern = "it/**";
42  
43      /**
44       * The directory containing generated test classes of the project being tested.
45       */
46      @MojoParameter(expression = "${project.build.testOutputDirectory}", required = true)
47      private File testClassesDirectory;
48  
49      /**
50       * A comma separated list of test groups to run.  If not specified, all
51       * test groups are run.
52       */
53      @MojoParameter(expression = "${testGroups}")
54      private String configuredTestGroupsToRun;
55      
56      /**
57       * Whether the reference application will not be started or not
58       */
59      @MojoParameter(expression = "${no.webapp}", defaultValue = "false")
60      private boolean noWebapp = false;
61  
62      @MojoComponent
63      private ArtifactHandlerManager artifactHandlerManager;
64  
65      @MojoParameter(expression="${maven.test.skip}", defaultValue = "false")
66      private boolean testsSkip = false;
67  
68      @MojoParameter(expression="${skipTests}", defaultValue = "false")
69      private boolean skipTests = false;
70      
71      /**
72       * Skip the integration tests along with any product startups
73       */
74      @MojoParameter(expression="${skipITs}", defaultValue = "false")
75      private boolean skipITs = false;
76  
77      protected void doExecute() throws MojoExecutionException
78      {
79          final MavenProject project = getMavenContext().getProject();
80  
81          // workaround for MNG-1682/MNG-2426: force maven to install artifact using the "jar" handler
82          project.getArtifact().setArtifactHandler(artifactHandlerManager.getArtifactHandler("jar"));
83  
84          if (!new File(testClassesDirectory, "it").exists())
85          {
86              getLog().info("No integration tests found");
87              return;
88          }
89  
90          if (skipTests || testsSkip || skipITs)
91          {
92              getLog().info("Integration tests skipped");
93              return;
94          }
95  
96          final MavenGoals goals = getMavenGoals();
97          final String pluginJar = targetDirectory.getAbsolutePath() + "/" + finalName + ".jar";
98  
99          final Set<String> configuredTestGroupIds = getTestGroupIds();
100         if (configuredTestGroupIds.isEmpty())
101         {
102             runTestsForTestGroup(NO_TEST_GROUP, goals, pluginJar, copy(systemPropertyVariables));
103         }
104         else if (configuredTestGroupsToRun != null)
105         {
106             String[] testGroupIdsToRun = configuredTestGroupsToRun.split(",");
107             
108             // now run the tests
109             for (String testGroupId : testGroupIdsToRun)
110             {
111                 if (!configuredTestGroupIds.contains(testGroupId))
112                 {
113                     getLog().warn("Test group " + testGroupId + " does not exist");
114                 }
115                 else
116                 {
117                     runTestsForTestGroup(testGroupId, goals, pluginJar, copy(systemPropertyVariables));
118                 }
119             }
120         }
121         else
122         {
123             for (String testGroupId : configuredTestGroupIds)
124             {
125                 runTestsForTestGroup(testGroupId, goals, pluginJar, copy(systemPropertyVariables));
126             }
127         }
128     }
129 
130     private Map<String,Object> copy(Map<String,Object> systemPropertyVariables)
131     {
132         return new HashMap<String,Object>(systemPropertyVariables);
133     }
134 
135     /**
136      * Returns product-specific properties to pass to the container during
137      * integration testing. Default implementation does nothing.
138      * @param product the {@code Product} object to use
139      * @return a {@code Map} of properties to add to the system properties passed
140      * to the container
141      */
142     protected Map<String, String> getProductFunctionalTestProperties(Product product)
143     {
144         return Collections.emptyMap();
145     }
146 
147     private Set<String> getTestGroupIds() throws MojoExecutionException
148     {
149         Set<String> ids = new HashSet<String>();
150 
151         //ids.addAll(ProductHandlerFactory.getIds());
152         for (TestGroup group : getTestGroups())
153         {
154             ids.add(group.getId());
155         }
156 
157         return ids;
158     }
159 
160     private void runTestsForTestGroup(String testGroupId, MavenGoals goals, String pluginJar, Map<String,Object> systemProperties) throws MojoExecutionException
161     {
162         List<String> includes = getIncludesForTestGroup(testGroupId);
163         List<String> excludes = getExcludesForTestGroup(testGroupId);
164 
165         List<ProductExecution> productExecutions = getTestGroupProductExecutions(testGroupId);
166 
167         ExecutorService executor = Executors.newFixedThreadPool(productExecutions.size());
168         AsyncCompleter completer = new AsyncCompleter.Builder(executor).handleExceptions(ExceptionPolicy.Policies.THROW).build();
169         
170         // Install the plugin in each product and start it
171         for (Map<String, Object> productProperties : completer.invokeAll(transform(productExecutions, productStarter(pluginJar))))
172         {
173             systemProperties.putAll(productProperties);
174         }
175         if (productExecutions.size() == 1)
176         {
177             Product product = productExecutions.get(0).getProduct();
178             systemProperties.put("http.port", systemProperties.get("http." + product.getInstanceId() + ".port"));
179             systemProperties.put("context.path", product.getContextPath());
180         }
181         systemProperties.put("testGroup", testGroupId);
182         systemProperties.putAll(getTestGroupSystemProperties(testGroupId));
183 
184         // Actually run the tests
185         goals.runTests("group-" + testGroupId, containerId, includes, excludes, systemProperties, targetDirectory);
186 
187         // Shut all products down
188         if (!noWebapp)
189         {
190             for (Void _ : completer.invokeAll(transform(productExecutions, productStopper()))) {}
191         }
192         executor.shutdown();
193     }
194 
195     private Function<ProductExecution, Callable<Map<String, Object>>> productStarter(final String pluginJar)
196     {
197         return new Function<ProductExecution, Callable<Map<String,Object>>>()
198         {
199             @Override
200             public Callable<Map<String, Object>> apply(ProductExecution productExecution)
201             {
202                 return new ProductStarter(pluginJar, productExecution);
203             }
204         };
205     }
206     
207     private final class ProductStarter implements Callable<Map<String, Object>>
208     {
209         private final String pluginJar;
210         private final ProductExecution productExecution;
211 
212         public ProductStarter(String pluginJar, ProductExecution productExecution)
213         {
214             this.pluginJar = pluginJar;
215             this.productExecution = productExecution;
216         }
217 
218         @Override
219         public Map<String, Object> call() throws Exception
220         {
221             Map<String, Object> properties = new HashMap<String, Object>();
222             ProductHandler productHandler = productExecution.getProductHandler();
223             Product product = productExecution.getProduct();
224             product.setInstallPlugin(installPlugin);
225 
226             int actualHttpPort = 0;
227             if (!noWebapp)
228             {
229                 actualHttpPort = productHandler.start(product);
230             }
231 
232             String baseUrl = MavenGoals.getBaseUrl(product.getServer(), actualHttpPort, product.getContextPath());
233             // hard coded system properties...
234             properties.put("http." + product.getInstanceId() + ".port", String.valueOf(actualHttpPort));
235             properties.put("context." + product.getInstanceId() + ".path", product.getContextPath());
236             properties.put("http." + product.getInstanceId() + ".url", MavenGoals.getBaseUrl(product.getServer(), actualHttpPort, product.getContextPath()));
237 
238             properties.put("baseurl." + product.getInstanceId(), baseUrl);
239             properties.put("plugin.jar", pluginJar);
240 
241             properties.put("baseurl", baseUrl);
242             
243             properties.put("homedir." + product.getInstanceId(), productHandler.getHomeDirectory(product).getAbsolutePath());
244             if (!properties.containsKey("homedir"))
245             {
246                 properties.put("homedir", productHandler.getHomeDirectory(product).getAbsolutePath());
247             }
248             
249             properties.putAll(getProductFunctionalTestProperties(product));
250             return properties;
251         }
252     }
253     
254     private Function<ProductExecution, Callable<Void>> productStopper()
255     {
256         return ProductStopper.INSTANCE;
257     }
258     
259     enum ProductStopper implements Function<ProductExecution, Callable<Void>>
260     {
261         INSTANCE;
262 
263         @Override
264         public Callable<Void> apply(final ProductExecution productExecution)
265         {
266             return new Callable<Void>()
267             {
268                 @Override
269                 public Void call() throws Exception
270                 {
271                     productExecution.getProductHandler().stop(productExecution.getProduct());
272                     return null;
273                 }
274             };
275         }
276     }
277 
278     private Map<String, String> getTestGroupSystemProperties(String testGroupId)
279     {
280         if (NO_TEST_GROUP.equals(testGroupId))
281         {
282             return Collections.emptyMap();
283         }
284 
285         for (TestGroup group : getTestGroups())
286         {
287             if (group.getId().equals(testGroupId))
288             {
289                 return group.getSystemProperties();
290             }
291         }
292         return Collections.emptyMap();
293     }
294 
295     private List<String> getIncludesForTestGroup(String testGroupId)
296     {
297         if (NO_TEST_GROUP.equals(testGroupId))
298         {
299             return Collections.singletonList(functionalTestPattern);
300         }
301         else
302         {
303             for (TestGroup group : getTestGroups())
304             {
305                 if (group.getId().equals(testGroupId))
306                 {
307                     return group.getIncludes();
308                 }
309             }
310         }
311         return Collections.singletonList(functionalTestPattern);
312     }
313 
314 
315     private List<String> getExcludesForTestGroup(String testGroupId)
316     {
317         if (NO_TEST_GROUP.equals(testGroupId))
318         {
319             return Collections.emptyList();
320         }
321         else
322         {
323             for (TestGroup group : getTestGroups())
324             {
325                 if (group.getId().equals(testGroupId))
326                 {
327                     return group.getExcludes();
328                 }
329             }
330         }
331         return Collections.emptyList();
332     }
333 }