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