1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
53
54
55
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 }