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.surefire.booter;
20  
21  import javax.annotation.Nonnull;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InterruptedIOException;
26  import java.lang.management.ManagementFactory;
27  import java.util.Random;
28  import java.util.regex.Matcher;
29  
30  import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
31  import org.junit.After;
32  import org.junit.Before;
33  import org.junit.Rule;
34  import org.junit.Test;
35  import org.junit.rules.ExpectedException;
36  import org.junit.rules.TemporaryFolder;
37  
38  import static java.nio.charset.StandardCharsets.US_ASCII;
39  import static java.nio.file.Files.readAllBytes;
40  import static java.util.concurrent.TimeUnit.SECONDS;
41  import static org.apache.maven.surefire.booter.ProcessInfo.unixProcessInfo;
42  import static org.apache.maven.surefire.booter.ProcessInfo.windowsProcessInfo;
43  import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_UNIX;
44  import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_WINDOWS;
45  import static org.assertj.core.api.Assertions.assertThat;
46  import static org.hamcrest.CoreMatchers.is;
47  import static org.hamcrest.CoreMatchers.not;
48  import static org.hamcrest.CoreMatchers.notNullValue;
49  import static org.junit.Assert.fail;
50  import static org.junit.Assume.assumeThat;
51  import static org.junit.Assume.assumeTrue;
52  import static org.powermock.reflect.Whitebox.invokeMethod;
53  import static org.powermock.reflect.Whitebox.setInternalState;
54  
55  /**
56   * Testing {@link PpidChecker} on a platform.
57   *
58   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
59   * @since 2.20.1
60   */
61  @SuppressWarnings("checkstyle:magicnumber")
62  public class PpidCheckerTest {
63      private static final Random RND = new Random();
64  
65      @Rule
66      public final ExpectedException exceptions = ExpectedException.none();
67  
68      @Rule
69      public final TemporaryFolder tempFolder = new TemporaryFolder();
70  
71      private File reportsDir;
72      private String dumpFileName;
73  
74      @Before
75      public void initTmpFile() {
76          reportsDir = tempFolder.getRoot();
77          dumpFileName = "surefire-" + RND.nextLong();
78      }
79  
80      @After
81      public void deleteTmpFiles() {
82          tempFolder.delete();
83      }
84  
85      @Test
86      public void canExecuteUnixPs() {
87          assumeTrue(IS_OS_UNIX);
88          assertThat(PpidChecker.canExecuteUnixPs())
89                  .as("Surefire should be tested on real box OS, e.g. Ubuntu or FreeBSD.")
90                  .isTrue();
91      }
92  
93      @Test
94      public void shouldHavePidAtBegin() {
95          String expectedPid =
96                  ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
97  
98          PpidChecker checker = new PpidChecker(expectedPid);
99          ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() : checker.windows();
100 
101         assertThat(processInfo).isNotNull();
102 
103         assertThat(checker.canUse()).isTrue();
104 
105         assertThat(checker.isProcessAlive()).isTrue();
106 
107         assertThat(processInfo.getPID()).isEqualTo(expectedPid);
108 
109         assertThat(processInfo.getTime()).isGreaterThan(0L);
110     }
111 
112     @Test
113     public void shouldHavePid() throws Exception {
114         String expectedPid =
115                 ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
116 
117         PpidChecker checker = new PpidChecker(expectedPid);
118         setInternalState(
119                 checker,
120                 "parentProcessInfo",
121                 IS_OS_UNIX
122                         ? unixProcessInfo(expectedPid, 0L)
123                         : windowsProcessInfo(expectedPid, windowsProcessStartTime(checker)));
124 
125         // the etime in Unix is measured in seconds. So let's wait 1s at least.
126         SECONDS.sleep(1L);
127 
128         ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() : checker.windows();
129 
130         assertThat(processInfo).isNotNull();
131 
132         assertThat(checker.canUse()).isTrue();
133 
134         assertThat(checker.isProcessAlive()).isTrue();
135 
136         assertThat(processInfo.getPID()).isEqualTo(expectedPid);
137 
138         assertThat(processInfo.getTime()).isGreaterThan(0L);
139 
140         assertThat(checker.toString())
141                 .contains("ppid=" + expectedPid)
142                 .contains("stopped=false")
143                 .contains("invalid=false")
144                 .contains("error=false");
145 
146         checker.destroyActiveCommands();
147         assertThat(checker.canUse()).isFalse();
148         assertThat((boolean) invokeMethod(checker, "isStopped")).isTrue();
149     }
150 
151     @Test
152     public void shouldBeStopped() {
153         PpidChecker checker = new PpidChecker("0");
154         checker.stop();
155 
156         assertThat(checker.canUse()).isFalse();
157 
158         exceptions.expect(IllegalStateException.class);
159         exceptions.expectMessage("irrelevant to call isProcessAlive()");
160 
161         checker.isProcessAlive();
162 
163         fail("this test should throw exception");
164     }
165 
166     @Test
167     public void shouldBeStoppedCheckerWithError() throws Exception {
168         String expectedPid =
169                 ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
170         DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
171 
172         PpidChecker checker = new PpidChecker(expectedPid);
173         checker.stop();
174 
175         ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() : checker.windows();
176         assertThat(processInfo.isError()).isTrue();
177 
178         String error = new String(readAllBytes(new File(reportsDir, dumpFileName + ".dump").toPath()));
179 
180         assertThat(error).contains("<<exit>> <<0>>").contains("<<stopped>> <<true>>");
181     }
182 
183     @Test
184     public void shouldBeEmptyDump() throws Exception {
185         String expectedPid =
186                 ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
187         DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
188 
189         PpidChecker checker = new PpidChecker(expectedPid);
190 
191         try {
192             Thread.currentThread().interrupt();
193 
194             ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() : checker.windows();
195             //noinspection ResultOfMethodCallIgnored
196             Thread.interrupted();
197             assertThat(processInfo.isError()).isTrue();
198 
199             File dumpFile = new File(reportsDir, dumpFileName + ".dump");
200             if (dumpFile.exists()) {
201                 String error = new String(readAllBytes(dumpFile.toPath()));
202 
203                 assertThat(error).contains("<<exit>>").contains("<<stopped>> <<false>>");
204             }
205         } finally {
206             //noinspection ResultOfMethodCallIgnored
207             Thread.interrupted();
208         }
209     }
210 
211     @Test
212     public void shouldStartedProcessThrowInterruptedException() throws Exception {
213         String expectedPid =
214                 ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
215         DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
216 
217         PpidChecker checker = new PpidChecker(expectedPid);
218 
219         PpidChecker.ProcessInfoConsumer consumer = checker.new ProcessInfoConsumer(US_ASCII.name()) {
220             @Nonnull
221             @Override
222             ProcessInfo consumeLine(String line, ProcessInfo previousProcessInfo) throws Exception {
223                 throw new InterruptedException();
224             }
225         };
226 
227         String[] cmd =
228                 IS_OS_WINDOWS ? new String[] {"CMD", "/A", "/X", "/C", "dir"} : new String[] {"/bin/sh", "-c", "ls"};
229 
230         assertThat(consumer.execute(cmd).isError()).isTrue();
231         assertThat(new File(reportsDir, dumpFileName + ".dump")).doesNotExist();
232     }
233 
234     @Test
235     public void shouldStartedProcessThrowInterruptedIOException() throws Exception {
236         String expectedPid =
237                 ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
238         DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
239 
240         PpidChecker checker = new PpidChecker(expectedPid);
241 
242         PpidChecker.ProcessInfoConsumer consumer = checker.new ProcessInfoConsumer(US_ASCII.name()) {
243             @Nonnull
244             @Override
245             ProcessInfo consumeLine(String line, ProcessInfo previousProcessInfo) throws Exception {
246                 throw new InterruptedIOException();
247             }
248         };
249 
250         String[] cmd =
251                 IS_OS_WINDOWS ? new String[] {"CMD", "/A", "/X", "/C", "dir"} : new String[] {"/bin/sh", "-c", "ls"};
252 
253         assertThat(consumer.execute(cmd).isError()).isTrue();
254         assertThat(new File(reportsDir, dumpFileName + ".dump")).doesNotExist();
255     }
256 
257     @Test
258     public void shouldStartedProcessThrowIOException() throws Exception {
259         String expectedPid =
260                 ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
261         DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
262 
263         PpidChecker checker = new PpidChecker(expectedPid);
264 
265         PpidChecker.ProcessInfoConsumer consumer = checker.new ProcessInfoConsumer(US_ASCII.name()) {
266             @Nonnull
267             @Override
268             ProcessInfo consumeLine(String line, ProcessInfo previousProcessInfo) throws Exception {
269                 throw new IOException("wrong command");
270             }
271         };
272 
273         String[] cmd =
274                 IS_OS_WINDOWS ? new String[] {"CMD", "/A", "/X", "/C", "dir"} : new String[] {"/bin/sh", "-c", "ls"};
275 
276         assertThat(consumer.execute(cmd).isError()).isTrue();
277 
278         File dumpFile = new File(reportsDir, dumpFileName + ".dump");
279 
280         String error = new String(readAllBytes(dumpFile.toPath()));
281 
282         assertThat(error).contains(IOException.class.getName()).contains("wrong command");
283     }
284 
285     @Test
286     public void shouldNotFindSuchPID() {
287         PpidChecker checker = new PpidChecker("1000000");
288         setInternalState(checker, "parentProcessInfo", ProcessInfo.ERR_PROCESS_INFO);
289 
290         assertThat(checker.canUse()).isFalse();
291 
292         exceptions.expect(IllegalStateException.class);
293         exceptions.expectMessage("irrelevant to call isProcessAlive()");
294 
295         checker.isProcessAlive();
296 
297         fail("this test should throw exception");
298     }
299 
300     @Test
301     public void shouldNotBeAlive() {
302         PpidChecker checker = new PpidChecker("1000000");
303 
304         assertThat(checker.canUse()).isTrue();
305 
306         assertThat(checker.isProcessAlive()).isFalse();
307     }
308 
309     @Test
310     public void shouldParseEtime() {
311         Matcher m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("38 1234567890");
312         assertThat(m.matches()).isFalse();
313 
314         m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("05:38 1234567890");
315         assertThat(m.matches()).isTrue();
316         assertThat(PpidChecker.fromDays(m)).isEqualTo(0L);
317         assertThat(PpidChecker.fromHours(m)).isEqualTo(0L);
318         assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
319         assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
320         assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
321 
322         m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("00:05:38 1234567890");
323         assertThat(m.matches()).isTrue();
324         assertThat(PpidChecker.fromDays(m)).isEqualTo(0L);
325         assertThat(PpidChecker.fromHours(m)).isEqualTo(0L);
326         assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
327         assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
328         assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
329 
330         m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("01:05:38 1234567890");
331         assertThat(m.matches()).isTrue();
332         assertThat(PpidChecker.fromDays(m)).isEqualTo(0L);
333         assertThat(PpidChecker.fromHours(m)).isEqualTo(3600L);
334         assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
335         assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
336         assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
337 
338         m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("02-01:05:38 1234567890");
339         assertThat(m.matches()).isTrue();
340         assertThat(PpidChecker.fromDays(m)).isEqualTo(2 * 24 * 3600L);
341         assertThat(PpidChecker.fromHours(m)).isEqualTo(3600L);
342         assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
343         assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
344         assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
345 
346         m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("02-1:5:3 1234567890");
347         assertThat(m.matches()).isTrue();
348         assertThat(PpidChecker.fromDays(m)).isEqualTo(2 * 24 * 3600L);
349         assertThat(PpidChecker.fromHours(m)).isEqualTo(3600L);
350         assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
351         assertThat(PpidChecker.fromSeconds(m)).isEqualTo(3L);
352         assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
353     }
354 
355     @Test
356     public void shouldParseBusyboxHoursEtime() {
357         Matcher m = PpidChecker.BUSYBOX_CMD_OUT_PATTERN.matcher("38 1234567890");
358         assertThat(m.matches()).isFalse();
359 
360         m = PpidChecker.BUSYBOX_CMD_OUT_PATTERN.matcher("05h38 1234567890");
361         assertThat(m.matches()).isTrue();
362         assertThat(PpidChecker.fromBusyboxHours(m)).isEqualTo(3600 * 5L);
363         assertThat(PpidChecker.fromBusyboxMinutes(m)).isEqualTo(60 * 38L);
364         assertThat(PpidChecker.fromBusyboxPID(m)).isEqualTo("1234567890");
365     }
366 
367     @Test
368     public void shouldHaveSystemPathToWmicOnWindows() throws Exception {
369         assumeTrue(IS_OS_WINDOWS);
370         assumeThat(System.getenv("SystemRoot"), is(notNullValue()));
371         assumeThat(System.getenv("SystemRoot"), is(not("")));
372         assumeTrue(new File(System.getenv("SystemRoot"), "System32\\Wbem").isDirectory());
373         assumeTrue(new File(System.getenv("SystemRoot"), "System32\\Wbem\\wmic.exe").isFile());
374         assertThat((Boolean) invokeMethod(PpidChecker.class, "hasWmicStandardSystemPath"))
375                 .isTrue();
376         assertThat(new File(System.getenv("SystemRoot"), "System32\\Wbem\\wmic.exe"))
377                 .isFile();
378     }
379 
380     @Test
381     public void shouldBeTypeNull() {
382         assertThat(ProcessCheckerType.toEnum(null)).isNull();
383 
384         assertThat(ProcessCheckerType.toEnum("   ")).isNull();
385 
386         assertThat(ProcessCheckerType.isValid(null)).isTrue();
387     }
388 
389     @Test
390     public void shouldBeException() {
391         exceptions.expect(IllegalArgumentException.class);
392         exceptions.expectMessage("unknown process checker");
393 
394         assertThat(ProcessCheckerType.toEnum("anything else")).isNull();
395     }
396 
397     @Test
398     public void shouldNotBeValid() {
399         assertThat(ProcessCheckerType.isValid("anything")).isFalse();
400     }
401 
402     @Test
403     public void shouldBeTypePing() {
404         assertThat(ProcessCheckerType.toEnum("ping")).isEqualTo(ProcessCheckerType.PING);
405 
406         assertThat(ProcessCheckerType.isValid("ping")).isTrue();
407 
408         assertThat(ProcessCheckerType.PING.getType()).isEqualTo("ping");
409     }
410 
411     @Test
412     public void shouldBeTypeNative() {
413         assertThat(ProcessCheckerType.toEnum("native")).isEqualTo(ProcessCheckerType.NATIVE);
414 
415         assertThat(ProcessCheckerType.isValid("native")).isTrue();
416 
417         assertThat(ProcessCheckerType.NATIVE.getType()).isEqualTo("native");
418     }
419 
420     @Test
421     public void shouldBeTypeAll() {
422         assertThat(ProcessCheckerType.toEnum("all")).isEqualTo(ProcessCheckerType.ALL);
423 
424         assertThat(ProcessCheckerType.isValid("all")).isTrue();
425 
426         assertThat(ProcessCheckerType.ALL.getType()).isEqualTo("all");
427     }
428 
429     private static long windowsProcessStartTime(PpidChecker checker) {
430         return checker.windows().getTime();
431     }
432 }