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