blob: a86eab74319b561b920164db5f0005cb10559e37 [file] [log] [blame]
/*
* 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.testtype;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize;
import com.android.ddmlib.testrunner.ITestRunListener.TestFailure;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.testtype.TestTimeoutListener.ITimeoutCallback;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.IRunUtil.IRunnableResult;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* A Test that runs an instrumentation test package on given device.
*/
public class InstrumentationTest extends AbstractRemoteTest implements IDeviceTest, IResumableTest,
ITimeoutCallback {
private static final String LOG_TAG = "InstrumentationTest";
/** max number of attempts to collect list of tests in package */
private static final int COLLECT_TESTS_ATTEMPTS = 3;
/** time in ms between collect list of tests attempts */
private static final int COLLECT_TESTS_POLL_INTERVAL = 5 * 1000;
/** max time in ms to allow for single collect list of tests attempt */
private static final int COLLECT_TESTS_OP_TIMEOUT = 2 * 60 * 1000;
static final String TIMED_OUT_MSG = "timed out: test did not complete in %d ms";
static final String DELAY_MSEC_ARG = "delay_msec";
@Option(name = "package", shortName = 'p',
description="The manifest package name of the Android test application to run")
private String mPackageName = null;
@Option(name = "runner",
description="The instrumentation test runner class name to use")
private String mRunnerName = "android.test.InstrumentationTestRunner";
@Option(name = "class", shortName = 'c',
description="The test class name to run")
private String mTestClassName = null;
@Option(name = "method", shortName = 'm',
description="The test method name to run.")
private String mTestMethodName = null;
@Option(name = "timeout",
description="Aborts the test run if any test takes longer than the specified number of "
+ "milliseconds ")
private long mTestTimeout = 10 * 60 * 1000; // default to 10 minutes
@Option(name = "size",
description="Restrict test to a specific test size")
private String mTestSize = null;
@Option(name = "rerun",
description="Rerun non-executed tests individually if test run fails to complete")
private boolean mIsRerunMode = true;
@Option(name = "log-delay",
description="Delay in msec between each test when collecting test information")
private int mTestDelay = 10;
@Option(name = "install-file",
description="Optional file path to apk file that contains the tests.")
private File mInstallFile = null;
private ITestDevice mDevice = null;
private IRemoteAndroidTestRunner mRunner;
private Collection<ITestRunListener> mListeners;
private Collection<TestIdentifier> mRemainingTests = new ArrayList<TestIdentifier>();
/**
* {@inheritDoc}
*/
public void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* Set the Android manifest package to run.
*/
public void setPackageName(String packageName) {
mPackageName = packageName;
}
/**
* Optionally, set the Android instrumentation runner to use.
*/
public void setRunnerName(String runnerName) {
mRunnerName = runnerName;
}
/**
* Optionally, set the test class name to run.
*/
public void setClassName(String testClassName) {
mTestClassName = testClassName;
}
/**
* Optionally, set the test method to run.
*/
public void setMethodName(String testMethodName) {
mTestMethodName = testMethodName;
}
/**
* Optionally, set the test size to run.
*/
public void setTestSize(String size) {
mTestSize = size;
}
/**
* Get the Android manifest package to run.
*/
public String getPackageName() {
return mPackageName;
}
/**
* Get the class name to run.
*/
String getClassName() {
return mTestClassName;
}
/**
* Get the test method to run.
*/
String getMethodName() {
return mTestMethodName;
}
/**
* Get the test size to run. Returns <code>null</code> if no size has been set.
*/
String getTestSize() {
return mTestSize;
}
/**
* Optionally, set the maximum time for each test.
*/
public void setTestTimeout(long timeout) {
mTestTimeout = timeout;
}
/**
* Return <code>true</code> if rerun mode is on.
*/
boolean isRerunMode() {
return mIsRerunMode;
}
/**
* Optionally, set the rerun mode.
*/
public void setRerunMode(boolean rerun) {
mIsRerunMode = rerun;
}
/**
* Get the test timeout in ms.
*/
long getTestTimeout() {
return mTestTimeout;
}
/**
* Get the delay in ms between each test when collecting test info.
*/
long getTestDelay() {
return mTestDelay;
}
/**
* Set the optional file to install that contains the tests.
*
* @param installFile the installable {@link File}
*/
public void setInstallFile(File installFile) {
mInstallFile = installFile;
}
/**
* Get the {@link RunUtil} instance to use.
* <p/>
* Exposed for unit testing.
*/
IRunUtil getRunUtil() {
return RunUtil.getInstance();
}
/**
* {@inheritDoc}
*/
public ITestDevice getDevice() {
return mDevice;
}
/**
* @return the {@link IRemoteAndroidTestRunner} to use.
*/
IRemoteAndroidTestRunner createRemoteAndroidTestRunner(String packageName, String runnerName,
IDevice device) {
return new RemoteAndroidTestRunner(packageName, runnerName, device);
}
/**
* {@inheritDoc}
*/
public void run(final List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
if (mPackageName == null) {
throw new IllegalArgumentException("package name has not been set");
}
if (mDevice == null) {
throw new IllegalArgumentException("Device has not been set");
}
mRunner = createRemoteAndroidTestRunner(mPackageName, mRunnerName,
mDevice.getIDevice());
if (mTestClassName != null) {
if (mTestMethodName != null) {
mRunner.setMethodName(mTestClassName, mTestMethodName);
} else {
mRunner.setClassName(mTestClassName);
}
}
if (mTestSize != null) {
mRunner.setTestSize(TestSize.getTestSize(mTestSize));
}
if (mInstallFile != null) {
mDevice.installPackage(mInstallFile, true);
doTestRun(listeners);
mDevice.uninstallPackage(mPackageName);
} else {
doTestRun(listeners);
}
}
/**
* Execute test run.
*
* @param listener the test result listener
* @returns true if tests were run. false if test run was skipped
* @throws DeviceNotAvailableException if device stops communicating
*/
private boolean doTestRun(final List<ITestInvocationListener> listeners)
throws DeviceNotAvailableException {
Collection<TestIdentifier> expectedTests = collectTestsToRun(mRunner);
mListeners = new ArrayList<ITestRunListener>();
mListeners.addAll(listeners);
if (mTestTimeout >= 0) {
mListeners.add(new TestTimeoutListener(mTestTimeout, this));
}
if (expectedTests != null) {
if (expectedTests.size() != 0) {
runWithRerun(listeners, expectedTests);
} else {
Log.i(LOG_TAG, String.format("No tests expected for %s, skipping", mPackageName));
return false;
}
} else {
mDevice.runInstrumentationTests(mRunner, mListeners);
}
return true;
}
/**
* Execute the test run, but re-run incomplete tests individually if run fails to complete.
*
* @param listeners list of {@link ITestInvocationListener}
* @param expectedTests the full set of expected tests in this run.
*/
private void runWithRerun(final List<ITestInvocationListener> listeners,
Collection<TestIdentifier> expectedTests) throws DeviceNotAvailableException {
CollectingTestListener testTracker = new CollectingTestListener();
mListeners.add(testTracker);
mRemainingTests = expectedTests;
try {
mDevice.runInstrumentationTests(mRunner, mListeners);
} finally {
calculateRemainingTests(mRemainingTests, testTracker);
}
rerunTests(listeners);
}
/**
* Rerun any <var>mRemainingTests</var> one by one
*
* @param listeners
* @throws DeviceNotAvailableException
*/
private void rerunTests(final List<ITestInvocationListener> listeners)
throws DeviceNotAvailableException {
if (mRemainingTests.size() > 0) {
InstrumentationListTest testRerunner = new InstrumentationListTest(mPackageName,
mRunnerName, mRemainingTests);
testRerunner.setDevice(getDevice());
testRerunner.setTestTimeout(getTestTimeout());
CollectingTestListener testTracker = new CollectingTestListener();
List<ITestInvocationListener> listenersCopy = new ArrayList<ITestInvocationListener>(
listeners);
listenersCopy.add(testTracker);
try {
testRerunner.run(listenersCopy);
} finally {
calculateRemainingTests(mRemainingTests, testTracker);
}
}
}
/**
* Remove the set of tests collected by testTracker from the set of expectedTests
*
* @param expectedTests
* @param testTracker
*/
private void calculateRemainingTests(Collection<TestIdentifier> expectedTests,
CollectingTestListener testTracker) {
expectedTests.removeAll(testTracker.getCurrentRunResults().getTests());
}
/**
* {@inheritDoc}
*/
public void testTimeout(TestIdentifier test) {
mRunner.cancel();
final String msg = String.format(TIMED_OUT_MSG, mTestTimeout);
for (ITestRunListener listener : mListeners) {
listener.testFailed(TestFailure.ERROR, test, msg);
listener.testRunFailed(msg);
}
}
/**
* {@inheritDoc}
*/
@Override
public void resume(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
rerunTests(listeners);
}
/**
* {@inheritDoc}
*/
@Override
public void resume(ITestInvocationListener listener) throws DeviceNotAvailableException {
List<ITestInvocationListener> list = new ArrayList<ITestInvocationListener>(1);
list.add(listener);
resume(list);
}
/**
* Collect the list of tests that should be executed by this test run.
* <p/>
* This will be done by executing the test run in 'logOnly' mode, and recording the list of
* tests.
*
* @param runner the {@link IRemoteAndroidTestRunner} to use to run the tests.
* @return a {@link Collection} of {@link TestIdentifier}s that represent all tests to be
* executed by this run
* @throws DeviceNotAvailableException
*/
private Collection<TestIdentifier> collectTestsToRun(final IRemoteAndroidTestRunner runner)
throws DeviceNotAvailableException {
if (isRerunMode()) {
Log.d(LOG_TAG, String.format("Collecting test info for %s on device %s",
mPackageName, mDevice.getSerialNumber()));
runner.setLogOnly(true);
// the collecting test command can fail for large volumes of test bug 1750602. insert a
// small delay between each test to prevent this
if (mTestDelay > 0) {
runner.addInstrumentationArg(DELAY_MSEC_ARG, Integer.toString(mTestDelay));
}
// try to collect tests multiple times, in case device is temporarily not available
// on first attempt
CollectingTestsRunnable collectRunnable = new CollectingTestsRunnable(mDevice,
mRunner);
boolean result = getRunUtil().runTimedRetry(COLLECT_TESTS_OP_TIMEOUT,
COLLECT_TESTS_POLL_INTERVAL, COLLECT_TESTS_ATTEMPTS, collectRunnable);
runner.setLogOnly(false);
mRunner.removeInstrumentationArg(DELAY_MSEC_ARG);
if (result) {
return collectRunnable.getTests();
} else if (collectRunnable.getException() != null) {
throw collectRunnable.getException();
} else {
Log.w(LOG_TAG, String.format("Failed to collect tests to run for %s on device %s",
mPackageName, mDevice.getSerialNumber()));
}
}
return null;
}
/**
* Collects list of tests to be executed by this test run.
* Wrapped as a {@link IRunnableResult} so this command can be re-attempted.
*/
private static class CollectingTestsRunnable implements IRunnableResult {
private final IRemoteAndroidTestRunner mRunner;
private final ITestDevice mDevice;
private Collection<TestIdentifier> mTests;
private DeviceNotAvailableException mException;
public CollectingTestsRunnable(ITestDevice device, IRemoteAndroidTestRunner runner) {
mRunner = runner;
mDevice = device;
mTests = null;
mException = null;
}
public boolean run() {
CollectingTestListener listener = new CollectingTestListener();
Collection<ITestRunListener> listeners = new ArrayList<ITestRunListener>(1);
listeners.add(listener);
try {
mDevice.runInstrumentationTests(mRunner, listeners);
TestRunResult runResults = listener.getCurrentRunResults();
mTests = runResults.getTests();
return !runResults.isRunFailure() && runResults.isRunComplete();
} catch (DeviceNotAvailableException e) {
// TODO: should throw this immediately if it occurs, rather than continuing to
// retry
mException = e;
}
return false;
}
/**
* Gets the collected tests. Must be called after {@link run}.
*/
public Collection<TestIdentifier> getTests() {
return new ArrayList<TestIdentifier>(mTests);
}
public DeviceNotAvailableException getException() {
return mException;
}
/**
* {@inheritDoc}
*/
public void cancel() {
mRunner.cancel();
}
}
}