android_commands.py revision 558790d6acca3451cf3a6b497803a5f07d0bec58
1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Provides an interface to communicate with the device via the adb command. 6 7Assumes adb binary is currently on system path. 8""" 9 10import collections 11import datetime 12import logging 13import os 14import re 15import shlex 16import subprocess 17import sys 18import tempfile 19import time 20 21import cmd_helper 22import constants 23import io_stats_parser 24try: 25 import pexpect 26except: 27 pexpect = None 28 29sys.path.append(os.path.join( 30 constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) 31import adb_interface 32import am_instrument_parser 33import errors 34 35 36# Pattern to search for the next whole line of pexpect output and capture it 37# into a match group. We can't use ^ and $ for line start end with pexpect, 38# see http://www.noah.org/python/pexpect/#doc for explanation why. 39PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') 40 41# Set the adb shell prompt to be a unique marker that will [hopefully] not 42# appear at the start of any line of a command's output. 43SHELL_PROMPT = '~+~PQ\x17RS~+~' 44 45# Java properties file 46LOCAL_PROPERTIES_PATH = '/data/local.prop' 47 48# Property in /data/local.prop that controls Java assertions. 49JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' 50 51MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$') 52NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*' 53 '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$') 54 55# Keycode "enum" suitable for passing to AndroidCommands.SendKey(). 56KEYCODE_HOME = 3 57KEYCODE_BACK = 4 58KEYCODE_DPAD_UP = 19 59KEYCODE_DPAD_DOWN = 20 60KEYCODE_DPAD_RIGHT = 22 61KEYCODE_ENTER = 66 62KEYCODE_MENU = 82 63 64MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/' 65MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin' 66MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER 67 68 69def GetAVDs(): 70 """Returns a list of AVDs.""" 71 re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE) 72 avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd'])) 73 return avds 74 75 76def GetAttachedDevices(hardware=True, emulator=True, offline=False): 77 """Returns a list of attached, android devices and emulators. 78 79 If a preferred device has been set with ANDROID_SERIAL, it will be first in 80 the returned list. The arguments specify what devices to include in the list. 81 82 Example output: 83 84 * daemon not running. starting it now on port 5037 * 85 * daemon started successfully * 86 List of devices attached 87 027c10494100b4d7 device 88 emulator-5554 offline 89 90 Args: 91 hardware: Include attached actual devices that are online. 92 emulator: Include emulators (i.e. AVD's) currently on host. 93 offline: Include devices and emulators that are offline. 94 95 Returns: List of devices. 96 """ 97 adb_devices_output = cmd_helper.GetCmdOutput([constants.ADB_PATH, 'devices']) 98 99 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) 100 online_devices = re_device.findall(adb_devices_output) 101 102 re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE) 103 emulator_devices = re_device.findall(adb_devices_output) 104 105 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\toffline$', re.MULTILINE) 106 offline_devices = re_device.findall(adb_devices_output) 107 108 devices = [] 109 # First determine list of online devices (e.g. hardware and/or emulator). 110 if hardware and emulator: 111 devices = online_devices 112 elif hardware: 113 devices = [device for device in online_devices 114 if device not in emulator_devices] 115 elif emulator: 116 devices = emulator_devices 117 118 # Now add offline devices if offline is true 119 if offline: 120 devices = devices + offline_devices 121 122 preferred_device = os.environ.get('ANDROID_SERIAL') 123 if preferred_device in devices: 124 devices.remove(preferred_device) 125 devices.insert(0, preferred_device) 126 return devices 127 128 129def IsDeviceAttached(device): 130 """Return true if the device is attached and online.""" 131 return device in GetAttachedDevices() 132 133 134def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): 135 """Gets a list of files from `ls` command output. 136 137 Python's os.walk isn't used because it doesn't work over adb shell. 138 139 Args: 140 path: The path to list. 141 ls_output: A list of lines returned by an `ls -lR` command. 142 re_file: A compiled regular expression which parses a line into named groups 143 consisting of at minimum "filename", "date", "time", "size" and 144 optionally "timezone". 145 utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a 146 2-digit string giving the number of UTC offset hours, and MM is a 147 2-digit string giving the number of UTC offset minutes. If the input 148 utc_offset is None, will try to look for the value of "timezone" if it 149 is specified in re_file. 150 151 Returns: 152 A dict of {"name": (size, lastmod), ...} where: 153 name: The file name relative to |path|'s directory. 154 size: The file size in bytes (0 for directories). 155 lastmod: The file last modification date in UTC. 156 """ 157 re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path)) 158 path_dir = os.path.dirname(path) 159 160 current_dir = '' 161 files = {} 162 for line in ls_output: 163 directory_match = re_directory.match(line) 164 if directory_match: 165 current_dir = directory_match.group('dir') 166 continue 167 file_match = re_file.match(line) 168 if file_match: 169 filename = os.path.join(current_dir, file_match.group('filename')) 170 if filename.startswith(path_dir): 171 filename = filename[len(path_dir) + 1:] 172 lastmod = datetime.datetime.strptime( 173 file_match.group('date') + ' ' + file_match.group('time')[:5], 174 '%Y-%m-%d %H:%M') 175 if not utc_offset and 'timezone' in re_file.groupindex: 176 utc_offset = file_match.group('timezone') 177 if isinstance(utc_offset, str) and len(utc_offset) == 5: 178 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), 179 minutes=int(utc_offset[3:5])) 180 if utc_offset[0:1] == '-': 181 utc_delta = -utc_delta 182 lastmod -= utc_delta 183 files[filename] = (int(file_match.group('size')), lastmod) 184 return files 185 186 187def _ComputeFileListHash(md5sum_output): 188 """Returns a list of tuples from the provided md5sum output. 189 190 Args: 191 md5sum_output: output directly from md5sum binary. 192 193 Returns: 194 List of namedtuples (hash, path). 195 """ 196 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path']) 197 split_lines = [line.split(' ') for line in md5sum_output] 198 return [HashAndPath._make(s) for s in split_lines if len(s) == 2] 199 200 201def _HasAdbPushSucceeded(command_output): 202 """Returns whether adb push has succeeded from the provided output.""" 203 # TODO(frankf): We should look at the return code instead of the command 204 # output for many of the commands in this file. 205 if not command_output: 206 return True 207 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" 208 # Errors look like this: "failed to copy ... " 209 if not re.search('^[0-9]', command_output.splitlines()[-1]): 210 logging.critical('PUSH FAILED: ' + command_output) 211 return False 212 return True 213 214 215def GetLogTimestamp(log_line, year): 216 """Returns the timestamp of the given |log_line| in the given year.""" 217 try: 218 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), 219 '%Y-%m-%d %H:%M:%S.%f') 220 except (ValueError, IndexError): 221 logging.critical('Error reading timestamp from ' + log_line) 222 return None 223 224 225class AndroidCommands(object): 226 """Helper class for communicating with Android device via adb. 227 228 Args: 229 device: If given, adb commands are only send to the device of this ID. 230 Otherwise commands are sent to all attached devices. 231 """ 232 233 def __init__(self, device=None): 234 adb_dir = os.path.dirname(constants.ADB_PATH) 235 if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep): 236 # Required by third_party/android_testrunner to call directly 'adb'. 237 os.environ['PATH'] += os.pathsep + adb_dir 238 self._adb = adb_interface.AdbInterface() 239 if device: 240 self._adb.SetTargetSerial(device) 241 self._device = device 242 self._logcat = None 243 self.logcat_process = None 244 self._logcat_tmpoutfile = None 245 self._pushed_files = [] 246 self._device_utc_offset = None 247 self._potential_push_size = 0 248 self._actual_push_size = 0 249 self._md5sum_build_dir = '' 250 self._external_storage = '' 251 self._util_wrapper = '' 252 253 def _LogShell(self, cmd): 254 """Logs the adb shell command.""" 255 if self._device: 256 device_repr = self._device[-4:] 257 else: 258 device_repr = '????' 259 logging.info('[%s]> %s', device_repr, cmd) 260 261 def Adb(self): 262 """Returns our AdbInterface to avoid us wrapping all its methods.""" 263 return self._adb 264 265 def IsOnline(self): 266 """Checks whether the device is online. 267 268 Returns: 269 True if device is in 'device' mode, False otherwise. 270 """ 271 out = self._adb.SendCommand('get-state') 272 return out.strip() == 'device' 273 274 def IsRootEnabled(self): 275 """Checks if root is enabled on the device.""" 276 root_test_output = self.RunShellCommand('ls /root') or [''] 277 return not 'Permission denied' in root_test_output[0] 278 279 def EnableAdbRoot(self): 280 """Enables adb root on the device. 281 282 Returns: 283 True: if output from executing adb root was as expected. 284 False: otherwise. 285 """ 286 if self.GetBuildType() == 'user': 287 logging.warning("Can't enable root in production builds with type user") 288 return False 289 else: 290 return_value = self._adb.EnableAdbRoot() 291 # EnableAdbRoot inserts a call for wait-for-device only when adb logcat 292 # output matches what is expected. Just to be safe add a call to 293 # wait-for-device. 294 self._adb.SendCommand('wait-for-device') 295 return return_value 296 297 def GetDeviceYear(self): 298 """Returns the year information of the date on device.""" 299 return self.RunShellCommand('date +%Y')[0] 300 301 def GetExternalStorage(self): 302 if not self._external_storage: 303 self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0] 304 assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE' 305 return self._external_storage 306 307 def WaitForDevicePm(self): 308 """Blocks until the device's package manager is available. 309 310 To workaround http://b/5201039, we restart the shell and retry if the 311 package manager isn't back after 120 seconds. 312 313 Raises: 314 errors.WaitForResponseTimedOutError after max retries reached. 315 """ 316 last_err = None 317 retries = 3 318 while retries: 319 try: 320 self._adb.WaitForDevicePm() 321 return # Success 322 except errors.WaitForResponseTimedOutError as e: 323 last_err = e 324 logging.warning('Restarting and retrying after timeout: %s', e) 325 retries -= 1 326 self.RestartShell() 327 raise last_err # Only reached after max retries, re-raise the last error. 328 329 def RestartShell(self): 330 """Restarts the shell on the device. Does not block for it to return.""" 331 self.RunShellCommand('stop') 332 self.RunShellCommand('start') 333 334 def Reboot(self, full_reboot=True): 335 """Reboots the device and waits for the package manager to return. 336 337 Args: 338 full_reboot: Whether to fully reboot the device or just restart the shell. 339 """ 340 # TODO(torne): hive can't reboot the device either way without breaking the 341 # connection; work out if we can handle this better 342 if os.environ.get('USING_HIVE'): 343 logging.warning('Ignoring reboot request as we are on hive') 344 return 345 if full_reboot or not self.IsRootEnabled(): 346 self._adb.SendCommand('reboot') 347 timeout = 300 348 else: 349 self.RestartShell() 350 timeout = 120 351 # To run tests we need at least the package manager and the sd card (or 352 # other external storage) to be ready. 353 self.WaitForDevicePm() 354 self.WaitForSdCardReady(timeout) 355 356 def Shutdown(self): 357 """Shuts down the device.""" 358 self._adb.SendCommand('reboot -p') 359 360 def Uninstall(self, package): 361 """Uninstalls the specified package from the device. 362 363 Args: 364 package: Name of the package to remove. 365 366 Returns: 367 A status string returned by adb uninstall 368 """ 369 uninstall_command = 'uninstall %s' % package 370 371 self._LogShell(uninstall_command) 372 return self._adb.SendCommand(uninstall_command, timeout_time=60) 373 374 def Install(self, package_file_path, reinstall=False): 375 """Installs the specified package to the device. 376 377 Args: 378 package_file_path: Path to .apk file to install. 379 reinstall: Reinstall an existing apk, keeping the data. 380 381 Returns: 382 A status string returned by adb install 383 """ 384 assert os.path.isfile(package_file_path), ('<%s> is not file' % 385 package_file_path) 386 387 install_cmd = ['install'] 388 389 if reinstall: 390 install_cmd.append('-r') 391 392 install_cmd.append(package_file_path) 393 install_cmd = ' '.join(install_cmd) 394 395 self._LogShell(install_cmd) 396 return self._adb.SendCommand(install_cmd, 397 timeout_time=2 * 60, 398 retry_count=0) 399 400 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, 401 reboots_on_failure=2): 402 """Installs specified package and reboots device on timeouts. 403 404 If package_name is supplied, checks if the package is already installed and 405 doesn't reinstall if the apk md5sums match. 406 407 Args: 408 apk_path: Path to .apk file to install. 409 keep_data: Reinstalls instead of uninstalling first, preserving the 410 application data. 411 package_name: Package name (only needed if keep_data=False). 412 reboots_on_failure: number of time to reboot if package manager is frozen. 413 """ 414 # Check if package is already installed and up to date. 415 if package_name: 416 installed_apk_path = self.GetApplicationPath(package_name) 417 if installed_apk_path and self.CheckMd5Sum(apk_path, installed_apk_path): 418 logging.info('Skipped install: identical %s APK already installed' % 419 package_name) 420 return 421 # Install. 422 reboots_left = reboots_on_failure 423 while True: 424 try: 425 if not keep_data: 426 assert package_name 427 self.Uninstall(package_name) 428 install_status = self.Install(apk_path, reinstall=keep_data) 429 if 'Success' in install_status: 430 return 431 except errors.WaitForResponseTimedOutError: 432 print '@@@STEP_WARNINGS@@@' 433 logging.info('Timeout on installing %s on device %s', apk_path, 434 self._device) 435 436 if reboots_left <= 0: 437 raise Exception('Install failure') 438 439 # Force a hard reboot on last attempt 440 self.Reboot(full_reboot=(reboots_left == 1)) 441 reboots_left -= 1 442 443 def MakeSystemFolderWritable(self): 444 """Remounts the /system folder rw.""" 445 out = self._adb.SendCommand('remount') 446 if out.strip() != 'remount succeeded': 447 raise errors.MsgException('Remount failed: %s' % out) 448 449 def RestartAdbServer(self): 450 """Restart the adb server.""" 451 self.KillAdbServer() 452 self.StartAdbServer() 453 454 def KillAdbServer(self): 455 """Kill adb server.""" 456 adb_cmd = [constants.ADB_PATH, 'kill-server'] 457 return cmd_helper.RunCmd(adb_cmd) 458 459 def StartAdbServer(self): 460 """Start adb server.""" 461 adb_cmd = [constants.ADB_PATH, 'start-server'] 462 return cmd_helper.RunCmd(adb_cmd) 463 464 def WaitForSystemBootCompleted(self, wait_time): 465 """Waits for targeted system's boot_completed flag to be set. 466 467 Args: 468 wait_time: time in seconds to wait 469 470 Raises: 471 WaitForResponseTimedOutError if wait_time elapses and flag still not 472 set. 473 """ 474 logging.info('Waiting for system boot completed...') 475 self._adb.SendCommand('wait-for-device') 476 # Now the device is there, but system not boot completed. 477 # Query the sys.boot_completed flag with a basic command 478 boot_completed = False 479 attempts = 0 480 wait_period = 5 481 while not boot_completed and (attempts * wait_period) < wait_time: 482 output = self._adb.SendShellCommand('getprop sys.boot_completed', 483 retry_count=1) 484 output = output.strip() 485 if output == '1': 486 boot_completed = True 487 else: 488 # If 'error: xxx' returned when querying the flag, it means 489 # adb server lost the connection to the emulator, so restart the adb 490 # server. 491 if 'error:' in output: 492 self.RestartAdbServer() 493 time.sleep(wait_period) 494 attempts += 1 495 if not boot_completed: 496 raise errors.WaitForResponseTimedOutError( 497 'sys.boot_completed flag was not set after %s seconds' % wait_time) 498 499 def WaitForSdCardReady(self, timeout_time): 500 """Wait for the SD card ready before pushing data into it.""" 501 logging.info('Waiting for SD card ready...') 502 sdcard_ready = False 503 attempts = 0 504 wait_period = 5 505 external_storage = self.GetExternalStorage() 506 while not sdcard_ready and attempts * wait_period < timeout_time: 507 output = self.RunShellCommand('ls ' + external_storage) 508 if output: 509 sdcard_ready = True 510 else: 511 time.sleep(wait_period) 512 attempts += 1 513 if not sdcard_ready: 514 raise errors.WaitForResponseTimedOutError( 515 'SD card not ready after %s seconds' % timeout_time) 516 517 # It is tempting to turn this function into a generator, however this is not 518 # possible without using a private (local) adb_shell instance (to ensure no 519 # other command interleaves usage of it), which would defeat the main aim of 520 # being able to reuse the adb shell instance across commands. 521 def RunShellCommand(self, command, timeout_time=20, log_result=False): 522 """Send a command to the adb shell and return the result. 523 524 Args: 525 command: String containing the shell command to send. Must not include 526 the single quotes as we use them to escape the whole command. 527 timeout_time: Number of seconds to wait for command to respond before 528 retrying, used by AdbInterface.SendShellCommand. 529 log_result: Boolean to indicate whether we should log the result of the 530 shell command. 531 532 Returns: 533 list containing the lines of output received from running the command 534 """ 535 self._LogShell(command) 536 if "'" in command: logging.warning(command + " contains ' quotes") 537 result = self._adb.SendShellCommand( 538 "'%s'" % command, timeout_time).splitlines() 539 if ['error: device not found'] == result: 540 raise errors.DeviceUnresponsiveError('device not found') 541 if log_result: 542 self._LogShell('\n'.join(result)) 543 return result 544 545 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, 546 log_result=False): 547 """See RunShellCommand() above. 548 549 Returns: 550 The tuple (exit code, list of output lines). 551 """ 552 lines = self.RunShellCommand( 553 command + '; echo %$?', timeout_time, log_result) 554 last_line = lines[-1] 555 status_pos = last_line.rfind('%') 556 assert status_pos >= 0 557 status = int(last_line[status_pos + 1:]) 558 if status_pos == 0: 559 lines = lines[:-1] 560 else: 561 lines = lines[:-1] + [last_line[:status_pos]] 562 return (status, lines) 563 564 def KillAll(self, process): 565 """Android version of killall, connected via adb. 566 567 Args: 568 process: name of the process to kill off 569 570 Returns: 571 the number of processes killed 572 """ 573 pids = self.ExtractPid(process) 574 if pids: 575 self.RunShellCommand('kill -9 ' + ' '.join(pids)) 576 return len(pids) 577 578 def KillAllBlocking(self, process, timeout_sec): 579 """Blocking version of killall, connected via adb. 580 581 This waits until no process matching the corresponding name appears in ps' 582 output anymore. 583 584 Args: 585 process: name of the process to kill off 586 timeout_sec: the timeout in seconds 587 588 Returns: 589 the number of processes killed 590 """ 591 processes_killed = self.KillAll(process) 592 if processes_killed: 593 elapsed = 0 594 wait_period = 0.1 595 # Note that this doesn't take into account the time spent in ExtractPid(). 596 while self.ExtractPid(process) and elapsed < timeout_sec: 597 time.sleep(wait_period) 598 elapsed += wait_period 599 if elapsed >= timeout_sec: 600 return 0 601 return processes_killed 602 603 def _GetActivityCommand(self, package, activity, wait_for_completion, action, 604 category, data, extras, trace_file_name, force_stop): 605 """Creates command to start |package|'s activity on the device. 606 607 Args - as for StartActivity 608 609 Returns: 610 the command to run on the target to start the activity 611 """ 612 cmd = 'am start -a %s' % action 613 if force_stop: 614 cmd += ' -S' 615 if wait_for_completion: 616 cmd += ' -W' 617 if category: 618 cmd += ' -c %s' % category 619 if package and activity: 620 cmd += ' -n %s/%s' % (package, activity) 621 if data: 622 cmd += ' -d "%s"' % data 623 if extras: 624 for key in extras: 625 value = extras[key] 626 if isinstance(value, str): 627 cmd += ' --es' 628 elif isinstance(value, bool): 629 cmd += ' --ez' 630 elif isinstance(value, int): 631 cmd += ' --ei' 632 else: 633 raise NotImplementedError( 634 'Need to teach StartActivity how to pass %s extras' % type(value)) 635 cmd += ' %s %s' % (key, value) 636 if trace_file_name: 637 cmd += ' --start-profiler ' + trace_file_name 638 return cmd 639 640 def StartActivity(self, package, activity, wait_for_completion=False, 641 action='android.intent.action.VIEW', 642 category=None, data=None, 643 extras=None, trace_file_name=None, 644 force_stop=False): 645 """Starts |package|'s activity on the device. 646 647 Args: 648 package: Name of package to start (e.g. 'com.google.android.apps.chrome'). 649 activity: Name of activity (e.g. '.Main' or 650 'com.google.android.apps.chrome.Main'). 651 wait_for_completion: wait for the activity to finish launching (-W flag). 652 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. 653 category: string (e.g. "android.intent.category.HOME") 654 data: Data string to pass to activity (e.g. 'http://www.example.com/'). 655 extras: Dict of extras to pass to activity. Values are significant. 656 trace_file_name: If used, turns on and saves the trace to this file name. 657 force_stop: force stop the target app before starting the activity (-S 658 flag). 659 """ 660 cmd = self._GetActivityCommand(package, activity, wait_for_completion, 661 action, category, data, extras, 662 trace_file_name, force_stop) 663 self.RunShellCommand(cmd) 664 665 def StartActivityTimed(self, package, activity, wait_for_completion=False, 666 action='android.intent.action.VIEW', 667 category=None, data=None, 668 extras=None, trace_file_name=None, 669 force_stop=False): 670 """Starts |package|'s activity on the device, returning the start time 671 672 Args - as for StartActivity 673 674 Returns: 675 a timestamp string for the time at which the activity started 676 """ 677 cmd = self._GetActivityCommand(package, activity, wait_for_completion, 678 action, category, data, extras, 679 trace_file_name, force_stop) 680 self.StartMonitoringLogcat() 681 self.RunShellCommand('log starting activity; ' + cmd) 682 activity_started_re = re.compile('.*starting activity.*') 683 m = self.WaitForLogMatch(activity_started_re, None) 684 assert m 685 start_line = m.group(0) 686 return GetLogTimestamp(start_line, self.GetDeviceYear()) 687 688 def GoHome(self): 689 """Tell the device to return to the home screen. Blocks until completion.""" 690 self.RunShellCommand('am start -W ' 691 '-a android.intent.action.MAIN -c android.intent.category.HOME') 692 693 def CloseApplication(self, package): 694 """Attempt to close down the application, using increasing violence. 695 696 Args: 697 package: Name of the process to kill off, e.g. 698 com.google.android.apps.chrome 699 """ 700 self.RunShellCommand('am force-stop ' + package) 701 702 def GetApplicationPath(self, package): 703 """Get the installed apk path on the device for the given package. 704 705 Args: 706 package: Name of the package. 707 708 Returns: 709 Path to the apk on the device if it exists, None otherwise. 710 """ 711 pm_path_output = self.RunShellCommand('pm path ' + package) 712 # The path output contains anything if and only if the package 713 # exists. 714 if pm_path_output: 715 # pm_path_output is of the form: "package:/path/to/foo.apk" 716 return pm_path_output[0].split(':')[1] 717 else: 718 return None 719 720 def ClearApplicationState(self, package): 721 """Closes and clears all state for the given |package|.""" 722 # Check that the package exists before clearing it. Necessary because 723 # calling pm clear on a package that doesn't exist may never return. 724 pm_path_output = self.RunShellCommand('pm path ' + package) 725 # The path output only contains anything if and only if the package exists. 726 if pm_path_output: 727 self.RunShellCommand('pm clear ' + package) 728 729 def SendKeyEvent(self, keycode): 730 """Sends keycode to the device. 731 732 Args: 733 keycode: Numeric keycode to send (see "enum" at top of file). 734 """ 735 self.RunShellCommand('input keyevent %d' % keycode) 736 737 def CheckMd5Sum(self, local_path, device_path): 738 """Compares the md5sum of a local path against a device path. 739 740 Args: 741 local_path: Path (file or directory) on the host. 742 device_path: Path on the device. 743 744 Returns: 745 True if the md5sums match. 746 """ 747 if not self._md5sum_build_dir: 748 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') 749 build_dir = '%s/%s/' % ( 750 cmd_helper.OutDirectory().get(), default_build_type) 751 md5sum_dist_path = '%s/md5sum_dist' % build_dir 752 if not os.path.exists(md5sum_dist_path): 753 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() 754 md5sum_dist_path = '%s/md5sum_dist' % build_dir 755 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' 756 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) 757 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) 758 self._md5sum_build_dir = build_dir 759 760 cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + 761 MD5SUM_DEVICE_PATH + ' ' + device_path) 762 device_hash_tuples = _ComputeFileListHash( 763 self.RunShellCommand(cmd, timeout_time=2 * 60)) 764 assert os.path.exists(local_path), 'Local path not found %s' % local_path 765 md5sum_output = cmd_helper.GetCmdOutput( 766 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) 767 host_hash_tuples = _ComputeFileListHash(md5sum_output.splitlines()) 768 769 # Ignore extra files on the device. 770 if len(device_hash_tuples) > len(host_hash_tuples): 771 host_files = [os.path.relpath(os.path.normpath(p.path), 772 os.path.normpath(local_path)) for p in host_hash_tuples] 773 774 def _host_has(fname): 775 return any(path in fname for path in host_files) 776 777 hashes_on_device = [h.hash for h in device_hash_tuples if 778 _host_has(h.path)] 779 else: 780 hashes_on_device = [h.hash for h in device_hash_tuples] 781 782 # Compare md5sums between host and device files. 783 hashes_on_host = [h.hash for h in host_hash_tuples] 784 hashes_on_device.sort() 785 hashes_on_host.sort() 786 return hashes_on_device == hashes_on_host 787 788 def PushIfNeeded(self, local_path, device_path): 789 """Pushes |local_path| to |device_path|. 790 791 Works for files and directories. This method skips copying any paths in 792 |test_data_paths| that already exist on the device with the same hash. 793 794 All pushed files can be removed by calling RemovePushedFiles(). 795 """ 796 assert os.path.exists(local_path), 'Local path not found %s' % local_path 797 size = int(cmd_helper.GetCmdOutput(['du', '-sb', local_path]).split()[0]) 798 self._pushed_files.append(device_path) 799 self._potential_push_size += size 800 801 if self.CheckMd5Sum(local_path, device_path): 802 return 803 804 self._actual_push_size += size 805 # They don't match, so remove everything first and then create it. 806 if os.path.isdir(local_path): 807 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) 808 self.RunShellCommand('mkdir -p %s' % device_path) 809 810 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of 811 # 60 seconds which isn't sufficient for a lot of users of this method. 812 push_command = 'push %s %s' % (local_path, device_path) 813 self._LogShell(push_command) 814 815 # Retry push with increasing backoff if the device is busy. 816 retry = 0 817 while True: 818 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) 819 if _HasAdbPushSucceeded(output): 820 return 821 if 'resource busy' in output and retry < 3: 822 retry += 1 823 wait_time = 5 * retry 824 logging.error('Push failed, retrying in %d seconds: %s' % 825 (wait_time, output)) 826 time.sleep(wait_time) 827 else: 828 raise Exception('Push failed: %s' % output) 829 830 def GetPushSizeInfo(self): 831 """Get total size of pushes to the device done via PushIfNeeded() 832 833 Returns: 834 A tuple: 835 1. Total size of push requests to PushIfNeeded (MB) 836 2. Total size that was actually pushed (MB) 837 """ 838 return (self._potential_push_size, self._actual_push_size) 839 840 def GetFileContents(self, filename, log_result=False): 841 """Gets contents from the file specified by |filename|.""" 842 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, 843 log_result=log_result) 844 845 def SetFileContents(self, filename, contents): 846 """Writes |contents| to the file specified by |filename|.""" 847 with tempfile.NamedTemporaryFile() as f: 848 f.write(contents) 849 f.flush() 850 self._adb.Push(f.name, filename) 851 852 _TEMP_FILE_BASE_FMT = 'temp_file_%d' 853 _TEMP_SCRIPT_FILE_BASE_FMT = 'temp_script_file_%d.sh' 854 855 def _GetDeviceTempFileName(self, base_name): 856 i = 0 857 while self.FileExistsOnDevice( 858 self.GetExternalStorage() + '/' + base_name % i): 859 i += 1 860 return self.GetExternalStorage() + '/' + base_name % i 861 862 def CanAccessProtectedFileContents(self): 863 """Returns True if Get/SetProtectedFileContents would work via "su". 864 865 Devices running user builds don't have adb root, but may provide "su" which 866 can be used for accessing protected files. 867 """ 868 r = self.RunShellCommand('su -c cat /dev/null') 869 return r == [] or r[0].strip() == '' 870 871 def GetProtectedFileContents(self, filename, log_result=False): 872 """Gets contents from the protected file specified by |filename|. 873 874 This is less efficient than GetFileContents, but will work for protected 875 files and device files. 876 """ 877 # Run the script as root 878 return self.RunShellCommand('su -c cat "%s" 2> /dev/null' % filename) 879 880 def SetProtectedFileContents(self, filename, contents): 881 """Writes |contents| to the protected file specified by |filename|. 882 883 This is less efficient than SetFileContents, but will work for protected 884 files and device files. 885 """ 886 temp_file = self._GetDeviceTempFileName(AndroidCommands._TEMP_FILE_BASE_FMT) 887 temp_script = self._GetDeviceTempFileName( 888 AndroidCommands._TEMP_SCRIPT_FILE_BASE_FMT) 889 890 # Put the contents in a temporary file 891 self.SetFileContents(temp_file, contents) 892 # Create a script to copy the file contents to its final destination 893 self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename)) 894 # Run the script as root 895 self.RunShellCommand('su -c sh %s' % temp_script) 896 # And remove the temporary files 897 self.RunShellCommand('rm ' + temp_file) 898 self.RunShellCommand('rm ' + temp_script) 899 900 def RemovePushedFiles(self): 901 """Removes all files pushed with PushIfNeeded() from the device.""" 902 for p in self._pushed_files: 903 self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60) 904 905 def ListPathContents(self, path): 906 """Lists files in all subdirectories of |path|. 907 908 Args: 909 path: The path to list. 910 911 Returns: 912 A dict of {"name": (size, lastmod), ...}. 913 """ 914 # Example output: 915 # /foo/bar: 916 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt 917 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' 918 '(?P<user>[^\s]+)\s+' 919 '(?P<group>[^\s]+)\s+' 920 '(?P<size>[^\s]+)\s+' 921 '(?P<date>[^\s]+)\s+' 922 '(?P<time>[^\s]+)\s+' 923 '(?P<filename>[^\s]+)$') 924 return _GetFilesFromRecursiveLsOutput( 925 path, self.RunShellCommand('ls -lR %s' % path), re_file, 926 self.GetUtcOffset()) 927 928 def GetUtcOffset(self): 929 if not self._device_utc_offset: 930 self._device_utc_offset = self.RunShellCommand('date +%z')[0] 931 return self._device_utc_offset 932 933 def SetJavaAssertsEnabled(self, enable): 934 """Sets or removes the device java assertions property. 935 936 Args: 937 enable: If True the property will be set. 938 939 Returns: 940 True if the file was modified (reboot is required for it to take effect). 941 """ 942 # First ensure the desired property is persisted. 943 temp_props_file = tempfile.NamedTemporaryFile() 944 properties = '' 945 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name): 946 properties = file(temp_props_file.name).read() 947 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + 948 r'\s*=\s*all\s*$', re.MULTILINE) 949 if enable != bool(re.search(re_search, properties)): 950 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + 951 r'\s*=\s*\w+\s*$', re.MULTILINE) 952 properties = re.sub(re_replace, '', properties) 953 if enable: 954 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY 955 956 file(temp_props_file.name, 'w').write(properties) 957 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH) 958 959 # Next, check the current runtime value is what we need, and 960 # if not, set it and report that a reboot is required. 961 was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY) 962 if was_set == enable: 963 return False 964 965 self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY, 966 enable and 'all' or '')) 967 return True 968 969 def GetBuildId(self): 970 """Returns the build ID of the system (e.g. JRM79C).""" 971 build_id = self.RunShellCommand('getprop ro.build.id')[0] 972 assert build_id 973 return build_id 974 975 def GetBuildType(self): 976 """Returns the build type of the system (e.g. eng).""" 977 build_type = self.RunShellCommand('getprop ro.build.type')[0] 978 assert build_type 979 return build_type 980 981 def GetBuildProduct(self): 982 """Returns the build product of the device (e.g. maguro).""" 983 build_product = self.RunShellCommand('getprop ro.build.product')[0] 984 assert build_product 985 return build_product 986 987 def GetProductName(self): 988 """Returns the product name of the device (e.g. takju).""" 989 name = self.RunShellCommand('getprop ro.product.name')[0] 990 assert name 991 return name 992 993 def GetBuildFingerprint(self): 994 """Returns the build fingerprint of the device.""" 995 build_fingerprint = self.RunShellCommand('getprop ro.build.fingerprint')[0] 996 assert build_fingerprint 997 return build_fingerprint 998 999 def GetDescription(self): 1000 """Returns the description of the system. 1001 1002 For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys". 1003 """ 1004 description = self.RunShellCommand('getprop ro.build.description')[0] 1005 assert description 1006 return description 1007 1008 def GetProductModel(self): 1009 """Returns the name of the product model (e.g. "Galaxy Nexus") """ 1010 model = self.RunShellCommand('getprop ro.product.model')[0] 1011 assert model 1012 return model 1013 1014 def GetWifiIP(self): 1015 """Returns the wifi IP on the device.""" 1016 wifi_ip = self.RunShellCommand('getprop dhcp.wlan0.ipaddress')[0] 1017 # Do not assert here. Devices (e.g. emulators) may not have a WifiIP. 1018 return wifi_ip 1019 1020 def GetSubscriberInfo(self): 1021 """Returns the device subscriber info (e.g. GSM and device ID) as string.""" 1022 iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo') 1023 assert iphone_sub 1024 return '\n'.join(iphone_sub) 1025 1026 def GetBatteryInfo(self): 1027 """Returns the device battery info (e.g. status, level, etc) as string.""" 1028 battery = self.RunShellCommand('dumpsys battery') 1029 assert battery 1030 return '\n'.join(battery) 1031 1032 def GetSetupWizardStatus(self): 1033 """Returns the status of the device setup wizard (e.g. DISABLED).""" 1034 status = self.RunShellCommand('getprop ro.setupwizard.mode')[0] 1035 assert status 1036 return status 1037 1038 def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None): 1039 """Starts monitoring the output of logcat, for use with WaitForLogMatch. 1040 1041 Args: 1042 clear: If True the existing logcat output will be cleared, to avoiding 1043 matching historical output lurking in the log. 1044 filters: A list of logcat filters to be used. 1045 """ 1046 if clear: 1047 self.RunShellCommand('logcat -c') 1048 args = [] 1049 if self._adb._target_arg: 1050 args += shlex.split(self._adb._target_arg) 1051 args += ['logcat', '-v', 'threadtime'] 1052 if filters: 1053 args.extend(filters) 1054 else: 1055 args.append('*:v') 1056 1057 if logfile: 1058 logfile = NewLineNormalizer(logfile) 1059 1060 # Spawn logcat and syncronize with it. 1061 for _ in range(4): 1062 self._logcat = pexpect.spawn(constants.ADB_PATH, args, timeout=10, 1063 logfile=logfile) 1064 self.RunShellCommand('log startup_sync') 1065 if self._logcat.expect(['startup_sync', pexpect.EOF, 1066 pexpect.TIMEOUT]) == 0: 1067 break 1068 self._logcat.close(force=True) 1069 else: 1070 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) 1071 sys.exit(1) 1072 1073 def GetMonitoredLogCat(self): 1074 """Returns an "adb logcat" command as created by pexpected.spawn.""" 1075 if not self._logcat: 1076 self.StartMonitoringLogcat(clear=False) 1077 return self._logcat 1078 1079 def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10): 1080 """Blocks until a matching line is logged or a timeout occurs. 1081 1082 Args: 1083 success_re: A compiled re to search each line for. 1084 error_re: A compiled re which, if found, terminates the search for 1085 |success_re|. If None is given, no error condition will be detected. 1086 clear: If True the existing logcat output will be cleared, defaults to 1087 false. 1088 timeout: Timeout in seconds to wait for a log match. 1089 1090 Raises: 1091 pexpect.TIMEOUT after |timeout| seconds without a match for |success_re| 1092 or |error_re|. 1093 1094 Returns: 1095 The re match object if |success_re| is matched first or None if |error_re| 1096 is matched first. 1097 """ 1098 logging.info('<<< Waiting for logcat:' + str(success_re.pattern)) 1099 t0 = time.time() 1100 while True: 1101 if not self._logcat: 1102 self.StartMonitoringLogcat(clear) 1103 try: 1104 while True: 1105 # Note this will block for upto the timeout _per log line_, so we need 1106 # to calculate the overall timeout remaining since t0. 1107 time_remaining = t0 + timeout - time.time() 1108 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) 1109 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) 1110 line = self._logcat.match.group(1) 1111 if error_re: 1112 error_match = error_re.search(line) 1113 if error_match: 1114 return None 1115 success_match = success_re.search(line) 1116 if success_match: 1117 return success_match 1118 logging.info('<<< Skipped Logcat Line:' + str(line)) 1119 except pexpect.TIMEOUT: 1120 raise pexpect.TIMEOUT( 1121 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' 1122 'to debug)' % 1123 (timeout, success_re.pattern)) 1124 except pexpect.EOF: 1125 # It seems that sometimes logcat can end unexpectedly. This seems 1126 # to happen during Chrome startup after a reboot followed by a cache 1127 # clean. I don't understand why this happens, but this code deals with 1128 # getting EOF in logcat. 1129 logging.critical('Found EOF in adb logcat. Restarting...') 1130 # Rerun spawn with original arguments. Note that self._logcat.args[0] is 1131 # the path of adb, so we don't want it in the arguments. 1132 self._logcat = pexpect.spawn(constants.ADB_PATH, 1133 self._logcat.args[1:], 1134 timeout=self._logcat.timeout, 1135 logfile=self._logcat.logfile) 1136 1137 def StartRecordingLogcat(self, clear=True, filters=['*:v']): 1138 """Starts recording logcat output to eventually be saved as a string. 1139 1140 This call should come before some series of tests are run, with either 1141 StopRecordingLogcat or SearchLogcatRecord following the tests. 1142 1143 Args: 1144 clear: True if existing log output should be cleared. 1145 filters: A list of logcat filters to be used. 1146 """ 1147 if clear: 1148 self._adb.SendCommand('logcat -c') 1149 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg, 1150 ' '.join(filters)) 1151 self._logcat_tmpoutfile = tempfile.TemporaryFile(bufsize=0) 1152 self.logcat_process = subprocess.Popen(logcat_command, shell=True, 1153 stdout=self._logcat_tmpoutfile) 1154 1155 def StopRecordingLogcat(self): 1156 """Stops an existing logcat recording subprocess and returns output. 1157 1158 Returns: 1159 The logcat output as a string or an empty string if logcat was not 1160 being recorded at the time. 1161 """ 1162 if not self.logcat_process: 1163 return '' 1164 # Cannot evaluate directly as 0 is a possible value. 1165 # Better to read the self.logcat_process.stdout before killing it, 1166 # Otherwise the communicate may return incomplete output due to pipe break. 1167 if self.logcat_process.poll() is None: 1168 self.logcat_process.kill() 1169 self.logcat_process.wait() 1170 self.logcat_process = None 1171 self._logcat_tmpoutfile.seek(0) 1172 output = self._logcat_tmpoutfile.read() 1173 self._logcat_tmpoutfile.close() 1174 return output 1175 1176 def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None, 1177 log_level=None, component=None): 1178 """Searches the specified logcat output and returns results. 1179 1180 This method searches through the logcat output specified by record for a 1181 certain message, narrowing results by matching them against any other 1182 specified criteria. It returns all matching lines as described below. 1183 1184 Args: 1185 record: A string generated by Start/StopRecordingLogcat to search. 1186 message: An output string to search for. 1187 thread_id: The thread id that is the origin of the message. 1188 proc_id: The process that is the origin of the message. 1189 log_level: The log level of the message. 1190 component: The name of the component that would create the message. 1191 1192 Returns: 1193 A list of dictionaries represeting matching entries, each containing keys 1194 thread_id, proc_id, log_level, component, and message. 1195 """ 1196 if thread_id: 1197 thread_id = str(thread_id) 1198 if proc_id: 1199 proc_id = str(proc_id) 1200 results = [] 1201 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$', 1202 re.MULTILINE) 1203 log_list = reg.findall(record) 1204 for (tid, pid, log_lev, comp, msg) in log_list: 1205 if ((not thread_id or thread_id == tid) and 1206 (not proc_id or proc_id == pid) and 1207 (not log_level or log_level == log_lev) and 1208 (not component or component == comp) and msg.find(message) > -1): 1209 match = dict({'thread_id': tid, 'proc_id': pid, 1210 'log_level': log_lev, 'component': comp, 1211 'message': msg}) 1212 results.append(match) 1213 return results 1214 1215 def ExtractPid(self, process_name): 1216 """Extracts Process Ids for a given process name from Android Shell. 1217 1218 Args: 1219 process_name: name of the process on the device. 1220 1221 Returns: 1222 List of all the process ids (as strings) that match the given name. 1223 If the name of a process exactly matches the given name, the pid of 1224 that process will be inserted to the front of the pid list. 1225 """ 1226 pids = [] 1227 for line in self.RunShellCommand('ps', log_result=False): 1228 data = line.split() 1229 try: 1230 if process_name in data[-1]: # name is in the last column 1231 if process_name == data[-1]: 1232 pids.insert(0, data[1]) # PID is in the second column 1233 else: 1234 pids.append(data[1]) 1235 except IndexError: 1236 pass 1237 return pids 1238 1239 def GetIoStats(self): 1240 """Gets cumulative disk IO stats since boot (for all processes). 1241 1242 Returns: 1243 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there 1244 was an error. 1245 """ 1246 for line in self.GetFileContents('/proc/diskstats', log_result=False): 1247 stats = io_stats_parser.ParseIoStatsLine(line) 1248 if stats.device == 'mmcblk0': 1249 return { 1250 'num_reads': stats.num_reads_issued, 1251 'num_writes': stats.num_writes_completed, 1252 'read_ms': stats.ms_spent_reading, 1253 'write_ms': stats.ms_spent_writing, 1254 } 1255 logging.warning('Could not find disk IO stats.') 1256 return None 1257 1258 def GetMemoryUsageForPid(self, pid): 1259 """Returns the memory usage for given pid. 1260 1261 Args: 1262 pid: The pid number of the specific process running on device. 1263 1264 Returns: 1265 A tuple containg: 1266 [0]: Dict of {metric:usage_kb}, for the process which has specified pid. 1267 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, 1268 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, 1269 KernelPageSize, MMUPageSize, Nvidia (tablet only). 1270 [1]: Detailed /proc/[PID]/smaps information. 1271 """ 1272 usage_dict = collections.defaultdict(int) 1273 smaps = collections.defaultdict(dict) 1274 current_smap = '' 1275 for line in self.GetProtectedFileContents('/proc/%s/smaps' % pid, 1276 log_result=False): 1277 items = line.split() 1278 # See man 5 proc for more details. The format is: 1279 # address perms offset dev inode pathname 1280 if len(items) > 5: 1281 current_smap = ' '.join(items[5:]) 1282 elif len(items) > 3: 1283 current_smap = ' '.join(items[3:]) 1284 match = re.match(MEMORY_INFO_RE, line) 1285 if match: 1286 key = match.group('key') 1287 usage_kb = int(match.group('usage_kb')) 1288 usage_dict[key] += usage_kb 1289 if key not in smaps[current_smap]: 1290 smaps[current_smap][key] = 0 1291 smaps[current_smap][key] += usage_kb 1292 if not usage_dict or not any(usage_dict.values()): 1293 # Presumably the process died between ps and calling this method. 1294 logging.warning('Could not find memory usage for pid ' + str(pid)) 1295 1296 for line in self.GetProtectedFileContents('/d/nvmap/generic-0/clients', 1297 log_result=False): 1298 match = re.match(NVIDIA_MEMORY_INFO_RE, line) 1299 if match and match.group('pid') == pid: 1300 usage_bytes = int(match.group('usage_bytes')) 1301 usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB 1302 break 1303 1304 return (usage_dict, smaps) 1305 1306 def GetMemoryUsageForPackage(self, package): 1307 """Returns the memory usage for all processes whose name contains |pacakge|. 1308 1309 Args: 1310 package: A string holding process name to lookup pid list for. 1311 1312 Returns: 1313 A tuple containg: 1314 [0]: Dict of {metric:usage_kb}, summed over all pids associated with 1315 |name|. 1316 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, 1317 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, 1318 KernelPageSize, MMUPageSize, Nvidia (tablet only). 1319 [1]: a list with detailed /proc/[PID]/smaps information. 1320 """ 1321 usage_dict = collections.defaultdict(int) 1322 pid_list = self.ExtractPid(package) 1323 smaps = collections.defaultdict(dict) 1324 1325 for pid in pid_list: 1326 usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid) 1327 smaps[pid] = smaps_per_pid 1328 for (key, value) in usage_dict_per_pid.items(): 1329 usage_dict[key] += value 1330 1331 return usage_dict, smaps 1332 1333 def ProcessesUsingDevicePort(self, device_port): 1334 """Lists processes using the specified device port on loopback interface. 1335 1336 Args: 1337 device_port: Port on device we want to check. 1338 1339 Returns: 1340 A list of (pid, process_name) tuples using the specified port. 1341 """ 1342 tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False) 1343 tcp_address = '0100007F:%04X' % device_port 1344 pids = [] 1345 for single_connect in tcp_results: 1346 connect_results = single_connect.split() 1347 # Column 1 is the TCP port, and Column 9 is the inode of the socket 1348 if connect_results[1] == tcp_address: 1349 socket_inode = connect_results[9] 1350 socket_name = 'socket:[%s]' % socket_inode 1351 lsof_results = self.RunShellCommand('lsof', log_result=False) 1352 for single_process in lsof_results: 1353 process_results = single_process.split() 1354 # Ignore the line if it has less than nine columns in it, which may 1355 # be the case when a process stops while lsof is executing. 1356 if len(process_results) <= 8: 1357 continue 1358 # Column 0 is the executable name 1359 # Column 1 is the pid 1360 # Column 8 is the Inode in use 1361 if process_results[8] == socket_name: 1362 pids.append((int(process_results[1]), process_results[0])) 1363 break 1364 logging.info('PidsUsingDevicePort: %s', pids) 1365 return pids 1366 1367 def FileExistsOnDevice(self, file_name): 1368 """Checks whether the given file exists on the device. 1369 1370 Args: 1371 file_name: Full path of file to check. 1372 1373 Returns: 1374 True if the file exists, False otherwise. 1375 """ 1376 assert '"' not in file_name, 'file_name cannot contain double quotes' 1377 try: 1378 status = self._adb.SendShellCommand( 1379 '\'test -e "%s"; echo $?\'' % (file_name)) 1380 if 'test: not found' not in status: 1381 return int(status) == 0 1382 1383 status = self._adb.SendShellCommand( 1384 '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name)) 1385 return int(status) == 0 1386 except ValueError: 1387 if IsDeviceAttached(self._device): 1388 raise errors.DeviceUnresponsiveError('Device may be offline.') 1389 1390 return False 1391 1392 def TakeScreenshot(self, host_file): 1393 """Saves a screenshot image to |host_file| on the host. 1394 1395 Args: 1396 host_file: Absolute path to the image file to store on the host. 1397 """ 1398 host_dir = os.path.dirname(host_file) 1399 if not os.path.exists(host_dir): 1400 os.makedirs(host_dir) 1401 device_file = '%s/screenshot.png' % self.GetExternalStorage() 1402 self.RunShellCommand('/system/bin/screencap -p %s' % device_file) 1403 assert self._adb.Pull(device_file, host_file) 1404 assert os.path.exists(host_file) 1405 1406 def SetUtilWrapper(self, util_wrapper): 1407 """Sets a wrapper prefix to be used when running a locally-built 1408 binary on the device (ex.: md5sum_bin). 1409 """ 1410 self._util_wrapper = util_wrapper 1411 1412 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): 1413 """Runs a single instrumentation test. 1414 1415 Args: 1416 test: Test class/method. 1417 test_package: Package name of test apk. 1418 instr_args: Extra key/value to pass to am instrument. 1419 timeout: Timeout time in seconds. 1420 1421 Returns: 1422 An instance of am_instrument_parser.TestResult object. 1423 """ 1424 instrumentation_path = ('%s/android.test.InstrumentationTestRunner' % 1425 test_package) 1426 args_with_filter = dict(instr_args) 1427 args_with_filter['class'] = test 1428 logging.info(args_with_filter) 1429 (raw_results, _) = self._adb.StartInstrumentation( 1430 instrumentation_path=instrumentation_path, 1431 instrumentation_args=args_with_filter, 1432 timeout_time=timeout) 1433 assert len(raw_results) == 1 1434 return raw_results[0] 1435 1436 def RunUIAutomatorTest(self, test, test_package, timeout): 1437 """Runs a single uiautomator test. 1438 1439 Args: 1440 test: Test class/method. 1441 test_package: Name of the test jar. 1442 timeout: Timeout time in seconds. 1443 1444 Returns: 1445 An instance of am_instrument_parser.TestResult object. 1446 """ 1447 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) 1448 self._LogShell(cmd) 1449 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) 1450 # uiautomator doesn't fully conform to the instrumenation test runner 1451 # convention and doesn't terminate with INSTRUMENTATION_CODE. 1452 # Just assume the first result is valid. 1453 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) 1454 if not test_results: 1455 raise errors.InstrumentationError( 1456 'no test results... device setup correctly?') 1457 return test_results[0] 1458 1459 1460class NewLineNormalizer(object): 1461 """A file-like object to normalize EOLs to '\n'. 1462 1463 Pexpect runs adb within a pseudo-tty device (see 1464 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written 1465 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate 1466 lines, the log ends up having '\r\r\n' at the end of each line. This 1467 filter replaces the above with a single '\n' in the data stream. 1468 """ 1469 def __init__(self, output): 1470 self._output = output 1471 1472 def write(self, data): 1473 data = data.replace('\r\r\n', '\n') 1474 self._output.write(data) 1475 1476 def flush(self): 1477 self._output.flush() 1478