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      MavenLauncher(Class<?> testClass, String resourceName, String suffix, String[] cli) {
73          this.testCaseBeingRun = testClass;
74          this.resourceName = resourceName;
75          this.suffix = suffix != null ? suffix : "";
76          this.cli = cli == null ? null : cli.clone();
77          resetGoals();
78          resetCliOptions();
79      }
80  
81      public MavenLauncher(Class<?> testClass, String resourceName, String suffix) {
82          this(testClass, resourceName, suffix, null);
83      }
84  
85      public File getUnpackedAt() {
86          return ensureUnpacked();
87      }
88  
89      private File ensureUnpacked() {
90          if (unpackedAt == null) {
91              unpackedAt = simpleExtractResources(testCaseBeingRun, resourceName);
92          }
93          return unpackedAt;
94      }
95  
96      public void moveUnpackTo(File dest) throws IOException {
97          FileUtils.deleteDirectory(dest);
98          //noinspection ResultOfMethodCallIgnored
99          boolean moved = getUnpackedAt().renameTo(dest);
100         if (!moved) {
101             String fileEncoding = System.getProperty("file.encoding");
102             String os = System.getProperty("os.name");
103             String version = System.getProperty("os.version");
104             String arch = System.getProperty("os.arch");
105             throw new IOException("Could not move " + getUnpackedAt() + " to " + dest + " (file.encoding="
106                     + fileEncoding + ", " + os + " " + version + " " + arch + ").");
107         }
108         unpackedAt = dest;
109     }
110 
111     private void resetGoals() {
112         goals.clear();
113     }
114 
115     private void addCliOption(String cliOption) {
116         cliOptions.add(cliOption);
117     }
118 
119     private StackTraceElement findTopElemenent(StackTraceElement[] stackTrace, Class<?> testClassToLookFor) {
120         StackTraceElement bestmatch = null;
121         for (StackTraceElement stackTraceElement : stackTrace) {
122             if (stackTraceElement.getClassName().equals(testClassToLookFor.getName())) {
123                 bestmatch = stackTraceElement;
124             }
125         }
126         return bestmatch;
127     }
128 
129     private static StackTraceElement[] getStackTraceElements() {
130         try {
131             throw new RuntimeException();
132         } catch (RuntimeException e) {
133             return e.getStackTrace();
134         }
135     }
136 
137     public void reset() {
138         resetGoals();
139         resetCliOptions();
140         verifier = null;
141         validator = null;
142     }
143 
144     private void resetCliOptions() {
145         cliOptions.clear();
146     }
147 
148     public MavenLauncher getSubProjectLauncher(String subProject) {
149         MavenLauncher mavenLauncher =
150                 new MavenLauncher(testCaseBeingRun, resourceName + File.separator + subProject, suffix, cli);
151         mavenLauncher.unpackedAt = new File(ensureUnpacked(), subProject);
152         return mavenLauncher;
153     }
154 
155     public OutputValidator getSubProjectValidator(String subProject) throws VerificationException {
156         String subProjectBasedir = getValidator().getSubFile(subProject).getAbsolutePath();
157         String settingsXml = settingsXmlPath().getAbsolutePath();
158         Verifier subProjectVerifier = createVerifier(subProjectBasedir, settingsXml, null);
159         return new OutputValidator(subProjectVerifier);
160     }
161 
162     public MavenLauncher addEnvVar(String key, String value) {
163         envVars.put(key, value);
164         return this;
165     }
166 
167     public MavenLauncher verifyFileNotPresent(String subFile) throws VerificationException {
168         getVerifier().verifyFileNotPresent(getValidator().getSubFile(subFile).getAbsolutePath());
169         return this;
170     }
171 
172     public MavenLauncher showErrorStackTraces() {
173         addCliOption("-e");
174         return this;
175     }
176 
177     public MavenLauncher debugLogging() {
178         addCliOption("-X");
179         return this;
180     }
181 
182     public MavenLauncher failNever() {
183         addCliOption("-fn");
184         return this;
185     }
186 
187     public MavenLauncher offline() {
188         addCliOption("-o");
189         return this;
190     }
191 
192     public MavenLauncher skipClean() {
193         writeGoal("-Dclean.skip=true" /* for maven-clean-plugin < 3.0 */);
194         writeGoal("-Dmaven.clean.skip=true" /* for maven-clean-plugin 3.0+ */);
195         return this;
196     }
197 
198     public MavenLauncher addGoal(String goal) {
199         writeGoal(goal);
200         return this;
201     }
202 
203     public FailsafeOutputValidator executeVerify() {
204         return new FailsafeOutputValidator(conditionalExec("verify"));
205     }
206 
207     public OutputValidator executeTest() {
208         return conditionalExec("test");
209     }
210 
211     List<String> getGoals() {
212         return unmodifiableList(goals);
213     }
214 
215     private void writeGoal(String newGoal) {
216         if (newGoal != null && newGoal.startsWith("-D")) {
217             String sysPropKey = newGoal.contains("=") ? newGoal.substring(0, newGoal.indexOf('=')) : newGoal;
218 
219             String sysPropStarter = sysPropKey + "=";
220 
221             for (ListIterator<String> it = goals.listIterator(); it.hasNext(); ) {
222                 String goal = it.next();
223                 if (goal.equals(sysPropKey) || goal.startsWith(sysPropStarter)) {
224                     System.out.printf(
225                             "[WARNING] System property already exists '%s'. Overriding to '%s'.%n", goal, newGoal);
226                     it.set(newGoal);
227                     return;
228                 }
229             }
230         }
231         goals.add(newGoal);
232     }
233 
234     private OutputValidator conditionalExec(String goal) {
235         OutputValidator verify;
236         try {
237             verify = execute(goal);
238         } catch (SurefireVerifierException exc) {
239             if (expectFailure) {
240                 return getValidator();
241             } else {
242                 throw exc;
243             }
244         }
245         if (expectFailure) {
246             throw new RuntimeException("Expecting build failure, have got none!");
247         }
248         return verify;
249     }
250 
251     public MavenLauncher withFailure() {
252         expectFailure = true;
253         return this;
254     }
255 
256     public OutputValidator execute(String goal) {
257         addGoal(goal);
258         return executeCurrentGoals();
259     }
260 
261     public OutputValidator executeCurrentGoals() {
262         try {
263             getVerifier().addCliArguments(cliOptions.toArray(new String[0]));
264             getVerifier().addCliArguments(goals.toArray(new String[] {}));
265             getVerifier().setSystemProperties(props);
266             getVerifier().setEnvironmentVariables(envVars);
267             getVerifier().execute();
268             return getValidator();
269         } catch (VerificationException e) {
270             throw new SurefireVerifierException(e.getLocalizedMessage(), e);
271         }
272     }
273 
274     public MavenLauncher activateProfile(String profile) {
275         return addGoal("-P" + profile);
276     }
277 
278     public MavenLauncher sysProp(String key, String value) {
279         return sysProp(singletonMap(key, value));
280     }
281 
282     public MavenLauncher sysProp(Map<String, String> properties) {
283         props.putAll(properties);
284         return this;
285     }
286 
287     public MavenLauncher sysProp(String key, boolean value) {
288         return sysProp(singletonMap(key, Boolean.toString(value)));
289     }
290 
291     public MavenLauncher sysProp(String key, int value) {
292         return sysProp(singletonMap(key, Integer.toString(value)));
293     }
294 
295     public MavenLauncher sysProp(String key, double value) {
296         return sysProp(singletonMap(key, Double.toString(value)));
297     }
298 
299     public MavenLauncher showExceptionMessages() {
300         addCliOption("-e");
301         return this;
302     }
303 
304     public MavenLauncher deleteReportsDir() {
305         try {
306             FileUtils.deleteDirectory(getValidator().getSubFile("reports"));
307         } catch (IOException e) {
308             throw new SurefireVerifierException(e);
309         }
310         return this;
311     }
312 
313     public OutputValidator getValidator() {
314         if (validator == null) {
315             validator = new OutputValidator(getVerifier());
316         }
317         return validator;
318     }
319 
320     public void setForkJvm(boolean forkJvm) {
321         getVerifier().setForkJvm(forkJvm);
322     }
323 
324     public String getLocalRepository() {
325         return getVerifier().getLocalRepository();
326     }
327 
328     public void setAutoclean(boolean autoclean) {
329         getVerifier().setAutoclean(autoclean);
330     }
331 
332     public void setLogFileName(String logFileName) {
333         getVerifier().setLogFileName(logFileName);
334     }
335 
336     private Verifier getVerifier() {
337         if (verifier == null) {
338             try {
339                 String unpackedPath = ensureUnpacked().getAbsolutePath();
340                 String settingsXml = SETTINGS_XML_PATH.getAbsolutePath();
341                 verifier = createVerifier(unpackedPath, settingsXml, cli);
342             } catch (VerificationException e) {
343                 throw new RuntimeException(e);
344             }
345         }
346         return verifier;
347     }
348 
349     private File simpleExtractResources(Class<?> cl, String resourcePath) {
350         if (!resourcePath.startsWith("/")) {
351             resourcePath = "/" + resourcePath;
352         }
353         File tempDir = getUnpackDir();
354         File testDir = new File(tempDir, resourcePath);
355         try {
356             File parentPom = new File(tempDir.getParentFile(), "pom.xml");
357             if (!parentPom.exists()) {
358                 URL resource = cl.getResource("/pom.xml");
359                 FileUtils.copyURLToFile(resource, parentPom);
360             }
361 
362             FileUtils.deleteDirectory(testDir);
363             File file = ResourceExtractor.extractResourceToDestination(cl, resourcePath, tempDir, true);
364             return file.getCanonicalFile();
365         } catch (IOException e) {
366             throw new RuntimeException(e);
367         }
368     }
369 
370     private File getUnpackDir() {
371         String tempDirPath = System.getProperty("maven.test.tmpdir", System.getProperty("java.io.tmpdir"));
372         return new File(tempDirPath, testCaseBeingRun.getSimpleName() + "_" + getTestMethodName() + suffix);
373     }
374 
375     public File getArtifactPath(String gid, String aid, String version, String ext) {
376         return new File(verifier.getArtifactPath(gid, aid, version, ext));
377     }
378 
379     String getTestMethodName() {
380         // dirty. Im sure we can use junit4 rules to attach testname to thread instead
381         StackTraceElement[] stackTrace = getStackTraceElements();
382         StackTraceElement topInTestClass;
383         topInTestClass = findTopElemenent(stackTrace, testCaseBeingRun);
384         if (topInTestClass == null) {
385             // Look in superclass...
386             topInTestClass = findTopElemenent(stackTrace, testCaseBeingRun.getSuperclass());
387         }
388         if (topInTestClass != null) {
389             return topInTestClass.getMethodName();
390         }
391         throw new IllegalStateException("Cannot find " + testCaseBeingRun.getName() + "in stacktrace");
392     }
393 
394     private static Verifier createVerifier(String basedir, String settingsFile, String[] defaultCliOptions)
395             throws VerificationException {
396 
397         return defaultCliOptions == null
398                 ? new Verifier(basedir, settingsFile, false)
399                 : new Verifier(basedir, settingsFile, false, defaultCliOptions);
400     }
401 
402     private static File settingsXmlPath() {
403         try {
404             return new File(System.getProperty("maven.settings.file")).getCanonicalFile();
405         } catch (IOException e) {
406             throw new IllegalStateException(e.getLocalizedMessage(), e);
407         }
408     }
409 }