| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tradefed.util; |
| |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.doThrow; |
| |
| import com.android.tradefed.command.CommandInterrupter; |
| import com.android.tradefed.util.IRunUtil.EnvPriority; |
| import com.android.tradefed.util.IRunUtil.IRunnableResult; |
| import com.android.tradefed.util.RunUtil.RunnableResult; |
| |
| import junit.framework.TestCase; |
| |
| import org.easymock.EasyMock; |
| import org.mockito.Mockito; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| |
| /** Unit tests for {@link RunUtil} */ |
| public class RunUtilTest extends TestCase { |
| |
| private RunUtil mRunUtil; |
| private RunnableResult mMockRunnableResult; |
| private long mSleepTime = 0L; |
| private boolean success = false; |
| private static final long VERY_SHORT_TIMEOUT_MS = 10L; |
| private static final long SHORT_TIMEOUT_MS = 200L; |
| private static final long LONG_TIMEOUT_MS = 1000L; |
| // Timeout to ensure that IO depend tests have enough time to finish. They should not use the |
| // full duration in most cases. |
| private static final long VERY_LONG_TIMEOUT_MS = 5000L; |
| |
| @Override |
| public void setUp() throws Exception { |
| mRunUtil = new RunUtil(new CommandInterrupter()); |
| mMockRunnableResult = null; |
| } |
| |
| @Override |
| public void tearDown() { |
| // clear interrupted status |
| Thread.interrupted(); |
| } |
| |
| /** Test class on {@link RunUtil} in order to avoid creating a real process. */ |
| class SpyRunUtil extends RunUtil { |
| private boolean mShouldThrow = false; |
| |
| public SpyRunUtil(boolean shouldThrow) { |
| mShouldThrow = shouldThrow; |
| } |
| |
| @Override |
| RunnableResult createRunnableResult( |
| OutputStream stdout, OutputStream stderr, String... command) { |
| RunnableResult real = super.createRunnableResult(stdout, stderr, command); |
| mMockRunnableResult = (RunnableResult) Mockito.spy(real); |
| try { |
| if (mShouldThrow) { |
| // Test if the binary does not exists, startProcess throws directly in this case |
| doThrow( |
| new RuntimeException( |
| "Cannot run program \"\": error=2," |
| + "No such file or directory")) |
| .when(mMockRunnableResult) |
| .startProcess(); |
| } else { |
| doReturn(new FakeProcess()).when(mMockRunnableResult).startProcess(); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| return mMockRunnableResult; |
| } |
| } |
| |
| /** |
| * Test success case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. |
| */ |
| public void testRunTimed() throws Exception { |
| IRunUtil.IRunnableResult mockRunnable = EasyMock.createStrictMock( |
| IRunUtil.IRunnableResult.class); |
| EasyMock.expect(mockRunnable.run()).andReturn(Boolean.TRUE); |
| mockRunnable.cancel(); // always ensure execution is cancelled |
| EasyMock.replay(mockRunnable); |
| assertEquals(CommandStatus.SUCCESS, |
| mRunUtil.runTimed(SHORT_TIMEOUT_MS, mockRunnable, true)); |
| } |
| |
| /** Test failure case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */ |
| public void testRunTimed_failed() throws Exception { |
| IRunUtil.IRunnableResult mockRunnable = |
| EasyMock.createStrictMock(IRunUtil.IRunnableResult.class); |
| EasyMock.expect(mockRunnable.run()).andReturn(Boolean.FALSE); |
| mockRunnable.cancel(); // always ensure execution is cancelled |
| EasyMock.replay(mockRunnable); |
| assertEquals(CommandStatus.FAILED, |
| mRunUtil.runTimed(SHORT_TIMEOUT_MS, mockRunnable, true)); |
| } |
| |
| /** Test exception case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */ |
| public void testRunTimed_exception() throws Exception { |
| IRunUtil.IRunnableResult mockRunnable = |
| EasyMock.createStrictMock(IRunUtil.IRunnableResult.class); |
| EasyMock.expect(mockRunnable.run()).andThrow(new RuntimeException()); |
| mockRunnable.cancel(); // cancel due to exception |
| mockRunnable.cancel(); // always ensure execution is cancelled |
| EasyMock.replay(mockRunnable); |
| assertEquals(CommandStatus.EXCEPTION, |
| mRunUtil.runTimed(SHORT_TIMEOUT_MS, mockRunnable, true)); |
| } |
| |
| /** Test interrupted case for {@link RunUtil#runTimed(long, IRunnableResult, boolean)}. */ |
| public void testRunTimed_interrupted() { |
| IRunnableResult runnable = Mockito.mock(IRunnableResult.class); |
| CommandInterrupter interrupter = Mockito.mock(CommandInterrupter.class); |
| RunUtil runUtil = new RunUtil(interrupter); |
| |
| // interrupted during execution |
| doNothing().doThrow(RunInterruptedException.class).when(interrupter).checkInterrupted(); |
| |
| try { |
| runUtil.runTimed(VERY_SHORT_TIMEOUT_MS, runnable, true); |
| fail("RunInterruptedException was expected, but not thrown."); |
| } catch (RunInterruptedException e) { |
| // execution was cancelled due to interruption |
| Mockito.verify(runnable, Mockito.times(1)).cancel(); |
| } |
| } |
| |
| /** Test that {@link RunUtil#runTimedCmd(long, String[])} fails when given a garbage command. */ |
| public void testRunTimedCmd_failed() { |
| RunUtil spyUtil = new SpyRunUtil(true); |
| CommandResult result = spyUtil.runTimedCmd(1000, "blahggggwarggg"); |
| assertEquals(CommandStatus.EXCEPTION, result.getStatus()); |
| assertEquals("", result.getStdout()); |
| assertEquals("", result.getStderr()); |
| } |
| |
| /** |
| * Test that {@link RunUtil#runTimedCmd(long, String[])} is returning timed out state when the |
| * command does not return in time. |
| */ |
| public void testRunTimedCmd_timeout() { |
| String[] command = {"sleep", "10000"}; |
| CommandResult result = mRunUtil.runTimedCmd(VERY_SHORT_TIMEOUT_MS, command); |
| assertEquals(CommandStatus.TIMED_OUT, result.getStatus()); |
| assertEquals("", result.getStdout()); |
| assertEquals("", result.getStderr()); |
| } |
| |
| /** |
| * Verify that calling {@link RunUtil#setWorkingDir(File)} is not allowed on default instance. |
| */ |
| public void testSetWorkingDir_default() { |
| try { |
| RunUtil.getDefault().setWorkingDir(new File("foo")); |
| fail("could set working dir on RunUtil.getDefault()"); |
| } catch (RuntimeException e) { |
| // expected |
| } |
| } |
| |
| /** |
| * Verify that calling {@link RunUtil#setEnvVariable(String, String)} is not allowed on default |
| * instance. |
| */ |
| public void testSetEnvVariable_default() { |
| try { |
| RunUtil.getDefault().setEnvVariable("foo", "bar"); |
| fail("could set env var on RunUtil.getDefault()"); |
| } catch (RuntimeException e) { |
| // expected |
| } |
| } |
| |
| /** |
| * Verify that calling {@link RunUtil#unsetEnvVariable(String)} is not allowed on default |
| * instance. |
| */ |
| public void testUnsetEnvVariable_default() { |
| try { |
| RunUtil.getDefault().unsetEnvVariable("foo"); |
| fail("could unset env var on RunUtil.getDefault()"); |
| } catch (Exception e) { |
| // expected |
| } |
| } |
| |
| /** |
| * Test that {@link RunUtil#runEscalatingTimedRetry(long, long, long, long, IRunnableResult)} |
| * fails when operation continually fails, and that the maxTime variable is respected. |
| */ |
| public void testRunEscalatingTimedRetry_timeout() throws Exception { |
| // create a RunUtil fixture with methods mocked out for |
| // fast execution |
| |
| RunUtil runUtil = new RunUtil() { |
| @Override |
| public void sleep(long time) { |
| mSleepTime += time; |
| } |
| |
| @Override |
| long getCurrentTime() { |
| return mSleepTime; |
| } |
| |
| @Override |
| public CommandStatus runTimed(long timeout, IRunUtil.IRunnableResult runnable, |
| boolean logErrors) { |
| try { |
| // override parent with simple version that doesn't create a thread |
| return runnable.run() ? CommandStatus.SUCCESS : CommandStatus.FAILED; |
| } catch (Exception e) { |
| return CommandStatus.EXCEPTION; |
| } |
| } |
| }; |
| |
| IRunUtil.IRunnableResult mockRunnable = EasyMock.createStrictMock( |
| IRunUtil.IRunnableResult.class); |
| // expect a call 4 times, at sleep time 0, 1, 4 and 10 ms |
| EasyMock.expect(mockRunnable.run()).andReturn(Boolean.FALSE).times(4); |
| EasyMock.replay(mockRunnable); |
| long maxTime = 10; |
| assertFalse(runUtil.runEscalatingTimedRetry(1, 1, 512, maxTime, mockRunnable)); |
| assertEquals(maxTime, mSleepTime); |
| EasyMock.verify(mockRunnable); |
| } |
| |
| /** |
| * Test a success case for {@link RunUtil#interrupt}. |
| */ |
| public void testInterrupt() { |
| final String message = "it is alright now"; |
| mRunUtil.allowInterrupt(true); |
| try{ |
| mRunUtil.interrupt(Thread.currentThread(), message); |
| fail("RunInterruptedException was expected, but not thrown."); |
| } catch (final RunInterruptedException e) { |
| assertEquals(message, e.getMessage()); |
| } |
| } |
| |
| /** |
| * Test whether a {@link RunUtil#interrupt} call is respected when called while interrupts are |
| * not allowed. |
| */ |
| public void testInterrupt_delayed() { |
| final String message = "it is alright now"; |
| try{ |
| mRunUtil.allowInterrupt(false); |
| mRunUtil.interrupt(Thread.currentThread(), message); |
| mRunUtil.sleep(1); |
| mRunUtil.allowInterrupt(true); |
| mRunUtil.sleep(1); |
| fail("RunInterruptedException was expected, but not thrown."); |
| } catch (final RunInterruptedException e) { |
| assertEquals(message, e.getMessage()); |
| } |
| } |
| |
| /** |
| * Test whether a {@link RunUtil#interrupt} call is respected when called multiple times. |
| */ |
| public void testInterrupt_multiple() { |
| final String message1 = "it is alright now"; |
| final String message2 = "without a fight"; |
| final String message3 = "rock this town"; |
| mRunUtil.allowInterrupt(true); |
| try { |
| mRunUtil.interrupt(Thread.currentThread(), message1); |
| mRunUtil.interrupt(Thread.currentThread(), message2); |
| mRunUtil.interrupt(Thread.currentThread(), message3); |
| fail("RunInterruptedException was expected, but not thrown."); |
| } catch (final RunInterruptedException e) { |
| assertEquals(message1, e.getMessage()); |
| } |
| } |
| |
| /** |
| * Test whether a {@link RunUtil#runTimedCmd(long, OutputStream, OutputStream, String[])} |
| * call correctly redirect the output to files. |
| */ |
| public void testRuntimedCmd_withFileOutputStream() { |
| File stdout = null; |
| File stderr = null; |
| OutputStream stdoutStream = null; |
| OutputStream stderrStream = null; |
| try { |
| stdout = FileUtil.createTempFile("stdout_subprocess_", ".txt"); |
| stdoutStream = new FileOutputStream(stdout); |
| stderr = FileUtil.createTempFile("stderr_subprocess_", ".txt"); |
| stderrStream = new FileOutputStream(stderr); |
| } catch (IOException e) { |
| fail("Failed to create output files: " + e.getMessage()); |
| } |
| RunUtil spyUtil = new SpyRunUtil(false); |
| String[] command = {"unused", "cmd"}; |
| CommandResult result = |
| spyUtil.runTimedCmd(LONG_TIMEOUT_MS, stdoutStream, stderrStream, command); |
| assertEquals(CommandStatus.SUCCESS, result.getStatus()); |
| assertEquals( |
| result.getStdout(), "redirected to " + stdoutStream.getClass().getSimpleName()); |
| assertEquals( |
| result.getStderr(), "redirected to " + stderrStream.getClass().getSimpleName()); |
| assertTrue(stdout.exists()); |
| assertTrue(stderr.exists()); |
| try { |
| assertEquals("TEST STDOUT\n", FileUtil.readStringFromFile(stdout)); |
| assertEquals("TEST STDERR\n", FileUtil.readStringFromFile(stderr)); |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| } finally { |
| FileUtil.deleteFile(stdout); |
| FileUtil.deleteFile(stderr); |
| } |
| } |
| |
| /** |
| * Test whether a {@link RunUtil#runTimedCmd(long, OutputStream, OutputStream, String[])} call |
| * correctly redirect the output to stdout because files are null. Replace the process by a fake |
| * one to avoid waiting on real system IO. |
| */ |
| public void testRuntimedCmd_regularOutput_fileNull() { |
| RunUtil spyUtil = new SpyRunUtil(false); |
| String[] command = {"unused", "cmd"}; |
| CommandResult result = spyUtil.runTimedCmd(LONG_TIMEOUT_MS, null, null, command); |
| assertEquals(CommandStatus.SUCCESS, result.getStatus()); |
| assertEquals(result.getStdout(), "TEST STDOUT\n"); |
| assertEquals(result.getStderr(), "TEST STDERR\n"); |
| } |
| |
| /** |
| * Test whether a {@link RunUtil#runTimedCmd(long, OutputStream, OutputStream, String[])} |
| * redirect to the file even if they become non-writable afterward. |
| */ |
| public void testRuntimedCmd_notWritable() { |
| File stdout = null; |
| File stderr = null; |
| OutputStream stdoutStream = null; |
| OutputStream stderrStream = null; |
| try { |
| stdout = FileUtil.createTempFile("stdout_subprocess_", ".txt"); |
| stdoutStream = new FileOutputStream(stdout); |
| stdout.setWritable(false); |
| stderr = FileUtil.createTempFile("stderr_subprocess_", ".txt"); |
| stderrStream = new FileOutputStream(stderr); |
| stderr.setWritable(false); |
| } catch (IOException e) { |
| fail("Failed to create output files: " + e.getMessage()); |
| } |
| RunUtil spyUtil = new SpyRunUtil(false); |
| String[] command = {"unused", "cmd"}; |
| CommandResult result = |
| spyUtil.runTimedCmd(SHORT_TIMEOUT_MS, stdoutStream, stderrStream, command); |
| assertEquals(CommandStatus.SUCCESS, result.getStatus()); |
| assertEquals( |
| result.getStdout(), "redirected to " + stdoutStream.getClass().getSimpleName()); |
| assertEquals( |
| result.getStderr(), "redirected to " + stderrStream.getClass().getSimpleName()); |
| assertTrue(stdout.exists()); |
| assertTrue(stderr.exists()); |
| try { |
| assertEquals(CommandStatus.SUCCESS, result.getStatus()); |
| assertEquals( |
| result.getStdout(), "redirected to " + stdoutStream.getClass().getSimpleName()); |
| assertEquals( |
| result.getStderr(), "redirected to " + stderrStream.getClass().getSimpleName()); |
| assertTrue(stdout.exists()); |
| assertTrue(stderr.exists()); |
| assertEquals("TEST STDOUT\n", FileUtil.readStringFromFile(stdout)); |
| assertEquals("TEST STDERR\n", FileUtil.readStringFromFile(stderr)); |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| } finally { |
| stdout.setWritable(true); |
| stderr.setWritable(true); |
| FileUtil.deleteFile(stdout); |
| FileUtil.deleteFile(stderr); |
| } |
| } |
| |
| /** |
| * Test whether a {@link RunUtil#setInterruptibleInFuture} change properly the interruptible |
| * state. |
| */ |
| public void testSetInterruptibleInFuture() { |
| final Thread test = |
| new Thread( |
| new Runnable() { |
| @Override |
| public void run() { |
| mRunUtil.allowInterrupt(false); |
| assertFalse(mRunUtil.isInterruptAllowed()); |
| mRunUtil.setInterruptibleInFuture(Thread.currentThread(), 10); |
| try { |
| mRunUtil.sleep(25); |
| mRunUtil.sleep(25); |
| fail(); |
| } catch (RunInterruptedException rie) { |
| assertEquals("TEST", rie.getMessage()); |
| } |
| success = mRunUtil.isInterruptAllowed(); |
| } |
| }); |
| mRunUtil.interrupt(test, "TEST"); |
| test.start(); |
| try { |
| test.join(); |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| assertTrue(success); |
| } |
| |
| /** Test whether a {@link RunUtil#setInterruptibleInFuture} has not change the state yet. */ |
| public void testSetInterruptibleInFuture_beforeTimeout() { |
| mRunUtil.allowInterrupt(false); |
| assertFalse(mRunUtil.isInterruptAllowed()); |
| |
| mRunUtil.setInterruptibleInFuture(Thread.currentThread(), SHORT_TIMEOUT_MS); |
| mRunUtil.sleep(50); |
| // Should still be false |
| assertFalse(mRunUtil.isInterruptAllowed()); |
| mRunUtil.sleep(SHORT_TIMEOUT_MS); |
| assertTrue(mRunUtil.isInterruptAllowed()); |
| } |
| |
| /** Test {@link RunUtil#setEnvVariablePriority(EnvPriority)} properly prioritize unset. */ |
| public void testUnsetPriority() { |
| final String ENV_NAME = "TF_GLO"; |
| RunUtil testRunUtil = new RunUtil(); |
| testRunUtil.setEnvVariablePriority(EnvPriority.UNSET); |
| testRunUtil.setEnvVariable(ENV_NAME, "initvalue"); |
| testRunUtil.unsetEnvVariable(ENV_NAME); |
| CommandResult result = |
| testRunUtil.runTimedCmd( |
| VERY_LONG_TIMEOUT_MS, "/bin/bash", "-c", "echo $" + ENV_NAME); |
| assertNotNull(result.getStdout()); |
| // Variable should be unset, some echo return empty line break. |
| assertEquals("\n", result.getStdout()); |
| } |
| |
| /** |
| * Test {@link RunUtil#setEnvVariablePriority(EnvPriority)} properly prioritize set. |
| */ |
| public void testUnsetPriority_inverted() { |
| final String ENV_NAME = "TF_GLO"; |
| final String expected = "initvalue"; |
| RunUtil testRunUtil = new RunUtil(); |
| testRunUtil.setEnvVariablePriority(EnvPriority.SET); |
| testRunUtil.setEnvVariable(ENV_NAME, expected); |
| testRunUtil.unsetEnvVariable(ENV_NAME); |
| CommandResult result = |
| testRunUtil.runTimedCmd(LONG_TIMEOUT_MS, "/bin/bash", "-c", "echo $" + ENV_NAME); |
| assertNotNull(result.getStdout()); |
| // Variable should be set and returned. |
| assertEquals(expected + "\n", result.getStdout()); |
| } |
| |
| public void testGotExitCodeFromCommand() { |
| RunUtil testRunUtil = new RunUtil(); |
| CommandResult result = |
| testRunUtil.runTimedCmd(VERY_LONG_TIMEOUT_MS, "/bin/bash", "-c", "exit 2"); |
| assertEquals("", result.getStdout()); |
| assertEquals("", result.getStderr()); |
| assertEquals(2, (int) result.getExitCode()); |
| } |
| |
| public void testSetRedirectStderrToStdout() { |
| RunUtil testRunUtil = new RunUtil(); |
| testRunUtil.setRedirectStderrToStdout(true); |
| CommandResult result = |
| testRunUtil.runTimedCmd( |
| VERY_LONG_TIMEOUT_MS, |
| "/bin/bash", |
| "-c", |
| "echo 'TEST STDOUT'; echo 'TEST STDERR' >&2"); |
| assertEquals("TEST STDOUT\nTEST STDERR\n", result.getStdout()); |
| assertEquals("", result.getStderr()); |
| } |
| |
| /** |
| * Implementation of {@link Process} to simulate a success of a command that echos to both |
| * stdout and stderr without actually calling the underlying system. |
| */ |
| private class FakeProcess extends Process { |
| |
| @Override |
| public int waitFor() throws InterruptedException { |
| return 0; |
| } |
| |
| @Override |
| public OutputStream getOutputStream() { |
| return null; |
| } |
| |
| @Override |
| public InputStream getInputStream() { |
| return new ByteArrayInputStream("TEST STDOUT\n".getBytes()); |
| } |
| |
| @Override |
| public InputStream getErrorStream() { |
| return new ByteArrayInputStream("TEST STDERR\n".getBytes()); |
| } |
| |
| @Override |
| public int exitValue() { |
| return 0; |
| } |
| |
| @Override |
| public void destroy() { |
| // ignore |
| } |
| } |
| } |