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