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