1 /* 2 * Copyright (C) 2015 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 com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 20 import com.android.tradefed.config.ConfigurationDescriptor; 21 import com.android.tradefed.config.ConfigurationException; 22 import com.android.tradefed.config.Option; 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.invoker.TestInformation; 28 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 31 import com.android.tradefed.result.FailureDescription; 32 import com.android.tradefed.result.FileInputStreamSource; 33 import com.android.tradefed.result.ITestInvocationListener; 34 import com.android.tradefed.result.LogDataType; 35 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 36 import com.android.tradefed.targetprep.BuildError; 37 import com.android.tradefed.targetprep.TargetSetupError; 38 import com.android.tradefed.targetprep.TestAppInstallSetup; 39 import com.android.tradefed.testtype.suite.params.InstantAppHandler; 40 import com.android.tradefed.util.ArrayUtil; 41 import com.android.tradefed.util.CommandResult; 42 import com.android.tradefed.util.FileUtil; 43 import com.android.tradefed.util.ListInstrumentationParser; 44 import com.android.tradefed.util.ResourceUtil; 45 46 import com.google.common.annotations.VisibleForTesting; 47 48 import org.junit.runner.notification.RunListener; 49 50 import java.io.File; 51 import java.io.IOException; 52 import java.lang.reflect.InvocationTargetException; 53 import java.util.ArrayList; 54 import java.util.Collection; 55 import java.util.Collections; 56 import java.util.HashMap; 57 import java.util.LinkedHashSet; 58 import java.util.List; 59 import java.util.Set; 60 import java.util.regex.Pattern; 61 import java.util.regex.PatternSyntaxException; 62 63 /** 64 * A Test that runs an instrumentation test package on given device using the 65 * android.support.test.runner.AndroidJUnitRunner. 66 */ 67 @OptionClass(alias = "android-junit") 68 public class AndroidJUnitTest extends InstrumentationTest 69 implements IRuntimeHintProvider, 70 ITestFileFilterReceiver, 71 ITestFilterReceiver, 72 ITestAnnotationFilterReceiver, 73 IShardableTest { 74 75 /** instrumentation test runner argument key used for including a class/test */ 76 private static final String INCLUDE_CLASS_INST_ARGS_KEY = "class"; 77 /** instrumentation test runner argument key used for excluding a class/test */ 78 private static final String EXCLUDE_CLASS_INST_ARGS_KEY = "notClass"; 79 /** instrumentation test runner argument key used for including a package */ 80 private static final String INCLUDE_PACKAGE_INST_ARGS_KEY = "package"; 81 /** instrumentation test runner argument key used for excluding a package */ 82 private static final String EXCLUDE_PACKAGE_INST_ARGS_KEY = "notPackage"; 83 /** instrumentation test runner argument key used for including a test regex */ 84 private static final String INCLUDE_REGEX_INST_ARGS_KEY = "tests_regex"; 85 /** instrumentation test runner argument key used for adding annotation filter */ 86 private static final String ANNOTATION_INST_ARGS_KEY = "annotation"; 87 /** instrumentation test runner argument key used for adding notAnnotation filter */ 88 private static final String NOT_ANNOTATION_INST_ARGS_KEY = "notAnnotation"; 89 /** instrumentation test runner argument used for adding testFile filter */ 90 private static final String TEST_FILE_INST_ARGS_KEY = "testFile"; 91 /** instrumentation test runner argument used for adding notTestFile filter */ 92 private static final String NOT_TEST_FILE_INST_ARGS_KEY = "notTestFile"; 93 /** instrumentation test runner argument used to specify the shardIndex of the test */ 94 private static final String SHARD_INDEX_INST_ARGS_KEY = "shardIndex"; 95 /** instrumentation test runner argument used to specify the total number of shards */ 96 private static final String NUM_SHARD_INST_ARGS_KEY = "numShards"; 97 /** 98 * instrumentation test runner argument used to enable the new {@link RunListener} order on 99 * device side. 100 */ 101 public static final String NEW_RUN_LISTENER_ORDER_KEY = "newRunListenerMode"; 102 103 public static final String USE_TEST_STORAGE_SERVICE = "useTestStorageService"; 104 105 /** Options from the collector side helper library. */ 106 public static final String INCLUDE_COLLECTOR_FILTER_KEY = "include-filter-group"; 107 108 public static final String EXCLUDE_COLLECTOR_FILTER_KEY = "exclude-filter-group"; 109 110 private static final String INCLUDE_FILE = "includes.txt"; 111 private static final String EXCLUDE_FILE = "excludes.txt"; 112 113 @Option(name = "runtime-hint", 114 isTimeVal=true, 115 description="The hint about the test's runtime.") 116 private long mRuntimeHint = 60000;// 1 minute 117 118 @Option( 119 name = "include-filter", 120 description = "The include filters of the test name to run.", 121 requiredForRerun = true) 122 private Set<String> mIncludeFilters = new LinkedHashSet<>(); 123 124 @Option( 125 name = "exclude-filter", 126 description = "The exclude filters of the test name to run.", 127 requiredForRerun = true) 128 private Set<String> mExcludeFilters = new LinkedHashSet<>(); 129 130 @Option( 131 name = "include-annotation", 132 description = "The annotation class name of the test name to run, can be repeated", 133 requiredForRerun = true) 134 private Set<String> mIncludeAnnotation = new LinkedHashSet<>(); 135 136 @Option( 137 name = "exclude-annotation", 138 description = "The notAnnotation class name of the test name to run, can be repeated", 139 requiredForRerun = true) 140 private Set<String> mExcludeAnnotation = new LinkedHashSet<>(); 141 142 @Option(name = "test-filter-dir", 143 description="The device directory path to which the test filtering files are pushed") 144 private String mTestFilterDir = "/data/local/tmp/ajur"; 145 146 @Option( 147 name = "test-storage-dir", 148 description = "The device directory path where test storage read files.") 149 private String mTestStorageInternalDir = "/sdcard/googletest/test_runfiles"; 150 151 @Option( 152 name = "use-test-storage", 153 description = 154 "If set to true, we will push filters to the test storage instead of disk.") 155 private boolean mUseTestStorage = true; 156 157 @Option( 158 name = "ajur-max-shard", 159 description = 160 "The maximum number of shard we want to allow the AJUR test to shard into") 161 private Integer mMaxShard = 4; 162 163 @Option( 164 name = "device-listeners", 165 description = 166 "Specify device side instrumentation listeners to be added for the run. " 167 + "Can be repeated. Note that while the ordering here is followed for " 168 + "now, future versions of AndroidJUnitRunner might not preserve the " 169 + "listener ordering.") 170 private Set<String> mExtraDeviceListeners = new LinkedHashSet<>(); 171 172 @Option( 173 name = "use-new-run-listener-order", 174 description = "Enables the new RunListener Order for AJUR." 175 ) 176 // Default to true as it is harmless if not supported. 177 private boolean mNewRunListenerOrderMode = true; 178 179 private File mInternalIncludeTestFile = null; 180 private File mInternalExcludeTestFile = null; 181 private String mDeviceIncludeFile = null; 182 private String mDeviceExcludeFile = null; 183 private int mTotalShards = 0; 184 private int mShardIndex = 0; 185 // Flag to avoid re-sharding a test that already was. 186 private boolean mIsSharded = false; 187 AndroidJUnitTest()188 public AndroidJUnitTest() { 189 super(); 190 setEnforceFormat(true); 191 } 192 193 /** 194 * {@inheritDoc} 195 */ 196 @Override getRuntimeHint()197 public long getRuntimeHint() { 198 return mRuntimeHint; 199 } 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override addIncludeFilter(String filter)205 public void addIncludeFilter(String filter) { 206 mIncludeFilters.add(filter); 207 } 208 209 /** 210 * {@inheritDoc} 211 */ 212 @Override addAllIncludeFilters(Set<String> filters)213 public void addAllIncludeFilters(Set<String> filters) { 214 mIncludeFilters.addAll(filters); 215 } 216 217 /** 218 * {@inheritDoc} 219 */ 220 @Override addExcludeFilter(String filter)221 public void addExcludeFilter(String filter) { 222 mExcludeFilters.add(filter); 223 } 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override addAllExcludeFilters(Set<String> filters)229 public void addAllExcludeFilters(Set<String> filters) { 230 mExcludeFilters.addAll(filters); 231 } 232 233 /** {@inheritDoc} */ 234 @Override clearIncludeFilters()235 public void clearIncludeFilters() { 236 mIncludeFilters.clear(); 237 } 238 239 /** {@inheritDoc} */ 240 @Override getIncludeFilters()241 public Set<String> getIncludeFilters() { 242 return mIncludeFilters; 243 } 244 245 /** {@inheritDoc} */ 246 @Override getExcludeFilters()247 public Set<String> getExcludeFilters() { 248 return mExcludeFilters; 249 } 250 251 /** {@inheritDoc} */ 252 @Override clearExcludeFilters()253 public void clearExcludeFilters() { 254 mExcludeFilters.clear(); 255 } 256 257 /** {@inheritDoc} */ 258 @Override setIncludeTestFile(File testFile)259 public void setIncludeTestFile(File testFile) { 260 mInternalIncludeTestFile = testFile; 261 } 262 263 /** {@inheritDoc} */ 264 @Override getIncludeTestFile()265 public File getIncludeTestFile() { 266 return mInternalIncludeTestFile; 267 } 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override setExcludeTestFile(File testFile)273 public void setExcludeTestFile(File testFile) { 274 mInternalExcludeTestFile = testFile; 275 } 276 277 /** {@inheritDoc} */ 278 @Override getExcludeTestFile()279 public File getExcludeTestFile() { 280 return mInternalExcludeTestFile; 281 } 282 283 /** 284 * {@inheritDoc} 285 */ 286 @Override addIncludeAnnotation(String annotation)287 public void addIncludeAnnotation(String annotation) { 288 mIncludeAnnotation.add(annotation); 289 } 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override addAllIncludeAnnotation(Set<String> annotations)295 public void addAllIncludeAnnotation(Set<String> annotations) { 296 mIncludeAnnotation.addAll(annotations); 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override addExcludeAnnotation(String excludeAnnotation)303 public void addExcludeAnnotation(String excludeAnnotation) { 304 mExcludeAnnotation.add(excludeAnnotation); 305 } 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override addAllExcludeAnnotation(Set<String> excludeAnnotations)311 public void addAllExcludeAnnotation(Set<String> excludeAnnotations) { 312 mExcludeAnnotation.addAll(excludeAnnotations); 313 } 314 315 /** {@inheritDoc} */ 316 @Override getIncludeAnnotations()317 public Set<String> getIncludeAnnotations() { 318 return mIncludeAnnotation; 319 } 320 321 /** {@inheritDoc} */ 322 @Override getExcludeAnnotations()323 public Set<String> getExcludeAnnotations() { 324 return mExcludeAnnotation; 325 } 326 327 /** {@inheritDoc} */ 328 @Override clearIncludeAnnotations()329 public void clearIncludeAnnotations() { 330 mIncludeAnnotation.clear(); 331 } 332 333 /** {@inheritDoc} */ 334 @Override clearExcludeAnnotations()335 public void clearExcludeAnnotations() { 336 mExcludeAnnotation.clear(); 337 } 338 339 /** {@inheritDoc} */ 340 @Override run(TestInformation testInfo, final ITestInvocationListener listener)341 public void run(TestInformation testInfo, final ITestInvocationListener listener) 342 throws DeviceNotAvailableException { 343 if (getDevice() == null) { 344 throw new IllegalArgumentException("Device has not been set"); 345 } 346 if (mUseTestStorage) { 347 // Check if we are a parameterized module 348 List<String> params = 349 getConfiguration() 350 .getConfigurationDescription() 351 .getMetaData(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY); 352 if (params != null && params.contains(InstantAppHandler.INSTANT_APP_ID)) { 353 mUseTestStorage = false; 354 CLog.d("Disable test storage on instant app module."); 355 } else if (isTestRunningOnSdkSandbox(testInfo)) { 356 // SDK sandboxes don't have access to the test ContentProvider. 357 mUseTestStorage = false; 358 CLog.d("Disable test storage for SDK sandbox instrumentation tests."); 359 } else { 360 mUseTestStorage = getDevice().checkApiLevelAgainstNextRelease(34); 361 if (!mUseTestStorage) { 362 CLog.d("Disabled test storage as it's not supported on that branch."); 363 } 364 } 365 } 366 367 boolean pushedFile = false; 368 try (CloseableTraceScope filter = new CloseableTraceScope("push_filter_files")) { 369 // if mInternalIncludeTestFile is set, perform filtering with this file 370 if (mInternalIncludeTestFile != null && mInternalIncludeTestFile.length() > 0) { 371 mDeviceIncludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + INCLUDE_FILE; 372 pushTestFile(mInternalIncludeTestFile, mDeviceIncludeFile, listener, false); 373 if (mUseTestStorage) { 374 pushTestFile( 375 mInternalIncludeTestFile, 376 mTestStorageInternalDir + mDeviceIncludeFile, 377 listener, 378 true); 379 } 380 pushedFile = true; 381 // If an explicit include file filter is provided, do not use the package 382 setTestPackageName(null); 383 } 384 385 // if mInternalExcludeTestFile is set, perform filtering with this file 386 if (mInternalExcludeTestFile != null && mInternalExcludeTestFile.length() > 0) { 387 mDeviceExcludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + EXCLUDE_FILE; 388 pushTestFile(mInternalExcludeTestFile, mDeviceExcludeFile, listener, false); 389 if (mUseTestStorage) { 390 pushTestFile( 391 mInternalExcludeTestFile, 392 mTestStorageInternalDir + mDeviceExcludeFile, 393 listener, 394 true); 395 } 396 pushedFile = true; 397 } 398 } 399 TestAppInstallSetup serviceInstaller = null; 400 if (mUseTestStorage) { 401 File testServices = null; 402 try (CloseableTraceScope serviceInstall = 403 new CloseableTraceScope("install_service_apk")) { 404 testServices = FileUtil.createTempFile("services", ".apk"); 405 boolean extracted = 406 ResourceUtil.extractResourceAsFile( 407 "/test-services-normalized.apk", testServices); 408 if (extracted) { 409 serviceInstaller = new TestAppInstallSetup(); 410 // Service apk needs force-queryable 411 serviceInstaller.setForceQueryable(true); 412 serviceInstaller.addTestFile(testServices); 413 if (testInfo != null 414 && testInfo.properties().containsKey(RUN_TESTS_AS_USER_KEY)) { 415 serviceInstaller.setUserId( 416 Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY))); 417 } 418 serviceInstaller.setUp(testInfo); 419 // Turn off battery optimization for androidx.test.services 420 CommandResult dumpsys = 421 getDevice() 422 .executeShellV2Command( 423 "dumpsys deviceidle whitelist +androidx.test.services"); 424 CLog.d("stdout: %s\nstderr: %s", dumpsys.getStdout(), dumpsys.getStderr()); 425 } else { 426 throw new IOException("Failed to extract test-services.apk"); 427 } 428 } catch (IOException | TargetSetupError | BuildError e) { 429 CLog.e(e); 430 mUseTestStorage = false; 431 } finally { 432 FileUtil.deleteFile(testServices); 433 } 434 } 435 if (mTotalShards > 0 && !isShardable() && mShardIndex != 0) { 436 // If not shardable, only first shard can run. 437 CLog.i("%s is not shardable.", getRunnerName()); 438 return; 439 } 440 super.run(testInfo, listener); 441 if (serviceInstaller != null) { 442 try (CloseableTraceScope serviceTeardown = 443 new CloseableTraceScope("service_teardown")) { 444 serviceInstaller.tearDown(testInfo, null); 445 } 446 } 447 if (pushedFile) { 448 // Remove the directory where the files where pushed 449 removeTestFilterDir(); 450 } 451 } 452 453 /** 454 * {@inheritDoc} 455 */ 456 @Override setRunnerArgs(IRemoteAndroidTestRunner runner)457 protected void setRunnerArgs(IRemoteAndroidTestRunner runner) { 458 super.setRunnerArgs(runner); 459 460 // if mIncludeTestFile is set, perform filtering with this file 461 if (mDeviceIncludeFile != null) { 462 runner.addInstrumentationArg(TEST_FILE_INST_ARGS_KEY, mDeviceIncludeFile); 463 } 464 465 // if mExcludeTestFile is set, perform filtering with this file 466 if (mDeviceExcludeFile != null) { 467 runner.addInstrumentationArg(NOT_TEST_FILE_INST_ARGS_KEY, mDeviceExcludeFile); 468 } 469 470 // Split filters into class, notClass, package and notPackage 471 List<String> classArg = new ArrayList<String>(); 472 List<String> notClassArg = new ArrayList<String>(); 473 List<String> packageArg = new ArrayList<String>(); 474 List<String> notPackageArg = new ArrayList<String>(); 475 List<String> regexArg = new ArrayList<String>(); 476 for (String test : mIncludeFilters) { 477 if (isRegex(test)) { 478 regexArg.add(test); 479 } else if (isClassOrMethod(test)) { 480 classArg.add(test); 481 } else { 482 packageArg.add(test); 483 } 484 } 485 for (String test : mExcludeFilters) { 486 // tests_regex doesn't support exclude-filter. Therefore, only check if the filter is 487 // for class/method or package. 488 if (isClassOrMethod(test)) { 489 notClassArg.add(test); 490 } else { 491 notPackageArg.add(test); 492 } 493 } 494 if (!regexArg.isEmpty() 495 && (!classArg.isEmpty() 496 || !notClassArg.isEmpty() 497 || !packageArg.isEmpty() 498 || !notPackageArg.isEmpty())) { 499 StringBuilder sb = new StringBuilder(); 500 if (!classArg.isEmpty()) { 501 sb.append("classArg: " + classArg); 502 } 503 if (!notClassArg.isEmpty()) { 504 sb.append("notClassArg: " + notClassArg); 505 } 506 if (!packageArg.isEmpty()) { 507 sb.append("packageArg: " + packageArg); 508 } 509 if (!notPackageArg.isEmpty()) { 510 sb.append("notPackageArg: " + notPackageArg); 511 } 512 throw new IllegalArgumentException( 513 String.format( 514 "Mixed filter types found. AndroidJUnitTest does not support mixing" 515 + " both regex [%s] and class/method/package filters: [%s]", 516 regexArg, sb.toString())); 517 } 518 if (!classArg.isEmpty()) { 519 runner.addInstrumentationArg(INCLUDE_CLASS_INST_ARGS_KEY, 520 ArrayUtil.join(",", classArg)); 521 } 522 if (!notClassArg.isEmpty()) { 523 runner.addInstrumentationArg(EXCLUDE_CLASS_INST_ARGS_KEY, 524 ArrayUtil.join(",", notClassArg)); 525 } 526 if (!packageArg.isEmpty()) { 527 runner.addInstrumentationArg(INCLUDE_PACKAGE_INST_ARGS_KEY, 528 ArrayUtil.join(",", packageArg)); 529 } 530 if (!notPackageArg.isEmpty()) { 531 runner.addInstrumentationArg(EXCLUDE_PACKAGE_INST_ARGS_KEY, 532 ArrayUtil.join(",", notPackageArg)); 533 } 534 if (!regexArg.isEmpty()) { 535 String regexFilter; 536 if (regexArg.size() == 1) { 537 regexFilter = regexArg.get(0); 538 } else { 539 Collections.sort(regexArg); 540 regexFilter = "\"(" + ArrayUtil.join("|", regexArg) + ")\""; 541 } 542 runner.addInstrumentationArg(INCLUDE_REGEX_INST_ARGS_KEY, regexFilter); 543 } 544 if (!mIncludeAnnotation.isEmpty()) { 545 runner.addInstrumentationArg(ANNOTATION_INST_ARGS_KEY, 546 ArrayUtil.join(",", mIncludeAnnotation)); 547 } 548 if (!mExcludeAnnotation.isEmpty()) { 549 runner.addInstrumentationArg(NOT_ANNOTATION_INST_ARGS_KEY, 550 ArrayUtil.join(",", mExcludeAnnotation)); 551 } 552 if (mTotalShards > 0 && isShardable()) { 553 runner.addInstrumentationArg(SHARD_INDEX_INST_ARGS_KEY, Integer.toString(mShardIndex)); 554 runner.addInstrumentationArg(NUM_SHARD_INST_ARGS_KEY, Integer.toString(mTotalShards)); 555 } 556 if (mNewRunListenerOrderMode) { 557 runner.addInstrumentationArg( 558 NEW_RUN_LISTENER_ORDER_KEY, Boolean.toString(mNewRunListenerOrderMode)); 559 } 560 if (mUseTestStorage) { 561 runner.addInstrumentationArg( 562 USE_TEST_STORAGE_SERVICE, Boolean.toString(mUseTestStorage)); 563 } 564 // Add the listeners received from Options 565 addDeviceListeners(mExtraDeviceListeners); 566 } 567 568 /** 569 * Push the testFile to the requested destination. This should only be called for a non-null 570 * testFile 571 * 572 * @param testFile file to be pushed from the host to the device. 573 * @param destination the path on the device to which testFile is pushed 574 * @param listener {@link ITestInvocationListener} to report failures. 575 */ pushTestFile( File testFile, String destination, ITestInvocationListener listener, boolean skipLog)576 private void pushTestFile( 577 File testFile, String destination, ITestInvocationListener listener, boolean skipLog) 578 throws DeviceNotAvailableException { 579 if (!testFile.canRead() || !testFile.isFile()) { 580 String message = String.format("Cannot read test file %s", testFile.getAbsolutePath()); 581 reportEarlyFailure(listener, message); 582 throw new IllegalArgumentException(message); 583 } 584 ITestDevice device = getDevice(); 585 try { 586 CLog.d("Attempting to push filters to %s", destination); 587 boolean filterDirExists = device.doesFileExist(mTestFilterDir); 588 if (!device.pushFile(testFile, destination, true)) { 589 String message = 590 String.format( 591 "Failed to push file %s to %s for %s in pushTestFile", 592 testFile.getAbsolutePath(), destination, device.getSerialNumber()); 593 reportEarlyFailure(listener, message); 594 throw new RuntimeException(message); 595 } 596 // in case the folder was created as 'root' we make is usable. 597 if (!filterDirExists) { 598 device.executeShellCommand( 599 String.format("chown -R shell:shell %s", mTestFilterDir)); 600 boolean filterExists = device.doesFileExist(destination); 601 if (!filterExists) { 602 CLog.e("Filter '%s' wasn't found on device after pushing.", destination); 603 } 604 } 605 } catch (DeviceNotAvailableException e) { 606 reportEarlyFailure(listener, e.getMessage()); 607 throw e; 608 } 609 if (skipLog) { 610 return; 611 } 612 try (FileInputStreamSource source = new FileInputStreamSource(testFile)) { 613 listener.testLog("filter-" + testFile.getName(), LogDataType.TEXT, source); 614 } 615 } 616 removeTestFilterDir()617 private void removeTestFilterDir() throws DeviceNotAvailableException { 618 getDevice().deleteFile(mTestFilterDir); 619 } 620 reportEarlyFailure(ITestInvocationListener listener, String errorMessage)621 private void reportEarlyFailure(ITestInvocationListener listener, String errorMessage) { 622 listener.testRunStarted("AndroidJUnitTest_setupError", 0); 623 FailureDescription failure = FailureDescription.create(errorMessage); 624 failure.setFailureStatus(FailureStatus.INFRA_FAILURE); 625 listener.testRunFailed(failure); 626 listener.testRunEnded(0, new HashMap<String, Metric>()); 627 } 628 629 /** 630 * Return if a string is the name of a Class or a Method. 631 */ 632 @VisibleForTesting isClassOrMethod(String filter)633 public boolean isClassOrMethod(String filter) { 634 if (filter.contains("#")) { 635 return true; 636 } 637 String[] parts = filter.split("\\."); 638 if (parts.length > 0) { 639 // FIXME Assume java package names starts with lowercase and class names start with 640 // uppercase. 641 // Return true iff the first character of the last word is uppercase 642 // com.android.foobar.Test 643 return Character.isUpperCase(parts[parts.length - 1].charAt(0)); 644 } 645 return false; 646 } 647 648 /** Return if a string is a regex for filter. */ 649 @VisibleForTesting isRegex(String filter)650 public boolean isRegex(String filter) { 651 if (isParameterizedTest(filter)) { 652 return false; 653 } 654 655 // If filter contains any special regex character, return true. 656 // Throw RuntimeException if the regex is invalid. 657 if (Pattern.matches(".*[\\?\\*\\^\\$\\(\\)\\[\\]\\{\\}\\|\\\\].*", filter)) { 658 try { 659 Pattern.compile(filter); 660 } catch (PatternSyntaxException e) { 661 CLog.e("Filter %s is not a valid regular expression string.", filter); 662 throw new RuntimeException(e); 663 } 664 return true; 665 } 666 667 return false; 668 } 669 670 /** Return if a string is a parameterized test. */ 671 @VisibleForTesting isParameterizedTest(String filter)672 public boolean isParameterizedTest(String filter) { 673 // If filter contains '#', '[', ']' and must ends with ']'. Only numbers, a-Z, -, _, 674 // [, ], (, ), and . are allowed between []. 675 if (Pattern.matches(".*#.*\\[[0-9a-zA-Z,\\-_.\\[\\]\\(\\)]*\\]$", filter)) { 676 CLog.i("Filter %s is a parameterized string.", filter); 677 return true; 678 } 679 return false; 680 } 681 682 /** 683 * Helper to return if the runner is one that support sharding. 684 */ isShardable()685 private boolean isShardable() { 686 // Edge toward shardable if no explicit runner specified. The runner will be determined 687 // later and if not shardable only the first shard will run. 688 if (getRunnerName() == null) { 689 return true; 690 } 691 return ListInstrumentationParser.SHARDABLE_RUNNERS.contains(getRunnerName()); 692 } 693 694 /** {@inheritDoc} */ 695 @Override split(int shardCount)696 public Collection<IRemoteTest> split(int shardCount) { 697 if (!isShardable()) { 698 return null; 699 } 700 if (mMaxShard != null) { 701 shardCount = Math.min(shardCount, mMaxShard); 702 } 703 if (!mIsSharded && shardCount > 1) { 704 mIsSharded = true; 705 Collection<IRemoteTest> shards = new ArrayList<>(shardCount); 706 for (int index = 0; index < shardCount; index++) { 707 shards.add(getTestShard(shardCount, index)); 708 } 709 return shards; 710 } 711 return null; 712 } 713 getTestShard(int shardCount, int shardIndex)714 private IRemoteTest getTestShard(int shardCount, int shardIndex) { 715 AndroidJUnitTest shard; 716 // ensure we handle runners that extend AndroidJUnitRunner 717 try { 718 shard = this.getClass().getDeclaredConstructor().newInstance(); 719 } catch (InstantiationException 720 | IllegalAccessException 721 | InvocationTargetException 722 | NoSuchMethodException e) { 723 throw new RuntimeException(e); 724 } 725 try { 726 OptionCopier.copyOptions(this, shard); 727 } catch (ConfigurationException e) { 728 CLog.e("Failed to copy instrumentation options: %s", e.getMessage()); 729 } 730 shard.mShardIndex = shardIndex; 731 shard.mTotalShards = shardCount; 732 shard.mIsSharded = true; 733 shard.setAbi(getAbi()); 734 shard.mInternalExcludeTestFile = mInternalExcludeTestFile; 735 shard.mInternalIncludeTestFile = mInternalIncludeTestFile; 736 // We approximate the runtime of each shard to be equal since we can't know. 737 shard.mRuntimeHint = mRuntimeHint / shardCount; 738 return shard; 739 } 740 } 741