View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.surefire.its.fixture;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.ListIterator;
28  import java.util.Map;
29  import java.util.Properties;
30  
31  import org.apache.maven.shared.utils.io.FileUtils;
32  import org.apache.maven.shared.verifier.VerificationException;
33  import org.apache.maven.shared.verifier.Verifier;
34  import org.apache.maven.shared.verifier.util.ResourceExtractor;
35  
36  import static java.util.Collections.singletonMap;
37  import static java.util.Collections.unmodifiableList;
38  
39  /**
40   * Encapsulate all needed features to start a maven run
41   * <br>
42   *
43   * @author Kristian Rosenvold
44   */
45  public final class MavenLauncher {
46      private static final File SETTINGS_XML_PATH = settingsXmlPath();
47  
48      private final List<String> cliOptions = new ArrayList<>();
49  
50      private final List<String> goals = new ArrayList<>();
51  
52      private final Map<String, String> envVars = new HashMap<>();
53  
54      private final Properties props = new Properties();
55  
56      private File unpackedAt;
57  
58      private Verifier verifier;
59  
60      private OutputValidator validator;
61  
62      private final Class<?> testCaseBeingRun;
63  
64      private final String resourceName;
65  
66      private final String suffix;
67  
68      private final String[] cli;
69  
70      private boolean expectFailure;
71  
72      private boolean forkJvm;
73  
74      MavenLauncher(Class<?> testClass, String resourceName, String suffix, String[] cli) {
75          this.testCaseBeingRun = testClass;
76          this.resourceName = resourceName;
77          this.suffix = suffix != null ? suffix : "";
78          this.cli = cli == null ? null : cli.clone();
79          // by default use embedded mode
80          this.forkJvm = false;
81          resetGoals();
82          resetCliOptions();
83      }
84  
85      public MavenLauncher(Class<?> testClass, String resourceName, String suffix) {
86          this(testClass, resourceName, suffix, null);
87      }
88  
89      public File getUnpackedAt() {
90          return ensureUnpacked();
91      }
92  
93      private File ensureUnpacked() {
94          if (unpackedAt == null) {
95              unpackedAt = simpleExtractResources(testCaseBeingRun, resourceName);
96          }
97          return unpackedAt;
98      }
99  
100     public void moveUnpackTo(File dest) throws IOException {
101         FileUtils.deleteDirectory(dest);
102         //noinspection ResultOfMethodCallIgnored
103         boolean moved = getUnpackedAt().renameTo(dest);
104         if (!moved) {
105             String fileEncoding = System.getProperty("file.encoding");
106             String os = System.getProperty("os.name");
107             String version = System.getProperty("os.version");
108             String arch = System.getProperty("os.arch");
109             throw new IOException("Could not move " + getUnpackedAt() + " to " + dest + " (file.encoding="
110                     + fileEncoding + ", " + os + " " + version + " " + arch + ").");
111         }
112         unpackedAt = dest;
113     }
114 
115     private void resetGoals() {
116         goals.clear();
117     }
118 
119     private void addCliOption(String cliOption) {
120         cliOptions.add(cliOption);
121     }
122 
123     private StackTraceElement findTopElemenent(StackTraceElement[] stackTrace, Class<?> testClassToLookFor) {
124         StackTraceElement bestmatch = null;
125         for (StackTraceElement stackTraceElement : stackTrace) {
126             if (stackTraceElement.getClassName().equals(testClassToLookFor.getName())) {
127                 bestmatch = stackTraceElement;
128             }
129         }
130         return bestmatch;
131     }
132 
133     private static StackTraceElement[] getStackTraceElements() {
134         try {
135             throw new RuntimeException();
136         } catch (RuntimeException e) {
137             return e.getStackTrace();
138         }
139     }
140 
141     public void reset() {
142         resetGoals();
143         resetCliOptions();
144         verifier = null;
145         validator = null;
146     }
147 
148     private void resetCliOptions() {
149         cliOptions.clear();
150     }
151 
152     public MavenLauncher getSubProjectLauncher(String subProject) {
153         MavenLauncher mavenLauncher =
154                 new MavenLauncher(testCaseBeingRun, resourceName + File.separator + subProject, suffix, cli);
155         mavenLauncher.unpackedAt = new File(ensureUnpacked(), subProject);
156         return mavenLauncher;
157     }
158 
159     public OutputValidator getSubProjectValidator(String subProject) throws VerificationException {
160         String subProjectBasedir = getValidator().getSubFile(subProject).getAbsolutePath();
161         String settingsXml = settingsXmlPath().getAbsolutePath();
162         Verifier subProjectVerifier = createVerifier(subProjectBasedir, settingsXml, null);
163         return new OutputValidator(subProjectVerifier);
164     }
165 
166     public MavenLauncher addEnvVar(String key, String value) {
167         envVars.put(key, value);
168         return this;
169     }
170 
171     public MavenLauncher verifyFileNotPresent(String subFile) throws VerificationException {
172         getVerifier().verifyFileNotPresent(getValidator().getSubFile(subFile).getAbsolutePath());
173         return this;
174     }
175 
176     public MavenLauncher showErrorStackTraces() {
177         addCliOption("-e");
178         return this;
179     }
180 
181     public MavenLauncher debugLogging() {
182         addCliOption("-X");
183         return this;
184     }
185 
186     public MavenLauncher failNever() {
187         addCliOption("-fn");
188         return this;
189     }
190 
191     public MavenLauncher offline() {
192         addCliOption("-o");
193         return this;
194     }
195 
196     public MavenLauncher skipClean() {
197         writeGoal("-Dclean.skip=true" /* for maven-clean-plugin < 3.0 */);
198         writeGoal("-Dmaven.clean.skip=true" /* for maven-clean-plugin 3.0+ */);
199         return this;
200     }
201 
202     public MavenLauncher addGoal(String goal) {
203         writeGoal(goal);
204         return this;
205     }
206 
207     public FailsafeOutputValidator executeVerify() {
208         return new FailsafeOutputValidator(conditionalExec("verify"));
209     }
210 
211     public OutputValidator executeTest() {
212         return conditionalExec("test");
213     }
214 
215     List<String> getGoals() {
216         return unmodifiableList(goals);
217     }
218 
219     private void writeGoal(String newGoal) {
220         if (newGoal != null && newGoal.startsWith("-D")) {
221             String sysPropKey = newGoal.contains("=") ? newGoal.substring(0, newGoal.indexOf('=')) : newGoal;
222 
223             String sysPropStarter = sysPropKey + "=";
224 
225             for (ListIterator<String> it = goals.listIterator(); it.hasNext(); ) {
226                 String goal = it.next();
227                 if (goal.equals(sysPropKey) || goal.startsWith(sysPropStarter)) {
228                     System.out.printf(
229                             "[WARNING] System property already exists '%s'. Overriding to '%s'.%n", goal, newGoal);
230                     it.set(newGoal);
231                     return;
232                 }
233             }
234         }
235         goals.add(newGoal);
236     }
237 
238     private OutputValidator conditionalExec(String goal) {
239         OutputValidator verify;
240         try {
241             verify = execute(goal);
242         } catch (SurefireVerifierException exc) {
243             if (expectFailure) {
244                 return getValidator();
245             } else {
246                 throw exc;
247             }
248         }
249         if (expectFailure) {
250             throw new RuntimeException("Expecting build failure, have got none!");
251         }
252         return verify;
253     }
254 
255     public MavenLauncher withFailure() {
256         expectFailure = true;
257         return this;
258     }
259 
260     public OutputValidator execute(String goal) {
261         addGoal(goal);
262         return executeCurrentGoals();
263     }
264 
265     public OutputValidator executeCurrentGoals() {
266         try {
267             getVerifier().addCliArguments(cliOptions.toArray(new String[0]));
268             getVerifier().addCliArguments(goals.toArray(new String[] {}));
269             getVerifier().setSystemProperties(props);
270             getVerifier().setEnvironmentVariables(envVars);
271             if (envVars.isEmpty()) {
272                 getVerifier().setForkJvm(forkJvm);
273             } else {
274                 getVerifier().setForkJvm(true);
275             }
276             getVerifier().execute();
277             return getValidator();
278         } catch (VerificationException e) {
279             throw new SurefireVerifierException(e.getLocalizedMessage(), e);
280         }
281     }
282 
283     public MavenLauncher activateProfile(String profile) {
284         return addGoal("-P" + profile);
285     }
286 
287     public MavenLauncher sysProp(String key, String value) {
288         return sysProp(singletonMap(key, value));
289     }
290 
291     public MavenLauncher sysProp(Map<String, String> properties) {
292         props.putAll(properties);
293         return this;
294     }
295 
296     public MavenLauncher sysProp(String key, boolean value) {
297         return sysProp(singletonMap(key, Boolean.toString(value)));
298     }
299 
300     public MavenLauncher sysProp(String key, int value) {
301         return sysProp(singletonMap(key, Integer.toString(value)));
302     }
303 
304     public MavenLauncher sysProp(String key, double value) {
305         return sysProp(singletonMap(key, Double.toString(value)));
306     }
307 
308     public MavenLauncher showExceptionMessages() {
309         addCliOption("-e");
310         return this;
311     }
312 
313     public MavenLauncher deleteReportsDir() {
314         try {
315             FileUtils.deleteDirectory(getValidator().getSubFile("reports"));
316         } catch (IOException e) {
317             throw new SurefireVerifierException(e);
318         }
319         return this;
320     }
321 
322     public OutputValidator getValidator() {
323         if (validator == null) {
324             validator = new OutputValidator(getVerifier());
325         }
326         return validator;
327     }
328 
329     public void setForkJvm(boolean forkJvm) {
330         this.forkJvm = forkJvm;
331     }
332 
333     public String getLocalRepository() {
334         return getVerifier().getLocalRepository();
335     }
336 
337     public void setAutoclean(boolean autoclean) {
338         getVerifier().setAutoclean(autoclean);
339     }
340 
341     public void setLogFileName(String logFileName) {
342         getVerifier().setLogFileName(logFileName);
343     }
344 
345     private Verifier getVerifier() {
346         if (verifier == null) {
347             try {
348                 String unpackedPath = ensureUnpacked().getAbsolutePath();
349                 String settingsXml = SETTINGS_XML_PATH.getAbsolutePath();
350                 verifier = createVerifier(unpackedPath, settingsXml, cli);
351             } catch (VerificationException e) {
352                 throw new RuntimeException(e);
353             }
354         }
355         return verifier;
356     }
357 
358     private File simpleExtractResources(Class<?> cl, String resourcePath) {
359         if (!resourcePath.startsWith("/")) {
360             resourcePath = "/" + resourcePath;
361         }
362         File tempDir = getUnpackDir();
363         File testDir = new File(tempDir, resourcePath);
364         try {
365             File parentPom = new File(tempDir.getParentFile(), "pom.xml");
366             if (!parentPom.exists()) {
367                 URL resource = cl.getResource("/pom.xml");
368                 FileUtils.copyURLToFile(resource, parentPom);
369             }
370 
371             FileUtils.deleteDirectory(testDir);
372             File file = ResourceExtractor.extractResourceToDestination(cl, resourcePath, tempDir, true);
373             return file.getCanonicalFile();
374         } catch (IOException e) {
375             throw new RuntimeException(e);
376         }
377     }
378 
379     private File getUnpackDir() {
380         String tempDirPath = System.getProperty("maven.test.tmpdir", System.getProperty("java.io.tmpdir"));
381         return new File(tempDirPath, testCaseBeingRun.getSimpleName() + "_" + getTestMethodName() + suffix);
382     }
383 
384     public File getArtifactPath(String gid, String aid, String version, String ext) {
385         return new File(verifier.getArtifactPath(gid, aid, version, ext));
386     }
387 
388     String getTestMethodName() {
389         // dirty. Im sure we can use junit4 rules to attach testname to thread instead
390         StackTraceElement[] stackTrace = getStackTraceElements();
391         StackTraceElement topInTestClass;
392         topInTestClass = findTopElemenent(stackTrace, testCaseBeingRun);
393         if (topInTestClass == null) {
394             // Look in superclass...
395             topInTestClass = findTopElemenent(stackTrace, testCaseBeingRun.getSuperclass());
396         }
397         if (topInTestClass != null) {
398             return topInTestClass.getMethodName();
399         }
400         throw new IllegalStateException("Cannot find " + testCaseBeingRun.getName() + "in stacktrace");
401     }
402 
403     private static Verifier createVerifier(String basedir, String settingsFile, String[] defaultCliOptions)
404             throws VerificationException {
405 
406         return defaultCliOptions == null
407                 ? new Verifier(basedir, settingsFile, false)
408                 : new Verifier(basedir, settingsFile, false, defaultCliOptions);
409     }
410 
411     private static File settingsXmlPath() {
412         try {
413             return new File(System.getProperty("maven.settings.file")).getCanonicalFile();
414         } catch (IOException e) {
415             throw new IllegalStateException(e.getLocalizedMessage(), e);
416         }
417     }
418 }