• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.testtype;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkState;
21 
22 import com.android.ddmlib.IDevice;
23 import com.android.ddmlib.Log;
24 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
25 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize;
26 import com.android.ddmlib.testrunner.InstrumentationResultParser;
27 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
28 import com.android.tradefed.config.ConfigurationException;
29 import com.android.tradefed.config.Option;
30 import com.android.tradefed.config.Option.Importance;
31 import com.android.tradefed.config.OptionClass;
32 import com.android.tradefed.device.DeviceNotAvailableException;
33 import com.android.tradefed.device.ITestDevice;
34 import com.android.tradefed.device.metric.IMetricCollector;
35 import com.android.tradefed.device.metric.IMetricCollectorReceiver;
36 import com.android.tradefed.invoker.IInvocationContext;
37 import com.android.tradefed.log.LogUtil.CLog;
38 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
39 import com.android.tradefed.result.BugreportCollector;
40 import com.android.tradefed.result.CollectingTestListener;
41 import com.android.tradefed.result.ITestInvocationListener;
42 import com.android.tradefed.result.LogcatCrashResultForwarder;
43 import com.android.tradefed.result.TestDescription;
44 import com.android.tradefed.result.TestRunResult;
45 import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner;
46 import com.android.tradefed.util.AbiFormatter;
47 import com.android.tradefed.util.ArrayUtil;
48 import com.android.tradefed.util.ListInstrumentationParser;
49 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
50 import com.android.tradefed.util.StringEscapeUtils;
51 
52 import com.google.common.annotations.VisibleForTesting;
53 import com.google.common.collect.Sets;
54 
55 import java.io.File;
56 import java.util.ArrayList;
57 import java.util.Collection;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.LinkedHashSet;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Set;
64 import java.util.concurrent.TimeUnit;
65 
66 /** A Test that runs an instrumentation test package on given device. */
67 @OptionClass(alias = "instrumentation")
68 public class InstrumentationTest
69         implements IDeviceTest,
70                 IResumableTest,
71                 ITestCollector,
72                 IAbiReceiver,
73                 IInvocationContextReceiver,
74                 IMetricCollectorReceiver {
75 
76     private static final String LOG_TAG = "InstrumentationTest";
77 
78     /** max number of attempts to collect list of tests in package */
79     private static final int COLLECT_TESTS_ATTEMPTS = 3;
80     /** instrumentation test runner argument key used for test execution using a file */
81     private static final String TEST_FILE_INST_ARGS_KEY = "testFile";
82 
83     /** instrumentation test runner argument key used for individual test timeout */
84     static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
85 
86     /** default timeout for tests collection */
87     static final long TEST_COLLECTION_TIMEOUT_MS = 2 * 60 * 1000;
88 
89     /** test run name for merging coverage measurements */
90     static final String MERGE_COVERAGE_MEASUREMENTS_TEST_NAME = "mergeCoverageMeasurements";
91 
92     @Option(
93         name = "package",
94         shortName = 'p',
95         description = "The manifest package name of the Android test application to run.",
96         importance = Importance.IF_UNSET
97     )
98     private String mPackageName = null;
99 
100     @Option(name = "runner",
101             description="The instrumentation test runner class name to use. Will try to determine "
102                     + "automatically if it is not specified.")
103     private String mRunnerName = null;
104 
105     @Option(name = "class", shortName = 'c',
106             description="The test class name to run.")
107     private String mTestClassName = null;
108 
109     @Option(name = "method", shortName = 'm',
110             description="The test method name to run.")
111     private String mTestMethodName = null;
112 
113     @Option(name = "test-package",
114             description="Only run tests within this specific java package. " +
115             "Will be ignored if --class is set.")
116     private String mTestPackageName = null;
117 
118     /**
119      * @deprecated use shell-timeout or test-timeout option instead.
120      */
121     @Deprecated
122     @Option(name = "timeout",
123             description="Deprecated - Use \"shell-timeout\" or \"test-timeout\" instead.")
124     private Integer mTimeout = null;
125 
126     @Option(
127         name = "shell-timeout",
128         description =
129                 "The defined timeout (in milliseconds) is used as a maximum waiting time when "
130                         + "expecting the command output from the device. At any time, if the shell "
131                         + "command does not output anything for a period longer than defined "
132                         + "timeout the TF run terminates. For no timeout, set to 0.",
133         isTimeVal = true
134     )
135     private long mShellTimeout = 10 * 60 * 1000L; // default to 10 minutes
136 
137     @Option(
138         name = "test-timeout",
139         description =
140                 "Sets timeout (in milliseconds) that will be applied to each test. In the event of "
141                         + "a test timeout, it will log the results and proceed with executing the "
142                         + "next test. For no timeout, set to 0.",
143         isTimeVal = true
144     )
145     private long mTestTimeout = 5 * 60 * 1000L; // default to 5 minutes
146 
147     @Option(
148         name = "max-timeout",
149         description =
150                 "Sets the max timeout for the instrumentation to terminate. "
151                         + "For no timeout, set to 0.",
152         isTimeVal = true
153     )
154     private long mMaxTimeout = 0l;
155 
156     @Option(name = "size",
157             description="Restrict test to a specific test size.")
158     private String mTestSize = null;
159 
160     @Option(name = "rerun",
161             description = "Rerun unexecuted tests individually on same device if test run " +
162             "fails to complete.")
163     private boolean mIsRerunMode = true;
164 
165     @Option(name = "resume",
166             description = "Schedule unexecuted tests for resumption on another device " +
167             "if first device becomes unavailable.")
168     private boolean mIsResumeMode = false;
169 
170     @Option(name = "install-file",
171             description="Optional file path to apk file that contains the tests.")
172     private File mInstallFile = null;
173 
174     @Option(name = "run-name",
175             description="Optional custom test run name to pass to listener. " +
176             "If unspecified, will use package name.")
177     private String mRunName = null;
178 
179     @Option(
180             name = "instrumentation-arg",
181             description = "Additional instrumentation arguments to provide.",
182             requiredForRerun = true)
183     private final Map<String, String> mInstrArgMap = new HashMap<String, String>();
184 
185     @Option(name = "bugreport-on-failure", description = "Sets which failed testcase events " +
186             "cause a bugreport to be collected. a bugreport after failed testcases.  Note that " +
187             "there is _no feedback mechanism_ between the test runner and the bugreport " +
188             "collector, so use the EACH setting with due caution.")
189     private BugreportCollector.Freq mBugreportFrequency = null;
190 
191     @Option(
192         name = "bugreport-on-run-failure",
193         description = "Take a bugreport if the instrumentation finish with a run failure"
194     )
195     private boolean mBugreportOnRunFailure = false;
196 
197     @Option(
198         name = "rerun-from-file",
199         description =
200                 "Use test file instead of separate adb commands for each test "
201                         + "when re-running instrumentations for tests that failed to run in previous attempts. "
202     )
203     private boolean mReRunUsingTestFile = true;
204 
205     @Option(
206         name = "rerun-from-file-attempts",
207         description = "Max attempts to rerun tests from file. -1 means rerun from file infinitely."
208     )
209     private int mReRunUsingTestFileAttempts = 3;
210 
211     @Option(
212         name = "fallback-to-serial-rerun",
213         description = "Rerun tests serially after rerun from file failed."
214     )
215     private boolean mFallbackToSerialRerun = false;
216 
217     @Option(name = "reboot-before-rerun", description =
218             "Reboot a device before re-running instrumentations.")
219     private boolean mRebootBeforeReRun = false;
220 
221     @Option(name = AbiFormatter.FORCE_ABI_STRING,
222             description = AbiFormatter.FORCE_ABI_DESCRIPTION,
223             importance = Importance.IF_UNSET)
224     private String mForceAbi = null;
225 
226     @Option(name = "collect-tests-only",
227             description = "Only invoke the instrumentation to collect list of applicable test "
228                     + "cases. All test run callbacks will be triggered, but test execution will "
229                     + "not be actually carried out.")
230     private boolean mCollectTestsOnly = false;
231 
232     @Option(
233         name = "collect-tests-timeout",
234         description = "Timeout for the tests collection operation.",
235         isTimeVal = true
236     )
237     private long mCollectTestTimeout = TEST_COLLECTION_TIMEOUT_MS;
238 
239     @Option(
240         name = "debug",
241         description =
242                 "Wait for debugger before instrumentation starts. Note "
243                         + "that this should only be used for local debugging, not suitable for automated runs."
244     )
245     protected boolean mDebug = false;
246 
247     @Option(
248         name = "coverage",
249         description =
250                 "Collect code coverage for this test run. Note that the build under test must be a "
251                         + "coverage build or else this will fail."
252     )
253     private boolean mCoverage = false;
254 
255     @Option(
256         name = "merge-coverage-measurements",
257         description =
258                 "Merge coverage measurements from all test runs into a single measurement before "
259                         + "logging."
260     )
261     private boolean mMergeCoverageMeasurements = false;
262 
263     @Option(
264         name = "enforce-ajur-format",
265         description = "Whether or not enforcing the AJUR instrumentation output format"
266     )
267     private boolean mShouldEnforceFormat = false;
268 
269     @Option(
270         name = "hidden-api-checks",
271         description =
272                 "If set to false, the '--no-hidden-api-checks' flag will be passed to the am "
273                         + "instrument command. Only works for P or later."
274     )
275     private boolean mHiddenApiChecks = true;
276 
277     @Option(
278         name = "isolated-storage",
279         description =
280                 "If set to false, the '--no-isolated-storage' flag will be passed to the am "
281                         + "instrument command. Only works for Q or later."
282     )
283     private boolean mIsolatedStorage = true;
284 
285     private IAbi mAbi = null;
286 
287     private Collection<String> mInstallArgs = new ArrayList<>();
288 
289     private ITestDevice mDevice = null;
290 
291     private IRemoteAndroidTestRunner mRunner;
292 
293     private Collection<TestDescription> mTestsToRun = null;
294 
295     private String mCoverageTarget = null;
296 
297     private String mTestFilePathOnDevice = null;
298 
299     private ListInstrumentationParser mListInstrumentationParser = null;
300 
301     private Set<String> mExtraDeviceListener = new HashSet<>();
302 
303     private boolean mIsRerun = false;
304 
305     private IInvocationContext mContext;
306     private List<IMetricCollector> mCollectors = new ArrayList<>();
307 
308     /**
309      * {@inheritDoc}
310      */
311     @Override
setDevice(ITestDevice device)312     public void setDevice(ITestDevice device) {
313         mDevice = device;
314     }
315 
316     /**
317      * Set the Android manifest package to run.
318      */
setPackageName(String packageName)319     public void setPackageName(String packageName) {
320         mPackageName = packageName;
321     }
322 
323     /**
324      * Optionally, set the Android instrumentation runner to use.
325      */
setRunnerName(String runnerName)326     public void setRunnerName(String runnerName) {
327         mRunnerName = runnerName;
328     }
329 
330     /**
331      * Gets the Android instrumentation runner to be used.
332      */
getRunnerName()333     public String getRunnerName() {
334         return mRunnerName;
335     }
336 
337     /**
338      * Optionally, set the test class name to run.
339      */
setClassName(String testClassName)340     public void setClassName(String testClassName) {
341         mTestClassName = testClassName;
342     }
343 
344     /**
345      * Optionally, set the test method to run.
346      */
setMethodName(String testMethodName)347     public void setMethodName(String testMethodName) {
348         mTestMethodName = StringEscapeUtils.escapeShell(testMethodName);
349     }
350 
351     /**
352      * Optionally, set the path to a file located on the device that should contain a list of line
353      * separated test classes and methods (format: com.foo.Class#method) to be run.
354      * If set, will automatically attempt to re-run tests using this test file
355      * via {@link InstrumentationFileTest} instead of executing separate adb commands for each
356      * remaining test via {@link InstrumentationSerialTest}"
357      */
setTestFilePathOnDevice(String testFilePathOnDevice)358     public void setTestFilePathOnDevice(String testFilePathOnDevice) {
359         mTestFilePathOnDevice = testFilePathOnDevice;
360     }
361 
362     /**
363      * Optionally, set the test size to run.
364      */
setTestSize(String size)365     public void setTestSize(String size) {
366         mTestSize = size;
367     }
368 
369     /**
370      * Get the Android manifest package to run.
371      */
getPackageName()372     public String getPackageName() {
373         return mPackageName;
374     }
375 
376     /**
377      * Get the custom test run name that will be provided to listener
378      */
getRunName()379     public String getRunName() {
380         return mRunName;
381     }
382 
383     /**
384      * Set the custom test run name that will be provided to listener
385      */
setRunName(String runName)386     public void setRunName(String runName) {
387         mRunName = runName;
388     }
389 
390     /**
391      * Set the collection of tests that should be executed by this InstrumentationTest.
392      *
393      * @param tests the tests to run
394      */
setTestsToRun(Collection<TestDescription> tests)395     public void setTestsToRun(Collection<TestDescription> tests) {
396         mTestsToRun = tests;
397     }
398 
399     /**
400      * Get the class name to run.
401      */
getClassName()402     protected String getClassName() {
403         return mTestClassName;
404     }
405 
406     /**
407      * Get the test method to run.
408      */
getMethodName()409     protected String getMethodName() {
410         return mTestMethodName;
411     }
412 
413     /**
414      * Get the path to a file that contains class#method combinations to be run
415      */
getTestFilePathOnDevice()416     String getTestFilePathOnDevice() {
417         return mTestFilePathOnDevice;
418     }
419 
420     /** Get the test java package to run. */
getTestPackageName()421     protected String getTestPackageName() {
422         return mTestPackageName;
423     }
424 
425     /**
426      * Sets the test package filter.
427      * <p/>
428      * If non-null, only tests within the given java package will be executed.
429      * <p/>
430      * Will be ignored if a non-null value has been provided to {@link #setClassName(String)}
431      */
setTestPackageName(String testPackageName)432     public void setTestPackageName(String testPackageName) {
433         mTestPackageName = testPackageName;
434     }
435 
436     /**
437      * Get the test size to run. Returns <code>null</code> if no size has been set.
438      */
getTestSize()439     String getTestSize() {
440         return mTestSize;
441     }
442 
443     /**
444      * Optionally, set the maximum time (in milliseconds) expecting shell output from the device.
445      */
setShellTimeout(long timeout)446     public void setShellTimeout(long timeout) {
447         mShellTimeout = timeout;
448     }
449 
450     /** Optionally, set the maximum time (in milliseconds) for each individual test run. */
setTestTimeout(long timeout)451     public void setTestTimeout(long timeout) {
452         mTestTimeout = timeout;
453     }
454 
455     /**
456      * Set the coverage target of this test.
457      * <p/>
458      * Currently unused. This method is just present so coverageTarget can be later retrieved via
459      * {@link #getCoverageTarget()}
460      */
setCoverageTarget(String coverageTarget)461     public void setCoverageTarget(String coverageTarget) {
462         mCoverageTarget = coverageTarget;
463     }
464 
465     /**
466      * Get the coverageTarget previously set via {@link #setCoverageTarget(String)}.
467      */
getCoverageTarget()468     public String getCoverageTarget() {
469         return mCoverageTarget;
470     }
471 
472     /**
473      * Return <code>true</code> if rerun mode is on.
474      */
isRerunMode()475     boolean isRerunMode() {
476         return mIsRerunMode;
477     }
478 
479     /** Sets whether this is a test rerun. Reruns do not create new listeners or merge coverage. */
setIsRerun(boolean isRerun)480     void setIsRerun(boolean isRerun) {
481         mIsRerun = isRerun;
482     }
483 
484     /**
485      * {@inheritDoc}
486      */
487     @Override
isResumable()488     public boolean isResumable() {
489         // hack to not resume if tests were never run
490         // TODO: fix this properly in TestInvocation
491         if (mTestsToRun == null) {
492             return false;
493         }
494         return mIsResumeMode;
495     }
496 
497     /**
498      * Optionally, set the rerun mode.
499      */
setRerunMode(boolean rerun)500     public void setRerunMode(boolean rerun) {
501         mIsRerunMode = rerun;
502     }
503 
504     /**
505      * Optionally, set the resume mode.
506      */
setResumeMode(boolean resume)507     public void setResumeMode(boolean resume) {
508         mIsResumeMode = resume;
509     }
510 
511     /**
512      * Get the shell timeout in ms.
513      */
getShellTimeout()514     long getShellTimeout() {
515         return mShellTimeout;
516     }
517 
518     /** Get the test timeout in ms. */
getTestTimeout()519     long getTestTimeout() {
520         return mTestTimeout;
521     }
522 
523     /** Returns the max timeout set for the instrumentation. */
getMaxTimeout()524     public long getMaxTimeout() {
525         return mMaxTimeout;
526     }
527 
528     /**
529      * Set the optional file to install that contains the tests.
530      *
531      * @param installFile the installable {@link File}
532      */
setInstallFile(File installFile)533     public void setInstallFile(File installFile) {
534         mInstallFile = installFile;
535     }
536 
537     /**
538      * {@inheritDoc}
539      */
540     @Override
getDevice()541     public ITestDevice getDevice() {
542         return mDevice;
543     }
544 
545     /**
546      * Set the max time in ms to allow for the 'max time to shell output response' when collecting
547      * tests.
548      * <p/>
549      * @deprecated This method is a no-op
550      */
551     @Deprecated
552     @SuppressWarnings("unused")
setCollectsTestsShellTimeout(int timeout)553     public void setCollectsTestsShellTimeout(int timeout) {
554         // no-op
555     }
556 
557     /**
558      * Set the frequency with which to automatically collect bugreports after test failures.
559      * <p />
560      * Note that there is _no feedback mechanism_ between the test runner and the bugreport
561      * collector, so use the EACH setting with due caution: if a large quantity of failures happen
562      * in rapid succession, the bugreport for a given one of the failures could end up being
563      * collected tens of minutes or hours after the respective failure occurred.
564      */
setBugreportFrequency(BugreportCollector.Freq freq)565     public void setBugreportFrequency(BugreportCollector.Freq freq) {
566         mBugreportFrequency = freq;
567     }
568 
569     /**
570      * Add an argument to provide when running the instrumentation tests.
571      *
572      * @param key the argument name
573      * @param value the argument value
574      */
addInstrumentationArg(String key, String value)575     public void addInstrumentationArg(String key, String value) {
576         mInstrArgMap.put(key, value);
577     }
578 
579     /** Allows to remove an entry from the instrumentation-arg. */
removeFromInstrumentationArg(String key)580     void removeFromInstrumentationArg(String key) {
581         mInstrArgMap.remove(key);
582     }
583 
584     /**
585      * Retrieve the value of an argument to provide when running the instrumentation tests.
586      *
587      * @param key the argument name
588      * <p/>
589      * Exposed for testing
590      */
getInstrumentationArg(String key)591     String getInstrumentationArg(String key) {
592         if (mInstrArgMap.containsKey(key)) {
593             return mInstrArgMap.get(key);
594         }
595         return null;
596     }
597 
598     /**
599      * Sets force-abi option.
600      * @param abi
601      */
setForceAbi(String abi)602     public void setForceAbi(String abi) {
603         mForceAbi = abi;
604     }
605 
getForceAbi()606     public String getForceAbi() {
607         return mForceAbi;
608     }
609 
610     /** Sets the --coverage option for testing. */
611     @VisibleForTesting
setCoverage(boolean coverageEnabled)612     void setCoverage(boolean coverageEnabled) {
613         mCoverage = coverageEnabled;
614     }
615 
616     /** Sets the --merge-coverage-measurements option for testing. */
617     @VisibleForTesting
setMergeCoverageMeasurements(boolean merge)618     void setMergeCoverageMeasurements(boolean merge) {
619         mMergeCoverageMeasurements = merge;
620     }
621 
622     /** Sets the --rerun-from-file option. */
setReRunUsingTestFile(boolean reRunUsingTestFile)623     public void setReRunUsingTestFile(boolean reRunUsingTestFile) {
624         mReRunUsingTestFile = reRunUsingTestFile;
625     }
626 
627     /** Sets the --fallback-to-serial-rerun option. */
setFallbackToSerialRerun(boolean reRunSerially)628     public void setFallbackToSerialRerun(boolean reRunSerially) {
629         mFallbackToSerialRerun = reRunSerially;
630     }
631 
632     /** Sets the --reboot-before-rerun option. */
setRebootBeforeReRun(boolean rebootBeforeReRun)633     public void setRebootBeforeReRun(boolean rebootBeforeReRun) {
634         mRebootBeforeReRun = rebootBeforeReRun;
635     }
636 
637     /** Allows to add more custom listeners to the runner */
addDeviceListeners(Set<String> extraListeners)638     public void addDeviceListeners(Set<String> extraListeners) {
639         mExtraDeviceListener.addAll(extraListeners);
640     }
641 
642     /**
643      * @return the {@link IRemoteAndroidTestRunner} to use.
644      * @throws DeviceNotAvailableException
645      */
createRemoteAndroidTestRunner(String packageName, String runnerName, IDevice device)646     IRemoteAndroidTestRunner createRemoteAndroidTestRunner(String packageName, String runnerName,
647             IDevice device) throws DeviceNotAvailableException {
648         RemoteAndroidTestRunner runner =
649                 new DefaultRemoteAndroidTestRunner(packageName, runnerName, device);
650         String abiName = resolveAbiName();
651         String runOptions = "";
652         // hidden-api-checks flag only exists in P and after.
653         if (!mHiddenApiChecks && getDevice().getApiLevel() >= 28) {
654             runOptions += "--no-hidden-api-checks ";
655         }
656         // isolated-storage flag only exists in Q and after.
657         if (!mIsolatedStorage && getDevice().checkApiLevelAgainstNextRelease(29)) {
658             runOptions += "--no-isolated-storage ";
659         }
660         if (abiName != null) {
661             mInstallArgs.add(String.format("--abi %s", abiName));
662             runOptions += String.format("--abi %s", abiName);
663         }
664         // Set the run options if any.
665         if (!runOptions.isEmpty()) {
666             runner.setRunOptions(runOptions);
667         }
668 
669         runner.setEnforceTimeStamp(mShouldEnforceFormat);
670         return runner;
671     }
672 
resolveAbiName()673     private String resolveAbiName() throws DeviceNotAvailableException {
674         if (mAbi != null && mForceAbi != null) {
675             throw new IllegalArgumentException("cannot specify both abi flags");
676         }
677         String abiName = null;
678         if (mAbi != null) {
679             abiName = mAbi.getName();
680         } else if (mForceAbi != null && !mForceAbi.isEmpty()) {
681             abiName = AbiFormatter.getDefaultAbi(mDevice, mForceAbi);
682             if (abiName == null) {
683                 throw new RuntimeException(
684                         String.format("Cannot find abi for force-abi %s", mForceAbi));
685             }
686         }
687         return abiName;
688     }
689 
690     /**
691      * Set the {@link ListInstrumentationParser}.
692      */
693     @VisibleForTesting
setListInstrumentationParser(ListInstrumentationParser listInstrumentationParser)694     void setListInstrumentationParser(ListInstrumentationParser listInstrumentationParser) {
695         mListInstrumentationParser = listInstrumentationParser;
696     }
697 
698     /**
699      * Get the {@link ListInstrumentationParser} used to parse 'pm list instrumentation' queries.
700      */
getListInstrumentationParser()701     protected ListInstrumentationParser getListInstrumentationParser() {
702         if (mListInstrumentationParser == null) {
703             mListInstrumentationParser = new ListInstrumentationParser();
704         }
705         return mListInstrumentationParser;
706     }
707 
708     /**
709      * Query the device for a test runner to use.
710      *
711      * @return the first test runner name that matches the package or null if we don't find any.
712      * @throws DeviceNotAvailableException
713      */
queryRunnerName()714     protected String queryRunnerName() throws DeviceNotAvailableException {
715         ListInstrumentationParser parser = getListInstrumentationParser();
716         getDevice().executeShellCommand("pm list instrumentation", parser);
717 
718         Set<String> candidates = new LinkedHashSet<>();
719         for (InstrumentationTarget target : parser.getInstrumentationTargets()) {
720             if (mPackageName.equals(target.packageName)) {
721                 candidates.add(target.runnerName);
722             }
723         }
724         if (candidates.isEmpty()) {
725             CLog.w("Unable to determine runner name for package: %s", mPackageName);
726             return null;
727         }
728         // Bias toward using one of the AJUR runner when available, otherwise use the first runner
729         // available.
730         Set<String> intersection =
731                 Sets.intersection(candidates, ListInstrumentationParser.SHARDABLE_RUNNERS);
732         if (intersection.isEmpty()) {
733             return candidates.iterator().next();
734         }
735         return intersection.iterator().next();
736     }
737 
738     /**
739      * {@inheritDoc}
740      */
741     @Override
run(final ITestInvocationListener listener)742     public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException {
743         checkArgument(mDevice != null, "Device has not been set.");
744         checkArgument(mPackageName != null, "Package name has not been set.");
745         // Install the apk before checking the runner
746         if (mInstallFile != null) {
747             String installOutput =
748                     mDevice.installPackage(
749                             mInstallFile, true, mInstallArgs.toArray(new String[] {}));
750             if (installOutput != null) {
751                 throw new RuntimeException(
752                         String.format(
753                                 "Error while installing '%s': %s",
754                                 mInstallFile.getName(), installOutput));
755             }
756         }
757         if (mRunnerName == null) {
758             setRunnerName(queryRunnerName());
759             checkArgument(
760                     mRunnerName != null,
761                     "Runner name has not been set and no matching instrumentations were found.");
762             CLog.i("No runner name specified. Using: %s.", mRunnerName);
763         }
764         mRunner = createRemoteAndroidTestRunner(mPackageName, mRunnerName, mDevice.getIDevice());
765         setRunnerArgs(mRunner);
766 
767         doTestRun(listener);
768         if (mInstallFile != null) {
769             mDevice.uninstallPackage(mPackageName);
770         }
771     }
772 
setRunnerArgs(IRemoteAndroidTestRunner runner)773     protected void setRunnerArgs(IRemoteAndroidTestRunner runner) {
774         if (mTestClassName != null) {
775             if (mTestMethodName != null) {
776                 runner.setMethodName(mTestClassName, mTestMethodName);
777             } else {
778                 runner.setClassName(mTestClassName);
779             }
780         } else if (mTestPackageName != null) {
781             runner.setTestPackageName(mTestPackageName);
782         }
783         if (mTestFilePathOnDevice != null) {
784             addInstrumentationArg(TEST_FILE_INST_ARGS_KEY, mTestFilePathOnDevice);
785         }
786         if (mTestSize != null) {
787             runner.setTestSize(TestSize.getTestSize(mTestSize));
788         }
789         addTimeoutsToRunner(runner);
790         if (mRunName != null) {
791             runner.setRunName(mRunName);
792         }
793         for (Map.Entry<String, String> argEntry : mInstrArgMap.entrySet()) {
794             runner.addInstrumentationArg(argEntry.getKey(), argEntry.getValue());
795         }
796     }
797 
798     /**
799      * Helper method to add test-timeout & shell-timeout timeouts to  given runner
800      */
addTimeoutsToRunner(IRemoteAndroidTestRunner runner)801     private void addTimeoutsToRunner(IRemoteAndroidTestRunner runner) {
802         if (mTimeout != null) {
803             CLog.w("\"timeout\" argument is deprecated and should not be used! \"shell-timeout\""
804                     + " argument value is overwritten with %d ms", mTimeout);
805             setShellTimeout(mTimeout);
806         }
807         if (mTestTimeout < 0) {
808             throw new IllegalArgumentException(
809                     String.format("test-timeout %d cannot be negative", mTestTimeout));
810         }
811         if (mShellTimeout <= mTestTimeout) {
812             // set shell timeout to 110% of test timeout
813             mShellTimeout = mTestTimeout + mTestTimeout / 10;
814             CLog.w(String.format("shell-timeout should be larger than test-timeout %d; "
815                     + "NOTE: extending shell-timeout to %d, please consider fixing this!",
816                     mTestTimeout, mShellTimeout));
817         }
818         runner.setMaxTimeToOutputResponse(mShellTimeout, TimeUnit.MILLISECONDS);
819         runner.setMaxTimeout(mMaxTimeout, TimeUnit.MILLISECONDS);
820         addInstrumentationArg(TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(mTestTimeout));
821     }
822 
823     /**
824      * Execute test run.
825      *
826      * @param listener the test result listener
827      * @throws DeviceNotAvailableException if device stops communicating
828      */
doTestRun(ITestInvocationListener listener)829     private void doTestRun(ITestInvocationListener listener) throws DeviceNotAvailableException {
830         // If this is a dry-run, just collect the tests and return
831         if (mCollectTestsOnly) {
832             checkState(
833                     mTestsToRun == null,
834                     "Tests to run should not be set explicitly when --collect-tests-only is set.");
835 
836             // Use the actual listener to collect the tests, and print a error if this fails
837             Collection<TestDescription> collectedTests = collectTestsToRun(mRunner, listener);
838             if (collectedTests == null) {
839                 CLog.e("Failed to collect tests for %s", mPackageName);
840             } else {
841                 CLog.i("Collected %d tests for %s", collectedTests.size(), mPackageName);
842             }
843             return;
844         }
845 
846         // If the tests to run weren't provided explicitly, collect them.
847         Collection<TestDescription> testsToRun = mTestsToRun;
848         if (testsToRun == null) {
849             // Don't notify the listener since it's not a real run.
850             testsToRun = collectTestsToRun(mRunner, null);
851         }
852 
853         // Only set the debug flag after collecting tests.
854         if (mDebug) {
855             mRunner.setDebug(true);
856         }
857         if (mCoverage) {
858             mRunner.addInstrumentationArg("coverage", "true");
859         }
860 
861         // Reruns do not create new listeners.
862         if (!mIsRerun) {
863             listener = addBugreportListenerIfEnabled(listener);
864             listener = addJavaCoverageListenerIfEnabled(listener);
865             listener = addNativeCoverageListenerIfEnabled(listener);
866 
867             // TODO: Convert to device-side collectors when possible.
868             for (IMetricCollector collector : mCollectors) {
869                 if (collector.isDisabled()) {
870                     CLog.d("%s has been disabled. Skipping.", collector);
871                 } else {
872                     CLog.d(
873                             "Initializing %s for instrumentation.",
874                             collector.getClass().getCanonicalName());
875                     listener = collector.init(mContext, listener);
876                 }
877             }
878         }
879 
880         // Add the extra listeners only to the actual run and not the --collect-test-only one
881         if (!mExtraDeviceListener.isEmpty()) {
882             mRunner.addInstrumentationArg("listener", ArrayUtil.join(",", mExtraDeviceListener));
883         }
884 
885         if (testsToRun == null) {
886             // Failed to collect the tests or collection is off. Just try to run them all.
887             mDevice.runInstrumentationTests(mRunner, listener);
888         } else if (!testsToRun.isEmpty()) {
889             runWithRerun(listener, testsToRun);
890         } else {
891             CLog.i("No tests expected for %s, skipping", mPackageName);
892         }
893 
894         // Merge coverage measurements after all tests have been run, but not inside the rerun
895         // itself since the merging will be handled by the caller.
896         if (!mIsRerun && mMergeCoverageMeasurements) {
897             listener.testRunStarted(MERGE_COVERAGE_MEASUREMENTS_TEST_NAME, 0);
898             listener.testRunEnded(0, new HashMap<String, Metric>());
899         }
900     }
901 
902     /**
903      * Returns a listener that will collect bugreports, or the original {@code listener} if this
904      * feature is disabled.
905      */
addBugreportListenerIfEnabled(ITestInvocationListener listener)906     ITestInvocationListener addBugreportListenerIfEnabled(ITestInvocationListener listener) {
907         if (mBugreportFrequency != null) {
908             // Collect a bugreport after EACH/FIRST failed testcase
909             BugreportCollector.Predicate pred = new BugreportCollector.Predicate(
910                     BugreportCollector.Relation.AFTER,
911                     mBugreportFrequency,
912                     BugreportCollector.Noun.FAILED_TESTCASE);
913             BugreportCollector collector = new BugreportCollector(listener, getDevice());
914             collector.addPredicate(pred);
915             listener = collector;
916         }
917         return listener;
918     }
919 
920     /**
921      * Returns a listener that will collect coverage measurements, or the original {@code listener}
922      * if this feature is disabled.
923      */
addJavaCoverageListenerIfEnabled(ITestInvocationListener listener)924     ITestInvocationListener addJavaCoverageListenerIfEnabled(ITestInvocationListener listener) {
925         if (mCoverage) {
926             return new JavaCodeCoverageListener(getDevice(), mMergeCoverageMeasurements, listener);
927         }
928         return listener;
929     }
930 
931     /**
932      * Returns a listener that will collect native coverage measurements, or the original {@code
933      * listener} if this feature is disabled.
934      */
addNativeCoverageListenerIfEnabled(ITestInvocationListener listener)935     ITestInvocationListener addNativeCoverageListenerIfEnabled(ITestInvocationListener listener) {
936         if (mCoverage) {
937             return new NativeCodeCoverageListener(getDevice(), listener);
938         }
939         return listener;
940     }
941 
942     /**
943      * Execute the test run, but re-run incomplete tests individually if run fails to complete.
944      *
945      * @param listener the {@link ITestInvocationListener}
946      * @param expectedTests the full set of expected tests in this run.
947      */
runWithRerun( final ITestInvocationListener listener, Collection<TestDescription> expectedTests)948     private void runWithRerun(
949             final ITestInvocationListener listener, Collection<TestDescription> expectedTests)
950             throws DeviceNotAvailableException {
951         CollectingTestListener testTracker = new CollectingTestListener();
952         mDevice.runInstrumentationTests(
953                 mRunner,
954                 // Use a crash forwarder to get stacks from logcat when crashing.
955                 new LogcatCrashResultForwarder(getDevice(), listener, testTracker) {
956                     @Override
957                     public void testRunStarted(String runName, int testCount) {
958                         // In case of crash, run will attempt to report with 0
959                         if (testCount == 0 && !expectedTests.isEmpty()) {
960                             CLog.e(
961                                     "Run reported 0 tests while we collected %s",
962                                     expectedTests.size());
963                             super.testRunStarted(runName, expectedTests.size());
964                         } else {
965                             super.testRunStarted(runName, testCount);
966                         }
967                     }
968                 });
969         TestRunResult testRun = testTracker.getCurrentRunResults();
970         if (testRun.isRunFailure() || !testRun.getCompletedTests().containsAll(expectedTests)) {
971             if (mBugreportOnRunFailure) {
972                 // Capture a bugreport to help with the failure.
973                 String name = (mTestClassName != null) ? mTestClassName : mPackageName;
974                 boolean res =
975                         mDevice.logBugreport(
976                                 String.format("bugreport-on-run-failure-%s", name), listener);
977                 if (!res) {
978                     CLog.e(
979                             "Failed to capture a bugreport for the run failure of '%s'",
980                             testRun.getName());
981                 }
982             }
983             // Don't re-run any completed tests, unless this is a coverage run.
984             if (!mCoverage) {
985                 expectedTests.removeAll(testTracker.getCurrentRunResults().getCompletedTests());
986             }
987             rerunTests(expectedTests, listener);
988         }
989     }
990 
991     /**
992      * Rerun any <var>mRemainingTests</var>
993      *
994      * @param listener the {@link ITestInvocationListener}
995      * @throws DeviceNotAvailableException
996      */
rerunTests( Collection<TestDescription> expectedTests, final ITestInvocationListener listener)997     private void rerunTests(
998             Collection<TestDescription> expectedTests, final ITestInvocationListener listener)
999             throws DeviceNotAvailableException {
1000         if (expectedTests.isEmpty()) {
1001             CLog.d("No tests to re-run, all tests executed at least once.");
1002             return;
1003         }
1004         if (mRebootBeforeReRun) {
1005             mDevice.reboot();
1006         }
1007 
1008         IRemoteTest testReRunner = null;
1009         try {
1010             testReRunner = getTestReRunner(expectedTests);
1011         } catch (ConfigurationException e) {
1012             CLog.e("Failed to create test runner: %s", e.getMessage());
1013             return;
1014         }
1015 
1016         testReRunner.run(listener);
1017     }
1018 
1019     @VisibleForTesting
getTestReRunner(Collection<TestDescription> tests)1020     IRemoteTest getTestReRunner(Collection<TestDescription> tests) throws ConfigurationException {
1021         if (mReRunUsingTestFile) {
1022             return new InstrumentationFileTest(
1023                     this, tests, mFallbackToSerialRerun, mReRunUsingTestFileAttempts);
1024         } else {
1025             // Since the same runner is reused we must ensure TEST_FILE_INST_ARGS_KEY is not set.
1026             // Otherwise, the runner will attempt to execute tests from file.
1027             mInstrArgMap.remove(TEST_FILE_INST_ARGS_KEY);
1028             return new InstrumentationSerialTest(this, tests);
1029         }
1030     }
1031 
1032     /**
1033      * Collect the list of tests that should be executed by this test run.
1034      *
1035      * <p>This will be done by executing the test run in 'logOnly' mode, and recording the list of
1036      * tests.
1037      *
1038      * @param runner the {@link IRemoteAndroidTestRunner} to use to run the tests.
1039      * @return a {@link Collection} of {@link TestDescription}s that represent all tests to be
1040      *     executed by this run
1041      * @throws DeviceNotAvailableException
1042      */
collectTestsToRun( final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)1043     private Collection<TestDescription> collectTestsToRun(
1044             final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)
1045             throws DeviceNotAvailableException {
1046         if (isRerunMode()) {
1047             Log.d(LOG_TAG, String.format("Collecting test info for %s on device %s",
1048                     mPackageName, mDevice.getSerialNumber()));
1049             runner.setTestCollection(true);
1050             // always explicitly set debug to false when collecting tests
1051             runner.setDebug(false);
1052             // try to collect tests multiple times, in case device is temporarily not available
1053             // on first attempt
1054             Collection<TestDescription> tests = collectTestsAndRetry(runner, listener);
1055             // done with "logOnly" mode, restore proper test timeout before real test execution
1056             addTimeoutsToRunner(runner);
1057             runner.setTestCollection(false);
1058             return tests;
1059         }
1060         return null;
1061     }
1062 
1063     /**
1064      * Performs the actual work of collecting tests, making multiple attempts if necessary
1065      *
1066      * @param runner the {@link IRemoteAndroidTestRunner} that will be used for the instrumentation
1067      * @param listener the {ITestInvocationListener} where to report results, can be null if we are
1068      *     not reporting the results to the main invocation and simply collecting tests.
1069      * @return the collection of tests, or <code>null</code> if tests could not be collected
1070      * @throws DeviceNotAvailableException if communication with the device was lost
1071      */
1072     @VisibleForTesting
collectTestsAndRetry( final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)1073     Collection<TestDescription> collectTestsAndRetry(
1074             final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)
1075             throws DeviceNotAvailableException {
1076         boolean communicationFailure = false;
1077         for (int i=0; i < COLLECT_TESTS_ATTEMPTS; i++) {
1078             CollectingTestListener collector = new CollectingTestListener();
1079             boolean instrResult = false;
1080             // We allow to override the ddmlib default timeout for collection of tests.
1081             runner.setMaxTimeToOutputResponse(mCollectTestTimeout, TimeUnit.MILLISECONDS);
1082             if (listener == null) {
1083                 instrResult = mDevice.runInstrumentationTests(runner, collector);
1084             } else {
1085                 instrResult = mDevice.runInstrumentationTests(runner, collector, listener);
1086             }
1087             TestRunResult runResults = collector.getCurrentRunResults();
1088             if (!instrResult || !runResults.isRunComplete()) {
1089                 // communication failure with device, retry
1090                 Log.w(LOG_TAG, String.format(
1091                         "No results when collecting tests to run for %s on device %s. Retrying",
1092                         mPackageName, mDevice.getSerialNumber()));
1093                 communicationFailure = true;
1094             } else if (runResults.isRunFailure()) {
1095                 // not a communication failure, but run still failed.
1096                 // TODO: should retry be attempted
1097                 CLog.w("Run failure %s when collecting tests to run for %s on device %s.",
1098                         runResults.getRunFailureMessage(), mPackageName,
1099                         mDevice.getSerialNumber());
1100                 if (mShouldEnforceFormat
1101                         && InstrumentationResultParser.INVALID_OUTPUT_ERR_MSG.equals(
1102                                 runResults.getRunFailureMessage())) {
1103                     throw new RuntimeException(InstrumentationResultParser.INVALID_OUTPUT_ERR_MSG);
1104                 }
1105                 return null;
1106             } else {
1107                 // success!
1108                 return runResults.getCompletedTests();
1109             }
1110         }
1111         if (communicationFailure) {
1112             // TODO: find a better way to handle this
1113             // throwing DeviceUnresponsiveException is not always ideal because a misbehaving
1114             // instrumentation can hang, even though device is responsive. Would be nice to have
1115             // a louder signal for this situation though than just logging an error
1116 //            throw new DeviceUnresponsiveException(String.format(
1117 //                    "Communication failure when attempting to collect tests %s on device %s",
1118 //                    mPackageName, mDevice.getSerialNumber()));
1119             CLog.w("Ignoring repeated communication failure when collecting tests %s for device %s",
1120                     mPackageName, mDevice.getSerialNumber());
1121         }
1122         CLog.e("Failed to collect tests to run for %s on device %s.",
1123                 mPackageName, mDevice.getSerialNumber());
1124         return null;
1125     }
1126 
1127     /**
1128      * {@inheritDoc}
1129      */
1130     @Override
setCollectTestsOnly(boolean shouldCollectTest)1131     public void setCollectTestsOnly(boolean shouldCollectTest) {
1132         mCollectTestsOnly = shouldCollectTest;
1133     }
1134 
1135     @Override
setAbi(IAbi abi)1136     public void setAbi(IAbi abi) {
1137         mAbi = abi;
1138     }
1139 
1140     @Override
getAbi()1141     public IAbi getAbi() {
1142         return mAbi;
1143     }
1144 
1145     @Override
setInvocationContext(IInvocationContext invocationContext)1146     public void setInvocationContext(IInvocationContext invocationContext) {
1147         mContext = invocationContext;
1148     }
1149 
1150     @Override
setMetricCollectors(List<IMetricCollector> collectors)1151     public void setMetricCollectors(List<IMetricCollector> collectors) {
1152         mCollectors = collectors;
1153     }
1154 
1155     /** Set True if we enforce the AJUR output format of instrumentation. */
setEnforceFormat(boolean enforce)1156     public void setEnforceFormat(boolean enforce) {
1157         mShouldEnforceFormat = enforce;
1158     }
1159 
1160     /**
1161      * Set the instrumentation debug setting.
1162      *
1163      * @param debug boolean value to set the instrumentation debug setting to.
1164      */
setDebug(boolean debug)1165     public void setDebug(boolean debug) {
1166         mDebug = debug;
1167     }
1168 
1169     /**
1170      * Get the instrumentation debug setting.
1171      *
1172      * @return The boolean debug setting.
1173      */
getDebug()1174     public boolean getDebug() {
1175         return mDebug;
1176     }
1177 
1178     /** Set wether or not to use the isolated storage. */
setIsolatedStorage(boolean isolatedStorage)1179     public void setIsolatedStorage(boolean isolatedStorage) {
1180         mIsolatedStorage = isolatedStorage;
1181     }
1182 }
1183