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