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 java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.lang.reflect.Method;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.ArrayDeque;
29 import java.util.Collections;
30 import java.util.jar.Manifest;
31 import java.util.zip.Deflater;
32
33 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
34 import org.apache.maven.plugin.surefire.SurefireProperties;
35 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractCommandReader;
36 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
37 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
38 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
39 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
40 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
41 import org.apache.maven.plugin.surefire.extensions.LegacyForkNodeFactory;
42 import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
43 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
44 import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
45 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
46 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
47 import org.apache.maven.surefire.api.booter.Shutdown;
48 import org.apache.maven.surefire.api.report.ReporterConfiguration;
49 import org.apache.maven.surefire.api.report.ReporterFactoryOptions;
50 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
51 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
52 import org.apache.maven.surefire.booter.Classpath;
53 import org.apache.maven.surefire.booter.PropertiesWrapper;
54 import org.apache.maven.surefire.booter.ProviderConfiguration;
55 import org.apache.maven.surefire.booter.StartupConfiguration;
56 import org.apache.maven.surefire.booter.SurefireBooterForkException;
57 import org.apache.maven.surefire.extensions.ForkNodeFactory;
58 import org.apache.maven.surefire.shared.compress.archivers.zip.Zip64Mode;
59 import org.apache.maven.surefire.shared.compress.archivers.zip.ZipArchiveEntry;
60 import org.apache.maven.surefire.shared.compress.archivers.zip.ZipArchiveOutputStream;
61 import org.junit.jupiter.api.AfterAll;
62 import org.junit.jupiter.api.BeforeAll;
63 import org.junit.jupiter.api.Test;
64
65 import static org.apache.commons.io.FileUtils.deleteQuietly;
66 import static org.assertj.core.api.Assertions.assertThat;
67 import static org.junit.jupiter.api.Assertions.assertThrows;
68 import static org.mockito.ArgumentMatchers.eq;
69 import static org.mockito.Mockito.mock;
70 import static org.mockito.Mockito.when;
71
72
73
74
75 public class ForkStarterTest {
76 private static String baseDir = System.getProperty("user.dir");
77 private static File tmp;
78
79 @BeforeAll
80 public static void prepareFiles() throws IOException {
81 File target = new File(baseDir, "target");
82 tmp = new File(target, "tmp");
83 tmp.mkdirs();
84 File booter = new File(tmp, "surefirebooter.jar");
85 booter.createNewFile();
86
87 try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(new FileOutputStream(booter))) {
88 zos.setUseZip64(Zip64Mode.Never);
89 zos.setLevel(Deflater.NO_COMPRESSION);
90
91 ZipArchiveEntry ze = new ZipArchiveEntry("META-INF/MANIFEST.MF");
92 zos.putArchiveEntry(ze);
93
94 Manifest man = new Manifest();
95
96 man.getMainAttributes().putValue("Manifest-Version", "1.0");
97 man.getMainAttributes().putValue("Main-Class", MainClass.class.getName());
98
99 man.write(zos);
100
101 zos.closeArchiveEntry();
102
103 ze = new ZipArchiveEntry("org/apache/maven/plugin/surefire/booterclient/MainClass.class");
104 zos.putArchiveEntry(ze);
105 String classesDir = Paths.get(target.getPath(), "test-classes").toString();
106 Path cls = Paths.get(
107 classesDir, "org", "apache", "maven", "plugin", "surefire", "booterclient", "MainClass.class");
108 zos.write(Files.readAllBytes(cls));
109 zos.closeArchiveEntry();
110 }
111 }
112
113 @AfterAll
114 public static void deleteTmp() {
115 deleteQuietly(tmp);
116 }
117
118 @Test
119 public void processShouldExitWithoutSayingGoodBye() throws Exception {
120 ReporterConfiguration reporterConfiguration = new ReporterConfiguration(tmp, true);
121
122 ProviderConfiguration providerConfiguration = mock(ProviderConfiguration.class);
123 when(providerConfiguration.getReporterConfiguration()).thenReturn(reporterConfiguration);
124 when(providerConfiguration.getShutdown()).thenReturn(Shutdown.EXIT);
125
126 StartupConfiguration startupConfiguration = mock(StartupConfiguration.class);
127 when(startupConfiguration.getClasspathConfiguration()).thenReturn(new ClasspathConfig());
128 when(startupConfiguration.getClassLoaderConfiguration()).thenReturn(new ClassLoaderConfiguration(false, false));
129 when(startupConfiguration.getProviderClassName()).thenReturn(MainClass.class.getName());
130
131 ForkConfiguration forkConfiguration = mock(ForkConfiguration.class);
132 when(forkConfiguration.getWorkingDirectory()).thenReturn(tmp);
133 when(forkConfiguration.getTempDirectory()).thenReturn(tmp);
134 when(forkConfiguration.getPluginPlatform()).thenReturn(new Platform());
135 Commandline cli = new Commandline();
136 cli.setWorkingDirectory(tmp);
137 cli.setExecutable(System.getProperty("java.home") + "/bin/java");
138 cli.createArg().setLine("-jar");
139 cli.createArg().setLine("surefirebooter.jar");
140 cli.createArg().setLine("fail");
141 when(forkConfiguration.createCommandLine(eq(startupConfiguration), eq(1), eq(tmp)))
142 .thenReturn(cli);
143
144 SurefireStatelessTestsetInfoReporter statelessTestsetInfoReporter = new SurefireStatelessTestsetInfoReporter();
145 SurefireConsoleOutputReporter outputReporter = new SurefireConsoleOutputReporter();
146 SurefireStatelessReporter xmlReporter = new SurefireStatelessReporter(true, "3");
147
148 StartupReportConfiguration startupReportConfiguration = new StartupReportConfiguration(
149 true,
150 true,
151 null,
152 false,
153 tmp,
154 true,
155 "",
156 null,
157 false,
158 0,
159 null,
160 null,
161 true,
162 true,
163 true,
164 false,
165 xmlReporter,
166 outputReporter,
167 statelessTestsetInfoReporter,
168 new ReporterFactoryOptions());
169
170 ConsoleLogger logger = mock(ConsoleLogger.class);
171
172 ForkStarter forkStarter = new ForkStarter(
173 providerConfiguration, startupConfiguration, forkConfiguration, 0, startupReportConfiguration, logger);
174
175 DefaultReporterFactory reporterFactory = new DefaultReporterFactory(startupReportConfiguration, logger, 1);
176
177 Class<?>[] types = {
178 Object.class,
179 PropertiesWrapper.class,
180 ForkClient.class,
181 SurefireProperties.class,
182 int.class,
183 AbstractCommandReader.class,
184 ForkNodeFactory.class,
185 boolean.class
186 };
187 TestProvidingInputStream testProvidingInputStream = new TestProvidingInputStream(new ArrayDeque<String>());
188 SurefireBooterForkException ex = assertThrows(
189 SurefireBooterForkException.class,
190 () -> invokeMethod(
191 forkStarter,
192 "fork",
193 types,
194 null,
195 new PropertiesWrapper(Collections.<String, String>emptyMap()),
196 new ForkClient(reporterFactory, null, 1),
197 new SurefireProperties(),
198 1,
199 testProvidingInputStream,
200 new LegacyForkNodeFactory(),
201 true));
202 assertThat(ex.getMessage())
203 .contains("Process Exit Code: 1")
204 .contains("The forked VM terminated without properly saying goodbye.")
205 .contains("VM crash or System.exit called?");
206 testProvidingInputStream.close();
207 }
208
209 @Test
210 public void processShouldWaitForAck() throws Exception {
211 ReporterConfiguration reporterConfiguration = new ReporterConfiguration(tmp, true);
212
213 ProviderConfiguration providerConfiguration = mock(ProviderConfiguration.class);
214 when(providerConfiguration.getReporterConfiguration()).thenReturn(reporterConfiguration);
215 when(providerConfiguration.getShutdown()).thenReturn(Shutdown.EXIT);
216
217 StartupConfiguration startupConfiguration = mock(StartupConfiguration.class);
218 when(startupConfiguration.getClasspathConfiguration()).thenReturn(new ClasspathConfig());
219 when(startupConfiguration.getClassLoaderConfiguration()).thenReturn(new ClassLoaderConfiguration(false, false));
220 when(startupConfiguration.getProviderClassName()).thenReturn(MainClass.class.getName());
221
222 ForkConfiguration forkConfiguration = mock(ForkConfiguration.class);
223 when(forkConfiguration.getWorkingDirectory()).thenReturn(tmp);
224 when(forkConfiguration.getTempDirectory()).thenReturn(tmp);
225 when(forkConfiguration.getPluginPlatform()).thenReturn(new Platform());
226 Commandline cli = new Commandline();
227 cli.setWorkingDirectory(tmp);
228 cli.setExecutable(System.getProperty("java.home") + "/bin/java");
229 cli.createArg().setLine("-jar");
230 cli.createArg().setLine("surefirebooter.jar");
231 when(forkConfiguration.createCommandLine(eq(startupConfiguration), eq(1), eq(tmp)))
232 .thenReturn(cli);
233
234 SurefireStatelessTestsetInfoReporter statelessTestsetInfoReporter = new SurefireStatelessTestsetInfoReporter();
235 SurefireConsoleOutputReporter outputReporter = new SurefireConsoleOutputReporter();
236 SurefireStatelessReporter xmlReporter = new SurefireStatelessReporter(true, "3");
237
238 StartupReportConfiguration startupReportConfiguration = new StartupReportConfiguration(
239 true,
240 true,
241 null,
242 false,
243 tmp,
244 true,
245 "",
246 null,
247 false,
248 0,
249 null,
250 null,
251 true,
252 true,
253 true,
254 false,
255 xmlReporter,
256 outputReporter,
257 statelessTestsetInfoReporter,
258 new ReporterFactoryOptions());
259
260 ConsoleLogger logger = mock(ConsoleLogger.class);
261
262 ForkStarter forkStarter = new ForkStarter(
263 providerConfiguration, startupConfiguration, forkConfiguration, 0, startupReportConfiguration, logger);
264
265 DefaultReporterFactory reporterFactory = new DefaultReporterFactory(startupReportConfiguration, logger, 1);
266
267 Class<?>[] types = {
268 Object.class,
269 PropertiesWrapper.class,
270 ForkClient.class,
271 SurefireProperties.class,
272 int.class,
273 AbstractCommandReader.class,
274 ForkNodeFactory.class,
275 boolean.class
276 };
277 TestLessInputStream testLessInputStream = new TestLessInputStreamBuilder().build();
278 invokeMethod(
279 forkStarter,
280 "fork",
281 types,
282 null,
283 new PropertiesWrapper(Collections.<String, String>emptyMap()),
284 new ForkClient(reporterFactory, testLessInputStream, 1),
285 new SurefireProperties(),
286 1,
287 testLessInputStream,
288 new LegacyForkNodeFactory(),
289 true);
290 testLessInputStream.close();
291 }
292
293 @SuppressWarnings("unchecked")
294 private static <T> T invokeMethod(Object target, String methodName, Class<?>[] paramTypes, Object... args)
295 throws Exception {
296 Class<?> clazz = target.getClass();
297 while (clazz != null) {
298 try {
299 Method method = clazz.getDeclaredMethod(methodName, paramTypes);
300 method.setAccessible(true);
301 try {
302 return (T) method.invoke(target, args);
303 } catch (java.lang.reflect.InvocationTargetException e) {
304 Throwable cause = e.getCause();
305 if (cause instanceof Exception) {
306 throw (Exception) cause;
307 }
308 throw e;
309 }
310 } catch (NoSuchMethodException e) {
311 clazz = clazz.getSuperclass();
312 }
313 }
314 throw new NoSuchMethodException(methodName);
315 }
316
317 private static class ClasspathConfig extends AbstractPathConfiguration {
318 ClasspathConfig() {
319 this(Classpath.emptyClasspath(), false, false);
320 }
321
322 private ClasspathConfig(Classpath surefireClasspathUrls, boolean enableAssertions, boolean childDelegation) {
323 super(surefireClasspathUrls, enableAssertions, childDelegation);
324 }
325
326 @Override
327 public Classpath getTestClasspath() {
328 return Classpath.emptyClasspath();
329 }
330
331 @Override
332 public boolean isModularPathConfig() {
333 return false;
334 }
335
336 @Override
337 public boolean isClassPathConfig() {
338 return true;
339 }
340
341 @Override
342 protected Classpath getInprocClasspath() {
343 return Classpath.emptyClasspath();
344 }
345 }
346 }