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.plugin.surefire.booterclient;
20  
21  import javax.annotation.Nonnull;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Properties;
33  
34  import org.apache.maven.plugin.surefire.JdkAttributes;
35  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
36  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
37  import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
38  import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
39  import org.apache.maven.surefire.booter.Classpath;
40  import org.apache.maven.surefire.booter.ClasspathConfiguration;
41  import org.apache.maven.surefire.booter.ModularClasspath;
42  import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
43  import org.apache.maven.surefire.booter.StartupConfiguration;
44  import org.apache.maven.surefire.booter.SurefireBooterForkException;
45  import org.apache.maven.surefire.extensions.ForkNodeFactory;
46  import org.apache.maven.surefire.shared.io.FileUtils;
47  import org.junit.After;
48  import org.junit.Before;
49  import org.junit.Test;
50  
51  import static java.lang.String.join;
52  import static java.nio.file.Files.readAllBytes;
53  import static java.util.Collections.singletonList;
54  import static org.apache.commons.io.FileUtils.getTempDirectory;
55  import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
56  import static org.apache.maven.surefire.booter.Classpath.emptyClasspath;
57  import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
58  import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_WINDOWS;
59  import static org.assertj.core.api.Assertions.assertThat;
60  import static org.junit.Assert.assertEquals;
61  import static org.junit.Assert.assertTrue;
62  import static org.junit.Assert.fail;
63  import static org.mockito.Mockito.mock;
64  
65  /**
66   *
67   */
68  public class ForkConfigurationTest {
69      private static final StartupConfiguration STARTUP_CONFIG = new StartupConfiguration(
70              "",
71              new ClasspathConfiguration(true, true),
72              new ClassLoaderConfiguration(true, true),
73              ALL,
74              Collections.<String[]>emptyList());
75  
76      private static int idx = 0;
77  
78      private File basedir;
79  
80      @Before
81      public void setupDirectories() throws IOException {
82          File target = new File(System.getProperty("user.dir"), "target");
83          basedir = new File(target, "SUREFIRE-1136-" + ++idx);
84          FileUtils.deleteDirectory(basedir);
85          assertTrue(basedir.mkdirs());
86      }
87  
88      @After
89      public void deleteDirectories() throws IOException {
90          FileUtils.deleteDirectory(basedir);
91      }
92  
93      @Test
94      public void testEnv() throws Exception {
95          Map<String, String> env = new HashMap<>();
96          env.put("key1", "val1");
97          env.put("key2", "val2");
98          env.put("key3", "val3");
99          String[] exclEnv = {"PATH"};
100 
101         String jvm = new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath();
102         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
103 
104         ForkConfiguration config =
105                 new DefaultForkConfiguration(
106                         emptyClasspath(),
107                         basedir,
108                         "",
109                         basedir,
110                         new Properties(),
111                         "",
112                         env,
113                         exclEnv,
114                         false,
115                         1,
116                         true,
117                         platform,
118                         new NullConsoleLogger(),
119                         mock(ForkNodeFactory.class)) {
120 
121                     @Override
122                     protected void resolveClasspath(
123                             @Nonnull Commandline cli,
124                             @Nonnull String booterThatHasMainMethod,
125                             @Nonnull StartupConfiguration config,
126                             @Nonnull File dumpLogDirectory) {}
127                 };
128 
129         List<String[]> providerJpmsArgs = new ArrayList<>();
130         providerJpmsArgs.add(new String[] {"arg2", "arg3"});
131 
132         File cpElement = getTempClasspathFile();
133         List<String> cp = singletonList(cpElement.getAbsolutePath());
134 
135         ClasspathConfiguration cpConfig =
136                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
137         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
138         StartupConfiguration startup = new StartupConfiguration("cls", cpConfig, clc, ALL, providerJpmsArgs);
139 
140         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
141                 config.createCommandLine(startup, 1, getTempDirectory());
142 
143         assertThat(cli.getEnvironmentVariables())
144                 .contains("key1=val1", "key2=val2", "key3=val3")
145                 .doesNotContain("PATH=")
146                 .doesNotHaveDuplicates();
147     }
148 
149     @Test
150     public void testCliArgs() throws Exception {
151         String jvm = new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath();
152         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
153 
154         ModularClasspathForkConfiguration config = new ModularClasspathForkConfiguration(
155                 emptyClasspath(),
156                 basedir,
157                 "",
158                 basedir,
159                 new Properties(),
160                 "arg1",
161                 Collections.<String, String>emptyMap(),
162                 new String[0],
163                 false,
164                 1,
165                 true,
166                 platform,
167                 new NullConsoleLogger(),
168                 mock(ForkNodeFactory.class));
169 
170         assertThat(config.isDebug()).isFalse();
171 
172         List<String[]> providerJpmsArgs = new ArrayList<>();
173         providerJpmsArgs.add(new String[] {"arg2", "arg3"});
174 
175         ModularClasspath modulepath = new ModularClasspath(
176                 "test.module", Collections.<String>emptyList(), Collections.<String>emptyList(), null, false);
177         ModularClasspathConfiguration cpConfig = new ModularClasspathConfiguration(
178                 modulepath, emptyClasspath(), emptyClasspath(), emptyClasspath(), false, true);
179         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
180         StartupConfiguration startup = new StartupConfiguration("cls", cpConfig, clc, ALL, providerJpmsArgs);
181 
182         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
183                 config.createCommandLine(startup, 1, getTempDirectory());
184         String cliAsString = cli.toString();
185 
186         assertThat(cliAsString).contains("arg1");
187 
188         // "/path/to/java arg1 @/path/to/argfile"
189         int beginOfFileArg = cliAsString.indexOf('@', cliAsString.lastIndexOf("arg1"));
190         assertThat(beginOfFileArg).isPositive();
191         int endOfFileArg = cliAsString.indexOf(IS_OS_WINDOWS ? '"' : '\'', beginOfFileArg);
192         assertThat(endOfFileArg).isPositive();
193         Path argFile = Paths.get(cliAsString.substring(beginOfFileArg + 1, endOfFileArg));
194         String argFileText = new String(readAllBytes(argFile));
195         assertThat(argFileText).contains("arg2").contains("arg3").contains("--add-modules" + NL + "test.module");
196     }
197 
198     @Test
199     public void testDebugLine() throws Exception {
200         String jvm = new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath();
201         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
202 
203         ConsoleLogger logger = mock(ConsoleLogger.class);
204         ForkNodeFactory forkNodeFactory = mock(ForkNodeFactory.class);
205 
206         ForkConfiguration config =
207                 new DefaultForkConfiguration(
208                         emptyClasspath(),
209                         basedir,
210                         "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
211                         basedir,
212                         new Properties(),
213                         "",
214                         Collections.<String, String>emptyMap(),
215                         new String[0],
216                         true,
217                         1,
218                         true,
219                         platform,
220                         logger,
221                         forkNodeFactory) {
222 
223                     @Override
224                     protected void resolveClasspath(
225                             @Nonnull Commandline cli,
226                             @Nonnull String booterThatHasMainMethod,
227                             @Nonnull StartupConfiguration config,
228                             @Nonnull File dumpLogDirectory) {}
229                 };
230 
231         assertThat(config.isDebug()).isTrue();
232 
233         assertThat(config.getDebugLine())
234                 .isEqualTo("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005");
235 
236         assertThat(config.getForkCount()).isEqualTo(1);
237 
238         assertThat(config.isReuseForks()).isTrue();
239 
240         assertThat(config.getForkNodeFactory()).isSameAs(forkNodeFactory);
241 
242         File cpElement = getTempClasspathFile();
243         List<String> cp = singletonList(cpElement.getAbsolutePath());
244 
245         ClasspathConfiguration cpConfig =
246                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
247         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
248         StartupConfiguration startup = new StartupConfiguration(
249                 "org.apache.maven.surefire.JUnitProvider#main", cpConfig, clc, ALL, Collections.<String[]>emptyList());
250 
251         assertThat(startup.isProviderMainClass()).isTrue();
252 
253         assertThat(startup.getProviderClassName()).isEqualTo("org.apache.maven.surefire.JUnitProvider#main");
254 
255         assertThat(startup.isShadefire()).isFalse();
256 
257         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
258                 config.createCommandLine(startup, 1, getTempDirectory());
259 
260         assertThat(cli.toString()).contains("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005");
261     }
262 
263     @Test
264     @SuppressWarnings({"checkstyle:methodname", "checkstyle:magicnumber"})
265     public void testCreateCommandLine_UseSystemClassLoaderForkOnce_ShouldConstructManifestOnlyJar()
266             throws IOException, SurefireBooterForkException {
267         ForkConfiguration config = getForkConfiguration(basedir, null);
268         File cpElement = getTempClasspathFile();
269 
270         List<String> cp = singletonList(cpElement.getAbsolutePath());
271         ClasspathConfiguration cpConfig =
272                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
273         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
274         StartupConfiguration startup =
275                 new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());
276 
277         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
278                 config.createCommandLine(startup, 1, getTempDirectory());
279 
280         String line = join(" ", cli.getCommandline());
281         assertTrue(line.contains("-jar"));
282     }
283 
284     @Test
285     public void testArglineWithNewline() throws IOException, SurefireBooterForkException {
286         // SUREFIRE-657
287         ForkConfiguration config = getForkConfiguration(basedir, "abc\ndef");
288         File cpElement = getTempClasspathFile();
289 
290         List<String> cp = singletonList(cpElement.getAbsolutePath());
291         ClasspathConfiguration cpConfig =
292                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
293         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
294         StartupConfiguration startup =
295                 new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());
296 
297         org.apache.maven.surefire.shared.utils.cli.Commandline commandLine =
298                 config.createCommandLine(startup, 1, getTempDirectory());
299         assertThat(commandLine.toString()).contains(IS_OS_WINDOWS ? "abc def" : "'abc' 'def'");
300     }
301 
302     @Test
303     public void testCurrentWorkingDirectoryPropagationIncludingForkNumberExpansion()
304             throws IOException, SurefireBooterForkException {
305         File cwd = new File(basedir, "fork_${surefire.forkNumber}");
306 
307         ClasspathConfiguration cpConfig =
308                 new ClasspathConfiguration(emptyClasspath(), emptyClasspath(), emptyClasspath(), true, true);
309         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
310         StartupConfiguration startup =
311                 new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());
312         ForkConfiguration config = getForkConfiguration(cwd.getCanonicalFile());
313         org.apache.maven.surefire.shared.utils.cli.Commandline commandLine =
314                 config.createCommandLine(startup, 1, getTempDirectory());
315 
316         File forkDirectory = new File(basedir, "fork_1");
317 
318         String shellWorkDir = commandLine.getShell().getWorkingDirectory().getCanonicalPath();
319         assertEquals(shellWorkDir, forkDirectory.getCanonicalPath());
320     }
321 
322     @Test
323     public void testExceptionWhenCurrentDirectoryIsNotRealDirectory() throws IOException {
324         File cwd = new File(basedir, "cwd.txt");
325         FileUtils.touch(cwd);
326 
327         try {
328             ForkConfiguration config = getForkConfiguration(cwd.getCanonicalFile());
329             config.createCommandLine(STARTUP_CONFIG, 1, getTempDirectory());
330         } catch (SurefireBooterForkException e) {
331             // To handle issue with ~ expansion on Windows
332             String absolutePath = cwd.getCanonicalPath();
333             assertEquals("WorkingDirectory " + absolutePath + " exists and is not a directory", e.getMessage());
334             return;
335         } finally {
336             assertTrue(cwd.delete());
337         }
338 
339         fail();
340     }
341 
342     @Test
343     public void testExceptionWhenCurrentDirectoryCannotBeCreated() throws IOException {
344         // NULL is invalid for JDK starting from 1.7.60
345         // - https://github.com/openjdk-mirror/jdk/commit/e5389115f3634d25d101e2dcc71f120d4fd9f72f
346         // ? character is invalid on Windows, seems to be imposable to create invalid directory using Java on Linux
347         File cwd = new File(basedir, "?\u0000InvalidDirectoryName");
348 
349         try {
350             ForkConfiguration config = getForkConfiguration(cwd.getAbsoluteFile());
351             config.createCommandLine(STARTUP_CONFIG, 1, getTempDirectory());
352         } catch (SurefireBooterForkException sbfe) {
353             assertEquals("Cannot create workingDirectory " + cwd.getAbsolutePath(), sbfe.getMessage());
354             return;
355         } finally {
356             FileUtils.deleteDirectory(cwd);
357         }
358 
359         if (IS_OS_WINDOWS || isJavaVersionAtLeast7u60()) {
360             fail();
361         }
362     }
363 
364     private File getTempClasspathFile() throws IOException {
365         File cpElement = new File(basedir, "ForkConfigurationTest." + idx + ".file");
366         FileUtils.deleteDirectory(cpElement);
367         return cpElement;
368     }
369 
370     static ForkConfiguration getForkConfiguration(File basedir, String argLine) throws IOException {
371         File jvm = new File(new File(System.getProperty("java.home"), "bin"), "java");
372         return getForkConfiguration(basedir, argLine, jvm.getAbsolutePath(), new File(".").getCanonicalFile());
373     }
374 
375     private ForkConfiguration getForkConfiguration(File cwd) throws IOException {
376         File jvm = new File(new File(System.getProperty("java.home"), "bin"), "java");
377         return getForkConfiguration(basedir, null, jvm.getAbsolutePath(), cwd);
378     }
379 
380     private static ForkConfiguration getForkConfiguration(File basedir, String argLine, String jvm, File cwd)
381             throws IOException {
382         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
383         File tmpDir = new File(new File(basedir, "target"), "surefire");
384         FileUtils.deleteDirectory(tmpDir);
385         assertTrue(tmpDir.mkdirs());
386         return new JarManifestForkConfiguration(
387                 emptyClasspath(),
388                 tmpDir,
389                 null,
390                 cwd,
391                 new Properties(),
392                 argLine,
393                 Collections.<String, String>emptyMap(),
394                 new String[0],
395                 false,
396                 1,
397                 false,
398                 platform,
399                 new NullConsoleLogger(),
400                 mock(ForkNodeFactory.class));
401     }
402 
403     // based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime
404     @SuppressWarnings("checkstyle:magicnumber")
405     private static boolean isJavaVersionAtLeast7u60() {
406         String[] javaVersionElements =
407                 System.getProperty("java.runtime.version").split("\\.|_|-b");
408         return Integer.parseInt(javaVersionElements[1]) >= 7 && Integer.parseInt(javaVersionElements[3]) >= 60;
409     }
410 }