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