1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.testtype;
17 
18 import com.android.tradefed.config.ConfigurationException;
19 import com.android.tradefed.config.IConfiguration;
20 import com.android.tradefed.config.IConfigurationReceiver;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.Option.Importance;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.config.OptionCopier;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.device.metric.CollectorHelper;
28 import com.android.tradefed.device.metric.IMetricCollector;
29 import com.android.tradefed.device.metric.IMetricCollectorReceiver;
30 import com.android.tradefed.invoker.TestInformation;
31 import com.android.tradefed.log.LogUtil.CLog;
32 import com.android.tradefed.result.BugreportCollector;
33 import com.android.tradefed.result.ITestInvocationListener;
34 import com.android.tradefed.result.TestDescription;
35 import com.android.tradefed.result.TestRunResult;
36 import com.android.tradefed.testtype.retry.IAutoRetriableTest;
37 import com.android.tradefed.util.AbiFormatter;
38 import com.android.tradefed.util.ListInstrumentationParser;
39 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
40 
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.HashMap;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.Map;
47 
48 /** Runs all instrumentation found on current device. */
49 @OptionClass(alias = "installed-instrumentation")
50 public class InstalledInstrumentationsTest
51         implements IDeviceTest,
52                 IShardableTest,
53                 IMetricCollectorReceiver,
54                 IAutoRetriableTest,
55                 IConfigurationReceiver {
56 
57     private static final String PM_LIST_CMD = "pm list instrumentation";
58     private static final String LINE_SEPARATOR = "\\r?\\n";
59 
60     private ITestDevice mDevice;
61 
62     @Option(name = "shell-timeout",
63             description="The defined timeout (in milliseconds) is used as a maximum waiting time "
64                     + "when expecting the command output from the device. At any time, if the "
65                     + "shell command does not output anything for a period longer than defined "
66                     + "timeout the TF run terminates. For no timeout, set to 0.")
67     private long mShellTimeout = 10 * 60 * 1000;  // default to 10 minutes
68 
69     @Option(name = "test-timeout",
70             description="Sets timeout (in milliseconds) that will be applied to each test. In the "
71                     + "event of a test timeout it will log the results and proceed with executing "
72                     + "the next test. For no timeout, set to 0.")
73     private int mTestTimeout = 5 * 60 * 1000;  // default to 5 minutes
74 
75     @Option(name = "size",
76             description = "Restrict tests to a specific test size. " +
77             "One of 'small', 'medium', 'large'",
78             importance = Importance.IF_UNSET)
79     private String mTestSize = null;
80 
81     @Option(name = "runner",
82             description = "Restrict tests executed to a specific instrumentation class runner. " +
83     "Installed instrumentations that do not have this runner will be skipped.")
84     private String mRunner = null;
85 
86     @Option(name = "rerun",
87             description = "Rerun unexecuted tests individually on same device if test run " +
88             "fails to complete.")
89     private boolean mIsRerunMode = true;
90 
91     @Option(name = "resume",
92             description = "Schedule unexecuted tests for resumption on another device " +
93             "if first device becomes unavailable.")
94     private boolean mIsResumeMode = false;
95 
96     /** @deprecated delete when we are sure it's not used anywhere. */
97     @Deprecated
98     @Option(name = "send-coverage", description = "Send coverage target info to test listeners.")
99     private boolean mSendCoverage = false;
100 
101     @Option(name = "bugreport-on-failure", description = "Sets which failed testcase events " +
102             "cause a bugreport to be collected. a bugreport after failed testcases.  Note that " +
103             "there is _no feedback mechanism_ between the test runner and the bugreport " +
104             "collector, so use the EACH setting with due caution.")
105     private BugreportCollector.Freq mBugreportFrequency = null;
106 
107     @Option(
108         name = "bugreport-on-run-failure",
109         description = "Take a bugreport if the instrumentation finish with a run failure"
110     )
111     private boolean mBugreportOnRunFailure = false;
112 
113     @Option(name = "screenshot-on-failure", description = "Take a screenshot on every test failure")
114     private boolean mScreenshotOnFailure = false;
115 
116     @Option(name = "logcat-on-failure", description =
117             "take a logcat snapshot on every test failure.")
118     private boolean mLogcatOnFailures = false;
119 
120     @Option(name = "logcat-on-failure-size", description =
121             "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " +
122             "Should be an amount that can comfortably fit in memory.")
123     private int mMaxLogcatBytes = 500 * 1024; // 500K
124 
125     @Option(name = "class",
126             description = "Only run tests in specified class")
127     private String mTestClass = null;
128 
129     @Option(name = "package",
130             description =
131             "Only run tests within this specific java package. Will be ignored if --class is set.")
132     private String mTestPackageName = null;
133 
134     @Option(name = "instrumentation-arg",
135             description = "Additional instrumentation arguments to provide.")
136     private Map<String, String> mInstrArgMap = new HashMap<String, String>();
137 
138     @Option(
139         name = "rerun-from-file",
140         description =
141                 "Use test file instead of separate adb commands for each test "
142                         + "when re-running instrumentations for tests that failed to run in "
143                         + "previous attempts. "
144     )
145     private boolean mReRunUsingTestFile = true;
146 
147     @Option(name = "rerun-from-file-attempts", description =
148             "Max attempts to rerun tests from file. -1 means rerun from file infinitely.")
149     private int mReRunUsingTestFileAttempts = -1;
150 
151     @Option(
152         name = "fallback-to-serial-rerun",
153         description = "Rerun tests serially after rerun from file failed."
154     )
155     private boolean mFallbackToSerialRerun = false;
156 
157     @Option(name = "reboot-before-rerun", description =
158             "Reboot a device before re-running instrumentations.")
159     private boolean mRebootBeforeReRun = false;
160 
161     @Option(name = "disable", description =
162             "Disable the test by setting this flag to true.")
163     private boolean mDisable = false;
164 
165     @Option(
166         name = "coverage",
167         description =
168                 "Collect code coverage for this test run. Note that the build under test must be a "
169                         + "coverage build or else this will fail."
170     )
171     private boolean mCoverage = false;
172 
173     @Option(
174         name = "hidden-api-checks",
175         description =
176                 "If set to false, the '--no-hidden-api-checks' flag will be passed to the am "
177                         + "instrument command. Only works for P or later."
178     )
179     private boolean mHiddenApiChecks = true;
180 
181     @Option(
182             name = "test-api-access",
183             description =
184                     "If set to false and hidden API checks are enabled, the '--no-test-api-access'"
185                             + " flag will be passed to the am instrument command."
186                             + " Only works for R or later.")
187     private boolean mTestApiAccess = true;
188 
189     @Option(
190         name = "isolated-storage",
191         description =
192                 "If set to false, the '--no-isolated-storage' flag will be passed to the am "
193                         + "instrument command. Only works for Q or later."
194     )
195     private boolean mIsolatedStorage = true;
196 
197     @Option(
198         name = "window-animation",
199         description =
200                 "If set to false, the '--no-window-animation' flag will be passed to the am "
201                         + "instrument command. Only works for ICS or later."
202     )
203     private boolean mWindowAnimation = true;
204 
205     @Option(
206             name = "disable-duplicate-test-check",
207             description =
208                     "If set to true, it will not check that a method is only run once by a "
209                             + "given instrumentation.")
210     private boolean mDisableDuplicateCheck = false;
211 
212     @Option(
213             name = "create-instrumentation-tests",
214             description =
215                     "Create InstrumentationTest type rather than more recent AndroidJUnitTest.")
216     private boolean mDowngradeInstrumentation = false;
217 
218     private int mTotalShards = 0;
219     private int mShardIndex = 0;
220     private List<IMetricCollector> mMetricCollectorList = new ArrayList<>();
221     private IConfiguration mConfiguration;
222 
223     private List<InstrumentationTest> mTests = null;
224     private Map<String, List<TestDescription>> mRunTestsFailureMap = null;
225 
226     @Option(name = AbiFormatter.FORCE_ABI_STRING,
227             description = AbiFormatter.FORCE_ABI_DESCRIPTION,
228             importance = Importance.IF_UNSET)
229     private String mForceAbi = null;
230 
231     @Override
shouldRetry(int attemptJustExecuted, List<TestRunResult> previousResults)232     public boolean shouldRetry(int attemptJustExecuted, List<TestRunResult> previousResults)
233             throws DeviceNotAvailableException {
234         boolean retry = false;
235         mRunTestsFailureMap = new HashMap<>();
236         for (TestRunResult run : previousResults) {
237             if (run == null) {
238                 continue;
239             }
240             if (run.isRunFailure()) {
241                 retry = true;
242                 // Retry the full run in case of run failure
243                 mRunTestsFailureMap.put(run.getName(), null);
244             } else if (run.hasFailedTests()) {
245                 retry = true;
246                 mRunTestsFailureMap.put(
247                         run.getName(), new ArrayList<TestDescription>(run.getFailedTests()));
248             }
249         }
250 
251         if (!retry) {
252             CLog.d("No test run or test case failures. No need to retry.");
253             mRunTestsFailureMap = null;
254         }
255         return retry;
256     }
257 
258     @Override
setConfiguration(IConfiguration configuration)259     public void setConfiguration(IConfiguration configuration) {
260         mConfiguration = configuration;
261     }
262 
263     /**
264      * {@inheritDoc}
265      */
266     @Override
getDevice()267     public ITestDevice getDevice() {
268         return mDevice;
269     }
270 
271     /**
272      * {@inheritDoc}
273      */
274     @Override
setDevice(ITestDevice device)275     public void setDevice(ITestDevice device) {
276         mDevice = device;
277     }
278 
279     /**
280      * Gets the list of {@link InstrumentationTest}s contained within.
281      * <p/>
282      * Exposed for unit testing.
283      */
getTests()284     List<InstrumentationTest> getTests() {
285         return mTests;
286     }
287 
288     /**
289      * Sets the number of total shards this test should be split into.
290      * <p/>
291      * Exposed for unit testing.
292      */
setTotalShards(int totalShards)293     void setTotalShards(int totalShards) {
294         mTotalShards = totalShards;
295     }
296 
297     /**
298      * Sets the shard index number of this test.
299      * <p/>
300      * Exposed for unit testing.
301      */
setShardIndex(int shardIndex)302     void setShardIndex(int shardIndex) {
303         mShardIndex = shardIndex;
304     }
305 
306     /** {@inheritDoc} */
307     @Override
run(TestInformation testInfo, ITestInvocationListener listener)308     public void run(TestInformation testInfo, ITestInvocationListener listener)
309             throws DeviceNotAvailableException {
310         if (getDevice() == null) {
311             throw new IllegalArgumentException("Device has not been set");
312         }
313 
314         if (mDisable) {
315             return;
316         }
317         buildTests();
318         doRun(testInfo, listener);
319     }
320 
321     /**
322      * Build the list of tests to run from the device, if not done already. Note: Can be called
323      * multiple times in case of resumed runs.
324      * @throws DeviceNotAvailableException
325      */
buildTests()326     private void buildTests() throws DeviceNotAvailableException {
327         if (mTests == null) {
328 
329             ListInstrumentationParser parser = new ListInstrumentationParser();
330             String pmListOutput = getDevice().executeShellCommand(PM_LIST_CMD);
331             String pmListLines[] = pmListOutput.split(LINE_SEPARATOR);
332             parser.processNewLines(pmListLines);
333 
334             if (parser.getInstrumentationTargets().isEmpty()) {
335                 throw new IllegalArgumentException(String.format(
336                         "No instrumentations were found on device %s - <%s>", getDevice()
337                                 .getSerialNumber(), pmListOutput));
338             }
339 
340             int numUnshardedTests = 0;
341             mTests = new LinkedList<InstrumentationTest>();
342             for (InstrumentationTarget target : parser.getInstrumentationTargets()) {
343                 if (mRunner == null || mRunner.equals(target.runnerName)) {
344                     // Some older instrumentations are not shardable. As a result, we should try to
345                     // shard these instrumentations by APKs, rather than by test methods.
346                     if (mTotalShards > 0 && !target.isShardable()) {
347                         numUnshardedTests += 1;
348                         if ((numUnshardedTests - 1) % mTotalShards != mShardIndex) {
349                             continue;
350                         }
351                     }
352                     List<IMetricCollector> collectors =
353                             CollectorHelper.cloneCollectors(mMetricCollectorList);
354                     InstrumentationTest t = createInstrumentationTest();
355                     try {
356                         // Copies all current argument values to the new runner that will be
357                         // used to actually run the tests.
358                         OptionCopier.copyOptions(InstalledInstrumentationsTest.this, t);
359                     } catch (ConfigurationException e) {
360                         // Bail out rather than run tests with unexpected options
361                         throw new RuntimeException("failed to copy instrumentation options", e);
362                     }
363                     // Pass the collectors to each instrumentation, which will take care of init
364                     t.setMetricCollectors(collectors);
365                     String targetPackageName = target.packageName;
366                     t.setPackageName(targetPackageName);
367                     if (mRunTestsFailureMap != null) {
368                         // Don't retry if there was no failure in a particular instrumentation.
369                         if (!mRunTestsFailureMap.containsKey(targetPackageName)) {
370                             CLog.d("Skipping %s at retry.", targetPackageName);
371                             continue;
372                         }
373                         // if possible reduce the scope of the retry to be more efficient.
374                         if (t instanceof AndroidJUnitTest) {
375                             AndroidJUnitTest filterable = (AndroidJUnitTest) t;
376                             if (mRunTestsFailureMap.containsKey(targetPackageName)) {
377                                 List<TestDescription> tests =
378                                         mRunTestsFailureMap.get(targetPackageName);
379                                 if (tests != null) {
380                                     for (TestDescription test : tests) {
381                                         filterable.addIncludeFilter(
382                                                 String.format(
383                                                         "%s#%s",
384                                                         test.getClassName(),
385                                                         test.getTestNameWithoutParams()));
386                                     }
387                                 }
388                             }
389                         }
390                     }
391                     t.setRunnerName(target.runnerName);
392                     t.setCoverageTarget(target.targetName);
393                     if (mTotalShards > 0 && target.isShardable()) {
394                         t.addInstrumentationArg("shardIndex", Integer.toString(mShardIndex));
395                         t.addInstrumentationArg("numShards", Integer.toString(mTotalShards));
396                     }
397                     mTests.add(t);
398                 }
399             }
400         }
401     }
402 
403     /**
404      * Run the previously built tests.
405      *
406      * @param testInfo the {@link TestInformation} of the invocation.
407      * @param listener the {@link ITestInvocationListener}
408      * @throws DeviceNotAvailableException
409      */
doRun(TestInformation testInfo, ITestInvocationListener listener)410     private void doRun(TestInformation testInfo, ITestInvocationListener listener)
411             throws DeviceNotAvailableException {
412         while (!mTests.isEmpty()) {
413             InstrumentationTest test = mTests.get(0);
414 
415             CLog.d("Running test %s on %s", test.getPackageName(), getDevice().getSerialNumber());
416 
417             test.setDevice(getDevice());
418             test.setConfiguration(mConfiguration);
419             if (mTestClass != null) {
420                 test.setClassName(mTestClass);
421                 if (mTestPackageName != null) {
422                     CLog.e(
423                             "Ignoring --package option with value '%s' since it's incompatible "
424                                     + "with using --class at the same time.",
425                             mTestPackageName);
426                 }
427             } else if (mTestPackageName != null) {
428                 test.setTestPackageName(mTestPackageName);
429             }
430             test.run(testInfo, listener);
431             // test completed, remove from list
432             mTests.remove(0);
433         }
434         mTests = null;
435     }
436 
getShellTimeout()437     long getShellTimeout() {
438         return mShellTimeout;
439     }
440 
getTestTimeout()441     int getTestTimeout() {
442         return mTestTimeout;
443     }
444 
getTestSize()445     String getTestSize() {
446         return mTestSize;
447     }
448 
449     /**
450      * Creates the {@link InstrumentationTest} to use. Exposed for unit testing.
451      */
createInstrumentationTest()452     InstrumentationTest createInstrumentationTest() {
453         // We do not know what kind of instrumentation we will find, so we don't enforce the ddmlib
454         // format for AndroidJUnitRunner.
455         InstrumentationTest test = null;
456         if (mDowngradeInstrumentation) {
457             test = new InstrumentationTest();
458         } else {
459             test = new AndroidJUnitTest();
460         }
461         test.setEnforceFormat(false);
462         return test;
463     }
464 
465     @Override
setMetricCollectors(List<IMetricCollector> collectors)466     public void setMetricCollectors(List<IMetricCollector> collectors) {
467         mMetricCollectorList = collectors;
468     }
469 
470     /** {@inheritDoc} */
471     @Override
split(int shardCountHint)472     public Collection<IRemoteTest> split(int shardCountHint) {
473         if (shardCountHint > 1) {
474             Collection<IRemoteTest> shards = new ArrayList<>(shardCountHint);
475             for (int index = 0; index < shardCountHint; index++) {
476                 shards.add(getTestShard(shardCountHint, index));
477             }
478             return shards;
479         }
480         return null;
481     }
482 
getTestShard(int shardCount, int shardIndex)483     private IRemoteTest getTestShard(int shardCount, int shardIndex) {
484         InstalledInstrumentationsTest shard = new InstalledInstrumentationsTest();
485         try {
486             OptionCopier.copyOptions(this, shard);
487         } catch (ConfigurationException e) {
488             CLog.e("failed to copy instrumentation options: %s", e.getMessage());
489         }
490         shard.mShardIndex = shardIndex;
491         shard.mTotalShards = shardCount;
492         return shard;
493     }
494 }
495