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 package com.android.tradefed.testtype; 17 18 import com.android.tradefed.build.BuildRetrievalError; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.config.ConfigurationException; 21 import com.android.tradefed.config.DynamicRemoteFileResolver; 22 import com.android.tradefed.config.IConfiguration; 23 import com.android.tradefed.config.IConfigurationReceiver; 24 import com.android.tradefed.config.Option; 25 import com.android.tradefed.config.Option.Importance; 26 import com.android.tradefed.config.OptionClass; 27 import com.android.tradefed.config.OptionCopier; 28 import com.android.tradefed.config.OptionSetter; 29 import com.android.tradefed.device.DeviceNotAvailableException; 30 import com.android.tradefed.device.ITestDevice; 31 import com.android.tradefed.device.metric.IMetricCollector; 32 import com.android.tradefed.device.metric.IMetricCollectorReceiver; 33 import com.android.tradefed.error.HarnessRuntimeException; 34 import com.android.tradefed.error.IHarnessException; 35 import com.android.tradefed.invoker.TestInformation; 36 import com.android.tradefed.invoker.logger.CurrentInvocation; 37 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 38 import com.android.tradefed.log.LogUtil.CLog; 39 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 40 import com.android.tradefed.observatory.IDiscoverTestClasses; 41 import com.android.tradefed.result.FailureDescription; 42 import com.android.tradefed.result.ITestInvocationListener; 43 import com.android.tradefed.result.ResultForwarder; 44 import com.android.tradefed.result.TestDescription; 45 import com.android.tradefed.result.error.ErrorIdentifier; 46 import com.android.tradefed.result.error.InfraErrorIdentifier; 47 import com.android.tradefed.testtype.host.PrettyTestEventLogger; 48 import com.android.tradefed.testtype.junit4.CarryDnaeError; 49 import com.android.tradefed.testtype.junit4.ExceptionThrowingRunnerWrapper; 50 import com.android.tradefed.testtype.junit4.JUnit4ResultForwarder; 51 import com.android.tradefed.testtype.suite.ModuleDefinition; 52 import com.android.tradefed.util.FileUtil; 53 import com.android.tradefed.util.JUnit4TestFilter; 54 import com.android.tradefed.util.StreamUtil; 55 import com.android.tradefed.util.TestFilterHelper; 56 57 import com.google.common.annotations.VisibleForTesting; 58 59 import junit.framework.Test; 60 import junit.framework.TestCase; 61 import junit.framework.TestSuite; 62 63 import org.junit.Ignore; 64 import org.junit.internal.runners.ErrorReportingRunner; 65 import org.junit.runner.Description; 66 import org.junit.runner.JUnitCore; 67 import org.junit.runner.Request; 68 import org.junit.runner.RunWith; 69 import org.junit.runner.Runner; 70 import org.junit.runner.notification.RunNotifier; 71 import org.junit.runners.Suite.SuiteClasses; 72 73 import java.io.File; 74 import java.io.FileNotFoundException; 75 import java.io.IOException; 76 import java.lang.annotation.Annotation; 77 import java.lang.reflect.AnnotatedElement; 78 import java.lang.reflect.InvocationTargetException; 79 import java.lang.reflect.Method; 80 import java.lang.reflect.Modifier; 81 import java.net.MalformedURLException; 82 import java.net.URL; 83 import java.net.URLClassLoader; 84 import java.time.Duration; 85 import java.util.ArrayDeque; 86 import java.util.ArrayList; 87 import java.util.Collection; 88 import java.util.Collections; 89 import java.util.Deque; 90 import java.util.Enumeration; 91 import java.util.HashMap; 92 import java.util.HashSet; 93 import java.util.LinkedHashSet; 94 import java.util.List; 95 import java.util.Set; 96 import java.util.concurrent.TimeUnit; 97 import java.util.jar.JarEntry; 98 import java.util.jar.JarFile; 99 import java.util.regex.Pattern; 100 101 /** 102 * A test runner for JUnit host based tests. If the test to be run implements {@link IDeviceTest} 103 * this runner will pass a reference to the device. 104 */ 105 @OptionClass(alias = "host") 106 public class HostTest 107 implements IDeviceTest, 108 ITestFilterReceiver, 109 ITestAnnotationFilterReceiver, 110 IRemoteTest, 111 ITestCollector, 112 IBuildReceiver, 113 IAbiReceiver, 114 IShardableTest, 115 IRuntimeHintProvider, 116 IConfigurationReceiver, 117 IDiscoverTestClasses { 118 119 @Option(name = "class", description = "The JUnit test classes to run, in the format " 120 + "<package>.<class>. eg. \"com.android.foo.Bar\". This field can be repeated.", 121 importance = Importance.IF_UNSET) 122 private Set<String> mClasses = new LinkedHashSet<>(); 123 124 @Option(name = "method", description = "The name of the method in the JUnit TestCase to run. " 125 + "eg. \"testFooBar\"", 126 importance = Importance.IF_UNSET) 127 private String mMethodName; 128 129 @Option( 130 name = "jar", 131 description = "The jars containing the JUnit test class to run.", 132 importance = Importance.IF_UNSET) 133 private Set<String> mJars = new LinkedHashSet<>(); 134 135 public static final String SET_OPTION_NAME = "set-option"; 136 public static final String SET_OPTION_DESC = 137 "Options to be passed down to the class under test, key and value should be separated" 138 + " by colon \":\"; for example, if class under test supports \"--iteration 1\"" 139 + " from a command line, it should be passed in as \"--set-option iteration:1\" or" 140 + " \"--set-option iteration:key=value\" for passing options to map. Values that" 141 + " contain \":\" or \"=\" can be escaped with a backslash. A particular class can" 142 + " be targeted by specifying it. \" --set-option <fully qualified class>:<option" 143 + " name>:<option value>\""; 144 145 @Option(name = SET_OPTION_NAME, description = SET_OPTION_DESC) 146 private List<String> mKeyValueOptions = new ArrayList<>(); 147 148 @Option( 149 name = "include-annotation", 150 description = "The set of annotations a test must have to be run.") 151 private Set<String> mIncludeAnnotations = new LinkedHashSet<>(); 152 153 @Option( 154 name = "exclude-annotation", 155 description = 156 "The set of annotations to exclude tests from running. A test must have " 157 + "none of the annotations in this list to run.") 158 private Set<String> mExcludeAnnotations = new LinkedHashSet<>(); 159 160 /** 161 * It is strongly recommended that clients set include and exclude filters at the suite level 162 * via the ITestFilter interface rather than relying on include-filter and 163 * exclude-filter @Options. 164 */ 165 @Option( 166 name = "include-filter", 167 description = "The set of annotations a test must have to be run.") 168 private Set<String> mIncludeFilters = new LinkedHashSet<>(); 169 170 /** 171 * It is strongly recommended that clients set include and exclude filters at the suite level 172 * via the ITestFilter interface rather than relying on include-filter and 173 * exclude-filter @Options. 174 */ 175 @Option( 176 name = "exclude-filter", 177 description = 178 "The set of annotations to exclude tests from running. A test must have " 179 + "none of the annotations in this list to run.") 180 private Set<String> mExcludeFilters = new LinkedHashSet<>(); 181 182 @Option(name = "collect-tests-only", 183 description = "Only invoke the instrumentation to collect list of applicable test " 184 + "cases. All test run callbacks will be triggered, but test execution will " 185 + "not be actually carried out.") 186 private boolean mCollectTestsOnly = false; 187 188 @Option( 189 name = "runtime-hint", 190 isTimeVal = true, 191 description = "The hint about the test's runtime." 192 ) 193 private long mRuntimeHint = 60000; // 1 minute 194 195 enum ShardUnit { 196 CLASS, METHOD; 197 } 198 199 @Option(name = "shard-unit", 200 description = "Shard by class or method") 201 private ShardUnit mShardUnit = ShardUnit.CLASS; 202 203 @Option( 204 name = "enable-pretty-logs", 205 description = 206 "whether or not to enable a logging for each test start and end on both host and " 207 + "device side." 208 ) 209 private boolean mEnableHostDeviceLogs = true; 210 211 @Option( 212 name = TestTimeoutEnforcer.TEST_CASE_TIMEOUT_OPTION, 213 description = TestTimeoutEnforcer.TEST_CASE_TIMEOUT_DESCRIPTION) 214 private Duration mTestCaseTimeout = Duration.ofSeconds(0L); 215 216 private IConfiguration mConfig; 217 private ITestDevice mDevice; 218 private IBuildInfo mBuildInfo; 219 private IAbi mAbi; 220 private TestInformation mTestInfo; 221 private TestFilterHelper mFilterHelper; 222 private boolean mSkipTestClassCheck = false; 223 224 private List<Object> mTestMethods; 225 private List<Class<?>> mLoadedClasses = new ArrayList<>(); 226 private List<URLClassLoader> mOpenClassLoaders = new ArrayList<>(); 227 228 // Initialized as -1 to indicate that this value needs to be recalculated 229 // when test count is requested. 230 private int mNumTestCases = -1; 231 232 private List<File> mJUnit4JarFiles = new ArrayList<>(); 233 234 private static final String EXCLUDE_NO_TEST_FAILURE = "org.junit.runner.manipulation.Filter"; 235 private static final String TEST_FULL_NAME_FORMAT = "%s#%s"; 236 237 /** Track the downloaded files. */ 238 private List<File> mDownloadedFiles = new ArrayList<>(); 239 HostTest()240 public HostTest() { 241 mFilterHelper = 242 new TestFilterHelper( 243 mIncludeFilters, mExcludeFilters, mIncludeAnnotations, mExcludeAnnotations); 244 } 245 setTestInformation(TestInformation testInfo)246 public void setTestInformation(TestInformation testInfo) { 247 mTestInfo = testInfo; 248 } 249 250 @Override setConfiguration(IConfiguration configuration)251 public void setConfiguration(IConfiguration configuration) { 252 mConfig = configuration; 253 } 254 255 /** 256 * {@inheritDoc} 257 */ 258 @Override getDevice()259 public ITestDevice getDevice() { 260 return mDevice; 261 } 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override setDevice(ITestDevice device)267 public void setDevice(ITestDevice device) { 268 mDevice = device; 269 } 270 271 /** {@inheritDoc} */ 272 @Override getRuntimeHint()273 public long getRuntimeHint() { 274 return mRuntimeHint; 275 } 276 277 /** {@inheritDoc} */ 278 @Override setAbi(IAbi abi)279 public void setAbi(IAbi abi) { 280 mAbi = abi; 281 } 282 283 /** {@inheritDoc} */ 284 @Override getAbi()285 public IAbi getAbi() { 286 return mAbi; 287 } 288 289 /** 290 * {@inheritDoc} 291 */ 292 @Override setBuild(IBuildInfo buildInfo)293 public void setBuild(IBuildInfo buildInfo) { 294 mBuildInfo = buildInfo; 295 } 296 297 /** 298 * Get the build info received by HostTest. 299 * 300 * @return the {@link IBuildInfo} 301 */ getBuild()302 protected IBuildInfo getBuild() { 303 return mBuildInfo; 304 } 305 306 /** 307 * @return true if shard-unit is method; false otherwise 308 */ shardUnitIsMethod()309 private boolean shardUnitIsMethod() { 310 return ShardUnit.METHOD.equals(mShardUnit); 311 } 312 313 /** 314 * {@inheritDoc} 315 */ 316 @Override addIncludeFilter(String filter)317 public void addIncludeFilter(String filter) { 318 // If filters change, reset test count so we recompute it next time it's requested. 319 mNumTestCases = -1; 320 mFilterHelper.addIncludeFilter(filter); 321 } 322 323 /** 324 * {@inheritDoc} 325 */ 326 @Override addAllIncludeFilters(Set<String> filters)327 public void addAllIncludeFilters(Set<String> filters) { 328 mNumTestCases = -1; 329 mFilterHelper.addAllIncludeFilters(filters); 330 } 331 332 /** {@inheritDoc} */ 333 @Override clearIncludeFilters()334 public void clearIncludeFilters() { 335 mNumTestCases = -1; 336 mFilterHelper.clearIncludeFilters(); 337 } 338 339 /** 340 * {@inheritDoc} 341 */ 342 @Override addExcludeFilter(String filter)343 public void addExcludeFilter(String filter) { 344 mNumTestCases = -1; 345 mFilterHelper.addExcludeFilter(filter); 346 } 347 348 /** {@inheritDoc} */ 349 @Override getIncludeFilters()350 public Set<String> getIncludeFilters() { 351 return mFilterHelper.getIncludeFilters(); 352 } 353 354 /** {@inheritDoc} */ 355 @Override getExcludeFilters()356 public Set<String> getExcludeFilters() { 357 return mFilterHelper.getExcludeFilters(); 358 } 359 360 /** 361 * {@inheritDoc} 362 */ 363 @Override addAllExcludeFilters(Set<String> filters)364 public void addAllExcludeFilters(Set<String> filters) { 365 mNumTestCases = -1; 366 mFilterHelper.addAllExcludeFilters(filters); 367 } 368 369 /** {@inheritDoc} */ 370 @Override clearExcludeFilters()371 public void clearExcludeFilters() { 372 mNumTestCases = -1; 373 mFilterHelper.clearExcludeFilters(); 374 } 375 376 /** 377 * Return the number of test cases across all classes part of the tests 378 */ countTestCases()379 public int countTestCases() { 380 if (mTestMethods != null) { 381 return mTestMethods.size(); 382 } else if (mNumTestCases >= 0) { 383 return mNumTestCases; 384 } 385 // Ensure filters are set in the helper 386 mFilterHelper.addAllIncludeAnnotation(mIncludeAnnotations); 387 mFilterHelper.addAllExcludeAnnotation(mExcludeAnnotations); 388 mFilterHelper.addAllIncludeFilters(mIncludeFilters); 389 mFilterHelper.addAllExcludeFilters(mExcludeFilters); 390 391 int count = 0; 392 for (Class<?> classObj : getClasses()) { 393 if (IRemoteTest.class.isAssignableFrom(classObj) 394 || Test.class.isAssignableFrom(classObj)) { 395 TestSuite suite = collectTests(collectClasses(classObj)); 396 int suiteCount = suite.countTestCases(); 397 if (suiteCount == 0 398 && IRemoteTest.class.isAssignableFrom(classObj) 399 && !Test.class.isAssignableFrom(classObj)) { 400 // If it's a pure IRemoteTest we count the run() as one test. 401 count++; 402 } else { 403 count += suiteCount; 404 } 405 } else if (hasJUnit4Annotation(classObj)) { 406 Request req = Request.aClass(classObj); 407 req = req.filterWith(new JUnit4TestFilter(mFilterHelper, mJUnit4JarFiles)); 408 Runner checkRunner = req.getRunner(); 409 // If no tests are remaining after filtering, checkRunner is ErrorReportingRunner. 410 // testCount() for ErrorReportingRunner returns 1, skip this classObj in this case. 411 if (checkRunner instanceof ErrorReportingRunner) { 412 if (!EXCLUDE_NO_TEST_FAILURE.equals( 413 checkRunner.getDescription().getClassName())) { 414 // If after filtering we have remaining tests that are malformed, we still 415 // count them toward the total number of tests. (each malformed class will 416 // count as 1 in the testCount()). 417 count += checkRunner.testCount(); 418 } 419 } else { 420 count += checkRunner.testCount(); 421 } 422 } else { 423 count++; 424 } 425 } 426 return mNumTestCases = count; 427 } 428 429 /** 430 * Clear then set a class name to be run. 431 */ setClassName(String className)432 protected void setClassName(String className) { 433 mClasses.clear(); 434 mClasses.add(className); 435 } 436 437 @Override getClassNames()438 public Set<String> getClassNames() { 439 return mClasses; 440 } 441 setMethodName(String methodName)442 void setMethodName(String methodName) { 443 mMethodName = methodName; 444 } 445 446 /** 447 * {@inheritDoc} 448 */ 449 @Override addIncludeAnnotation(String annotation)450 public void addIncludeAnnotation(String annotation) { 451 mIncludeAnnotations.add(annotation); 452 mFilterHelper.addIncludeAnnotation(annotation); 453 } 454 455 /** 456 * {@inheritDoc} 457 */ 458 @Override addAllIncludeAnnotation(Set<String> annotations)459 public void addAllIncludeAnnotation(Set<String> annotations) { 460 mIncludeAnnotations.addAll(annotations); 461 mFilterHelper.addAllIncludeAnnotation(annotations); 462 } 463 464 /** 465 * {@inheritDoc} 466 */ 467 @Override addExcludeAnnotation(String notAnnotation)468 public void addExcludeAnnotation(String notAnnotation) { 469 mExcludeAnnotations.add(notAnnotation); 470 mFilterHelper.addExcludeAnnotation(notAnnotation); 471 } 472 473 /** 474 * {@inheritDoc} 475 */ 476 @Override addAllExcludeAnnotation(Set<String> notAnnotations)477 public void addAllExcludeAnnotation(Set<String> notAnnotations) { 478 mExcludeAnnotations.addAll(notAnnotations); 479 mFilterHelper.addAllExcludeAnnotation(notAnnotations); 480 } 481 482 /** {@inheritDoc} */ 483 @Override getIncludeAnnotations()484 public Set<String> getIncludeAnnotations() { 485 return mIncludeAnnotations; 486 } 487 488 /** {@inheritDoc} */ 489 @Override getExcludeAnnotations()490 public Set<String> getExcludeAnnotations() { 491 return mExcludeAnnotations; 492 } 493 494 /** {@inheritDoc} */ 495 @Override clearIncludeAnnotations()496 public void clearIncludeAnnotations() { 497 mIncludeAnnotations.clear(); 498 mFilterHelper.clearIncludeAnnotations(); 499 } 500 501 /** {@inheritDoc} */ 502 @Override clearExcludeAnnotations()503 public void clearExcludeAnnotations() { 504 mExcludeAnnotations.clear(); 505 mFilterHelper.clearExcludeAnnotations(); 506 } 507 508 /** 509 * Helper to set the information of an object based on some of its type. 510 */ setTestObjectInformation(Object testObj)511 private void setTestObjectInformation(Object testObj) { 512 if (testObj instanceof IBuildReceiver) { 513 if (mBuildInfo == null) { 514 throw new IllegalArgumentException("Missing build information"); 515 } 516 ((IBuildReceiver)testObj).setBuild(mBuildInfo); 517 } 518 if (testObj instanceof IDeviceTest) { 519 if (mDevice == null) { 520 throw new IllegalArgumentException("Missing device"); 521 } 522 ((IDeviceTest)testObj).setDevice(mDevice); 523 } 524 // We are more flexible about abi info since not always available. 525 if (testObj instanceof IAbiReceiver) { 526 ((IAbiReceiver)testObj).setAbi(mAbi); 527 } 528 if (testObj instanceof IInvocationContextReceiver) { 529 ((IInvocationContextReceiver) testObj).setInvocationContext(mTestInfo.getContext()); 530 } 531 if (testObj instanceof ITestInformationReceiver) { 532 ((ITestInformationReceiver) testObj).setTestInformation(mTestInfo); 533 } 534 // managed runner should have the same set-option to pass option too. 535 if (testObj instanceof ISetOptionReceiver) { 536 try { 537 OptionSetter setter = new OptionSetter(testObj); 538 for (String item : mKeyValueOptions) { 539 setter.setOptionValue(SET_OPTION_NAME, item); 540 } 541 } catch (ConfigurationException e) { 542 throw new RuntimeException(e); 543 } 544 } 545 // TODO(olivernguyen): Clean this up after instrumenting runInstrumentationTests(...) API. 546 if (testObj instanceof IConfigurationReceiver) { 547 ((IConfigurationReceiver) testObj).setConfiguration(mConfig); 548 } 549 } 550 551 /** {@inheritDoc} */ 552 @Override run(TestInformation testInfo, ITestInvocationListener listener)553 public void run(TestInformation testInfo, ITestInvocationListener listener) 554 throws DeviceNotAvailableException { 555 mTestInfo = testInfo; 556 // Ensure filters are set in the helper 557 mFilterHelper.addAllIncludeAnnotation(mIncludeAnnotations); 558 mFilterHelper.addAllExcludeAnnotation(mExcludeAnnotations); 559 mFilterHelper.addAllIncludeFilters(mIncludeFilters); 560 mFilterHelper.addAllExcludeFilters(mExcludeFilters); 561 562 try { 563 try { 564 List<Class<?>> classes = getClasses(); 565 if (!mSkipTestClassCheck) { 566 if (classes.isEmpty()) { 567 throw new HarnessRuntimeException( 568 "No '--class' option was specified.", 569 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 570 } 571 } 572 if (mMethodName != null && classes.size() > 1) { 573 throw new HarnessRuntimeException( 574 String.format( 575 "'--method' only supports one '--class' name. Multiple were " 576 + "given: '%s'", 577 classes), 578 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 579 } 580 } catch (RuntimeException e) { 581 listener.testRunStarted(this.getClass().getCanonicalName(), 0); 582 listener.testRunFailed(createFromException(e)); 583 listener.testRunEnded(0L, new HashMap<String, Metric>()); 584 return; 585 } 586 587 // Add a pretty logger to the events to mark clearly start/end of test cases. 588 if (mEnableHostDeviceLogs) { 589 PrettyTestEventLogger logger = new PrettyTestEventLogger(mTestInfo.getDevices()); 590 listener = new ResultForwarder(logger, listener); 591 } 592 if (mTestMethods != null) { 593 runTestCases(listener); 594 } else { 595 runTestClasses(listener); 596 } 597 } finally { 598 mLoadedClasses.clear(); 599 for (URLClassLoader cl : mOpenClassLoaders) { 600 StreamUtil.close(cl); 601 } 602 mOpenClassLoaders.clear(); 603 } 604 } 605 runTestClasses(ITestInvocationListener listener)606 private void runTestClasses(ITestInvocationListener listener) 607 throws DeviceNotAvailableException { 608 for (Class<?> classObj : getClasses()) { 609 if (IRemoteTest.class.isAssignableFrom(classObj)) { 610 IRemoteTest test = (IRemoteTest) loadObject(classObj); 611 applyFilters(classObj, test); 612 runRemoteTest(listener, test); 613 } else if (Test.class.isAssignableFrom(classObj)) { 614 TestSuite junitTest = collectTests(collectClasses(classObj)); 615 // Resolve dynamic files for the junit3 test objects 616 Enumeration<Test> allTest = junitTest.tests(); 617 while (allTest.hasMoreElements()) { 618 Test testObj = allTest.nextElement(); 619 mDownloadedFiles.addAll(resolveRemoteFileForObject(testObj)); 620 } 621 try { 622 runJUnit3Tests(listener, junitTest, classObj.getName()); 623 } finally { 624 for (File f : mDownloadedFiles) { 625 FileUtil.recursiveDelete(f); 626 } 627 } 628 } else if (hasJUnit4Annotation(classObj)) { 629 // Include the method name filtering 630 Set<String> includes = mFilterHelper.getIncludeFilters(); 631 if (mMethodName != null) { 632 includes.add(String.format(TEST_FULL_NAME_FORMAT, classObj.getName(), 633 mMethodName)); 634 } 635 // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test} 636 Request req = Request.aClass(classObj); 637 Runner checkRunner = null; 638 try { 639 req = req.filterWith(new JUnit4TestFilter(mFilterHelper, mJUnit4JarFiles)); 640 checkRunner = req.getRunner(); 641 } catch (RuntimeException e) { 642 listener.testRunStarted(classObj.getName(), 0); 643 listener.testRunFailed(createFromException(e)); 644 listener.testRunEnded(0L, new HashMap<String, Metric>()); 645 return; 646 } 647 runJUnit4Tests(listener, checkRunner, classObj.getName()); 648 } else { 649 throw new IllegalArgumentException( 650 String.format("%s is not a supported test", classObj.getName())); 651 } 652 } 653 } 654 runTestCases(ITestInvocationListener listener)655 private void runTestCases(ITestInvocationListener listener) throws DeviceNotAvailableException { 656 Set<String> skippedTests = new LinkedHashSet<>(); 657 for (Object obj : getTestMethods()) { 658 if (IRemoteTest.class.isInstance(obj)) { 659 IRemoteTest test = (IRemoteTest) obj; 660 runRemoteTest(listener, test); 661 } else if (TestSuite.class.isInstance(obj)) { 662 TestSuite junitTest = (TestSuite) obj; 663 if (!runJUnit3Tests(listener, junitTest, junitTest.getName())) { 664 skippedTests.add(junitTest.getName()); 665 } 666 } else if (Description.class.isInstance(obj)) { 667 // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test} 668 Description desc = (Description) obj; 669 Request req = Request.aClass(desc.getTestClass()); 670 Runner checkRunner = req.filterWith(desc).getRunner(); 671 try { 672 runJUnit4Tests(listener, checkRunner, desc.getClassName()); 673 } catch (RuntimeException e) { 674 listener.testRunStarted(desc.getClassName(), 0); 675 listener.testRunFailed(createFromException(e)); 676 listener.testRunEnded(0L, new HashMap<String, Metric>()); 677 throw e; 678 } 679 } else { 680 throw new IllegalArgumentException( 681 String.format("%s is not a supported test", obj)); 682 } 683 } 684 CLog.v("The following classes were skipped due to no test cases found: %s", skippedTests); 685 } 686 runRemoteTest(ITestInvocationListener listener, IRemoteTest test)687 private void runRemoteTest(ITestInvocationListener listener, IRemoteTest test) 688 throws DeviceNotAvailableException { 689 if (mCollectTestsOnly) { 690 // Collect only mode is propagated to the test. 691 if (test instanceof ITestCollector) { 692 ((ITestCollector) test).setCollectTestsOnly(true); 693 } else { 694 throw new IllegalArgumentException( 695 String.format( 696 "%s does not implement ITestCollector", test.getClass())); 697 } 698 } 699 // Set collectors for completeness but this isn't really supported as part of HostTest. 700 if (test instanceof IMetricCollectorReceiver) { 701 ((IMetricCollectorReceiver) test) 702 .setMetricCollectors(new ArrayList<IMetricCollector>()); 703 } 704 test.run(mTestInfo, listener); 705 } 706 707 /** Returns True if some tests were executed, false otherwise. */ runJUnit3Tests( ITestInvocationListener listener, TestSuite junitTest, String className)708 private boolean runJUnit3Tests( 709 ITestInvocationListener listener, TestSuite junitTest, String className) 710 throws DeviceNotAvailableException { 711 if (mCollectTestsOnly) { 712 // Collect only mode, fake the junit test execution. 713 int testCount = junitTest.countTestCases(); 714 listener.testRunStarted(className, testCount); 715 HashMap<String, Metric> empty = new HashMap<>(); 716 for (int i = 0; i < testCount; i++) { 717 Test t = junitTest.testAt(i); 718 // Test does not have a getName method. 719 // using the toString format instead: <testName>(className) 720 String testName = t.toString().split("\\(")[0]; 721 TestDescription testId = new TestDescription(t.getClass().getName(), testName); 722 listener.testStarted(testId); 723 listener.testEnded(testId, empty); 724 } 725 HashMap<String, Metric> emptyMap = new HashMap<>(); 726 listener.testRunEnded(0, emptyMap); 727 if (testCount > 0) { 728 return true; 729 } else { 730 return false; 731 } 732 } else { 733 if (mTestCaseTimeout.toMillis() > 0L) { 734 listener = 735 new TestTimeoutEnforcer( 736 mTestCaseTimeout.toMillis(), TimeUnit.MILLISECONDS, listener); 737 } 738 try (CloseableTraceScope ignored = new CloseableTraceScope(className)) { 739 return JUnitRunUtil.runTest(listener, junitTest, className, mTestInfo); 740 } 741 } 742 } 743 runJUnit4Tests( ITestInvocationListener listener, Runner checkRunner, String className)744 private void runJUnit4Tests( 745 ITestInvocationListener listener, Runner checkRunner, String className) 746 throws DeviceNotAvailableException { 747 JUnitCore runnerCore = new JUnitCore(); 748 if (mTestCaseTimeout.toMillis() > 0L) { 749 listener = 750 new TestTimeoutEnforcer( 751 mTestCaseTimeout.toMillis(), TimeUnit.MILLISECONDS, listener); 752 } 753 JUnit4ResultForwarder list = new JUnit4ResultForwarder(listener); 754 runnerCore.addListener(list); 755 756 if (!(checkRunner instanceof ErrorReportingRunner)) { 757 // If no tests are remaining after filtering, it returns an Error Runner. 758 long startTime = System.currentTimeMillis(); 759 listener.testRunStarted(className, checkRunner.testCount()); 760 try (CloseableTraceScope ignore = new CloseableTraceScope(className)) { 761 if (mCollectTestsOnly) { 762 fakeDescriptionExecution(checkRunner.getDescription(), list); 763 } else { 764 setTestObjectInformation(checkRunner); 765 ExceptionThrowingRunnerWrapper runnerWrapper = 766 new ExceptionThrowingRunnerWrapper(checkRunner, mTestInfo); 767 runnerCore.run(runnerWrapper); 768 } 769 } catch (CarryDnaeError e) { 770 throw e.getDeviceNotAvailableException(); 771 } finally { 772 for (Description d : findIgnoredClass(checkRunner.getDescription())) { 773 TestDescription testDescription = 774 new TestDescription(d.getClassName(), "No Tests"); 775 listener.testStarted(testDescription); 776 listener.testIgnored(testDescription); 777 listener.testEnded(testDescription, new HashMap<String, Metric>()); 778 } 779 listener.testRunEnded( 780 System.currentTimeMillis() - startTime, new HashMap<String, Metric>()); 781 } 782 } else { 783 // Special case where filtering leaves no tests to run, we report no failure 784 // in this case. 785 if (EXCLUDE_NO_TEST_FAILURE.equals( 786 checkRunner.getDescription().getClassName())) { 787 listener.testRunStarted(className, 0); 788 listener.testRunEnded(0, new HashMap<String, Metric>()); 789 } else { 790 // Run the Error runner to get the failures from test classes. 791 listener.testRunStarted(className, checkRunner.testCount()); 792 RunNotifier failureNotifier = new RunNotifier(); 793 failureNotifier.addListener(list); 794 checkRunner.run(failureNotifier); 795 listener.testRunEnded(0, new HashMap<String, Metric>()); 796 } 797 } 798 } 799 800 /** Search and return all the classes that are @Ignored */ findIgnoredClass(Description description)801 private List<Description> findIgnoredClass(Description description) { 802 List<Description> ignoredClass = new ArrayList<>(); 803 if (description.isSuite()) { 804 for (Description childDescription : description.getChildren()) { 805 ignoredClass.addAll(findIgnoredClass(childDescription)); 806 } 807 } else { 808 if (description.getMethodName() == null) { 809 for (Annotation a : description.getAnnotations()) { 810 if (a.annotationType() != null && a.annotationType().equals(Ignore.class)) { 811 ignoredClass.add(description); 812 break; 813 } 814 } 815 } 816 } 817 return ignoredClass; 818 } 819 820 /** 821 * Helper to fake the execution of JUnit4 Tests, using the {@link Description} 822 */ fakeDescriptionExecution(Description desc, JUnit4ResultForwarder listener)823 private void fakeDescriptionExecution(Description desc, JUnit4ResultForwarder listener) { 824 if (desc.getMethodName() == null || !desc.getChildren().isEmpty()) { 825 for (Description child : desc.getChildren()) { 826 fakeDescriptionExecution(child, listener); 827 } 828 } else { 829 try { 830 listener.testStarted(desc); 831 listener.testFinished(desc); 832 } catch (Exception e) { 833 // Should never happen 834 CLog.e(e); 835 } 836 } 837 } 838 collectClasses(Class<?> classObj)839 private Set<Class<?>> collectClasses(Class<?> classObj) { 840 Set<Class<?>> classes = new HashSet<>(); 841 if (TestSuite.class.isAssignableFrom(classObj)) { 842 TestSuite testObj = (TestSuite) loadObject(classObj); 843 classes.addAll(getClassesFromSuite(testObj)); 844 } else { 845 classes.add(classObj); 846 } 847 return classes; 848 } 849 getClassesFromSuite(TestSuite suite)850 private Set<Class<?>> getClassesFromSuite(TestSuite suite) { 851 Set<Class<?>> classes = new HashSet<>(); 852 Enumeration<Test> tests = suite.tests(); 853 while (tests.hasMoreElements()) { 854 Test test = tests.nextElement(); 855 if (test instanceof TestSuite) { 856 classes.addAll(getClassesFromSuite((TestSuite) test)); 857 } else { 858 classes.addAll(collectClasses(test.getClass())); 859 } 860 } 861 return classes; 862 } 863 collectTests(Set<Class<?>> classes)864 private TestSuite collectTests(Set<Class<?>> classes) { 865 TestSuite suite = new TestSuite(); 866 for (Class<?> classObj : classes) { 867 String packageName = classObj.getPackage().getName(); 868 String className = classObj.getName(); 869 Method[] methods = null; 870 if (mMethodName == null) { 871 methods = classObj.getMethods(); 872 } else { 873 try { 874 methods = new Method[] { 875 classObj.getMethod(mMethodName, (Class[]) null) 876 }; 877 } catch (NoSuchMethodException e) { 878 throw new IllegalArgumentException( 879 String.format("Cannot find %s#%s", className, mMethodName), e); 880 } 881 } 882 883 for (Method method : methods) { 884 if (!Modifier.isPublic(method.getModifiers()) 885 || !method.getReturnType().equals(Void.TYPE) 886 || method.getParameterTypes().length > 0 887 || !method.getName().startsWith("test") 888 || !mFilterHelper.shouldRun(packageName, classObj, method)) { 889 continue; 890 } 891 Test testObj = (Test) loadObject(classObj, false); 892 if (testObj instanceof TestCase) { 893 ((TestCase)testObj).setName(method.getName()); 894 } 895 suite.addTest(testObj); 896 } 897 } 898 return suite; 899 } 900 getTestMethods()901 private List<Object> getTestMethods() throws IllegalArgumentException { 902 if (mTestMethods != null) { 903 return mTestMethods; 904 } 905 mTestMethods = new ArrayList<>(); 906 mFilterHelper.addAllIncludeAnnotation(mIncludeAnnotations); 907 mFilterHelper.addAllExcludeAnnotation(mExcludeAnnotations); 908 List<Class<?>> classes = getClasses(); 909 for (Class<?> classObj : classes) { 910 if (Test.class.isAssignableFrom(classObj)) { 911 TestSuite suite = collectTests(collectClasses(classObj)); 912 for (int i = 0; i < suite.testCount(); i++) { 913 TestSuite singletonSuite = new TestSuite(); 914 singletonSuite.setName(classObj.getName()); 915 Test testObj = suite.testAt(i); 916 singletonSuite.addTest(testObj); 917 if (IRemoteTest.class.isInstance(testObj)) { 918 setTestObjectInformation(testObj); 919 } 920 mTestMethods.add(singletonSuite); 921 } 922 } else if (IRemoteTest.class.isAssignableFrom(classObj)) { 923 // a pure IRemoteTest is considered a test method itself 924 IRemoteTest test = (IRemoteTest) loadObject(classObj); 925 applyFilters(classObj, test); 926 mTestMethods.add(test); 927 } else if (hasJUnit4Annotation(classObj)) { 928 // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test} 929 Request req = Request.aClass(classObj); 930 // Include the method name filtering 931 Set<String> includes = mFilterHelper.getIncludeFilters(); 932 if (mMethodName != null) { 933 includes.add(String.format(TEST_FULL_NAME_FORMAT, classObj.getName(), 934 mMethodName)); 935 } 936 937 req = req.filterWith(new JUnit4TestFilter(mFilterHelper, mJUnit4JarFiles)); 938 Runner checkRunner = req.getRunner(); 939 Deque<Description> descriptions = new ArrayDeque<>(); 940 descriptions.push(checkRunner.getDescription()); 941 while (!descriptions.isEmpty()) { 942 Description desc = descriptions.pop(); 943 if (desc.isTest()) { 944 mTestMethods.add(desc); 945 } 946 List<Description> children = desc.getChildren(); 947 Collections.reverse(children); 948 for (Description child : children) { 949 descriptions.push(child); 950 } 951 } 952 } else { 953 throw new IllegalArgumentException( 954 String.format("%s is not a supported test", classObj.getName())); 955 } 956 } 957 return mTestMethods; 958 } 959 getClasses()960 protected final List<Class<?>> getClasses() throws IllegalArgumentException { 961 if (!mLoadedClasses.isEmpty()) { 962 return mLoadedClasses; 963 } 964 // Use a set to avoid repeat between filters and jar search 965 Set<String> classNames = new HashSet<>(); 966 List<Class<?>> classes = mLoadedClasses; 967 for (String className : mClasses) { 968 if (classNames.contains(className)) { 969 continue; 970 } 971 IllegalArgumentException initialError = null; 972 try { 973 classes.add(Class.forName(className, true, getClassLoader())); 974 classNames.add(className); 975 } catch (ClassNotFoundException e) { 976 initialError = 977 new IllegalArgumentException( 978 String.format("Could not load Test class %s", className), e); 979 } 980 if (initialError != null) { 981 // Fallback search a jar for the module under tests if any. 982 String moduleName = 983 mTestInfo 984 .getContext() 985 .getAttributes() 986 .getUniqueMap() 987 .get(ModuleDefinition.MODULE_NAME); 988 if (moduleName != null) { 989 URLClassLoader cl = null; 990 try { 991 File f = getJarFile(moduleName + ".jar", mTestInfo); 992 URL[] urls = {f.toURI().toURL()}; 993 cl = URLClassLoader.newInstance(urls); 994 mJUnit4JarFiles.add(f); 995 Class<?> cls = cl.loadClass(className); 996 classes.add(cls); 997 classNames.add(className); 998 initialError = null; 999 mOpenClassLoaders.add(cl); 1000 } catch (FileNotFoundException 1001 | MalformedURLException 1002 | ClassNotFoundException fallbackSearch) { 1003 StreamUtil.close(cl); 1004 CLog.e( 1005 "Fallback search for a jar containing '%s' didn't work.Consider" 1006 + " using --jar option directly instead of using --class", 1007 className); 1008 } 1009 } 1010 } 1011 if (initialError != null) { 1012 throw initialError; 1013 } 1014 } 1015 URLClassLoader cl = null; 1016 // Inspect for the jar files 1017 for (String jarName : mJars) { 1018 JarFile jarFile = null; 1019 try { 1020 File file = getJarFile(jarName, mTestInfo); 1021 jarFile = new JarFile(file); 1022 Enumeration<JarEntry> e = jarFile.entries(); 1023 URL[] urls = {file.toURI().toURL()}; 1024 cl = URLClassLoader.newInstance(urls); 1025 mJUnit4JarFiles.add(file); 1026 mOpenClassLoaders.add(cl); 1027 1028 while (e.hasMoreElements()) { 1029 JarEntry je = e.nextElement(); 1030 if (je.isDirectory() 1031 || !je.getName().endsWith(".class") 1032 || je.getName().contains("$")) { 1033 continue; 1034 } 1035 String className = getClassName(je.getName()); 1036 if (classNames.contains(className)) { 1037 continue; 1038 } 1039 try { 1040 Class<?> cls = cl.loadClass(className); 1041 int modifiers = cls.getModifiers(); 1042 if ((IRemoteTest.class.isAssignableFrom(cls) 1043 || Test.class.isAssignableFrom(cls) 1044 || hasJUnit4Annotation(cls)) 1045 && !Modifier.isStatic(modifiers) 1046 && !Modifier.isPrivate(modifiers) 1047 && !Modifier.isProtected(modifiers) 1048 && !Modifier.isInterface(modifiers) 1049 && !Modifier.isAbstract(modifiers)) { 1050 if (!mClasses.isEmpty() && !mClasses.contains(className)) { 1051 continue; 1052 } 1053 classes.add(cls); 1054 classNames.add(className); 1055 } 1056 } catch (UnsupportedClassVersionError ucve) { 1057 throw new IllegalArgumentException( 1058 String.format( 1059 "Could not load class %s from jar %s. Reason:\n%s", 1060 className, jarName, StreamUtil.getStackTrace(ucve))); 1061 } catch (ClassNotFoundException cnfe) { 1062 throw new IllegalArgumentException( 1063 String.format("Cannot find test class %s", className)); 1064 } catch (IllegalAccessError | NoClassDefFoundError err) { 1065 // IllegalAccessError can happen when the class or one of its super 1066 // class/interfaces are package-private. We can't load such class from 1067 // here (= outside of the package). Since our intention is not to load 1068 // all classes in the jar, but to find our the main test classes, this 1069 // can be safely skipped. 1070 // NoClassDefFoundErrror is also okay because certain CTS test cases 1071 // might statically link to a jar library (e.g. tools.jar from JDK) 1072 // where certain internal classes in the library are referencing 1073 // classes that are not available in the jar. Again, since our goal here 1074 // is to find test classes, this can be safely skipped. 1075 continue; 1076 } 1077 } 1078 } catch (IOException e) { 1079 CLog.e(e); 1080 throw new IllegalArgumentException(e); 1081 } finally { 1082 StreamUtil.close(jarFile); 1083 } 1084 } 1085 return classes; 1086 } 1087 1088 /** Returns the default classloader. */ 1089 @VisibleForTesting getClassLoader()1090 protected ClassLoader getClassLoader() { 1091 return this.getClass().getClassLoader(); 1092 } 1093 1094 /** load the class object and set the test info (device, build). */ loadObject(Class<?> classObj)1095 protected Object loadObject(Class<?> classObj) { 1096 return loadObject(classObj, true); 1097 } 1098 1099 /** 1100 * Load the class object and set the test info if requested. 1101 * 1102 * @param classObj the class object to be loaded. 1103 * @param setInfo True the test infos need to be set. 1104 * @return The loaded object from the class. 1105 */ loadObject(Class<?> classObj, boolean setInfo)1106 private Object loadObject(Class<?> classObj, boolean setInfo) throws IllegalArgumentException { 1107 final String className = classObj.getName(); 1108 try { 1109 Object testObj = classObj.getDeclaredConstructor().newInstance(); 1110 // set options 1111 setOptionToLoadedObject(testObj, mKeyValueOptions); 1112 // Set the test information if needed. 1113 if (setInfo) { 1114 setTestObjectInformation(testObj); 1115 } 1116 return testObj; 1117 } catch (InstantiationException e) { 1118 throw new IllegalArgumentException(String.format("Could not load Test class %s", 1119 className), e); 1120 } catch (IllegalAccessException e) { 1121 throw new IllegalArgumentException(String.format("Could not load Test class %s", 1122 className), e); 1123 } catch (InvocationTargetException | NoSuchMethodException e) { 1124 throw new IllegalArgumentException( 1125 String.format("Could not load Test class %s", className), e); 1126 } 1127 } 1128 1129 /** 1130 * Helper for Device Runners to use to set options the same way as HostTest, from set-option. 1131 * 1132 * @param testObj the object that will receive the options. 1133 * @param keyValueOptions the list of options formatted as HostTest set-option requires. 1134 */ setOptionToLoadedObject(Object testObj, List<String> keyValueOptions)1135 public static void setOptionToLoadedObject(Object testObj, List<String> keyValueOptions) { 1136 if (!keyValueOptions.isEmpty()) { 1137 OptionSetter setter; 1138 try { 1139 setter = new OptionSetter(testObj); 1140 } catch (ConfigurationException ce) { 1141 CLog.e(ce); 1142 throw new RuntimeException("error creating option setter", ce); 1143 } 1144 for (String item : keyValueOptions) { 1145 // Support escaping ':' using lookbehind in the regex. The regex engine will 1146 // step backwards to check for the escape char when it matches the delim char. 1147 // If it doesn't find the escape char, then a match is registered. 1148 String delim = ":"; 1149 String esc = "\\"; 1150 String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim); 1151 String[] fields = item.split(regex); 1152 String key; 1153 String value; 1154 if (fields.length == 3) { 1155 String target = fields[0]; 1156 if (testObj.getClass().getName().equals(target)) { 1157 key = fields[1]; 1158 value = 1159 fields[2].replaceAll( 1160 Pattern.quote(esc) + Pattern.quote(delim), delim); 1161 } else { 1162 // TODO: We should track that all targeted option end up assigned 1163 // eventually. 1164 CLog.d( 1165 "Targeted option %s is not applicable to %s", 1166 item, testObj.getClass().getName()); 1167 continue; 1168 } 1169 } else if (fields.length == 2) { 1170 key = fields[0]; 1171 value = fields[1].replaceAll(Pattern.quote(esc) + Pattern.quote(delim), delim); 1172 } else { 1173 throw new RuntimeException(String.format("invalid option spec \"%s\"", item)); 1174 } 1175 try { 1176 injectOption(setter, item, key, value); 1177 } catch (ConfigurationException ce) { 1178 CLog.e(ce); 1179 throw new RuntimeException( 1180 "error passing option '" 1181 + item 1182 + "' down to test class as key=" 1183 + key 1184 + " value=" 1185 + value, 1186 ce); 1187 } 1188 } 1189 } 1190 } 1191 injectOption(OptionSetter setter, String origItem, String key, String value)1192 private static void injectOption(OptionSetter setter, String origItem, String key, String value) 1193 throws ConfigurationException { 1194 String esc = "\\"; 1195 String delim = "="; 1196 String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim); 1197 String escDelim = Pattern.quote(esc) + Pattern.quote(delim); 1198 String[] values = value.split(regex); 1199 if (values.length == 1) { 1200 setter.setOptionValue(key, values[0].replaceAll(escDelim, delim)); 1201 } else if (values.length == 2) { 1202 setter.setOptionValue( 1203 key, 1204 values[0].replaceAll(escDelim, delim), 1205 values[1].replaceAll(escDelim, delim)); 1206 } else { 1207 throw new RuntimeException( 1208 String.format( 1209 "set-option provided '%s' format is invalid. Only one " 1210 + "'=' is allowed", 1211 origItem)); 1212 } 1213 } 1214 1215 /** 1216 * Check if an elements that has annotation pass the filter. Exposed for unit testing. 1217 * @param annotatedElement 1218 * @return false if the test should not run. 1219 */ shouldTestRun(AnnotatedElement annotatedElement)1220 protected boolean shouldTestRun(AnnotatedElement annotatedElement) { 1221 return mFilterHelper.shouldTestRun(annotatedElement); 1222 } 1223 1224 /** 1225 * {@inheritDoc} 1226 */ 1227 @Override setCollectTestsOnly(boolean shouldCollectTest)1228 public void setCollectTestsOnly(boolean shouldCollectTest) { 1229 mCollectTestsOnly = shouldCollectTest; 1230 } 1231 1232 /** 1233 * Helper to determine if we are dealing with a Test class with Junit4 annotations. 1234 */ hasJUnit4Annotation(Class<?> classObj)1235 protected boolean hasJUnit4Annotation(Class<?> classObj) { 1236 if (classObj.isAnnotationPresent(SuiteClasses.class)) { 1237 return true; 1238 } 1239 if (classObj.isAnnotationPresent(RunWith.class)) { 1240 return true; 1241 } 1242 for (Method m : classObj.getMethods()) { 1243 if (m.isAnnotationPresent(org.junit.Test.class)) { 1244 return true; 1245 } 1246 } 1247 return false; 1248 } 1249 1250 /** 1251 * Helper method to apply all the filters to an IRemoteTest. 1252 */ applyFilters(Class<?> classObj, IRemoteTest test)1253 private void applyFilters(Class<?> classObj, IRemoteTest test) { 1254 Set<String> includes = mFilterHelper.getIncludeFilters(); 1255 if (mMethodName != null) { 1256 includes.add(String.format(TEST_FULL_NAME_FORMAT, classObj.getName(), mMethodName)); 1257 } 1258 Set<String> excludes = mFilterHelper.getExcludeFilters(); 1259 if (test instanceof ITestFilterReceiver) { 1260 ((ITestFilterReceiver) test).addAllIncludeFilters(includes); 1261 ((ITestFilterReceiver) test).addAllExcludeFilters(excludes); 1262 } else if (!includes.isEmpty() || !excludes.isEmpty()) { 1263 throw new IllegalArgumentException(String.format( 1264 "%s does not implement ITestFilterReceiver", classObj.getName())); 1265 } 1266 if (test instanceof ITestAnnotationFilterReceiver) { 1267 ((ITestAnnotationFilterReceiver) test).addAllIncludeAnnotation( 1268 mIncludeAnnotations); 1269 ((ITestAnnotationFilterReceiver) test).addAllExcludeAnnotation( 1270 mExcludeAnnotations); 1271 } 1272 } 1273 1274 /** We split by individual by either test class or method. */ 1275 @Override split(Integer shardCount, TestInformation testInfo)1276 public Collection<IRemoteTest> split(Integer shardCount, TestInformation testInfo) { 1277 if (shardCount == null) { 1278 return null; 1279 } 1280 if (shardCount < 1) { 1281 throw new IllegalArgumentException("Must have at least 1 shard"); 1282 } 1283 mTestInfo = testInfo; 1284 List<IRemoteTest> listTests = new ArrayList<>(); 1285 try { 1286 List<Class<?>> classes = getClasses(); 1287 if (classes.isEmpty()) { 1288 throw new IllegalArgumentException("Missing Test class name"); 1289 } 1290 if (mMethodName != null && classes.size() > 1) { 1291 throw new IllegalArgumentException("Method name given with multiple test classes"); 1292 } 1293 List<? extends Object> testObjects; 1294 if (shardUnitIsMethod()) { 1295 testObjects = getTestMethods(); 1296 } else { 1297 testObjects = classes; 1298 // ignore shardCount when shard unit is class; 1299 // simply shard by the number of classes 1300 shardCount = testObjects.size(); 1301 } 1302 if (testObjects.size() == 1) { 1303 return null; 1304 } 1305 int i = 0; 1306 int numTotalTestCases = countTestCases(); 1307 for (Object testObj : testObjects) { 1308 Class<?> classObj = Class.class.isInstance(testObj) ? (Class<?>) testObj : null; 1309 HostTest test; 1310 if (i >= listTests.size()) { 1311 test = createHostTest(classObj); 1312 test.mRuntimeHint = 0; 1313 // Carry over non-annotation filters to shards. 1314 test.addAllExcludeFilters(mFilterHelper.getExcludeFilters()); 1315 test.addAllIncludeFilters(mFilterHelper.getIncludeFilters()); 1316 listTests.add(test); 1317 } 1318 test = (HostTest) listTests.get(i); 1319 Collection<? extends Object> subTests; 1320 if (classObj != null) { 1321 test.addClassName(classObj.getName()); 1322 test.mJars = this.mJars; 1323 subTests = test.mClasses; 1324 } else { 1325 test.addTestMethod(testObj); 1326 subTests = test.mTestMethods; 1327 } 1328 if (numTotalTestCases == 0) { 1329 // In case there is no tests left 1330 test.mRuntimeHint = 0L; 1331 } else { 1332 test.mRuntimeHint = mRuntimeHint * subTests.size() / numTotalTestCases; 1333 } 1334 i = (i + 1) % shardCount; 1335 } 1336 } finally { 1337 mLoadedClasses.clear(); 1338 for (URLClassLoader cl : mOpenClassLoaders) { 1339 StreamUtil.close(cl); 1340 } 1341 mOpenClassLoaders.clear(); 1342 } 1343 return listTests; 1344 } 1345 addTestMethod(Object testObject)1346 private void addTestMethod(Object testObject) { 1347 if (mTestMethods == null) { 1348 mTestMethods = new ArrayList<>(); 1349 mClasses.clear(); 1350 } 1351 mTestMethods.add(testObject); 1352 if (IRemoteTest.class.isInstance(testObject)) { 1353 addClassName(testObject.getClass().getName()); 1354 } else if (TestSuite.class.isInstance(testObject)) { 1355 addClassName(((TestSuite)testObject).getName()); 1356 } else if (Description.class.isInstance(testObject)) { 1357 addClassName(((Description)testObject).getTestClass().getName()); 1358 } 1359 } 1360 1361 /** 1362 * Add a class to be ran by HostTest. 1363 */ addClassName(String className)1364 private void addClassName(String className) { 1365 mClasses.add(className); 1366 } 1367 1368 /** 1369 * Helper to create a HostTest instance when sharding. Override to return any child from 1370 * HostTest. 1371 */ createHostTest(Class<?> classObj)1372 protected HostTest createHostTest(Class<?> classObj) { 1373 HostTest test; 1374 try { 1375 test = this.getClass().getDeclaredConstructor().newInstance(); 1376 } catch (InstantiationException 1377 | IllegalAccessException 1378 | InvocationTargetException 1379 | NoSuchMethodException e) { 1380 throw new RuntimeException(e); 1381 } 1382 OptionCopier.copyOptionsNoThrow(this, test); 1383 if (classObj != null) { 1384 test.setClassName(classObj.getName()); 1385 } 1386 // clean the jar option since we are loading directly from classes after. 1387 test.mJars = new HashSet<>(); 1388 // Copy the abi if available 1389 test.setAbi(mAbi); 1390 return test; 1391 } 1392 getClassName(String name)1393 private String getClassName(String name) { 1394 // -6 because of .class 1395 return name.substring(0, name.length() - 6).replace('/', '.'); 1396 } 1397 1398 /** 1399 * Inspect several location where the artifact are usually located for different use cases to 1400 * find our jar. 1401 */ 1402 @VisibleForTesting getJarFile(String jarName, TestInformation testInfo)1403 protected File getJarFile(String jarName, TestInformation testInfo) 1404 throws FileNotFoundException { 1405 return testInfo.getDependencyFile(jarName, /* target first*/ false); 1406 } 1407 1408 @VisibleForTesting createResolver()1409 DynamicRemoteFileResolver createResolver() { 1410 DynamicRemoteFileResolver resolver = new DynamicRemoteFileResolver(); 1411 resolver.setDevice(mDevice); 1412 resolver.addExtraArgs(mConfig.getCommandOptions().getDynamicDownloadArgs()); 1413 return resolver; 1414 } 1415 resolveRemoteFileForObject(Object obj)1416 private Set<File> resolveRemoteFileForObject(Object obj) { 1417 try (CloseableTraceScope ignore = new CloseableTraceScope("infra:resolveRemoteFiles")) { 1418 OptionSetter setter = new OptionSetter(obj); 1419 return setter.validateRemoteFilePath(createResolver()); 1420 } catch (BuildRetrievalError | ConfigurationException e) { 1421 throw new RuntimeException(e); 1422 } 1423 } 1424 createFromException(Throwable exception)1425 private FailureDescription createFromException(Throwable exception) { 1426 FailureDescription failure = 1427 CurrentInvocation.createFailure(StreamUtil.getStackTrace(exception), null) 1428 .setCause(exception); 1429 if (exception instanceof IHarnessException) { 1430 ErrorIdentifier id = ((IHarnessException) exception).getErrorId(); 1431 failure.setErrorIdentifier(id); 1432 if (id != null) { 1433 failure.setFailureStatus(id.status()); 1434 } 1435 failure.setOrigin(((IHarnessException) exception).getOrigin()); 1436 } 1437 return failure; 1438 } 1439 } 1440