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