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