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