1 /* 2 * Copyright (C) 2010 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.tradefed.testtype; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkState; 21 22 import com.android.ddmlib.IDevice; 23 import com.android.ddmlib.Log; 24 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 25 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize; 26 import com.android.ddmlib.testrunner.InstrumentationResultParser; 27 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 28 import com.android.tradefed.config.ConfigurationException; 29 import com.android.tradefed.config.Option; 30 import com.android.tradefed.config.Option.Importance; 31 import com.android.tradefed.config.OptionClass; 32 import com.android.tradefed.device.DeviceNotAvailableException; 33 import com.android.tradefed.device.ITestDevice; 34 import com.android.tradefed.log.LogUtil.CLog; 35 import com.android.tradefed.result.BugreportCollector; 36 import com.android.tradefed.result.CollectingTestListener; 37 import com.android.tradefed.result.ITestInvocationListener; 38 import com.android.tradefed.result.InputStreamSource; 39 import com.android.tradefed.result.LogDataType; 40 import com.android.tradefed.result.LogcatCrashResultForwarder; 41 import com.android.tradefed.result.ResultForwarder; 42 import com.android.tradefed.result.TestDescription; 43 import com.android.tradefed.result.TestRunResult; 44 import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner; 45 import com.android.tradefed.util.AbiFormatter; 46 import com.android.tradefed.util.ArrayUtil; 47 import com.android.tradefed.util.ListInstrumentationParser; 48 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget; 49 import com.android.tradefed.util.StreamUtil; 50 import com.android.tradefed.util.StringEscapeUtils; 51 52 import com.google.common.annotations.VisibleForTesting; 53 54 import org.junit.Assert; 55 56 import java.io.File; 57 import java.util.ArrayList; 58 import java.util.Collection; 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.concurrent.TimeUnit; 63 64 /** 65 * A Test that runs an instrumentation test package on given device. 66 */ 67 @OptionClass(alias = "instrumentation") 68 public class InstrumentationTest implements IDeviceTest, IResumableTest, ITestCollector, 69 IAbiReceiver { 70 71 private static final String LOG_TAG = "InstrumentationTest"; 72 73 /** max number of attempts to collect list of tests in package */ 74 private static final int COLLECT_TESTS_ATTEMPTS = 3; 75 /** instrumentation test runner argument key used for test execution using a file */ 76 private static final String TEST_FILE_INST_ARGS_KEY = "testFile"; 77 78 /** instrumentation test runner argument key used for individual test timeout */ 79 static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec"; 80 81 /** default timeout for tests collection */ 82 static final long TEST_COLLECTION_TIMEOUT_MS = 2 * 60 * 1000; 83 84 @Option( 85 name = "package", 86 shortName = 'p', 87 description = "The manifest package name of the Android test application to run.", 88 importance = Importance.IF_UNSET 89 ) 90 private String mPackageName = null; 91 92 @Option(name = "runner", 93 description="The instrumentation test runner class name to use. Will try to determine " 94 + "automatically if it is not specified.") 95 private String mRunnerName = null; 96 97 @Option(name = "class", shortName = 'c', 98 description="The test class name to run.") 99 private String mTestClassName = null; 100 101 @Option(name = "method", shortName = 'm', 102 description="The test method name to run.") 103 private String mTestMethodName = null; 104 105 @Option(name = "test-package", 106 description="Only run tests within this specific java package. " + 107 "Will be ignored if --class is set.") 108 private String mTestPackageName = null; 109 110 /** 111 * @deprecated use shell-timeout or test-timeout option instead. 112 */ 113 @Deprecated 114 @Option(name = "timeout", 115 description="Deprecated - Use \"shell-timeout\" or \"test-timeout\" instead.") 116 private Integer mTimeout = null; 117 118 @Option( 119 name = "shell-timeout", 120 description = 121 "The defined timeout (in milliseconds) is used as a maximum waiting time when " 122 + "expecting the command output from the device. At any time, if the shell " 123 + "command does not output anything for a period longer than defined " 124 + "timeout the TF run terminates. For no timeout, set to 0.", 125 isTimeVal = true 126 ) 127 private long mShellTimeout = 10 * 60 * 1000L; // default to 10 minutes 128 129 @Option( 130 name = "test-timeout", 131 description = 132 "Sets timeout (in milliseconds) that will be applied to each test. In the event of " 133 + "a test timeout, it will log the results and proceed with executing the " 134 + "next test. For no timeout, set to 0.", 135 isTimeVal = true 136 ) 137 private long mTestTimeout = 5 * 60 * 1000L; // default to 5 minutes 138 139 @Option( 140 name = "max-timeout", 141 description = 142 "Sets the max timeout for the instrumentation to terminate. " 143 + "For no timeout, set to 0.", 144 isTimeVal = true 145 ) 146 private long mMaxTimeout = 0l; 147 148 @Option(name = "size", 149 description="Restrict test to a specific test size.") 150 private String mTestSize = null; 151 152 @Option(name = "rerun", 153 description = "Rerun unexecuted tests individually on same device if test run " + 154 "fails to complete.") 155 private boolean mIsRerunMode = true; 156 157 @Option(name = "resume", 158 description = "Schedule unexecuted tests for resumption on another device " + 159 "if first device becomes unavailable.") 160 private boolean mIsResumeMode = false; 161 162 @Option(name = "install-file", 163 description="Optional file path to apk file that contains the tests.") 164 private File mInstallFile = null; 165 166 @Option(name = "run-name", 167 description="Optional custom test run name to pass to listener. " + 168 "If unspecified, will use package name.") 169 private String mRunName = null; 170 171 @Option( 172 name = "instrumentation-arg", 173 description = "Additional instrumentation arguments to provide." 174 ) 175 private final Map<String, String> mInstrArgMap = new HashMap<String, String>(); 176 177 @Option(name = "bugreport-on-failure", description = "Sets which failed testcase events " + 178 "cause a bugreport to be collected. a bugreport after failed testcases. Note that " + 179 "there is _no feedback mechanism_ between the test runner and the bugreport " + 180 "collector, so use the EACH setting with due caution.") 181 private BugreportCollector.Freq mBugreportFrequency = null; 182 183 @Option( 184 name = "bugreport-on-run-failure", 185 description = "Take a bugreport if the instrumentation finish with a run failure" 186 ) 187 private boolean mBugreportOnRunFailure = false; 188 189 @Option(name = "screenshot-on-failure", description = "Take a screenshot on every test failure") 190 private boolean mScreenshotOnFailure = false; 191 192 @Option(name = "logcat-on-failure", description = 193 "take a logcat snapshot on every test failure.") 194 private boolean mLogcatOnFailure = false; 195 196 @Option(name = "logcat-on-failure-size", description = 197 "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " + 198 "Should be an amount that can comfortably fit in memory.") 199 private int mMaxLogcatBytes = 500 * 1024; // 500K 200 201 @Option(name = "rerun-from-file", description = 202 "Use test file instead of separate adb commands for each test " + 203 "when re-running instrumentations for tests that failed to run in previous attempts. ") 204 private boolean mReRunUsingTestFile = false; 205 206 @Option(name = "rerun-from-file-attempts", description = 207 "Max attempts to rerun tests from file. -1 means rerun from file infinitely.") 208 private int mReRunUsingTestFileAttempts = -1; 209 210 @Option(name = "fallback-to-serial-rerun", description = 211 "Rerun tests serially after rerun from file failed.") 212 private boolean mFallbackToSerialRerun = true; 213 214 @Option(name = "reboot-before-rerun", description = 215 "Reboot a device before re-running instrumentations.") 216 private boolean mRebootBeforeReRun = false; 217 218 @Option(name = AbiFormatter.FORCE_ABI_STRING, 219 description = AbiFormatter.FORCE_ABI_DESCRIPTION, 220 importance = Importance.IF_UNSET) 221 private String mForceAbi = null; 222 223 @Option(name = "collect-tests-only", 224 description = "Only invoke the instrumentation to collect list of applicable test " 225 + "cases. All test run callbacks will be triggered, but test execution will " 226 + "not be actually carried out.") 227 private boolean mCollectTestsOnly = false; 228 229 @Option( 230 name = "collect-tests-timeout", 231 description = "Timeout for the tests collection operation.", 232 isTimeVal = true 233 ) 234 private long mCollectTestTimeout = TEST_COLLECTION_TIMEOUT_MS; 235 236 @Option( 237 name = "debug", 238 description = 239 "Wait for debugger before instrumentation starts. Note " 240 + "that this should only be used for local debugging, not suitable for automated runs." 241 ) 242 protected boolean mDebug = false; 243 244 @Option( 245 name = "coverage", 246 description = 247 "Collect code coverage for this test run. Note that the build under test must be a " 248 + "coverage build or else this will fail." 249 ) 250 private boolean mCoverage = false; 251 252 @Option( 253 name = "enforce-ajur-format", 254 description = "Whether or not enforcing the AJUR instrumentation output format" 255 ) 256 private boolean mShouldEnforceFormat = false; 257 258 @Option( 259 name = "hidden-api-checks", 260 description = 261 "If set to false, the '--no-hidden-api-checks' flag will be passed to the am " 262 + "instrument command. Only works for P or later." 263 ) 264 private boolean mHiddenApiChecks = true; 265 266 private IAbi mAbi = null; 267 268 private Collection<String> mInstallArgs = new ArrayList<>(); 269 270 private ITestDevice mDevice = null; 271 272 private IRemoteAndroidTestRunner mRunner; 273 274 private Collection<TestDescription> mTestsToRun = null; 275 276 private String mCoverageTarget = null; 277 278 private String mTestFilePathOnDevice = null; 279 280 private ListInstrumentationParser mListInstrumentationParser = null; 281 282 private List<String> mExtraDeviceListener = new ArrayList<>(); 283 284 /** 285 * {@inheritDoc} 286 */ 287 @Override setDevice(ITestDevice device)288 public void setDevice(ITestDevice device) { 289 mDevice = device; 290 } 291 292 /** 293 * Set the Android manifest package to run. 294 */ setPackageName(String packageName)295 public void setPackageName(String packageName) { 296 mPackageName = packageName; 297 } 298 299 /** 300 * Optionally, set the Android instrumentation runner to use. 301 */ setRunnerName(String runnerName)302 public void setRunnerName(String runnerName) { 303 mRunnerName = runnerName; 304 } 305 306 /** 307 * Gets the Android instrumentation runner to be used. 308 */ getRunnerName()309 public String getRunnerName() { 310 return mRunnerName; 311 } 312 313 /** 314 * Optionally, set the test class name to run. 315 */ setClassName(String testClassName)316 public void setClassName(String testClassName) { 317 mTestClassName = testClassName; 318 } 319 320 /** 321 * Optionally, set the test method to run. 322 */ setMethodName(String testMethodName)323 public void setMethodName(String testMethodName) { 324 mTestMethodName = StringEscapeUtils.escapeShell(testMethodName); 325 } 326 327 /** 328 * Optionally, set the path to a file located on the device that should contain a list of line 329 * separated test classes and methods (format: com.foo.Class#method) to be run. 330 * If set, will automatically attempt to re-run tests using this test file 331 * via {@link InstrumentationFileTest} instead of executing separate adb commands for each 332 * remaining test via {@link InstrumentationSerialTest}" 333 */ setTestFilePathOnDevice(String testFilePathOnDevice)334 public void setTestFilePathOnDevice(String testFilePathOnDevice) { 335 mTestFilePathOnDevice = testFilePathOnDevice; 336 } 337 338 /** 339 * Optionally, set the test size to run. 340 */ setTestSize(String size)341 public void setTestSize(String size) { 342 mTestSize = size; 343 } 344 345 /** 346 * Get the Android manifest package to run. 347 */ getPackageName()348 public String getPackageName() { 349 return mPackageName; 350 } 351 352 /** 353 * Get the custom test run name that will be provided to listener 354 */ getRunName()355 public String getRunName() { 356 return mRunName; 357 } 358 359 /** 360 * Set the custom test run name that will be provided to listener 361 */ setRunName(String runName)362 public void setRunName(String runName) { 363 mRunName = runName; 364 } 365 366 /** 367 * Set the collection of tests that should be executed by this InstrumentationTest. 368 * 369 * @param tests the tests to run 370 */ setTestsToRun(Collection<TestDescription> tests)371 public void setTestsToRun(Collection<TestDescription> tests) { 372 mTestsToRun = tests; 373 } 374 375 /** 376 * Get the class name to run. 377 */ getClassName()378 String getClassName() { 379 return mTestClassName; 380 } 381 382 /** 383 * Get the test method to run. 384 */ getMethodName()385 String getMethodName() { 386 return mTestMethodName; 387 } 388 389 /** 390 * Get the path to a file that contains class#method combinations to be run 391 */ getTestFilePathOnDevice()392 String getTestFilePathOnDevice() { 393 return mTestFilePathOnDevice; 394 } 395 396 /** 397 * Get the test java package to run. 398 */ getTestPackageName()399 String getTestPackageName() { 400 return mTestPackageName; 401 } 402 403 /** 404 * Sets the test package filter. 405 * <p/> 406 * If non-null, only tests within the given java package will be executed. 407 * <p/> 408 * Will be ignored if a non-null value has been provided to {@link #setClassName(String)} 409 */ setTestPackageName(String testPackageName)410 public void setTestPackageName(String testPackageName) { 411 mTestPackageName = testPackageName; 412 } 413 414 /** 415 * Get the test size to run. Returns <code>null</code> if no size has been set. 416 */ getTestSize()417 String getTestSize() { 418 return mTestSize; 419 } 420 421 /** 422 * Optionally, set the maximum time (in milliseconds) expecting shell output from the device. 423 */ setShellTimeout(long timeout)424 public void setShellTimeout(long timeout) { 425 mShellTimeout = timeout; 426 } 427 428 /** Optionally, set the maximum time (in milliseconds) for each individual test run. */ setTestTimeout(long timeout)429 public void setTestTimeout(long timeout) { 430 mTestTimeout = timeout; 431 } 432 433 /** 434 * Set the coverage target of this test. 435 * <p/> 436 * Currently unused. This method is just present so coverageTarget can be later retrieved via 437 * {@link #getCoverageTarget()} 438 */ setCoverageTarget(String coverageTarget)439 public void setCoverageTarget(String coverageTarget) { 440 mCoverageTarget = coverageTarget; 441 } 442 443 /** 444 * Get the coverageTarget previously set via {@link #setCoverageTarget(String)}. 445 */ getCoverageTarget()446 public String getCoverageTarget() { 447 return mCoverageTarget; 448 } 449 450 /** 451 * Return <code>true</code> if rerun mode is on. 452 */ isRerunMode()453 boolean isRerunMode() { 454 return mIsRerunMode; 455 } 456 457 /** 458 * {@inheritDoc} 459 */ 460 @Override isResumable()461 public boolean isResumable() { 462 // hack to not resume if tests were never run 463 // TODO: fix this properly in TestInvocation 464 if (mTestsToRun == null) { 465 return false; 466 } 467 return mIsResumeMode; 468 } 469 470 /** 471 * Optionally, set the rerun mode. 472 */ setRerunMode(boolean rerun)473 public void setRerunMode(boolean rerun) { 474 mIsRerunMode = rerun; 475 } 476 477 /** 478 * Optionally, set the resume mode. 479 */ setResumeMode(boolean resume)480 public void setResumeMode(boolean resume) { 481 mIsResumeMode = resume; 482 } 483 484 /** 485 * Get the shell timeout in ms. 486 */ getShellTimeout()487 long getShellTimeout() { 488 return mShellTimeout; 489 } 490 491 /** Get the test timeout in ms. */ getTestTimeout()492 long getTestTimeout() { 493 return mTestTimeout; 494 } 495 496 /** Returns the max timeout set for the instrumentation. */ getMaxTimeout()497 public long getMaxTimeout() { 498 return mMaxTimeout; 499 } 500 501 /** 502 * Set the optional file to install that contains the tests. 503 * 504 * @param installFile the installable {@link File} 505 */ setInstallFile(File installFile)506 public void setInstallFile(File installFile) { 507 mInstallFile = installFile; 508 } 509 510 /** 511 * {@inheritDoc} 512 */ 513 @Override getDevice()514 public ITestDevice getDevice() { 515 return mDevice; 516 } 517 518 /** 519 * Set the max time in ms to allow for the 'max time to shell output response' when collecting 520 * tests. 521 * <p/> 522 * @deprecated This method is a no-op 523 */ 524 @Deprecated 525 @SuppressWarnings("unused") setCollectsTestsShellTimeout(int timeout)526 public void setCollectsTestsShellTimeout(int timeout) { 527 // no-op 528 } 529 530 /** 531 * Set the frequency with which to automatically collect bugreports after test failures. 532 * <p /> 533 * Note that there is _no feedback mechanism_ between the test runner and the bugreport 534 * collector, so use the EACH setting with due caution: if a large quantity of failures happen 535 * in rapid succession, the bugreport for a given one of the failures could end up being 536 * collected tens of minutes or hours after the respective failure occurred. 537 */ setBugreportFrequency(BugreportCollector.Freq freq)538 public void setBugreportFrequency(BugreportCollector.Freq freq) { 539 mBugreportFrequency = freq; 540 } 541 542 /** 543 * Add an argument to provide when running the instrumentation tests. 544 * 545 * @param key the argument name 546 * @param value the argument value 547 */ addInstrumentationArg(String key, String value)548 public void addInstrumentationArg(String key, String value) { 549 mInstrArgMap.put(key, value); 550 } 551 552 /** 553 * Retrieve the value of an argument to provide when running the instrumentation tests. 554 * 555 * @param key the argument name 556 * <p/> 557 * Exposed for testing 558 */ getInstrumentationArg(String key)559 String getInstrumentationArg(String key) { 560 if (mInstrArgMap.containsKey(key)) { 561 return mInstrArgMap.get(key); 562 } 563 return null; 564 } 565 566 /** 567 * Sets force-abi option. 568 * @param abi 569 */ setForceAbi(String abi)570 public void setForceAbi(String abi) { 571 mForceAbi = abi; 572 } 573 getForceAbi()574 public String getForceAbi() { 575 return mForceAbi; 576 } 577 578 /** Sets the --screenshot-on-failure option. */ setScreenshotOnFailure(boolean screenshotOnFailure)579 public void setScreenshotOnFailure(boolean screenshotOnFailure) { 580 mScreenshotOnFailure = screenshotOnFailure; 581 } 582 583 /** Sets the --logcat-on-failure option. */ setLogcatOnFailure(boolean logcatOnFailure)584 public void setLogcatOnFailure(boolean logcatOnFailure) { 585 mLogcatOnFailure = logcatOnFailure; 586 } 587 588 /** Sets the --logcat-on-failure-size option. */ setLogcatOnFailureSize(int logcatOnFailureSize)589 public void setLogcatOnFailureSize(int logcatOnFailureSize) { 590 mMaxLogcatBytes = logcatOnFailureSize; 591 } 592 593 /** Sets the --coverage option for testing. */ 594 @VisibleForTesting setCoverage(boolean coverageEnabled)595 void setCoverage(boolean coverageEnabled) { 596 mCoverage = coverageEnabled; 597 } 598 599 /** Sets the --rerun-from-file option. */ setReRunUsingTestFile(boolean reRunUsingTestFile)600 public void setReRunUsingTestFile(boolean reRunUsingTestFile) { 601 mReRunUsingTestFile = reRunUsingTestFile; 602 } 603 604 /** Sets the --fallback-to-serial-rerun option. */ setFallbackToSerialRerun(boolean reRunSerially)605 public void setFallbackToSerialRerun(boolean reRunSerially) { 606 mFallbackToSerialRerun = reRunSerially; 607 } 608 609 /** Sets the --reboot-before-rerun option. */ setRebootBeforeReRun(boolean rebootBeforeReRun)610 public void setRebootBeforeReRun(boolean rebootBeforeReRun) { 611 mRebootBeforeReRun = rebootBeforeReRun; 612 } 613 614 /** Allows to add more custom listeners to the runner */ addDeviceListener(List<String> extraListeners)615 public void addDeviceListener(List<String> extraListeners) { 616 mExtraDeviceListener.addAll(extraListeners); 617 } 618 619 /** 620 * @return the {@link IRemoteAndroidTestRunner} to use. 621 * @throws DeviceNotAvailableException 622 */ createRemoteAndroidTestRunner(String packageName, String runnerName, IDevice device)623 IRemoteAndroidTestRunner createRemoteAndroidTestRunner(String packageName, String runnerName, 624 IDevice device) throws DeviceNotAvailableException { 625 RemoteAndroidTestRunner runner = 626 new DefaultRemoteAndroidTestRunner(packageName, runnerName, device); 627 String abiName = resolveAbiName(); 628 String runOptions = ""; 629 // hidden-api-checks flag only exists in P and after. 630 if (!mHiddenApiChecks && getDevice().getApiLevel() >= 28) { 631 runOptions += "--no-hidden-api-checks "; 632 } 633 if (abiName != null) { 634 mInstallArgs.add(String.format("--abi %s", abiName)); 635 runOptions += String.format("--abi %s", abiName); 636 } 637 // Set the run options if any. 638 if (!runOptions.isEmpty()) { 639 runner.setRunOptions(runOptions); 640 } 641 642 runner.setEnforceTimeStamp(mShouldEnforceFormat); 643 return runner; 644 } 645 resolveAbiName()646 private String resolveAbiName() throws DeviceNotAvailableException { 647 if (mAbi != null && mForceAbi != null) { 648 throw new IllegalArgumentException("cannot specify both abi flags"); 649 } 650 String abiName = null; 651 if (mAbi != null) { 652 abiName = mAbi.getName(); 653 } else if (mForceAbi != null && !mForceAbi.isEmpty()) { 654 abiName = AbiFormatter.getDefaultAbi(mDevice, mForceAbi); 655 if (abiName == null) { 656 throw new RuntimeException( 657 String.format("Cannot find abi for force-abi %s", mForceAbi)); 658 } 659 } 660 return abiName; 661 } 662 663 /** 664 * Set the {@link ListInstrumentationParser}. 665 */ 666 @VisibleForTesting setListInstrumentationParser(ListInstrumentationParser listInstrumentationParser)667 void setListInstrumentationParser(ListInstrumentationParser listInstrumentationParser) { 668 mListInstrumentationParser = listInstrumentationParser; 669 } 670 671 /** 672 * Get the {@link ListInstrumentationParser} used to parse 'pm list instrumentation' queries. 673 */ getListInstrumentationParser()674 protected ListInstrumentationParser getListInstrumentationParser() { 675 if (mListInstrumentationParser == null) { 676 mListInstrumentationParser = new ListInstrumentationParser(); 677 } 678 return mListInstrumentationParser; 679 } 680 681 /** 682 * Query the device for a test runner to use. 683 * 684 * @return the first test runner name that matches the package or null if we don't find any. 685 * @throws DeviceNotAvailableException 686 */ queryRunnerName()687 protected String queryRunnerName() throws DeviceNotAvailableException { 688 ListInstrumentationParser parser = getListInstrumentationParser(); 689 getDevice().executeShellCommand("pm list instrumentation", parser); 690 691 for (InstrumentationTarget target : parser.getInstrumentationTargets()) { 692 if (mPackageName.equals(target.packageName)) { 693 return target.runnerName; 694 } 695 } 696 CLog.w("Unable to determine runner name for package: %s", mPackageName); 697 return null; 698 } 699 700 /** 701 * {@inheritDoc} 702 */ 703 @Override run(final ITestInvocationListener listener)704 public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException { 705 checkArgument(mDevice != null, "Device has not been set."); 706 checkArgument(mPackageName != null, "Package name has not been set."); 707 708 if (mRunnerName == null) { 709 setRunnerName(queryRunnerName()); 710 checkArgument( 711 mRunnerName != null, 712 "Runner name has not been set and no matching instrumentations were found."); 713 CLog.i("No runner name specified. Using: %s.", mRunnerName); 714 } 715 716 mRunner = createRemoteAndroidTestRunner(mPackageName, mRunnerName, mDevice.getIDevice()); 717 setRunnerArgs(mRunner); 718 if (mInstallFile != null) { 719 Assert.assertNull(mDevice.installPackage(mInstallFile, true, 720 mInstallArgs.toArray(new String[]{}))); 721 } 722 doTestRun(listener); 723 if (mInstallFile != null) { 724 mDevice.uninstallPackage(mPackageName); 725 } 726 } 727 setRunnerArgs(IRemoteAndroidTestRunner runner)728 protected void setRunnerArgs(IRemoteAndroidTestRunner runner) { 729 if (mTestClassName != null) { 730 if (mTestMethodName != null) { 731 runner.setMethodName(mTestClassName, mTestMethodName); 732 } else { 733 runner.setClassName(mTestClassName); 734 } 735 } else if (mTestPackageName != null) { 736 runner.setTestPackageName(mTestPackageName); 737 } 738 if (mTestFilePathOnDevice != null) { 739 addInstrumentationArg(TEST_FILE_INST_ARGS_KEY, mTestFilePathOnDevice); 740 } 741 if (mTestSize != null) { 742 runner.setTestSize(TestSize.getTestSize(mTestSize)); 743 } 744 addTimeoutsToRunner(runner); 745 if (mRunName != null) { 746 runner.setRunName(mRunName); 747 } 748 for (Map.Entry<String, String> argEntry : mInstrArgMap.entrySet()) { 749 runner.addInstrumentationArg(argEntry.getKey(), argEntry.getValue()); 750 } 751 } 752 753 /** 754 * Helper method to add test-timeout & shell-timeout timeouts to given runner 755 */ addTimeoutsToRunner(IRemoteAndroidTestRunner runner)756 private void addTimeoutsToRunner(IRemoteAndroidTestRunner runner) { 757 if (mTimeout != null) { 758 CLog.w("\"timeout\" argument is deprecated and should not be used! \"shell-timeout\"" 759 + " argument value is overwritten with %d ms", mTimeout); 760 setShellTimeout(mTimeout); 761 } 762 if (mTestTimeout < 0) { 763 throw new IllegalArgumentException( 764 String.format("test-timeout %d cannot be negative", mTestTimeout)); 765 } 766 if (mShellTimeout <= mTestTimeout) { 767 // set shell timeout to 110% of test timeout 768 mShellTimeout = mTestTimeout + mTestTimeout / 10; 769 CLog.w(String.format("shell-timeout should be larger than test-timeout %d; " 770 + "NOTE: extending shell-timeout to %d, please consider fixing this!", 771 mTestTimeout, mShellTimeout)); 772 } 773 runner.setMaxTimeToOutputResponse(mShellTimeout, TimeUnit.MILLISECONDS); 774 runner.setMaxTimeout(mMaxTimeout, TimeUnit.MILLISECONDS); 775 addInstrumentationArg(TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(mTestTimeout)); 776 } 777 778 /** 779 * Execute test run. 780 * 781 * @param listener the test result listener 782 * @throws DeviceNotAvailableException if device stops communicating 783 */ doTestRun(ITestInvocationListener listener)784 private void doTestRun(ITestInvocationListener listener) throws DeviceNotAvailableException { 785 // If this is a dry-run, just collect the tests and return 786 if (mCollectTestsOnly) { 787 checkState( 788 mTestsToRun == null, 789 "Tests to run should not be set explicitly when --collect-tests-only is set."); 790 791 // Use the actual listener to collect the tests, and print a error if this fails 792 Collection<TestDescription> collectedTests = collectTestsToRun(mRunner, listener); 793 if (collectedTests == null) { 794 CLog.e("Failed to collect tests for %s", mPackageName); 795 } else { 796 CLog.i("Collected %d tests for %s", collectedTests.size(), mPackageName); 797 } 798 return; 799 } 800 801 // If the tests to run weren't provided explicitly, collect them. 802 Collection<TestDescription> testsToRun = mTestsToRun; 803 if (testsToRun == null) { 804 // Don't notify the listener since it's not a real run. 805 testsToRun = collectTestsToRun(mRunner, null); 806 } 807 808 // Only set the debug flag after collecting tests. 809 if (mDebug) { 810 mRunner.setDebug(true); 811 } 812 if (mCoverage) { 813 mRunner.addInstrumentationArg("coverage", "true"); 814 } 815 listener = addBugreportListenerIfEnabled(listener); 816 listener = addLogcatListenerIfEnabled(listener); 817 listener = addScreenshotListenerIfEnabled(listener); 818 listener = addCoverageListenerIfEnabled(listener); 819 // Add the extra listeners only to the actual run and not the --collect-test-only one 820 if (!mExtraDeviceListener.isEmpty()) { 821 mRunner.addInstrumentationArg("listener", ArrayUtil.join(",", mExtraDeviceListener)); 822 } 823 824 if (testsToRun == null) { 825 // Failed to collect the tests or collection is off. Just try to run them all. 826 mDevice.runInstrumentationTests(mRunner, listener); 827 } else if (!testsToRun.isEmpty()) { 828 runWithRerun(listener, testsToRun); 829 } else { 830 CLog.i("No tests expected for %s, skipping", mPackageName); 831 } 832 } 833 834 /** 835 * Returns a listener that will collect bugreports, or the original {@code listener} if this 836 * feature is disabled. 837 */ addBugreportListenerIfEnabled(ITestInvocationListener listener)838 ITestInvocationListener addBugreportListenerIfEnabled(ITestInvocationListener listener) { 839 if (mBugreportFrequency != null) { 840 // Collect a bugreport after EACH/FIRST failed testcase 841 BugreportCollector.Predicate pred = new BugreportCollector.Predicate( 842 BugreportCollector.Relation.AFTER, 843 mBugreportFrequency, 844 BugreportCollector.Noun.FAILED_TESTCASE); 845 BugreportCollector collector = new BugreportCollector(listener, getDevice()); 846 collector.addPredicate(pred); 847 listener = collector; 848 } 849 return listener; 850 } 851 852 /** 853 * Returns a listener that will collect screenshots, or the original {@code listener} if this 854 * feature is disabled. 855 */ addScreenshotListenerIfEnabled(ITestInvocationListener listener)856 ITestInvocationListener addScreenshotListenerIfEnabled(ITestInvocationListener listener) { 857 if (mScreenshotOnFailure) { 858 listener = new FailedTestScreenshotGenerator(listener, getDevice()); 859 } 860 return listener; 861 } 862 863 /** 864 * Returns a listener that will collect logcat logs, or the original {@code listener} if this 865 * feature is disabled. 866 */ addLogcatListenerIfEnabled(ITestInvocationListener listener)867 ITestInvocationListener addLogcatListenerIfEnabled(ITestInvocationListener listener) { 868 if (mLogcatOnFailure) { 869 listener = new FailedTestLogcatGenerator(listener, getDevice(), mMaxLogcatBytes); 870 } 871 return listener; 872 } 873 874 /** 875 * Returns a listener that will collect coverage measurements, or the original {@code listener} 876 * if this feature is disabled. 877 */ addCoverageListenerIfEnabled(ITestInvocationListener listener)878 ITestInvocationListener addCoverageListenerIfEnabled(ITestInvocationListener listener) { 879 if (mCoverage) { 880 listener = new CodeCoverageListener(getDevice(), listener); 881 } 882 return listener; 883 } 884 885 /** 886 * Execute the test run, but re-run incomplete tests individually if run fails to complete. 887 * 888 * @param listener the {@link ITestInvocationListener} 889 * @param expectedTests the full set of expected tests in this run. 890 */ runWithRerun( final ITestInvocationListener listener, Collection<TestDescription> expectedTests)891 private void runWithRerun( 892 final ITestInvocationListener listener, Collection<TestDescription> expectedTests) 893 throws DeviceNotAvailableException { 894 CollectingTestListener testTracker = new CollectingTestListener(); 895 mDevice.runInstrumentationTests( 896 mRunner, 897 // Use a crash forwarder to get stacks from logcat when crashing. 898 new LogcatCrashResultForwarder(getDevice(), listener, testTracker) { 899 @Override 900 public void testRunStarted(String runName, int testCount) { 901 // In case of crash, run will attempt to report with 0 902 if (testCount == 0 && !expectedTests.isEmpty()) { 903 CLog.e( 904 "Run reported 0 tests while we collected %s", 905 expectedTests.size()); 906 super.testRunStarted(runName, expectedTests.size()); 907 } else { 908 super.testRunStarted(runName, testCount); 909 } 910 } 911 }); 912 TestRunResult testRun = testTracker.getCurrentRunResults(); 913 if (testRun.isRunFailure() || !testRun.getCompletedTests().containsAll(expectedTests)) { 914 if (mBugreportOnRunFailure) { 915 // Capture a bugreport to help with the failure. 916 String name = (mTestClassName != null) ? mTestClassName : mPackageName; 917 boolean res = 918 mDevice.logBugreport( 919 String.format("bugreport-on-run-failure-%s", name), listener); 920 if (!res) { 921 CLog.e( 922 "Failed to capture a bugreport for the run failure of '%s'", 923 testRun.getName()); 924 } 925 } 926 // Don't re-run any completed tests, unless this is a coverage run. 927 if (!mCoverage) { 928 expectedTests.removeAll(testTracker.getCurrentRunResults().getCompletedTests()); 929 } 930 rerunTests(expectedTests, listener); 931 } 932 } 933 934 /** 935 * Rerun any <var>mRemainingTests</var> 936 * 937 * @param listener the {@link ITestInvocationListener} 938 * @throws DeviceNotAvailableException 939 */ rerunTests( Collection<TestDescription> expectedTests, final ITestInvocationListener listener)940 private void rerunTests( 941 Collection<TestDescription> expectedTests, final ITestInvocationListener listener) 942 throws DeviceNotAvailableException { 943 if (mRebootBeforeReRun) { 944 mDevice.reboot(); 945 } 946 947 IRemoteTest testReRunner = null; 948 try { 949 testReRunner = getTestReRunner(expectedTests); 950 } catch (ConfigurationException e) { 951 CLog.e("Failed to create test runner: %s", e.getMessage()); 952 return; 953 } 954 955 testReRunner.run(listener); 956 } 957 958 @VisibleForTesting getTestReRunner(Collection<TestDescription> tests)959 IRemoteTest getTestReRunner(Collection<TestDescription> tests) throws ConfigurationException { 960 if (mReRunUsingTestFile) { 961 return new InstrumentationFileTest( 962 this, tests, mFallbackToSerialRerun, mReRunUsingTestFileAttempts); 963 } else { 964 // Since the same runner is reused we must ensure TEST_FILE_INST_ARGS_KEY is not set. 965 // Otherwise, the runner will attempt to execute tests from file. 966 mInstrArgMap.remove(TEST_FILE_INST_ARGS_KEY); 967 return new InstrumentationSerialTest(this, tests); 968 } 969 } 970 971 /** 972 * Collect the list of tests that should be executed by this test run. 973 * 974 * <p>This will be done by executing the test run in 'logOnly' mode, and recording the list of 975 * tests. 976 * 977 * @param runner the {@link IRemoteAndroidTestRunner} to use to run the tests. 978 * @return a {@link Collection} of {@link TestDescription}s that represent all tests to be 979 * executed by this run 980 * @throws DeviceNotAvailableException 981 */ collectTestsToRun( final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)982 private Collection<TestDescription> collectTestsToRun( 983 final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener) 984 throws DeviceNotAvailableException { 985 if (isRerunMode()) { 986 Log.d(LOG_TAG, String.format("Collecting test info for %s on device %s", 987 mPackageName, mDevice.getSerialNumber())); 988 runner.setTestCollection(true); 989 // always explicitly set debug to false when collecting tests 990 runner.setDebug(false); 991 // try to collect tests multiple times, in case device is temporarily not available 992 // on first attempt 993 Collection<TestDescription> tests = collectTestsAndRetry(runner, listener); 994 // done with "logOnly" mode, restore proper test timeout before real test execution 995 addTimeoutsToRunner(runner); 996 runner.setTestCollection(false); 997 return tests; 998 } 999 return null; 1000 } 1001 1002 /** 1003 * Performs the actual work of collecting tests, making multiple attempts if necessary 1004 * 1005 * @param runner the {@link IRemoteAndroidTestRunner} that will be used for the instrumentation 1006 * @param listener the {ITestInvocationListener} where to report results, can be null if we are 1007 * not reporting the results to the main invocation and simply collecting tests. 1008 * @return the collection of tests, or <code>null</code> if tests could not be collected 1009 * @throws DeviceNotAvailableException if communication with the device was lost 1010 */ 1011 @VisibleForTesting collectTestsAndRetry( final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)1012 Collection<TestDescription> collectTestsAndRetry( 1013 final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener) 1014 throws DeviceNotAvailableException { 1015 boolean communicationFailure = false; 1016 for (int i=0; i < COLLECT_TESTS_ATTEMPTS; i++) { 1017 CollectingTestListener collector = new CollectingTestListener(); 1018 boolean instrResult = false; 1019 // We allow to override the ddmlib default timeout for collection of tests. 1020 runner.setMaxTimeToOutputResponse(mCollectTestTimeout, TimeUnit.MILLISECONDS); 1021 if (listener == null) { 1022 instrResult = mDevice.runInstrumentationTests(runner, collector); 1023 } else { 1024 instrResult = mDevice.runInstrumentationTests(runner, collector, listener); 1025 } 1026 TestRunResult runResults = collector.getCurrentRunResults(); 1027 if (!instrResult || !runResults.isRunComplete()) { 1028 // communication failure with device, retry 1029 Log.w(LOG_TAG, String.format( 1030 "No results when collecting tests to run for %s on device %s. Retrying", 1031 mPackageName, mDevice.getSerialNumber())); 1032 communicationFailure = true; 1033 } else if (runResults.isRunFailure()) { 1034 // not a communication failure, but run still failed. 1035 // TODO: should retry be attempted 1036 CLog.w("Run failure %s when collecting tests to run for %s on device %s.", 1037 runResults.getRunFailureMessage(), mPackageName, 1038 mDevice.getSerialNumber()); 1039 if (mShouldEnforceFormat 1040 && InstrumentationResultParser.INVALID_OUTPUT_ERR_MSG.equals( 1041 runResults.getRunFailureMessage())) { 1042 throw new RuntimeException(InstrumentationResultParser.INVALID_OUTPUT_ERR_MSG); 1043 } 1044 return null; 1045 } else { 1046 // success! 1047 return runResults.getCompletedTests(); 1048 } 1049 } 1050 if (communicationFailure) { 1051 // TODO: find a better way to handle this 1052 // throwing DeviceUnresponsiveException is not always ideal because a misbehaving 1053 // instrumentation can hang, even though device is responsive. Would be nice to have 1054 // a louder signal for this situation though than just logging an error 1055 // throw new DeviceUnresponsiveException(String.format( 1056 // "Communication failure when attempting to collect tests %s on device %s", 1057 // mPackageName, mDevice.getSerialNumber())); 1058 CLog.w("Ignoring repeated communication failure when collecting tests %s for device %s", 1059 mPackageName, mDevice.getSerialNumber()); 1060 } 1061 CLog.e("Failed to collect tests to run for %s on device %s.", 1062 mPackageName, mDevice.getSerialNumber()); 1063 return null; 1064 } 1065 1066 /** A {@link ResultForwarder} that will forward a screenshot on test failures. */ 1067 @VisibleForTesting 1068 static class FailedTestScreenshotGenerator extends ResultForwarder { 1069 private ITestDevice mDevice; 1070 FailedTestScreenshotGenerator(ITestInvocationListener listener, ITestDevice device)1071 public FailedTestScreenshotGenerator(ITestInvocationListener listener, 1072 ITestDevice device) { 1073 super(listener); 1074 mDevice = device; 1075 } 1076 1077 @Override testFailed(TestDescription test, String trace)1078 public void testFailed(TestDescription test, String trace) { 1079 try { 1080 InputStreamSource screenSource = mDevice.getScreenshot(); 1081 super.testLog(String.format("screenshot-%s_%s", test.getClassName(), 1082 test.getTestName()), LogDataType.PNG, screenSource); 1083 StreamUtil.cancel(screenSource); 1084 } catch (DeviceNotAvailableException e) { 1085 // TODO: rethrow this somehow 1086 CLog.e("Device %s became unavailable while capturing screenshot, %s", 1087 mDevice.getSerialNumber(), e.toString()); 1088 } 1089 1090 super.testFailed(test, trace); 1091 } 1092 } 1093 1094 /** A {@link ResultForwarder} that will forward a logcat snapshot on each failed test. */ 1095 @VisibleForTesting 1096 static class FailedTestLogcatGenerator extends ResultForwarder { 1097 private ITestDevice mDevice; 1098 private int mNumLogcatBytes; 1099 private Map<TestDescription, Long> mMapStartTime = new HashMap<TestDescription, Long>(); 1100 FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device, int maxLogcatBytes)1101 public FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device, 1102 int maxLogcatBytes) { 1103 super(listener); 1104 mDevice = device; 1105 mNumLogcatBytes = maxLogcatBytes; 1106 } 1107 getMaxSize()1108 int getMaxSize() { 1109 return mNumLogcatBytes; 1110 } 1111 1112 @Override testStarted(TestDescription test)1113 public void testStarted(TestDescription test) { 1114 super.testStarted(test); 1115 // capture the starting date of the tests. 1116 try { 1117 mMapStartTime.put(test, mDevice.getDeviceDate()); 1118 } catch (DeviceNotAvailableException e) { 1119 // For convenience of interface we catch here, test will mostlikely throw it again 1120 // and it will be properly handle (recovery, etc.) 1121 CLog.e(e); 1122 mMapStartTime.put(test, 0l); 1123 } 1124 } 1125 1126 @Override testFailed(TestDescription test, String trace)1127 public void testFailed(TestDescription test, String trace) { 1128 super.testFailed(test, trace); 1129 captureLog(test); 1130 } 1131 1132 @Override testAssumptionFailure(TestDescription test, String trace)1133 public void testAssumptionFailure(TestDescription test, String trace) { 1134 super.testAssumptionFailure(test, trace); 1135 captureLog(test); 1136 } 1137 captureLog(TestDescription test)1138 private void captureLog(TestDescription test) { 1139 // if we can, capture starting the beginning of the test only to be more precise 1140 long startTime = 0; 1141 if (mMapStartTime.containsKey(test)) { 1142 startTime = mMapStartTime.remove(test); 1143 } 1144 if (startTime != 0) { 1145 try (InputStreamSource logSource = mDevice.getLogcatSince(startTime)) { 1146 super.testLog( 1147 String.format("logcat-%s_%s", test.getClassName(), test.getTestName()), 1148 LogDataType.TEXT, 1149 logSource); 1150 } 1151 } else { 1152 try (InputStreamSource logSource = mDevice.getLogcat(mNumLogcatBytes)) { 1153 super.testLog( 1154 String.format("logcat-%s_%s", test.getClassName(), test.getTestName()), 1155 LogDataType.TEXT, 1156 logSource); 1157 } 1158 } 1159 } 1160 } 1161 1162 /** 1163 * {@inheritDoc} 1164 */ 1165 @Override setCollectTestsOnly(boolean shouldCollectTest)1166 public void setCollectTestsOnly(boolean shouldCollectTest) { 1167 mCollectTestsOnly = shouldCollectTest; 1168 } 1169 1170 @Override setAbi(IAbi abi)1171 public void setAbi(IAbi abi) { 1172 mAbi = abi; 1173 } 1174 1175 @Override getAbi()1176 public IAbi getAbi() { 1177 return mAbi; 1178 } 1179 1180 /** Set True if we enforce the AJUR output format of instrumentation. */ setEnforceFormat(boolean enforce)1181 public void setEnforceFormat(boolean enforce) { 1182 mShouldEnforceFormat = enforce; 1183 } 1184 1185 /** 1186 * Set the instrumentation debug setting. 1187 * 1188 * @param debug boolean value to set the instrumentation debug setting to. 1189 */ setDebug(boolean debug)1190 public void setDebug(boolean debug) { 1191 mDebug = debug; 1192 } 1193 1194 /** 1195 * Get the instrumentation debug setting. 1196 * 1197 * @return The boolean debug setting. 1198 */ getDebug()1199 public boolean getDebug() { 1200 return mDebug; 1201 } 1202 } 1203