• 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.ConfigurationDescriptor;
21 import com.android.tradefed.config.ConfigurationException;
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.invoker.TestInformation;
28 import com.android.tradefed.invoker.tracing.CloseableTraceScope;
29 import com.android.tradefed.log.LogUtil.CLog;
30 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
31 import com.android.tradefed.result.FailureDescription;
32 import com.android.tradefed.result.FileInputStreamSource;
33 import com.android.tradefed.result.ITestInvocationListener;
34 import com.android.tradefed.result.LogDataType;
35 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
36 import com.android.tradefed.targetprep.BuildError;
37 import com.android.tradefed.targetprep.TargetSetupError;
38 import com.android.tradefed.targetprep.TestAppInstallSetup;
39 import com.android.tradefed.testtype.suite.params.InstantAppHandler;
40 import com.android.tradefed.util.ArrayUtil;
41 import com.android.tradefed.util.CommandResult;
42 import com.android.tradefed.util.FileUtil;
43 import com.android.tradefed.util.ListInstrumentationParser;
44 import com.android.tradefed.util.ResourceUtil;
45 
46 import com.google.common.annotations.VisibleForTesting;
47 
48 import org.junit.runner.notification.RunListener;
49 
50 import java.io.File;
51 import java.io.IOException;
52 import java.lang.reflect.InvocationTargetException;
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.LinkedHashSet;
58 import java.util.List;
59 import java.util.Set;
60 import java.util.regex.Pattern;
61 import java.util.regex.PatternSyntaxException;
62 
63 /**
64  * A Test that runs an instrumentation test package on given device using the
65  * android.support.test.runner.AndroidJUnitRunner.
66  */
67 @OptionClass(alias = "android-junit")
68 public class AndroidJUnitTest extends InstrumentationTest
69         implements IRuntimeHintProvider,
70                 ITestFileFilterReceiver,
71                 ITestFilterReceiver,
72                 ITestAnnotationFilterReceiver,
73                 IShardableTest {
74 
75     /** instrumentation test runner argument key used for including a class/test */
76     private static final String INCLUDE_CLASS_INST_ARGS_KEY = "class";
77     /** instrumentation test runner argument key used for excluding a class/test */
78     private static final String EXCLUDE_CLASS_INST_ARGS_KEY = "notClass";
79     /** instrumentation test runner argument key used for including a package */
80     private static final String INCLUDE_PACKAGE_INST_ARGS_KEY = "package";
81     /** instrumentation test runner argument key used for excluding a package */
82     private static final String EXCLUDE_PACKAGE_INST_ARGS_KEY = "notPackage";
83     /** instrumentation test runner argument key used for including a test regex */
84     private static final String INCLUDE_REGEX_INST_ARGS_KEY = "tests_regex";
85     /** instrumentation test runner argument key used for adding annotation filter */
86     private static final String ANNOTATION_INST_ARGS_KEY = "annotation";
87     /** instrumentation test runner argument key used for adding notAnnotation filter */
88     private static final String NOT_ANNOTATION_INST_ARGS_KEY = "notAnnotation";
89     /** instrumentation test runner argument used for adding testFile filter */
90     private static final String TEST_FILE_INST_ARGS_KEY = "testFile";
91     /** instrumentation test runner argument used for adding notTestFile filter */
92     private static final String NOT_TEST_FILE_INST_ARGS_KEY = "notTestFile";
93     /** instrumentation test runner argument used to specify the shardIndex of the test */
94     private static final String SHARD_INDEX_INST_ARGS_KEY = "shardIndex";
95     /** instrumentation test runner argument used to specify the total number of shards */
96     private static final String NUM_SHARD_INST_ARGS_KEY = "numShards";
97     /**
98      * instrumentation test runner argument used to enable the new {@link RunListener} order on
99      * device side.
100      */
101     public static final String NEW_RUN_LISTENER_ORDER_KEY = "newRunListenerMode";
102 
103     public static final String USE_TEST_STORAGE_SERVICE = "useTestStorageService";
104 
105     /** Options from the collector side helper library. */
106     public static final String INCLUDE_COLLECTOR_FILTER_KEY = "include-filter-group";
107 
108     public static final String EXCLUDE_COLLECTOR_FILTER_KEY = "exclude-filter-group";
109 
110     private static final String INCLUDE_FILE = "includes.txt";
111     private static final String EXCLUDE_FILE = "excludes.txt";
112 
113     @Option(name = "runtime-hint",
114             isTimeVal=true,
115             description="The hint about the test's runtime.")
116     private long mRuntimeHint = 60000;// 1 minute
117 
118     @Option(
119             name = "include-filter",
120             description = "The include filters of the test name to run.",
121             requiredForRerun = true)
122     private Set<String> mIncludeFilters = new LinkedHashSet<>();
123 
124     @Option(
125             name = "exclude-filter",
126             description = "The exclude filters of the test name to run.",
127             requiredForRerun = true)
128     private Set<String> mExcludeFilters = new LinkedHashSet<>();
129 
130     @Option(
131             name = "include-annotation",
132             description = "The annotation class name of the test name to run, can be repeated",
133             requiredForRerun = true)
134     private Set<String> mIncludeAnnotation = new LinkedHashSet<>();
135 
136     @Option(
137             name = "exclude-annotation",
138             description = "The notAnnotation class name of the test name to run, can be repeated",
139             requiredForRerun = true)
140     private Set<String> mExcludeAnnotation = new LinkedHashSet<>();
141 
142     @Option(name = "test-filter-dir",
143             description="The device directory path to which the test filtering files are pushed")
144     private String mTestFilterDir = "/data/local/tmp/ajur";
145 
146     @Option(
147             name = "test-storage-dir",
148             description = "The device directory path where test storage read files.")
149     private String mTestStorageInternalDir = "/sdcard/googletest/test_runfiles";
150 
151     @Option(
152             name = "use-test-storage",
153             description =
154                     "If set to true, we will push filters to the test storage instead of disk.")
155     private boolean mUseTestStorage = true;
156 
157     @Option(
158             name = "ajur-max-shard",
159             description =
160                     "The maximum number of shard we want to allow the AJUR test to shard into")
161     private Integer mMaxShard = 4;
162 
163     @Option(
164             name = "device-listeners",
165             description =
166                     "Specify device side instrumentation listeners to be added for the run. "
167                             + "Can be repeated. Note that while the ordering here is followed for "
168                             + "now, future versions of AndroidJUnitRunner might not preserve the "
169                             + "listener ordering.")
170     private Set<String> mExtraDeviceListeners = new LinkedHashSet<>();
171 
172     @Option(
173         name = "use-new-run-listener-order",
174         description = "Enables the new RunListener Order for AJUR."
175     )
176     // Default to true as it is harmless if not supported.
177     private boolean mNewRunListenerOrderMode = true;
178 
179     private File mInternalIncludeTestFile = null;
180     private File mInternalExcludeTestFile = null;
181     private String mDeviceIncludeFile = null;
182     private String mDeviceExcludeFile = null;
183     private int mTotalShards = 0;
184     private int mShardIndex = 0;
185     // Flag to avoid re-sharding a test that already was.
186     private boolean mIsSharded = false;
187 
AndroidJUnitTest()188     public AndroidJUnitTest() {
189         super();
190         setEnforceFormat(true);
191     }
192 
193     /**
194      * {@inheritDoc}
195      */
196     @Override
getRuntimeHint()197     public long getRuntimeHint() {
198         return mRuntimeHint;
199     }
200 
201     /**
202      * {@inheritDoc}
203      */
204     @Override
addIncludeFilter(String filter)205     public void addIncludeFilter(String filter) {
206         mIncludeFilters.add(filter);
207     }
208 
209     /**
210      * {@inheritDoc}
211      */
212     @Override
addAllIncludeFilters(Set<String> filters)213     public void addAllIncludeFilters(Set<String> filters) {
214         mIncludeFilters.addAll(filters);
215     }
216 
217     /**
218      * {@inheritDoc}
219      */
220     @Override
addExcludeFilter(String filter)221     public void addExcludeFilter(String filter) {
222         mExcludeFilters.add(filter);
223     }
224 
225     /**
226      * {@inheritDoc}
227      */
228     @Override
addAllExcludeFilters(Set<String> filters)229     public void addAllExcludeFilters(Set<String> filters) {
230         mExcludeFilters.addAll(filters);
231     }
232 
233     /** {@inheritDoc} */
234     @Override
clearIncludeFilters()235     public void clearIncludeFilters() {
236         mIncludeFilters.clear();
237     }
238 
239     /** {@inheritDoc} */
240     @Override
getIncludeFilters()241     public Set<String> getIncludeFilters() {
242         return mIncludeFilters;
243     }
244 
245     /** {@inheritDoc} */
246     @Override
getExcludeFilters()247     public Set<String> getExcludeFilters() {
248         return mExcludeFilters;
249     }
250 
251     /** {@inheritDoc} */
252     @Override
clearExcludeFilters()253     public void clearExcludeFilters() {
254         mExcludeFilters.clear();
255     }
256 
257     /** {@inheritDoc} */
258     @Override
setIncludeTestFile(File testFile)259     public void setIncludeTestFile(File testFile) {
260         mInternalIncludeTestFile = testFile;
261     }
262 
263     /** {@inheritDoc} */
264     @Override
getIncludeTestFile()265     public File getIncludeTestFile() {
266         return mInternalIncludeTestFile;
267     }
268 
269     /**
270      * {@inheritDoc}
271      */
272     @Override
setExcludeTestFile(File testFile)273     public void setExcludeTestFile(File testFile) {
274         mInternalExcludeTestFile = testFile;
275     }
276 
277     /** {@inheritDoc} */
278     @Override
getExcludeTestFile()279     public File getExcludeTestFile() {
280         return mInternalExcludeTestFile;
281     }
282 
283     /**
284      * {@inheritDoc}
285      */
286     @Override
addIncludeAnnotation(String annotation)287     public void addIncludeAnnotation(String annotation) {
288         mIncludeAnnotation.add(annotation);
289     }
290 
291     /**
292      * {@inheritDoc}
293      */
294     @Override
addAllIncludeAnnotation(Set<String> annotations)295     public void addAllIncludeAnnotation(Set<String> annotations) {
296         mIncludeAnnotation.addAll(annotations);
297     }
298 
299     /**
300      * {@inheritDoc}
301      */
302     @Override
addExcludeAnnotation(String excludeAnnotation)303     public void addExcludeAnnotation(String excludeAnnotation) {
304         mExcludeAnnotation.add(excludeAnnotation);
305     }
306 
307     /**
308      * {@inheritDoc}
309      */
310     @Override
addAllExcludeAnnotation(Set<String> excludeAnnotations)311     public void addAllExcludeAnnotation(Set<String> excludeAnnotations) {
312         mExcludeAnnotation.addAll(excludeAnnotations);
313     }
314 
315     /** {@inheritDoc} */
316     @Override
getIncludeAnnotations()317     public Set<String> getIncludeAnnotations() {
318         return mIncludeAnnotation;
319     }
320 
321     /** {@inheritDoc} */
322     @Override
getExcludeAnnotations()323     public Set<String> getExcludeAnnotations() {
324         return mExcludeAnnotation;
325     }
326 
327     /** {@inheritDoc} */
328     @Override
clearIncludeAnnotations()329     public void clearIncludeAnnotations() {
330         mIncludeAnnotation.clear();
331     }
332 
333     /** {@inheritDoc} */
334     @Override
clearExcludeAnnotations()335     public void clearExcludeAnnotations() {
336         mExcludeAnnotation.clear();
337     }
338 
339     /** {@inheritDoc} */
340     @Override
run(TestInformation testInfo, final ITestInvocationListener listener)341     public void run(TestInformation testInfo, final ITestInvocationListener listener)
342             throws DeviceNotAvailableException {
343         if (getDevice() == null) {
344             throw new IllegalArgumentException("Device has not been set");
345         }
346         if (mUseTestStorage) {
347             // Check if we are a parameterized module
348             List<String> params =
349                     getConfiguration()
350                             .getConfigurationDescription()
351                             .getMetaData(ConfigurationDescriptor.ACTIVE_PARAMETER_KEY);
352             if (params != null && params.contains(InstantAppHandler.INSTANT_APP_ID)) {
353                 mUseTestStorage = false;
354                 CLog.d("Disable test storage on instant app module.");
355             } else if (isTestRunningOnSdkSandbox(testInfo)) {
356                 // SDK sandboxes don't have access to the test ContentProvider.
357                 mUseTestStorage = false;
358                 CLog.d("Disable test storage for SDK sandbox instrumentation tests.");
359             } else {
360                 mUseTestStorage = getDevice().checkApiLevelAgainstNextRelease(34);
361                 if (!mUseTestStorage) {
362                     CLog.d("Disabled test storage as it's not supported on that branch.");
363                 }
364             }
365         }
366 
367         boolean pushedFile = false;
368         try (CloseableTraceScope filter = new CloseableTraceScope("push_filter_files")) {
369             // if mInternalIncludeTestFile is set, perform filtering with this file
370             if (mInternalIncludeTestFile != null && mInternalIncludeTestFile.length() > 0) {
371                 mDeviceIncludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + INCLUDE_FILE;
372                 pushTestFile(mInternalIncludeTestFile, mDeviceIncludeFile, listener, false);
373                 if (mUseTestStorage) {
374                     pushTestFile(
375                             mInternalIncludeTestFile,
376                             mTestStorageInternalDir + mDeviceIncludeFile,
377                             listener,
378                             true);
379                 }
380                 pushedFile = true;
381                 // If an explicit include file filter is provided, do not use the package
382                 setTestPackageName(null);
383             }
384 
385             // if mInternalExcludeTestFile is set, perform filtering with this file
386             if (mInternalExcludeTestFile != null && mInternalExcludeTestFile.length() > 0) {
387                 mDeviceExcludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + EXCLUDE_FILE;
388                 pushTestFile(mInternalExcludeTestFile, mDeviceExcludeFile, listener, false);
389                 if (mUseTestStorage) {
390                     pushTestFile(
391                             mInternalExcludeTestFile,
392                             mTestStorageInternalDir + mDeviceExcludeFile,
393                             listener,
394                             true);
395                 }
396                 pushedFile = true;
397             }
398         }
399         TestAppInstallSetup serviceInstaller = null;
400         if (mUseTestStorage) {
401             File testServices = null;
402             try (CloseableTraceScope serviceInstall =
403                     new CloseableTraceScope("install_service_apk")) {
404                 testServices = FileUtil.createTempFile("services", ".apk");
405                 boolean extracted =
406                         ResourceUtil.extractResourceAsFile(
407                                 "/test-services-normalized.apk", testServices);
408                 if (extracted) {
409                     serviceInstaller = new TestAppInstallSetup();
410                     // Service apk needs force-queryable
411                     serviceInstaller.setForceQueryable(true);
412                     serviceInstaller.addTestFile(testServices);
413                     if (testInfo != null
414                             && testInfo.properties().containsKey(RUN_TESTS_AS_USER_KEY)) {
415                         serviceInstaller.setUserId(
416                                 Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY)));
417                     }
418                     serviceInstaller.setUp(testInfo);
419                     // Turn off battery optimization for androidx.test.services
420                     CommandResult dumpsys =
421                             getDevice()
422                                     .executeShellV2Command(
423                                             "dumpsys deviceidle whitelist +androidx.test.services");
424                     CLog.d("stdout: %s\nstderr: %s", dumpsys.getStdout(), dumpsys.getStderr());
425                 } else {
426                     throw new IOException("Failed to extract test-services.apk");
427                 }
428             } catch (IOException | TargetSetupError | BuildError e) {
429                 CLog.e(e);
430                 mUseTestStorage = false;
431             } finally {
432                 FileUtil.deleteFile(testServices);
433             }
434         }
435         if (mTotalShards > 0 && !isShardable() && mShardIndex != 0) {
436             // If not shardable, only first shard can run.
437             CLog.i("%s is not shardable.", getRunnerName());
438             return;
439         }
440         super.run(testInfo, listener);
441         if (serviceInstaller != null) {
442             try (CloseableTraceScope serviceTeardown =
443                     new CloseableTraceScope("service_teardown")) {
444                 serviceInstaller.tearDown(testInfo, null);
445             }
446         }
447         if (pushedFile) {
448             // Remove the directory where the files where pushed
449             removeTestFilterDir();
450         }
451     }
452 
453     /**
454      * {@inheritDoc}
455      */
456     @Override
setRunnerArgs(IRemoteAndroidTestRunner runner)457     protected void setRunnerArgs(IRemoteAndroidTestRunner runner) {
458         super.setRunnerArgs(runner);
459 
460         // if mIncludeTestFile is set, perform filtering with this file
461         if (mDeviceIncludeFile != null) {
462             runner.addInstrumentationArg(TEST_FILE_INST_ARGS_KEY, mDeviceIncludeFile);
463         }
464 
465         // if mExcludeTestFile is set, perform filtering with this file
466         if (mDeviceExcludeFile != null) {
467             runner.addInstrumentationArg(NOT_TEST_FILE_INST_ARGS_KEY, mDeviceExcludeFile);
468         }
469 
470         // Split filters into class, notClass, package and notPackage
471         List<String> classArg = new ArrayList<String>();
472         List<String> notClassArg = new ArrayList<String>();
473         List<String> packageArg = new ArrayList<String>();
474         List<String> notPackageArg = new ArrayList<String>();
475         List<String> regexArg = new ArrayList<String>();
476         for (String test : mIncludeFilters) {
477             if (isRegex(test)) {
478                 regexArg.add(test);
479             } else if (isClassOrMethod(test)) {
480                 classArg.add(test);
481             } else {
482                 packageArg.add(test);
483             }
484         }
485         for (String test : mExcludeFilters) {
486             // tests_regex doesn't support exclude-filter. Therefore, only check if the filter is
487             // for class/method or package.
488             if (isClassOrMethod(test)) {
489                 notClassArg.add(test);
490             } else {
491                 notPackageArg.add(test);
492             }
493         }
494         if (!regexArg.isEmpty()
495                 && (!classArg.isEmpty()
496                         || !notClassArg.isEmpty()
497                         || !packageArg.isEmpty()
498                         || !notPackageArg.isEmpty())) {
499             StringBuilder sb = new StringBuilder();
500             if (!classArg.isEmpty()) {
501                 sb.append("classArg: " + classArg);
502             }
503             if (!notClassArg.isEmpty()) {
504                 sb.append("notClassArg: " + notClassArg);
505             }
506             if (!packageArg.isEmpty()) {
507                 sb.append("packageArg: " + packageArg);
508             }
509             if (!notPackageArg.isEmpty()) {
510                 sb.append("notPackageArg: " + notPackageArg);
511             }
512             throw new IllegalArgumentException(
513                     String.format(
514                             "Mixed filter types found. AndroidJUnitTest does not support mixing"
515                                     + " both regex [%s] and class/method/package filters: [%s]",
516                             regexArg, sb.toString()));
517         }
518         if (!classArg.isEmpty()) {
519             runner.addInstrumentationArg(INCLUDE_CLASS_INST_ARGS_KEY,
520                     ArrayUtil.join(",", classArg));
521         }
522         if (!notClassArg.isEmpty()) {
523             runner.addInstrumentationArg(EXCLUDE_CLASS_INST_ARGS_KEY,
524                     ArrayUtil.join(",", notClassArg));
525         }
526         if (!packageArg.isEmpty()) {
527             runner.addInstrumentationArg(INCLUDE_PACKAGE_INST_ARGS_KEY,
528                     ArrayUtil.join(",", packageArg));
529         }
530         if (!notPackageArg.isEmpty()) {
531             runner.addInstrumentationArg(EXCLUDE_PACKAGE_INST_ARGS_KEY,
532                     ArrayUtil.join(",", notPackageArg));
533         }
534         if (!regexArg.isEmpty()) {
535             String regexFilter;
536             if (regexArg.size() == 1) {
537                 regexFilter = regexArg.get(0);
538             } else {
539                 Collections.sort(regexArg);
540                 regexFilter = "\"(" + ArrayUtil.join("|", regexArg) + ")\"";
541             }
542             runner.addInstrumentationArg(INCLUDE_REGEX_INST_ARGS_KEY, regexFilter);
543         }
544         if (!mIncludeAnnotation.isEmpty()) {
545             runner.addInstrumentationArg(ANNOTATION_INST_ARGS_KEY,
546                     ArrayUtil.join(",", mIncludeAnnotation));
547         }
548         if (!mExcludeAnnotation.isEmpty()) {
549             runner.addInstrumentationArg(NOT_ANNOTATION_INST_ARGS_KEY,
550                     ArrayUtil.join(",", mExcludeAnnotation));
551         }
552         if (mTotalShards > 0 && isShardable()) {
553             runner.addInstrumentationArg(SHARD_INDEX_INST_ARGS_KEY, Integer.toString(mShardIndex));
554             runner.addInstrumentationArg(NUM_SHARD_INST_ARGS_KEY, Integer.toString(mTotalShards));
555         }
556         if (mNewRunListenerOrderMode) {
557             runner.addInstrumentationArg(
558                     NEW_RUN_LISTENER_ORDER_KEY, Boolean.toString(mNewRunListenerOrderMode));
559         }
560         if (mUseTestStorage) {
561             runner.addInstrumentationArg(
562                     USE_TEST_STORAGE_SERVICE, Boolean.toString(mUseTestStorage));
563         }
564         // Add the listeners received from Options
565         addDeviceListeners(mExtraDeviceListeners);
566     }
567 
568     /**
569      * Push the testFile to the requested destination. This should only be called for a non-null
570      * testFile
571      *
572      * @param testFile file to be pushed from the host to the device.
573      * @param destination the path on the device to which testFile is pushed
574      * @param listener {@link ITestInvocationListener} to report failures.
575      */
pushTestFile( File testFile, String destination, ITestInvocationListener listener, boolean skipLog)576     private void pushTestFile(
577             File testFile, String destination, ITestInvocationListener listener, boolean skipLog)
578             throws DeviceNotAvailableException {
579         if (!testFile.canRead() || !testFile.isFile()) {
580             String message = String.format("Cannot read test file %s", testFile.getAbsolutePath());
581             reportEarlyFailure(listener, message);
582             throw new IllegalArgumentException(message);
583         }
584         ITestDevice device = getDevice();
585         try {
586             CLog.d("Attempting to push filters to %s", destination);
587             boolean filterDirExists = device.doesFileExist(mTestFilterDir);
588             if (!device.pushFile(testFile, destination, true)) {
589                 String message =
590                         String.format(
591                                 "Failed to push file %s to %s for %s in pushTestFile",
592                                 testFile.getAbsolutePath(), destination, device.getSerialNumber());
593                 reportEarlyFailure(listener, message);
594                 throw new RuntimeException(message);
595             }
596             // in case the folder was created as 'root' we make is usable.
597             if (!filterDirExists) {
598                 device.executeShellCommand(
599                         String.format("chown -R shell:shell %s", mTestFilterDir));
600                 boolean filterExists = device.doesFileExist(destination);
601                 if (!filterExists) {
602                     CLog.e("Filter '%s' wasn't found on device after pushing.", destination);
603                 }
604             }
605         } catch (DeviceNotAvailableException e) {
606             reportEarlyFailure(listener, e.getMessage());
607             throw e;
608         }
609         if (skipLog) {
610             return;
611         }
612         try (FileInputStreamSource source = new FileInputStreamSource(testFile)) {
613             listener.testLog("filter-" + testFile.getName(), LogDataType.TEXT, source);
614         }
615     }
616 
removeTestFilterDir()617     private void removeTestFilterDir() throws DeviceNotAvailableException {
618         getDevice().deleteFile(mTestFilterDir);
619     }
620 
reportEarlyFailure(ITestInvocationListener listener, String errorMessage)621     private void reportEarlyFailure(ITestInvocationListener listener, String errorMessage) {
622         listener.testRunStarted("AndroidJUnitTest_setupError", 0);
623         FailureDescription failure = FailureDescription.create(errorMessage);
624         failure.setFailureStatus(FailureStatus.INFRA_FAILURE);
625         listener.testRunFailed(failure);
626         listener.testRunEnded(0, new HashMap<String, Metric>());
627     }
628 
629     /**
630      * Return if a string is the name of a Class or a Method.
631      */
632     @VisibleForTesting
isClassOrMethod(String filter)633     public boolean isClassOrMethod(String filter) {
634         if (filter.contains("#")) {
635             return true;
636         }
637         String[] parts = filter.split("\\.");
638         if (parts.length > 0) {
639             // FIXME Assume java package names starts with lowercase and class names start with
640             // uppercase.
641             // Return true iff the first character of the last word is uppercase
642             // com.android.foobar.Test
643             return Character.isUpperCase(parts[parts.length - 1].charAt(0));
644         }
645         return false;
646     }
647 
648     /** Return if a string is a regex for filter. */
649     @VisibleForTesting
isRegex(String filter)650     public boolean isRegex(String filter) {
651         if (isParameterizedTest(filter)) {
652             return false;
653         }
654 
655         // If filter contains any special regex character, return true.
656         // Throw RuntimeException if the regex is invalid.
657         if (Pattern.matches(".*[\\?\\*\\^\\$\\(\\)\\[\\]\\{\\}\\|\\\\].*", filter)) {
658             try {
659                 Pattern.compile(filter);
660             } catch (PatternSyntaxException e) {
661                 CLog.e("Filter %s is not a valid regular expression string.", filter);
662                 throw new RuntimeException(e);
663             }
664             return true;
665         }
666 
667         return false;
668     }
669 
670     /** Return if a string is a parameterized test. */
671     @VisibleForTesting
isParameterizedTest(String filter)672     public boolean isParameterizedTest(String filter) {
673         // If filter contains '#', '[', ']' and must ends with ']'. Only numbers, a-Z, -, _,
674         // [, ], (, ), and . are allowed between [].
675         if (Pattern.matches(".*#.*\\[[0-9a-zA-Z,\\-_.\\[\\]\\(\\)]*\\]$", filter)) {
676             CLog.i("Filter %s is a parameterized string.", filter);
677             return true;
678         }
679         return false;
680     }
681 
682     /**
683      * Helper to return if the runner is one that support sharding.
684      */
isShardable()685     private boolean isShardable() {
686         // Edge toward shardable if no explicit runner specified. The runner will be determined
687         // later and if not shardable only the first shard will run.
688         if (getRunnerName() == null) {
689             return true;
690         }
691         return ListInstrumentationParser.SHARDABLE_RUNNERS.contains(getRunnerName());
692     }
693 
694     /** {@inheritDoc} */
695     @Override
split(int shardCount)696     public Collection<IRemoteTest> split(int shardCount) {
697         if (!isShardable()) {
698             return null;
699         }
700         if (mMaxShard != null) {
701             shardCount = Math.min(shardCount, mMaxShard);
702         }
703         if (!mIsSharded && shardCount > 1) {
704             mIsSharded = true;
705             Collection<IRemoteTest> shards = new ArrayList<>(shardCount);
706             for (int index = 0; index < shardCount; index++) {
707                 shards.add(getTestShard(shardCount, index));
708             }
709             return shards;
710         }
711         return null;
712     }
713 
getTestShard(int shardCount, int shardIndex)714     private IRemoteTest getTestShard(int shardCount, int shardIndex) {
715         AndroidJUnitTest shard;
716         // ensure we handle runners that extend AndroidJUnitRunner
717         try {
718             shard = this.getClass().getDeclaredConstructor().newInstance();
719         } catch (InstantiationException
720                 | IllegalAccessException
721                 | InvocationTargetException
722                 | NoSuchMethodException e) {
723             throw new RuntimeException(e);
724         }
725         try {
726             OptionCopier.copyOptions(this, shard);
727         } catch (ConfigurationException e) {
728             CLog.e("Failed to copy instrumentation options: %s", e.getMessage());
729         }
730         shard.mShardIndex = shardIndex;
731         shard.mTotalShards = shardCount;
732         shard.mIsSharded = true;
733         shard.setAbi(getAbi());
734         shard.mInternalExcludeTestFile = mInternalExcludeTestFile;
735         shard.mInternalIncludeTestFile = mInternalIncludeTestFile;
736         // We approximate the runtime of each shard to be equal since we can't know.
737         shard.mRuntimeHint = mRuntimeHint / shardCount;
738         return shard;
739     }
740 }
741