blob: ae38299275d8274058caa9ee29cca7b98ef9a7eb [file] [log] [blame]
/*
* Copyright (C) 2012 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.FileListingService;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.StubTestInvocationListener;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class UiAutomatorTest implements IRemoteTest, IDeviceTest {
public enum LoggingOption {
AFTER_TEST,
AFTER_FAILURE,
OFF,
}
public enum TestFailureAction {
BUGREPORT,
SCREENSHOT,
BUGREPORT_AND_SCREENSHOT,
}
private static final String SHELL_EXE_BASE = "/data/local/tmp/";
private ITestDevice mDevice = null;
private IRemoteAndroidTestRunner mRunner = null;
@Option(name = "jar-path", description = "path to jars containing UI Automator test cases and"
+ " dependencies; May be repeated. " +
"If unspecified will use all jars found in /data/local/tmp/")
private List<String> mJarPaths = new ArrayList<String>();
@Option(name = "class",
description = "test class to run, may be repeated; multiple classess will be run"
+ " in the same order as provided in command line")
private List<String> mClasses = new ArrayList<String>();
@Option(name = "sync-time", description = "time to allow for initial sync, in ms")
private long mSyncTime = 0;
@Option(name = "run-arg",
description = "Additional test specific arguments to provide.")
private Map<String, String> mArgMap = new LinkedHashMap<String, String>();
@Option(name = "timeout",
description = "Aborts the test run if any test takes longer than the specified number "
+ "of milliseconds. For no timeout, set to 0.")
private int mTestTimeout = 30 * 60 * 1000; // default to 30 minutes
@Option(name = "capture-logs", description =
"capture bugreport and screenshot as specified.")
private LoggingOption mLoggingOption = LoggingOption.AFTER_FAILURE;
@Option(name = "runner-path", description = "path to uiautomator runner; may be null and "
+ "default will be used in this case")
private String mRunnerPath = null;
@Option(name = "on-test-failure",
description = "sets the action to perform if a test fails")
private TestFailureAction mFailureAction = TestFailureAction.BUGREPORT_AND_SCREENSHOT;
@Option(name = "ignore-sighup",
description = "allows uiautomator test to ignore SIGHUP signal")
private boolean mIgnoreSighup = false;
@Option(name = "run-name",
description = "the run name to use when reporting test results.")
private String mRunName = "uiautomator";
@Option(name = "instrumentation",
description = "the specified test should be driven with instrumentation."
+ "jar-path, runner-path, ignore-sighup are ignored when this is set.")
private boolean mInstrumentation = false;
@Option(name = "package",
description = "The manifest package name of the UI test package."
+ "Only applies when 'instrumentation' option is set.")
private String mPackage = null;
@Option(name = "runner",
description="The instrumentation based test runner class name to use."
+ "Only applies when 'instrumentation' option is set.")
private String mRunnerName =
"android.support.test.uiautomator.UiAutomatorInstrumentationTestRunner";
@Option(name="final-screenshot", description="Take a screenshot at the end of a test")
private boolean mFinalScreenshot = false;
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mDevice;
}
public void setLoggingOption(LoggingOption loggingOption) {
mLoggingOption = loggingOption;
}
/**
* @deprecated use {@link #setLoggingOption(LoggingOption)} instead.
* <p/>
* Retained for compatibility with cts-tradefed
*/
@Deprecated
public void setCaptureLogs(boolean captureLogs) {
if (captureLogs) {
setLoggingOption(LoggingOption.AFTER_FAILURE);
} else {
setLoggingOption(LoggingOption.OFF);
}
}
public void setRunName(String runName) {
mRunName = runName;
}
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
if (!isInstrumentationTest()) {
buildJarPaths();
}
mRunner = createTestRunner();
if (!mClasses.isEmpty()) {
getTestRunner().setClassNames(mClasses.toArray(new String[]{}));
}
getTestRunner().setRunName(mRunName);
preTestSetup();
getRunUtil().sleep(getSyncTime());
getTestRunner().setMaxTimeToOutputResponse(mTestTimeout, TimeUnit.MILLISECONDS);
for (Map.Entry<String, String> entry : getTestRunArgMap().entrySet()) {
getTestRunner().addInstrumentationArg(entry.getKey(), entry.getValue());
}
if (!isInstrumentationTest()) {
((UiAutomatorRunner)getTestRunner()).setIgnoreSighup(mIgnoreSighup);
}
if (mLoggingOption != LoggingOption.OFF) {
getDevice().runInstrumentationTests(getTestRunner(), listener,
new LoggingWrapper(listener));
} else {
getDevice().runInstrumentationTests(getTestRunner(), listener);
}
if (mFinalScreenshot) {
saveScreenshot(listener, "final_screenshot");
}
}
protected IRemoteAndroidTestRunner createTestRunner() {
if (isInstrumentationTest()) {
if (mPackage == null) {
throw new IllegalArgumentException("package name has not been set");
}
IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mPackage, mRunnerName,
getDevice().getIDevice());
return runner;
} else {
return new UiAutomatorRunner(getDevice().getIDevice(),
getTestJarPaths().toArray(new String[]{}), mRunnerPath);
}
}
private void buildJarPaths() throws DeviceNotAvailableException {
if (mJarPaths.isEmpty()) {
String rawFileString =
getDevice().executeShellCommand(String.format("ls %s", SHELL_EXE_BASE));
String[] rawFiles = rawFileString.split("\r?\n");
for (String rawFile : rawFiles) {
if (rawFile.endsWith(".jar")) {
mJarPaths.add(rawFile);
}
}
Assert.assertFalse(String.format("could not find jars in %s", SHELL_EXE_BASE),
mJarPaths.isEmpty());
CLog.d("built jar paths %s", mJarPaths);
}
}
/**
* Add an argument to provide when running the UI Automator tests
*
* @param key the argument name
* @param value the argument value
*/
public void addRunArg(String key, String value) {
getTestRunArgMap().put(key, value);
}
/**
* Checks if the UI Automator components are present on device
*
* @throws DeviceNotAvailableException
*/
protected void preTestSetup() throws DeviceNotAvailableException {
if (!isInstrumentationTest()) {
String runnerPath = ((UiAutomatorRunner)getTestRunner()).getRunnerPath();
if (!getDevice().doesFileExist(runnerPath)) {
throw new RuntimeException("Missing UI Automator runner: " + runnerPath);
}
for (String jarPath : getTestJarPaths()) {
if (!jarPath.startsWith(FileListingService.FILE_SEPARATOR)) {
jarPath = SHELL_EXE_BASE + jarPath;
}
if (!getDevice().doesFileExist(jarPath)) {
throw new RuntimeException("Missing UI Automator test jar on device: "
+ jarPath);
}
}
}
}
protected void onScreenshotAndBugreport(ITestDevice device, ITestInvocationListener listener,
String prefix) {
onScreenshotAndBugreport(device, listener, prefix, null);
}
protected void onScreenshotAndBugreport(ITestDevice device, ITestInvocationListener listener,
String prefix, TestFailureAction overrideAction) {
if (overrideAction == null) {
overrideAction = mFailureAction;
}
// get screen shot
if (overrideAction == TestFailureAction.SCREENSHOT ||
overrideAction == TestFailureAction.BUGREPORT_AND_SCREENSHOT) {
InputStreamSource screenshot = null;
try {
screenshot = device.getScreenshot();
listener.testLog(prefix + "_screenshot", LogDataType.PNG, screenshot);
} catch (DeviceNotAvailableException e) {
CLog.e(e);
} finally {
if (screenshot != null) {
screenshot.cancel();
}
}
}
// get bugreport
if (overrideAction == TestFailureAction.BUGREPORT ||
overrideAction == TestFailureAction.BUGREPORT_AND_SCREENSHOT) {
InputStreamSource data = null;
data = device.getBugreport();
listener.testLog(prefix + "_bugreport", LogDataType.BUGREPORT, data);
if (data != null) {
data.cancel();
}
}
}
/**
* Wraps an existing listener, capture some data in case of test failure
*/
// TODO replace this once we have a generic event triggered reporter like
// BugReportCollector
private class LoggingWrapper extends StubTestInvocationListener {
ITestInvocationListener mListener;
private boolean mLoggedTestFailure = false;
private boolean mLoggedTestRunFailure = false;
public LoggingWrapper(ITestInvocationListener listener) {
mListener = listener;
}
@Override
public void testFailed(TestIdentifier test, String trace) {
captureFailureLog(test);
}
@Override
public void testAssumptionFailure(TestIdentifier test, String trace) {
captureFailureLog(test);
}
private void captureFailureLog(TestIdentifier test) {
if (mLoggingOption == LoggingOption.AFTER_FAILURE) {
onScreenshotAndBugreport(getDevice(), mListener, String.format("%s_%s_failure",
test.getClassName(), test.getTestName()));
// set the flag so that we don't log again when test finishes
mLoggedTestFailure = true;
}
}
@Override
public void testRunFailed(String errorMessage) {
if (mLoggingOption == LoggingOption.AFTER_FAILURE) {
onScreenshotAndBugreport(getDevice(), mListener, "test_run_failure");
// set the flag so that we don't log again when test run finishes
mLoggedTestRunFailure = true;
}
}
@Override
public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
if (!mLoggedTestFailure && mLoggingOption == LoggingOption.AFTER_TEST) {
onScreenshotAndBugreport(getDevice(), mListener, String.format("%s_%s_final",
test.getClassName(), test.getTestName()));
}
}
@Override
public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
if (!mLoggedTestRunFailure && mLoggingOption == LoggingOption.AFTER_TEST) {
onScreenshotAndBugreport(getDevice(), mListener, "test_run_final");
}
}
}
private void saveScreenshot(ITestInvocationListener listener, String name) {
}
protected IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* @return the time allocated for the tests to sync.
*/
public long getSyncTime() {
return mSyncTime;
}
/**
* @param syncTime the time for the tests files to sync.
*/
public void setSyncTime(long syncTime) {
mSyncTime = syncTime;
}
/**
* @return the test runner.
*/
public IRemoteAndroidTestRunner getTestRunner() {
return mRunner;
}
/**
* @return the test jar path.
*/
public List<String> getTestJarPaths() {
return mJarPaths;
}
/**
* @param jarPaths the locations of the test jars.
*/
public void setTestJarPaths(List<String> jarPaths) {
mJarPaths = jarPaths;
}
/**
* @return the arguments map to pass to the UiAutomatorRunner.
*/
public Map<String, String> getTestRunArgMap() {
return mArgMap;
}
/**
* @param runArgMap the arguments to pass to the UiAutomatorRunner.
*/
public void setTestRunArgMap(Map<String, String> runArgMap) {
mArgMap = runArgMap;
}
/**
* Add a test class name to run.
*/
public void addClassName(String className) {
mClasses.add(className);
}
/**
* Add a test class name collection to run.
*/
public void addClassNames(Collection<String> classNames) {
mClasses.addAll(classNames);
}
public boolean isInstrumentationTest() {
return mInstrumentation;
}
public void setRunnerName(String runnerName) {
mRunnerName = runnerName;
}
/**
* Gets the list of test class names that the harness is configured to run
* @return list of test class names
*/
public List<String> getClassNames() {
return mClasses;
}
}