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