Home | History | Annotate | Download | only in testtype
      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 
    181     public HostTest() {
    182         mFilterHelper = new TestFilterHelper(new ArrayList<String>(), new ArrayList<String>(),
    183                 mIncludeAnnotations, mExcludeAnnotations);
    184     }
    185 
    186     /**
    187      * {@inheritDoc}
    188      */
    189     @Override
    190     public ITestDevice getDevice() {
    191         return mDevice;
    192     }
    193 
    194     /**
    195      * {@inheritDoc}
    196      */
    197     @Override
    198     public void setDevice(ITestDevice device) {
    199         mDevice = device;
    200     }
    201 
    202     /** {@inheritDoc} */
    203     @Override
    204     public long getRuntimeHint() {
    205         return mRuntimeHint;
    206     }
    207 
    208     /** {@inheritDoc} */
    209     @Override
    210     public void setAbi(IAbi abi) {
    211         mAbi = abi;
    212     }
    213 
    214     /** {@inheritDoc} */
    215     @Override
    216     public IAbi getAbi() {
    217         return mAbi;
    218     }
    219 
    220     /**
    221      * {@inheritDoc}
    222      */
    223     @Override
    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      */
    233     protected IBuildInfo getBuild() {
    234         return mBuildInfo;
    235     }
    236 
    237     @Override
    238     public void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) {
    239         mDeviceInfos = deviceInfos;
    240     }
    241 
    242     @Override
    243     public void setInvocationContext(IInvocationContext invocationContext) {
    244         mContext = invocationContext;
    245     }
    246 
    247     /**
    248      * @return true if shard-unit is method; false otherwise
    249      */
    250     private boolean shardUnitIsMethod() {
    251         return ShardUnit.METHOD.equals(mShardUnit);
    252     }
    253 
    254     /**
    255      * {@inheritDoc}
    256      */
    257     @Override
    258     public void addIncludeFilter(String filter) {
    259         mFilterHelper.addIncludeFilter(filter);
    260     }
    261 
    262     /**
    263      * {@inheritDoc}
    264      */
    265     @Override
    266     public void addAllIncludeFilters(Set<String> filters) {
    267         mFilterHelper.addAllIncludeFilters(filters);
    268     }
    269 
    270     /**
    271      * {@inheritDoc}
    272      */
    273     @Override
    274     public void addExcludeFilter(String filter) {
    275         mFilterHelper.addExcludeFilter(filter);
    276     }
    277 
    278     /**
    279      * {@inheritDoc}
    280      */
    281     @Override
    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      */
    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      */
    340     protected void setClassName(String className) {
    341         mClasses.clear();
    342         mClasses.add(className);
    343     }
    344 
    345     @VisibleForTesting
    346     public Set<String> getClassNames() {
    347         return mClasses;
    348     }
    349 
    350     void setMethodName(String methodName) {
    351         mMethodName = methodName;
    352     }
    353 
    354     /**
    355      * {@inheritDoc}
    356      */
    357     @Override
    358     public void addIncludeAnnotation(String annotation) {
    359         mIncludeAnnotations.add(annotation);
    360         mFilterHelper.addIncludeAnnotation(annotation);
    361     }
    362 
    363     /**
    364      * {@inheritDoc}
    365      */
    366     @Override
    367     public void addAllIncludeAnnotation(Set<String> annotations) {
    368         mIncludeAnnotations.addAll(annotations);
    369         mFilterHelper.addAllIncludeAnnotation(annotations);
    370     }
    371 
    372     /**
    373      * {@inheritDoc}
    374      */
    375     @Override
    376     public void addExcludeAnnotation(String notAnnotation) {
    377         mExcludeAnnotations.add(notAnnotation);
    378         mFilterHelper.addExcludeAnnotation(notAnnotation);
    379     }
    380 
    381     /**
    382      * {@inheritDoc}
    383      */
    384     @Override
    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      */
    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
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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
    790     protected ClassLoader getClassLoader() {
    791         return this.getClass().getClassLoader();
    792     }
    793 
    794     /** load the class object and set the test info (device, build). */
    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      */
    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      */
    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      */
    868     protected boolean shouldTestRun(AnnotatedElement annotatedElement) {
    869         return mFilterHelper.shouldTestRun(annotatedElement);
    870     }
    871 
    872     /**
    873      * {@inheritDoc}
    874      */
    875     @Override
    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      */
    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      */
    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
    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 
    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      */
   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      */
   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
   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 
   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 
   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
   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 
   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