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.assertTrue;
21 import org.junit.Test;
22 import org.junit.runner.RunWith;
23 import org.junit.runners.JUnit4;
24 
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.util.IRunUtil.IRunnableResult;
27 
28 import java.io.BufferedWriter;
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.FileWriter;
32 import java.io.IOException;
33 import java.io.Writer;
34 
35 /** Longer running tests for {@link RunUtilFuncTest} */
36 @RunWith(JUnit4.class)
37 public class RunUtilFuncTest {
38 
39     private static final long VERY_SHORT_TIMEOUT_MS = 10L;
40     private static final long SHORT_TIMEOUT_MS = 500L;
41     private static final long LONG_TIMEOUT_MS = 5000L;
42 
43     private abstract class MyRunnable implements IRunUtil.IRunnableResult {
44         boolean mCanceled = false;
45 
46         @Override
cancel()47         public void cancel() {
48             mCanceled = true;
49         }
50     }
51 
52     /** Test timeout case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */
53     @Test
testRunTimed_timeout()54     public void testRunTimed_timeout() {
55         MyRunnable mockRunnable = new MyRunnable() {
56             @Override
57             public boolean run() {
58                 try {
59                     Thread.sleep(SHORT_TIMEOUT_MS * 5);
60                 } catch (InterruptedException e) {
61                     // ignore
62                 }
63                 return true;
64             }
65         };
66         assertEquals(CommandStatus.TIMED_OUT, RunUtil.getDefault().runTimed(SHORT_TIMEOUT_MS,
67                 mockRunnable, true));
68         assertTrue(mockRunnable.mCanceled);
69     }
70 
71     /**
72      * Test method for {@link RunUtil#runTimedRetry(long, long, int, IRunnableResult)}. Verify that
73      * multiple attempts are made.
74      */
75     @Test
testRunTimedRetry()76     public void testRunTimedRetry() {
77         final int maxAttempts = 5;
78         IRunUtil.IRunnableResult mockRunnable = new IRunUtil.IRunnableResult() {
79             int attempts = 0;
80             @Override
81             public boolean run() {
82                 attempts++;
83                 return attempts == maxAttempts;
84             }
85             @Override
86             public void cancel() {
87                 // ignore
88             }
89         };
90         final long startTime = System.currentTimeMillis();
91         assertTrue(RunUtil.getDefault().runTimedRetry(100, SHORT_TIMEOUT_MS, maxAttempts,
92                 mockRunnable));
93         final long actualTime = System.currentTimeMillis() - startTime;
94         // assert that time actually taken is at least, and no more than twice expected
95         final long expectedPollTime = SHORT_TIMEOUT_MS * (maxAttempts-1);
96         assertTrue(String.format("Expected poll time %d, got %d", expectedPollTime, actualTime),
97                 expectedPollTime <= actualTime && actualTime <= (2 * expectedPollTime));
98     }
99 
100     /**
101      * Test timeout case for {@link RunUtil#runTimedCmd(long, String...)} and ensure we consistently
102      * get the right stdout for a fast running command.
103      */
104     @Test
testRunTimedCmd_repeatedOutput()105     public void testRunTimedCmd_repeatedOutput() {
106         for (int i = 0; i < 1000; i++) {
107             CommandResult result =
108                     RunUtil.getDefault().runTimedCmd(LONG_TIMEOUT_MS, "echo", "hello");
109             assertTrue("Failed at iteration " + i,
110                     CommandStatus.SUCCESS.equals(result.getStatus()));
111             CLog.d(result.getStdout());
112             assertEquals("hello\n", result.getStdout());
113         }
114     }
115 
116     /**
117      * Test that that running a command with a 0 timeout results in no timeout being applied to it.
118      */
119     @Test
testRunTimedCmd_noTimeout()120     public void testRunTimedCmd_noTimeout() {
121         // When there is no timeout, max_poll interval will be 30sec so we need a test with more
122         // than 30sec
123         CommandResult result = RunUtil.getDefault().runTimedCmd(0L, "sleep", "35");
124         assertTrue(CommandStatus.SUCCESS.equals(result.getStatus()));
125         assertTrue(result.getStdout().isEmpty());
126     }
127 
128     /**
129      * Test case for {@link RunUtil#runTimedCmd(long, String...)} for a command that produces a
130      * large amount of output
131      *
132      * @throws IOException
133      */
134     @Test
testRunTimedCmd_largeOutput()135     public void testRunTimedCmd_largeOutput() throws IOException {
136         // 1M  chars
137         int dataSize = 1000000;
138         File f = FileUtil.createTempFile("foo", ".txt");
139         Writer s = null;
140         try {
141             s = new BufferedWriter(new FileWriter(f));
142             for (int i=0; i < dataSize; i++) {
143                 s.write('a');
144             }
145             s.close();
146 
147             // FIXME: this test case is not ideal, as it will only work on platforms that support
148             // cat command.
149             CommandResult result =
150                     RunUtil.getDefault()
151                             .runTimedCmd(3 * LONG_TIMEOUT_MS, "cat", f.getAbsolutePath());
152             assertEquals(
153                     String.format(
154                             "We expected SUCCESS but got %s, with stdout: '%s'\nstderr: %s",
155                             result.getStatus(), result.getStdout(), result.getStderr()),
156                     CommandStatus.SUCCESS,
157                     result.getStatus());
158             assertTrue(result.getStdout().length() == dataSize);
159         } finally {
160             f.delete();
161             StreamUtil.close(s);
162         }
163     }
164 
165     /** Test case for {@link RunUtil#unsetEnvVariable(String key)} */
166     @Test
testUnsetEnvVariable()167     public void testUnsetEnvVariable() {
168         RunUtil runUtil = new RunUtil();
169         runUtil.setEnvVariable("bar", "foo");
170         // FIXME: this test case is not ideal, as it will only work on platforms that support
171         // printenv
172         CommandResult result =
173                 runUtil.runTimedCmdRetry(SHORT_TIMEOUT_MS, SHORT_TIMEOUT_MS, 3, "printenv", "bar");
174         assertEquals(
175                 String.format(
176                         "We expected SUCCESS but got %s, with stdout: '%s'\nstderr: %s",
177                         result.getStatus(), result.getStdout(), result.getStderr()),
178                 CommandStatus.SUCCESS,
179                 result.getStatus());
180         assertEquals("foo", result.getStdout().trim());
181 
182         // remove env variable
183         runUtil.unsetEnvVariable("bar");
184         // printenv with non-exist variable will fail
185         result = runUtil.runTimedCmd(SHORT_TIMEOUT_MS, "printenv", "bar");
186         assertEquals(CommandStatus.FAILED, result.getStatus());
187         assertEquals("", result.getStdout().trim());
188     }
189 
190     /**
191      * Test that {@link RunUtil#runTimedCmd(long, String[])} returns timeout when the command is too
192      * short and also clean up all its thread.
193      */
194     @Test
testRunTimedCmd_timeout()195     public void testRunTimedCmd_timeout() throws InterruptedException {
196         RunUtil runUtil = new RunUtil();
197         String[] command = {"sleep", "10000"};
198         CommandResult result = runUtil.runTimedCmd(VERY_SHORT_TIMEOUT_MS, command);
199         assertEquals(
200                 String.format(
201                         "We expected TIMED_OUT but got %s, with stdout: '%s'\nstderr: %s",
202                         result.getStatus(), result.getStdout(), result.getStderr()),
203                 CommandStatus.TIMED_OUT,
204                 result.getStatus());
205         assertEquals("", result.getStdout());
206         assertEquals("", result.getStderr());
207         // We give it some times to clean up the process
208         Thread.sleep(5000);
209         Thread[] list = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
210         Thread.currentThread().getThreadGroup().enumerate(list);
211         // Ensure the list of Threads does not contain the RunnableNotifier or InheritIO threads.
212         for (Thread t : list) {
213             assertFalse(
214                     String.format("We found a thread: %s", t.getName()),
215                     t.getName().contains(RunUtil.RUNNABLE_NOTIFIER_NAME));
216             assertFalse(
217                     String.format("We found a thread: %s", t.getName()),
218                     t.getName().contains(RunUtil.INHERITIO_PREFIX));
219         }
220     }
221 
222     /** Test running a command with redirecting input from a file. */
223     @Test
testRunTimedCmd_WithInputRedirect()224     public void testRunTimedCmd_WithInputRedirect() throws IOException {
225         File inputRedirect = FileUtil.createTempFile("input_redirect", ".txt");
226 
227         try {
228             FileUtil.writeToFile("TEST_INPUT", inputRedirect);
229 
230             CommandResult result =
231                     RunUtil.getDefault()
232                             .runTimedCmdWithInputRedirect(SHORT_TIMEOUT_MS, inputRedirect, "cat");
233             assertTrue(CommandStatus.SUCCESS.equals(result.getStatus()));
234             assertEquals("TEST_INPUT", result.getStdout());
235         } finally {
236             FileUtil.deleteFile(inputRedirect);
237         }
238     }
239 
240     /** Test running a command with redirecting output to a file. */
241     @Test
testRunTimedCmd_WithOutputRedirect()242     public void testRunTimedCmd_WithOutputRedirect() throws IOException {
243         File outputRedirect = FileUtil.createTempFile("output_redirect", ".txt");
244         FileOutputStream outputStream = new FileOutputStream(outputRedirect);
245         try {
246             CommandResult result =
247                     RunUtil.getDefault()
248                             .runTimedCmd(
249                                     SHORT_TIMEOUT_MS,
250                                     outputStream,
251                                     /* stderr= */ null,
252                                     "echo",
253                                     "TEST_OUTPUT");
254             assertTrue(CommandStatus.SUCCESS.equals(result.getStatus()));
255             assertEquals("TEST_OUTPUT\n", FileUtil.readStringFromFile(outputRedirect));
256         } finally {
257             FileUtil.deleteFile(outputRedirect);
258             outputStream.close();
259         }
260     }
261 }
262