media/webrtc/trunk/build/android/pylib/android_commands.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
michael@0 2 # Use of this source code is governed by a BSD-style license that can be
michael@0 3 # found in the LICENSE file.
michael@0 4
michael@0 5 """Provides an interface to communicate with the device via the adb command.
michael@0 6
michael@0 7 Assumes adb binary is currently on system path.
michael@0 8 """
michael@0 9
michael@0 10 import collections
michael@0 11 import datetime
michael@0 12 import logging
michael@0 13 import os
michael@0 14 import re
michael@0 15 import shlex
michael@0 16 import subprocess
michael@0 17 import sys
michael@0 18 import tempfile
michael@0 19 import time
michael@0 20
michael@0 21 import io_stats_parser
michael@0 22 from pylib import pexpect
michael@0 23
michael@0 24 CHROME_SRC = os.path.join(
michael@0 25 os.path.abspath(os.path.dirname(__file__)), '..', '..', '..')
michael@0 26
michael@0 27 sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner'))
michael@0 28 import adb_interface
michael@0 29
michael@0 30 import cmd_helper
michael@0 31 import errors # is under ../../../third_party/android_testrunner/errors.py
michael@0 32
michael@0 33
michael@0 34 # Pattern to search for the next whole line of pexpect output and capture it
michael@0 35 # into a match group. We can't use ^ and $ for line start end with pexpect,
michael@0 36 # see http://www.noah.org/python/pexpect/#doc for explanation why.
michael@0 37 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
michael@0 38
michael@0 39 # Set the adb shell prompt to be a unique marker that will [hopefully] not
michael@0 40 # appear at the start of any line of a command's output.
michael@0 41 SHELL_PROMPT = '~+~PQ\x17RS~+~'
michael@0 42
michael@0 43 # Java properties file
michael@0 44 LOCAL_PROPERTIES_PATH = '/data/local.prop'
michael@0 45
michael@0 46 # Property in /data/local.prop that controls Java assertions.
michael@0 47 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
michael@0 48
michael@0 49 MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$')
michael@0 50 NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*'
michael@0 51 '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$')
michael@0 52
michael@0 53 # Keycode "enum" suitable for passing to AndroidCommands.SendKey().
michael@0 54 KEYCODE_HOME = 3
michael@0 55 KEYCODE_BACK = 4
michael@0 56 KEYCODE_DPAD_UP = 19
michael@0 57 KEYCODE_DPAD_DOWN = 20
michael@0 58 KEYCODE_DPAD_RIGHT = 22
michael@0 59 KEYCODE_ENTER = 66
michael@0 60 KEYCODE_MENU = 82
michael@0 61
michael@0 62 MD5SUM_DEVICE_PATH = '/data/local/tmp/md5sum_bin'
michael@0 63
michael@0 64 def GetEmulators():
michael@0 65 """Returns a list of emulators. Does not filter by status (e.g. offline).
michael@0 66
michael@0 67 Both devices starting with 'emulator' will be returned in below output:
michael@0 68
michael@0 69 * daemon not running. starting it now on port 5037 *
michael@0 70 * daemon started successfully *
michael@0 71 List of devices attached
michael@0 72 027c10494100b4d7 device
michael@0 73 emulator-5554 offline
michael@0 74 emulator-5558 device
michael@0 75 """
michael@0 76 re_device = re.compile('^emulator-[0-9]+', re.MULTILINE)
michael@0 77 devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices']))
michael@0 78 return devices
michael@0 79
michael@0 80
michael@0 81 def GetAVDs():
michael@0 82 """Returns a list of AVDs."""
michael@0 83 re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
michael@0 84 avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
michael@0 85 return avds
michael@0 86
michael@0 87
michael@0 88 def GetAttachedDevices():
michael@0 89 """Returns a list of attached, online android devices.
michael@0 90
michael@0 91 If a preferred device has been set with ANDROID_SERIAL, it will be first in
michael@0 92 the returned list.
michael@0 93
michael@0 94 Example output:
michael@0 95
michael@0 96 * daemon not running. starting it now on port 5037 *
michael@0 97 * daemon started successfully *
michael@0 98 List of devices attached
michael@0 99 027c10494100b4d7 device
michael@0 100 emulator-5554 offline
michael@0 101 """
michael@0 102 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
michael@0 103 devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices']))
michael@0 104 preferred_device = os.environ.get('ANDROID_SERIAL')
michael@0 105 if preferred_device in devices:
michael@0 106 devices.remove(preferred_device)
michael@0 107 devices.insert(0, preferred_device)
michael@0 108 return devices
michael@0 109
michael@0 110 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
michael@0 111 """Gets a list of files from `ls` command output.
michael@0 112
michael@0 113 Python's os.walk isn't used because it doesn't work over adb shell.
michael@0 114
michael@0 115 Args:
michael@0 116 path: The path to list.
michael@0 117 ls_output: A list of lines returned by an `ls -lR` command.
michael@0 118 re_file: A compiled regular expression which parses a line into named groups
michael@0 119 consisting of at minimum "filename", "date", "time", "size" and
michael@0 120 optionally "timezone".
michael@0 121 utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
michael@0 122 2-digit string giving the number of UTC offset hours, and MM is a
michael@0 123 2-digit string giving the number of UTC offset minutes. If the input
michael@0 124 utc_offset is None, will try to look for the value of "timezone" if it
michael@0 125 is specified in re_file.
michael@0 126
michael@0 127 Returns:
michael@0 128 A dict of {"name": (size, lastmod), ...} where:
michael@0 129 name: The file name relative to |path|'s directory.
michael@0 130 size: The file size in bytes (0 for directories).
michael@0 131 lastmod: The file last modification date in UTC.
michael@0 132 """
michael@0 133 re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
michael@0 134 path_dir = os.path.dirname(path)
michael@0 135
michael@0 136 current_dir = ''
michael@0 137 files = {}
michael@0 138 for line in ls_output:
michael@0 139 directory_match = re_directory.match(line)
michael@0 140 if directory_match:
michael@0 141 current_dir = directory_match.group('dir')
michael@0 142 continue
michael@0 143 file_match = re_file.match(line)
michael@0 144 if file_match:
michael@0 145 filename = os.path.join(current_dir, file_match.group('filename'))
michael@0 146 if filename.startswith(path_dir):
michael@0 147 filename = filename[len(path_dir)+1:]
michael@0 148 lastmod = datetime.datetime.strptime(
michael@0 149 file_match.group('date') + ' ' + file_match.group('time')[:5],
michael@0 150 '%Y-%m-%d %H:%M')
michael@0 151 if not utc_offset and 'timezone' in re_file.groupindex:
michael@0 152 utc_offset = file_match.group('timezone')
michael@0 153 if isinstance(utc_offset, str) and len(utc_offset) == 5:
michael@0 154 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
michael@0 155 minutes=int(utc_offset[3:5]))
michael@0 156 if utc_offset[0:1] == '-':
michael@0 157 utc_delta = -utc_delta
michael@0 158 lastmod -= utc_delta
michael@0 159 files[filename] = (int(file_match.group('size')), lastmod)
michael@0 160 return files
michael@0 161
michael@0 162 def _ComputeFileListHash(md5sum_output):
michael@0 163 """Returns a list of MD5 strings from the provided md5sum output."""
michael@0 164 return [line.split(' ')[0] for line in md5sum_output]
michael@0 165
michael@0 166 def _HasAdbPushSucceeded(command_output):
michael@0 167 """Returns whether adb push has succeeded from the provided output."""
michael@0 168 if not command_output:
michael@0 169 return False
michael@0 170 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
michael@0 171 # Errors look like this: "failed to copy ... "
michael@0 172 if not re.search('^[0-9]', command_output.splitlines()[-1]):
michael@0 173 logging.critical('PUSH FAILED: ' + command_output)
michael@0 174 return False
michael@0 175 return True
michael@0 176
michael@0 177 def GetLogTimestamp(log_line, year):
michael@0 178 """Returns the timestamp of the given |log_line| in the given year."""
michael@0 179 try:
michael@0 180 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
michael@0 181 '%Y-%m-%d %H:%M:%S.%f')
michael@0 182 except (ValueError, IndexError):
michael@0 183 logging.critical('Error reading timestamp from ' + log_line)
michael@0 184 return None
michael@0 185
michael@0 186
michael@0 187 class AndroidCommands(object):
michael@0 188 """Helper class for communicating with Android device via adb.
michael@0 189
michael@0 190 Args:
michael@0 191 device: If given, adb commands are only send to the device of this ID.
michael@0 192 Otherwise commands are sent to all attached devices.
michael@0 193 """
michael@0 194
michael@0 195 def __init__(self, device=None):
michael@0 196 self._adb = adb_interface.AdbInterface()
michael@0 197 if device:
michael@0 198 self._adb.SetTargetSerial(device)
michael@0 199 self._logcat = None
michael@0 200 self.logcat_process = None
michael@0 201 self._pushed_files = []
michael@0 202 self._device_utc_offset = self.RunShellCommand('date +%z')[0]
michael@0 203 self._md5sum_path = ''
michael@0 204 self._external_storage = ''
michael@0 205
michael@0 206 def Adb(self):
michael@0 207 """Returns our AdbInterface to avoid us wrapping all its methods."""
michael@0 208 return self._adb
michael@0 209
michael@0 210 def IsRootEnabled(self):
michael@0 211 """Checks if root is enabled on the device."""
michael@0 212 root_test_output = self.RunShellCommand('ls /root') or ['']
michael@0 213 return not 'Permission denied' in root_test_output[0]
michael@0 214
michael@0 215 def EnableAdbRoot(self):
michael@0 216 """Enables adb root on the device.
michael@0 217
michael@0 218 Returns:
michael@0 219 True: if output from executing adb root was as expected.
michael@0 220 False: otherwise.
michael@0 221 """
michael@0 222 return_value = self._adb.EnableAdbRoot()
michael@0 223 # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
michael@0 224 # output matches what is expected. Just to be safe add a call to
michael@0 225 # wait-for-device.
michael@0 226 self._adb.SendCommand('wait-for-device')
michael@0 227 return return_value
michael@0 228
michael@0 229 def GetDeviceYear(self):
michael@0 230 """Returns the year information of the date on device."""
michael@0 231 return self.RunShellCommand('date +%Y')[0]
michael@0 232
michael@0 233 def GetExternalStorage(self):
michael@0 234 if not self._external_storage:
michael@0 235 self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
michael@0 236 assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE'
michael@0 237 return self._external_storage
michael@0 238
michael@0 239 def WaitForDevicePm(self):
michael@0 240 """Blocks until the device's package manager is available.
michael@0 241
michael@0 242 To workaround http://b/5201039, we restart the shell and retry if the
michael@0 243 package manager isn't back after 120 seconds.
michael@0 244
michael@0 245 Raises:
michael@0 246 errors.WaitForResponseTimedOutError after max retries reached.
michael@0 247 """
michael@0 248 last_err = None
michael@0 249 retries = 3
michael@0 250 while retries:
michael@0 251 try:
michael@0 252 self._adb.WaitForDevicePm()
michael@0 253 return # Success
michael@0 254 except errors.WaitForResponseTimedOutError as e:
michael@0 255 last_err = e
michael@0 256 logging.warning('Restarting and retrying after timeout: %s', e)
michael@0 257 retries -= 1
michael@0 258 self.RestartShell()
michael@0 259 raise last_err # Only reached after max retries, re-raise the last error.
michael@0 260
michael@0 261 def RestartShell(self):
michael@0 262 """Restarts the shell on the device. Does not block for it to return."""
michael@0 263 self.RunShellCommand('stop')
michael@0 264 self.RunShellCommand('start')
michael@0 265
michael@0 266 def Reboot(self, full_reboot=True):
michael@0 267 """Reboots the device and waits for the package manager to return.
michael@0 268
michael@0 269 Args:
michael@0 270 full_reboot: Whether to fully reboot the device or just restart the shell.
michael@0 271 """
michael@0 272 # TODO(torne): hive can't reboot the device either way without breaking the
michael@0 273 # connection; work out if we can handle this better
michael@0 274 if os.environ.get('USING_HIVE'):
michael@0 275 logging.warning('Ignoring reboot request as we are on hive')
michael@0 276 return
michael@0 277 if full_reboot or not self.IsRootEnabled():
michael@0 278 self._adb.SendCommand('reboot')
michael@0 279 timeout = 300
michael@0 280 else:
michael@0 281 self.RestartShell()
michael@0 282 timeout = 120
michael@0 283 # To run tests we need at least the package manager and the sd card (or
michael@0 284 # other external storage) to be ready.
michael@0 285 self.WaitForDevicePm()
michael@0 286 self.WaitForSdCardReady(timeout)
michael@0 287
michael@0 288 def Uninstall(self, package):
michael@0 289 """Uninstalls the specified package from the device.
michael@0 290
michael@0 291 Args:
michael@0 292 package: Name of the package to remove.
michael@0 293
michael@0 294 Returns:
michael@0 295 A status string returned by adb uninstall
michael@0 296 """
michael@0 297 uninstall_command = 'uninstall %s' % package
michael@0 298
michael@0 299 logging.info('>>> $' + uninstall_command)
michael@0 300 return self._adb.SendCommand(uninstall_command, timeout_time=60)
michael@0 301
michael@0 302 def Install(self, package_file_path, reinstall=False):
michael@0 303 """Installs the specified package to the device.
michael@0 304
michael@0 305 Args:
michael@0 306 package_file_path: Path to .apk file to install.
michael@0 307 reinstall: Reinstall an existing apk, keeping the data.
michael@0 308
michael@0 309 Returns:
michael@0 310 A status string returned by adb install
michael@0 311 """
michael@0 312 assert os.path.isfile(package_file_path), ('<%s> is not file' %
michael@0 313 package_file_path)
michael@0 314
michael@0 315 install_cmd = ['install']
michael@0 316
michael@0 317 if reinstall:
michael@0 318 install_cmd.append('-r')
michael@0 319
michael@0 320 install_cmd.append(package_file_path)
michael@0 321 install_cmd = ' '.join(install_cmd)
michael@0 322
michael@0 323 logging.info('>>> $' + install_cmd)
michael@0 324 return self._adb.SendCommand(install_cmd, timeout_time=2*60, retry_count=0)
michael@0 325
michael@0 326 def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
michael@0 327 reboots_on_failure=2):
michael@0 328 """Installs specified package and reboots device on timeouts.
michael@0 329
michael@0 330 Args:
michael@0 331 apk_path: Path to .apk file to install.
michael@0 332 keep_data: Reinstalls instead of uninstalling first, preserving the
michael@0 333 application data.
michael@0 334 package_name: Package name (only needed if keep_data=False).
michael@0 335 reboots_on_failure: number of time to reboot if package manager is frozen.
michael@0 336
michael@0 337 Returns:
michael@0 338 A status string returned by adb install
michael@0 339 """
michael@0 340 reboots_left = reboots_on_failure
michael@0 341 while True:
michael@0 342 try:
michael@0 343 if not keep_data:
michael@0 344 assert package_name
michael@0 345 self.Uninstall(package_name)
michael@0 346 install_status = self.Install(apk_path, reinstall=keep_data)
michael@0 347 if 'Success' in install_status:
michael@0 348 return install_status
michael@0 349 except errors.WaitForResponseTimedOutError:
michael@0 350 print '@@@STEP_WARNINGS@@@'
michael@0 351 logging.info('Timeout on installing %s' % apk_path)
michael@0 352
michael@0 353 if reboots_left <= 0:
michael@0 354 raise Exception('Install failure')
michael@0 355
michael@0 356 # Force a hard reboot on last attempt
michael@0 357 self.Reboot(full_reboot=(reboots_left == 1))
michael@0 358 reboots_left -= 1
michael@0 359
michael@0 360 def MakeSystemFolderWritable(self):
michael@0 361 """Remounts the /system folder rw."""
michael@0 362 out = self._adb.SendCommand('remount')
michael@0 363 if out.strip() != 'remount succeeded':
michael@0 364 raise errors.MsgException('Remount failed: %s' % out)
michael@0 365
michael@0 366 def RestartAdbServer(self):
michael@0 367 """Restart the adb server."""
michael@0 368 self.KillAdbServer()
michael@0 369 self.StartAdbServer()
michael@0 370
michael@0 371 def KillAdbServer(self):
michael@0 372 """Kill adb server."""
michael@0 373 adb_cmd = ['adb', 'kill-server']
michael@0 374 return cmd_helper.RunCmd(adb_cmd)
michael@0 375
michael@0 376 def StartAdbServer(self):
michael@0 377 """Start adb server."""
michael@0 378 adb_cmd = ['adb', 'start-server']
michael@0 379 return cmd_helper.RunCmd(adb_cmd)
michael@0 380
michael@0 381 def WaitForSystemBootCompleted(self, wait_time):
michael@0 382 """Waits for targeted system's boot_completed flag to be set.
michael@0 383
michael@0 384 Args:
michael@0 385 wait_time: time in seconds to wait
michael@0 386
michael@0 387 Raises:
michael@0 388 WaitForResponseTimedOutError if wait_time elapses and flag still not
michael@0 389 set.
michael@0 390 """
michael@0 391 logging.info('Waiting for system boot completed...')
michael@0 392 self._adb.SendCommand('wait-for-device')
michael@0 393 # Now the device is there, but system not boot completed.
michael@0 394 # Query the sys.boot_completed flag with a basic command
michael@0 395 boot_completed = False
michael@0 396 attempts = 0
michael@0 397 wait_period = 5
michael@0 398 while not boot_completed and (attempts * wait_period) < wait_time:
michael@0 399 output = self._adb.SendShellCommand('getprop sys.boot_completed',
michael@0 400 retry_count=1)
michael@0 401 output = output.strip()
michael@0 402 if output == '1':
michael@0 403 boot_completed = True
michael@0 404 else:
michael@0 405 # If 'error: xxx' returned when querying the flag, it means
michael@0 406 # adb server lost the connection to the emulator, so restart the adb
michael@0 407 # server.
michael@0 408 if 'error:' in output:
michael@0 409 self.RestartAdbServer()
michael@0 410 time.sleep(wait_period)
michael@0 411 attempts += 1
michael@0 412 if not boot_completed:
michael@0 413 raise errors.WaitForResponseTimedOutError(
michael@0 414 'sys.boot_completed flag was not set after %s seconds' % wait_time)
michael@0 415
michael@0 416 def WaitForSdCardReady(self, timeout_time):
michael@0 417 """Wait for the SD card ready before pushing data into it."""
michael@0 418 logging.info('Waiting for SD card ready...')
michael@0 419 sdcard_ready = False
michael@0 420 attempts = 0
michael@0 421 wait_period = 5
michael@0 422 external_storage = self.GetExternalStorage()
michael@0 423 while not sdcard_ready and attempts * wait_period < timeout_time:
michael@0 424 output = self.RunShellCommand('ls ' + external_storage)
michael@0 425 if output:
michael@0 426 sdcard_ready = True
michael@0 427 else:
michael@0 428 time.sleep(wait_period)
michael@0 429 attempts += 1
michael@0 430 if not sdcard_ready:
michael@0 431 raise errors.WaitForResponseTimedOutError(
michael@0 432 'SD card not ready after %s seconds' % timeout_time)
michael@0 433
michael@0 434 # It is tempting to turn this function into a generator, however this is not
michael@0 435 # possible without using a private (local) adb_shell instance (to ensure no
michael@0 436 # other command interleaves usage of it), which would defeat the main aim of
michael@0 437 # being able to reuse the adb shell instance across commands.
michael@0 438 def RunShellCommand(self, command, timeout_time=20, log_result=False):
michael@0 439 """Send a command to the adb shell and return the result.
michael@0 440
michael@0 441 Args:
michael@0 442 command: String containing the shell command to send. Must not include
michael@0 443 the single quotes as we use them to escape the whole command.
michael@0 444 timeout_time: Number of seconds to wait for command to respond before
michael@0 445 retrying, used by AdbInterface.SendShellCommand.
michael@0 446 log_result: Boolean to indicate whether we should log the result of the
michael@0 447 shell command.
michael@0 448
michael@0 449 Returns:
michael@0 450 list containing the lines of output received from running the command
michael@0 451 """
michael@0 452 logging.info('>>> $' + command)
michael@0 453 if "'" in command: logging.warning(command + " contains ' quotes")
michael@0 454 result = self._adb.SendShellCommand(
michael@0 455 "'%s'" % command, timeout_time).splitlines()
michael@0 456 if ['error: device not found'] == result:
michael@0 457 raise errors.DeviceUnresponsiveError('device not found')
michael@0 458 if log_result:
michael@0 459 logging.info('\n>>> '.join(result))
michael@0 460 return result
michael@0 461
michael@0 462 def KillAll(self, process):
michael@0 463 """Android version of killall, connected via adb.
michael@0 464
michael@0 465 Args:
michael@0 466 process: name of the process to kill off
michael@0 467
michael@0 468 Returns:
michael@0 469 the number of processes killed
michael@0 470 """
michael@0 471 pids = self.ExtractPid(process)
michael@0 472 if pids:
michael@0 473 self.RunShellCommand('kill ' + ' '.join(pids))
michael@0 474 return len(pids)
michael@0 475
michael@0 476 def KillAllBlocking(self, process, timeout_sec):
michael@0 477 """Blocking version of killall, connected via adb.
michael@0 478
michael@0 479 This waits until no process matching the corresponding name appears in ps'
michael@0 480 output anymore.
michael@0 481
michael@0 482 Args:
michael@0 483 process: name of the process to kill off
michael@0 484 timeout_sec: the timeout in seconds
michael@0 485
michael@0 486 Returns:
michael@0 487 the number of processes killed
michael@0 488 """
michael@0 489 processes_killed = self.KillAll(process)
michael@0 490 if processes_killed:
michael@0 491 elapsed = 0
michael@0 492 wait_period = 0.1
michael@0 493 # Note that this doesn't take into account the time spent in ExtractPid().
michael@0 494 while self.ExtractPid(process) and elapsed < timeout_sec:
michael@0 495 time.sleep(wait_period)
michael@0 496 elapsed += wait_period
michael@0 497 if elapsed >= timeout_sec:
michael@0 498 return 0
michael@0 499 return processes_killed
michael@0 500
michael@0 501 def StartActivity(self, package, activity, wait_for_completion=False,
michael@0 502 action='android.intent.action.VIEW',
michael@0 503 category=None, data=None,
michael@0 504 extras=None, trace_file_name=None):
michael@0 505 """Starts |package|'s activity on the device.
michael@0 506
michael@0 507 Args:
michael@0 508 package: Name of package to start (e.g. 'com.google.android.apps.chrome').
michael@0 509 activity: Name of activity (e.g. '.Main' or
michael@0 510 'com.google.android.apps.chrome.Main').
michael@0 511 wait_for_completion: wait for the activity to finish launching (-W flag).
michael@0 512 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
michael@0 513 category: string (e.g. "android.intent.category.HOME")
michael@0 514 data: Data string to pass to activity (e.g. 'http://www.example.com/').
michael@0 515 extras: Dict of extras to pass to activity. Values are significant.
michael@0 516 trace_file_name: If used, turns on and saves the trace to this file name.
michael@0 517 """
michael@0 518 cmd = 'am start -a %s' % action
michael@0 519 if wait_for_completion:
michael@0 520 cmd += ' -W'
michael@0 521 if category:
michael@0 522 cmd += ' -c %s' % category
michael@0 523 if package and activity:
michael@0 524 cmd += ' -n %s/%s' % (package, activity)
michael@0 525 if data:
michael@0 526 cmd += ' -d "%s"' % data
michael@0 527 if extras:
michael@0 528 for key in extras:
michael@0 529 value = extras[key]
michael@0 530 if isinstance(value, str):
michael@0 531 cmd += ' --es'
michael@0 532 elif isinstance(value, bool):
michael@0 533 cmd += ' --ez'
michael@0 534 elif isinstance(value, int):
michael@0 535 cmd += ' --ei'
michael@0 536 else:
michael@0 537 raise NotImplementedError(
michael@0 538 'Need to teach StartActivity how to pass %s extras' % type(value))
michael@0 539 cmd += ' %s %s' % (key, value)
michael@0 540 if trace_file_name:
michael@0 541 cmd += ' --start-profiler ' + trace_file_name
michael@0 542 self.RunShellCommand(cmd)
michael@0 543
michael@0 544 def GoHome(self):
michael@0 545 """Tell the device to return to the home screen. Blocks until completion."""
michael@0 546 self.RunShellCommand('am start -W '
michael@0 547 '-a android.intent.action.MAIN -c android.intent.category.HOME')
michael@0 548
michael@0 549 def CloseApplication(self, package):
michael@0 550 """Attempt to close down the application, using increasing violence.
michael@0 551
michael@0 552 Args:
michael@0 553 package: Name of the process to kill off, e.g.
michael@0 554 com.google.android.apps.chrome
michael@0 555 """
michael@0 556 self.RunShellCommand('am force-stop ' + package)
michael@0 557
michael@0 558 def ClearApplicationState(self, package):
michael@0 559 """Closes and clears all state for the given |package|."""
michael@0 560 self.CloseApplication(package)
michael@0 561 self.RunShellCommand('rm -r /data/data/%s/app_*' % package)
michael@0 562 self.RunShellCommand('rm -r /data/data/%s/cache/*' % package)
michael@0 563 self.RunShellCommand('rm -r /data/data/%s/files/*' % package)
michael@0 564 self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package)
michael@0 565
michael@0 566 def SendKeyEvent(self, keycode):
michael@0 567 """Sends keycode to the device.
michael@0 568
michael@0 569 Args:
michael@0 570 keycode: Numeric keycode to send (see "enum" at top of file).
michael@0 571 """
michael@0 572 self.RunShellCommand('input keyevent %d' % keycode)
michael@0 573
michael@0 574 def PushIfNeeded(self, local_path, device_path):
michael@0 575 """Pushes |local_path| to |device_path|.
michael@0 576
michael@0 577 Works for files and directories. This method skips copying any paths in
michael@0 578 |test_data_paths| that already exist on the device with the same hash.
michael@0 579
michael@0 580 All pushed files can be removed by calling RemovePushedFiles().
michael@0 581 """
michael@0 582 assert os.path.exists(local_path), 'Local path not found %s' % local_path
michael@0 583
michael@0 584 if not self._md5sum_path:
michael@0 585 default_build_type = os.environ.get('BUILD_TYPE', 'Debug')
michael@0 586 md5sum_path = '%s/out/%s/md5sum_bin' % (CHROME_SRC, default_build_type)
michael@0 587 if not os.path.exists(md5sum_path):
michael@0 588 md5sum_path = '%s/out/Release/md5sum_bin' % (CHROME_SRC)
michael@0 589 if not os.path.exists(md5sum_path):
michael@0 590 print >> sys.stderr, 'Please build md5sum.'
michael@0 591 sys.exit(1)
michael@0 592 command = 'push %s %s' % (md5sum_path, MD5SUM_DEVICE_PATH)
michael@0 593 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
michael@0 594 self._md5sum_path = md5sum_path
michael@0 595
michael@0 596 self._pushed_files.append(device_path)
michael@0 597 hashes_on_device = _ComputeFileListHash(
michael@0 598 self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path))
michael@0 599 assert os.path.exists(local_path), 'Local path not found %s' % local_path
michael@0 600 hashes_on_host = _ComputeFileListHash(
michael@0 601 subprocess.Popen(
michael@0 602 '%s_host %s' % (self._md5sum_path, local_path),
michael@0 603 stdout=subprocess.PIPE, shell=True).stdout)
michael@0 604 if hashes_on_device == hashes_on_host:
michael@0 605 return
michael@0 606
michael@0 607 # They don't match, so remove everything first and then create it.
michael@0 608 if os.path.isdir(local_path):
michael@0 609 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60)
michael@0 610 self.RunShellCommand('mkdir -p %s' % device_path)
michael@0 611
michael@0 612 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of
michael@0 613 # 60 seconds which isn't sufficient for a lot of users of this method.
michael@0 614 push_command = 'push %s %s' % (local_path, device_path)
michael@0 615 logging.info('>>> $' + push_command)
michael@0 616 output = self._adb.SendCommand(push_command, timeout_time=30*60)
michael@0 617 assert _HasAdbPushSucceeded(output)
michael@0 618
michael@0 619
michael@0 620 def GetFileContents(self, filename, log_result=False):
michael@0 621 """Gets contents from the file specified by |filename|."""
michael@0 622 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' +
michael@0 623 filename + '"; fi', log_result=log_result)
michael@0 624
michael@0 625 def SetFileContents(self, filename, contents):
michael@0 626 """Writes |contents| to the file specified by |filename|."""
michael@0 627 with tempfile.NamedTemporaryFile() as f:
michael@0 628 f.write(contents)
michael@0 629 f.flush()
michael@0 630 self._adb.Push(f.name, filename)
michael@0 631
michael@0 632 def RemovePushedFiles(self):
michael@0 633 """Removes all files pushed with PushIfNeeded() from the device."""
michael@0 634 for p in self._pushed_files:
michael@0 635 self.RunShellCommand('rm -r %s' % p, timeout_time=2*60)
michael@0 636
michael@0 637 def ListPathContents(self, path):
michael@0 638 """Lists files in all subdirectories of |path|.
michael@0 639
michael@0 640 Args:
michael@0 641 path: The path to list.
michael@0 642
michael@0 643 Returns:
michael@0 644 A dict of {"name": (size, lastmod), ...}.
michael@0 645 """
michael@0 646 # Example output:
michael@0 647 # /foo/bar:
michael@0 648 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt
michael@0 649 re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
michael@0 650 '(?P<user>[^\s]+)\s+'
michael@0 651 '(?P<group>[^\s]+)\s+'
michael@0 652 '(?P<size>[^\s]+)\s+'
michael@0 653 '(?P<date>[^\s]+)\s+'
michael@0 654 '(?P<time>[^\s]+)\s+'
michael@0 655 '(?P<filename>[^\s]+)$')
michael@0 656 return _GetFilesFromRecursiveLsOutput(
michael@0 657 path, self.RunShellCommand('ls -lR %s' % path), re_file,
michael@0 658 self._device_utc_offset)
michael@0 659
michael@0 660
michael@0 661 def SetJavaAssertsEnabled(self, enable):
michael@0 662 """Sets or removes the device java assertions property.
michael@0 663
michael@0 664 Args:
michael@0 665 enable: If True the property will be set.
michael@0 666
michael@0 667 Returns:
michael@0 668 True if the file was modified (reboot is required for it to take effect).
michael@0 669 """
michael@0 670 # First ensure the desired property is persisted.
michael@0 671 temp_props_file = tempfile.NamedTemporaryFile()
michael@0 672 properties = ''
michael@0 673 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
michael@0 674 properties = file(temp_props_file.name).read()
michael@0 675 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
michael@0 676 r'\s*=\s*all\s*$', re.MULTILINE)
michael@0 677 if enable != bool(re.search(re_search, properties)):
michael@0 678 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
michael@0 679 r'\s*=\s*\w+\s*$', re.MULTILINE)
michael@0 680 properties = re.sub(re_replace, '', properties)
michael@0 681 if enable:
michael@0 682 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
michael@0 683
michael@0 684 file(temp_props_file.name, 'w').write(properties)
michael@0 685 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
michael@0 686
michael@0 687 # Next, check the current runtime value is what we need, and
michael@0 688 # if not, set it and report that a reboot is required.
michael@0 689 was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY)
michael@0 690 if was_set == enable:
michael@0 691 return False
michael@0 692
michael@0 693 self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY,
michael@0 694 enable and 'all' or ''))
michael@0 695 return True
michael@0 696
michael@0 697 def GetBuildId(self):
michael@0 698 """Returns the build ID of the system (e.g. JRM79C)."""
michael@0 699 build_id = self.RunShellCommand('getprop ro.build.id')[0]
michael@0 700 assert build_id
michael@0 701 return build_id
michael@0 702
michael@0 703 def GetBuildType(self):
michael@0 704 """Returns the build type of the system (e.g. eng)."""
michael@0 705 build_type = self.RunShellCommand('getprop ro.build.type')[0]
michael@0 706 assert build_type
michael@0 707 return build_type
michael@0 708
michael@0 709 def StartMonitoringLogcat(self, clear=True, timeout=10, logfile=None,
michael@0 710 filters=None):
michael@0 711 """Starts monitoring the output of logcat, for use with WaitForLogMatch.
michael@0 712
michael@0 713 Args:
michael@0 714 clear: If True the existing logcat output will be cleared, to avoiding
michael@0 715 matching historical output lurking in the log.
michael@0 716 timeout: How long WaitForLogMatch will wait for the given match
michael@0 717 filters: A list of logcat filters to be used.
michael@0 718 """
michael@0 719 if clear:
michael@0 720 self.RunShellCommand('logcat -c')
michael@0 721 args = []
michael@0 722 if self._adb._target_arg:
michael@0 723 args += shlex.split(self._adb._target_arg)
michael@0 724 args += ['logcat', '-v', 'threadtime']
michael@0 725 if filters:
michael@0 726 args.extend(filters)
michael@0 727 else:
michael@0 728 args.append('*:v')
michael@0 729
michael@0 730 if logfile:
michael@0 731 logfile = NewLineNormalizer(logfile)
michael@0 732
michael@0 733 # Spawn logcat and syncronize with it.
michael@0 734 for _ in range(4):
michael@0 735 self._logcat = pexpect.spawn('adb', args, timeout=timeout,
michael@0 736 logfile=logfile)
michael@0 737 self.RunShellCommand('log startup_sync')
michael@0 738 if self._logcat.expect(['startup_sync', pexpect.EOF,
michael@0 739 pexpect.TIMEOUT]) == 0:
michael@0 740 break
michael@0 741 self._logcat.close(force=True)
michael@0 742 else:
michael@0 743 logging.critical('Error reading from logcat: ' + str(self._logcat.match))
michael@0 744 sys.exit(1)
michael@0 745
michael@0 746 def GetMonitoredLogCat(self):
michael@0 747 """Returns an "adb logcat" command as created by pexpected.spawn."""
michael@0 748 if not self._logcat:
michael@0 749 self.StartMonitoringLogcat(clear=False)
michael@0 750 return self._logcat
michael@0 751
michael@0 752 def WaitForLogMatch(self, success_re, error_re, clear=False):
michael@0 753 """Blocks until a matching line is logged or a timeout occurs.
michael@0 754
michael@0 755 Args:
michael@0 756 success_re: A compiled re to search each line for.
michael@0 757 error_re: A compiled re which, if found, terminates the search for
michael@0 758 |success_re|. If None is given, no error condition will be detected.
michael@0 759 clear: If True the existing logcat output will be cleared, defaults to
michael@0 760 false.
michael@0 761
michael@0 762 Raises:
michael@0 763 pexpect.TIMEOUT upon the timeout specified by StartMonitoringLogcat().
michael@0 764
michael@0 765 Returns:
michael@0 766 The re match object if |success_re| is matched first or None if |error_re|
michael@0 767 is matched first.
michael@0 768 """
michael@0 769 logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
michael@0 770 t0 = time.time()
michael@0 771 while True:
michael@0 772 if not self._logcat:
michael@0 773 self.StartMonitoringLogcat(clear)
michael@0 774 try:
michael@0 775 while True:
michael@0 776 # Note this will block for upto the timeout _per log line_, so we need
michael@0 777 # to calculate the overall timeout remaining since t0.
michael@0 778 time_remaining = t0 + self._logcat.timeout - time.time()
michael@0 779 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat)
michael@0 780 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
michael@0 781 line = self._logcat.match.group(1)
michael@0 782 if error_re:
michael@0 783 error_match = error_re.search(line)
michael@0 784 if error_match:
michael@0 785 return None
michael@0 786 success_match = success_re.search(line)
michael@0 787 if success_match:
michael@0 788 return success_match
michael@0 789 logging.info('<<< Skipped Logcat Line:' + str(line))
michael@0 790 except pexpect.TIMEOUT:
michael@0 791 raise pexpect.TIMEOUT(
michael@0 792 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
michael@0 793 'to debug)' %
michael@0 794 (self._logcat.timeout, success_re.pattern))
michael@0 795 except pexpect.EOF:
michael@0 796 # It seems that sometimes logcat can end unexpectedly. This seems
michael@0 797 # to happen during Chrome startup after a reboot followed by a cache
michael@0 798 # clean. I don't understand why this happens, but this code deals with
michael@0 799 # getting EOF in logcat.
michael@0 800 logging.critical('Found EOF in adb logcat. Restarting...')
michael@0 801 # Rerun spawn with original arguments. Note that self._logcat.args[0] is
michael@0 802 # the path of adb, so we don't want it in the arguments.
michael@0 803 self._logcat = pexpect.spawn('adb',
michael@0 804 self._logcat.args[1:],
michael@0 805 timeout=self._logcat.timeout,
michael@0 806 logfile=self._logcat.logfile)
michael@0 807
michael@0 808 def StartRecordingLogcat(self, clear=True, filters=['*:v']):
michael@0 809 """Starts recording logcat output to eventually be saved as a string.
michael@0 810
michael@0 811 This call should come before some series of tests are run, with either
michael@0 812 StopRecordingLogcat or SearchLogcatRecord following the tests.
michael@0 813
michael@0 814 Args:
michael@0 815 clear: True if existing log output should be cleared.
michael@0 816 filters: A list of logcat filters to be used.
michael@0 817 """
michael@0 818 if clear:
michael@0 819 self._adb.SendCommand('logcat -c')
michael@0 820 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
michael@0 821 ' '.join(filters))
michael@0 822 self.logcat_process = subprocess.Popen(logcat_command, shell=True,
michael@0 823 stdout=subprocess.PIPE)
michael@0 824
michael@0 825 def StopRecordingLogcat(self):
michael@0 826 """Stops an existing logcat recording subprocess and returns output.
michael@0 827
michael@0 828 Returns:
michael@0 829 The logcat output as a string or an empty string if logcat was not
michael@0 830 being recorded at the time.
michael@0 831 """
michael@0 832 if not self.logcat_process:
michael@0 833 return ''
michael@0 834 # Cannot evaluate directly as 0 is a possible value.
michael@0 835 # Better to read the self.logcat_process.stdout before killing it,
michael@0 836 # Otherwise the communicate may return incomplete output due to pipe break.
michael@0 837 if self.logcat_process.poll() is None:
michael@0 838 self.logcat_process.kill()
michael@0 839 (output, _) = self.logcat_process.communicate()
michael@0 840 self.logcat_process = None
michael@0 841 return output
michael@0 842
michael@0 843 def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None,
michael@0 844 log_level=None, component=None):
michael@0 845 """Searches the specified logcat output and returns results.
michael@0 846
michael@0 847 This method searches through the logcat output specified by record for a
michael@0 848 certain message, narrowing results by matching them against any other
michael@0 849 specified criteria. It returns all matching lines as described below.
michael@0 850
michael@0 851 Args:
michael@0 852 record: A string generated by Start/StopRecordingLogcat to search.
michael@0 853 message: An output string to search for.
michael@0 854 thread_id: The thread id that is the origin of the message.
michael@0 855 proc_id: The process that is the origin of the message.
michael@0 856 log_level: The log level of the message.
michael@0 857 component: The name of the component that would create the message.
michael@0 858
michael@0 859 Returns:
michael@0 860 A list of dictionaries represeting matching entries, each containing keys
michael@0 861 thread_id, proc_id, log_level, component, and message.
michael@0 862 """
michael@0 863 if thread_id:
michael@0 864 thread_id = str(thread_id)
michael@0 865 if proc_id:
michael@0 866 proc_id = str(proc_id)
michael@0 867 results = []
michael@0 868 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
michael@0 869 re.MULTILINE)
michael@0 870 log_list = reg.findall(record)
michael@0 871 for (tid, pid, log_lev, comp, msg) in log_list:
michael@0 872 if ((not thread_id or thread_id == tid) and
michael@0 873 (not proc_id or proc_id == pid) and
michael@0 874 (not log_level or log_level == log_lev) and
michael@0 875 (not component or component == comp) and msg.find(message) > -1):
michael@0 876 match = dict({'thread_id': tid, 'proc_id': pid,
michael@0 877 'log_level': log_lev, 'component': comp,
michael@0 878 'message': msg})
michael@0 879 results.append(match)
michael@0 880 return results
michael@0 881
michael@0 882 def ExtractPid(self, process_name):
michael@0 883 """Extracts Process Ids for a given process name from Android Shell.
michael@0 884
michael@0 885 Args:
michael@0 886 process_name: name of the process on the device.
michael@0 887
michael@0 888 Returns:
michael@0 889 List of all the process ids (as strings) that match the given name.
michael@0 890 If the name of a process exactly matches the given name, the pid of
michael@0 891 that process will be inserted to the front of the pid list.
michael@0 892 """
michael@0 893 pids = []
michael@0 894 for line in self.RunShellCommand('ps', log_result=False):
michael@0 895 data = line.split()
michael@0 896 try:
michael@0 897 if process_name in data[-1]: # name is in the last column
michael@0 898 if process_name == data[-1]:
michael@0 899 pids.insert(0, data[1]) # PID is in the second column
michael@0 900 else:
michael@0 901 pids.append(data[1])
michael@0 902 except IndexError:
michael@0 903 pass
michael@0 904 return pids
michael@0 905
michael@0 906 def GetIoStats(self):
michael@0 907 """Gets cumulative disk IO stats since boot (for all processes).
michael@0 908
michael@0 909 Returns:
michael@0 910 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
michael@0 911 was an error.
michael@0 912 """
michael@0 913 for line in self.GetFileContents('/proc/diskstats', log_result=False):
michael@0 914 stats = io_stats_parser.ParseIoStatsLine(line)
michael@0 915 if stats.device == 'mmcblk0':
michael@0 916 return {
michael@0 917 'num_reads': stats.num_reads_issued,
michael@0 918 'num_writes': stats.num_writes_completed,
michael@0 919 'read_ms': stats.ms_spent_reading,
michael@0 920 'write_ms': stats.ms_spent_writing,
michael@0 921 }
michael@0 922 logging.warning('Could not find disk IO stats.')
michael@0 923 return None
michael@0 924
michael@0 925 def GetMemoryUsageForPid(self, pid):
michael@0 926 """Returns the memory usage for given pid.
michael@0 927
michael@0 928 Args:
michael@0 929 pid: The pid number of the specific process running on device.
michael@0 930
michael@0 931 Returns:
michael@0 932 A tuple containg:
michael@0 933 [0]: Dict of {metric:usage_kb}, for the process which has specified pid.
michael@0 934 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
michael@0 935 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
michael@0 936 KernelPageSize, MMUPageSize, Nvidia (tablet only).
michael@0 937 [1]: Detailed /proc/[PID]/smaps information.
michael@0 938 """
michael@0 939 usage_dict = collections.defaultdict(int)
michael@0 940 smaps = collections.defaultdict(dict)
michael@0 941 current_smap = ''
michael@0 942 for line in self.GetFileContents('/proc/%s/smaps' % pid, log_result=False):
michael@0 943 items = line.split()
michael@0 944 # See man 5 proc for more details. The format is:
michael@0 945 # address perms offset dev inode pathname
michael@0 946 if len(items) > 5:
michael@0 947 current_smap = ' '.join(items[5:])
michael@0 948 elif len(items) > 3:
michael@0 949 current_smap = ' '.join(items[3:])
michael@0 950 match = re.match(MEMORY_INFO_RE, line)
michael@0 951 if match:
michael@0 952 key = match.group('key')
michael@0 953 usage_kb = int(match.group('usage_kb'))
michael@0 954 usage_dict[key] += usage_kb
michael@0 955 if key not in smaps[current_smap]:
michael@0 956 smaps[current_smap][key] = 0
michael@0 957 smaps[current_smap][key] += usage_kb
michael@0 958 if not usage_dict or not any(usage_dict.values()):
michael@0 959 # Presumably the process died between ps and calling this method.
michael@0 960 logging.warning('Could not find memory usage for pid ' + str(pid))
michael@0 961
michael@0 962 for line in self.GetFileContents('/d/nvmap/generic-0/clients',
michael@0 963 log_result=False):
michael@0 964 match = re.match(NVIDIA_MEMORY_INFO_RE, line)
michael@0 965 if match and match.group('pid') == pid:
michael@0 966 usage_bytes = int(match.group('usage_bytes'))
michael@0 967 usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB
michael@0 968 break
michael@0 969
michael@0 970 return (usage_dict, smaps)
michael@0 971
michael@0 972 def GetMemoryUsageForPackage(self, package):
michael@0 973 """Returns the memory usage for all processes whose name contains |pacakge|.
michael@0 974
michael@0 975 Args:
michael@0 976 package: A string holding process name to lookup pid list for.
michael@0 977
michael@0 978 Returns:
michael@0 979 A tuple containg:
michael@0 980 [0]: Dict of {metric:usage_kb}, summed over all pids associated with
michael@0 981 |name|.
michael@0 982 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
michael@0 983 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
michael@0 984 KernelPageSize, MMUPageSize, Nvidia (tablet only).
michael@0 985 [1]: a list with detailed /proc/[PID]/smaps information.
michael@0 986 """
michael@0 987 usage_dict = collections.defaultdict(int)
michael@0 988 pid_list = self.ExtractPid(package)
michael@0 989 smaps = collections.defaultdict(dict)
michael@0 990
michael@0 991 for pid in pid_list:
michael@0 992 usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid)
michael@0 993 smaps[pid] = smaps_per_pid
michael@0 994 for (key, value) in usage_dict_per_pid.items():
michael@0 995 usage_dict[key] += value
michael@0 996
michael@0 997 return usage_dict, smaps
michael@0 998
michael@0 999 def ProcessesUsingDevicePort(self, device_port):
michael@0 1000 """Lists processes using the specified device port on loopback interface.
michael@0 1001
michael@0 1002 Args:
michael@0 1003 device_port: Port on device we want to check.
michael@0 1004
michael@0 1005 Returns:
michael@0 1006 A list of (pid, process_name) tuples using the specified port.
michael@0 1007 """
michael@0 1008 tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
michael@0 1009 tcp_address = '0100007F:%04X' % device_port
michael@0 1010 pids = []
michael@0 1011 for single_connect in tcp_results:
michael@0 1012 connect_results = single_connect.split()
michael@0 1013 # Column 1 is the TCP port, and Column 9 is the inode of the socket
michael@0 1014 if connect_results[1] == tcp_address:
michael@0 1015 socket_inode = connect_results[9]
michael@0 1016 socket_name = 'socket:[%s]' % socket_inode
michael@0 1017 lsof_results = self.RunShellCommand('lsof', log_result=False)
michael@0 1018 for single_process in lsof_results:
michael@0 1019 process_results = single_process.split()
michael@0 1020 # Ignore the line if it has less than nine columns in it, which may
michael@0 1021 # be the case when a process stops while lsof is executing.
michael@0 1022 if len(process_results) <= 8:
michael@0 1023 continue
michael@0 1024 # Column 0 is the executable name
michael@0 1025 # Column 1 is the pid
michael@0 1026 # Column 8 is the Inode in use
michael@0 1027 if process_results[8] == socket_name:
michael@0 1028 pids.append((int(process_results[1]), process_results[0]))
michael@0 1029 break
michael@0 1030 logging.info('PidsUsingDevicePort: %s', pids)
michael@0 1031 return pids
michael@0 1032
michael@0 1033 def FileExistsOnDevice(self, file_name):
michael@0 1034 """Checks whether the given file exists on the device.
michael@0 1035
michael@0 1036 Args:
michael@0 1037 file_name: Full path of file to check.
michael@0 1038
michael@0 1039 Returns:
michael@0 1040 True if the file exists, False otherwise.
michael@0 1041 """
michael@0 1042 assert '"' not in file_name, 'file_name cannot contain double quotes'
michael@0 1043 status = self._adb.SendShellCommand(
michael@0 1044 '\'test -e "%s"; echo $?\'' % (file_name))
michael@0 1045 if 'test: not found' not in status:
michael@0 1046 return int(status) == 0
michael@0 1047
michael@0 1048 status = self._adb.SendShellCommand(
michael@0 1049 '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
michael@0 1050 return int(status) == 0
michael@0 1051
michael@0 1052
michael@0 1053 class NewLineNormalizer(object):
michael@0 1054 """A file-like object to normalize EOLs to '\n'.
michael@0 1055
michael@0 1056 Pexpect runs adb within a pseudo-tty device (see
michael@0 1057 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
michael@0 1058 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
michael@0 1059 lines, the log ends up having '\r\r\n' at the end of each line. This
michael@0 1060 filter replaces the above with a single '\n' in the data stream.
michael@0 1061 """
michael@0 1062 def __init__(self, output):
michael@0 1063 self._output = output
michael@0 1064
michael@0 1065 def write(self, data):
michael@0 1066 data = data.replace('\r\r\n', '\n')
michael@0 1067 self._output.write(data)
michael@0 1068
michael@0 1069 def flush(self):
michael@0 1070 self._output.flush()
michael@0 1071

mercurial