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