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 17 package com.android.test.runner; 18 19 import android.app.Activity; 20 import android.app.Instrumentation; 21 import android.os.Bundle; 22 import android.os.Debug; 23 import android.os.Looper; 24 import android.test.suitebuilder.annotation.LargeTest; 25 import android.util.Log; 26 27 import org.junit.internal.TextListener; 28 import org.junit.runner.Description; 29 import org.junit.runner.JUnitCore; 30 import org.junit.runner.Result; 31 import org.junit.runner.notification.Failure; 32 import org.junit.runner.notification.RunListener; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.PrintStream; 36 37 /** 38 * An {@link Instrumentation} that runs JUnit3 and JUnit4 tests against 39 * an Android package (application). 40 * <p/> 41 * Currently experimental. Based on {@link android.test.InstrumentationTestRunner}. 42 * <p/> 43 * Will eventually support a superset of {@link android.test.InstrumentationTestRunner} features, 44 * while maintaining command/output format compatibility with that class. 45 * 46 * <h3>Typical Usage</h3> 47 * <p/> 48 * Write JUnit3 style {@link junit.framework.TestCase}s and/or JUnit4 style 49 * {@link org.junit.Test}s that perform tests against the classes in your package. 50 * Make use of the {@link com.android.test.InjectContext} and 51 * {@link com.android.test.InjectInstrumentation} annotations if needed. 52 * <p/> 53 * In an appropriate AndroidManifest.xml, define an instrumentation with android:name set to 54 * {@link com.android.test.runner.AndroidJUnitRunner} and the appropriate android:targetPackage set. 55 * <p/> 56 * Execution options: 57 * <p/> 58 * <b>Running all tests:</b> adb shell am instrument -w 59 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 60 * <p/> 61 * <b>Running all tests in a class:</b> adb shell am instrument -w 62 * -e class com.android.foo.FooTest 63 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 64 * <p/> 65 * <b>Running a single test:</b> adb shell am instrument -w 66 * -e class com.android.foo.FooTest#testFoo 67 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 68 * <p/> 69 * <b>Running all tests in multiple classes:</b> adb shell am instrument -w 70 * -e class com.android.foo.FooTest,com.android.foo.TooTest 71 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 72 * <p/> 73 * <b>To debug your tests, set a break point in your code and pass:</b> 74 * -e debug true 75 * <p/> 76 * <b>Running a specific test size i.e. annotated with 77 * {@link android.test.suitebuilder.annotation.SmallTest} or 78 * {@link android.test.suitebuilder.annotation.MediumTest} or 79 * {@link android.test.suitebuilder.annotation.LargeTest}:</b> 80 * adb shell am instrument -w -e size [small|medium|large] 81 * com.android.foo/android.test.InstrumentationTestRunner 82 * <p/> 83 * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w 84 * -e annotation com.android.foo.MyAnnotation 85 * com.android.foo/android.test.InstrumentationTestRunner 86 * <p/> 87 * If used with other options, the resulting test run will contain the intersection of the two 88 * options. 89 * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both 90 * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations. 91 * <p/> 92 * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w 93 * -e notAnnotation com.android.foo.MyAnnotation 94 * com.android.foo/android.test.InstrumentationTestRunner 95 * <p/> 96 * As above, if used with other options, the resulting test run will contain the intersection of 97 * the two options. 98 * e.g. "-e size large -e notAnnotation com.android.foo.MyAnnotation" will run tests with 99 * the {@link LargeTest} annotation that do NOT have the "com.android.foo.MyAnnotation" annotations. 100 * <p/> 101 * <b>To run in 'log only' mode</b> 102 * -e log true 103 * This option will load and iterate through all test classes and methods, but will bypass actual 104 * test execution. Useful for quickly obtaining info on the tests to be executed by an 105 * instrumentation command. 106 * <p/> 107 */ 108 public class AndroidJUnitRunner extends Instrumentation { 109 110 public static final String ARGUMENT_TEST_CLASS = "class"; 111 112 private static final String ARGUMENT_TEST_SIZE = "size"; 113 private static final String ARGUMENT_LOG_ONLY = "log"; 114 private static final String ARGUMENT_ANNOTATION = "annotation"; 115 private static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation"; 116 117 /** 118 * The following keys are used in the status bundle to provide structured reports to 119 * an IInstrumentationWatcher. 120 */ 121 122 /** 123 * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER}, 124 * identifies InstrumentationTestRunner as the source of the report. This is sent with all 125 * status messages. 126 */ 127 public static final String REPORT_VALUE_ID = "InstrumentationTestRunner"; 128 /** 129 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 130 * identifies the total number of tests that are being run. This is sent with all status 131 * messages. 132 */ 133 public static final String REPORT_KEY_NUM_TOTAL = "numtests"; 134 /** 135 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 136 * identifies the sequence number of the current test. This is sent with any status message 137 * describing a specific test being started or completed. 138 */ 139 public static final String REPORT_KEY_NUM_CURRENT = "current"; 140 /** 141 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 142 * identifies the name of the current test class. This is sent with any status message 143 * describing a specific test being started or completed. 144 */ 145 public static final String REPORT_KEY_NAME_CLASS = "class"; 146 /** 147 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key 148 * identifies the name of the current test. This is sent with any status message 149 * describing a specific test being started or completed. 150 */ 151 public static final String REPORT_KEY_NAME_TEST = "test"; 152 153 /** 154 * The test is starting. 155 */ 156 public static final int REPORT_VALUE_RESULT_START = 1; 157 /** 158 * The test completed successfully. 159 */ 160 public static final int REPORT_VALUE_RESULT_OK = 0; 161 /** 162 * The test completed with an error. 163 */ 164 public static final int REPORT_VALUE_RESULT_ERROR = -1; 165 /** 166 * The test completed with a failure. 167 */ 168 public static final int REPORT_VALUE_RESULT_FAILURE = -2; 169 /** 170 * The test was ignored. 171 */ 172 public static final int REPORT_VALUE_RESULT_IGNORED = -3; 173 /** 174 * If included in the status bundle sent to an IInstrumentationWatcher, this key 175 * identifies a stack trace describing an error or failure. This is sent with any status 176 * message describing a specific test being completed. 177 */ 178 public static final String REPORT_KEY_STACK = "stack"; 179 180 private static final String LOG_TAG = "InstrumentationTestRunner"; 181 182 private final Bundle mResults = new Bundle(); 183 private Bundle mArguments; 184 185 @Override 186 public void onCreate(Bundle arguments) { 187 super.onCreate(arguments); 188 mArguments = arguments; 189 190 start(); 191 } 192 193 /** 194 * Get the Bundle object that contains the arguments passed to the instrumentation 195 * 196 * @return the Bundle object 197 * @hide 198 */ 199 public Bundle getArguments(){ 200 return mArguments; 201 } 202 203 private boolean getBooleanArgument(Bundle arguments, String tag) { 204 String tagString = arguments.getString(tag); 205 return tagString != null && Boolean.parseBoolean(tagString); 206 } 207 208 /** 209 * Initialize the current thread as a looper. 210 * <p/> 211 * Exposed for unit testing. 212 */ 213 void prepareLooper() { 214 Looper.prepare(); 215 } 216 217 @Override 218 public void onStart() { 219 prepareLooper(); 220 221 if (getBooleanArgument(getArguments(), "debug")) { 222 Debug.waitForDebugger(); 223 } 224 225 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 226 PrintStream writer = new PrintStream(byteArrayOutputStream); 227 try { 228 JUnitCore testRunner = new JUnitCore(); 229 testRunner.addListener(new TextListener(writer)); 230 WatcherResultPrinter detailedResultPrinter = new WatcherResultPrinter(); 231 testRunner.addListener(detailedResultPrinter); 232 233 TestRequest testRequest = buildRequest(getArguments(), writer); 234 Result result = testRunner.run(testRequest.getRequest()); 235 result.getFailures().addAll(testRequest.getFailures()); 236 Log.i(LOG_TAG, String.format("Test run complete. %d tests, %d failed, %d ignored", 237 result.getRunCount(), result.getFailureCount(), result.getIgnoreCount())); 238 } catch (Throwable t) { 239 // catch all exceptions so a more verbose error message can be displayed 240 writer.println(String.format( 241 "Test run aborted due to unexpected exception: %s", 242 t.getMessage())); 243 t.printStackTrace(writer); 244 245 } finally { 246 writer.close(); 247 mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 248 String.format("\n%s", 249 byteArrayOutputStream.toString())); 250 finish(Activity.RESULT_OK, mResults); 251 } 252 253 } 254 255 /** 256 * Builds a {@link TestRequest} based on given input arguments. 257 * <p/> 258 * Exposed for unit testing. 259 */ 260 TestRequest buildRequest(Bundle arguments, PrintStream writer) { 261 // only load tests for current aka testContext 262 // Note that this represents a change from InstrumentationTestRunner where 263 // getTargetContext().getPackageCodePath() was also scanned 264 TestRequestBuilder builder = createTestRequestBuilder(writer, 265 getContext().getPackageCodePath()); 266 267 String testClassName = arguments.getString(ARGUMENT_TEST_CLASS); 268 if (testClassName != null) { 269 for (String className : testClassName.split(",")) { 270 parseTestClass(className, builder); 271 } 272 } 273 274 String testSize = arguments.getString(ARGUMENT_TEST_SIZE); 275 if (testSize != null) { 276 builder.addTestSizeFilter(testSize); 277 } 278 279 String annotation = arguments.getString(ARGUMENT_ANNOTATION); 280 if (annotation != null) { 281 builder.addAnnotationInclusionFilter(annotation); 282 } 283 284 String notAnnotation = arguments.getString(ARGUMENT_NOT_ANNOTATION); 285 if (notAnnotation != null) { 286 builder.addAnnotationExclusionFilter(notAnnotation); 287 } 288 289 boolean logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY); 290 if (logOnly) { 291 builder.setSkipExecution(true); 292 } 293 return builder.build(this); 294 } 295 296 /** 297 * Factory method for {@link TestRequestBuilder}. 298 * <p/> 299 * Exposed for unit testing. 300 */ 301 TestRequestBuilder createTestRequestBuilder(PrintStream writer, String... packageCodePaths) { 302 return new TestRequestBuilder(writer, packageCodePaths); 303 } 304 305 /** 306 * Parse and load the given test class and, optionally, method 307 * 308 * @param testClassName - full package name of test class and optionally method to add. 309 * Expected format: com.android.TestClass#testMethod 310 * @param testSuiteBuilder - builder to add tests to 311 */ 312 private void parseTestClass(String testClassName, TestRequestBuilder testRequestBuilder) { 313 int methodSeparatorIndex = testClassName.indexOf('#'); 314 315 if (methodSeparatorIndex > 0) { 316 String testMethodName = testClassName.substring(methodSeparatorIndex + 1); 317 testClassName = testClassName.substring(0, methodSeparatorIndex); 318 testRequestBuilder.addTestMethod(testClassName, testMethodName); 319 } else { 320 testRequestBuilder.addTestClass(testClassName); 321 } 322 } 323 324 /** 325 * This class sends status reports back to the IInstrumentationWatcher 326 */ 327 private class WatcherResultPrinter extends RunListener { 328 private final Bundle mResultTemplate; 329 Bundle mTestResult; 330 int mTestNum = 0; 331 int mTestResultCode = 0; 332 String mTestClass = null; 333 334 public WatcherResultPrinter() { 335 mResultTemplate = new Bundle(); 336 } 337 338 @Override 339 public void testRunStarted(Description description) throws Exception { 340 mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID); 341 mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, description.testCount()); 342 } 343 344 @Override 345 public void testRunFinished(Result result) throws Exception { 346 // TODO: implement this 347 } 348 349 /** 350 * send a status for the start of a each test, so long tests can be seen 351 * as "running" 352 */ 353 @Override 354 public void testStarted(Description description) throws Exception { 355 String testClass = description.getClassName(); 356 String testName = description.getMethodName(); 357 mTestResult = new Bundle(mResultTemplate); 358 mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass); 359 mTestResult.putString(REPORT_KEY_NAME_TEST, testName); 360 mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum); 361 // pretty printing 362 if (testClass != null && !testClass.equals(mTestClass)) { 363 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 364 String.format("\n%s:", testClass)); 365 mTestClass = testClass; 366 } else { 367 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ""); 368 } 369 370 sendStatus(REPORT_VALUE_RESULT_START, mTestResult); 371 mTestResultCode = 0; 372 } 373 374 @Override 375 public void testFinished(Description description) throws Exception { 376 if (mTestResultCode == 0) { 377 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "."); 378 } 379 sendStatus(mTestResultCode, mTestResult); 380 } 381 382 @Override 383 public void testFailure(Failure failure) throws Exception { 384 mTestResultCode = REPORT_VALUE_RESULT_ERROR; 385 reportFailure(failure); 386 } 387 388 389 @Override 390 public void testAssumptionFailure(Failure failure) { 391 mTestResultCode = REPORT_VALUE_RESULT_FAILURE; 392 reportFailure(failure); 393 } 394 395 private void reportFailure(Failure failure) { 396 mTestResult.putString(REPORT_KEY_STACK, failure.getTrace()); 397 // pretty printing 398 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 399 String.format("\nError in %s:\n%s", 400 failure.getDescription().getDisplayName(), failure.getTrace())); 401 } 402 403 @Override 404 public void testIgnored(Description description) throws Exception { 405 testStarted(description); 406 mTestResultCode = REPORT_VALUE_RESULT_IGNORED; 407 testFinished(description); 408 } 409 } 410 } 411