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