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