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 com.android.test.runner.listener.CoverageListener; 28 import com.android.test.runner.listener.DelayInjector; 29 import com.android.test.runner.listener.InstrumentationResultPrinter; 30 import com.android.test.runner.listener.InstrumentationRunListener; 31 import com.android.test.runner.listener.SuiteAssignmentPrinter; 32 33 import org.junit.internal.TextListener; 34 import org.junit.runner.JUnitCore; 35 import org.junit.runner.Result; 36 import org.junit.runner.notification.RunListener; 37 38 import java.io.ByteArrayOutputStream; 39 import java.io.PrintStream; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * An {@link Instrumentation} that runs JUnit3 and JUnit4 tests against 45 * an Android package (application). 46 * <p/> 47 * Currently experimental. Based on {@link android.test.InstrumentationTestRunner}. 48 * <p/> 49 * Will eventually support a superset of {@link android.test.InstrumentationTestRunner} features, 50 * while maintaining command/output format compatibility with that class. 51 * 52 * <h3>Typical Usage</h3> 53 * <p/> 54 * Write JUnit3 style {@link junit.framework.TestCase}s and/or JUnit4 style 55 * {@link org.junit.Test}s that perform tests against the classes in your package. 56 * Make use of the {@link com.android.test.InjectContext} and 57 * {@link com.android.test.InjectInstrumentation} annotations if needed. 58 * <p/> 59 * In an appropriate AndroidManifest.xml, define an instrumentation with android:name set to 60 * {@link com.android.test.runner.AndroidJUnitRunner} and the appropriate android:targetPackage set. 61 * <p/> 62 * Execution options: 63 * <p/> 64 * <b>Running all tests:</b> adb shell am instrument -w 65 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 66 * <p/> 67 * <b>Running all tests in a class:</b> adb shell am instrument -w 68 * -e class com.android.foo.FooTest 69 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 70 * <p/> 71 * <b>Running a single test:</b> adb shell am instrument -w 72 * -e class com.android.foo.FooTest#testFoo 73 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 74 * <p/> 75 * <b>Running all tests in multiple classes:</b> adb shell am instrument -w 76 * -e class com.android.foo.FooTest,com.android.foo.TooTest 77 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 78 * <p/> 79 * <b>Running all tests in a java package:</b> adb shell am instrument -w 80 * -e package com.android.foo.bar 81 * com.android.foo/com.android.test.runner.AndroidJUnitRunner 82 * <b>To debug your tests, set a break point in your code and pass:</b> 83 * -e debug true 84 * <p/> 85 * <b>Running a specific test size i.e. annotated with 86 * {@link android.test.suitebuilder.annotation.SmallTest} or 87 * {@link android.test.suitebuilder.annotation.MediumTest} or 88 * {@link android.test.suitebuilder.annotation.LargeTest}:</b> 89 * adb shell am instrument -w -e size [small|medium|large] 90 * com.android.foo/android.test.InstrumentationTestRunner 91 * <p/> 92 * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w 93 * -e annotation com.android.foo.MyAnnotation 94 * com.android.foo/android.test.InstrumentationTestRunner 95 * <p/> 96 * If used with other options, the resulting test run will contain the intersection of the two 97 * options. 98 * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both 99 * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations. 100 * <p/> 101 * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w 102 * -e notAnnotation com.android.foo.MyAnnotation 103 * com.android.foo/android.test.InstrumentationTestRunner 104 * <p/> 105 * As above, if used with other options, the resulting test run will contain the intersection of 106 * the two options. 107 * e.g. "-e size large -e notAnnotation com.android.foo.MyAnnotation" will run tests with 108 * the {@link LargeTest} annotation that do NOT have the "com.android.foo.MyAnnotation" annotations. 109 * <p/> 110 * <b>To run in 'log only' mode</b> 111 * -e log true 112 * This option will load and iterate through all test classes and methods, but will bypass actual 113 * test execution. Useful for quickly obtaining info on the tests to be executed by an 114 * instrumentation command. 115 * <p/> 116 * <b>To generate EMMA code coverage:</b> 117 * -e coverage true 118 * Note: this requires an emma instrumented build. By default, the code coverage results file 119 * will be saved in a /data/<app>/coverage.ec file, unless overridden by coverageFile flag (see 120 * below) 121 * <p/> 122 * <b> To specify EMMA code coverage results file path:</b> 123 * -e coverageFile /sdcard/myFile.ec 124 * <p/> 125 */ 126 public class AndroidJUnitRunner extends Instrumentation { 127 128 // constants for supported instrumentation arguments 129 public static final String ARGUMENT_TEST_CLASS = "class"; 130 private static final String ARGUMENT_TEST_SIZE = "size"; 131 private static final String ARGUMENT_LOG_ONLY = "log"; 132 private static final String ARGUMENT_ANNOTATION = "annotation"; 133 private static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation"; 134 private static final String ARGUMENT_DELAY_MSEC = "delay_msec"; 135 private static final String ARGUMENT_COVERAGE = "coverage"; 136 private static final String ARGUMENT_COVERAGE_PATH = "coverageFile"; 137 private static final String ARGUMENT_SUITE_ASSIGNMENT = "suiteAssignment"; 138 private static final String ARGUMENT_DEBUG = "debug"; 139 private static final String ARGUMENT_EXTRA_LISTENER = "extraListener"; 140 private static final String ARGUMENT_TEST_PACKAGE = "package"; 141 // TODO: consider supporting 'count' from InstrumentationTestRunner 142 143 private static final String LOG_TAG = "AndroidJUnitRunner"; 144 145 private Bundle mArguments; 146 147 @Override 148 public void onCreate(Bundle arguments) { 149 super.onCreate(arguments); 150 mArguments = arguments; 151 152 start(); 153 } 154 155 /** 156 * Get the Bundle object that contains the arguments passed to the instrumentation 157 * 158 * @return the Bundle object 159 * @hide 160 */ 161 public Bundle getArguments(){ 162 return mArguments; 163 } 164 165 /** 166 * Set the arguments. 167 * 168 * @VisibleForTesting 169 */ 170 void setArguments(Bundle args) { 171 mArguments = args; 172 } 173 174 private boolean getBooleanArgument(String tag) { 175 String tagString = getArguments().getString(tag); 176 return tagString != null && Boolean.parseBoolean(tagString); 177 } 178 179 /** 180 * Initialize the current thread as a looper. 181 * <p/> 182 * Exposed for unit testing. 183 */ 184 void prepareLooper() { 185 Looper.prepare(); 186 } 187 188 @Override 189 public void onStart() { 190 prepareLooper(); 191 192 if (getBooleanArgument(ARGUMENT_DEBUG)) { 193 Debug.waitForDebugger(); 194 } 195 196 setupDexmaker(); 197 198 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 199 PrintStream writer = new PrintStream(byteArrayOutputStream); 200 List<RunListener> listeners = new ArrayList<RunListener>(); 201 202 try { 203 JUnitCore testRunner = new JUnitCore(); 204 addListeners(listeners, testRunner, writer); 205 206 TestRequest testRequest = buildRequest(getArguments(), writer); 207 Result result = testRunner.run(testRequest.getRequest()); 208 result.getFailures().addAll(testRequest.getFailures()); 209 Log.i(LOG_TAG, String.format("Test run complete. %d tests, %d failed, %d ignored", 210 result.getRunCount(), result.getFailureCount(), result.getIgnoreCount())); 211 } catch (Throwable t) { 212 // catch all exceptions so a more verbose error message can be displayed 213 writer.println(String.format( 214 "Test run aborted due to unexpected exception: %s", 215 t.getMessage())); 216 t.printStackTrace(writer); 217 218 } finally { 219 Bundle results = new Bundle(); 220 reportRunEnded(listeners, writer, results); 221 writer.close(); 222 results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 223 String.format("\n%s", 224 byteArrayOutputStream.toString())); 225 finish(Activity.RESULT_OK, results); 226 } 227 228 } 229 230 private void addListeners(List<RunListener> listeners, JUnitCore testRunner, 231 PrintStream writer) { 232 if (getBooleanArgument(ARGUMENT_SUITE_ASSIGNMENT)) { 233 addListener(listeners, testRunner, new SuiteAssignmentPrinter(writer)); 234 } else { 235 addListener(listeners, testRunner, new TextListener(writer)); 236 addListener(listeners, testRunner, new InstrumentationResultPrinter(this)); 237 addDelayListener(listeners, testRunner); 238 addCoverageListener(listeners, testRunner); 239 } 240 241 addExtraListeners(listeners, testRunner, writer); 242 } 243 244 private void addListener(List<RunListener> list, JUnitCore testRunner, RunListener listener) { 245 list.add(listener); 246 testRunner.addListener(listener); 247 } 248 249 private void addCoverageListener(List<RunListener> list, JUnitCore testRunner) { 250 if (getBooleanArgument(ARGUMENT_COVERAGE)) { 251 String coverageFilePath = getArguments().getString(ARGUMENT_COVERAGE_PATH); 252 addListener(list, testRunner, new CoverageListener(this, coverageFilePath)); 253 } 254 } 255 256 /** 257 * Sets up listener to inject {@link #ARGUMENT_DELAY_MSEC}, if specified. 258 * @param testRunner 259 */ 260 private void addDelayListener(List<RunListener> list, JUnitCore testRunner) { 261 try { 262 Object delay = getArguments().get(ARGUMENT_DELAY_MSEC); // Accept either string or int 263 if (delay != null) { 264 int delayMsec = Integer.parseInt(delay.toString()); 265 addListener(list, testRunner, new DelayInjector(delayMsec)); 266 } 267 } catch (NumberFormatException e) { 268 Log.e(LOG_TAG, "Invalid delay_msec parameter", e); 269 } 270 } 271 272 private void addExtraListeners(List<RunListener> listeners, JUnitCore testRunner, 273 PrintStream writer) { 274 String extraListenerList = getArguments().getString(ARGUMENT_EXTRA_LISTENER); 275 if (extraListenerList == null) { 276 return; 277 } 278 279 for (String listenerName : extraListenerList.split(",")) { 280 addExtraListener(listeners, testRunner, writer, listenerName); 281 } 282 } 283 284 private void addExtraListener(List<RunListener> listeners, JUnitCore testRunner, 285 PrintStream writer, String extraListener) { 286 if (extraListener == null || extraListener.length() == 0) { 287 return; 288 } 289 290 final Class<?> klass; 291 try { 292 klass = Class.forName(extraListener); 293 } catch (ClassNotFoundException e) { 294 writer.println("Could not find extra RunListener class " + extraListener); 295 return; 296 } 297 298 if (!RunListener.class.isAssignableFrom(klass)) { 299 writer.println("Extra listeners must extend RunListener class " + extraListener); 300 return; 301 } 302 303 try { 304 klass.getConstructor().setAccessible(true); 305 } catch (NoSuchMethodException e) { 306 writer.println("Must have no argument constructor for class " + extraListener); 307 return; 308 } 309 310 final RunListener l; 311 try { 312 l = (RunListener) klass.newInstance(); 313 } catch (Throwable t) { 314 writer.println("Could not instantiate extra RunListener class " + extraListener); 315 t.printStackTrace(writer); 316 return; 317 } 318 319 addListener(listeners, testRunner, l); 320 } 321 322 private void reportRunEnded(List<RunListener> listeners, PrintStream writer, Bundle results) { 323 for (RunListener listener : listeners) { 324 if (listener instanceof InstrumentationRunListener) { 325 ((InstrumentationRunListener)listener).instrumentationRunFinished(writer, results); 326 } 327 } 328 } 329 330 /** 331 * Builds a {@link TestRequest} based on given input arguments. 332 * <p/> 333 * Exposed for unit testing. 334 */ 335 TestRequest buildRequest(Bundle arguments, PrintStream writer) { 336 // only load tests for current aka testContext 337 // Note that this represents a change from InstrumentationTestRunner where 338 // getTargetContext().getPackageCodePath() was also scanned 339 TestRequestBuilder builder = createTestRequestBuilder(writer, 340 getContext().getPackageCodePath()); 341 342 String testClassName = arguments.getString(ARGUMENT_TEST_CLASS); 343 if (testClassName != null) { 344 for (String className : testClassName.split(",")) { 345 parseTestClass(className, builder); 346 } 347 } 348 349 String testPackage = arguments.getString(ARGUMENT_TEST_PACKAGE); 350 if (testPackage != null) { 351 builder.addTestPackageFilter(testPackage); 352 } 353 354 String testSize = arguments.getString(ARGUMENT_TEST_SIZE); 355 if (testSize != null) { 356 builder.addTestSizeFilter(testSize); 357 } 358 359 String annotation = arguments.getString(ARGUMENT_ANNOTATION); 360 if (annotation != null) { 361 builder.addAnnotationInclusionFilter(annotation); 362 } 363 364 String notAnnotation = arguments.getString(ARGUMENT_NOT_ANNOTATION); 365 if (notAnnotation != null) { 366 builder.addAnnotationExclusionFilter(notAnnotation); 367 } 368 369 if (getBooleanArgument(ARGUMENT_LOG_ONLY)) { 370 builder.setSkipExecution(true); 371 } 372 return builder.build(this, arguments); 373 } 374 375 /** 376 * Factory method for {@link TestRequestBuilder}. 377 * <p/> 378 * Exposed for unit testing. 379 */ 380 TestRequestBuilder createTestRequestBuilder(PrintStream writer, String... packageCodePaths) { 381 return new TestRequestBuilder(writer, packageCodePaths); 382 } 383 384 /** 385 * Parse and load the given test class and, optionally, method 386 * 387 * @param testClassName - full package name of test class and optionally method to add. 388 * Expected format: com.android.TestClass#testMethod 389 * @param testSuiteBuilder - builder to add tests to 390 */ 391 private void parseTestClass(String testClassName, TestRequestBuilder testRequestBuilder) { 392 int methodSeparatorIndex = testClassName.indexOf('#'); 393 394 if (methodSeparatorIndex > 0) { 395 String testMethodName = testClassName.substring(methodSeparatorIndex + 1); 396 testClassName = testClassName.substring(0, methodSeparatorIndex); 397 testRequestBuilder.addTestMethod(testClassName, testMethodName); 398 } else { 399 testRequestBuilder.addTestClass(testClassName); 400 } 401 } 402 403 private void setupDexmaker() { 404 // Explicitly set the Dexmaker cache, so tests that use mocking frameworks work 405 String dexCache = getTargetContext().getCacheDir().getPath(); 406 Log.i(LOG_TAG, "Setting dexmaker.dexcache to " + dexCache); 407 System.setProperty("dexmaker.dexcache", getTargetContext().getCacheDir().getPath()); 408 } 409 } 410