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   *   https://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  
20  package org.apache.commons.exec.issues;
21  
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.io.File;
25  
26  import org.apache.commons.exec.CommandLine;
27  import org.apache.commons.exec.DefaultExecutor;
28  import org.apache.commons.exec.ExecuteException;
29  import org.apache.commons.exec.ExecuteWatchdog;
30  import org.apache.commons.exec.OS;
31  import org.apache.commons.exec.PumpStreamHandler;
32  import org.apache.commons.exec.TestUtil;
33  import org.apache.commons.lang3.SystemProperties;
34  import org.junit.jupiter.api.Test;
35  
36  /**
37   * Test the patch for EXEC-41 (https://issues.apache.org/jira/browse/EXEC-41).
38   */
39  class Exec41Test {
40  
41      private final File testDir = new File("src/test/scripts");
42      private final File pingScript = TestUtil.resolveScriptFileForOS(testDir + "/ping");
43  
44      /**
45       * Test EXEC-41 with a disabled PumpStreamHandler to check if we could return immediately after killing the process (no streams implies no blocking stream
46       * pumper threads). But you have to be 100% sure that the subprocess is not writing to 'stdout' and 'stderr'.
47       *
48       * For this test we are using the batch file - under Windows the 'ping' process can't be killed (not supported by Win32) and will happily run the given time
49       * (e.g. 10 seconds) even hwen the batch file is already killed.
50       *
51       * @throws Exception the test failed
52       */
53      @Test
54      void testExec41WithoutStreams() throws Exception {
55  
56          final CommandLine cmdLine = new CommandLine(pingScript);
57          cmdLine.addArgument("10"); // sleep 10 seconds
58          final DefaultExecutor executor = DefaultExecutor.builder().get();
59          final ExecuteWatchdog watchdog = new ExecuteWatchdog(2 * 1000); // allow process no more than 2 seconds
60  
61          // create a custom "PumpStreamHandler" doing no pumping at all
62          final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(null, null, null);
63  
64          executor.setWatchdog(watchdog);
65          executor.setStreamHandler(pumpStreamHandler);
66  
67          final long startTime = System.currentTimeMillis();
68  
69          try {
70              executor.execute(cmdLine);
71          } catch (final ExecuteException e) {
72              System.out.println(e);
73          }
74  
75          final long duration = System.currentTimeMillis() - startTime;
76  
77          System.out.println("Process completed in " + duration + " millis; below is its output");
78  
79          if (watchdog.killedProcess()) {
80              System.out.println("Process timed out and was killed.");
81          }
82  
83          assertTrue(watchdog.killedProcess(), "The process was killed by the watchdog");
84          assertTrue(duration < 9000, () -> "Skipping the Thread.join() did not work, duration=" + duration);
85      }
86  
87      /**
88       *
89       * When a process runs longer than allowed by a configured watchdog's timeout, the watchdog tries to destroy it and then DefaultExecutor tries to clean up
90       * by joining with all installed pump stream threads. Problem is, that sometimes the native process doesn't die and thus streams aren't closed and the
91       * stream threads do not complete.
92       *
93       * @throws Exception the test failed
94       */
95      @Test
96      void testExec41WithStreams() throws Exception {
97  
98          CommandLine cmdLine;
99  
100         if (OS.isFamilyWindows()) {
101             cmdLine = CommandLine.parse("ping.exe -n 10 -w 1000 127.0.0.1");
102         } else if ("HP-UX".equals(SystemProperties.getOsName())) {
103             // see EXEC-52 - option must appear after the hostname!
104             cmdLine = CommandLine.parse("ping 127.0.0.1 -n 10");
105         } else if (OS.isFamilyUnix()) {
106             cmdLine = CommandLine.parse("ping -c 10 127.0.0.1");
107         } else {
108             System.err.println("The test 'testExec41WithStreams' does not support the following OS : " + SystemProperties.getOsName());
109             return;
110         }
111 
112         final DefaultExecutor executor = DefaultExecutor.builder().get();
113         final ExecuteWatchdog watchdog = new ExecuteWatchdog(2 * 1000); // allow process no more than 2 seconds
114         final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(System.out, System.err);
115         // this method was part of the patch I reverted
116         // pumpStreamHandler.setAlwaysWaitForStreamThreads(false);
117 
118         executor.setWatchdog(watchdog);
119         executor.setStreamHandler(pumpStreamHandler);
120 
121         final long startTime = System.currentTimeMillis();
122 
123         try {
124             executor.execute(cmdLine);
125         } catch (final ExecuteException e) {
126             // nothing to do
127         }
128 
129         final long duration = System.currentTimeMillis() - startTime;
130 
131         System.out.println("Process completed in " + duration + " millis; below is its output");
132 
133         if (watchdog.killedProcess()) {
134             System.out.println("Process timed out and was killed by watchdog.");
135         }
136 
137         assertTrue(watchdog.killedProcess(), "The process was killed by the watchdog");
138         assertTrue(duration < 9000, "Skipping the Thread.join() did not work");
139     }
140 }