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(executeGoal("verify"));
205     }
206 
207     public OutputValidator executeTest() {
208         return executeGoal("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 executeGoal(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             props.put("maven.build.cache.enabled", "false");
264             getVerifier().addCliArguments(cliOptions.toArray(new String[0]));
265             getVerifier().addCliArguments(goals.toArray(new String[] {}));
266             getVerifier().setSystemProperties(props);
267             getVerifier().setEnvironmentVariables(envVars);
268             getVerifier().execute();
269             return getValidator();
270         } catch (VerificationException e) {
271             throw new SurefireVerifierException(e.getLocalizedMessage(), e);
272         }
273     }
274 
275     public MavenLauncher activateProfile(String profile) {
276         return addGoal("-P" + profile);
277     }
278 
279     public MavenLauncher sysProp(String key, String value) {
280         return sysProp(singletonMap(key, value));
281     }
282 
283     public MavenLauncher sysProp(Map<String, String> properties) {
284         props.putAll(properties);
285         return this;
286     }
287 
288     public MavenLauncher sysProp(String key, boolean value) {
289         return sysProp(singletonMap(key, Boolean.toString(value)));
290     }
291 
292     public MavenLauncher sysProp(String key, int value) {
293         return sysProp(singletonMap(key, Integer.toString(value)));
294     }
295 
296     public MavenLauncher sysProp(String key, double value) {
297         return sysProp(singletonMap(key, Double.toString(value)));
298     }
299 
300     public MavenLauncher showExceptionMessages() {
301         addCliOption("-e");
302         return this;
303     }
304 
305     public MavenLauncher deleteReportsDir() {
306         try {
307             FileUtils.deleteDirectory(getValidator().getSubFile("reports"));
308         } catch (IOException e) {
309             throw new SurefireVerifierException(e);
310         }
311         return this;
312     }
313 
314     public OutputValidator getValidator() {
315         if (validator == null) {
316             validator = new OutputValidator(getVerifier());
317         }
318         return validator;
319     }
320 
321     public void setForkJvm(boolean forkJvm) {
322         getVerifier().setForkJvm(forkJvm);
323     }
324 
325     public String getLocalRepository() {
326         return getVerifier().getLocalRepository();
327     }
328 
329     public void setAutoclean(boolean autoclean) {
330         getVerifier().setAutoclean(autoclean);
331     }
332 
333     public void setLogFileName(String logFileName) {
334         getVerifier().setLogFileName(logFileName);
335     }
336 
337     private Verifier getVerifier() {
338         if (verifier == null) {
339             try {
340                 String unpackedPath = ensureUnpacked().getAbsolutePath();
341                 String settingsXml = SETTINGS_XML_PATH.getAbsolutePath();
342                 verifier = createVerifier(unpackedPath, settingsXml, cli);
343             } catch (VerificationException e) {
344                 throw new RuntimeException(e);
345             }
346         }
347         return verifier;
348     }
349 
350     private File simpleExtractResources(Class<?> cl, String resourcePath) {
351         if (!resourcePath.startsWith("/")) {
352             resourcePath = "/" + resourcePath;
353         }
354         File tempDir = getUnpackDir();
355         File testDir = new File(tempDir, resourcePath);
356         try {
357             File parentPom = new File(tempDir.getParentFile(), "pom.xml");
358             if (!parentPom.exists()) {
359                 URL resource = cl.getResource("/pom.xml");
360                 FileUtils.copyURLToFile(resource, parentPom);
361             }
362 
363             FileUtils.deleteDirectory(testDir);
364             File file = ResourceExtractor.extractResourceToDestination(cl, resourcePath, tempDir, true);
365             return file.getCanonicalFile();
366         } catch (IOException e) {
367             throw new RuntimeException(e);
368         }
369     }
370 
371     private File getUnpackDir() {
372         String tempDirPath = System.getProperty("maven.test.tmpdir", System.getProperty("java.io.tmpdir"));
373         return new File(tempDirPath, testCaseBeingRun.getSimpleName() + "_" + getTestMethodName() + suffix);
374     }
375 
376     public File getArtifactPath(String gid, String aid, String version, String ext) {
377         return new File(verifier.getArtifactPath(gid, aid, version, ext));
378     }
379 
380     String getTestMethodName() {
381         // dirty. Im sure we can use junit4 rules to attach testname to thread instead
382         StackTraceElement[] stackTrace = getStackTraceElements();
383         StackTraceElement topInTestClass;
384         topInTestClass = findTopElemenent(stackTrace, testCaseBeingRun);
385         if (topInTestClass == null) {
386             // Look in superclass...
387             topInTestClass = findTopElemenent(stackTrace, testCaseBeingRun.getSuperclass());
388         }
389         if (topInTestClass != null) {
390             return topInTestClass.getMethodName();
391         }
392         throw new IllegalStateException("Cannot find " + testCaseBeingRun.getName() + "in stacktrace");
393     }
394 
395     private static Verifier createVerifier(String basedir, String settingsFile, String[] defaultCliOptions)
396             throws VerificationException {
397 
398         return defaultCliOptions == null
399                 ? new Verifier(basedir, settingsFile, false)
400                 : new Verifier(basedir, settingsFile, false, defaultCliOptions);
401     }
402 
403     private static File settingsXmlPath() {
404         try {
405             return new File(System.getProperty("maven.settings.file")).getCanonicalFile();
406         } catch (IOException e) {
407             throw new IllegalStateException(e.getLocalizedMessage(), e);
408         }
409     }
410 }