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.invoker.TestInformation; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 35 import com.android.tradefed.result.ByteArrayInputStreamSource; 36 import com.android.tradefed.result.DeviceFileReporter; 37 import com.android.tradefed.result.FileInputStreamSource; 38 import com.android.tradefed.result.ITestInvocationListener; 39 import com.android.tradefed.result.InputStreamSource; 40 import com.android.tradefed.result.LogDataType; 41 import com.android.tradefed.result.TestDescription; 42 import com.android.tradefed.testtype.IDeviceTest; 43 import com.android.tradefed.testtype.IRemoteTest; 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 { 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_ALLOWLIST_PATH = "/data/local/tmp/monkey_allowlist.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-allowlist-file", 214 description = 215 "Whether to use the monkey " 216 + "--pkg-whitelist-file option to work around cmdline length limits") 217 private boolean mUseAllowlistFile = 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 /** @deprecated b/139751666 */ 235 @Deprecated 236 @Option(name = "retry-on-failure", description = "Retry the test on failure") 237 private boolean mRetryOnFailure = false; 238 239 // FIXME: Remove this once traces.txt is no longer needed. 240 @Option( 241 name = "upload-file-pattern", 242 description = 243 "File glob of on-device files to upload " 244 + "if found. Takes two arguments: the glob, and the file type " 245 + "(text/xml/zip/gzip/png/unknown). May be repeated.") 246 private Map<String, LogDataType> mUploadFilePatterns = new LinkedHashMap<>(); 247 248 @Option(name = "screenshot", description = "Take a device screenshot on monkey completion") 249 private boolean mScreenshot = false; 250 251 @Option( 252 name = "ignore-security-exceptions", 253 description = "Ignore SecurityExceptions while injecting events") 254 private boolean mIgnoreSecurityExceptions = true; 255 256 @Option( 257 name = "collect-atrace", 258 description = "Enable a continuous circular buffer to collect atrace information") 259 private boolean mAtraceEnabled = false; 260 261 // options for generating ANR report via post processing script 262 @Option(name = "generate-anr-report", description = "Generate ANR report via post-processing") 263 private boolean mGenerateAnrReport = false; 264 265 @Option( 266 name = "anr-report-script", 267 description = "Path to the script for monkey ANR " + "report generation.") 268 private String mAnrReportScriptPath = null; 269 270 @Option( 271 name = "anr-report-storage-backend-base-path", 272 description = "Base path to the storage " + "backend used for saving the reports") 273 private String mAnrReportBasePath = null; 274 275 @Option( 276 name = "anr-report-storage-backend-url-prefix", 277 description = 278 "URL prefix for the " 279 + "storage backend that would enable web acess to the stored reports.") 280 private String mAnrReportUrlPrefix = null; 281 282 @Option( 283 name = "anr-report-storage-path", 284 description = 285 "Sub path under the base storage " 286 + "location for generated monkey ANR reports.") 287 private String mAnrReportPath = null; 288 289 private ITestDevice mTestDevice = null; 290 private MonkeyLogItem mMonkeyLog = null; 291 private BugreportItem mBugreport = null; 292 private AnrReportGenerator mAnrGen = null; 293 294 /** {@inheritDoc} */ 295 @Override run(TestInformation testInfo, ITestInvocationListener listener)296 public void run(TestInformation testInfo, ITestInvocationListener listener) 297 throws DeviceNotAvailableException { 298 Assert.assertNotNull(getDevice()); 299 300 TestDescription id = new TestDescription(getClass().getCanonicalName(), "monkey"); 301 long startTime = System.currentTimeMillis(); 302 303 listener.testRunStarted(getClass().getCanonicalName(), 1); 304 listener.testStarted(id); 305 306 try { 307 runMonkey(listener); 308 } finally { 309 listener.testEnded(id, new HashMap<String, Metric>()); 310 listener.testRunEnded( 311 System.currentTimeMillis() - startTime, new HashMap<String, Metric>()); 312 } 313 } 314 315 /** Returns the command that should be used to launch the app, */ getAppCmdWithExtras()316 private String getAppCmdWithExtras() { 317 String extras = ""; 318 for (Map.Entry<String, String> sEntry : mStringExtras.entrySet()) { 319 extras += String.format(" -e %s %s", sEntry.getKey(), sEntry.getValue()); 320 } 321 for (Map.Entry<String, Integer> sEntry : mIntegerExtras.entrySet()) { 322 extras += String.format(" --ei %s %d", sEntry.getKey(), sEntry.getValue()); 323 } 324 return LAUNCH_APP_CMD + extras; 325 } 326 327 /** Run the monkey one time and return a {@link MonkeyLogItem} for the run. */ runMonkey(ITestInvocationListener listener)328 protected void runMonkey(ITestInvocationListener listener) throws DeviceNotAvailableException { 329 ITestDevice device = getDevice(); 330 if (mRebootDevice) { 331 CLog.v("Rebooting device prior to running Monkey"); 332 device.reboot(); 333 } else { 334 CLog.v("Pre-run reboot disabled; skipping..."); 335 } 336 337 if (mIdleTimeSecs > 0) { 338 CLog.i("Sleeping for %d seconds to allow device to settle...", mIdleTimeSecs); 339 getRunUtil().sleep(mIdleTimeSecs * 1000); 340 CLog.i("Done sleeping."); 341 } 342 343 // launch the list of apps that needs warm-up 344 for (String componentName : mLaunchComponents) { 345 getDevice().executeShellCommand(String.format(getAppCmdWithExtras(), componentName)); 346 // give it some more time to settle down 347 getRunUtil().sleep(5000); 348 } 349 350 if (mUseAllowlistFile) { 351 // Use \r\n for new lines on the device. 352 String allowlist = ArrayUtil.join("\r\n", setSubtract(mPackages, mExcludePackages)); 353 device.pushString(allowlist.toString(), DEVICE_ALLOWLIST_PATH); 354 } 355 356 // Generate the monkey command to run, given the options 357 String command = buildMonkeyCommand(); 358 CLog.i("About to run monkey with at %d minute timeout: %s", mMonkeyTimeout, command); 359 360 StringBuilder outputBuilder = new StringBuilder(); 361 CommandHelper commandHelper = new CommandHelper(); 362 363 long start = System.currentTimeMillis(); 364 long duration = 0; 365 Date dateAfter = null; 366 String uptimeAfter = NULL_UPTIME; 367 FileInputStreamSource atraceStream = null; 368 369 // Generate the monkey log prefix, which includes the device uptime 370 outputBuilder.append( 371 String.format( 372 "# %s - device uptime = %s: Monkey command used " 373 + "for this test:\nadb shell %s\n\n", 374 new Date().toString(), getUptime(), command)); 375 376 // Start atrace before running the monkey command, but after reboot 377 if (mAtraceEnabled) { 378 CircularAtraceUtil.startTrace(getDevice(), null, 10); 379 } 380 381 if (mGenerateAnrReport) { 382 mAnrGen = 383 new AnrReportGenerator( 384 mAnrReportScriptPath, 385 mAnrReportBasePath, 386 mAnrReportUrlPrefix, 387 mAnrReportPath, 388 mTestDevice.getBuildId(), 389 mTestDevice.getBuildFlavor(), 390 mTestDevice.getSerialNumber()); 391 } 392 393 try { 394 onMonkeyStart(); 395 commandHelper.runCommand(mTestDevice, command, getMonkeyTimeoutMs()); 396 } finally { 397 // Wait for device to recover if it's not online. If it hasn't recovered, ignore. 398 try { 399 mTestDevice.waitForDeviceOnline(); 400 mTestDevice.enableAdbRoot(); 401 duration = System.currentTimeMillis() - start; 402 dateAfter = new Date(); 403 uptimeAfter = getUptime(); 404 onMonkeyFinish(); 405 takeScreenshot(listener, "screenshot"); 406 407 if (mAtraceEnabled) { 408 atraceStream = CircularAtraceUtil.endTrace(getDevice()); 409 } 410 411 mBugreport = takeBugreport(listener, BUGREPORT_NAME); 412 // FIXME: Remove this once traces.txt is no longer needed. 413 takeTraces(listener); 414 } finally { 415 // @@@ DO NOT add anything that requires device interaction into this block @@@ 416 // @@@ logging that no longer requires device interaction MUST be in this block @@@ 417 outputBuilder.append(commandHelper.getOutput()); 418 if (dateAfter == null) { 419 dateAfter = new Date(); 420 } 421 422 // Generate the monkey log suffix, which includes the device uptime. 423 outputBuilder.append( 424 String.format( 425 "\n# %s - device uptime = %s: Monkey command " 426 + "ran for: %d:%02d (mm:ss)\n", 427 dateAfter.toString(), 428 uptimeAfter, 429 duration / 1000 / 60, 430 duration / 1000 % 60)); 431 mMonkeyLog = createMonkeyLog(listener, MONKEY_LOG_NAME, outputBuilder.toString()); 432 433 boolean isAnr = mMonkeyLog.getCrash() instanceof AnrItem; 434 if (mAtraceEnabled && isAnr) { 435 // This was identified as an ANR; post atrace data 436 listener.testLog("circular-atrace", LogDataType.TEXT, atraceStream); 437 } 438 if (mAnrGen != null) { 439 if (isAnr) { 440 if (!mAnrGen.genereateAnrReport(listener)) { 441 CLog.w("Failed to post-process ANR."); 442 } else { 443 CLog.i("Successfully post-processed ANR."); 444 } 445 mAnrGen.cleanTempFiles(); 446 } else { 447 CLog.d("ANR post-processing enabled but no ANR detected."); 448 } 449 } 450 StreamUtil.cancel(atraceStream); 451 } 452 } 453 454 // Extra logs for what was found 455 if (mBugreport != null && mBugreport.getLastKmsg() != null) { 456 List<MiscKernelLogItem> kernelErrors = 457 mBugreport.getLastKmsg().getMiscEvents(KernelLogParser.KERNEL_ERROR); 458 List<MiscKernelLogItem> kernelResets = 459 mBugreport.getLastKmsg().getMiscEvents(KernelLogParser.KERNEL_ERROR); 460 CLog.d( 461 "Found %d kernel errors and %d kernel resets in last kmsg", 462 kernelErrors.size(), kernelResets.size()); 463 for (int i = 0; i < kernelErrors.size(); i++) { 464 String stack = kernelErrors.get(i).getStack(); 465 if (stack != null) { 466 CLog.d("Kernel Error #%d: %s", i + 1, stack.split("\n")[0].trim()); 467 } 468 } 469 for (int i = 0; i < kernelResets.size(); i++) { 470 String stack = kernelResets.get(i).getStack(); 471 if (stack != null) { 472 CLog.d("Kernel Reset #%d: %s", i + 1, stack.split("\n")[0].trim()); 473 } 474 } 475 } 476 477 checkResults(); 478 } 479 480 /** A hook to allow subclasses to perform actions just before the monkey starts. */ onMonkeyStart()481 protected void onMonkeyStart() { 482 // empty 483 } 484 485 /** A hook to allow sublaccess to perform actions just after the monkey finished. */ onMonkeyFinish()486 protected void onMonkeyFinish() { 487 // empty 488 } 489 490 /** 491 * If enabled, capture a screenshot and send it to a listener. 492 * 493 * @throws DeviceNotAvailableException 494 */ takeScreenshot(ITestInvocationListener listener, String screenshotName)495 protected void takeScreenshot(ITestInvocationListener listener, String screenshotName) 496 throws DeviceNotAvailableException { 497 if (mScreenshot) { 498 try (InputStreamSource screenshot = mTestDevice.getScreenshot("JPEG")) { 499 listener.testLog(screenshotName, LogDataType.JPEG, screenshot); 500 } 501 } 502 } 503 504 /** Capture a bugreport and send it to a listener. */ takeBugreport(ITestInvocationListener listener, String bugreportName)505 protected BugreportItem takeBugreport(ITestInvocationListener listener, String bugreportName) { 506 Bugreport bugreport = mTestDevice.takeBugreport(); 507 if (bugreport == null) { 508 CLog.e("Could not take bugreport"); 509 return null; 510 } 511 bugreport.log(bugreportName, listener); 512 File main = null; 513 InputStreamSource is = null; 514 try { 515 main = bugreport.getMainFile(); 516 if (main == null) { 517 CLog.e("Bugreport has no main file"); 518 return null; 519 } 520 if (mAnrGen != null) { 521 is = new FileInputStreamSource(main); 522 mAnrGen.setBugReportInfo(is); 523 } 524 return new BugreportParser().parse(new BufferedReader(new FileReader(main))); 525 } catch (IOException e) { 526 CLog.e("Could not process bugreport"); 527 CLog.e(e); 528 return null; 529 } finally { 530 StreamUtil.close(bugreport); 531 StreamUtil.cancel(is); 532 FileUtil.deleteFile(main); 533 } 534 } 535 takeTraces(ITestInvocationListener listener)536 protected void takeTraces(ITestInvocationListener listener) { 537 DeviceFileReporter dfr = new DeviceFileReporter(mTestDevice, listener); 538 dfr.addPatterns(mUploadFilePatterns); 539 try { 540 dfr.run(); 541 } catch (DeviceNotAvailableException e) { 542 // Log but don't throw 543 CLog.e( 544 "Device %s became unresponsive while pulling files", 545 mTestDevice.getSerialNumber()); 546 } 547 } 548 549 /** Create the monkey log, parse it, and send it to a listener. */ createMonkeyLog( ITestInvocationListener listener, String monkeyLogName, String log)550 protected MonkeyLogItem createMonkeyLog( 551 ITestInvocationListener listener, String monkeyLogName, String log) { 552 try (InputStreamSource source = new ByteArrayInputStreamSource(log.getBytes())) { 553 if (mAnrGen != null) { 554 mAnrGen.setMonkeyLogInfo(source); 555 } 556 listener.testLog(monkeyLogName, LogDataType.MONKEY_LOG, source); 557 return new MonkeyLogParser() 558 .parse(new BufferedReader(new InputStreamReader(source.createInputStream()))); 559 } catch (IOException e) { 560 CLog.e("Could not process monkey log."); 561 CLog.e(e); 562 return null; 563 } 564 } 565 566 /** 567 * A helper method to build a monkey command given the specified arguments. 568 * 569 * <p>Actual output argument order is: {@code monkey [-p PACKAGE]... [-c CATEGORY]... 570 * [--OPTION]... -s SEED -v -v -v COUNT} 571 * 572 * @return a {@link String} containing the command with the arguments assembled in the proper 573 * order. 574 */ buildMonkeyCommand()575 protected String buildMonkeyCommand() { 576 List<String> cmdList = new LinkedList<>(); 577 cmdList.add("monkey"); 578 579 if (!mUseAllowlistFile) { 580 for (String pkg : setSubtract(mPackages, mExcludePackages)) { 581 cmdList.add("-p"); 582 cmdList.add(pkg); 583 } 584 } 585 586 for (String cat : mCategories) { 587 cmdList.add("-c"); 588 cmdList.add(cat); 589 } 590 591 if (mIgnoreSecurityExceptions) { 592 cmdList.add("--ignore-security-exceptions"); 593 } 594 595 if (mThrottle >= 1) { 596 cmdList.add("--throttle"); 597 cmdList.add(Integer.toString(mThrottle)); 598 } 599 if (mIgnoreCrashes) { 600 cmdList.add("--ignore-crashes"); 601 } 602 if (mIgnoreTimeouts) { 603 cmdList.add("--ignore-timeouts"); 604 } 605 606 if (mUseAllowlistFile) { 607 cmdList.add("--pkg-whitelist-file"); 608 cmdList.add(DEVICE_ALLOWLIST_PATH); 609 } 610 611 for (String arg : mMonkeyArgs) { 612 String[] args = arg.split(":"); 613 cmdList.add(String.format("--%s", args[0])); 614 if (args.length > 1) { 615 cmdList.add(args[1]); 616 } 617 } 618 619 cmdList.addAll(mOptions); 620 621 cmdList.add("-s"); 622 if (mRandomSeed == null) { 623 // Pick a number that is random, but in a small enough range that some seeds are likely 624 // to be repeated 625 cmdList.add(Long.toString(new Random().nextInt(1000))); 626 } else { 627 cmdList.add(Long.toString(mRandomSeed)); 628 } 629 630 // verbose 631 cmdList.add("-v"); 632 cmdList.add("-v"); 633 cmdList.add("-v"); 634 cmdList.add(Integer.toString(mTargetCount)); 635 636 return ArrayUtil.join(" ", cmdList); 637 } 638 639 /** 640 * Get a {@link String} containing the number seconds since the device was booted. 641 * 642 * <p>{@code NULL_UPTIME} is returned if the device becomes unresponsive. Used in the monkey log 643 * prefix and suffix. 644 */ getUptime()645 protected String getUptime() { 646 try { 647 // make two attempts to get valid uptime 648 for (int i = 0; i < 2; i++) { 649 // uptime will typically have a format like "5278.73 1866.80". Use the first one 650 // (which is wall-time) 651 String uptime = mTestDevice.executeShellCommand("cat /proc/uptime").split(" ")[0]; 652 try { 653 Float.parseFloat(uptime); 654 // if this parsed, its a valid uptime 655 return uptime; 656 } catch (NumberFormatException e) { 657 CLog.w( 658 "failed to get valid uptime from %s. Received: '%s'", 659 mTestDevice.getSerialNumber(), uptime); 660 } 661 } 662 } catch (DeviceNotAvailableException e) { 663 CLog.e( 664 "Device %s became unresponsive while getting the uptime.", 665 mTestDevice.getSerialNumber()); 666 } 667 return NULL_UPTIME; 668 } 669 670 /** 671 * Perform set subtraction between two {@link Collection} objects. 672 * 673 * <p>The return value will consist of all of the elements of {@code keep}, excluding the 674 * elements that are also in {@code exclude}. Exposed for unit testing. 675 * 676 * @param keep the minuend in the subtraction 677 * @param exclude the subtrahend 678 * @return the collection of elements in {@code keep} that are not also in {@code exclude}. If 679 * {@code keep} is an ordered {@link Collection}, the remaining elements in the return value 680 * will remain in their original order. 681 */ setSubtract(Collection<String> keep, Collection<String> exclude)682 static Collection<String> setSubtract(Collection<String> keep, Collection<String> exclude) { 683 if (exclude.isEmpty()) { 684 return keep; 685 } 686 687 Collection<String> output = new ArrayList<>(keep); 688 output.removeAll(exclude); 689 return output; 690 } 691 692 /** Get {@link IRunUtil} to use. Exposed for unit testing. */ getRunUtil()693 IRunUtil getRunUtil() { 694 return RunUtil.getDefault(); 695 } 696 697 /** {@inheritDoc} */ 698 @Override setDevice(ITestDevice device)699 public void setDevice(ITestDevice device) { 700 mTestDevice = device; 701 } 702 703 /** {@inheritDoc} */ 704 @Override getDevice()705 public ITestDevice getDevice() { 706 return mTestDevice; 707 } 708 709 /** Check the results and return if valid or throw an assertion error if not valid. */ checkResults()710 private void checkResults() { 711 Assert.assertNotNull("Monkey log is null", mMonkeyLog); 712 Assert.assertNotNull("Bugreport is null", mBugreport); 713 Assert.assertNotNull("Bugreport is empty", mBugreport.getTime()); 714 715 // If there are no activities, retrying the test won't matter. 716 if (mMonkeyLog.getNoActivities()) { 717 return; 718 } 719 720 Assert.assertNotNull("Start uptime is missing", mMonkeyLog.getStartUptimeDuration()); 721 Assert.assertNotNull("Stop uptime is missing", mMonkeyLog.getStopUptimeDuration()); 722 Assert.assertNotNull("Total duration is missing", mMonkeyLog.getTotalDuration()); 723 724 long startUptime = mMonkeyLog.getStartUptimeDuration(); 725 long stopUptime = mMonkeyLog.getStopUptimeDuration(); 726 long totalDuration = mMonkeyLog.getTotalDuration(); 727 728 Assert.assertTrue( 729 "Uptime failure", stopUptime - startUptime > totalDuration - UPTIME_BUFFER); 730 731 // False count 732 Assert.assertFalse( 733 "False count", 734 mMonkeyLog.getIsFinished() 735 && mMonkeyLog.getTargetCount() - mMonkeyLog.getIntermediateCount() > 100); 736 737 // Monkey finished or crashed, so don't fail 738 if (mMonkeyLog.getIsFinished() || mMonkeyLog.getFinalCount() != null) { 739 return; 740 } 741 742 // Missing count 743 Assert.fail("Missing count"); 744 } 745 746 /** Get the monkey timeout in milliseconds */ getMonkeyTimeoutMs()747 protected long getMonkeyTimeoutMs() { 748 return mMonkeyTimeout * 60 * 1000; 749 } 750 } 751