blob: 0a2613ab19568ef405427874193d964a4304008c [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.IBinder;
import android.test.RepetitiveTest;
import android.util.Log;
import com.android.uiautomator.core.Tracer;
import com.android.uiautomator.core.Tracer.Mode;
import com.android.uiautomator.core.UiDevice;
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;
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;
/**
* @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 boolean mDebug;
private Bundle mParams = null;
private UiDevice mUiDevice;
private List<String> mTestClasses = null;
private FakeInstrumentationWatcher mWatcher = new FakeInstrumentationWatcher();
private IAutomationSupport mAutomationSupport = new IAutomationSupport() {
@Override
public void sendStatus(int resultCode, Bundle status) {
mWatcher.instrumentationStatus(null, resultCode, status);
}
};
private List<TestListener> mTestListeners = new ArrayList<TestListener>();
public void run(List<String> testClasses, Bundle params, boolean debug) {
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;
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();
}
mUiDevice = UiDevice.getInstance();
List<TestCase> testCases = collector.getTestCases();
Bundle testRunOutput = new Bundle();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PrintStream writer = new PrintStream(byteArrayOutputStream);
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);
}
try {
StringResultPrinter resultPrinter = new StringResultPrinter(writer);
TestResult testRunResult = new TestResult();
// add test listeners
testRunResult.addListener(new WatcherResultPrinter(testCases.size()));
testRunResult.addListener(resultPrinter);
// add all custom listeners
for (TestListener listener : mTestListeners) {
testRunResult.addListener(listener);
}
long startTime = System.currentTimeMillis();
// run tests for realz!
for (TestCase testCase : testCases) {
prepareTestCase(testCase);
testCase.run(testRunResult);
}
long runTime = System.currentTimeMillis() - startTime;
resultPrinter.print2(testRunResult, runTime);
} catch (Throwable t) {
// catch all exceptions so a more verbose error message can be outputted
writer.println(String.format("Test run aborted due to unexpected exception: %s",
t.getMessage()));
t.printStackTrace(writer);
} finally {
testRunOutput.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nTest results for %s=%s",
getClass().getSimpleName(),
byteArrayOutputStream.toString()));
writer.close();
mAutomationSupport.sendStatus(Activity.RESULT_OK, testRunOutput);
}
}
// copy & pasted from com.android.commands.am.Am
private class FakeInstrumentationWatcher implements IInstrumentationWatcher {
private 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();
}
}
}
// Copy & pasted from InstrumentationTestRunner.WatcherResultPrinter
private class WatcherResultPrinter implements TestListener {
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;
public WatcherResultPrinter(int numTests) {
mResultTemplate = new Bundle();
mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
}
/**
* 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;
}
@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)));
}
@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)));
}
@Override
public void endTest(Test test) {
if (mTestResultCode == 0) {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
}
mAutomationSupport.sendStatus(mTestResultCode, mTestResult);
}
}
// copy pasted from InstrumentationTestRunner
private class StringResultPrinter extends ResultPrinter {
public StringResultPrinter(PrintStream writer) {
super(writer);
}
synchronized void print2(TestResult result, long runTime) {
printHeader(runTime);
printFooter(result);
}
}
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);
}
}