• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.util;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 import static org.mockito.Mockito.doNothing;
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.doThrow;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import com.android.tradefed.command.CommandInterrupter;
31 import com.android.tradefed.result.error.InfraErrorIdentifier;
32 import com.android.tradefed.util.IRunUtil.EnvPriority;
33 import com.android.tradefed.util.IRunUtil.IRunnableResult;
34 import com.android.tradefed.util.RunUtil.RunnableResult;
35 
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 import org.junit.runners.JUnit4;
41 import org.mockito.Mockito;
42 
43 import java.io.ByteArrayInputStream;
44 import java.io.File;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.util.ArrayList;
50 import java.util.concurrent.TimeUnit;
51 
52 /** Unit tests for {@link RunUtil} */
53 @RunWith(JUnit4.class)
54 public class RunUtilTest {
55 
56     private RunUtil mRunUtil;
57     private RunnableResult mMockRunnableResult;
58     private long mSleepTime = 0L;
59     private File mWorkingDir;
60     private static final long VERY_SHORT_TIMEOUT_MS = 10L;
61     private static final long SHORT_TIMEOUT_MS = 200L;
62     private static final long LONG_TIMEOUT_MS = 1000L;
63     // Timeout to ensure that IO depend tests have enough time to finish. They should not use the
64     // full duration in most cases.
65     private static final long VERY_LONG_TIMEOUT_MS = 5000L;
66 
67     @Before
setUp()68     public void setUp() throws Exception {
69         mRunUtil = new RunUtil(new CommandInterrupter());
70         mRunUtil.setPollingInterval(SHORT_TIMEOUT_MS);
71         mMockRunnableResult = null;
72         mWorkingDir = FileUtil.createTempDir("working_dir_");
73     }
74 
75     @After
tearDown()76     public void tearDown() {
77         // clear interrupted status
78         Thread.interrupted();
79         FileUtil.recursiveDelete(mWorkingDir);
80     }
81 
82     /** Test class on {@link RunUtil} in order to avoid creating a real process. */
83     class SpyRunUtil extends RunUtil {
84         private boolean mShouldThrow = false;
85 
SpyRunUtil(boolean shouldThrow)86         public SpyRunUtil(boolean shouldThrow) {
87             mShouldThrow = shouldThrow;
88         }
89 
90         @Override
createRunnableResult( OutputStream stdout, OutputStream stderr, ProcessBuilder processBuilder)91         RunnableResult createRunnableResult(
92                 OutputStream stdout, OutputStream stderr, ProcessBuilder processBuilder) {
93             RunnableResult real = super.createRunnableResult(stdout, stderr, processBuilder);
94             mMockRunnableResult = Mockito.spy(real);
95             try {
96                 if (mShouldThrow) {
97                     // Test if the binary does not exists, startProcess throws directly in this case
98                     doThrow(
99                                     new RuntimeException(
100                                             String.format(
101                                                     "Cannot run program \"%s\": error=2,"
102                                                             + "No such file or directory",
103                                                     processBuilder.command().get(0))))
104                             .when(mMockRunnableResult)
105                             .startProcess();
106                 } else {
107                     doReturn(new FakeProcess()).when(mMockRunnableResult).startProcess();
108                 }
109             } catch (Exception e) {
110                 throw new RuntimeException(e);
111             }
112             return mMockRunnableResult;
113         }
114     }
115 
116     /** Test class on {@link RunUtil} in order to monitor the real process. */
117     class MonitoredRunUtil extends RunUtil {
118         public ProcessBuilder processBuilder;
119 
MonitoredRunUtil(boolean inheritEnvVars)120         public MonitoredRunUtil(boolean inheritEnvVars) {
121             super(inheritEnvVars);
122         }
123 
124         @Override
createRunnableResult( OutputStream stdout, OutputStream stderr, ProcessBuilder processBuilder)125         RunnableResult createRunnableResult(
126                 OutputStream stdout, OutputStream stderr, ProcessBuilder processBuilder) {
127             this.processBuilder = processBuilder;
128             return super.createRunnableResult(stdout, stderr, processBuilder);
129         }
130     }
131 
132     /** Test success case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */
133     @Test
testRunTimed()134     public void testRunTimed() throws Exception {
135         IRunUtil.IRunnableResult mockRunnable = mock(IRunUtil.IRunnableResult.class);
136         when(mockRunnable.getCommand()).thenReturn(new ArrayList<>());
137         when(mockRunnable.run()).thenReturn(Boolean.TRUE);
138 
139         assertEquals(
140                 CommandStatus.SUCCESS, mRunUtil.runTimed(SHORT_TIMEOUT_MS, mockRunnable, true));
141     }
142 
143     /** Test failure case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */
144     @Test
testRunTimed_failed()145     public void testRunTimed_failed() throws Exception {
146         IRunUtil.IRunnableResult mockRunnable = mock(IRunUtil.IRunnableResult.class);
147         when(mockRunnable.getCommand()).thenReturn(new ArrayList<>());
148         when(mockRunnable.run()).thenReturn(Boolean.FALSE);
149 
150         assertEquals(CommandStatus.FAILED, mRunUtil.runTimed(SHORT_TIMEOUT_MS, mockRunnable, true));
151     }
152 
153     /** Test exception case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */
154     @Test
testRunTimed_exception()155     public void testRunTimed_exception() throws Exception {
156         IRunUtil.IRunnableResult mockRunnable = mock(IRunUtil.IRunnableResult.class);
157         when(mockRunnable.getCommand()).thenReturn(new ArrayList<>());
158         when(mockRunnable.run()).thenThrow(new RuntimeException());
159         when(mockRunnable.getResult()).thenReturn(null);
160 
161         assertEquals(
162                 CommandStatus.EXCEPTION,
163                 mRunUtil.runTimed(VERY_LONG_TIMEOUT_MS, mockRunnable, true));
164     }
165 
166     /** Test interrupted case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */
167     @Test
testRunTimed_interrupted()168     public void testRunTimed_interrupted() {
169         IRunnableResult runnable = Mockito.mock(IRunnableResult.class);
170         CommandInterrupter interrupter = Mockito.mock(CommandInterrupter.class);
171         RunUtil runUtil = new RunUtil(interrupter);
172 
173         // interrupted during execution
174         doNothing().doThrow(RunInterruptedException.class).when(interrupter).checkInterrupted();
175 
176         try {
177             runUtil.runTimed(VERY_SHORT_TIMEOUT_MS, runnable, true);
178             fail("RunInterruptedException was expected, but not thrown.");
179         } catch (RunInterruptedException e) {
180             // Execution was cancelled due to interruption
181             Mockito.verify(runnable, Mockito.atLeast(1)).cancel();
182         }
183     }
184 
185     /** Test that {@link RunUtil#runTimedCmd(long, String[])} fails when given a garbage command. */
186     @Test
testRunTimedCmd_failed()187     public void testRunTimedCmd_failed() {
188         RunUtil spyUtil = new SpyRunUtil(true);
189         CommandResult result = spyUtil.runTimedCmd(VERY_LONG_TIMEOUT_MS, "blahggggwarggg");
190         assertEquals(CommandStatus.EXCEPTION, result.getStatus());
191         assertEquals("", result.getStdout());
192         assertTrue(result.getStderr().contains("Cannot run program \"blahggggwarggg\""));
193     }
194 
195     /**
196      * Test {@link RunUtil#runTimedCmd(long, String[])} exits with status SUCCESS since the output
197      * monitor observed output on streams through the command time until finished.
198      */
199     @Test
testRunTimed_output_monitor()200     public void testRunTimed_output_monitor() {
201         // Long-running operation with changing output stream.
202         String[] command = {"/bin/bash", "-c", "for i in {1..5}; do echo hello; sleep 1; done"};
203 
204         // Should succeed and return sooner regardless of timeout.
205         CommandResult result =
206                 mRunUtil.runTimedCmdWithOutputMonitor(VERY_LONG_TIMEOUT_MS * 5, 1200, command);
207         assertEquals(CommandStatus.SUCCESS, result.getStatus());
208     }
209 
210     /**
211      * Test {@link RunUtil#runTimedCmd(long, String[])} exits with status FAILED due to the output
212      * monitor not observing any output on the streams.
213      */
214     @Test
testRunTimed_output_monitor_failed()215     public void testRunTimed_output_monitor_failed() {
216         // Long-running operation with no output sent to stream.
217         String[] command = {"sleep", String.valueOf(VERY_LONG_TIMEOUT_MS * 5)};
218 
219         // Should fail and return sooner regardless of timeout.
220         CommandResult result =
221                 mRunUtil.runTimedCmdWithOutputMonitor(VERY_LONG_TIMEOUT_MS * 5, 1200, command);
222         assertEquals(CommandStatus.FAILED, result.getStatus());
223     }
224 
225     /**
226      * Test {@link RunUtil#runTimedCmd(long, String[])} exits with status TIMED_OUT even if the
227      * output monitor is observing new output on the output streams since the timeout is short.
228      */
229     @Test
testRunTimed_output_monitor_timeout()230     public void testRunTimed_output_monitor_timeout() {
231         // Long-running operation with no output.
232         String[] command = {"sleep", String.valueOf(VERY_LONG_TIMEOUT_MS * 5)};
233 
234         // Should run out of time and timeout.
235         CommandResult result =
236                 mRunUtil.runTimedCmdWithOutputMonitor(
237                         SHORT_TIMEOUT_MS, SHORT_TIMEOUT_MS * 2, command);
238         assertEquals(CommandStatus.TIMED_OUT, result.getStatus());
239     }
240 
241     /**
242      * Test that {@link RunUtil#runTimedCmdWithInput(long, String, File, File, String...)} properly
243      * backfill errors.
244      */
245     @Test
testRunTimedCmdWithInput_failed()246     public void testRunTimedCmdWithInput_failed() throws Exception {
247         RunUtil spyUtil = new SpyRunUtil(true);
248         File stdout = FileUtil.createTempFile("stdout-test", "txt");
249         File stderr = FileUtil.createTempFile("stderr-test", "txt");
250         try {
251             CommandResult result =
252                     spyUtil.runTimedCmdWithInput(
253                             VERY_LONG_TIMEOUT_MS, null, stdout, stderr, "blahggggwarggg");
254             assertEquals(CommandStatus.EXCEPTION, result.getStatus());
255             assertEquals("", result.getStdout());
256             assertTrue(result.getStderr().contains("Cannot run program \"blahggggwarggg\""));
257             // Error was backfilled in stderr file
258             assertTrue(
259                     FileUtil.readStringFromFile(stderr)
260                             .contains("Cannot run program \"blahggggwarggg\""));
261         } finally {
262             FileUtil.deleteFile(stdout);
263             FileUtil.deleteFile(stderr);
264         }
265     }
266 
267     /**
268      * Test that {@link RunUtil#runTimedCmd(long, String[])} is returning timed out state when the
269      * command does not return in time.
270      */
271     @Test
testRunTimedCmd_timeout()272     public void testRunTimedCmd_timeout() {
273         String[] command = {"sleep", "10000"};
274         CommandResult result = mRunUtil.runTimedCmd(VERY_SHORT_TIMEOUT_MS, command);
275         assertEquals(CommandStatus.TIMED_OUT, result.getStatus());
276         assertEquals("", result.getStdout());
277         assertEquals("", result.getStderr());
278     }
279 
280     /**
281      * Verify that calling {@link RunUtil#setWorkingDir(File)} is not allowed on default instance.
282      */
283     @Test
testSetWorkingDir_default()284     public void testSetWorkingDir_default() {
285         try {
286             RunUtil.getDefault().setWorkingDir(new File("foo"));
287             fail("could set working dir on RunUtil.getDefault()");
288         } catch (RuntimeException e) {
289             // expected
290         }
291     }
292 
293     /**
294      * Verify that calling {@link RunUtil#setEnvVariable(String, String)} is not allowed on default
295      * instance.
296      */
297     @Test
testSetEnvVariable_default()298     public void testSetEnvVariable_default() {
299         try {
300             RunUtil.getDefault().setEnvVariable("foo", "bar");
301             fail("could set env var on RunUtil.getDefault()");
302         } catch (RuntimeException e) {
303             // expected
304         }
305     }
306 
307     /**
308      * Verify that calling {@link RunUtil#unsetEnvVariable(String)} is not allowed on default
309      * instance.
310      */
311     @Test
testUnsetEnvVariable_default()312     public void testUnsetEnvVariable_default() {
313         try {
314             RunUtil.getDefault().unsetEnvVariable("foo");
315             fail("could unset env var on RunUtil.getDefault()");
316         } catch (Exception e) {
317             // expected
318         }
319     }
320 
321     /**
322      * Test that {@link RunUtil#runEscalatingTimedRetry(long, long, long, long, IRunnableResult)}
323      * fails when operation continually fails, and that the maxTime variable is respected.
324      */
325     @Test
testRunEscalatingTimedRetry_timeout()326     public void testRunEscalatingTimedRetry_timeout() throws Exception {
327         // create a RunUtil fixture with methods mocked out for
328         // fast execution
329         RunUtil runUtil =
330                 new RunUtil() {
331                     @Override
332                     public void sleep(long time) {
333                         mSleepTime += time;
334                     }
335 
336                     @Override
337                     long getCurrentTime() {
338                         return mSleepTime;
339                     }
340 
341                     @Override
342                     public CommandStatus runTimed(
343                             long timeout, IRunUtil.IRunnableResult runnable, boolean logErrors) {
344                         try {
345                             // override parent with simple version that doesn't create a thread
346                             return runnable.run() ? CommandStatus.SUCCESS : CommandStatus.FAILED;
347                         } catch (Exception e) {
348                             return CommandStatus.EXCEPTION;
349                         }
350                     }
351                 };
352 
353         IRunUtil.IRunnableResult mockRunnable = mock(IRunUtil.IRunnableResult.class);
354         // expect a call 4 times, at sleep time 0, 1, 4 and 10 ms
355         when(mockRunnable.run()).thenReturn(Boolean.FALSE);
356 
357         long maxTime = 12;
358         assertFalse(runUtil.runEscalatingTimedRetry(1, 1, 512, maxTime, mockRunnable));
359         assertEquals(maxTime, mSleepTime);
360         verify(mockRunnable, Mockito.atLeast(3)).run();
361     }
362 
363     /** Test a success case for {@link RunUtil#interrupt}. */
364     @Test
testInterrupt()365     public void testInterrupt() {
366         final String message = "it is alright now";
367         mRunUtil.allowInterrupt(true);
368         try {
369             mRunUtil.interrupt(
370                     Thread.currentThread(), message, InfraErrorIdentifier.TRADEFED_SHUTTING_DOWN);
371             fail("RunInterruptedException was expected, but not thrown.");
372         } catch (final RunInterruptedException e) {
373             assertEquals(message, e.getMessage());
374         }
375     }
376 
377     /**
378      * Test whether a {@link RunUtil#interrupt} call is respected when called while interrupts are
379      * not allowed.
380      */
381     @Test
testInterrupt_delayed()382     public void testInterrupt_delayed() {
383         final String message = "it is alright now";
384         try {
385             mRunUtil.allowInterrupt(false);
386             mRunUtil.interrupt(
387                     Thread.currentThread(), message, InfraErrorIdentifier.TRADEFED_SHUTTING_DOWN);
388             mRunUtil.sleep(1);
389             mRunUtil.allowInterrupt(true);
390             mRunUtil.sleep(1);
391             fail("RunInterruptedException was expected, but not thrown.");
392         } catch (final RunInterruptedException e) {
393             assertEquals(message, e.getMessage());
394         }
395     }
396 
397     /** Test whether a {@link RunUtil#interrupt} call is respected when called multiple times. */
398     @Test
testInterrupt_multiple()399     public void testInterrupt_multiple() {
400         final String message1 = "it is alright now";
401         final String message2 = "without a fight";
402         final String message3 = "rock this town";
403         mRunUtil.allowInterrupt(true);
404         try {
405             mRunUtil.interrupt(
406                     Thread.currentThread(), message1, InfraErrorIdentifier.TRADEFED_SHUTTING_DOWN);
407             mRunUtil.interrupt(
408                     Thread.currentThread(), message2, InfraErrorIdentifier.TRADEFED_SHUTTING_DOWN);
409             mRunUtil.interrupt(
410                     Thread.currentThread(), message3, InfraErrorIdentifier.TRADEFED_SHUTTING_DOWN);
411             fail("RunInterruptedException was expected, but not thrown.");
412         } catch (final RunInterruptedException e) {
413             assertEquals(message1, e.getMessage());
414         }
415     }
416 
417     /**
418      * Test whether a {@link RunUtil#runTimedCmd(long, OutputStream, OutputStream, String[])} call
419      * correctly redirect the output to files.
420      */
421     @Test
testRuntimedCmd_withFileOutputStream()422     public void testRuntimedCmd_withFileOutputStream() {
423         File stdout = null;
424         File stderr = null;
425         OutputStream stdoutStream = null;
426         OutputStream stderrStream = null;
427         try {
428             stdout = FileUtil.createTempFile("stdout_subprocess_", ".txt");
429             stdoutStream = new FileOutputStream(stdout);
430             stderr = FileUtil.createTempFile("stderr_subprocess_", ".txt");
431             stderrStream = new FileOutputStream(stderr);
432         } catch (IOException e) {
433             fail("Failed to create output files: " + e.getMessage());
434         }
435         RunUtil spyUtil = new SpyRunUtil(false);
436         String[] command = {"unused", "cmd"};
437         CommandResult result =
438                 spyUtil.runTimedCmd(LONG_TIMEOUT_MS, stdoutStream, stderrStream, command);
439         assertEquals(CommandStatus.SUCCESS, result.getStatus());
440         assertEquals(
441                 result.getStdout(), "redirected to " + stdoutStream.getClass().getSimpleName());
442         assertEquals(
443                 result.getStderr(), "redirected to " + stderrStream.getClass().getSimpleName());
444         assertTrue(stdout.exists());
445         assertTrue(stderr.exists());
446         try {
447             assertEquals("TEST STDOUT\n", FileUtil.readStringFromFile(stdout));
448             assertEquals("TEST STDERR\n", FileUtil.readStringFromFile(stderr));
449         } catch (IOException e) {
450             fail(e.getMessage());
451         } finally {
452             FileUtil.deleteFile(stdout);
453             FileUtil.deleteFile(stderr);
454         }
455     }
456 
457     /**
458      * Test whether a {@link RunUtil#runTimedCmd(long, OutputStream, OutputStream, String[])} call
459      * correctly redirect the output to stdout because files are null. Replace the process by a fake
460      * one to avoid waiting on real system IO.
461      */
462     @Test
testRuntimedCmd_regularOutput_fileNull()463     public void testRuntimedCmd_regularOutput_fileNull() {
464         String[] command = {"echo", "TEST STDOUT"};
465         CommandResult result = mRunUtil.runTimedCmd(VERY_LONG_TIMEOUT_MS, null, null, command);
466         assertEquals(CommandStatus.SUCCESS, result.getStatus());
467         assertEquals("TEST STDOUT\n", result.getStdout());
468         assertEquals("", result.getStderr());
469     }
470 
471     /**
472      * Test whether a {@link RunUtil#runTimedCmd(long, OutputStream, OutputStream, String[])}
473      * redirect to the file even if they become non-writable afterward.
474      */
475     @Test
testRuntimedCmd_notWritable()476     public void testRuntimedCmd_notWritable() {
477         File stdout = null;
478         File stderr = null;
479         OutputStream stdoutStream = null;
480         OutputStream stderrStream = null;
481         try {
482             stdout = FileUtil.createTempFile("stdout_subprocess_", ".txt");
483             stdoutStream = new FileOutputStream(stdout);
484             stdout.setWritable(false);
485             stderr = FileUtil.createTempFile("stderr_subprocess_", ".txt");
486             stderrStream = new FileOutputStream(stderr);
487             stderr.setWritable(false);
488         } catch (IOException e) {
489             fail("Failed to create output files: " + e.getMessage());
490         }
491         RunUtil spyUtil = new SpyRunUtil(false);
492         String[] command = {"unused", "cmd"};
493         CommandResult result =
494                 spyUtil.runTimedCmd(LONG_TIMEOUT_MS, stdoutStream, stderrStream, command);
495         try {
496             assertEquals(CommandStatus.SUCCESS, result.getStatus());
497             assertEquals(
498                     result.getStdout(), "redirected to " + stdoutStream.getClass().getSimpleName());
499             assertEquals(
500                     result.getStderr(), "redirected to " + stderrStream.getClass().getSimpleName());
501             assertTrue(stdout.exists());
502             assertTrue(stderr.exists());
503             assertEquals("TEST STDOUT\n", FileUtil.readStringFromFile(stdout));
504             assertEquals("TEST STDERR\n", FileUtil.readStringFromFile(stderr));
505         } catch (IOException e) {
506             fail(e.getMessage());
507         } finally {
508             stdout.setWritable(true);
509             stderr.setWritable(true);
510             FileUtil.deleteFile(stdout);
511             FileUtil.deleteFile(stderr);
512         }
513     }
514 
515     /**
516      * Test whether a {@link RunUtil#setInterruptibleInFuture} change properly the interruptible
517      * state.
518      */
519     @Test
testSetInterruptibleInFuture()520     public void testSetInterruptibleInFuture() {
521         CommandInterrupter interrupter = Mockito.mock(CommandInterrupter.class);
522         RunUtil runUtil = new RunUtil(interrupter);
523 
524         Thread thread = new Thread();
525         runUtil.setInterruptibleInFuture(thread, 123L);
526 
527         // RunUtil delegates to CommandInterrupter#allowInterruptAsync
528         Mockito.verify(interrupter)
529                 .allowInterruptAsync(
530                         Mockito.eq(thread), Mockito.eq(123L), Mockito.eq(TimeUnit.MILLISECONDS));
531         Mockito.verifyNoMoreInteractions(interrupter);
532     }
533 
534     /** Test {@link RunUtil#setEnvVariablePriority(EnvPriority)} properly prioritize unset. */
535     @Test
testUnsetPriority()536     public void testUnsetPriority() {
537         final String ENV_NAME = "TF_GLO";
538         RunUtil testRunUtil = new RunUtil();
539         testRunUtil.setEnvVariablePriority(EnvPriority.UNSET);
540         testRunUtil.setEnvVariable(ENV_NAME, "initvalue");
541         testRunUtil.unsetEnvVariable(ENV_NAME);
542         CommandResult result =
543                 testRunUtil.runTimedCmd(
544                         VERY_LONG_TIMEOUT_MS, "/bin/bash", "-c", "echo $" + ENV_NAME);
545         assertNotNull(result.getStdout());
546         // Variable should be unset, some echo return empty line break.
547         assertEquals("\n", result.getStdout());
548     }
549 
550     /** Test {@link RunUtil#setEnvVariablePriority(EnvPriority)} properly prioritize set. */
551     @Test
testUnsetPriority_inverted()552     public void testUnsetPriority_inverted() {
553         final String ENV_NAME = "TF_GLO";
554         final String expected = "initvalue";
555         RunUtil testRunUtil = new RunUtil();
556         testRunUtil.setEnvVariablePriority(EnvPriority.SET);
557         testRunUtil.setEnvVariable(ENV_NAME, expected);
558         testRunUtil.unsetEnvVariable(ENV_NAME);
559         CommandResult result =
560                 testRunUtil.runTimedCmd(
561                         VERY_LONG_TIMEOUT_MS, "/bin/bash", "-c", "echo $" + ENV_NAME);
562         assertNotNull(result.getStdout());
563         // Variable should be set and returned.
564         assertEquals(expected + "\n", result.getStdout());
565     }
566 
567     @Test
testGotExitCodeFromCommand()568     public void testGotExitCodeFromCommand() {
569         RunUtil testRunUtil = new RunUtil();
570         CommandResult result =
571                 testRunUtil.runTimedCmd(VERY_LONG_TIMEOUT_MS, "/bin/bash", "-c", "exit 2");
572         assertEquals("", result.getStdout());
573         assertEquals("", result.getStderr());
574         assertEquals(2, (int) result.getExitCode());
575     }
576 
577     @Test
testSetRedirectStderrToStdout()578     public void testSetRedirectStderrToStdout() {
579         RunUtil testRunUtil = new RunUtil();
580         testRunUtil.setRedirectStderrToStdout(true);
581         CommandResult result =
582                 testRunUtil.runTimedCmd(
583                         VERY_LONG_TIMEOUT_MS,
584                         "/bin/bash",
585                         "-c",
586                         "echo 'TEST STDOUT'; echo 'TEST STDERR' >&2");
587         assertEquals("TEST STDOUT\nTEST STDERR\n", result.getStdout());
588         assertEquals("", result.getStderr());
589     }
590 
591     /**
592      * Implementation of {@link Process} to simulate a success of a command that echos to both
593      * stdout and stderr without actually calling the underlying system.
594      */
595     private class FakeProcess extends Process {
596 
597         @Override
waitFor()598         public int waitFor() throws InterruptedException {
599             return 0;
600         }
601 
602         @Override
getOutputStream()603         public OutputStream getOutputStream() {
604             return null;
605         }
606 
607         @Override
getInputStream()608         public InputStream getInputStream() {
609             return new ByteArrayInputStream("TEST STDOUT\n".getBytes());
610         }
611 
612         @Override
getErrorStream()613         public InputStream getErrorStream() {
614             return new ByteArrayInputStream("TEST STDERR\n".getBytes());
615         }
616 
617         @Override
exitValue()618         public int exitValue() {
619             return 0;
620         }
621 
622         @Override
destroy()623         public void destroy() {
624             // ignore
625         }
626     }
627 }
628