• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.tradefed.testtype;
18 
19 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
20 import com.android.tradefed.config.ConfigurationException;
21 import com.android.tradefed.config.IConfiguration;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.config.OptionCopier;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.device.metric.target.DeviceSideCollectorSpecification;
28 import com.android.tradefed.log.LogUtil.CLog;
29 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
30 import com.android.tradefed.result.ITestInvocationListener;
31 import com.android.tradefed.util.ArrayUtil;
32 import com.android.tradefed.util.ListInstrumentationParser;
33 
34 import com.google.common.annotations.VisibleForTesting;
35 import com.google.inject.Inject;
36 
37 import org.junit.runner.notification.RunListener;
38 
39 import java.io.File;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.regex.Pattern;
47 import java.util.regex.PatternSyntaxException;
48 import java.util.Set;
49 
50 /**
51  * A Test that runs an instrumentation test package on given device using the
52  * android.support.test.runner.AndroidJUnitRunner.
53  */
54 @OptionClass(alias = "android-junit")
55 public class AndroidJUnitTest extends InstrumentationTest
56         implements IRuntimeHintProvider,
57                 ITestFileFilterReceiver,
58                 ITestFilterReceiver,
59                 ITestAnnotationFilterReceiver,
60                 IShardableTest {
61 
62     /** instrumentation test runner argument key used for including a class/test */
63     private static final String INCLUDE_CLASS_INST_ARGS_KEY = "class";
64     /** instrumentation test runner argument key used for excluding a class/test */
65     private static final String EXCLUDE_CLASS_INST_ARGS_KEY = "notClass";
66     /** instrumentation test runner argument key used for including a package */
67     private static final String INCLUDE_PACKAGE_INST_ARGS_KEY = "package";
68     /** instrumentation test runner argument key used for excluding a package */
69     private static final String EXCLUDE_PACKAGE_INST_ARGS_KEY = "notPackage";
70     /** instrumentation test runner argument key used for including a test regex */
71     private static final String INCLUDE_REGEX_INST_ARGS_KEY = "tests_regex";
72     /** instrumentation test runner argument key used for adding annotation filter */
73     private static final String ANNOTATION_INST_ARGS_KEY = "annotation";
74     /** instrumentation test runner argument key used for adding notAnnotation filter */
75     private static final String NOT_ANNOTATION_INST_ARGS_KEY = "notAnnotation";
76     /** instrumentation test runner argument used for adding testFile filter */
77     private static final String TEST_FILE_INST_ARGS_KEY = "testFile";
78     /** instrumentation test runner argument used for adding notTestFile filter */
79     private static final String NOT_TEST_FILE_INST_ARGS_KEY = "notTestFile";
80     /** instrumentation test runner argument used to specify the shardIndex of the test */
81     private static final String SHARD_INDEX_INST_ARGS_KEY = "shardIndex";
82     /** instrumentation test runner argument used to specify the total number of shards */
83     private static final String NUM_SHARD_INST_ARGS_KEY = "numShards";
84     /**
85      * instrumentation test runner argument used to enable the new {@link RunListener} order on
86      * device side.
87      */
88     public static final String NEW_RUN_LISTENER_ORDER_KEY = "newRunListenerMode";
89 
90     /** Options from the collector side helper library. */
91     public static final String INCLUDE_COLLECTOR_FILTER_KEY = "include-filter-group";
92 
93     public static final String EXCLUDE_COLLECTOR_FILTER_KEY = "exclude-filter-group";
94 
95     private static final String INCLUDE_FILE = "includes.txt";
96     private static final String EXCLUDE_FILE = "excludes.txt";
97 
98     @Option(name = "runtime-hint",
99             isTimeVal=true,
100             description="The hint about the test's runtime.")
101     private long mRuntimeHint = 60000;// 1 minute
102 
103     @Option(
104             name = "include-filter",
105             description = "The include filters of the test name to run.",
106             requiredForRerun = true)
107     private Set<String> mIncludeFilters = new HashSet<>();
108 
109     @Option(
110             name = "exclude-filter",
111             description = "The exclude filters of the test name to run.",
112             requiredForRerun = true)
113     private Set<String> mExcludeFilters = new HashSet<>();
114 
115     @Option(
116             name = "include-annotation",
117             description = "The annotation class name of the test name to run, can be repeated",
118             requiredForRerun = true)
119     private Set<String> mIncludeAnnotation = new HashSet<>();
120 
121     @Option(
122             name = "exclude-annotation",
123             description = "The notAnnotation class name of the test name to run, can be repeated",
124             requiredForRerun = true)
125     private Set<String> mExcludeAnnotation = new HashSet<>();
126 
127     @Option(name = "test-file-include-filter",
128             description="A file containing a list of line separated test classes and optionally"
129             + " methods to include")
130     private File mIncludeTestFile = null;
131 
132     @Option(name = "test-file-exclude-filter",
133             description="A file containing a list of line separated test classes and optionally"
134             + " methods to exclude")
135     private File mExcludeTestFile = null;
136 
137     @Option(name = "test-filter-dir",
138             description="The device directory path to which the test filtering files are pushed")
139     private String mTestFilterDir = "/data/local/tmp/ajur";
140 
141     @Option(
142         name = "ajur-max-shard",
143         description = "The maximum number of shard we want to allow the AJUR test to shard into"
144     )
145     private Integer mMaxShard = null;
146 
147     @Option(
148             name = "device-listeners",
149             description =
150                     "Specify a device side instrumentation listener to be added for the run. "
151                             + "Can be repeated.")
152     private Set<String> mExtraDeviceListeners = new HashSet<>();
153 
154     @Option(
155         name = "use-new-run-listener-order",
156         description = "Enables the new RunListener Order for AJUR."
157     )
158     // Default to true as it is harmless if not supported.
159     private boolean mNewRunListenerOrderMode = true;
160 
161     private String mDeviceIncludeFile = null;
162     private String mDeviceExcludeFile = null;
163     private int mTotalShards = 0;
164     private int mShardIndex = 0;
165     // Flag to avoid re-sharding a test that already was.
166     private boolean mIsSharded = false;
167 
168     // Special object that can tune some device side aspects.
169     private DeviceSideCollectorSpecification mDeviceSideSpec = null;
170 
AndroidJUnitTest()171     public AndroidJUnitTest() {
172         super();
173         setEnforceFormat(true);
174     }
175 
176     /** Guice-injected object, that can influence the instrumentation args. */
177     @Inject
setDeviceSpec(IConfiguration spec)178     public void setDeviceSpec(IConfiguration spec) {
179         if (spec.getDeviceSideCollectorsSpec() != null) {
180             mDeviceSideSpec = spec.getDeviceSideCollectorsSpec();
181         }
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
getRuntimeHint()188     public long getRuntimeHint() {
189         return mRuntimeHint;
190     }
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
addIncludeFilter(String filter)196     public void addIncludeFilter(String filter) {
197         mIncludeFilters.add(filter);
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
addAllIncludeFilters(Set<String> filters)204     public void addAllIncludeFilters(Set<String> filters) {
205         mIncludeFilters.addAll(filters);
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     @Override
addExcludeFilter(String filter)212     public void addExcludeFilter(String filter) {
213         mExcludeFilters.add(filter);
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
addAllExcludeFilters(Set<String> filters)220     public void addAllExcludeFilters(Set<String> filters) {
221         mExcludeFilters.addAll(filters);
222     }
223 
224     /** {@inheritDoc} */
225     @Override
clearIncludeFilters()226     public void clearIncludeFilters() {
227         mIncludeFilters.clear();
228     }
229 
230     /** {@inheritDoc} */
231     @Override
getIncludeFilters()232     public Set<String> getIncludeFilters() {
233         return mIncludeFilters;
234     }
235 
236     /** {@inheritDoc} */
237     @Override
getExcludeFilters()238     public Set<String> getExcludeFilters() {
239         return mExcludeFilters;
240     }
241 
242     /** {@inheritDoc} */
243     @Override
clearExcludeFilters()244     public void clearExcludeFilters() {
245         mExcludeFilters.clear();
246     }
247 
248     /** {@inheritDoc} */
249     @Override
setIncludeTestFile(File testFile)250     public void setIncludeTestFile(File testFile) {
251         mIncludeTestFile = testFile;
252     }
253 
254     /**
255      * {@inheritDoc}
256      */
257     @Override
setExcludeTestFile(File testFile)258     public void setExcludeTestFile(File testFile) {
259         mExcludeTestFile = testFile;
260     }
261 
262     /**
263      * {@inheritDoc}
264      */
265     @Override
addIncludeAnnotation(String annotation)266     public void addIncludeAnnotation(String annotation) {
267         mIncludeAnnotation.add(annotation);
268     }
269 
270     /**
271      * {@inheritDoc}
272      */
273     @Override
addAllIncludeAnnotation(Set<String> annotations)274     public void addAllIncludeAnnotation(Set<String> annotations) {
275         mIncludeAnnotation.addAll(annotations);
276     }
277 
278     /**
279      * {@inheritDoc}
280      */
281     @Override
addExcludeAnnotation(String excludeAnnotation)282     public void addExcludeAnnotation(String excludeAnnotation) {
283         mExcludeAnnotation.add(excludeAnnotation);
284     }
285 
286     /**
287      * {@inheritDoc}
288      */
289     @Override
addAllExcludeAnnotation(Set<String> excludeAnnotations)290     public void addAllExcludeAnnotation(Set<String> excludeAnnotations) {
291         mExcludeAnnotation.addAll(excludeAnnotations);
292     }
293 
294     /** {@inheritDoc} */
295     @Override
getIncludeAnnotations()296     public Set<String> getIncludeAnnotations() {
297         return mIncludeAnnotation;
298     }
299 
300     /** {@inheritDoc} */
301     @Override
getExcludeAnnotations()302     public Set<String> getExcludeAnnotations() {
303         return mExcludeAnnotation;
304     }
305 
306     /** {@inheritDoc} */
307     @Override
clearIncludeAnnotations()308     public void clearIncludeAnnotations() {
309         mIncludeAnnotation.clear();
310     }
311 
312     /** {@inheritDoc} */
313     @Override
clearExcludeAnnotations()314     public void clearExcludeAnnotations() {
315         mExcludeAnnotation.clear();
316     }
317 
318     /**
319      * {@inheritDoc}
320      */
321     @Override
run(final ITestInvocationListener listener)322     public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException {
323         if (getDevice() == null) {
324             throw new IllegalArgumentException("Device has not been set");
325         }
326         boolean pushedFile = false;
327         // if mIncludeTestFile is set, perform filtering with this file
328         if (mIncludeTestFile != null && mIncludeTestFile.length() > 0) {
329             mDeviceIncludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + INCLUDE_FILE;
330             pushTestFile(mIncludeTestFile, mDeviceIncludeFile, listener);
331             pushedFile = true;
332             // If an explicit include file filter is provided, do not use the package
333             setTestPackageName(null);
334         }
335 
336         // if mExcludeTestFile is set, perform filtering with this file
337         if (mExcludeTestFile != null && mExcludeTestFile.length() > 0) {
338             mDeviceExcludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + EXCLUDE_FILE;
339             pushTestFile(mExcludeTestFile, mDeviceExcludeFile, listener);
340             pushedFile = true;
341         }
342         if (mTotalShards > 0 && !isShardable() && mShardIndex != 0) {
343             // If not shardable, only first shard can run.
344             CLog.i("%s is not shardable.", getRunnerName());
345             return;
346         }
347         super.run(listener);
348         if (pushedFile) {
349             // Remove the directory where the files where pushed
350             removeTestFilterDir();
351         }
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     @Override
setRunnerArgs(IRemoteAndroidTestRunner runner)358     protected void setRunnerArgs(IRemoteAndroidTestRunner runner) {
359         super.setRunnerArgs(runner);
360 
361         // if mIncludeTestFile is set, perform filtering with this file
362         if (mDeviceIncludeFile != null) {
363             runner.addInstrumentationArg(TEST_FILE_INST_ARGS_KEY, mDeviceIncludeFile);
364         }
365 
366         // if mExcludeTestFile is set, perform filtering with this file
367         if (mDeviceExcludeFile != null) {
368             runner.addInstrumentationArg(NOT_TEST_FILE_INST_ARGS_KEY, mDeviceExcludeFile);
369         }
370 
371         // Split filters into class, notClass, package and notPackage
372         List<String> classArg = new ArrayList<String>();
373         List<String> notClassArg = new ArrayList<String>();
374         List<String> packageArg = new ArrayList<String>();
375         List<String> notPackageArg = new ArrayList<String>();
376         List<String> regexArg = new ArrayList<String>();
377         for (String test : mIncludeFilters) {
378             if (isRegex(test)) {
379                 regexArg.add(test);
380             } else if (isClassOrMethod(test)) {
381                 classArg.add(test);
382             } else {
383                 packageArg.add(test);
384             }
385         }
386         for (String test : mExcludeFilters) {
387             // tests_regex doesn't support exclude-filter. Therefore, only check if the filter is
388             // for class/method or package.
389             if (isClassOrMethod(test)) {
390                 notClassArg.add(test);
391             } else {
392                 notPackageArg.add(test);
393             }
394         }
395         if (!classArg.isEmpty()) {
396             runner.addInstrumentationArg(INCLUDE_CLASS_INST_ARGS_KEY,
397                     ArrayUtil.join(",", classArg));
398         }
399         if (!notClassArg.isEmpty()) {
400             runner.addInstrumentationArg(EXCLUDE_CLASS_INST_ARGS_KEY,
401                     ArrayUtil.join(",", notClassArg));
402         }
403         if (!packageArg.isEmpty()) {
404             runner.addInstrumentationArg(INCLUDE_PACKAGE_INST_ARGS_KEY,
405                     ArrayUtil.join(",", packageArg));
406         }
407         if (!notPackageArg.isEmpty()) {
408             runner.addInstrumentationArg(EXCLUDE_PACKAGE_INST_ARGS_KEY,
409                     ArrayUtil.join(",", notPackageArg));
410         }
411         if (!regexArg.isEmpty()) {
412             String regexFilter;
413             if (regexArg.size() == 1) {
414                 regexFilter = regexArg.get(0);
415             } else {
416                 Collections.sort(regexArg);
417                 regexFilter = "\"(" + ArrayUtil.join("|", regexArg) + ")\"";
418             }
419             runner.addInstrumentationArg(INCLUDE_REGEX_INST_ARGS_KEY, regexFilter);
420         }
421         if (!mIncludeAnnotation.isEmpty()) {
422             runner.addInstrumentationArg(ANNOTATION_INST_ARGS_KEY,
423                     ArrayUtil.join(",", mIncludeAnnotation));
424         }
425         if (!mExcludeAnnotation.isEmpty()) {
426             runner.addInstrumentationArg(NOT_ANNOTATION_INST_ARGS_KEY,
427                     ArrayUtil.join(",", mExcludeAnnotation));
428         }
429         if (mTotalShards > 0 && isShardable()) {
430             runner.addInstrumentationArg(SHARD_INDEX_INST_ARGS_KEY, Integer.toString(mShardIndex));
431             runner.addInstrumentationArg(NUM_SHARD_INST_ARGS_KEY, Integer.toString(mTotalShards));
432         }
433         if (mNewRunListenerOrderMode) {
434             runner.addInstrumentationArg(
435                     NEW_RUN_LISTENER_ORDER_KEY, Boolean.toString(mNewRunListenerOrderMode));
436         }
437 
438         // Load the device side configuration from Guice
439         if (mDeviceSideSpec != null) {
440             CLog.d("Got a DeviceSideCollectorSpecification from Guice Tradefed.");
441             mExtraDeviceListeners.addAll(mDeviceSideSpec.getCollectorNames());
442             for (String key : mDeviceSideSpec.getCollectorOptions().keySet()) {
443                 runner.addInstrumentationArg(
444                         key, ArrayUtil.join(",", mDeviceSideSpec.getCollectorOptions().get(key)));
445             }
446             if (!mDeviceSideSpec.getExcludeGroupFilters().isEmpty()) {
447                 runner.addInstrumentationArg(
448                         EXCLUDE_COLLECTOR_FILTER_KEY,
449                         ArrayUtil.join(",", mDeviceSideSpec.getExcludeGroupFilters()));
450             }
451             if (!mDeviceSideSpec.getIncludeGroupFilters().isEmpty()) {
452                 runner.addInstrumentationArg(
453                         INCLUDE_COLLECTOR_FILTER_KEY,
454                         ArrayUtil.join(",", mDeviceSideSpec.getIncludeGroupFilters()));
455             }
456         }
457         // Add the listeners received from Options
458         addDeviceListeners(mExtraDeviceListeners);
459     }
460 
461     /**
462      * Push the testFile to the requested destination. This should only be called for a non-null
463      * testFile
464      *
465      * @param testFile file to be pushed from the host to the device.
466      * @param destination the path on the device to which testFile is pushed
467      * @param listener {@link ITestInvocationListener} to report failures.
468      */
pushTestFile(File testFile, String destination, ITestInvocationListener listener)469     private void pushTestFile(File testFile, String destination, ITestInvocationListener listener)
470             throws DeviceNotAvailableException {
471         if (!testFile.canRead() || !testFile.isFile()) {
472             String message = String.format("Cannot read test file %s", testFile.getAbsolutePath());
473             reportEarlyFailure(listener, message);
474             throw new IllegalArgumentException(message);
475         }
476         ITestDevice device = getDevice();
477         try {
478             CLog.d("Attempting to push filters to %s", destination);
479             if (!device.pushFile(testFile, destination)) {
480                 String message =
481                         String.format(
482                                 "Failed to push file %s to %s for %s in pushTestFile",
483                                 testFile.getAbsolutePath(), destination, device.getSerialNumber());
484                 reportEarlyFailure(listener, message);
485                 throw new RuntimeException(message);
486             }
487             // in case the folder was created as 'root' we make is usable.
488             device.executeShellCommand(String.format("chown -R shell:shell %s", mTestFilterDir));
489         } catch (DeviceNotAvailableException e) {
490             reportEarlyFailure(listener, e.getMessage());
491             throw e;
492         }
493     }
494 
removeTestFilterDir()495     private void removeTestFilterDir() throws DeviceNotAvailableException {
496         getDevice().deleteFile(mTestFilterDir);
497     }
498 
reportEarlyFailure(ITestInvocationListener listener, String errorMessage)499     private void reportEarlyFailure(ITestInvocationListener listener, String errorMessage) {
500         listener.testRunStarted("AndroidJUnitTest_setupError", 0);
501         listener.testRunFailed(errorMessage);
502         listener.testRunEnded(0, new HashMap<String, Metric>());
503     }
504 
505     /**
506      * Return if a string is the name of a Class or a Method.
507      */
508     @VisibleForTesting
isClassOrMethod(String filter)509     public boolean isClassOrMethod(String filter) {
510         if (filter.contains("#")) {
511             return true;
512         }
513         String[] parts = filter.split("\\.");
514         if (parts.length > 0) {
515             // FIXME Assume java package names starts with lowercase and class names start with
516             // uppercase.
517             // Return true iff the first character of the last word is uppercase
518             // com.android.foobar.Test
519             return Character.isUpperCase(parts[parts.length - 1].charAt(0));
520         }
521         return false;
522     }
523 
524     /** Return if a string is a regex for filter. */
525     @VisibleForTesting
isRegex(String filter)526     public boolean isRegex(String filter) {
527         // If filter contains any special regex character, return true.
528         // Throw RuntimeException if the regex is invalid.
529         if (Pattern.matches(".*[\\?\\*\\^\\$\\(\\)\\[\\]\\{\\}\\|\\\\].*", filter)) {
530             try {
531                 Pattern.compile(filter);
532             } catch (PatternSyntaxException e) {
533                 CLog.e("Filter %s is not a valid regular expression string.", filter);
534                 throw new RuntimeException(e);
535             }
536             return true;
537         }
538 
539         return false;
540     }
541 
542     /**
543      * Helper to return if the runner is one that support sharding.
544      */
isShardable()545     private boolean isShardable() {
546         // Edge toward shardable if no explicit runner specified. The runner will be determined
547         // later and if not shardable only the first shard will run.
548         if (getRunnerName() == null) {
549             return true;
550         }
551         return ListInstrumentationParser.SHARDABLE_RUNNERS.contains(getRunnerName());
552     }
553 
554     /** {@inheritDoc} */
555     @Override
split(int shardCount)556     public Collection<IRemoteTest> split(int shardCount) {
557         if (!isShardable()) {
558             return null;
559         }
560         if (mMaxShard != null) {
561             shardCount = Math.min(shardCount, mMaxShard);
562         }
563         if (!mIsSharded && shardCount > 1) {
564             mIsSharded = true;
565             Collection<IRemoteTest> shards = new ArrayList<>(shardCount);
566             for (int index = 0; index < shardCount; index++) {
567                 shards.add(getTestShard(shardCount, index));
568             }
569             return shards;
570         }
571         return null;
572     }
573 
getTestShard(int shardCount, int shardIndex)574     private IRemoteTest getTestShard(int shardCount, int shardIndex) {
575         AndroidJUnitTest shard;
576         // ensure we handle runners that extend AndroidJUnitRunner
577         try {
578             shard = this.getClass().newInstance();
579         } catch (InstantiationException | IllegalAccessException e) {
580             throw new RuntimeException(e);
581         }
582         try {
583             OptionCopier.copyOptions(this, shard);
584         } catch (ConfigurationException e) {
585             CLog.e("Failed to copy instrumentation options: %s", e.getMessage());
586         }
587         shard.mShardIndex = shardIndex;
588         shard.mTotalShards = shardCount;
589         shard.mIsSharded = true;
590         shard.setAbi(getAbi());
591         // We approximate the runtime of each shard to be equal since we can't know.
592         shard.mRuntimeHint = mRuntimeHint / shardCount;
593         return shard;
594     }
595 }
596