blob: ef167f90e33e6de42bcb87fca92342c32e7c197b [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.uiautomator.testrunner;
import android.app.Activity;
import android.app.IInstrumentationWatcher;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Debug;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.SystemClock;
import android.test.RepetitiveTest;
import android.util.Log;
import com.android.uiautomator.core.ShellUiAutomatorBridge;
import com.android.uiautomator.core.Tracer;
import com.android.uiautomator.core.UiAutomationShellWrapper;
import com.android.uiautomator.core.UiDevice;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.runner.BaseTestRunner;
import junit.textui.ResultPrinter;
/**
* @hide
*/
public class UiAutomatorTestRunner {
private static final String LOGTAG = UiAutomatorTestRunner.class.getSimpleName();
private static final int EXIT_OK = 0;
private static final int EXIT_EXCEPTION = -1;
private static final String HANDLER_THREAD_NAME = "UiAutomatorHandlerThread";
private boolean mDebug;
private boolean mMonkey;
private Bundle mParams = null;
private UiDevice mUiDevice;
private List<String> mTestClasses = null;
private final FakeInstrumentationWatcher mWatcher = new FakeInstrumentationWatcher();
private final IAutomationSupport mAutomationSupport = new IAutomationSupport() {
@Override
public void sendStatus(int resultCode, Bundle status) {
mWatcher.instrumentationStatus(null, resultCode, status);
}
};
private final List<TestListener> mTestListeners = new ArrayList<TestListener>();
private HandlerThread mHandlerThread;
public void run(List<String> testClasses, Bundle params, boolean debug, boolean monkey) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.e(LOGTAG, "uncaught exception", ex);
Bundle results = new Bundle();
results.putString("shortMsg", ex.getClass().getName());
results.putString("longMsg", ex.getMessage());
mWatcher.instrumentationFinished(null, 0, results);
// bailing on uncaught exception
System.exit(EXIT_EXCEPTION);
}
});
mTestClasses = testClasses;
mParams = params;
mDebug = debug;
mMonkey = monkey;
start();
System.exit(EXIT_OK);
}
/**
* Called after all test classes are in place, ready to test
*/
protected void start() {
TestCaseCollector collector = getTestCaseCollector(this.getClass().getClassLoader());
try {
collector.addTestClasses(mTestClasses);
} catch (ClassNotFoundException e) {
// will be caught by uncaught handler
throw new RuntimeException(e.getMessage(), e);
}
if (mDebug) {
Debug.waitForDebugger();
}
mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
mHandlerThread.setDaemon(true);
mHandlerThread.start();
UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
automationWrapper.connect();
long startTime = SystemClock.uptimeMillis();
TestResult testRunResult = new TestResult();
ResultReporter resultPrinter;
String outputFormat = mParams.getString("outputFormat");
List<TestCase> testCases = collector.getTestCases();
Bundle testRunOutput = new Bundle();
if ("simple".equals(outputFormat)) {
resultPrinter = new SimpleResultPrinter(System.out, true);
} else {
resultPrinter = new WatcherResultPrinter(testCases.size());
}
try {
automationWrapper.setRunAsMonkey(mMonkey);
mUiDevice = UiDevice.getInstance();
mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation()));
String traceType = mParams.getString("traceOutputMode");
if(traceType != null) {
Tracer.Mode mode = Tracer.Mode.valueOf(Tracer.Mode.class, traceType);
if (mode == Tracer.Mode.FILE || mode == Tracer.Mode.ALL) {
String filename = mParams.getString("traceLogFilename");
if (filename == null) {
throw new RuntimeException("Name of log file not specified. " +
"Please specify it using traceLogFilename parameter");
}
Tracer.getInstance().setOutputFilename(filename);
}
Tracer.getInstance().setOutputMode(mode);
}
// add test listeners
testRunResult.addListener(resultPrinter);
// add all custom listeners
for (TestListener listener : mTestListeners) {
testRunResult.addListener(listener);
}
// run tests for realz!
for (TestCase testCase : testCases) {
prepareTestCase(testCase);
testCase.run(testRunResult);
}
} catch (Throwable t) {
// catch all exceptions so a more verbose error message can be outputted
resultPrinter.printUnexpectedError(t);
testRunOutput.putString("shortMsg", t.getMessage());
} finally {
long runTime = SystemClock.uptimeMillis() - startTime;
resultPrinter.print(testRunResult, runTime, testRunOutput);
automationWrapper.disconnect();
automationWrapper.setRunAsMonkey(false);
mHandlerThread.quit();
}
}
// copy & pasted from com.android.commands.am.Am
private class FakeInstrumentationWatcher implements IInstrumentationWatcher {
private final boolean mRawMode = true;
@Override
public IBinder asBinder() {
throw new UnsupportedOperationException("I'm just a fake!");
}
@Override
public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.print(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println("INSTRUMENTATION_STATUS: " + key + "="
+ results.get(key));
}
}
System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
}
notifyAll();
}
}
@Override
public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.println(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println("INSTRUMENTATION_RESULT: " + key + "="
+ results.get(key));
}
}
System.out.println("INSTRUMENTATION_CODE: " + resultCode);
}
notifyAll();
}
}
}
private interface ResultReporter extends TestListener {
public void print(TestResult result, long runTime, Bundle testOutput);
public void printUnexpectedError(Throwable t);
}
// Copy & pasted from InstrumentationTestRunner.WatcherResultPrinter
private class WatcherResultPrinter implements ResultReporter {
private static final String REPORT_KEY_NUM_TOTAL = "numtests";
private static final String REPORT_KEY_NAME_CLASS = "class";
private static final String REPORT_KEY_NUM_CURRENT = "current";
private static final String REPORT_KEY_NAME_TEST = "test";
private static final String REPORT_KEY_NUM_ITERATIONS = "numiterations";
private static final String REPORT_VALUE_ID = "UiAutomatorTestRunner";
private static final String REPORT_KEY_STACK = "stack";
private static final int REPORT_VALUE_RESULT_START = 1;
private static final int REPORT_VALUE_RESULT_ERROR = -1;
private static final int REPORT_VALUE_RESULT_FAILURE = -2;
private final Bundle mResultTemplate;
Bundle mTestResult;
int mTestNum = 0;
int mTestResultCode = 0;
String mTestClass = null;
private final SimpleResultPrinter mPrinter;
private final ByteArrayOutputStream mStream;
private final PrintStream mWriter;
public WatcherResultPrinter(int numTests) {
mResultTemplate = new Bundle();
mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
mStream = new ByteArrayOutputStream();
mWriter = new PrintStream(mStream);
mPrinter = new SimpleResultPrinter(mWriter, false);
}
/**
* send a status for the start of a each test, so long tests can be seen
* as "running"
*/
@Override
public void startTest(Test test) {
String testClass = test.getClass().getName();
String testName = ((TestCase) test).getName();
mTestResult = new Bundle(mResultTemplate);
mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
mTestResult.putString(REPORT_KEY_NAME_TEST, testName);
mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
// pretty printing
if (testClass != null && !testClass.equals(mTestClass)) {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\n%s:", testClass));
mTestClass = testClass;
} else {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "");
}
Method testMethod = null;
try {
testMethod = test.getClass().getMethod(testName);
// Report total number of iterations, if test is repetitive
if (testMethod.isAnnotationPresent(RepetitiveTest.class)) {
int numIterations = testMethod.getAnnotation(RepetitiveTest.class)
.numIterations();
mTestResult.putInt(REPORT_KEY_NUM_ITERATIONS, numIterations);
}
} catch (NoSuchMethodException e) {
// ignore- the test with given name does not exist. Will be
// handled during test
// execution
}
mAutomationSupport.sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
mTestResultCode = 0;
mPrinter.startTest(test);
}
@Override
public void addError(Test test, Throwable t) {
mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
mTestResultCode = REPORT_VALUE_RESULT_ERROR;
// pretty printing
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nError in %s:\n%s",
((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
mPrinter.addError(test, t);
}
@Override
public void addFailure(Test test, AssertionFailedError t) {
mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
// pretty printing
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nFailure in %s:\n%s",
((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
mPrinter.addFailure(test, t);
}
@Override
public void endTest(Test test) {
if (mTestResultCode == 0) {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
}
mAutomationSupport.sendStatus(mTestResultCode, mTestResult);
mPrinter.endTest(test);
}
@Override
public void print(TestResult result, long runTime, Bundle testOutput) {
mPrinter.print(result, runTime, testOutput);
testOutput.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nTest results for %s=%s",
getClass().getSimpleName(),
mStream.toString()));
mWriter.close();
mAutomationSupport.sendStatus(Activity.RESULT_OK, testOutput);
}
@Override
public void printUnexpectedError(Throwable t) {
mWriter.println(String.format("Test run aborted due to unexpected exception: %s",
t.getMessage()));
t.printStackTrace(mWriter);
}
}
/**
* Class that produces the same output as JUnit when running from command line. Can be
* used when default UiAutomator output is too verbose.
*/
private class SimpleResultPrinter extends ResultPrinter implements ResultReporter {
private final boolean mFullOutput;
public SimpleResultPrinter(PrintStream writer, boolean fullOutput) {
super(writer);
mFullOutput = fullOutput;
}
@Override
public void print(TestResult result, long runTime, Bundle testOutput) {
printHeader(runTime);
if (mFullOutput) {
printErrors(result);
printFailures(result);
}
printFooter(result);
}
@Override
public void printUnexpectedError(Throwable t) {
if (mFullOutput) {
getWriter().printf("Test run aborted due to unexpected exeption: %s",
t.getMessage());
t.printStackTrace(getWriter());
}
}
}
protected TestCaseCollector getTestCaseCollector(ClassLoader classLoader) {
return new TestCaseCollector(classLoader, getTestCaseFilter());
}
/**
* Returns an object which determines if the class and its methods should be
* accepted into the test suite.
* @return
*/
public UiAutomatorTestCaseFilter getTestCaseFilter() {
return new UiAutomatorTestCaseFilter();
}
protected void addTestListener(TestListener listener) {
if (!mTestListeners.contains(listener)) {
mTestListeners.add(listener);
}
}
protected void removeTestListener(TestListener listener) {
mTestListeners.remove(listener);
}
/**
* subclass may override this method to perform further preparation
*
* @param testCase
*/
protected void prepareTestCase(TestCase testCase) {
((UiAutomatorTestCase)testCase).setAutomationSupport(mAutomationSupport);
((UiAutomatorTestCase)testCase).setUiDevice(mUiDevice);
((UiAutomatorTestCase)testCase).setParams(mParams);
}
}