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