View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.exec;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assertions.fail;
27  
28  import java.io.BufferedReader;
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.FileReader;
34  import java.io.IOException;
35  import java.io.OutputStream;
36  import java.nio.file.Files;
37  import java.nio.file.Path;
38  import java.time.Duration;
39  import java.util.HashMap;
40  import java.util.Map;
41  
42  import org.apache.commons.exec.environment.EnvironmentUtils;
43  import org.junit.jupiter.api.AfterEach;
44  import org.junit.jupiter.api.BeforeAll;
45  import org.junit.jupiter.api.BeforeEach;
46  import org.junit.jupiter.api.Disabled;
47  import org.junit.jupiter.api.Test;
48  import org.junitpioneer.jupiter.SetSystemProperty;
49  
50  /**
51   */
52  //turn on debug mode and throw an exception for each encountered problem
53  @SetSystemProperty(key = "org.apache.commons.exec.lenient", value = "false")
54  @SetSystemProperty(key = "org.apache.commons.exec.debug", value = "true")
55  public class DefaultExecutorTest {
56  
57      /** Maximum time to wait (15s) */
58      private static final int WAITFOR_TIMEOUT = 15000;
59      private static final Duration WAITFOR_TIMEOUT_D = Duration.ofMillis(WAITFOR_TIMEOUT);
60  
61      // Get suitable exit codes for the OS
62      private static int SUCCESS_STATUS; // test script successful exit code
63      private static int ERROR_STATUS; // test script error exit code
64  
65      @BeforeAll
66      public static void classSetUp() {
67          final int[] statuses = TestUtil.getTestScriptCodesForOS();
68          SUCCESS_STATUS = statuses[0];
69          ERROR_STATUS = statuses[1];
70      }
71  
72      private final Executor exec = DefaultExecutor.builder().get();
73  
74      private final File testDir = new File("src/test/scripts");
75      private final File foreverOutputFile = new File("./target/forever.txt");
76      private ByteArrayOutputStream baos;
77      private final File testScript = TestUtil.resolveScriptForOS(testDir + "/test");
78      private final File errorTestScript = TestUtil.resolveScriptForOS(testDir + "/error");
79      private final File foreverTestScript = TestUtil.resolveScriptForOS(testDir + "/forever");
80      private final File nonExistingTestScript = TestUtil.resolveScriptForOS(testDir + "/grmpffffff");
81      private final File redirectScript = TestUtil.resolveScriptForOS(testDir + "/redirect");
82  
83      private final File printArgsScript = TestUtil.resolveScriptForOS(testDir + "/printargs");
84      // private final File acroRd32Script = TestUtil.resolveScriptForOS(testDir + "/acrord32");
85      private final File stdinSript = TestUtil.resolveScriptForOS(testDir + "/stdin");
86  
87      private final File environmentSript = TestUtil.resolveScriptForOS(testDir + "/environment");
88  //    private final File wrapperScript = TestUtil.resolveScriptForOS(testDir + "/wrapper");
89  
90      /**
91       * Start any processes in a loop to make sure that we do not leave any handles/resources open.
92       *
93       * @throws Exception the test failed
94       */
95      @Test
96      @Disabled
97      public void _testExecuteStability() throws Exception {
98  
99          // make a plain-vanilla test
100         for (int i = 0; i < 100; i++) {
101             final Map<String, String> env = new HashMap<>();
102             env.put("TEST_ENV_VAR", Integer.toString(i));
103             final CommandLine cl = new CommandLine(testScript);
104             final int exitValue = exec.execute(cl, env);
105             assertFalse(exec.isFailure(exitValue));
106             assertEquals("FOO." + i + ".", baos.toString().trim());
107             baos.reset();
108         }
109 
110         // now be nasty and use the watchdog to kill out sub-processes
111         for (int i = 0; i < 100; i++) {
112             final Map<String, String> env = new HashMap<>();
113             env.put("TEST_ENV_VAR", Integer.toString(i));
114             final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
115             final CommandLine cl = new CommandLine(foreverTestScript);
116             final ExecuteWatchdog watchdog = new ExecuteWatchdog(500);
117             exec.setWatchdog(watchdog);
118             exec.execute(cl, env, resultHandler);
119             resultHandler.waitFor(WAITFOR_TIMEOUT);
120             assertTrue(resultHandler.hasResult(), "ResultHandler received a result");
121             assertNotNull(resultHandler.getException());
122             baos.reset();
123         }
124     }
125 
126     private int getOccurrences(final String data, final char c) {
127 
128         int result = 0;
129 
130         for (int i = 0; i < data.length(); i++) {
131             if (data.charAt(i) == c) {
132                 result++;
133             }
134         }
135 
136         return result;
137     }
138 
139     private String readFile(final File file) throws Exception {
140         String text;
141         final StringBuilder contents = new StringBuilder();
142         try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
143             while ((text = reader.readLine()) != null) {
144                 contents.append(text).append(System.lineSeparator());
145             }
146         }
147         return contents.toString();
148     }
149 
150     @BeforeEach
151     public void setUp() throws Exception {
152 
153         // delete the marker file
154         this.foreverOutputFile.getParentFile().mkdirs();
155         if (this.foreverOutputFile.exists()) {
156             this.foreverOutputFile.delete();
157         }
158 
159         // prepare a ready to Executor
160         this.baos = new ByteArrayOutputStream();
161         this.exec.setStreamHandler(new PumpStreamHandler(baos, baos));
162     }
163 
164     @AfterEach
165     public void tearDown() throws Exception {
166         this.baos.close();
167         foreverOutputFile.delete();
168     }
169 
170     @Test
171     public void testAddEnvironmentVariableEmbeddedQuote() throws Exception {
172         final Map<String, String> myEnvVars = new HashMap<>(EnvironmentUtils.getProcEnvironment());
173         final String name = "NEW_VAR";
174         final String value = "NEW_\"_VAL";
175         myEnvVars.put(name, value);
176         exec.execute(new CommandLine(environmentSript), myEnvVars);
177         final String environment = baos.toString().trim();
178         assertTrue(environment.indexOf(name) >= 0, () -> "Expecting " + name + " in " + environment);
179         assertTrue(environment.indexOf(value) >= 0, () -> "Expecting " + value + " in " + environment);
180     }
181 
182     /**
183      * Call a script to dump the environment variables of the subprocess after adding a custom environment variable.
184      *
185      * @throws Exception the test failed
186      */
187     @Test
188     public void testAddEnvironmentVariables() throws Exception {
189         final Map<String, String> myEnvVars = new HashMap<>(EnvironmentUtils.getProcEnvironment());
190         myEnvVars.put("NEW_VAR", "NEW_VAL");
191         exec.execute(new CommandLine(environmentSript), myEnvVars);
192         final String environment = baos.toString().trim();
193         assertTrue(environment.indexOf("NEW_VAR") >= 0, () -> "Expecting NEW_VAR in " + environment);
194         assertTrue(environment.indexOf("NEW_VAL") >= 0, () -> "Expecting NEW_VAL in " + environment);
195     }
196 
197     /**
198      * Call a script to dump the environment variables of the subprocess.
199      *
200      * @throws Exception the test failed
201      */
202     @Test
203     public void testEnvironmentVariables() throws Exception {
204         exec.execute(new CommandLine(environmentSript));
205         final String environment = baos.toString().trim();
206         assertFalse(environment.isEmpty(), "Found no environment variables");
207         assertFalse(environment.indexOf("NEW_VAR") >= 0);
208     }
209 
210     /**
211      * The simplest possible test - start a script and check that the output was pumped into our {@code ByteArrayOutputStream}.
212      *
213      * @throws Exception the test failed
214      */
215     @Test
216     public void testExecute() throws Exception {
217         final CommandLine cl = new CommandLine(testScript);
218         final int exitValue = exec.execute(cl);
219         assertEquals("FOO..", baos.toString().trim());
220         assertFalse(exec.isFailure(exitValue));
221         assertEquals(new File("."), exec.getWorkingDirectory());
222     }
223 
224     /**
225      * Start a asynchronous process which returns an success exit value.
226      *
227      * @throws Exception the test failed
228      */
229     @Test
230     public void testExecuteAsync() throws Exception {
231         final CommandLine cl = new CommandLine(testScript);
232         final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
233         exec.execute(cl, resultHandler);
234         resultHandler.waitFor(2000);
235         assertTrue(resultHandler.hasResult());
236         assertNull(resultHandler.getException());
237         assertFalse(exec.isFailure(resultHandler.getExitValue()));
238         assertEquals("FOO..", baos.toString().trim());
239     }
240 
241     /**
242      * Try to start an non-existing application where the exception is caught/processed by the result handler.
243      */
244     @Test
245     public void testExecuteAsyncNonExistingApplication() throws Exception {
246         final CommandLine cl = new CommandLine(nonExistingTestScript);
247         final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
248         final DefaultExecutor executor = DefaultExecutor.builder().get();
249 
250         executor.execute(cl, resultHandler);
251         resultHandler.waitFor();
252 
253         assertTrue(executor.isFailure(resultHandler.getExitValue()));
254         assertNotNull(resultHandler.getException());
255     }
256 
257     /**
258      * Try to start an non-existing application where the exception is caught/processed by the result handler. The watchdog in notified to avoid waiting for the
259      * process infinitely.
260      *
261      * @see <a href="https://issues.apache.org/jira/browse/EXEC-71">EXEC-71</a>
262      */
263     @Test
264     public void testExecuteAsyncNonExistingApplicationWithWatchdog() throws Exception {
265         final CommandLine cl = new CommandLine(nonExistingTestScript);
266         final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler() {
267             @Override
268             public void onProcessFailed(final ExecuteException e) {
269                 System.out.println("Process did not stop gracefully, had exception '" + e.getMessage() + "' while executing process");
270                 super.onProcessFailed(e);
271             }
272         };
273         final DefaultExecutor executor = DefaultExecutor.builder().get();
274         executor.setWatchdog(new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT));
275 
276         executor.execute(cl, resultHandler);
277         resultHandler.waitFor();
278 
279         assertTrue(executor.isFailure(resultHandler.getExitValue()));
280         assertNotNull(resultHandler.getException());
281         assertFalse(executor.getWatchdog().isWatching());
282         assertFalse(executor.getWatchdog().killedProcess());
283         executor.getWatchdog().destroyProcess();
284     }
285 
286     /**
287      * Start a asynchronous process which returns an error exit value.
288      *
289      * @throws Exception the test failed
290      */
291     @Test
292     public void testExecuteAsyncWithError() throws Exception {
293         final CommandLine cl = new CommandLine(errorTestScript);
294         final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
295         exec.execute(cl, resultHandler);
296         resultHandler.waitFor(2000);
297         assertTrue(resultHandler.hasResult());
298         assertTrue(exec.isFailure(resultHandler.getExitValue()));
299         assertNotNull(resultHandler.getException());
300         assertEquals("FOO..", baos.toString().trim());
301     }
302 
303     /**
304      * Test the proper handling of ProcessDestroyer for an asynchronous process. Since we do not terminate the process it will be terminated in the
305      * ShutdownHookProcessDestroyer implementation.
306      *
307      * @throws Exception the test failed
308      */
309     @Test
310     public void testExecuteAsyncWithProcessDestroyer() throws Exception {
311 
312         final CommandLine cl = new CommandLine(foreverTestScript);
313         final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
314         final ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();
315         final ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
316 
317         assertNull(exec.getProcessDestroyer());
318         assertTrue(processDestroyer.isEmpty());
319         assertFalse(processDestroyer.isAddedAsShutdownHook());
320 
321         exec.setWatchdog(watchdog);
322         exec.setProcessDestroyer(processDestroyer);
323         exec.execute(cl, handler);
324 
325         // wait for script to start
326         Thread.sleep(2000);
327 
328         // our process destroyer should be initialized now
329         assertNotNull(exec.getProcessDestroyer(), "Process destroyer should exist");
330         assertEquals(1, processDestroyer.size(), "Process destroyer size should be 1");
331         assertTrue(processDestroyer.isAddedAsShutdownHook(), "Process destroyer should exist as shutdown hook");
332 
333         // terminate it and the process destroyer is detached
334         watchdog.destroyProcess();
335         assertTrue(watchdog.killedProcess());
336         handler.waitFor(WAITFOR_TIMEOUT);
337         assertTrue(handler.hasResult(), "ResultHandler received a result");
338         assertNotNull(handler.getException());
339         assertEquals(0, processDestroyer.size(), "Processor Destroyer size should be 0");
340         assertFalse(processDestroyer.isAddedAsShutdownHook(), "Process destroyer should not exist as shutdown hook");
341     }
342 
343     /**
344      * Start a asynchronous process and terminate it manually before the watchdog timeout occurs.
345      *
346      * @throws Exception the test failed
347      */
348     @Test
349     public void testExecuteAsyncWithTimelyUserTermination() throws Exception {
350         final CommandLine cl = new CommandLine(foreverTestScript);
351         final ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
352         exec.setWatchdog(watchdog);
353         final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
354         exec.execute(cl, handler);
355         // wait for script to run
356         Thread.sleep(2000);
357         assertTrue(watchdog.isWatching(), "Watchdog should watch the process");
358         // terminate it manually using the watchdog
359         watchdog.destroyProcess();
360         // wait until the result of the process execution is propagated
361         handler.waitFor(WAITFOR_TIMEOUT);
362         assertTrue(watchdog.killedProcess(), "Watchdog should have killed the process");
363         assertFalse(watchdog.isWatching(), "Watchdog is no longer watching the process");
364         assertTrue(handler.hasResult(), "ResultHandler received a result");
365         assertNotNull(handler.getException(), "ResultHandler received an exception as result");
366     }
367 
368     /**
369      * Start a asynchronous process and try to terminate it manually but the process was already terminated by the watchdog. This is basically a race condition
370      * between infrastructure and user code.
371      *
372      * @throws Exception the test failed
373      */
374     @Test
375     public void testExecuteAsyncWithTooLateUserTermination() throws Exception {
376         final CommandLine cl = new CommandLine(foreverTestScript);
377         final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
378         final ExecuteWatchdog watchdog = new ExecuteWatchdog(3000);
379         exec.setWatchdog(watchdog);
380         exec.execute(cl, handler);
381         // wait for script to be terminated by the watchdog
382         Thread.sleep(6000);
383         // try to terminate the already terminated process
384         watchdog.destroyProcess();
385         // wait until the result of the process execution is propagated
386         handler.waitFor(WAITFOR_TIMEOUT);
387         assertTrue(watchdog.killedProcess(), "Watchdog should have killed the process already");
388         assertFalse(watchdog.isWatching(), "Watchdog is no longer watching the process");
389         assertTrue(handler.hasResult(), "ResultHandler received a result");
390         assertNotNull(handler.getException(), "ResultHandler received an exception as result");
391     }
392 
393     /**
394      * Try to start an non-existing application which should result in an exception.
395      */
396     @Test
397     public void testExecuteNonExistingApplication() throws Exception {
398         final CommandLine cl = new CommandLine(nonExistingTestScript);
399         final DefaultExecutor executor = DefaultExecutor.builder().get();
400 
401         assertThrows(IOException.class, () -> executor.execute(cl));
402     }
403 
404     /**
405      * Try to start an non-existing application which should result in an exception.
406      */
407     @Test
408     public void testExecuteNonExistingApplicationWithWatchDog() throws Exception {
409         final CommandLine cl = new CommandLine(nonExistingTestScript);
410         final DefaultExecutor executor = DefaultExecutor.builder().get();
411         executor.setWatchdog(new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT));
412 
413         assertThrows(IOException.class, () -> executor.execute(cl));
414     }
415 
416     /**
417      * Start a script looping forever (asynchronously) and check if the ExecuteWatchdog kicks in killing the run away process. To make killing a process more
418      * testable the "forever" scripts write each second a '.' into "./target/forever.txt" (a marker file). After a test run we should have a few dots in there.
419      *
420      * @throws Exception the test failed
421      */
422     @Test
423     public void testExecuteWatchdogAsync() throws Exception {
424 
425         final long timeout = 10000;
426 
427         final CommandLine cl = new CommandLine(foreverTestScript);
428         final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
429         final DefaultExecutor executor = DefaultExecutor.builder().get();
430         executor.setWorkingDirectory(new File("."));
431         executor.setWatchdog(new ExecuteWatchdog(timeout));
432 
433         executor.execute(cl, handler);
434         handler.waitFor(WAITFOR_TIMEOUT);
435 
436         assertTrue(executor.getWatchdog().killedProcess(), "Killed process should be true");
437         assertTrue(handler.hasResult(), "ResultHandler received a result");
438         assertNotNull(handler.getException(), "ResultHandler received an exception as result");
439 
440         final int nrOfInvocations = getOccurrences(readFile(this.foreverOutputFile), '.');
441         assertTrue(nrOfInvocations > 5 && nrOfInvocations <= 11, () -> "Killing the process did not work : " + nrOfInvocations);
442     }
443 
444     /**
445      * Start a script looping forever (synchronously) and check if the ExecuteWatchdog kicks in killing the run away process. To make killing a process more
446      * testable the "forever" scripts write each second a '.' into "./target/forever.txt" (a marker file). After a test run we should have a few dots in there.
447      *
448      * @throws Exception the test failed
449      */
450     @Test
451     public void testExecuteWatchdogSync() throws Exception {
452 
453         if (OS.isFamilyOpenVms()) {
454             System.out.println("The test 'testExecuteWatchdogSync' currently hangs on the following OS : " + System.getProperty("os.name"));
455             return;
456         }
457 
458         final long timeout = 10000;
459 
460         final CommandLine cl = new CommandLine(foreverTestScript);
461         final DefaultExecutor executor = DefaultExecutor.builder().get();
462         executor.setWorkingDirectory(new File("."));
463         final ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
464         executor.setWatchdog(watchdog);
465 
466         try {
467             executor.execute(cl);
468         } catch (final ExecuteException e) {
469             Thread.sleep(timeout);
470             final int nrOfInvocations = getOccurrences(readFile(this.foreverOutputFile), '.');
471             assertTrue(executor.getWatchdog().killedProcess());
472             assertTrue(nrOfInvocations > 5 && nrOfInvocations <= 11, () -> "killing the subprocess did not work : " + nrOfInvocations);
473             return;
474         } catch (final Throwable t) {
475             fail(t.getMessage());
476         }
477 
478         assertTrue(executor.getWatchdog().killedProcess(), "Killed process should be true");
479         fail("Process did not create ExecuteException when killed");
480     }
481 
482     /**
483      * [EXEC-68] Synchronously starts a short script with a Watchdog attached with an extremely large timeout. Checks to see if the script terminated naturally
484      * or if it was killed by the Watchdog. Fail if killed by Watchdog.
485      *
486      * @throws Exception the test failed
487      */
488     @Test
489     public void testExecuteWatchdogVeryLongTimeout() throws Exception {
490         final long timeout = Long.MAX_VALUE;
491 
492         final CommandLine cl = new CommandLine(testScript);
493         final DefaultExecutor executor = DefaultExecutor.builder().get();
494         executor.setWorkingDirectory(new File("."));
495         final ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
496         executor.setWatchdog(watchdog);
497 
498         try {
499             executor.execute(cl);
500         } catch (final ExecuteException e) {
501             assertFalse(watchdog.killedProcess(), "Process should exit normally, not be killed by watchdog");
502             // If the Watchdog did not kill it, something else went wrong.
503             throw e;
504         }
505     }
506 
507     @Test
508     public void testExecuteWithArg() throws Exception {
509         final CommandLine cl = new CommandLine(testScript);
510         cl.addArgument("BAR");
511         final int exitValue = exec.execute(cl);
512 
513         assertEquals("FOO..BAR", baos.toString().trim());
514         assertFalse(exec.isFailure(exitValue));
515     }
516 
517     /**
518      * A generic test case to print the command line arguments to 'printargs' script to solve even more command line puzzles.
519      *
520      * @throws Exception the test failed
521      */
522     @Test
523     public void testExecuteWithComplexArguments() throws Exception {
524         final CommandLine cl = new CommandLine(printArgsScript);
525         cl.addArgument("gdal_translate");
526         cl.addArgument("HDF5:\"/home/kk/grass/data/4404.he5\"://HDFEOS/GRIDS/OMI_Column_Amount_O3/Data_Fields/ColumnAmountO3/home/kk/4.tif", false);
527         final DefaultExecutor executor = DefaultExecutor.builder().get();
528         final int exitValue = executor.execute(cl);
529         assertFalse(exec.isFailure(exitValue));
530     }
531 
532     /**
533      * Invoke the error script but define that the ERROR_STATUS is a good exit value and therefore no exception should be thrown.
534      *
535      * @throws Exception the test failed
536      */
537     @Test
538     public void testExecuteWithCustomExitValue1() throws Exception {
539         exec.setExitValue(ERROR_STATUS);
540         final CommandLine cl = new CommandLine(errorTestScript);
541         exec.execute(cl);
542     }
543 
544     /**
545      * Invoke the error script but define that SUCCESS_STATUS is a bad exit value and therefore an exception should be thrown.
546      *
547      * @throws Exception the test failed
548      */
549     @Test
550     public void testExecuteWithCustomExitValue2() throws Exception {
551         final CommandLine cl = new CommandLine(errorTestScript);
552         exec.setExitValue(SUCCESS_STATUS);
553         try {
554             exec.execute(cl);
555             fail("Must throw ExecuteException");
556         } catch (final ExecuteException e) {
557             assertTrue(exec.isFailure(e.getExitValue()));
558         }
559     }
560 
561     @Test
562     public void testExecuteWithError() throws Exception {
563         final CommandLine cl = new CommandLine(errorTestScript);
564 
565         try {
566             exec.execute(cl);
567             fail("Must throw ExecuteException");
568         } catch (final ExecuteException e) {
569             assertTrue(exec.isFailure(e.getExitValue()));
570         }
571     }
572 
573     /**
574      * Invoke the test using some fancy arguments.
575      *
576      * @throws Exception the test failed
577      */
578     @Test
579     public void testExecuteWithFancyArg() throws Exception {
580         final CommandLine cl = new CommandLine(testScript);
581         cl.addArgument("test $;`(0)[1]{2}");
582         final int exitValue = exec.execute(cl);
583         assertTrue(baos.toString().trim().indexOf("test $;`(0)[1]{2}") > 0);
584         assertFalse(exec.isFailure(exitValue));
585     }
586 
587     @Test
588     public void testExecuteWithInvalidWorkingDirectory() throws Exception {
589         final File workingDir = new File("/foo/bar");
590         final CommandLine cl = new CommandLine(testScript);
591         exec.setWorkingDirectory(workingDir);
592 
593         assertThrows(IOException.class, () -> exec.execute(cl));
594     }
595 
596     /**
597      * Start a process and connect it to no stream.
598      *
599      * @throws Exception the test failed
600      */
601     @Test
602     public void testExecuteWithNullOutErr() throws Exception {
603         final CommandLine cl = new CommandLine(testScript);
604         final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(null, null);
605         final DefaultExecutor executor = DefaultExecutor.builder().get();
606         executor.setStreamHandler(pumpStreamHandler);
607         final int exitValue = executor.execute(cl);
608         assertFalse(exec.isFailure(exitValue));
609     }
610 
611     /**
612      * Test the proper handling of ProcessDestroyer for an synchronous process.
613      *
614      * @throws Exception the test failed
615      */
616     @Test
617     public void testExecuteWithProcessDestroyer() throws Exception {
618 
619         final CommandLine cl = new CommandLine(testScript);
620         final ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();
621         exec.setProcessDestroyer(processDestroyer);
622 
623         assertTrue(processDestroyer.isEmpty());
624         assertFalse(processDestroyer.isAddedAsShutdownHook());
625 
626         final int exitValue = exec.execute(cl);
627 
628         assertEquals("FOO..", baos.toString().trim());
629         assertFalse(exec.isFailure(exitValue));
630         assertTrue(processDestroyer.isEmpty());
631         assertFalse(processDestroyer.isAddedAsShutdownHook());
632     }
633 
634     /**
635      * Start a process with redirected streams - stdin of the newly created process is connected to a FileInputStream whereas the "redirect" script reads all
636      * lines from stdin and prints them on stdout. Furthermore the script prints a status message on stderr.
637      *
638      * @throws Exception the test failed
639      */
640     @Test
641     public void testExecuteWithRedirectedStreams() throws Exception {
642         if (OS.isFamilyUnix()) {
643             final int exitValue;
644             try (FileInputStream fis = new FileInputStream("./NOTICE.txt")) {
645                 final CommandLine cl = new CommandLine(redirectScript);
646                 final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(baos, baos, fis);
647                 final DefaultExecutor executor = DefaultExecutor.builder().get();
648                 executor.setWorkingDirectory(new File("."));
649                 executor.setStreamHandler(pumpStreamHandler);
650                 exitValue = executor.execute(cl);
651             }
652             final String result = baos.toString().trim();
653             assertTrue(result.indexOf("Finished reading from stdin") > 0, result);
654             assertFalse(exec.isFailure(exitValue), () -> "exitValue=" + exitValue);
655         } else {
656             if (OS.isFamilyWindows()) {
657                 System.err.println("The code samples to do that in windows look like a joke ... :-( .., no way I'm doing that");
658             }
659             System.err.println("The test 'testExecuteWithRedirectedStreams' does not support the following OS : " + System.getProperty("os.name"));
660         }
661     }
662 
663     /**
664      * Start a process and connect out and err to a file.
665      *
666      * @throws Exception the test failed
667      */
668     @Test
669     public void testExecuteWithRedirectOutErr() throws Exception {
670         final Path outFile = Files.createTempFile("EXEC", ".test");
671         final CommandLine cl = new CommandLine(testScript);
672         try (OutputStream outAndErr = Files.newOutputStream(outFile)) {
673             final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(outAndErr);
674             final DefaultExecutor executor = DefaultExecutor.builder().get();
675             executor.setStreamHandler(pumpStreamHandler);
676             final int exitValue = executor.execute(cl);
677             assertFalse(exec.isFailure(exitValue));
678             assertTrue(Files.exists(outFile));
679         } finally {
680             Files.delete(outFile);
681         }
682     }
683 
684     /**
685      * Execute the test script and pass a environment containing 'TEST_ENV_VAR'.
686      */
687     @Test
688     public void testExecuteWithSingleEnvironmentVariable() throws Exception {
689         final Map<String, String> env = new HashMap<>();
690         env.put("TEST_ENV_VAR", "XYZ");
691 
692         final CommandLine cl = new CommandLine(testScript);
693 
694         final int exitValue = exec.execute(cl, env);
695 
696         assertEquals("FOO.XYZ.", baos.toString().trim());
697         assertFalse(exec.isFailure(exitValue));
698     }
699 
700     // ======================================================================
701     // === Long running tests
702     // ======================================================================
703 
704     /**
705      * Start a process and connect stdout and stderr.
706      *
707      * @throws Exception the test failed
708      */
709     @Test
710     public void testExecuteWithStdOutErr() throws Exception {
711         final CommandLine cl = new CommandLine(testScript);
712         final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(System.out, System.err);
713         final DefaultExecutor executor = DefaultExecutor.builder().get();
714         executor.setStreamHandler(pumpStreamHandler);
715         final int exitValue = executor.execute(cl);
716         assertFalse(exec.isFailure(exitValue));
717     }
718 
719     // ======================================================================
720     // === Helper methods
721     // ======================================================================
722 
723     @Test
724     public void testExecuteWithWorkingDirectory() throws Exception {
725         final File workingDir = new File("./target");
726         final CommandLine cl = new CommandLine(testScript);
727         exec.setWorkingDirectory(workingDir);
728         final int exitValue = exec.execute(cl);
729         assertEquals("FOO..", baos.toString().trim());
730         assertFalse(exec.isFailure(exitValue));
731         assertEquals(exec.getWorkingDirectory(), workingDir);
732     }
733 
734     /**
735      * The test script reads an argument from {@code stdin} and prints the result to stdout. To make things slightly more interesting we are using an
736      * asynchronous process.
737      *
738      * @throws Exception the test failed
739      */
740     @Test
741     public void testStdInHandling() throws Exception {
742         // newline not needed; causes problems for VMS
743         final ByteArrayInputStream bais = new ByteArrayInputStream("Foo".getBytes());
744         final CommandLine cl = new CommandLine(this.stdinSript);
745         final PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(this.baos, System.err, bais);
746         final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
747         final Executor executor = DefaultExecutor.builder().get();
748         executor.setStreamHandler(pumpStreamHandler);
749         executor.execute(cl, resultHandler);
750 
751         resultHandler.waitFor(WAITFOR_TIMEOUT);
752         assertTrue(resultHandler.hasResult(), "ResultHandler received a result");
753 
754         assertFalse(exec.isFailure(resultHandler.getExitValue()));
755         final String result = baos.toString();
756         assertTrue(result.indexOf("Hello Foo!") >= 0, "Result '" + result + "' should contain 'Hello Foo!'");
757     }
758 }