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