• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.monkey;
18 
19 import com.android.ddmlib.CollectingOutputReceiver;
20 import com.android.ddmlib.IShellOutputReceiver;
21 import com.android.loganalysis.item.AnrItem;
22 import com.android.loganalysis.item.BugreportItem;
23 import com.android.loganalysis.item.MiscKernelLogItem;
24 import com.android.loganalysis.item.MonkeyLogItem;
25 import com.android.loganalysis.parser.BugreportParser;
26 import com.android.loganalysis.parser.KernelLogParser;
27 import com.android.loganalysis.parser.MonkeyLogParser;
28 import com.android.tradefed.config.Option;
29 import com.android.tradefed.config.Option.Importance;
30 import com.android.tradefed.device.DeviceNotAvailableException;
31 import com.android.tradefed.device.ITestDevice;
32 import com.android.tradefed.log.LogUtil.CLog;
33 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
34 import com.android.tradefed.result.ByteArrayInputStreamSource;
35 import com.android.tradefed.result.DeviceFileReporter;
36 import com.android.tradefed.result.FileInputStreamSource;
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.TestDescription;
41 import com.android.tradefed.testtype.IDeviceTest;
42 import com.android.tradefed.testtype.IRemoteTest;
43 import com.android.tradefed.testtype.IRetriableTest;
44 import com.android.tradefed.util.ArrayUtil;
45 import com.android.tradefed.util.Bugreport;
46 import com.android.tradefed.util.CircularAtraceUtil;
47 import com.android.tradefed.util.FileUtil;
48 import com.android.tradefed.util.IRunUtil;
49 import com.android.tradefed.util.RunUtil;
50 import com.android.tradefed.util.StreamUtil;
51 
52 import org.junit.Assert;
53 
54 import java.io.BufferedReader;
55 import java.io.File;
56 import java.io.FileReader;
57 import java.io.IOException;
58 import java.io.InputStreamReader;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.Date;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.LinkedHashMap;
65 import java.util.LinkedList;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Random;
69 import java.util.concurrent.TimeUnit;
70 
71 /** Runner for stress tests which use the monkey command. */
72 public class MonkeyBase implements IDeviceTest, IRemoteTest, IRetriableTest {
73 
74     public static final String MONKEY_LOG_NAME = "monkey_log";
75     public static final String BUGREPORT_NAME = "bugreport";
76 
77     /** Allow a 15 second buffer between the monkey run time and the delta uptime. */
78     public static final long UPTIME_BUFFER = 15 * 1000;
79 
80     private static final String DEVICE_WHITELIST_PATH = "/data/local/tmp/monkey_whitelist.txt";
81 
82     /**
83      * am command template to launch app intent with same action, category and task flags as if user
84      * started it from the app's launcher icon
85      */
86     private static final String LAUNCH_APP_CMD =
87             "am start -W -n '%s' "
88                     + "-a android.intent.action.MAIN -c android.intent.category.LAUNCHER -f 0x10200000";
89 
90     private static final String NULL_UPTIME = "0.00";
91 
92     /**
93      * Helper to run a monkey command with an absolute timeout.
94      *
95      * <p>This is used so that the command can be stopped after a set timeout, since the timeout
96      * that {@link ITestDevice#executeShellCommand(String, IShellOutputReceiver, long, TimeUnit,
97      * int)} takes applies to the time between output, not the overall time of the command.
98      */
99     private class CommandHelper {
100         private DeviceNotAvailableException mException = null;
101         private String mOutput = null;
102 
runCommand(final ITestDevice device, final String command, long timeout)103         public void runCommand(final ITestDevice device, final String command, long timeout)
104                 throws DeviceNotAvailableException {
105             final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
106             Thread t =
107                     new Thread() {
108                         @Override
109                         public void run() {
110                             try {
111                                 device.executeShellCommand(command, receiver);
112                             } catch (DeviceNotAvailableException e) {
113                                 mException = e;
114                             }
115                         }
116                     };
117 
118             t.start();
119 
120             try {
121                 t.join(timeout);
122             } catch (InterruptedException e) {
123                 // Ignore and log.  The thread should terminate once receiver.cancel() is called.
124                 CLog.e("Thread was interrupted while running %s", command);
125             }
126 
127             mOutput = receiver.getOutput();
128             receiver.cancel();
129 
130             if (mException != null) {
131                 throw mException;
132             }
133         }
134 
getOutput()135         public String getOutput() {
136             return mOutput;
137         }
138     }
139 
140     @Option(name = "package", description = "Package name to send events to.  May be repeated.")
141     private Collection<String> mPackages = new LinkedList<>();
142 
143     @Option(
144             name = "exclude-package",
145             description =
146                     "Substring of package names to exclude from "
147                             + "the package list. May be repeated.",
148             importance = Importance.IF_UNSET)
149     private Collection<String> mExcludePackages = new HashSet<>();
150 
151     @Option(name = "category", description = "App Category. May be repeated.")
152     private Collection<String> mCategories = new LinkedList<>();
153 
154     @Option(name = "option", description = "Option to pass to monkey command. May be repeated.")
155     private Collection<String> mOptions = new LinkedList<>();
156 
157     @Option(
158             name = "launch-extras-int",
159             description =
160                     "Launch int extras. May be repeated. "
161                             + "Format: --launch-extras-i key value. Note: this will be applied to all components.")
162     private Map<String, Integer> mIntegerExtras = new HashMap<>();
163 
164     @Option(
165             name = "launch-extras-str",
166             description =
167                     "Launch string extras. May be repeated. "
168                             + "Format: --launch-extras-s key value. Note: this will be applied to all components.")
169     private Map<String, String> mStringExtras = new HashMap<>();
170 
171     @Option(
172             name = "target-count",
173             description = "Target number of events to send.",
174             importance = Importance.ALWAYS)
175     private int mTargetCount = 125000;
176 
177     @Option(name = "random-seed", description = "Random seed to use for the monkey.")
178     private Long mRandomSeed = null;
179 
180     @Option(
181             name = "throttle",
182             description =
183                     "How much time to wait between sending successive "
184                             + "events, in msecs.  Default is 0ms.")
185     private int mThrottle = 0;
186 
187     @Option(
188             name = "ignore-crashes",
189             description = "Monkey should keep going after encountering " + "an app crash")
190     private boolean mIgnoreCrashes = false;
191 
192     @Option(
193             name = "ignore-timeout",
194             description = "Monkey should keep going after encountering " + "an app timeout (ANR)")
195     private boolean mIgnoreTimeouts = false;
196 
197     @Option(
198             name = "reboot-device",
199             description = "Reboot device before running monkey. Defaults " + "to true.")
200     private boolean mRebootDevice = true;
201 
202     @Option(name = "idle-time", description = "How long to sleep before running monkey, in secs")
203     private int mIdleTimeSecs = 5 * 60;
204 
205     @Option(
206             name = "monkey-arg",
207             description =
208                     "Extra parameters to pass onto monkey. Key/value "
209                             + "pairs should be passed as key:value. May be repeated.")
210     private Collection<String> mMonkeyArgs = new LinkedList<>();
211 
212     @Option(
213             name = "use-pkg-whitelist-file",
214             description =
215                     "Whether to use the monkey "
216                             + "--pkg-whitelist-file option to work around cmdline length limits")
217     private boolean mUseWhitelistFile = false;
218 
219     @Option(
220             name = "monkey-timeout",
221             description =
222                     "How long to wait for the monkey to "
223                             + "complete, in minutes. Default is 4 hours.")
224     private int mMonkeyTimeout = 4 * 60;
225 
226     @Option(
227             name = "warmup-component",
228             description =
229                     "Component name of app to launch for "
230                             + "\"warming up\" before monkey test, will be used in an intent together with standard "
231                             + "flags and parameters as launched from Launcher. May be repeated")
232     private List<String> mLaunchComponents = new ArrayList<>();
233 
234     @Option(name = "retry-on-failure", description = "Retry the test on failure")
235     private boolean mRetryOnFailure = false;
236 
237     // FIXME: Remove this once traces.txt is no longer needed.
238     @Option(
239             name = "upload-file-pattern",
240             description =
241                     "File glob of on-device files to upload "
242                             + "if found. Takes two arguments: the glob, and the file type "
243                             + "(text/xml/zip/gzip/png/unknown).  May be repeated.")
244     private Map<String, LogDataType> mUploadFilePatterns = new LinkedHashMap<>();
245 
246     @Option(name = "screenshot", description = "Take a device screenshot on monkey completion")
247     private boolean mScreenshot = false;
248 
249     @Option(
250             name = "ignore-security-exceptions",
251             description = "Ignore SecurityExceptions while injecting events")
252     private boolean mIgnoreSecurityExceptions = true;
253 
254     @Option(
255             name = "collect-atrace",
256             description = "Enable a continuous circular buffer to collect atrace information")
257     private boolean mAtraceEnabled = false;
258 
259     // options for generating ANR report via post processing script
260     @Option(name = "generate-anr-report", description = "Generate ANR report via post-processing")
261     private boolean mGenerateAnrReport = false;
262 
263     @Option(
264             name = "anr-report-script",
265             description = "Path to the script for monkey ANR " + "report generation.")
266     private String mAnrReportScriptPath = null;
267 
268     @Option(
269             name = "anr-report-storage-backend-base-path",
270             description = "Base path to the storage " + "backend used for saving the reports")
271     private String mAnrReportBasePath = null;
272 
273     @Option(
274             name = "anr-report-storage-backend-url-prefix",
275             description =
276                     "URL prefix for the "
277                             + "storage backend that would enable web acess to the stored reports.")
278     private String mAnrReportUrlPrefix = null;
279 
280     @Option(
281             name = "anr-report-storage-path",
282             description =
283                     "Sub path under the base storage "
284                             + "location for generated monkey ANR reports.")
285     private String mAnrReportPath = null;
286 
287     private ITestDevice mTestDevice = null;
288     private MonkeyLogItem mMonkeyLog = null;
289     private BugreportItem mBugreport = null;
290     private AnrReportGenerator mAnrGen = null;
291 
292     /** {@inheritDoc} */
293     @Override
run(ITestInvocationListener listener)294     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
295         Assert.assertNotNull(getDevice());
296 
297         TestDescription id = new TestDescription(getClass().getCanonicalName(), "monkey");
298         long startTime = System.currentTimeMillis();
299 
300         listener.testRunStarted(getClass().getCanonicalName(), 1);
301         listener.testStarted(id);
302 
303         try {
304             runMonkey(listener);
305         } finally {
306             listener.testEnded(id, new HashMap<String, Metric>());
307             listener.testRunEnded(
308                     System.currentTimeMillis() - startTime, new HashMap<String, Metric>());
309         }
310     }
311 
312     /** Returns the command that should be used to launch the app, */
getAppCmdWithExtras()313     private String getAppCmdWithExtras() {
314         String extras = "";
315         for (Map.Entry<String, String> sEntry : mStringExtras.entrySet()) {
316             extras += String.format(" -e %s %s", sEntry.getKey(), sEntry.getValue());
317         }
318         for (Map.Entry<String, Integer> sEntry : mIntegerExtras.entrySet()) {
319             extras += String.format(" --ei %s %d", sEntry.getKey(), sEntry.getValue());
320         }
321         return LAUNCH_APP_CMD + extras;
322     }
323 
324     /** Run the monkey one time and return a {@link MonkeyLogItem} for the run. */
runMonkey(ITestInvocationListener listener)325     protected void runMonkey(ITestInvocationListener listener) throws DeviceNotAvailableException {
326         ITestDevice device = getDevice();
327         if (mRebootDevice) {
328             CLog.v("Rebooting device prior to running Monkey");
329             device.reboot();
330         } else {
331             CLog.v("Pre-run reboot disabled; skipping...");
332         }
333 
334         if (mIdleTimeSecs > 0) {
335             CLog.i("Sleeping for %d seconds to allow device to settle...", mIdleTimeSecs);
336             getRunUtil().sleep(mIdleTimeSecs * 1000);
337             CLog.i("Done sleeping.");
338         }
339 
340         // launch the list of apps that needs warm-up
341         for (String componentName : mLaunchComponents) {
342             getDevice().executeShellCommand(String.format(getAppCmdWithExtras(), componentName));
343             // give it some more time to settle down
344             getRunUtil().sleep(5000);
345         }
346 
347         if (mUseWhitelistFile) {
348             // Use \r\n for new lines on the device.
349             String whitelist = ArrayUtil.join("\r\n", setSubtract(mPackages, mExcludePackages));
350             device.pushString(whitelist.toString(), DEVICE_WHITELIST_PATH);
351         }
352 
353         // Generate the monkey command to run, given the options
354         String command = buildMonkeyCommand();
355         CLog.i("About to run monkey with at %d minute timeout: %s", mMonkeyTimeout, command);
356 
357         StringBuilder outputBuilder = new StringBuilder();
358         CommandHelper commandHelper = new CommandHelper();
359 
360         long start = System.currentTimeMillis();
361         long duration = 0;
362         Date dateAfter = null;
363         String uptimeAfter = NULL_UPTIME;
364         FileInputStreamSource atraceStream = null;
365 
366         // Generate the monkey log prefix, which includes the device uptime
367         outputBuilder.append(
368                 String.format(
369                         "# %s - device uptime = %s: Monkey command used "
370                                 + "for this test:\nadb shell %s\n\n",
371                         new Date().toString(), getUptime(), command));
372 
373         // Start atrace before running the monkey command, but after reboot
374         if (mAtraceEnabled) {
375             CircularAtraceUtil.startTrace(getDevice(), null, 10);
376         }
377 
378         if (mGenerateAnrReport) {
379             mAnrGen =
380                     new AnrReportGenerator(
381                             mAnrReportScriptPath,
382                             mAnrReportBasePath,
383                             mAnrReportUrlPrefix,
384                             mAnrReportPath,
385                             mTestDevice.getBuildId(),
386                             mTestDevice.getBuildFlavor(),
387                             mTestDevice.getSerialNumber());
388         }
389 
390         try {
391             onMonkeyStart();
392             commandHelper.runCommand(mTestDevice, command, getMonkeyTimeoutMs());
393         } finally {
394             // Wait for device to recover if it's not online.  If it hasn't recovered, ignore.
395             try {
396                 mTestDevice.waitForDeviceOnline();
397                 mTestDevice.enableAdbRoot();
398                 duration = System.currentTimeMillis() - start;
399                 dateAfter = new Date();
400                 uptimeAfter = getUptime();
401                 onMonkeyFinish();
402                 takeScreenshot(listener, "screenshot");
403 
404                 if (mAtraceEnabled) {
405                     atraceStream = CircularAtraceUtil.endTrace(getDevice());
406                 }
407 
408                 mBugreport = takeBugreport(listener, BUGREPORT_NAME);
409                 // FIXME: Remove this once traces.txt is no longer needed.
410                 takeTraces(listener);
411             } finally {
412                 // @@@ DO NOT add anything that requires device interaction into this block     @@@
413                 // @@@ logging that no longer requires device interaction MUST be in this block @@@
414                 outputBuilder.append(commandHelper.getOutput());
415                 if (dateAfter == null) {
416                     dateAfter = new Date();
417                 }
418 
419                 // Generate the monkey log suffix, which includes the device uptime.
420                 outputBuilder.append(
421                         String.format(
422                                 "\n# %s - device uptime = %s: Monkey command "
423                                         + "ran for: %d:%02d (mm:ss)\n",
424                                 dateAfter.toString(),
425                                 uptimeAfter,
426                                 duration / 1000 / 60,
427                                 duration / 1000 % 60));
428                 mMonkeyLog = createMonkeyLog(listener, MONKEY_LOG_NAME, outputBuilder.toString());
429 
430                 boolean isAnr = mMonkeyLog.getCrash() instanceof AnrItem;
431                 if (mAtraceEnabled && isAnr) {
432                     // This was identified as an ANR; post atrace data
433                     listener.testLog("circular-atrace", LogDataType.TEXT, atraceStream);
434                 }
435                 if (mAnrGen != null) {
436                     if (isAnr) {
437                         if (!mAnrGen.genereateAnrReport(listener)) {
438                             CLog.w("Failed to post-process ANR.");
439                         } else {
440                             CLog.i("Successfully post-processed ANR.");
441                         }
442                         mAnrGen.cleanTempFiles();
443                     } else {
444                         CLog.d("ANR post-processing enabled but no ANR detected.");
445                     }
446                 }
447                 StreamUtil.cancel(atraceStream);
448             }
449         }
450 
451         // Extra logs for what was found
452         if (mBugreport != null && mBugreport.getLastKmsg() != null) {
453             List<MiscKernelLogItem> kernelErrors =
454                     mBugreport.getLastKmsg().getMiscEvents(KernelLogParser.KERNEL_ERROR);
455             List<MiscKernelLogItem> kernelResets =
456                     mBugreport.getLastKmsg().getMiscEvents(KernelLogParser.KERNEL_ERROR);
457             CLog.d(
458                     "Found %d kernel errors and %d kernel resets in last kmsg",
459                     kernelErrors.size(), kernelResets.size());
460             for (int i = 0; i < kernelErrors.size(); i++) {
461                 String stack = kernelErrors.get(i).getStack();
462                 if (stack != null) {
463                     CLog.d("Kernel Error #%d: %s", i + 1, stack.split("\n")[0].trim());
464                 }
465             }
466             for (int i = 0; i < kernelResets.size(); i++) {
467                 String stack = kernelResets.get(i).getStack();
468                 if (stack != null) {
469                     CLog.d("Kernel Reset #%d: %s", i + 1, stack.split("\n")[0].trim());
470                 }
471             }
472         }
473 
474         checkResults();
475     }
476 
477     /** A hook to allow subclasses to perform actions just before the monkey starts. */
onMonkeyStart()478     protected void onMonkeyStart() {
479         // empty
480     }
481 
482     /** A hook to allow sublaccess to perform actions just after the monkey finished. */
onMonkeyFinish()483     protected void onMonkeyFinish() {
484         // empty
485     }
486 
487     /**
488      * If enabled, capture a screenshot and send it to a listener.
489      *
490      * @throws DeviceNotAvailableException
491      */
takeScreenshot(ITestInvocationListener listener, String screenshotName)492     protected void takeScreenshot(ITestInvocationListener listener, String screenshotName)
493             throws DeviceNotAvailableException {
494         if (mScreenshot) {
495             try (InputStreamSource screenshot = mTestDevice.getScreenshot("JPEG")) {
496                 listener.testLog(screenshotName, LogDataType.JPEG, screenshot);
497             }
498         }
499     }
500 
501     /** Capture a bugreport and send it to a listener. */
takeBugreport(ITestInvocationListener listener, String bugreportName)502     protected BugreportItem takeBugreport(ITestInvocationListener listener, String bugreportName) {
503         Bugreport bugreport = mTestDevice.takeBugreport();
504         if (bugreport == null) {
505             CLog.e("Could not take bugreport");
506             return null;
507         }
508         bugreport.log(bugreportName, listener);
509         File main = null;
510         InputStreamSource is = null;
511         try {
512             main = bugreport.getMainFile();
513             if (main == null) {
514                 CLog.e("Bugreport has no main file");
515                 return null;
516             }
517             if (mAnrGen != null) {
518                 is = new FileInputStreamSource(main);
519                 mAnrGen.setBugReportInfo(is);
520             }
521             return new BugreportParser().parse(new BufferedReader(new FileReader(main)));
522         } catch (IOException e) {
523             CLog.e("Could not process bugreport");
524             CLog.e(e);
525             return null;
526         } finally {
527             StreamUtil.close(bugreport);
528             StreamUtil.cancel(is);
529             FileUtil.deleteFile(main);
530         }
531     }
532 
takeTraces(ITestInvocationListener listener)533     protected void takeTraces(ITestInvocationListener listener) {
534         DeviceFileReporter dfr = new DeviceFileReporter(mTestDevice, listener);
535         dfr.addPatterns(mUploadFilePatterns);
536         try {
537             dfr.run();
538         } catch (DeviceNotAvailableException e) {
539             // Log but don't throw
540             CLog.e(
541                     "Device %s became unresponsive while pulling files",
542                     mTestDevice.getSerialNumber());
543         }
544     }
545 
546     /** Create the monkey log, parse it, and send it to a listener. */
createMonkeyLog( ITestInvocationListener listener, String monkeyLogName, String log)547     protected MonkeyLogItem createMonkeyLog(
548             ITestInvocationListener listener, String monkeyLogName, String log) {
549         try (InputStreamSource source = new ByteArrayInputStreamSource(log.getBytes())) {
550             if (mAnrGen != null) {
551                 mAnrGen.setMonkeyLogInfo(source);
552             }
553             listener.testLog(monkeyLogName, LogDataType.MONKEY_LOG, source);
554             return new MonkeyLogParser()
555                     .parse(new BufferedReader(new InputStreamReader(source.createInputStream())));
556         } catch (IOException e) {
557             CLog.e("Could not process monkey log.");
558             CLog.e(e);
559             return null;
560         }
561     }
562 
563     /**
564      * A helper method to build a monkey command given the specified arguments.
565      *
566      * <p>Actual output argument order is: {@code monkey [-p PACKAGE]... [-c CATEGORY]...
567      * [--OPTION]... -s SEED -v -v -v COUNT}
568      *
569      * @return a {@link String} containing the command with the arguments assembled in the proper
570      *     order.
571      */
buildMonkeyCommand()572     protected String buildMonkeyCommand() {
573         List<String> cmdList = new LinkedList<>();
574         cmdList.add("monkey");
575 
576         if (!mUseWhitelistFile) {
577             for (String pkg : setSubtract(mPackages, mExcludePackages)) {
578                 cmdList.add("-p");
579                 cmdList.add(pkg);
580             }
581         }
582 
583         for (String cat : mCategories) {
584             cmdList.add("-c");
585             cmdList.add(cat);
586         }
587 
588         if (mIgnoreSecurityExceptions) {
589             cmdList.add("--ignore-security-exceptions");
590         }
591 
592         if (mThrottle >= 1) {
593             cmdList.add("--throttle");
594             cmdList.add(Integer.toString(mThrottle));
595         }
596         if (mIgnoreCrashes) {
597             cmdList.add("--ignore-crashes");
598         }
599         if (mIgnoreTimeouts) {
600             cmdList.add("--ignore-timeouts");
601         }
602 
603         if (mUseWhitelistFile) {
604             cmdList.add("--pkg-whitelist-file");
605             cmdList.add(DEVICE_WHITELIST_PATH);
606         }
607 
608         for (String arg : mMonkeyArgs) {
609             String[] args = arg.split(":");
610             cmdList.add(String.format("--%s", args[0]));
611             if (args.length > 1) {
612                 cmdList.add(args[1]);
613             }
614         }
615 
616         cmdList.addAll(mOptions);
617 
618         cmdList.add("-s");
619         if (mRandomSeed == null) {
620             // Pick a number that is random, but in a small enough range that some seeds are likely
621             // to be repeated
622             cmdList.add(Long.toString(new Random().nextInt(1000)));
623         } else {
624             cmdList.add(Long.toString(mRandomSeed));
625         }
626 
627         // verbose
628         cmdList.add("-v");
629         cmdList.add("-v");
630         cmdList.add("-v");
631         cmdList.add(Integer.toString(mTargetCount));
632 
633         return ArrayUtil.join(" ", cmdList);
634     }
635 
636     /**
637      * Get a {@link String} containing the number seconds since the device was booted.
638      *
639      * <p>{@code NULL_UPTIME} is returned if the device becomes unresponsive. Used in the monkey log
640      * prefix and suffix.
641      */
getUptime()642     protected String getUptime() {
643         try {
644             // make two attempts to get valid uptime
645             for (int i = 0; i < 2; i++) {
646                 // uptime will typically have a format like "5278.73 1866.80".  Use the first one
647                 // (which is wall-time)
648                 String uptime = mTestDevice.executeShellCommand("cat /proc/uptime").split(" ")[0];
649                 try {
650                     Float.parseFloat(uptime);
651                     // if this parsed, its a valid uptime
652                     return uptime;
653                 } catch (NumberFormatException e) {
654                     CLog.w(
655                             "failed to get valid uptime from %s. Received: '%s'",
656                             mTestDevice.getSerialNumber(), uptime);
657                 }
658             }
659         } catch (DeviceNotAvailableException e) {
660             CLog.e(
661                     "Device %s became unresponsive while getting the uptime.",
662                     mTestDevice.getSerialNumber());
663         }
664         return NULL_UPTIME;
665     }
666 
667     /**
668      * Perform set subtraction between two {@link Collection} objects.
669      *
670      * <p>The return value will consist of all of the elements of {@code keep}, excluding the
671      * elements that are also in {@code exclude}. Exposed for unit testing.
672      *
673      * @param keep the minuend in the subtraction
674      * @param exclude the subtrahend
675      * @return the collection of elements in {@code keep} that are not also in {@code exclude}. If
676      *     {@code keep} is an ordered {@link Collection}, the remaining elements in the return value
677      *     will remain in their original order.
678      */
setSubtract(Collection<String> keep, Collection<String> exclude)679     static Collection<String> setSubtract(Collection<String> keep, Collection<String> exclude) {
680         if (exclude.isEmpty()) {
681             return keep;
682         }
683 
684         Collection<String> output = new ArrayList<>(keep);
685         output.removeAll(exclude);
686         return output;
687     }
688 
689     /** Get {@link IRunUtil} to use. Exposed for unit testing. */
getRunUtil()690     IRunUtil getRunUtil() {
691         return RunUtil.getDefault();
692     }
693 
694     /** {@inheritDoc} */
695     @Override
setDevice(ITestDevice device)696     public void setDevice(ITestDevice device) {
697         mTestDevice = device;
698     }
699 
700     /** {@inheritDoc} */
701     @Override
getDevice()702     public ITestDevice getDevice() {
703         return mTestDevice;
704     }
705 
706     /**
707      * {@inheritDoc}
708      *
709      * @return {@code false} if retry-on-failure is not set, if the monkey ran to completion,
710      *     crashed in an understood way, or if there were no packages to run, {@code true}
711      *     otherwise.
712      */
713     @Override
isRetriable()714     public boolean isRetriable() {
715         return mRetryOnFailure;
716     }
717 
718     /** Check the results and return if valid or throw an assertion error if not valid. */
checkResults()719     private void checkResults() {
720         Assert.assertNotNull("Monkey log is null", mMonkeyLog);
721         Assert.assertNotNull("Bugreport is null", mBugreport);
722         Assert.assertNotNull("Bugreport is empty", mBugreport.getTime());
723 
724         // If there are no activities, retrying the test won't matter.
725         if (mMonkeyLog.getNoActivities()) {
726             return;
727         }
728 
729         Assert.assertNotNull("Start uptime is missing", mMonkeyLog.getStartUptimeDuration());
730         Assert.assertNotNull("Stop uptime is missing", mMonkeyLog.getStopUptimeDuration());
731         Assert.assertNotNull("Total duration is missing", mMonkeyLog.getTotalDuration());
732 
733         long startUptime = mMonkeyLog.getStartUptimeDuration();
734         long stopUptime = mMonkeyLog.getStopUptimeDuration();
735         long totalDuration = mMonkeyLog.getTotalDuration();
736 
737         Assert.assertTrue(
738                 "Uptime failure", stopUptime - startUptime > totalDuration - UPTIME_BUFFER);
739 
740         // False count
741         Assert.assertFalse(
742                 "False count",
743                 mMonkeyLog.getIsFinished()
744                         && mMonkeyLog.getTargetCount() - mMonkeyLog.getIntermediateCount() > 100);
745 
746         // Monkey finished or crashed, so don't fail
747         if (mMonkeyLog.getIsFinished() || mMonkeyLog.getFinalCount() != null) {
748             return;
749         }
750 
751         // Missing count
752         Assert.fail("Missing count");
753     }
754 
755     /** Get the monkey timeout in milliseconds */
getMonkeyTimeoutMs()756     protected long getMonkeyTimeoutMs() {
757         return mMonkeyTimeout * 60 * 1000;
758     }
759 }
760