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.lang.reflect.Method;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Properties;
29  
30  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
31  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
32  import org.apache.maven.plugin.surefire.util.Relocator;
33  import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
34  import org.apache.maven.surefire.booter.Classpath;
35  import org.apache.maven.surefire.booter.ClasspathConfiguration;
36  import org.apache.maven.surefire.booter.StartupConfiguration;
37  import org.apache.maven.surefire.extensions.ForkNodeFactory;
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  import org.mockito.MockedStatic;
41  
42  import static java.util.Collections.singleton;
43  import static org.assertj.core.api.Assertions.assertThat;
44  import static org.mockito.ArgumentMatchers.anyString;
45  import static org.mockito.Mockito.mock;
46  import static org.mockito.Mockito.mockStatic;
47  import static org.mockito.Mockito.spy;
48  import static org.mockito.Mockito.times;
49  import static org.mockito.Mockito.verify;
50  
51  /**
52   * Unit tests for {@link DefaultForkConfiguration}.
53   *
54   * @author Tibor Digana (tibor17)
55   * @since 2.21
56   */
57  public class DefaultForkConfigurationTest {
58      private Classpath booterClasspath;
59      private File tempDirectory;
60      private String debugLine;
61      private File workingDirectory;
62      private Properties modelProperties;
63      private String argLine;
64      private Map<String, String> environmentVariables;
65      private String[] excludedEnvironmentVariables;
66      private boolean debug;
67      private int forkCount;
68      private boolean reuseForks;
69      private Platform pluginPlatform;
70      private ConsoleLogger log;
71      private ForkNodeFactory forkNodeFactory;
72  
73      @BeforeEach
74      public void setup() {
75          booterClasspath = new Classpath(singleton("provider.jar"));
76          tempDirectory = new File("target/surefire");
77          debugLine = "";
78          workingDirectory = new File(".");
79          modelProperties = new Properties();
80          argLine = null;
81          environmentVariables = new HashMap<>();
82          excludedEnvironmentVariables = new String[0];
83          debug = true;
84          forkCount = 2;
85          reuseForks = true;
86          pluginPlatform = new Platform();
87          log = mock(ConsoleLogger.class);
88          forkNodeFactory = mock(ForkNodeFactory.class);
89      }
90  
91      @Test
92      public void shouldBeNullArgLine() throws Exception {
93          DefaultForkConfiguration config =
94                  new DefaultForkConfiguration(
95                          booterClasspath,
96                          tempDirectory,
97                          debugLine,
98                          workingDirectory,
99                          modelProperties,
100                         argLine,
101                         environmentVariables,
102                         excludedEnvironmentVariables,
103                         debug,
104                         forkCount,
105                         reuseForks,
106                         pluginPlatform,
107                         log,
108                         forkNodeFactory) {
109 
110                     @Override
111                     protected void resolveClasspath(
112                             @Nonnull Commandline cli,
113                             @Nonnull String booterThatHasMainMethod,
114                             @Nonnull StartupConfiguration config,
115                             @Nonnull File dumpLogDirectory) {}
116                 };
117 
118         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
119         assertThat(newArgLine).isEmpty();
120     }
121 
122     @Test
123     public void shouldBeEmptyArgLine() throws Exception {
124         argLine = "";
125         DefaultForkConfiguration config =
126                 new DefaultForkConfiguration(
127                         booterClasspath,
128                         tempDirectory,
129                         debugLine,
130                         workingDirectory,
131                         modelProperties,
132                         argLine,
133                         environmentVariables,
134                         excludedEnvironmentVariables,
135                         debug,
136                         forkCount,
137                         reuseForks,
138                         pluginPlatform,
139                         log,
140                         forkNodeFactory) {
141 
142                     @Override
143                     protected void resolveClasspath(
144                             @Nonnull Commandline cli,
145                             @Nonnull String booterThatHasMainMethod,
146                             @Nonnull StartupConfiguration config,
147                             @Nonnull File dumpLogDirectory) {}
148                 };
149 
150         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
151         assertThat(newArgLine).isEmpty();
152     }
153 
154     @Test
155     public void shouldBeEmptyArgLineInsteadOfNewLines() throws Exception {
156         argLine = "\n\r";
157         DefaultForkConfiguration config =
158                 new DefaultForkConfiguration(
159                         booterClasspath,
160                         tempDirectory,
161                         debugLine,
162                         workingDirectory,
163                         modelProperties,
164                         argLine,
165                         environmentVariables,
166                         excludedEnvironmentVariables,
167                         debug,
168                         forkCount,
169                         reuseForks,
170                         pluginPlatform,
171                         log,
172                         forkNodeFactory) {
173 
174                     @Override
175                     protected void resolveClasspath(
176                             @Nonnull Commandline cli,
177                             @Nonnull String booterThatHasMainMethod,
178                             @Nonnull StartupConfiguration config,
179                             @Nonnull File dumpLogDirectory) {}
180                 };
181 
182         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
183         assertThat(newArgLine).isEmpty();
184     }
185 
186     @Test
187     public void shouldBeWithoutEscaping() throws Exception {
188         argLine = "-Dfile.encoding=UTF-8";
189         DefaultForkConfiguration config =
190                 new DefaultForkConfiguration(
191                         booterClasspath,
192                         tempDirectory,
193                         debugLine,
194                         workingDirectory,
195                         modelProperties,
196                         argLine,
197                         environmentVariables,
198                         excludedEnvironmentVariables,
199                         debug,
200                         forkCount,
201                         reuseForks,
202                         pluginPlatform,
203                         log,
204                         forkNodeFactory) {
205 
206                     @Override
207                     protected void resolveClasspath(
208                             @Nonnull Commandline cli,
209                             @Nonnull String booterThatHasMainMethod,
210                             @Nonnull StartupConfiguration config,
211                             @Nonnull File dumpLogDirectory) {}
212                 };
213 
214         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
215         assertThat(newArgLine).isEqualTo("-Dfile.encoding=UTF-8");
216     }
217 
218     @Test
219     public void shouldBeWithEscaping() throws Exception {
220         modelProperties.put("encoding", "UTF-8");
221         argLine = "-Dfile.encoding=@{encoding}";
222         DefaultForkConfiguration config =
223                 new DefaultForkConfiguration(
224                         booterClasspath,
225                         tempDirectory,
226                         debugLine,
227                         workingDirectory,
228                         modelProperties,
229                         argLine,
230                         environmentVariables,
231                         excludedEnvironmentVariables,
232                         debug,
233                         forkCount,
234                         reuseForks,
235                         pluginPlatform,
236                         log,
237                         forkNodeFactory) {
238 
239                     @Override
240                     protected void resolveClasspath(
241                             @Nonnull Commandline cli,
242                             @Nonnull String booterThatHasMainMethod,
243                             @Nonnull StartupConfiguration config,
244                             @Nonnull File dumpLogDirectory) {}
245                 };
246 
247         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
248         assertThat(newArgLine).isEqualTo("-Dfile.encoding=UTF-8");
249     }
250 
251     @Test
252     public void shouldBeWhitespaceInsteadOfNewLines() throws Exception {
253         argLine = "a\n\rb";
254         DefaultForkConfiguration config =
255                 new DefaultForkConfiguration(
256                         booterClasspath,
257                         tempDirectory,
258                         debugLine,
259                         workingDirectory,
260                         modelProperties,
261                         argLine,
262                         environmentVariables,
263                         excludedEnvironmentVariables,
264                         debug,
265                         forkCount,
266                         reuseForks,
267                         pluginPlatform,
268                         log,
269                         forkNodeFactory) {
270 
271                     @Override
272                     protected void resolveClasspath(
273                             @Nonnull Commandline cli,
274                             @Nonnull String booterThatHasMainMethod,
275                             @Nonnull StartupConfiguration config,
276                             @Nonnull File dumpLogDirectory) {}
277                 };
278 
279         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
280         assertThat(newArgLine).isEqualTo("a  b");
281     }
282 
283     @Test
284     public void shouldEscapeThreadNumber() throws Exception {
285         argLine = "-Dthread=${surefire.threadNumber}";
286         DefaultForkConfiguration config =
287                 new DefaultForkConfiguration(
288                         booterClasspath,
289                         tempDirectory,
290                         debugLine,
291                         workingDirectory,
292                         modelProperties,
293                         argLine,
294                         environmentVariables,
295                         excludedEnvironmentVariables,
296                         debug,
297                         forkCount,
298                         reuseForks,
299                         pluginPlatform,
300                         log,
301                         forkNodeFactory) {
302 
303                     @Override
304                     protected void resolveClasspath(
305                             @Nonnull Commandline cli,
306                             @Nonnull String booterThatHasMainMethod,
307                             @Nonnull StartupConfiguration config,
308                             @Nonnull File dumpLogDirectory) {}
309                 };
310 
311         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
312         assertThat(newArgLine).isEqualTo("-Dthread=" + forkCount);
313     }
314 
315     @Test
316     public void shouldEscapeForkNumber() throws Exception {
317         argLine = "-Dthread=${surefire.forkNumber}";
318         DefaultForkConfiguration config =
319                 new DefaultForkConfiguration(
320                         booterClasspath,
321                         tempDirectory,
322                         debugLine,
323                         workingDirectory,
324                         modelProperties,
325                         argLine,
326                         environmentVariables,
327                         excludedEnvironmentVariables,
328                         debug,
329                         forkCount,
330                         reuseForks,
331                         pluginPlatform,
332                         log,
333                         forkNodeFactory) {
334 
335                     @Override
336                     protected void resolveClasspath(
337                             @Nonnull Commandline cli,
338                             @Nonnull String booterThatHasMainMethod,
339                             @Nonnull StartupConfiguration config,
340                             @Nonnull File dumpLogDirectory) {}
341                 };
342 
343         String newArgLine = invokeMethod(config, "newJvmArgLine", new Class[] {int.class}, 2);
344         assertThat(newArgLine).isEqualTo("-Dthread=" + forkCount);
345     }
346 
347     @Test
348     public void shouldRelocateBooterClassWhenShadefire() throws Exception {
349         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
350         ClasspathConfiguration cc = new ClasspathConfiguration(true, true);
351         StartupConfiguration conf = new StartupConfiguration(
352                 "org.apache.maven.shadefire.surefire.MyProvider", cc, clc, null, Collections.<String[]>emptyList());
353         StartupConfiguration confMock = spy(conf);
354 
355         try (MockedStatic<Relocator> relocatorMock = mockStatic(Relocator.class)) {
356             relocatorMock.when(() -> Relocator.relocate(anyString())).thenCallRealMethod();
357 
358             String cls = invokeMethod(
359                     DefaultForkConfiguration.class,
360                     "findStartClass",
361                     new Class[] {StartupConfiguration.class},
362                     confMock);
363 
364             verify(confMock, times(1)).isShadefire();
365 
366             assertThat(cls).isEqualTo("org.apache.maven.shadefire.surefire.booter.ForkedBooter");
367             assertThat(confMock.isShadefire()).isTrue();
368         }
369     }
370 
371     @Test
372     public void shouldNotRelocateBooterClass() throws Exception {
373         ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
374         ClasspathConfiguration cc = new ClasspathConfiguration(true, true);
375         StartupConfiguration conf = new StartupConfiguration(
376                 "org.apache.maven.surefire.MyProvider", cc, clc, null, Collections.<String[]>emptyList());
377         StartupConfiguration confMock = spy(conf);
378 
379         try (MockedStatic<Relocator> relocatorMock = mockStatic(Relocator.class)) {
380             relocatorMock.when(() -> Relocator.relocate(anyString())).thenCallRealMethod();
381 
382             String cls = invokeMethod(
383                     DefaultForkConfiguration.class,
384                     "findStartClass",
385                     new Class[] {StartupConfiguration.class},
386                     confMock);
387 
388             verify(confMock, times(1)).isShadefire();
389 
390             assertThat(cls).isEqualTo("org.apache.maven.surefire.booter.ForkedBooter");
391             assertThat(confMock.isShadefire()).isFalse();
392         }
393     }
394 
395     @SuppressWarnings("unchecked")
396     private static <T> T invokeMethod(Object target, String methodName, Class<?>[] paramTypes, Object... args)
397             throws Exception {
398         Class<?> clazz = target instanceof Class ? (Class<?>) target : target.getClass();
399         while (clazz != null) {
400             try {
401                 Method method = clazz.getDeclaredMethod(methodName, paramTypes);
402                 method.setAccessible(true);
403                 return (T) method.invoke(target instanceof Class ? null : target, args);
404             } catch (NoSuchMethodException e) {
405                 clazz = clazz.getSuperclass();
406             }
407         }
408         throw new NoSuchMethodException(methodName);
409     }
410 }