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