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