1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/webrtc/trunk/build/android/pylib/android_commands.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1071 @@ 1.4 +# Copyright (c) 2012 The Chromium Authors. All rights reserved. 1.5 +# Use of this source code is governed by a BSD-style license that can be 1.6 +# found in the LICENSE file. 1.7 + 1.8 +"""Provides an interface to communicate with the device via the adb command. 1.9 + 1.10 +Assumes adb binary is currently on system path. 1.11 +""" 1.12 + 1.13 +import collections 1.14 +import datetime 1.15 +import logging 1.16 +import os 1.17 +import re 1.18 +import shlex 1.19 +import subprocess 1.20 +import sys 1.21 +import tempfile 1.22 +import time 1.23 + 1.24 +import io_stats_parser 1.25 +from pylib import pexpect 1.26 + 1.27 +CHROME_SRC = os.path.join( 1.28 + os.path.abspath(os.path.dirname(__file__)), '..', '..', '..') 1.29 + 1.30 +sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner')) 1.31 +import adb_interface 1.32 + 1.33 +import cmd_helper 1.34 +import errors # is under ../../../third_party/android_testrunner/errors.py 1.35 + 1.36 + 1.37 +# Pattern to search for the next whole line of pexpect output and capture it 1.38 +# into a match group. We can't use ^ and $ for line start end with pexpect, 1.39 +# see http://www.noah.org/python/pexpect/#doc for explanation why. 1.40 +PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') 1.41 + 1.42 +# Set the adb shell prompt to be a unique marker that will [hopefully] not 1.43 +# appear at the start of any line of a command's output. 1.44 +SHELL_PROMPT = '~+~PQ\x17RS~+~' 1.45 + 1.46 +# Java properties file 1.47 +LOCAL_PROPERTIES_PATH = '/data/local.prop' 1.48 + 1.49 +# Property in /data/local.prop that controls Java assertions. 1.50 +JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' 1.51 + 1.52 +MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$') 1.53 +NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*' 1.54 + '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$') 1.55 + 1.56 +# Keycode "enum" suitable for passing to AndroidCommands.SendKey(). 1.57 +KEYCODE_HOME = 3 1.58 +KEYCODE_BACK = 4 1.59 +KEYCODE_DPAD_UP = 19 1.60 +KEYCODE_DPAD_DOWN = 20 1.61 +KEYCODE_DPAD_RIGHT = 22 1.62 +KEYCODE_ENTER = 66 1.63 +KEYCODE_MENU = 82 1.64 + 1.65 +MD5SUM_DEVICE_PATH = '/data/local/tmp/md5sum_bin' 1.66 + 1.67 +def GetEmulators(): 1.68 + """Returns a list of emulators. Does not filter by status (e.g. offline). 1.69 + 1.70 + Both devices starting with 'emulator' will be returned in below output: 1.71 + 1.72 + * daemon not running. starting it now on port 5037 * 1.73 + * daemon started successfully * 1.74 + List of devices attached 1.75 + 027c10494100b4d7 device 1.76 + emulator-5554 offline 1.77 + emulator-5558 device 1.78 + """ 1.79 + re_device = re.compile('^emulator-[0-9]+', re.MULTILINE) 1.80 + devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) 1.81 + return devices 1.82 + 1.83 + 1.84 +def GetAVDs(): 1.85 + """Returns a list of AVDs.""" 1.86 + re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE) 1.87 + avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd'])) 1.88 + return avds 1.89 + 1.90 + 1.91 +def GetAttachedDevices(): 1.92 + """Returns a list of attached, online android devices. 1.93 + 1.94 + If a preferred device has been set with ANDROID_SERIAL, it will be first in 1.95 + the returned list. 1.96 + 1.97 + Example output: 1.98 + 1.99 + * daemon not running. starting it now on port 5037 * 1.100 + * daemon started successfully * 1.101 + List of devices attached 1.102 + 027c10494100b4d7 device 1.103 + emulator-5554 offline 1.104 + """ 1.105 + re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) 1.106 + devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) 1.107 + preferred_device = os.environ.get('ANDROID_SERIAL') 1.108 + if preferred_device in devices: 1.109 + devices.remove(preferred_device) 1.110 + devices.insert(0, preferred_device) 1.111 + return devices 1.112 + 1.113 +def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): 1.114 + """Gets a list of files from `ls` command output. 1.115 + 1.116 + Python's os.walk isn't used because it doesn't work over adb shell. 1.117 + 1.118 + Args: 1.119 + path: The path to list. 1.120 + ls_output: A list of lines returned by an `ls -lR` command. 1.121 + re_file: A compiled regular expression which parses a line into named groups 1.122 + consisting of at minimum "filename", "date", "time", "size" and 1.123 + optionally "timezone". 1.124 + utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a 1.125 + 2-digit string giving the number of UTC offset hours, and MM is a 1.126 + 2-digit string giving the number of UTC offset minutes. If the input 1.127 + utc_offset is None, will try to look for the value of "timezone" if it 1.128 + is specified in re_file. 1.129 + 1.130 + Returns: 1.131 + A dict of {"name": (size, lastmod), ...} where: 1.132 + name: The file name relative to |path|'s directory. 1.133 + size: The file size in bytes (0 for directories). 1.134 + lastmod: The file last modification date in UTC. 1.135 + """ 1.136 + re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path)) 1.137 + path_dir = os.path.dirname(path) 1.138 + 1.139 + current_dir = '' 1.140 + files = {} 1.141 + for line in ls_output: 1.142 + directory_match = re_directory.match(line) 1.143 + if directory_match: 1.144 + current_dir = directory_match.group('dir') 1.145 + continue 1.146 + file_match = re_file.match(line) 1.147 + if file_match: 1.148 + filename = os.path.join(current_dir, file_match.group('filename')) 1.149 + if filename.startswith(path_dir): 1.150 + filename = filename[len(path_dir)+1:] 1.151 + lastmod = datetime.datetime.strptime( 1.152 + file_match.group('date') + ' ' + file_match.group('time')[:5], 1.153 + '%Y-%m-%d %H:%M') 1.154 + if not utc_offset and 'timezone' in re_file.groupindex: 1.155 + utc_offset = file_match.group('timezone') 1.156 + if isinstance(utc_offset, str) and len(utc_offset) == 5: 1.157 + utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), 1.158 + minutes=int(utc_offset[3:5])) 1.159 + if utc_offset[0:1] == '-': 1.160 + utc_delta = -utc_delta 1.161 + lastmod -= utc_delta 1.162 + files[filename] = (int(file_match.group('size')), lastmod) 1.163 + return files 1.164 + 1.165 +def _ComputeFileListHash(md5sum_output): 1.166 + """Returns a list of MD5 strings from the provided md5sum output.""" 1.167 + return [line.split(' ')[0] for line in md5sum_output] 1.168 + 1.169 +def _HasAdbPushSucceeded(command_output): 1.170 + """Returns whether adb push has succeeded from the provided output.""" 1.171 + if not command_output: 1.172 + return False 1.173 + # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" 1.174 + # Errors look like this: "failed to copy ... " 1.175 + if not re.search('^[0-9]', command_output.splitlines()[-1]): 1.176 + logging.critical('PUSH FAILED: ' + command_output) 1.177 + return False 1.178 + return True 1.179 + 1.180 +def GetLogTimestamp(log_line, year): 1.181 + """Returns the timestamp of the given |log_line| in the given year.""" 1.182 + try: 1.183 + return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), 1.184 + '%Y-%m-%d %H:%M:%S.%f') 1.185 + except (ValueError, IndexError): 1.186 + logging.critical('Error reading timestamp from ' + log_line) 1.187 + return None 1.188 + 1.189 + 1.190 +class AndroidCommands(object): 1.191 + """Helper class for communicating with Android device via adb. 1.192 + 1.193 + Args: 1.194 + device: If given, adb commands are only send to the device of this ID. 1.195 + Otherwise commands are sent to all attached devices. 1.196 + """ 1.197 + 1.198 + def __init__(self, device=None): 1.199 + self._adb = adb_interface.AdbInterface() 1.200 + if device: 1.201 + self._adb.SetTargetSerial(device) 1.202 + self._logcat = None 1.203 + self.logcat_process = None 1.204 + self._pushed_files = [] 1.205 + self._device_utc_offset = self.RunShellCommand('date +%z')[0] 1.206 + self._md5sum_path = '' 1.207 + self._external_storage = '' 1.208 + 1.209 + def Adb(self): 1.210 + """Returns our AdbInterface to avoid us wrapping all its methods.""" 1.211 + return self._adb 1.212 + 1.213 + def IsRootEnabled(self): 1.214 + """Checks if root is enabled on the device.""" 1.215 + root_test_output = self.RunShellCommand('ls /root') or [''] 1.216 + return not 'Permission denied' in root_test_output[0] 1.217 + 1.218 + def EnableAdbRoot(self): 1.219 + """Enables adb root on the device. 1.220 + 1.221 + Returns: 1.222 + True: if output from executing adb root was as expected. 1.223 + False: otherwise. 1.224 + """ 1.225 + return_value = self._adb.EnableAdbRoot() 1.226 + # EnableAdbRoot inserts a call for wait-for-device only when adb logcat 1.227 + # output matches what is expected. Just to be safe add a call to 1.228 + # wait-for-device. 1.229 + self._adb.SendCommand('wait-for-device') 1.230 + return return_value 1.231 + 1.232 + def GetDeviceYear(self): 1.233 + """Returns the year information of the date on device.""" 1.234 + return self.RunShellCommand('date +%Y')[0] 1.235 + 1.236 + def GetExternalStorage(self): 1.237 + if not self._external_storage: 1.238 + self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0] 1.239 + assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE' 1.240 + return self._external_storage 1.241 + 1.242 + def WaitForDevicePm(self): 1.243 + """Blocks until the device's package manager is available. 1.244 + 1.245 + To workaround http://b/5201039, we restart the shell and retry if the 1.246 + package manager isn't back after 120 seconds. 1.247 + 1.248 + Raises: 1.249 + errors.WaitForResponseTimedOutError after max retries reached. 1.250 + """ 1.251 + last_err = None 1.252 + retries = 3 1.253 + while retries: 1.254 + try: 1.255 + self._adb.WaitForDevicePm() 1.256 + return # Success 1.257 + except errors.WaitForResponseTimedOutError as e: 1.258 + last_err = e 1.259 + logging.warning('Restarting and retrying after timeout: %s', e) 1.260 + retries -= 1 1.261 + self.RestartShell() 1.262 + raise last_err # Only reached after max retries, re-raise the last error. 1.263 + 1.264 + def RestartShell(self): 1.265 + """Restarts the shell on the device. Does not block for it to return.""" 1.266 + self.RunShellCommand('stop') 1.267 + self.RunShellCommand('start') 1.268 + 1.269 + def Reboot(self, full_reboot=True): 1.270 + """Reboots the device and waits for the package manager to return. 1.271 + 1.272 + Args: 1.273 + full_reboot: Whether to fully reboot the device or just restart the shell. 1.274 + """ 1.275 + # TODO(torne): hive can't reboot the device either way without breaking the 1.276 + # connection; work out if we can handle this better 1.277 + if os.environ.get('USING_HIVE'): 1.278 + logging.warning('Ignoring reboot request as we are on hive') 1.279 + return 1.280 + if full_reboot or not self.IsRootEnabled(): 1.281 + self._adb.SendCommand('reboot') 1.282 + timeout = 300 1.283 + else: 1.284 + self.RestartShell() 1.285 + timeout = 120 1.286 + # To run tests we need at least the package manager and the sd card (or 1.287 + # other external storage) to be ready. 1.288 + self.WaitForDevicePm() 1.289 + self.WaitForSdCardReady(timeout) 1.290 + 1.291 + def Uninstall(self, package): 1.292 + """Uninstalls the specified package from the device. 1.293 + 1.294 + Args: 1.295 + package: Name of the package to remove. 1.296 + 1.297 + Returns: 1.298 + A status string returned by adb uninstall 1.299 + """ 1.300 + uninstall_command = 'uninstall %s' % package 1.301 + 1.302 + logging.info('>>> $' + uninstall_command) 1.303 + return self._adb.SendCommand(uninstall_command, timeout_time=60) 1.304 + 1.305 + def Install(self, package_file_path, reinstall=False): 1.306 + """Installs the specified package to the device. 1.307 + 1.308 + Args: 1.309 + package_file_path: Path to .apk file to install. 1.310 + reinstall: Reinstall an existing apk, keeping the data. 1.311 + 1.312 + Returns: 1.313 + A status string returned by adb install 1.314 + """ 1.315 + assert os.path.isfile(package_file_path), ('<%s> is not file' % 1.316 + package_file_path) 1.317 + 1.318 + install_cmd = ['install'] 1.319 + 1.320 + if reinstall: 1.321 + install_cmd.append('-r') 1.322 + 1.323 + install_cmd.append(package_file_path) 1.324 + install_cmd = ' '.join(install_cmd) 1.325 + 1.326 + logging.info('>>> $' + install_cmd) 1.327 + return self._adb.SendCommand(install_cmd, timeout_time=2*60, retry_count=0) 1.328 + 1.329 + def ManagedInstall(self, apk_path, keep_data=False, package_name=None, 1.330 + reboots_on_failure=2): 1.331 + """Installs specified package and reboots device on timeouts. 1.332 + 1.333 + Args: 1.334 + apk_path: Path to .apk file to install. 1.335 + keep_data: Reinstalls instead of uninstalling first, preserving the 1.336 + application data. 1.337 + package_name: Package name (only needed if keep_data=False). 1.338 + reboots_on_failure: number of time to reboot if package manager is frozen. 1.339 + 1.340 + Returns: 1.341 + A status string returned by adb install 1.342 + """ 1.343 + reboots_left = reboots_on_failure 1.344 + while True: 1.345 + try: 1.346 + if not keep_data: 1.347 + assert package_name 1.348 + self.Uninstall(package_name) 1.349 + install_status = self.Install(apk_path, reinstall=keep_data) 1.350 + if 'Success' in install_status: 1.351 + return install_status 1.352 + except errors.WaitForResponseTimedOutError: 1.353 + print '@@@STEP_WARNINGS@@@' 1.354 + logging.info('Timeout on installing %s' % apk_path) 1.355 + 1.356 + if reboots_left <= 0: 1.357 + raise Exception('Install failure') 1.358 + 1.359 + # Force a hard reboot on last attempt 1.360 + self.Reboot(full_reboot=(reboots_left == 1)) 1.361 + reboots_left -= 1 1.362 + 1.363 + def MakeSystemFolderWritable(self): 1.364 + """Remounts the /system folder rw.""" 1.365 + out = self._adb.SendCommand('remount') 1.366 + if out.strip() != 'remount succeeded': 1.367 + raise errors.MsgException('Remount failed: %s' % out) 1.368 + 1.369 + def RestartAdbServer(self): 1.370 + """Restart the adb server.""" 1.371 + self.KillAdbServer() 1.372 + self.StartAdbServer() 1.373 + 1.374 + def KillAdbServer(self): 1.375 + """Kill adb server.""" 1.376 + adb_cmd = ['adb', 'kill-server'] 1.377 + return cmd_helper.RunCmd(adb_cmd) 1.378 + 1.379 + def StartAdbServer(self): 1.380 + """Start adb server.""" 1.381 + adb_cmd = ['adb', 'start-server'] 1.382 + return cmd_helper.RunCmd(adb_cmd) 1.383 + 1.384 + def WaitForSystemBootCompleted(self, wait_time): 1.385 + """Waits for targeted system's boot_completed flag to be set. 1.386 + 1.387 + Args: 1.388 + wait_time: time in seconds to wait 1.389 + 1.390 + Raises: 1.391 + WaitForResponseTimedOutError if wait_time elapses and flag still not 1.392 + set. 1.393 + """ 1.394 + logging.info('Waiting for system boot completed...') 1.395 + self._adb.SendCommand('wait-for-device') 1.396 + # Now the device is there, but system not boot completed. 1.397 + # Query the sys.boot_completed flag with a basic command 1.398 + boot_completed = False 1.399 + attempts = 0 1.400 + wait_period = 5 1.401 + while not boot_completed and (attempts * wait_period) < wait_time: 1.402 + output = self._adb.SendShellCommand('getprop sys.boot_completed', 1.403 + retry_count=1) 1.404 + output = output.strip() 1.405 + if output == '1': 1.406 + boot_completed = True 1.407 + else: 1.408 + # If 'error: xxx' returned when querying the flag, it means 1.409 + # adb server lost the connection to the emulator, so restart the adb 1.410 + # server. 1.411 + if 'error:' in output: 1.412 + self.RestartAdbServer() 1.413 + time.sleep(wait_period) 1.414 + attempts += 1 1.415 + if not boot_completed: 1.416 + raise errors.WaitForResponseTimedOutError( 1.417 + 'sys.boot_completed flag was not set after %s seconds' % wait_time) 1.418 + 1.419 + def WaitForSdCardReady(self, timeout_time): 1.420 + """Wait for the SD card ready before pushing data into it.""" 1.421 + logging.info('Waiting for SD card ready...') 1.422 + sdcard_ready = False 1.423 + attempts = 0 1.424 + wait_period = 5 1.425 + external_storage = self.GetExternalStorage() 1.426 + while not sdcard_ready and attempts * wait_period < timeout_time: 1.427 + output = self.RunShellCommand('ls ' + external_storage) 1.428 + if output: 1.429 + sdcard_ready = True 1.430 + else: 1.431 + time.sleep(wait_period) 1.432 + attempts += 1 1.433 + if not sdcard_ready: 1.434 + raise errors.WaitForResponseTimedOutError( 1.435 + 'SD card not ready after %s seconds' % timeout_time) 1.436 + 1.437 + # It is tempting to turn this function into a generator, however this is not 1.438 + # possible without using a private (local) adb_shell instance (to ensure no 1.439 + # other command interleaves usage of it), which would defeat the main aim of 1.440 + # being able to reuse the adb shell instance across commands. 1.441 + def RunShellCommand(self, command, timeout_time=20, log_result=False): 1.442 + """Send a command to the adb shell and return the result. 1.443 + 1.444 + Args: 1.445 + command: String containing the shell command to send. Must not include 1.446 + the single quotes as we use them to escape the whole command. 1.447 + timeout_time: Number of seconds to wait for command to respond before 1.448 + retrying, used by AdbInterface.SendShellCommand. 1.449 + log_result: Boolean to indicate whether we should log the result of the 1.450 + shell command. 1.451 + 1.452 + Returns: 1.453 + list containing the lines of output received from running the command 1.454 + """ 1.455 + logging.info('>>> $' + command) 1.456 + if "'" in command: logging.warning(command + " contains ' quotes") 1.457 + result = self._adb.SendShellCommand( 1.458 + "'%s'" % command, timeout_time).splitlines() 1.459 + if ['error: device not found'] == result: 1.460 + raise errors.DeviceUnresponsiveError('device not found') 1.461 + if log_result: 1.462 + logging.info('\n>>> '.join(result)) 1.463 + return result 1.464 + 1.465 + def KillAll(self, process): 1.466 + """Android version of killall, connected via adb. 1.467 + 1.468 + Args: 1.469 + process: name of the process to kill off 1.470 + 1.471 + Returns: 1.472 + the number of processes killed 1.473 + """ 1.474 + pids = self.ExtractPid(process) 1.475 + if pids: 1.476 + self.RunShellCommand('kill ' + ' '.join(pids)) 1.477 + return len(pids) 1.478 + 1.479 + def KillAllBlocking(self, process, timeout_sec): 1.480 + """Blocking version of killall, connected via adb. 1.481 + 1.482 + This waits until no process matching the corresponding name appears in ps' 1.483 + output anymore. 1.484 + 1.485 + Args: 1.486 + process: name of the process to kill off 1.487 + timeout_sec: the timeout in seconds 1.488 + 1.489 + Returns: 1.490 + the number of processes killed 1.491 + """ 1.492 + processes_killed = self.KillAll(process) 1.493 + if processes_killed: 1.494 + elapsed = 0 1.495 + wait_period = 0.1 1.496 + # Note that this doesn't take into account the time spent in ExtractPid(). 1.497 + while self.ExtractPid(process) and elapsed < timeout_sec: 1.498 + time.sleep(wait_period) 1.499 + elapsed += wait_period 1.500 + if elapsed >= timeout_sec: 1.501 + return 0 1.502 + return processes_killed 1.503 + 1.504 + def StartActivity(self, package, activity, wait_for_completion=False, 1.505 + action='android.intent.action.VIEW', 1.506 + category=None, data=None, 1.507 + extras=None, trace_file_name=None): 1.508 + """Starts |package|'s activity on the device. 1.509 + 1.510 + Args: 1.511 + package: Name of package to start (e.g. 'com.google.android.apps.chrome'). 1.512 + activity: Name of activity (e.g. '.Main' or 1.513 + 'com.google.android.apps.chrome.Main'). 1.514 + wait_for_completion: wait for the activity to finish launching (-W flag). 1.515 + action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. 1.516 + category: string (e.g. "android.intent.category.HOME") 1.517 + data: Data string to pass to activity (e.g. 'http://www.example.com/'). 1.518 + extras: Dict of extras to pass to activity. Values are significant. 1.519 + trace_file_name: If used, turns on and saves the trace to this file name. 1.520 + """ 1.521 + cmd = 'am start -a %s' % action 1.522 + if wait_for_completion: 1.523 + cmd += ' -W' 1.524 + if category: 1.525 + cmd += ' -c %s' % category 1.526 + if package and activity: 1.527 + cmd += ' -n %s/%s' % (package, activity) 1.528 + if data: 1.529 + cmd += ' -d "%s"' % data 1.530 + if extras: 1.531 + for key in extras: 1.532 + value = extras[key] 1.533 + if isinstance(value, str): 1.534 + cmd += ' --es' 1.535 + elif isinstance(value, bool): 1.536 + cmd += ' --ez' 1.537 + elif isinstance(value, int): 1.538 + cmd += ' --ei' 1.539 + else: 1.540 + raise NotImplementedError( 1.541 + 'Need to teach StartActivity how to pass %s extras' % type(value)) 1.542 + cmd += ' %s %s' % (key, value) 1.543 + if trace_file_name: 1.544 + cmd += ' --start-profiler ' + trace_file_name 1.545 + self.RunShellCommand(cmd) 1.546 + 1.547 + def GoHome(self): 1.548 + """Tell the device to return to the home screen. Blocks until completion.""" 1.549 + self.RunShellCommand('am start -W ' 1.550 + '-a android.intent.action.MAIN -c android.intent.category.HOME') 1.551 + 1.552 + def CloseApplication(self, package): 1.553 + """Attempt to close down the application, using increasing violence. 1.554 + 1.555 + Args: 1.556 + package: Name of the process to kill off, e.g. 1.557 + com.google.android.apps.chrome 1.558 + """ 1.559 + self.RunShellCommand('am force-stop ' + package) 1.560 + 1.561 + def ClearApplicationState(self, package): 1.562 + """Closes and clears all state for the given |package|.""" 1.563 + self.CloseApplication(package) 1.564 + self.RunShellCommand('rm -r /data/data/%s/app_*' % package) 1.565 + self.RunShellCommand('rm -r /data/data/%s/cache/*' % package) 1.566 + self.RunShellCommand('rm -r /data/data/%s/files/*' % package) 1.567 + self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package) 1.568 + 1.569 + def SendKeyEvent(self, keycode): 1.570 + """Sends keycode to the device. 1.571 + 1.572 + Args: 1.573 + keycode: Numeric keycode to send (see "enum" at top of file). 1.574 + """ 1.575 + self.RunShellCommand('input keyevent %d' % keycode) 1.576 + 1.577 + def PushIfNeeded(self, local_path, device_path): 1.578 + """Pushes |local_path| to |device_path|. 1.579 + 1.580 + Works for files and directories. This method skips copying any paths in 1.581 + |test_data_paths| that already exist on the device with the same hash. 1.582 + 1.583 + All pushed files can be removed by calling RemovePushedFiles(). 1.584 + """ 1.585 + assert os.path.exists(local_path), 'Local path not found %s' % local_path 1.586 + 1.587 + if not self._md5sum_path: 1.588 + default_build_type = os.environ.get('BUILD_TYPE', 'Debug') 1.589 + md5sum_path = '%s/out/%s/md5sum_bin' % (CHROME_SRC, default_build_type) 1.590 + if not os.path.exists(md5sum_path): 1.591 + md5sum_path = '%s/out/Release/md5sum_bin' % (CHROME_SRC) 1.592 + if not os.path.exists(md5sum_path): 1.593 + print >> sys.stderr, 'Please build md5sum.' 1.594 + sys.exit(1) 1.595 + command = 'push %s %s' % (md5sum_path, MD5SUM_DEVICE_PATH) 1.596 + assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) 1.597 + self._md5sum_path = md5sum_path 1.598 + 1.599 + self._pushed_files.append(device_path) 1.600 + hashes_on_device = _ComputeFileListHash( 1.601 + self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path)) 1.602 + assert os.path.exists(local_path), 'Local path not found %s' % local_path 1.603 + hashes_on_host = _ComputeFileListHash( 1.604 + subprocess.Popen( 1.605 + '%s_host %s' % (self._md5sum_path, local_path), 1.606 + stdout=subprocess.PIPE, shell=True).stdout) 1.607 + if hashes_on_device == hashes_on_host: 1.608 + return 1.609 + 1.610 + # They don't match, so remove everything first and then create it. 1.611 + if os.path.isdir(local_path): 1.612 + self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) 1.613 + self.RunShellCommand('mkdir -p %s' % device_path) 1.614 + 1.615 + # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of 1.616 + # 60 seconds which isn't sufficient for a lot of users of this method. 1.617 + push_command = 'push %s %s' % (local_path, device_path) 1.618 + logging.info('>>> $' + push_command) 1.619 + output = self._adb.SendCommand(push_command, timeout_time=30*60) 1.620 + assert _HasAdbPushSucceeded(output) 1.621 + 1.622 + 1.623 + def GetFileContents(self, filename, log_result=False): 1.624 + """Gets contents from the file specified by |filename|.""" 1.625 + return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + 1.626 + filename + '"; fi', log_result=log_result) 1.627 + 1.628 + def SetFileContents(self, filename, contents): 1.629 + """Writes |contents| to the file specified by |filename|.""" 1.630 + with tempfile.NamedTemporaryFile() as f: 1.631 + f.write(contents) 1.632 + f.flush() 1.633 + self._adb.Push(f.name, filename) 1.634 + 1.635 + def RemovePushedFiles(self): 1.636 + """Removes all files pushed with PushIfNeeded() from the device.""" 1.637 + for p in self._pushed_files: 1.638 + self.RunShellCommand('rm -r %s' % p, timeout_time=2*60) 1.639 + 1.640 + def ListPathContents(self, path): 1.641 + """Lists files in all subdirectories of |path|. 1.642 + 1.643 + Args: 1.644 + path: The path to list. 1.645 + 1.646 + Returns: 1.647 + A dict of {"name": (size, lastmod), ...}. 1.648 + """ 1.649 + # Example output: 1.650 + # /foo/bar: 1.651 + # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt 1.652 + re_file = re.compile('^-(?P<perms>[^\s]+)\s+' 1.653 + '(?P<user>[^\s]+)\s+' 1.654 + '(?P<group>[^\s]+)\s+' 1.655 + '(?P<size>[^\s]+)\s+' 1.656 + '(?P<date>[^\s]+)\s+' 1.657 + '(?P<time>[^\s]+)\s+' 1.658 + '(?P<filename>[^\s]+)$') 1.659 + return _GetFilesFromRecursiveLsOutput( 1.660 + path, self.RunShellCommand('ls -lR %s' % path), re_file, 1.661 + self._device_utc_offset) 1.662 + 1.663 + 1.664 + def SetJavaAssertsEnabled(self, enable): 1.665 + """Sets or removes the device java assertions property. 1.666 + 1.667 + Args: 1.668 + enable: If True the property will be set. 1.669 + 1.670 + Returns: 1.671 + True if the file was modified (reboot is required for it to take effect). 1.672 + """ 1.673 + # First ensure the desired property is persisted. 1.674 + temp_props_file = tempfile.NamedTemporaryFile() 1.675 + properties = '' 1.676 + if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name): 1.677 + properties = file(temp_props_file.name).read() 1.678 + re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + 1.679 + r'\s*=\s*all\s*$', re.MULTILINE) 1.680 + if enable != bool(re.search(re_search, properties)): 1.681 + re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + 1.682 + r'\s*=\s*\w+\s*$', re.MULTILINE) 1.683 + properties = re.sub(re_replace, '', properties) 1.684 + if enable: 1.685 + properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY 1.686 + 1.687 + file(temp_props_file.name, 'w').write(properties) 1.688 + self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH) 1.689 + 1.690 + # Next, check the current runtime value is what we need, and 1.691 + # if not, set it and report that a reboot is required. 1.692 + was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY) 1.693 + if was_set == enable: 1.694 + return False 1.695 + 1.696 + self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY, 1.697 + enable and 'all' or '')) 1.698 + return True 1.699 + 1.700 + def GetBuildId(self): 1.701 + """Returns the build ID of the system (e.g. JRM79C).""" 1.702 + build_id = self.RunShellCommand('getprop ro.build.id')[0] 1.703 + assert build_id 1.704 + return build_id 1.705 + 1.706 + def GetBuildType(self): 1.707 + """Returns the build type of the system (e.g. eng).""" 1.708 + build_type = self.RunShellCommand('getprop ro.build.type')[0] 1.709 + assert build_type 1.710 + return build_type 1.711 + 1.712 + def StartMonitoringLogcat(self, clear=True, timeout=10, logfile=None, 1.713 + filters=None): 1.714 + """Starts monitoring the output of logcat, for use with WaitForLogMatch. 1.715 + 1.716 + Args: 1.717 + clear: If True the existing logcat output will be cleared, to avoiding 1.718 + matching historical output lurking in the log. 1.719 + timeout: How long WaitForLogMatch will wait for the given match 1.720 + filters: A list of logcat filters to be used. 1.721 + """ 1.722 + if clear: 1.723 + self.RunShellCommand('logcat -c') 1.724 + args = [] 1.725 + if self._adb._target_arg: 1.726 + args += shlex.split(self._adb._target_arg) 1.727 + args += ['logcat', '-v', 'threadtime'] 1.728 + if filters: 1.729 + args.extend(filters) 1.730 + else: 1.731 + args.append('*:v') 1.732 + 1.733 + if logfile: 1.734 + logfile = NewLineNormalizer(logfile) 1.735 + 1.736 + # Spawn logcat and syncronize with it. 1.737 + for _ in range(4): 1.738 + self._logcat = pexpect.spawn('adb', args, timeout=timeout, 1.739 + logfile=logfile) 1.740 + self.RunShellCommand('log startup_sync') 1.741 + if self._logcat.expect(['startup_sync', pexpect.EOF, 1.742 + pexpect.TIMEOUT]) == 0: 1.743 + break 1.744 + self._logcat.close(force=True) 1.745 + else: 1.746 + logging.critical('Error reading from logcat: ' + str(self._logcat.match)) 1.747 + sys.exit(1) 1.748 + 1.749 + def GetMonitoredLogCat(self): 1.750 + """Returns an "adb logcat" command as created by pexpected.spawn.""" 1.751 + if not self._logcat: 1.752 + self.StartMonitoringLogcat(clear=False) 1.753 + return self._logcat 1.754 + 1.755 + def WaitForLogMatch(self, success_re, error_re, clear=False): 1.756 + """Blocks until a matching line is logged or a timeout occurs. 1.757 + 1.758 + Args: 1.759 + success_re: A compiled re to search each line for. 1.760 + error_re: A compiled re which, if found, terminates the search for 1.761 + |success_re|. If None is given, no error condition will be detected. 1.762 + clear: If True the existing logcat output will be cleared, defaults to 1.763 + false. 1.764 + 1.765 + Raises: 1.766 + pexpect.TIMEOUT upon the timeout specified by StartMonitoringLogcat(). 1.767 + 1.768 + Returns: 1.769 + The re match object if |success_re| is matched first or None if |error_re| 1.770 + is matched first. 1.771 + """ 1.772 + logging.info('<<< Waiting for logcat:' + str(success_re.pattern)) 1.773 + t0 = time.time() 1.774 + while True: 1.775 + if not self._logcat: 1.776 + self.StartMonitoringLogcat(clear) 1.777 + try: 1.778 + while True: 1.779 + # Note this will block for upto the timeout _per log line_, so we need 1.780 + # to calculate the overall timeout remaining since t0. 1.781 + time_remaining = t0 + self._logcat.timeout - time.time() 1.782 + if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) 1.783 + self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) 1.784 + line = self._logcat.match.group(1) 1.785 + if error_re: 1.786 + error_match = error_re.search(line) 1.787 + if error_match: 1.788 + return None 1.789 + success_match = success_re.search(line) 1.790 + if success_match: 1.791 + return success_match 1.792 + logging.info('<<< Skipped Logcat Line:' + str(line)) 1.793 + except pexpect.TIMEOUT: 1.794 + raise pexpect.TIMEOUT( 1.795 + 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' 1.796 + 'to debug)' % 1.797 + (self._logcat.timeout, success_re.pattern)) 1.798 + except pexpect.EOF: 1.799 + # It seems that sometimes logcat can end unexpectedly. This seems 1.800 + # to happen during Chrome startup after a reboot followed by a cache 1.801 + # clean. I don't understand why this happens, but this code deals with 1.802 + # getting EOF in logcat. 1.803 + logging.critical('Found EOF in adb logcat. Restarting...') 1.804 + # Rerun spawn with original arguments. Note that self._logcat.args[0] is 1.805 + # the path of adb, so we don't want it in the arguments. 1.806 + self._logcat = pexpect.spawn('adb', 1.807 + self._logcat.args[1:], 1.808 + timeout=self._logcat.timeout, 1.809 + logfile=self._logcat.logfile) 1.810 + 1.811 + def StartRecordingLogcat(self, clear=True, filters=['*:v']): 1.812 + """Starts recording logcat output to eventually be saved as a string. 1.813 + 1.814 + This call should come before some series of tests are run, with either 1.815 + StopRecordingLogcat or SearchLogcatRecord following the tests. 1.816 + 1.817 + Args: 1.818 + clear: True if existing log output should be cleared. 1.819 + filters: A list of logcat filters to be used. 1.820 + """ 1.821 + if clear: 1.822 + self._adb.SendCommand('logcat -c') 1.823 + logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg, 1.824 + ' '.join(filters)) 1.825 + self.logcat_process = subprocess.Popen(logcat_command, shell=True, 1.826 + stdout=subprocess.PIPE) 1.827 + 1.828 + def StopRecordingLogcat(self): 1.829 + """Stops an existing logcat recording subprocess and returns output. 1.830 + 1.831 + Returns: 1.832 + The logcat output as a string or an empty string if logcat was not 1.833 + being recorded at the time. 1.834 + """ 1.835 + if not self.logcat_process: 1.836 + return '' 1.837 + # Cannot evaluate directly as 0 is a possible value. 1.838 + # Better to read the self.logcat_process.stdout before killing it, 1.839 + # Otherwise the communicate may return incomplete output due to pipe break. 1.840 + if self.logcat_process.poll() is None: 1.841 + self.logcat_process.kill() 1.842 + (output, _) = self.logcat_process.communicate() 1.843 + self.logcat_process = None 1.844 + return output 1.845 + 1.846 + def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None, 1.847 + log_level=None, component=None): 1.848 + """Searches the specified logcat output and returns results. 1.849 + 1.850 + This method searches through the logcat output specified by record for a 1.851 + certain message, narrowing results by matching them against any other 1.852 + specified criteria. It returns all matching lines as described below. 1.853 + 1.854 + Args: 1.855 + record: A string generated by Start/StopRecordingLogcat to search. 1.856 + message: An output string to search for. 1.857 + thread_id: The thread id that is the origin of the message. 1.858 + proc_id: The process that is the origin of the message. 1.859 + log_level: The log level of the message. 1.860 + component: The name of the component that would create the message. 1.861 + 1.862 + Returns: 1.863 + A list of dictionaries represeting matching entries, each containing keys 1.864 + thread_id, proc_id, log_level, component, and message. 1.865 + """ 1.866 + if thread_id: 1.867 + thread_id = str(thread_id) 1.868 + if proc_id: 1.869 + proc_id = str(proc_id) 1.870 + results = [] 1.871 + reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$', 1.872 + re.MULTILINE) 1.873 + log_list = reg.findall(record) 1.874 + for (tid, pid, log_lev, comp, msg) in log_list: 1.875 + if ((not thread_id or thread_id == tid) and 1.876 + (not proc_id or proc_id == pid) and 1.877 + (not log_level or log_level == log_lev) and 1.878 + (not component or component == comp) and msg.find(message) > -1): 1.879 + match = dict({'thread_id': tid, 'proc_id': pid, 1.880 + 'log_level': log_lev, 'component': comp, 1.881 + 'message': msg}) 1.882 + results.append(match) 1.883 + return results 1.884 + 1.885 + def ExtractPid(self, process_name): 1.886 + """Extracts Process Ids for a given process name from Android Shell. 1.887 + 1.888 + Args: 1.889 + process_name: name of the process on the device. 1.890 + 1.891 + Returns: 1.892 + List of all the process ids (as strings) that match the given name. 1.893 + If the name of a process exactly matches the given name, the pid of 1.894 + that process will be inserted to the front of the pid list. 1.895 + """ 1.896 + pids = [] 1.897 + for line in self.RunShellCommand('ps', log_result=False): 1.898 + data = line.split() 1.899 + try: 1.900 + if process_name in data[-1]: # name is in the last column 1.901 + if process_name == data[-1]: 1.902 + pids.insert(0, data[1]) # PID is in the second column 1.903 + else: 1.904 + pids.append(data[1]) 1.905 + except IndexError: 1.906 + pass 1.907 + return pids 1.908 + 1.909 + def GetIoStats(self): 1.910 + """Gets cumulative disk IO stats since boot (for all processes). 1.911 + 1.912 + Returns: 1.913 + Dict of {num_reads, num_writes, read_ms, write_ms} or None if there 1.914 + was an error. 1.915 + """ 1.916 + for line in self.GetFileContents('/proc/diskstats', log_result=False): 1.917 + stats = io_stats_parser.ParseIoStatsLine(line) 1.918 + if stats.device == 'mmcblk0': 1.919 + return { 1.920 + 'num_reads': stats.num_reads_issued, 1.921 + 'num_writes': stats.num_writes_completed, 1.922 + 'read_ms': stats.ms_spent_reading, 1.923 + 'write_ms': stats.ms_spent_writing, 1.924 + } 1.925 + logging.warning('Could not find disk IO stats.') 1.926 + return None 1.927 + 1.928 + def GetMemoryUsageForPid(self, pid): 1.929 + """Returns the memory usage for given pid. 1.930 + 1.931 + Args: 1.932 + pid: The pid number of the specific process running on device. 1.933 + 1.934 + Returns: 1.935 + A tuple containg: 1.936 + [0]: Dict of {metric:usage_kb}, for the process which has specified pid. 1.937 + The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, 1.938 + Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, 1.939 + KernelPageSize, MMUPageSize, Nvidia (tablet only). 1.940 + [1]: Detailed /proc/[PID]/smaps information. 1.941 + """ 1.942 + usage_dict = collections.defaultdict(int) 1.943 + smaps = collections.defaultdict(dict) 1.944 + current_smap = '' 1.945 + for line in self.GetFileContents('/proc/%s/smaps' % pid, log_result=False): 1.946 + items = line.split() 1.947 + # See man 5 proc for more details. The format is: 1.948 + # address perms offset dev inode pathname 1.949 + if len(items) > 5: 1.950 + current_smap = ' '.join(items[5:]) 1.951 + elif len(items) > 3: 1.952 + current_smap = ' '.join(items[3:]) 1.953 + match = re.match(MEMORY_INFO_RE, line) 1.954 + if match: 1.955 + key = match.group('key') 1.956 + usage_kb = int(match.group('usage_kb')) 1.957 + usage_dict[key] += usage_kb 1.958 + if key not in smaps[current_smap]: 1.959 + smaps[current_smap][key] = 0 1.960 + smaps[current_smap][key] += usage_kb 1.961 + if not usage_dict or not any(usage_dict.values()): 1.962 + # Presumably the process died between ps and calling this method. 1.963 + logging.warning('Could not find memory usage for pid ' + str(pid)) 1.964 + 1.965 + for line in self.GetFileContents('/d/nvmap/generic-0/clients', 1.966 + log_result=False): 1.967 + match = re.match(NVIDIA_MEMORY_INFO_RE, line) 1.968 + if match and match.group('pid') == pid: 1.969 + usage_bytes = int(match.group('usage_bytes')) 1.970 + usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB 1.971 + break 1.972 + 1.973 + return (usage_dict, smaps) 1.974 + 1.975 + def GetMemoryUsageForPackage(self, package): 1.976 + """Returns the memory usage for all processes whose name contains |pacakge|. 1.977 + 1.978 + Args: 1.979 + package: A string holding process name to lookup pid list for. 1.980 + 1.981 + Returns: 1.982 + A tuple containg: 1.983 + [0]: Dict of {metric:usage_kb}, summed over all pids associated with 1.984 + |name|. 1.985 + The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, 1.986 + Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, 1.987 + KernelPageSize, MMUPageSize, Nvidia (tablet only). 1.988 + [1]: a list with detailed /proc/[PID]/smaps information. 1.989 + """ 1.990 + usage_dict = collections.defaultdict(int) 1.991 + pid_list = self.ExtractPid(package) 1.992 + smaps = collections.defaultdict(dict) 1.993 + 1.994 + for pid in pid_list: 1.995 + usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid) 1.996 + smaps[pid] = smaps_per_pid 1.997 + for (key, value) in usage_dict_per_pid.items(): 1.998 + usage_dict[key] += value 1.999 + 1.1000 + return usage_dict, smaps 1.1001 + 1.1002 + def ProcessesUsingDevicePort(self, device_port): 1.1003 + """Lists processes using the specified device port on loopback interface. 1.1004 + 1.1005 + Args: 1.1006 + device_port: Port on device we want to check. 1.1007 + 1.1008 + Returns: 1.1009 + A list of (pid, process_name) tuples using the specified port. 1.1010 + """ 1.1011 + tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False) 1.1012 + tcp_address = '0100007F:%04X' % device_port 1.1013 + pids = [] 1.1014 + for single_connect in tcp_results: 1.1015 + connect_results = single_connect.split() 1.1016 + # Column 1 is the TCP port, and Column 9 is the inode of the socket 1.1017 + if connect_results[1] == tcp_address: 1.1018 + socket_inode = connect_results[9] 1.1019 + socket_name = 'socket:[%s]' % socket_inode 1.1020 + lsof_results = self.RunShellCommand('lsof', log_result=False) 1.1021 + for single_process in lsof_results: 1.1022 + process_results = single_process.split() 1.1023 + # Ignore the line if it has less than nine columns in it, which may 1.1024 + # be the case when a process stops while lsof is executing. 1.1025 + if len(process_results) <= 8: 1.1026 + continue 1.1027 + # Column 0 is the executable name 1.1028 + # Column 1 is the pid 1.1029 + # Column 8 is the Inode in use 1.1030 + if process_results[8] == socket_name: 1.1031 + pids.append((int(process_results[1]), process_results[0])) 1.1032 + break 1.1033 + logging.info('PidsUsingDevicePort: %s', pids) 1.1034 + return pids 1.1035 + 1.1036 + def FileExistsOnDevice(self, file_name): 1.1037 + """Checks whether the given file exists on the device. 1.1038 + 1.1039 + Args: 1.1040 + file_name: Full path of file to check. 1.1041 + 1.1042 + Returns: 1.1043 + True if the file exists, False otherwise. 1.1044 + """ 1.1045 + assert '"' not in file_name, 'file_name cannot contain double quotes' 1.1046 + status = self._adb.SendShellCommand( 1.1047 + '\'test -e "%s"; echo $?\'' % (file_name)) 1.1048 + if 'test: not found' not in status: 1.1049 + return int(status) == 0 1.1050 + 1.1051 + status = self._adb.SendShellCommand( 1.1052 + '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name)) 1.1053 + return int(status) == 0 1.1054 + 1.1055 + 1.1056 +class NewLineNormalizer(object): 1.1057 + """A file-like object to normalize EOLs to '\n'. 1.1058 + 1.1059 + Pexpect runs adb within a pseudo-tty device (see 1.1060 + http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written 1.1061 + as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate 1.1062 + lines, the log ends up having '\r\r\n' at the end of each line. This 1.1063 + filter replaces the above with a single '\n' in the data stream. 1.1064 + """ 1.1065 + def __init__(self, output): 1.1066 + self._output = output 1.1067 + 1.1068 + def write(self, data): 1.1069 + data = data.replace('\r\r\n', '\n') 1.1070 + self._output.write(data) 1.1071 + 1.1072 + def flush(self): 1.1073 + self._output.flush() 1.1074 +