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 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 }