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.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
23  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
24  import org.apache.maven.plugin.surefire.util.Relocator;
25  import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
26  import org.apache.maven.surefire.booter.Classpath;
27  import org.apache.maven.surefire.booter.ClasspathConfiguration;
28  import org.apache.maven.surefire.booter.ForkedBooter;
29  import org.apache.maven.surefire.booter.StartupConfiguration;
30  import org.apache.maven.surefire.extensions.ForkNodeFactory;
31  import org.junit.Before;
32  import org.junit.Test;
33  import org.junit.runner.RunWith;
34  import org.powermock.core.classloader.annotations.PowerMockIgnore;
35  import org.powermock.core.classloader.annotations.PrepareForTest;
36  import org.powermock.modules.junit4.PowerMockRunner;
37  
38  import javax.annotation.Nonnull;
39  import java.io.File;
40  import java.util.Collections;
41  import java.util.HashMap;
42  import java.util.Map;
43  import java.util.Properties;
44  
45  import static java.util.Collections.singleton;
46  import static org.fest.assertions.Assertions.assertThat;
47  import static org.mockito.ArgumentMatchers.anyString;
48  import static org.mockito.ArgumentMatchers.eq;
49  import static org.mockito.Mockito.never;
50  import static org.mockito.Mockito.times;
51  import static org.mockito.Mockito.verify;
52  import static org.powermock.api.mockito.PowerMockito.mock;
53  import static org.powermock.api.mockito.PowerMockito.mockStatic;
54  import static org.powermock.api.mockito.PowerMockito.spy;
55  import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
56  import static org.powermock.api.mockito.PowerMockito.verifyStatic;
57  import static org.powermock.api.mockito.PowerMockito.when;
58  import static org.powermock.reflect.Whitebox.invokeMethod;
59  
60  /**
61   * Unit tests for {@link DefaultForkConfiguration}.
62   *
63   * @author Tibor Digana (tibor17)
64   * @since 2.21
65   */
66  @RunWith( PowerMockRunner.class )
67  @PrepareForTest( { DefaultForkConfiguration.class, Relocator.class } )
68  @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
69  public class DefaultForkConfigurationTest
70  {
71      private Classpath booterClasspath;
72      private File tempDirectory;
73      private String debugLine;
74      private File workingDirectory;
75      private Properties modelProperties;
76      private String argLine;
77      private Map<String, String> environmentVariables;
78      private String[] excludedEnvironmentVariables;
79      private boolean debug;
80      private int forkCount;
81      private boolean reuseForks;
82      private Platform pluginPlatform;
83      private ConsoleLogger log;
84      private ForkNodeFactory forkNodeFactory;
85  
86      @Before
87      public void setup()
88      {
89          booterClasspath = new Classpath( singleton( "provider.jar" ) );
90          tempDirectory = new File( "target/surefire" );
91          debugLine = "";
92          workingDirectory = new File( "." );
93          modelProperties = new Properties();
94          argLine = null;
95          environmentVariables = new HashMap<>();
96          excludedEnvironmentVariables = new String[0];
97          debug = true;
98          forkCount = 2;
99          reuseForks = true;
100         pluginPlatform = new Platform();
101         log = mock( ConsoleLogger.class );
102         forkNodeFactory = mock( ForkNodeFactory.class );
103     }
104 
105     @Test
106     public void shouldBeNullArgLine() throws Exception
107     {
108         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
109                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
110                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
111         {
112 
113             @Override
114             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
115                                              @Nonnull String booterThatHasMainMethod,
116                                              @Nonnull StartupConfiguration config,
117                                              @Nonnull File dumpLogDirectory )
118             {
119             }
120         };
121 
122         DefaultForkConfiguration mockedConfig = spy( config );
123         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
124         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
125         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "" ) );
126         assertThat( newArgLine ).isEmpty();
127     }
128 
129     @Test
130     public void shouldBeEmptyArgLine() throws Exception
131     {
132         argLine = "";
133         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
134                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
135                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
136         {
137 
138             @Override
139             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
140                                              @Nonnull String booterThatHasMainMethod,
141                                              @Nonnull StartupConfiguration config,
142                                              @Nonnull File dumpLogDirectory )
143             {
144             }
145         };
146 
147         DefaultForkConfiguration mockedConfig = spy( config );
148         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
149         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
150         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "" ) );
151         assertThat( newArgLine ).isEmpty();
152     }
153 
154     @Test
155     public void shouldBeEmptyArgLineInsteadOfNewLines() throws Exception
156     {
157         argLine = "\n\r";
158         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
159                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
160                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
161         {
162 
163             @Override
164             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
165                                              @Nonnull String booterThatHasMainMethod,
166                                              @Nonnull StartupConfiguration config,
167                                              @Nonnull File dumpLogDirectory )
168             {
169             }
170         };
171 
172         DefaultForkConfiguration mockedConfig = spy( config );
173         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
174         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
175         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "" ) );
176         assertThat( newArgLine ).isEmpty();
177     }
178 
179     @Test
180     public void shouldBeWithoutEscaping() throws Exception
181     {
182         argLine = "-Dfile.encoding=UTF-8";
183         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
184                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
185                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
186         {
187 
188             @Override
189             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
190                                              @Nonnull String booterThatHasMainMethod,
191                                              @Nonnull StartupConfiguration config,
192                                              @Nonnull File dumpLogDirectory )
193             {
194             }
195         };
196 
197         DefaultForkConfiguration mockedConfig = spy( config );
198         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
199         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
200         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "-Dfile.encoding=UTF-8" ) );
201         assertThat( newArgLine ).isEqualTo( "-Dfile.encoding=UTF-8" );
202     }
203 
204     @Test
205     public void shouldBeWithEscaping() throws Exception
206     {
207         modelProperties.put( "encoding", "UTF-8" );
208         argLine = "-Dfile.encoding=@{encoding}";
209         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
210                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
211                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
212         {
213 
214             @Override
215             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
216                                              @Nonnull String booterThatHasMainMethod,
217                                              @Nonnull StartupConfiguration config,
218                                              @Nonnull File dumpLogDirectory )
219             {
220             }
221         };
222 
223         DefaultForkConfiguration mockedConfig = spy( config );
224         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
225         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
226         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "-Dfile.encoding=UTF-8" ) );
227         assertThat( newArgLine ).isEqualTo( "-Dfile.encoding=UTF-8" );
228     }
229 
230     @Test
231     public void shouldBeWhitespaceInsteadOfNewLines() throws Exception
232     {
233         argLine = "a\n\rb";
234         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
235                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
236                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
237         {
238 
239             @Override
240             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
241                                              @Nonnull String booterThatHasMainMethod,
242                                              @Nonnull StartupConfiguration config,
243                                              @Nonnull File dumpLogDirectory )
244             {
245             }
246         };
247 
248         DefaultForkConfiguration mockedConfig = spy( config );
249         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
250         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
251         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "a  b" ) );
252         assertThat( newArgLine ).isEqualTo( "a  b" );
253     }
254 
255     @Test
256     public void shouldEscapeThreadNumber() throws Exception
257     {
258         argLine = "-Dthread=${surefire.threadNumber}";
259         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
260                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
261                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
262         {
263 
264             @Override
265             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
266                                              @Nonnull String booterThatHasMainMethod,
267                                              @Nonnull StartupConfiguration config,
268                                              @Nonnull File dumpLogDirectory )
269             {
270             }
271         };
272 
273         DefaultForkConfiguration mockedConfig = spy( config );
274         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
275         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
276         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "-Dthread=" + forkCount ) );
277         assertThat( newArgLine ).isEqualTo( "-Dthread=" + forkCount );
278     }
279 
280     @Test
281     public void shouldEscapeForkNumber() throws Exception
282     {
283         argLine = "-Dthread=${surefire.forkNumber}";
284         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
285                 workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
286                 debug, forkCount, reuseForks, pluginPlatform, log, forkNodeFactory )
287         {
288 
289             @Override
290             protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
291                                              @Nonnull String booterThatHasMainMethod,
292                                              @Nonnull StartupConfiguration config,
293                                              @Nonnull File dumpLogDirectory )
294             {
295             }
296         };
297 
298         DefaultForkConfiguration mockedConfig = spy( config );
299         String newArgLine = invokeMethod( mockedConfig, "newJvmArgLine", new Class[] { int.class }, 2 );
300         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "interpolateArgLineWithPropertyExpressions" );
301         verifyPrivate( mockedConfig, times( 1 ) ).invoke( "extendJvmArgLine", eq( "-Dthread=" + forkCount ) );
302         assertThat( newArgLine ).isEqualTo( "-Dthread=" + forkCount );
303     }
304 
305     @Test
306     public void shouldRelocateBooterClassWhenShadefire() throws Exception
307     {
308         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
309         ClasspathConfiguration cc = new ClasspathConfiguration( true, true );
310         StartupConfiguration conf = new StartupConfiguration( "org.apache.maven.shadefire.surefire.MyProvider",
311                 cc, clc, null, Collections.<String[]>emptyList() );
312         StartupConfiguration confMock = spy( conf );
313         mockStatic( Relocator.class );
314         when( Relocator.relocate( anyString() ) ).thenCallRealMethod();
315 
316         String cls = invokeMethod( DefaultForkConfiguration.class, "findStartClass", confMock );
317 
318         verify( confMock, times( 1 ) ).isShadefire();
319         verifyStatic( Relocator.class, times( 1 ) );
320         Relocator.relocate( eq( ForkedBooter.class.getName() ) );
321 
322         assertThat( cls ).isEqualTo( "org.apache.maven.shadefire.surefire.booter.ForkedBooter" );
323         assertThat( confMock.isShadefire() ).isTrue();
324     }
325 
326     @Test
327     public void shouldNotRelocateBooterClass() throws Exception
328     {
329         ClassLoaderConfiguration clc = new ClassLoaderConfiguration( true, true );
330         ClasspathConfiguration cc = new ClasspathConfiguration( true, true );
331         StartupConfiguration conf = new StartupConfiguration( "org.apache.maven.surefire.MyProvider",
332             cc, clc, null, Collections.<String[]>emptyList() );
333         StartupConfiguration confMock = spy( conf );
334         mockStatic( Relocator.class );
335         when( Relocator.relocate( anyString() ) ).thenCallRealMethod();
336 
337         String cls = invokeMethod( DefaultForkConfiguration.class, "findStartClass", confMock );
338 
339         verify( confMock, times( 1 ) ).isShadefire();
340         verifyStatic( Relocator.class, never() );
341         Relocator.relocate( eq( ForkedBooter.class.getName() ) );
342 
343         assertThat( cls ).isEqualTo( "org.apache.maven.surefire.booter.ForkedBooter" );
344         assertThat( confMock.isShadefire() ).isFalse();
345     }
346 }