1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.uiautomator.testrunner; 18 19import android.app.Activity; 20import android.app.IInstrumentationWatcher; 21import android.app.Instrumentation; 22import android.content.ComponentName; 23import android.os.Bundle; 24import android.os.Debug; 25import android.os.IBinder; 26import android.test.RepetitiveTest; 27import android.util.Log; 28 29import com.android.uiautomator.core.UiDevice; 30 31import junit.framework.AssertionFailedError; 32import junit.framework.Test; 33import junit.framework.TestCase; 34import junit.framework.TestListener; 35import junit.framework.TestResult; 36import junit.runner.BaseTestRunner; 37import junit.textui.ResultPrinter; 38 39import java.io.ByteArrayOutputStream; 40import java.io.PrintStream; 41import java.lang.Thread.UncaughtExceptionHandler; 42import java.lang.reflect.Method; 43import java.util.ArrayList; 44import java.util.List; 45 46/** 47 * @hide 48 */ 49public class UiAutomatorTestRunner { 50 51 private static final String LOGTAG = UiAutomatorTestRunner.class.getSimpleName(); 52 private static final int EXIT_OK = 0; 53 private static final int EXIT_EXCEPTION = -1; 54 55 private boolean mDebug; 56 private Bundle mParams = null; 57 private UiDevice mUiDevice; 58 private List<String> mTestClasses = null; 59 private FakeInstrumentationWatcher mWatcher = new FakeInstrumentationWatcher(); 60 private IAutomationSupport mAutomationSupport = new IAutomationSupport() { 61 @Override 62 public void sendStatus(int resultCode, Bundle status) { 63 mWatcher.instrumentationStatus(null, resultCode, status); 64 } 65 }; 66 private List<TestListener> mTestListeners = new ArrayList<TestListener>(); 67 68 public void run(List<String> testClasses, Bundle params, boolean debug) { 69 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { 70 @Override 71 public void uncaughtException(Thread thread, Throwable ex) { 72 Log.e(LOGTAG, "uncaught exception", ex); 73 Bundle results = new Bundle(); 74 results.putString("shortMsg", ex.getClass().getName()); 75 results.putString("longMsg", ex.getMessage()); 76 mWatcher.instrumentationFinished(null, 0, results); 77 // bailing on uncaught exception 78 System.exit(EXIT_EXCEPTION); 79 } 80 }); 81 82 mTestClasses = testClasses; 83 mParams = params; 84 mDebug = debug; 85 start(); 86 System.exit(EXIT_OK); 87 } 88 89 /** 90 * Called after all test classes are in place, ready to test 91 */ 92 protected void start() { 93 TestCaseCollector collector = getTestCaseCollector(this.getClass().getClassLoader()); 94 try { 95 collector.addTestClasses(mTestClasses); 96 } catch (ClassNotFoundException e) { 97 // will be caught by uncaught handler 98 throw new RuntimeException(e.getMessage(), e); 99 } 100 if (mDebug) { 101 Debug.waitForDebugger(); 102 } 103 mUiDevice = UiDevice.getInstance(); 104 List<TestCase> testCases = collector.getTestCases(); 105 Bundle testRunOutput = new Bundle(); 106 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 107 PrintStream writer = new PrintStream(byteArrayOutputStream); 108 try { 109 StringResultPrinter resultPrinter = new StringResultPrinter(writer); 110 111 TestResult testRunResult = new TestResult(); 112 // add test listeners 113 testRunResult.addListener(new WatcherResultPrinter(testCases.size())); 114 testRunResult.addListener(resultPrinter); 115 // add all custom listeners 116 for (TestListener listener : mTestListeners) { 117 testRunResult.addListener(listener); 118 } 119 long startTime = System.currentTimeMillis(); 120 121 // run tests for realz! 122 for (TestCase testCase : testCases) { 123 prepareTestCase(testCase); 124 testCase.run(testRunResult); 125 } 126 long runTime = System.currentTimeMillis() - startTime; 127 128 resultPrinter.print2(testRunResult, runTime); 129 } catch (Throwable t) { 130 // catch all exceptions so a more verbose error message can be outputted 131 writer.println(String.format("Test run aborted due to unexpected exception: %s", 132 t.getMessage())); 133 t.printStackTrace(writer); 134 } finally { 135 testRunOutput.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 136 String.format("\nTest results for %s=%s", 137 getClass().getSimpleName(), 138 byteArrayOutputStream.toString())); 139 writer.close(); 140 mAutomationSupport.sendStatus(Activity.RESULT_OK, testRunOutput); 141 } 142 } 143 144 // copy & pasted from com.android.commands.am.Am 145 private class FakeInstrumentationWatcher implements IInstrumentationWatcher { 146 147 private boolean mRawMode = true; 148 149 @Override 150 public IBinder asBinder() { 151 throw new UnsupportedOperationException("I'm just a fake!"); 152 } 153 154 @Override 155 public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) { 156 synchronized (this) { 157 // pretty printer mode? 158 String pretty = null; 159 if (!mRawMode && results != null) { 160 pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); 161 } 162 if (pretty != null) { 163 System.out.print(pretty); 164 } else { 165 if (results != null) { 166 for (String key : results.keySet()) { 167 System.out.println("INSTRUMENTATION_STATUS: " + key + "=" 168 + results.get(key)); 169 } 170 } 171 System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode); 172 } 173 notifyAll(); 174 } 175 } 176 177 @Override 178 public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) { 179 synchronized (this) { 180 // pretty printer mode? 181 String pretty = null; 182 if (!mRawMode && results != null) { 183 pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); 184 } 185 if (pretty != null) { 186 System.out.println(pretty); 187 } else { 188 if (results != null) { 189 for (String key : results.keySet()) { 190 System.out.println("INSTRUMENTATION_RESULT: " + key + "=" 191 + results.get(key)); 192 } 193 } 194 System.out.println("INSTRUMENTATION_CODE: " + resultCode); 195 } 196 notifyAll(); 197 } 198 } 199 } 200 201 // Copy & pasted from InstrumentationTestRunner.WatcherResultPrinter 202 private class WatcherResultPrinter implements TestListener { 203 204 private static final String REPORT_KEY_NUM_TOTAL = "numtests"; 205 private static final String REPORT_KEY_NAME_CLASS = "class"; 206 private static final String REPORT_KEY_NUM_CURRENT = "current"; 207 private static final String REPORT_KEY_NAME_TEST = "test"; 208 private static final String REPORT_KEY_NUM_ITERATIONS = "numiterations"; 209 private static final String REPORT_VALUE_ID = "UiAutomatorTestRunner"; 210 private static final String REPORT_KEY_STACK = "stack"; 211 212 private static final int REPORT_VALUE_RESULT_START = 1; 213 private static final int REPORT_VALUE_RESULT_ERROR = -1; 214 private static final int REPORT_VALUE_RESULT_FAILURE = -2; 215 216 private final Bundle mResultTemplate; 217 Bundle mTestResult; 218 int mTestNum = 0; 219 int mTestResultCode = 0; 220 String mTestClass = null; 221 222 public WatcherResultPrinter(int numTests) { 223 mResultTemplate = new Bundle(); 224 mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID); 225 mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests); 226 } 227 228 /** 229 * send a status for the start of a each test, so long tests can be seen 230 * as "running" 231 */ 232 @Override 233 public void startTest(Test test) { 234 String testClass = test.getClass().getName(); 235 String testName = ((TestCase) test).getName(); 236 mTestResult = new Bundle(mResultTemplate); 237 mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass); 238 mTestResult.putString(REPORT_KEY_NAME_TEST, testName); 239 mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum); 240 // pretty printing 241 if (testClass != null && !testClass.equals(mTestClass)) { 242 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 243 String.format("\n%s:", testClass)); 244 mTestClass = testClass; 245 } else { 246 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ""); 247 } 248 249 Method testMethod = null; 250 try { 251 testMethod = test.getClass().getMethod(testName); 252 // Report total number of iterations, if test is repetitive 253 if (testMethod.isAnnotationPresent(RepetitiveTest.class)) { 254 int numIterations = testMethod.getAnnotation(RepetitiveTest.class) 255 .numIterations(); 256 mTestResult.putInt(REPORT_KEY_NUM_ITERATIONS, numIterations); 257 } 258 } catch (NoSuchMethodException e) { 259 // ignore- the test with given name does not exist. Will be 260 // handled during test 261 // execution 262 } 263 264 mAutomationSupport.sendStatus(REPORT_VALUE_RESULT_START, mTestResult); 265 mTestResultCode = 0; 266 } 267 268 @Override 269 public void addError(Test test, Throwable t) { 270 mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t)); 271 mTestResultCode = REPORT_VALUE_RESULT_ERROR; 272 // pretty printing 273 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 274 String.format("\nError in %s:\n%s", 275 ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t))); 276 } 277 278 @Override 279 public void addFailure(Test test, AssertionFailedError t) { 280 mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t)); 281 mTestResultCode = REPORT_VALUE_RESULT_FAILURE; 282 // pretty printing 283 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 284 String.format("\nFailure in %s:\n%s", 285 ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t))); 286 } 287 288 @Override 289 public void endTest(Test test) { 290 if (mTestResultCode == 0) { 291 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "."); 292 } 293 mAutomationSupport.sendStatus(mTestResultCode, mTestResult); 294 } 295 296 } 297 298 // copy pasted from InstrumentationTestRunner 299 private class StringResultPrinter extends ResultPrinter { 300 301 public StringResultPrinter(PrintStream writer) { 302 super(writer); 303 } 304 305 synchronized void print2(TestResult result, long runTime) { 306 printHeader(runTime); 307 printFooter(result); 308 } 309 } 310 311 protected TestCaseCollector getTestCaseCollector(ClassLoader classLoader) { 312 return new TestCaseCollector(classLoader, new UiAutomatorTestCaseFilter()); 313 } 314 315 protected void addTestListener(TestListener listener) { 316 if (!mTestListeners.contains(listener)) { 317 mTestListeners.add(listener); 318 } 319 } 320 321 protected void removeTestListener(TestListener listener) { 322 mTestListeners.remove(listener); 323 } 324 325 /** 326 * subclass may override this method to perform further preparation 327 * 328 * @param testCase 329 */ 330 protected void prepareTestCase(TestCase testCase) { 331 ((UiAutomatorTestCase)testCase).setAutomationSupport(mAutomationSupport); 332 ((UiAutomatorTestCase)testCase).setUiDevice(mUiDevice); 333 ((UiAutomatorTestCase)testCase).setParams(mParams); 334 } 335} 336