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 testEnvInterpolateForkNumber() throws Exception {
151         Map<String, String> env = new HashMap<>();
152         env.put("FORK_ID", "${surefire.forkNumber}");
153         String[] exclEnv = {"PATH"};
154 
155         String jvm = new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath();
156         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
157 
158         ForkConfiguration config =
159                 new DefaultForkConfiguration(
160                         emptyClasspath(),
161                         basedir,
162                         "",
163                         basedir,
164                         new Properties(),
165                         "",
166                         env,
167                         exclEnv,
168                         false,
169                         2,
170                         true,
171                         platform,
172                         new NullConsoleLogger(),
173                         mock(ForkNodeFactory.class)) {
174 
175                     @Override
176                     protected void resolveClasspath(
177                             @Nonnull Commandline cli,
178                             @Nonnull String booterThatHasMainMethod,
179                             @Nonnull StartupConfiguration config,
180                             @Nonnull File dumpLogDirectory) {}
181                 };
182 
183         List<String[]> providerJpmsArgs = new ArrayList<>();
184         providerJpmsArgs.add(new String[] {"arg2", "arg3"});
185 
186         File cpElement = getTempClasspathFile();
187         List<String> cp = singletonList(cpElement.getAbsolutePath());
188 
189         ClasspathConfiguration cpConfig =
190                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
191         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
192         StartupConfiguration startup = new StartupConfiguration("cls", cpConfig, clc, ALL, providerJpmsArgs);
193 
194         org.apache.maven.surefire.shared.utils.cli.Commandline cliFork1 =
195                 config.createCommandLine(startup, 1, getTempDirectory());
196 
197         assertThat(cliFork1.getEnvironmentVariables())
198                 .contains("FORK_ID=1")
199                 .doesNotContain("PATH=")
200                 .doesNotHaveDuplicates();
201 
202         org.apache.maven.surefire.shared.utils.cli.Commandline cliFork2 =
203                 config.createCommandLine(startup, 2, getTempDirectory());
204 
205         assertThat(cliFork2.getEnvironmentVariables())
206                 .contains("FORK_ID=2")
207                 .doesNotContain("PATH=")
208                 .doesNotHaveDuplicates();
209     }
210 
211     @Test
212     public void testCliArgs() throws Exception {
213         String jvm = new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath();
214         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
215 
216         ModularClasspathForkConfiguration config = new ModularClasspathForkConfiguration(
217                 emptyClasspath(),
218                 basedir,
219                 "",
220                 basedir,
221                 new Properties(),
222                 "arg1",
223                 Collections.<String, String>emptyMap(),
224                 new String[0],
225                 false,
226                 1,
227                 true,
228                 platform,
229                 new NullConsoleLogger(),
230                 mock(ForkNodeFactory.class));
231 
232         assertThat(config.isDebug()).isFalse();
233 
234         List<String[]> providerJpmsArgs = new ArrayList<>();
235         providerJpmsArgs.add(new String[] {"arg2", "arg3"});
236 
237         ModularClasspath modulepath = new ModularClasspath(
238                 "test.module", Collections.<String>emptyList(), Collections.<String>emptyList(), null, false);
239         ModularClasspathConfiguration cpConfig = new ModularClasspathConfiguration(
240                 modulepath, emptyClasspath(), emptyClasspath(), emptyClasspath(), false, true);
241         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
242         StartupConfiguration startup = new StartupConfiguration("cls", cpConfig, clc, ALL, providerJpmsArgs);
243 
244         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
245                 config.createCommandLine(startup, 1, getTempDirectory());
246         String cliAsString = cli.toString();
247 
248         assertThat(cliAsString).contains("arg1");
249 
250         // "/path/to/java arg1 @/path/to/argfile"
251         int beginOfFileArg = cliAsString.indexOf('@', cliAsString.lastIndexOf("arg1"));
252         assertThat(beginOfFileArg).isPositive();
253         int endOfFileArg = cliAsString.indexOf(IS_OS_WINDOWS ? '"' : '\'', beginOfFileArg);
254         assertThat(endOfFileArg).isPositive();
255         Path argFile = Paths.get(cliAsString.substring(beginOfFileArg + 1, endOfFileArg));
256         String argFileText = new String(readAllBytes(argFile));
257         assertThat(argFileText).contains("arg2").contains("arg3").contains("--add-modules" + NL + "ALL-MODULE-PATH");
258     }
259 
260     @Test
261     public void testDebugLine() throws Exception {
262         String jvm = new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath();
263         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
264 
265         ConsoleLogger logger = mock(ConsoleLogger.class);
266         ForkNodeFactory forkNodeFactory = mock(ForkNodeFactory.class);
267 
268         ForkConfiguration config =
269                 new DefaultForkConfiguration(
270                         emptyClasspath(),
271                         basedir,
272                         "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
273                         basedir,
274                         new Properties(),
275                         "",
276                         Collections.<String, String>emptyMap(),
277                         new String[0],
278                         true,
279                         1,
280                         true,
281                         platform,
282                         logger,
283                         forkNodeFactory) {
284 
285                     @Override
286                     protected void resolveClasspath(
287                             @Nonnull Commandline cli,
288                             @Nonnull String booterThatHasMainMethod,
289                             @Nonnull StartupConfiguration config,
290                             @Nonnull File dumpLogDirectory) {}
291                 };
292 
293         assertThat(config.isDebug()).isTrue();
294 
295         assertThat(config.getDebugLine())
296                 .isEqualTo("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005");
297 
298         assertThat(config.getForkCount()).isEqualTo(1);
299 
300         assertThat(config.isReuseForks()).isTrue();
301 
302         assertThat(config.getForkNodeFactory()).isSameAs(forkNodeFactory);
303 
304         File cpElement = getTempClasspathFile();
305         List<String> cp = singletonList(cpElement.getAbsolutePath());
306 
307         ClasspathConfiguration cpConfig =
308                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
309         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
310         StartupConfiguration startup = new StartupConfiguration(
311                 "org.apache.maven.surefire.JUnitProvider#main", cpConfig, clc, ALL, Collections.<String[]>emptyList());
312 
313         assertThat(startup.isProviderMainClass()).isTrue();
314 
315         assertThat(startup.getProviderClassName()).isEqualTo("org.apache.maven.surefire.JUnitProvider#main");
316 
317         assertThat(startup.isShadefire()).isFalse();
318 
319         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
320                 config.createCommandLine(startup, 1, getTempDirectory());
321 
322         assertThat(cli.toString()).contains("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005");
323     }
324 
325     @Test
326     @SuppressWarnings({"checkstyle:methodname", "checkstyle:magicnumber"})
327     public void testCreateCommandLine_UseSystemClassLoaderForkOnce_ShouldConstructManifestOnlyJar()
328             throws IOException, SurefireBooterForkException {
329         ForkConfiguration config = getForkConfiguration(basedir, null);
330         File cpElement = getTempClasspathFile();
331 
332         List<String> cp = singletonList(cpElement.getAbsolutePath());
333         ClasspathConfiguration cpConfig =
334                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
335         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
336         StartupConfiguration startup =
337                 new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());
338 
339         org.apache.maven.surefire.shared.utils.cli.Commandline cli =
340                 config.createCommandLine(startup, 1, getTempDirectory());
341 
342         String line = join(" ", cli.getCommandline());
343         assertTrue(line.contains("-jar"));
344     }
345 
346     @Test
347     public void testArglineWithNewline() throws IOException, SurefireBooterForkException {
348         // SUREFIRE-657
349         ForkConfiguration config = getForkConfiguration(basedir, "abc\ndef");
350         File cpElement = getTempClasspathFile();
351 
352         List<String> cp = singletonList(cpElement.getAbsolutePath());
353         ClasspathConfiguration cpConfig =
354                 new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
355         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
356         StartupConfiguration startup =
357                 new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());
358 
359         org.apache.maven.surefire.shared.utils.cli.Commandline commandLine =
360                 config.createCommandLine(startup, 1, getTempDirectory());
361         assertThat(commandLine.toString()).contains(IS_OS_WINDOWS ? "abc def" : "'abc' 'def'");
362     }
363 
364     @Test
365     public void testCurrentWorkingDirectoryPropagationIncludingForkNumberExpansion()
366             throws IOException, SurefireBooterForkException {
367         File cwd = new File(basedir, "fork_${surefire.forkNumber}");
368 
369         ClasspathConfiguration cpConfig =
370                 new ClasspathConfiguration(emptyClasspath(), emptyClasspath(), emptyClasspath(), true, true);
371         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
372         StartupConfiguration startup =
373                 new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());
374         ForkConfiguration config = getForkConfiguration(cwd.getCanonicalFile());
375         org.apache.maven.surefire.shared.utils.cli.Commandline commandLine =
376                 config.createCommandLine(startup, 1, getTempDirectory());
377 
378         File forkDirectory = new File(basedir, "fork_1");
379 
380         String shellWorkDir = commandLine.getShell().getWorkingDirectory().getCanonicalPath();
381         assertEquals(shellWorkDir, forkDirectory.getCanonicalPath());
382     }
383 
384     @Test
385     public void testExceptionWhenCurrentDirectoryIsNotRealDirectory() throws IOException {
386         File cwd = new File(basedir, "cwd.txt");
387         FileUtils.touch(cwd);
388 
389         try {
390             ForkConfiguration config = getForkConfiguration(cwd.getCanonicalFile());
391             config.createCommandLine(STARTUP_CONFIG, 1, getTempDirectory());
392         } catch (SurefireBooterForkException e) {
393             // To handle issue with ~ expansion on Windows
394             String absolutePath = cwd.getCanonicalPath();
395             assertEquals("WorkingDirectory " + absolutePath + " exists and is not a directory", e.getMessage());
396             return;
397         } finally {
398             assertTrue(cwd.delete());
399         }
400 
401         fail();
402     }
403 
404     @Test
405     public void testExceptionWhenCurrentDirectoryCannotBeCreated() throws IOException {
406         // NULL is invalid for JDK starting from 1.7.60
407         // - https://github.com/openjdk-mirror/jdk/commit/e5389115f3634d25d101e2dcc71f120d4fd9f72f
408         // ? character is invalid on Windows, seems to be imposable to create invalid directory using Java on Linux
409         File cwd = new File(basedir, "?\u0000InvalidDirectoryName");
410 
411         try {
412             ForkConfiguration config = getForkConfiguration(cwd.getAbsoluteFile());
413             config.createCommandLine(STARTUP_CONFIG, 1, getTempDirectory());
414         } catch (SurefireBooterForkException sbfe) {
415             assertEquals("Cannot create workingDirectory " + cwd.getAbsolutePath(), sbfe.getMessage());
416             return;
417         } finally {
418             FileUtils.deleteDirectory(cwd);
419         }
420 
421         if (IS_OS_WINDOWS || isJavaVersionAtLeast7u60()) {
422             fail();
423         }
424     }
425 
426     private File getTempClasspathFile() throws IOException {
427         File cpElement = new File(basedir, "ForkConfigurationTest." + idx + ".file");
428         FileUtils.deleteDirectory(cpElement);
429         return cpElement;
430     }
431 
432     static ForkConfiguration getForkConfiguration(File basedir, String argLine) throws IOException {
433         File jvm = new File(new File(System.getProperty("java.home"), "bin"), "java");
434         return getForkConfiguration(basedir, argLine, jvm.getAbsolutePath(), new File(".").getCanonicalFile());
435     }
436 
437     private ForkConfiguration getForkConfiguration(File cwd) throws IOException {
438         File jvm = new File(new File(System.getProperty("java.home"), "bin"), "java");
439         return getForkConfiguration(basedir, null, jvm.getAbsolutePath(), cwd);
440     }
441 
442     private static ForkConfiguration getForkConfiguration(File basedir, String argLine, String jvm, File cwd)
443             throws IOException {
444         Platform platform = new Platform().withJdkExecAttributesForTests(new JdkAttributes(jvm, false));
445         File tmpDir = new File(new File(basedir, "target"), "surefire");
446         FileUtils.deleteDirectory(tmpDir);
447         assertTrue(tmpDir.mkdirs());
448         return new JarManifestForkConfiguration(
449                 emptyClasspath(),
450                 tmpDir,
451                 null,
452                 cwd,
453                 new Properties(),
454                 argLine,
455                 Collections.<String, String>emptyMap(),
456                 new String[0],
457                 false,
458                 1,
459                 false,
460                 platform,
461                 new NullConsoleLogger(),
462                 mock(ForkNodeFactory.class));
463     }
464 
465     // based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime
466     @SuppressWarnings("checkstyle:magicnumber")
467     private static boolean isJavaVersionAtLeast7u60() {
468         String[] javaVersionElements =
469                 System.getProperty("java.runtime.version").split("\\.|_|-b");
470         return Integer.parseInt(javaVersionElements[1]) >= 7 && Integer.parseInt(javaVersionElements[3]) >= 60;
471     }
472 }