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.util.Collections;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.Properties;
28  
29  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
30  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
31  import org.apache.maven.plugin.surefire.util.Relocator;
32  import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
33  import org.apache.maven.surefire.booter.Classpath;
34  import org.apache.maven.surefire.booter.ClasspathConfiguration;
35  import org.apache.maven.surefire.booter.ForkedBooter;
36  import org.apache.maven.surefire.booter.StartupConfiguration;
37  import org.apache.maven.surefire.extensions.ForkNodeFactory;
38  import org.junit.Before;
39  import org.junit.Test;
40  import org.junit.runner.RunWith;
41  import org.powermock.core.classloader.annotations.PowerMockIgnore;
42  import org.powermock.core.classloader.annotations.PrepareForTest;
43  import org.powermock.modules.junit4.PowerMockRunner;
44  
45  import static java.util.Collections.singleton;
46  import static org.assertj.core.api.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      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      private ForkNodeFactory forkNodeFactory;
84  
85      @Before
86      public void setup() {
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         forkNodeFactory = mock(ForkNodeFactory.class);
101     }
102 
103     @Test
104     public void shouldBeNullArgLine() throws Exception {
105         DefaultForkConfiguration config =
106                 new DefaultForkConfiguration(
107                         booterClasspath,
108                         tempDirectory,
109                         debugLine,
110                         workingDirectory,
111                         modelProperties,
112                         argLine,
113                         environmentVariables,
114                         excludedEnvironmentVariables,
115                         debug,
116                         forkCount,
117                         reuseForks,
118                         pluginPlatform,
119                         log,
120                         forkNodeFactory) {
121 
122                     @Override
123                     protected void resolveClasspath(
124                             @Nonnull Commandline cli,
125                             @Nonnull String booterThatHasMainMethod,
126                             @Nonnull StartupConfiguration config,
127                             @Nonnull File dumpLogDirectory) {}
128                 };
129 
130         DefaultForkConfiguration mockedConfig = spy(config);
131         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
132         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
133         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq(""));
134         assertThat(newArgLine).isEmpty();
135     }
136 
137     @Test
138     public void shouldBeEmptyArgLine() throws Exception {
139         argLine = "";
140         DefaultForkConfiguration config =
141                 new DefaultForkConfiguration(
142                         booterClasspath,
143                         tempDirectory,
144                         debugLine,
145                         workingDirectory,
146                         modelProperties,
147                         argLine,
148                         environmentVariables,
149                         excludedEnvironmentVariables,
150                         debug,
151                         forkCount,
152                         reuseForks,
153                         pluginPlatform,
154                         log,
155                         forkNodeFactory) {
156 
157                     @Override
158                     protected void resolveClasspath(
159                             @Nonnull Commandline cli,
160                             @Nonnull String booterThatHasMainMethod,
161                             @Nonnull StartupConfiguration config,
162                             @Nonnull File dumpLogDirectory) {}
163                 };
164 
165         DefaultForkConfiguration mockedConfig = spy(config);
166         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
167         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
168         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq(""));
169         assertThat(newArgLine).isEmpty();
170     }
171 
172     @Test
173     public void shouldBeEmptyArgLineInsteadOfNewLines() throws Exception {
174         argLine = "\n\r";
175         DefaultForkConfiguration config =
176                 new DefaultForkConfiguration(
177                         booterClasspath,
178                         tempDirectory,
179                         debugLine,
180                         workingDirectory,
181                         modelProperties,
182                         argLine,
183                         environmentVariables,
184                         excludedEnvironmentVariables,
185                         debug,
186                         forkCount,
187                         reuseForks,
188                         pluginPlatform,
189                         log,
190                         forkNodeFactory) {
191 
192                     @Override
193                     protected void resolveClasspath(
194                             @Nonnull Commandline cli,
195                             @Nonnull String booterThatHasMainMethod,
196                             @Nonnull StartupConfiguration config,
197                             @Nonnull File dumpLogDirectory) {}
198                 };
199 
200         DefaultForkConfiguration mockedConfig = spy(config);
201         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
202         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
203         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq(""));
204         assertThat(newArgLine).isEmpty();
205     }
206 
207     @Test
208     public void shouldBeWithoutEscaping() throws Exception {
209         argLine = "-Dfile.encoding=UTF-8";
210         DefaultForkConfiguration config =
211                 new DefaultForkConfiguration(
212                         booterClasspath,
213                         tempDirectory,
214                         debugLine,
215                         workingDirectory,
216                         modelProperties,
217                         argLine,
218                         environmentVariables,
219                         excludedEnvironmentVariables,
220                         debug,
221                         forkCount,
222                         reuseForks,
223                         pluginPlatform,
224                         log,
225                         forkNodeFactory) {
226 
227                     @Override
228                     protected void resolveClasspath(
229                             @Nonnull Commandline cli,
230                             @Nonnull String booterThatHasMainMethod,
231                             @Nonnull StartupConfiguration config,
232                             @Nonnull File dumpLogDirectory) {}
233                 };
234 
235         DefaultForkConfiguration mockedConfig = spy(config);
236         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
237         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
238         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq("-Dfile.encoding=UTF-8"));
239         assertThat(newArgLine).isEqualTo("-Dfile.encoding=UTF-8");
240     }
241 
242     @Test
243     public void shouldBeWithEscaping() throws Exception {
244         modelProperties.put("encoding", "UTF-8");
245         argLine = "-Dfile.encoding=@{encoding}";
246         DefaultForkConfiguration config =
247                 new DefaultForkConfiguration(
248                         booterClasspath,
249                         tempDirectory,
250                         debugLine,
251                         workingDirectory,
252                         modelProperties,
253                         argLine,
254                         environmentVariables,
255                         excludedEnvironmentVariables,
256                         debug,
257                         forkCount,
258                         reuseForks,
259                         pluginPlatform,
260                         log,
261                         forkNodeFactory) {
262 
263                     @Override
264                     protected void resolveClasspath(
265                             @Nonnull Commandline cli,
266                             @Nonnull String booterThatHasMainMethod,
267                             @Nonnull StartupConfiguration config,
268                             @Nonnull File dumpLogDirectory) {}
269                 };
270 
271         DefaultForkConfiguration mockedConfig = spy(config);
272         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
273         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
274         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq("-Dfile.encoding=UTF-8"));
275         assertThat(newArgLine).isEqualTo("-Dfile.encoding=UTF-8");
276     }
277 
278     @Test
279     public void shouldBeWhitespaceInsteadOfNewLines() throws Exception {
280         argLine = "a\n\rb";
281         DefaultForkConfiguration config =
282                 new DefaultForkConfiguration(
283                         booterClasspath,
284                         tempDirectory,
285                         debugLine,
286                         workingDirectory,
287                         modelProperties,
288                         argLine,
289                         environmentVariables,
290                         excludedEnvironmentVariables,
291                         debug,
292                         forkCount,
293                         reuseForks,
294                         pluginPlatform,
295                         log,
296                         forkNodeFactory) {
297 
298                     @Override
299                     protected void resolveClasspath(
300                             @Nonnull Commandline cli,
301                             @Nonnull String booterThatHasMainMethod,
302                             @Nonnull StartupConfiguration config,
303                             @Nonnull File dumpLogDirectory) {}
304                 };
305 
306         DefaultForkConfiguration mockedConfig = spy(config);
307         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
308         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
309         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq("a  b"));
310         assertThat(newArgLine).isEqualTo("a  b");
311     }
312 
313     @Test
314     public void shouldEscapeThreadNumber() throws Exception {
315         argLine = "-Dthread=${surefire.threadNumber}";
316         DefaultForkConfiguration config =
317                 new DefaultForkConfiguration(
318                         booterClasspath,
319                         tempDirectory,
320                         debugLine,
321                         workingDirectory,
322                         modelProperties,
323                         argLine,
324                         environmentVariables,
325                         excludedEnvironmentVariables,
326                         debug,
327                         forkCount,
328                         reuseForks,
329                         pluginPlatform,
330                         log,
331                         forkNodeFactory) {
332 
333                     @Override
334                     protected void resolveClasspath(
335                             @Nonnull Commandline cli,
336                             @Nonnull String booterThatHasMainMethod,
337                             @Nonnull StartupConfiguration config,
338                             @Nonnull File dumpLogDirectory) {}
339                 };
340 
341         DefaultForkConfiguration mockedConfig = spy(config);
342         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
343         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
344         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq("-Dthread=" + forkCount));
345         assertThat(newArgLine).isEqualTo("-Dthread=" + forkCount);
346     }
347 
348     @Test
349     public void shouldEscapeForkNumber() throws Exception {
350         argLine = "-Dthread=${surefire.forkNumber}";
351         DefaultForkConfiguration config =
352                 new DefaultForkConfiguration(
353                         booterClasspath,
354                         tempDirectory,
355                         debugLine,
356                         workingDirectory,
357                         modelProperties,
358                         argLine,
359                         environmentVariables,
360                         excludedEnvironmentVariables,
361                         debug,
362                         forkCount,
363                         reuseForks,
364                         pluginPlatform,
365                         log,
366                         forkNodeFactory) {
367 
368                     @Override
369                     protected void resolveClasspath(
370                             @Nonnull Commandline cli,
371                             @Nonnull String booterThatHasMainMethod,
372                             @Nonnull StartupConfiguration config,
373                             @Nonnull File dumpLogDirectory) {}
374                 };
375 
376         DefaultForkConfiguration mockedConfig = spy(config);
377         String newArgLine = invokeMethod(mockedConfig, "newJvmArgLine", new Class[] {int.class}, 2);
378         verifyPrivate(mockedConfig, times(1)).invoke("interpolateArgLineWithPropertyExpressions");
379         verifyPrivate(mockedConfig, times(1)).invoke("extendJvmArgLine", eq("-Dthread=" + forkCount));
380         assertThat(newArgLine).isEqualTo("-Dthread=" + forkCount);
381     }
382 
383     @Test
384     public void shouldRelocateBooterClassWhenShadefire() throws Exception {
385         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
386         ClasspathConfiguration cc = new ClasspathConfiguration(true, true);
387         StartupConfiguration conf = new StartupConfiguration(
388                 "org.apache.maven.shadefire.surefire.MyProvider", cc, clc, null, Collections.<String[]>emptyList());
389         StartupConfiguration confMock = spy(conf);
390         mockStatic(Relocator.class);
391         when(Relocator.relocate(anyString())).thenCallRealMethod();
392 
393         String cls = invokeMethod(DefaultForkConfiguration.class, "findStartClass", confMock);
394 
395         verify(confMock, times(1)).isShadefire();
396         verifyStatic(Relocator.class, times(1));
397         Relocator.relocate(eq(ForkedBooter.class.getName()));
398 
399         assertThat(cls).isEqualTo("org.apache.maven.shadefire.surefire.booter.ForkedBooter");
400         assertThat(confMock.isShadefire()).isTrue();
401     }
402 
403     @Test
404     public void shouldNotRelocateBooterClass() throws Exception {
405         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
406         ClasspathConfiguration cc = new ClasspathConfiguration(true, true);
407         StartupConfiguration conf = new StartupConfiguration(
408                 "org.apache.maven.surefire.MyProvider", cc, clc, null, Collections.<String[]>emptyList());
409         StartupConfiguration confMock = spy(conf);
410         mockStatic(Relocator.class);
411         when(Relocator.relocate(anyString())).thenCallRealMethod();
412 
413         String cls = invokeMethod(DefaultForkConfiguration.class, "findStartClass", confMock);
414 
415         verify(confMock, times(1)).isShadefire();
416         verifyStatic(Relocator.class, never());
417         Relocator.relocate(eq(ForkedBooter.class.getName()));
418 
419         assertThat(cls).isEqualTo("org.apache.maven.surefire.booter.ForkedBooter");
420         assertThat(confMock.isShadefire()).isFalse();
421     }
422 }