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 package com.android.tradefed.testtype; 17 18 import com.android.tradefed.config.ConfigurationException; 19 import com.android.tradefed.config.IConfiguration; 20 import com.android.tradefed.config.IConfigurationReceiver; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.config.Option.Importance; 23 import com.android.tradefed.config.OptionClass; 24 import com.android.tradefed.config.OptionCopier; 25 import com.android.tradefed.device.DeviceNotAvailableException; 26 import com.android.tradefed.device.ITestDevice; 27 import com.android.tradefed.device.metric.CollectorHelper; 28 import com.android.tradefed.device.metric.IMetricCollector; 29 import com.android.tradefed.device.metric.IMetricCollectorReceiver; 30 import com.android.tradefed.invoker.TestInformation; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.result.BugreportCollector; 33 import com.android.tradefed.result.ITestInvocationListener; 34 import com.android.tradefed.result.TestDescription; 35 import com.android.tradefed.result.TestRunResult; 36 import com.android.tradefed.testtype.retry.IAutoRetriableTest; 37 import com.android.tradefed.util.AbiFormatter; 38 import com.android.tradefed.util.ListInstrumentationParser; 39 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget; 40 41 import java.util.ArrayList; 42 import java.util.Collection; 43 import java.util.HashMap; 44 import java.util.LinkedList; 45 import java.util.List; 46 import java.util.Map; 47 48 /** Runs all instrumentation found on current device. */ 49 @OptionClass(alias = "installed-instrumentation") 50 public class InstalledInstrumentationsTest 51 implements IDeviceTest, 52 IShardableTest, 53 IMetricCollectorReceiver, 54 IAutoRetriableTest, 55 IConfigurationReceiver { 56 57 private static final String PM_LIST_CMD = "pm list instrumentation"; 58 private static final String LINE_SEPARATOR = "\\r?\\n"; 59 60 private ITestDevice mDevice; 61 62 @Option(name = "shell-timeout", 63 description="The defined timeout (in milliseconds) is used as a maximum waiting time " 64 + "when expecting the command output from the device. At any time, if the " 65 + "shell command does not output anything for a period longer than defined " 66 + "timeout the TF run terminates. For no timeout, set to 0.") 67 private long mShellTimeout = 10 * 60 * 1000; // default to 10 minutes 68 69 @Option(name = "test-timeout", 70 description="Sets timeout (in milliseconds) that will be applied to each test. In the " 71 + "event of a test timeout it will log the results and proceed with executing " 72 + "the next test. For no timeout, set to 0.") 73 private int mTestTimeout = 5 * 60 * 1000; // default to 5 minutes 74 75 @Option(name = "size", 76 description = "Restrict tests to a specific test size. " + 77 "One of 'small', 'medium', 'large'", 78 importance = Importance.IF_UNSET) 79 private String mTestSize = null; 80 81 @Option(name = "runner", 82 description = "Restrict tests executed to a specific instrumentation class runner. " + 83 "Installed instrumentations that do not have this runner will be skipped.") 84 private String mRunner = null; 85 86 @Option(name = "rerun", 87 description = "Rerun unexecuted tests individually on same device if test run " + 88 "fails to complete.") 89 private boolean mIsRerunMode = true; 90 91 @Option(name = "resume", 92 description = "Schedule unexecuted tests for resumption on another device " + 93 "if first device becomes unavailable.") 94 private boolean mIsResumeMode = false; 95 96 /** @deprecated delete when we are sure it's not used anywhere. */ 97 @Deprecated 98 @Option(name = "send-coverage", description = "Send coverage target info to test listeners.") 99 private boolean mSendCoverage = false; 100 101 @Option(name = "bugreport-on-failure", description = "Sets which failed testcase events " + 102 "cause a bugreport to be collected. a bugreport after failed testcases. Note that " + 103 "there is _no feedback mechanism_ between the test runner and the bugreport " + 104 "collector, so use the EACH setting with due caution.") 105 private BugreportCollector.Freq mBugreportFrequency = null; 106 107 @Option( 108 name = "bugreport-on-run-failure", 109 description = "Take a bugreport if the instrumentation finish with a run failure" 110 ) 111 private boolean mBugreportOnRunFailure = false; 112 113 @Option(name = "screenshot-on-failure", description = "Take a screenshot on every test failure") 114 private boolean mScreenshotOnFailure = false; 115 116 @Option(name = "logcat-on-failure", description = 117 "take a logcat snapshot on every test failure.") 118 private boolean mLogcatOnFailures = false; 119 120 @Option(name = "logcat-on-failure-size", description = 121 "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " + 122 "Should be an amount that can comfortably fit in memory.") 123 private int mMaxLogcatBytes = 500 * 1024; // 500K 124 125 @Option(name = "class", 126 description = "Only run tests in specified class") 127 private String mTestClass = null; 128 129 @Option(name = "package", 130 description = 131 "Only run tests within this specific java package. Will be ignored if --class is set.") 132 private String mTestPackageName = null; 133 134 @Option(name = "instrumentation-arg", 135 description = "Additional instrumentation arguments to provide.") 136 private Map<String, String> mInstrArgMap = new HashMap<String, String>(); 137 138 @Option( 139 name = "rerun-from-file", 140 description = 141 "Use test file instead of separate adb commands for each test " 142 + "when re-running instrumentations for tests that failed to run in " 143 + "previous attempts. " 144 ) 145 private boolean mReRunUsingTestFile = true; 146 147 @Option(name = "rerun-from-file-attempts", description = 148 "Max attempts to rerun tests from file. -1 means rerun from file infinitely.") 149 private int mReRunUsingTestFileAttempts = -1; 150 151 @Option( 152 name = "fallback-to-serial-rerun", 153 description = "Rerun tests serially after rerun from file failed." 154 ) 155 private boolean mFallbackToSerialRerun = false; 156 157 @Option(name = "reboot-before-rerun", description = 158 "Reboot a device before re-running instrumentations.") 159 private boolean mRebootBeforeReRun = false; 160 161 @Option(name = "disable", description = 162 "Disable the test by setting this flag to true.") 163 private boolean mDisable = false; 164 165 @Option( 166 name = "coverage", 167 description = 168 "Collect code coverage for this test run. Note that the build under test must be a " 169 + "coverage build or else this will fail." 170 ) 171 private boolean mCoverage = false; 172 173 @Option( 174 name = "hidden-api-checks", 175 description = 176 "If set to false, the '--no-hidden-api-checks' flag will be passed to the am " 177 + "instrument command. Only works for P or later." 178 ) 179 private boolean mHiddenApiChecks = true; 180 181 @Option( 182 name = "test-api-access", 183 description = 184 "If set to false and hidden API checks are enabled, the '--no-test-api-access'" 185 + " flag will be passed to the am instrument command." 186 + " Only works for R or later.") 187 private boolean mTestApiAccess = true; 188 189 @Option( 190 name = "isolated-storage", 191 description = 192 "If set to false, the '--no-isolated-storage' flag will be passed to the am " 193 + "instrument command. Only works for Q or later." 194 ) 195 private boolean mIsolatedStorage = true; 196 197 @Option( 198 name = "window-animation", 199 description = 200 "If set to false, the '--no-window-animation' flag will be passed to the am " 201 + "instrument command. Only works for ICS or later." 202 ) 203 private boolean mWindowAnimation = true; 204 205 @Option( 206 name = "disable-duplicate-test-check", 207 description = 208 "If set to true, it will not check that a method is only run once by a " 209 + "given instrumentation.") 210 private boolean mDisableDuplicateCheck = false; 211 212 @Option( 213 name = "create-instrumentation-tests", 214 description = 215 "Create InstrumentationTest type rather than more recent AndroidJUnitTest.") 216 private boolean mDowngradeInstrumentation = false; 217 218 private int mTotalShards = 0; 219 private int mShardIndex = 0; 220 private List<IMetricCollector> mMetricCollectorList = new ArrayList<>(); 221 private IConfiguration mConfiguration; 222 223 private List<InstrumentationTest> mTests = null; 224 private Map<String, List<TestDescription>> mRunTestsFailureMap = null; 225 226 @Option(name = AbiFormatter.FORCE_ABI_STRING, 227 description = AbiFormatter.FORCE_ABI_DESCRIPTION, 228 importance = Importance.IF_UNSET) 229 private String mForceAbi = null; 230 231 @Override shouldRetry(int attemptJustExecuted, List<TestRunResult> previousResults)232 public boolean shouldRetry(int attemptJustExecuted, List<TestRunResult> previousResults) 233 throws DeviceNotAvailableException { 234 boolean retry = false; 235 mRunTestsFailureMap = new HashMap<>(); 236 for (TestRunResult run : previousResults) { 237 if (run == null) { 238 continue; 239 } 240 if (run.isRunFailure()) { 241 retry = true; 242 // Retry the full run in case of run failure 243 mRunTestsFailureMap.put(run.getName(), null); 244 } else if (run.hasFailedTests()) { 245 retry = true; 246 mRunTestsFailureMap.put( 247 run.getName(), new ArrayList<TestDescription>(run.getFailedTests())); 248 } 249 } 250 251 if (!retry) { 252 CLog.d("No test run or test case failures. No need to retry."); 253 mRunTestsFailureMap = null; 254 } 255 return retry; 256 } 257 258 @Override setConfiguration(IConfiguration configuration)259 public void setConfiguration(IConfiguration configuration) { 260 mConfiguration = configuration; 261 } 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override getDevice()267 public ITestDevice getDevice() { 268 return mDevice; 269 } 270 271 /** 272 * {@inheritDoc} 273 */ 274 @Override setDevice(ITestDevice device)275 public void setDevice(ITestDevice device) { 276 mDevice = device; 277 } 278 279 /** 280 * Gets the list of {@link InstrumentationTest}s contained within. 281 * <p/> 282 * Exposed for unit testing. 283 */ getTests()284 List<InstrumentationTest> getTests() { 285 return mTests; 286 } 287 288 /** 289 * Sets the number of total shards this test should be split into. 290 * <p/> 291 * Exposed for unit testing. 292 */ setTotalShards(int totalShards)293 void setTotalShards(int totalShards) { 294 mTotalShards = totalShards; 295 } 296 297 /** 298 * Sets the shard index number of this test. 299 * <p/> 300 * Exposed for unit testing. 301 */ setShardIndex(int shardIndex)302 void setShardIndex(int shardIndex) { 303 mShardIndex = shardIndex; 304 } 305 306 /** {@inheritDoc} */ 307 @Override run(TestInformation testInfo, ITestInvocationListener listener)308 public void run(TestInformation testInfo, ITestInvocationListener listener) 309 throws DeviceNotAvailableException { 310 if (getDevice() == null) { 311 throw new IllegalArgumentException("Device has not been set"); 312 } 313 314 if (mDisable) { 315 return; 316 } 317 buildTests(); 318 doRun(testInfo, listener); 319 } 320 321 /** 322 * Build the list of tests to run from the device, if not done already. Note: Can be called 323 * multiple times in case of resumed runs. 324 * @throws DeviceNotAvailableException 325 */ buildTests()326 private void buildTests() throws DeviceNotAvailableException { 327 if (mTests == null) { 328 329 ListInstrumentationParser parser = new ListInstrumentationParser(); 330 String pmListOutput = getDevice().executeShellCommand(PM_LIST_CMD); 331 String pmListLines[] = pmListOutput.split(LINE_SEPARATOR); 332 parser.processNewLines(pmListLines); 333 334 if (parser.getInstrumentationTargets().isEmpty()) { 335 throw new IllegalArgumentException(String.format( 336 "No instrumentations were found on device %s - <%s>", getDevice() 337 .getSerialNumber(), pmListOutput)); 338 } 339 340 int numUnshardedTests = 0; 341 mTests = new LinkedList<InstrumentationTest>(); 342 for (InstrumentationTarget target : parser.getInstrumentationTargets()) { 343 if (mRunner == null || mRunner.equals(target.runnerName)) { 344 // Some older instrumentations are not shardable. As a result, we should try to 345 // shard these instrumentations by APKs, rather than by test methods. 346 if (mTotalShards > 0 && !target.isShardable()) { 347 numUnshardedTests += 1; 348 if ((numUnshardedTests - 1) % mTotalShards != mShardIndex) { 349 continue; 350 } 351 } 352 List<IMetricCollector> collectors = 353 CollectorHelper.cloneCollectors(mMetricCollectorList); 354 InstrumentationTest t = createInstrumentationTest(); 355 try { 356 // Copies all current argument values to the new runner that will be 357 // used to actually run the tests. 358 OptionCopier.copyOptions(InstalledInstrumentationsTest.this, t); 359 } catch (ConfigurationException e) { 360 // Bail out rather than run tests with unexpected options 361 throw new RuntimeException("failed to copy instrumentation options", e); 362 } 363 // Pass the collectors to each instrumentation, which will take care of init 364 t.setMetricCollectors(collectors); 365 String targetPackageName = target.packageName; 366 t.setPackageName(targetPackageName); 367 if (mRunTestsFailureMap != null) { 368 // Don't retry if there was no failure in a particular instrumentation. 369 if (!mRunTestsFailureMap.containsKey(targetPackageName)) { 370 CLog.d("Skipping %s at retry.", targetPackageName); 371 continue; 372 } 373 // if possible reduce the scope of the retry to be more efficient. 374 if (t instanceof AndroidJUnitTest) { 375 AndroidJUnitTest filterable = (AndroidJUnitTest) t; 376 if (mRunTestsFailureMap.containsKey(targetPackageName)) { 377 List<TestDescription> tests = 378 mRunTestsFailureMap.get(targetPackageName); 379 if (tests != null) { 380 for (TestDescription test : tests) { 381 filterable.addIncludeFilter( 382 String.format( 383 "%s#%s", 384 test.getClassName(), 385 test.getTestNameWithoutParams())); 386 } 387 } 388 } 389 } 390 } 391 t.setRunnerName(target.runnerName); 392 t.setCoverageTarget(target.targetName); 393 if (mTotalShards > 0 && target.isShardable()) { 394 t.addInstrumentationArg("shardIndex", Integer.toString(mShardIndex)); 395 t.addInstrumentationArg("numShards", Integer.toString(mTotalShards)); 396 } 397 mTests.add(t); 398 } 399 } 400 } 401 } 402 403 /** 404 * Run the previously built tests. 405 * 406 * @param testInfo the {@link TestInformation} of the invocation. 407 * @param listener the {@link ITestInvocationListener} 408 * @throws DeviceNotAvailableException 409 */ doRun(TestInformation testInfo, ITestInvocationListener listener)410 private void doRun(TestInformation testInfo, ITestInvocationListener listener) 411 throws DeviceNotAvailableException { 412 while (!mTests.isEmpty()) { 413 InstrumentationTest test = mTests.get(0); 414 415 CLog.d("Running test %s on %s", test.getPackageName(), getDevice().getSerialNumber()); 416 417 test.setDevice(getDevice()); 418 test.setConfiguration(mConfiguration); 419 if (mTestClass != null) { 420 test.setClassName(mTestClass); 421 if (mTestPackageName != null) { 422 CLog.e( 423 "Ignoring --package option with value '%s' since it's incompatible " 424 + "with using --class at the same time.", 425 mTestPackageName); 426 } 427 } else if (mTestPackageName != null) { 428 test.setTestPackageName(mTestPackageName); 429 } 430 test.run(testInfo, listener); 431 // test completed, remove from list 432 mTests.remove(0); 433 } 434 mTests = null; 435 } 436 getShellTimeout()437 long getShellTimeout() { 438 return mShellTimeout; 439 } 440 getTestTimeout()441 int getTestTimeout() { 442 return mTestTimeout; 443 } 444 getTestSize()445 String getTestSize() { 446 return mTestSize; 447 } 448 449 /** 450 * Creates the {@link InstrumentationTest} to use. Exposed for unit testing. 451 */ createInstrumentationTest()452 InstrumentationTest createInstrumentationTest() { 453 // We do not know what kind of instrumentation we will find, so we don't enforce the ddmlib 454 // format for AndroidJUnitRunner. 455 InstrumentationTest test = null; 456 if (mDowngradeInstrumentation) { 457 test = new InstrumentationTest(); 458 } else { 459 test = new AndroidJUnitTest(); 460 } 461 test.setEnforceFormat(false); 462 return test; 463 } 464 465 @Override setMetricCollectors(List<IMetricCollector> collectors)466 public void setMetricCollectors(List<IMetricCollector> collectors) { 467 mMetricCollectorList = collectors; 468 } 469 470 /** {@inheritDoc} */ 471 @Override split(int shardCountHint)472 public Collection<IRemoteTest> split(int shardCountHint) { 473 if (shardCountHint > 1) { 474 Collection<IRemoteTest> shards = new ArrayList<>(shardCountHint); 475 for (int index = 0; index < shardCountHint; index++) { 476 shards.add(getTestShard(shardCountHint, index)); 477 } 478 return shards; 479 } 480 return null; 481 } 482 getTestShard(int shardCount, int shardIndex)483 private IRemoteTest getTestShard(int shardCount, int shardIndex) { 484 InstalledInstrumentationsTest shard = new InstalledInstrumentationsTest(); 485 try { 486 OptionCopier.copyOptions(this, shard); 487 } catch (ConfigurationException e) { 488 CLog.e("failed to copy instrumentation options: %s", e.getMessage()); 489 } 490 shard.mShardIndex = shardIndex; 491 shard.mTotalShards = shardCount; 492 return shard; 493 } 494 } 495