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