OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Provides an interface to communicate with the device via the adb command. |
| 6 |
| 7 Assumes adb binary is currently on system path. |
| 8 |
| 9 Note that this module is deprecated. |
| 10 """ |
| 11 # TODO(jbudorick): Delete this file once no clients use it. |
| 12 |
| 13 # pylint: skip-file |
| 14 |
| 15 import collections |
| 16 import datetime |
| 17 import inspect |
| 18 import logging |
| 19 import os |
| 20 import random |
| 21 import re |
| 22 import shlex |
| 23 import signal |
| 24 import subprocess |
| 25 import sys |
| 26 import tempfile |
| 27 import time |
| 28 |
| 29 import cmd_helper |
| 30 import constants |
| 31 import system_properties |
| 32 from utils import host_utils |
| 33 |
| 34 try: |
| 35 from pylib import pexpect |
| 36 except ImportError: |
| 37 pexpect = None |
| 38 |
| 39 sys.path.append(os.path.join( |
| 40 constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) |
| 41 import adb_interface |
| 42 import am_instrument_parser |
| 43 import errors |
| 44 |
| 45 from pylib.device import device_blacklist |
| 46 from pylib.device import device_errors |
| 47 |
| 48 # Pattern to search for the next whole line of pexpect output and capture it |
| 49 # into a match group. We can't use ^ and $ for line start end with pexpect, |
| 50 # see http://www.noah.org/python/pexpect/#doc for explanation why. |
| 51 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') |
| 52 |
| 53 # Set the adb shell prompt to be a unique marker that will [hopefully] not |
| 54 # appear at the start of any line of a command's output. |
| 55 SHELL_PROMPT = '~+~PQ\x17RS~+~' |
| 56 |
| 57 # Java properties file |
| 58 LOCAL_PROPERTIES_PATH = constants.DEVICE_LOCAL_PROPERTIES_PATH |
| 59 |
| 60 # Property in /data/local.prop that controls Java assertions. |
| 61 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
| 62 |
| 63 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). |
| 64 KEYCODE_HOME = 3 |
| 65 KEYCODE_BACK = 4 |
| 66 KEYCODE_DPAD_UP = 19 |
| 67 KEYCODE_DPAD_DOWN = 20 |
| 68 KEYCODE_DPAD_RIGHT = 22 |
| 69 KEYCODE_ENTER = 66 |
| 70 KEYCODE_MENU = 82 |
| 71 |
| 72 MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/' |
| 73 MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin' |
| 74 |
| 75 PIE_WRAPPER_PATH = constants.TEST_EXECUTABLE_DIR + '/run_pie' |
| 76 |
| 77 CONTROL_USB_CHARGING_COMMANDS = [ |
| 78 { |
| 79 # Nexus 4 |
| 80 'witness_file': '/sys/module/pm8921_charger/parameters/disabled', |
| 81 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled', |
| 82 'disable_command': |
| 83 'echo 1 > /sys/module/pm8921_charger/parameters/disabled', |
| 84 }, |
| 85 { |
| 86 # Nexus 5 |
| 87 # Setting the HIZ bit of the bq24192 causes the charger to actually ignore |
| 88 # energy coming from USB. Setting the power_supply offline just updates the |
| 89 # Android system to reflect that. |
| 90 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT', |
| 91 'enable_command': ( |
| 92 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' |
| 93 'echo 1 > /sys/class/power_supply/usb/online'), |
| 94 'disable_command': ( |
| 95 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' |
| 96 'chmod 644 /sys/class/power_supply/usb/online && ' |
| 97 'echo 0 > /sys/class/power_supply/usb/online'), |
| 98 }, |
| 99 ] |
| 100 |
| 101 class DeviceTempFile(object): |
| 102 def __init__(self, android_commands, prefix='temp_file', suffix=''): |
| 103 """Find an unused temporary file path in the devices external directory. |
| 104 |
| 105 When this object is closed, the file will be deleted on the device. |
| 106 """ |
| 107 self.android_commands = android_commands |
| 108 while True: |
| 109 # TODO(cjhopman): This could actually return the same file in multiple |
| 110 # calls if the caller doesn't write to the files immediately. This is |
| 111 # expected to never happen. |
| 112 i = random.randint(0, 1000000) |
| 113 self.name = '%s/%s-%d-%010d%s' % ( |
| 114 android_commands.GetExternalStorage(), |
| 115 prefix, int(time.time()), i, suffix) |
| 116 if not android_commands.FileExistsOnDevice(self.name): |
| 117 break |
| 118 |
| 119 def __enter__(self): |
| 120 return self |
| 121 |
| 122 def __exit__(self, type, value, traceback): |
| 123 self.close() |
| 124 |
| 125 def close(self): |
| 126 self.android_commands.RunShellCommand('rm ' + self.name) |
| 127 |
| 128 |
| 129 def GetAVDs(): |
| 130 """Returns a list of AVDs.""" |
| 131 re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE) |
| 132 avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd'])) |
| 133 return avds |
| 134 |
| 135 def ResetBadDevices(): |
| 136 """Removes the blacklist that keeps track of bad devices for a current |
| 137 build. |
| 138 """ |
| 139 device_blacklist.ResetBlacklist() |
| 140 |
| 141 def ExtendBadDevices(devices): |
| 142 """Adds devices to the blacklist that keeps track of bad devices for a |
| 143 current build. |
| 144 |
| 145 The devices listed in the bad devices file will not be returned by |
| 146 GetAttachedDevices. |
| 147 |
| 148 Args: |
| 149 devices: list of bad devices to be added to the bad devices file. |
| 150 """ |
| 151 device_blacklist.ExtendBlacklist(devices) |
| 152 |
| 153 |
| 154 def GetAttachedDevices(hardware=True, emulator=True, offline=False): |
| 155 """Returns a list of attached, android devices and emulators. |
| 156 |
| 157 If a preferred device has been set with ANDROID_SERIAL, it will be first in |
| 158 the returned list. The arguments specify what devices to include in the list. |
| 159 |
| 160 Example output: |
| 161 |
| 162 * daemon not running. starting it now on port 5037 * |
| 163 * daemon started successfully * |
| 164 List of devices attached |
| 165 027c10494100b4d7 device |
| 166 emulator-5554 offline |
| 167 |
| 168 Args: |
| 169 hardware: Include attached actual devices that are online. |
| 170 emulator: Include emulators (i.e. AVD's) currently on host. |
| 171 offline: Include devices and emulators that are offline. |
| 172 |
| 173 Returns: List of devices. |
| 174 """ |
| 175 adb_devices_output = cmd_helper.GetCmdOutput([constants.GetAdbPath(), |
| 176 'devices']) |
| 177 |
| 178 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) |
| 179 online_devices = re_device.findall(adb_devices_output) |
| 180 |
| 181 re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE) |
| 182 emulator_devices = re_device.findall(adb_devices_output) |
| 183 |
| 184 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\t(?:offline|unauthorized)$', |
| 185 re.MULTILINE) |
| 186 offline_devices = re_device.findall(adb_devices_output) |
| 187 |
| 188 devices = [] |
| 189 # First determine list of online devices (e.g. hardware and/or emulator). |
| 190 if hardware and emulator: |
| 191 devices = online_devices |
| 192 elif hardware: |
| 193 devices = [device for device in online_devices |
| 194 if device not in emulator_devices] |
| 195 elif emulator: |
| 196 devices = emulator_devices |
| 197 |
| 198 # Now add offline devices if offline is true |
| 199 if offline: |
| 200 devices = devices + offline_devices |
| 201 |
| 202 # Remove any devices in the blacklist. |
| 203 blacklist = device_blacklist.ReadBlacklist() |
| 204 if len(blacklist): |
| 205 logging.info('Avoiding bad devices %s', ' '.join(blacklist)) |
| 206 devices = [device for device in devices if device not in blacklist] |
| 207 |
| 208 preferred_device = os.environ.get('ANDROID_SERIAL') |
| 209 if preferred_device in devices: |
| 210 devices.remove(preferred_device) |
| 211 devices.insert(0, preferred_device) |
| 212 return devices |
| 213 |
| 214 |
| 215 def IsDeviceAttached(device): |
| 216 """Return true if the device is attached and online.""" |
| 217 return device in GetAttachedDevices() |
| 218 |
| 219 |
| 220 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): |
| 221 """Gets a list of files from `ls` command output. |
| 222 |
| 223 Python's os.walk isn't used because it doesn't work over adb shell. |
| 224 |
| 225 Args: |
| 226 path: The path to list. |
| 227 ls_output: A list of lines returned by an `ls -lR` command. |
| 228 re_file: A compiled regular expression which parses a line into named groups |
| 229 consisting of at minimum "filename", "date", "time", "size" and |
| 230 optionally "timezone". |
| 231 utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a |
| 232 2-digit string giving the number of UTC offset hours, and MM is a |
| 233 2-digit string giving the number of UTC offset minutes. If the input |
| 234 utc_offset is None, will try to look for the value of "timezone" if it |
| 235 is specified in re_file. |
| 236 |
| 237 Returns: |
| 238 A dict of {"name": (size, lastmod), ...} where: |
| 239 name: The file name relative to |path|'s directory. |
| 240 size: The file size in bytes (0 for directories). |
| 241 lastmod: The file last modification date in UTC. |
| 242 """ |
| 243 re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path)) |
| 244 path_dir = os.path.dirname(path) |
| 245 |
| 246 current_dir = '' |
| 247 files = {} |
| 248 for line in ls_output: |
| 249 directory_match = re_directory.match(line) |
| 250 if directory_match: |
| 251 current_dir = directory_match.group('dir') |
| 252 continue |
| 253 file_match = re_file.match(line) |
| 254 if file_match: |
| 255 filename = os.path.join(current_dir, file_match.group('filename')) |
| 256 if filename.startswith(path_dir): |
| 257 filename = filename[len(path_dir) + 1:] |
| 258 lastmod = datetime.datetime.strptime( |
| 259 file_match.group('date') + ' ' + file_match.group('time')[:5], |
| 260 '%Y-%m-%d %H:%M') |
| 261 if not utc_offset and 'timezone' in re_file.groupindex: |
| 262 utc_offset = file_match.group('timezone') |
| 263 if isinstance(utc_offset, str) and len(utc_offset) == 5: |
| 264 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), |
| 265 minutes=int(utc_offset[3:5])) |
| 266 if utc_offset[0:1] == '-': |
| 267 utc_delta = -utc_delta |
| 268 lastmod -= utc_delta |
| 269 files[filename] = (int(file_match.group('size')), lastmod) |
| 270 return files |
| 271 |
| 272 |
| 273 def _ParseMd5SumOutput(md5sum_output): |
| 274 """Returns a list of tuples from the provided md5sum output. |
| 275 |
| 276 Args: |
| 277 md5sum_output: output directly from md5sum binary. |
| 278 |
| 279 Returns: |
| 280 List of namedtuples with attributes |hash| and |path|, where |path| is the |
| 281 absolute path to the file with an Md5Sum of |hash|. |
| 282 """ |
| 283 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path']) |
| 284 split_lines = [line.split(' ') for line in md5sum_output] |
| 285 return [HashAndPath._make(s) for s in split_lines if len(s) == 2] |
| 286 |
| 287 |
| 288 def _HasAdbPushSucceeded(command_output): |
| 289 """Returns whether adb push has succeeded from the provided output.""" |
| 290 # TODO(frankf): We should look at the return code instead of the command |
| 291 # output for many of the commands in this file. |
| 292 if not command_output: |
| 293 return True |
| 294 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
| 295 # Errors look like this: "failed to copy ... " |
| 296 if not re.search('^[0-9]', command_output.splitlines()[-1]): |
| 297 logging.critical('PUSH FAILED: ' + command_output) |
| 298 return False |
| 299 return True |
| 300 |
| 301 |
| 302 def GetLogTimestamp(log_line, year): |
| 303 """Returns the timestamp of the given |log_line| in the given year.""" |
| 304 try: |
| 305 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), |
| 306 '%Y-%m-%d %H:%M:%S.%f') |
| 307 except (ValueError, IndexError): |
| 308 logging.critical('Error reading timestamp from ' + log_line) |
| 309 return None |
| 310 |
| 311 |
| 312 class AndroidCommands(object): |
| 313 """Helper class for communicating with Android device via adb.""" |
| 314 |
| 315 def __init__(self, device=None): |
| 316 """Constructor. |
| 317 |
| 318 Args: |
| 319 device: If given, adb commands are only send to the device of this ID. |
| 320 Otherwise commands are sent to all attached devices. |
| 321 """ |
| 322 self._adb = adb_interface.AdbInterface(constants.GetAdbPath()) |
| 323 if device: |
| 324 self._adb.SetTargetSerial(device) |
| 325 self._device = device |
| 326 self._logcat = None |
| 327 self.logcat_process = None |
| 328 self._logcat_tmpoutfile = None |
| 329 self._pushed_files = [] |
| 330 self._device_utc_offset = None |
| 331 self._potential_push_size = 0 |
| 332 self._actual_push_size = 0 |
| 333 self._external_storage = '' |
| 334 self._util_wrapper = '' |
| 335 self._system_properties = system_properties.SystemProperties(self.Adb()) |
| 336 self._push_if_needed_cache = {} |
| 337 self._control_usb_charging_command = { |
| 338 'command': None, |
| 339 'cached': False, |
| 340 } |
| 341 self._protected_file_access_method_initialized = None |
| 342 self._privileged_command_runner = None |
| 343 self._pie_wrapper = None |
| 344 |
| 345 @property |
| 346 def system_properties(self): |
| 347 return self._system_properties |
| 348 |
| 349 def _LogShell(self, cmd): |
| 350 """Logs the adb shell command.""" |
| 351 if self._device: |
| 352 device_repr = self._device[-4:] |
| 353 else: |
| 354 device_repr = '????' |
| 355 logging.info('[%s]> %s', device_repr, cmd) |
| 356 |
| 357 def Adb(self): |
| 358 """Returns our AdbInterface to avoid us wrapping all its methods.""" |
| 359 # TODO(tonyg): Goal should be to git rid of this method by making this API |
| 360 # complete and alleviating the need. |
| 361 return self._adb |
| 362 |
| 363 def GetDevice(self): |
| 364 """Returns the device serial.""" |
| 365 return self._device |
| 366 |
| 367 def IsOnline(self): |
| 368 """Checks whether the device is online. |
| 369 |
| 370 Returns: |
| 371 True if device is in 'device' mode, False otherwise. |
| 372 """ |
| 373 # TODO(aurimas): revert to using adb get-state when android L adb is fixed. |
| 374 #out = self._adb.SendCommand('get-state') |
| 375 #return out.strip() == 'device' |
| 376 |
| 377 out = self._adb.SendCommand('devices') |
| 378 for line in out.split('\n'): |
| 379 if self._device in line and 'device' in line: |
| 380 return True |
| 381 return False |
| 382 |
| 383 def IsRootEnabled(self): |
| 384 """Checks if root is enabled on the device.""" |
| 385 root_test_output = self.RunShellCommand('ls /root') or [''] |
| 386 return not 'Permission denied' in root_test_output[0] |
| 387 |
| 388 def EnableAdbRoot(self): |
| 389 """Enables adb root on the device. |
| 390 |
| 391 Returns: |
| 392 True: if output from executing adb root was as expected. |
| 393 False: otherwise. |
| 394 """ |
| 395 if self.GetBuildType() == 'user': |
| 396 logging.warning("Can't enable root in production builds with type user") |
| 397 return False |
| 398 else: |
| 399 return_value = self._adb.EnableAdbRoot() |
| 400 # EnableAdbRoot inserts a call for wait-for-device only when adb logcat |
| 401 # output matches what is expected. Just to be safe add a call to |
| 402 # wait-for-device. |
| 403 self._adb.SendCommand('wait-for-device') |
| 404 return return_value |
| 405 |
| 406 def GetDeviceYear(self): |
| 407 """Returns the year information of the date on device.""" |
| 408 return self.RunShellCommand('date +%Y')[0] |
| 409 |
| 410 def GetExternalStorage(self): |
| 411 if not self._external_storage: |
| 412 self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0] |
| 413 if not self._external_storage: |
| 414 raise device_errors.CommandFailedError( |
| 415 ['shell', "'echo $EXTERNAL_STORAGE'"], |
| 416 'Unable to find $EXTERNAL_STORAGE') |
| 417 return self._external_storage |
| 418 |
| 419 def WaitForDevicePm(self, timeout=120): |
| 420 """Blocks until the device's package manager is available. |
| 421 |
| 422 To workaround http://b/5201039, we restart the shell and retry if the |
| 423 package manager isn't back after 120 seconds. |
| 424 |
| 425 Raises: |
| 426 errors.WaitForResponseTimedOutError after max retries reached. |
| 427 """ |
| 428 last_err = None |
| 429 retries = 3 |
| 430 while retries: |
| 431 try: |
| 432 self._adb.WaitForDevicePm(wait_time=timeout) |
| 433 return # Success |
| 434 except errors.WaitForResponseTimedOutError as e: |
| 435 last_err = e |
| 436 logging.warning('Restarting and retrying after timeout: %s', e) |
| 437 retries -= 1 |
| 438 self.RestartShell() |
| 439 raise last_err # Only reached after max retries, re-raise the last error. |
| 440 |
| 441 def RestartShell(self): |
| 442 """Restarts the shell on the device. Does not block for it to return.""" |
| 443 self.RunShellCommand('stop') |
| 444 self.RunShellCommand('start') |
| 445 |
| 446 def Reboot(self, full_reboot=True): |
| 447 """Reboots the device and waits for the package manager to return. |
| 448 |
| 449 Args: |
| 450 full_reboot: Whether to fully reboot the device or just restart the shell. |
| 451 """ |
| 452 # TODO(torne): hive can't reboot the device either way without breaking the |
| 453 # connection; work out if we can handle this better |
| 454 if os.environ.get('USING_HIVE'): |
| 455 logging.warning('Ignoring reboot request as we are on hive') |
| 456 return |
| 457 if full_reboot or not self.IsRootEnabled(): |
| 458 self._adb.SendCommand('reboot') |
| 459 self._system_properties = system_properties.SystemProperties(self.Adb()) |
| 460 timeout = 300 |
| 461 retries = 1 |
| 462 # Wait for the device to disappear. |
| 463 while retries < 10 and self.IsOnline(): |
| 464 time.sleep(1) |
| 465 retries += 1 |
| 466 else: |
| 467 self.RestartShell() |
| 468 timeout = 120 |
| 469 # To run tests we need at least the package manager and the sd card (or |
| 470 # other external storage) to be ready. |
| 471 self.WaitForDevicePm(timeout) |
| 472 self.WaitForSdCardReady(timeout) |
| 473 |
| 474 def Shutdown(self): |
| 475 """Shuts down the device.""" |
| 476 self._adb.SendCommand('reboot -p') |
| 477 self._system_properties = system_properties.SystemProperties(self.Adb()) |
| 478 |
| 479 def Uninstall(self, package): |
| 480 """Uninstalls the specified package from the device. |
| 481 |
| 482 Args: |
| 483 package: Name of the package to remove. |
| 484 |
| 485 Returns: |
| 486 A status string returned by adb uninstall |
| 487 """ |
| 488 uninstall_command = 'uninstall %s' % package |
| 489 |
| 490 self._LogShell(uninstall_command) |
| 491 return self._adb.SendCommand(uninstall_command, timeout_time=60) |
| 492 |
| 493 def Install(self, package_file_path, reinstall=False): |
| 494 """Installs the specified package to the device. |
| 495 |
| 496 Args: |
| 497 package_file_path: Path to .apk file to install. |
| 498 reinstall: Reinstall an existing apk, keeping the data. |
| 499 |
| 500 Returns: |
| 501 A status string returned by adb install |
| 502 """ |
| 503 assert os.path.isfile(package_file_path), ('<%s> is not file' % |
| 504 package_file_path) |
| 505 |
| 506 install_cmd = ['install'] |
| 507 |
| 508 if reinstall: |
| 509 install_cmd.append('-r') |
| 510 |
| 511 install_cmd.append(package_file_path) |
| 512 install_cmd = ' '.join(install_cmd) |
| 513 |
| 514 self._LogShell(install_cmd) |
| 515 return self._adb.SendCommand(install_cmd, |
| 516 timeout_time=2 * 60, |
| 517 retry_count=0) |
| 518 |
| 519 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, |
| 520 reboots_on_timeout=2): |
| 521 """Installs specified package and reboots device on timeouts. |
| 522 |
| 523 If package_name is supplied, checks if the package is already installed and |
| 524 doesn't reinstall if the apk md5sums match. |
| 525 |
| 526 Args: |
| 527 apk_path: Path to .apk file to install. |
| 528 keep_data: Reinstalls instead of uninstalling first, preserving the |
| 529 application data. |
| 530 package_name: Package name (only needed if keep_data=False). |
| 531 reboots_on_timeout: number of time to reboot if package manager is frozen. |
| 532 """ |
| 533 # Check if package is already installed and up to date. |
| 534 if package_name: |
| 535 installed_apk_path = self.GetApplicationPath(package_name) |
| 536 if (installed_apk_path and |
| 537 not self.GetFilesChanged(apk_path, installed_apk_path, |
| 538 ignore_filenames=True)): |
| 539 logging.info('Skipped install: identical %s APK already installed' % |
| 540 package_name) |
| 541 return |
| 542 # Install. |
| 543 reboots_left = reboots_on_timeout |
| 544 while True: |
| 545 try: |
| 546 if not keep_data: |
| 547 assert package_name |
| 548 self.Uninstall(package_name) |
| 549 install_status = self.Install(apk_path, reinstall=keep_data) |
| 550 if 'Success' in install_status: |
| 551 return |
| 552 else: |
| 553 raise Exception('Install failure: %s' % install_status) |
| 554 except errors.WaitForResponseTimedOutError: |
| 555 print '@@@STEP_WARNINGS@@@' |
| 556 logging.info('Timeout on installing %s on device %s', apk_path, |
| 557 self._device) |
| 558 |
| 559 if reboots_left <= 0: |
| 560 raise Exception('Install timed out') |
| 561 |
| 562 # Force a hard reboot on last attempt |
| 563 self.Reboot(full_reboot=(reboots_left == 1)) |
| 564 reboots_left -= 1 |
| 565 |
| 566 def MakeSystemFolderWritable(self): |
| 567 """Remounts the /system folder rw.""" |
| 568 out = self._adb.SendCommand('remount') |
| 569 if out.strip() != 'remount succeeded': |
| 570 raise errors.MsgException('Remount failed: %s' % out) |
| 571 |
| 572 def RestartAdbdOnDevice(self): |
| 573 logging.info('Restarting adbd on the device...') |
| 574 with DeviceTempFile(self, suffix=".sh") as temp_script_file: |
| 575 host_script_path = os.path.join(constants.DIR_SOURCE_ROOT, |
| 576 'build', |
| 577 'android', |
| 578 'pylib', |
| 579 'restart_adbd.sh') |
| 580 self._adb.Push(host_script_path, temp_script_file.name) |
| 581 self.RunShellCommand('. %s' % temp_script_file.name) |
| 582 self._adb.SendCommand('wait-for-device') |
| 583 |
| 584 def RestartAdbServer(self): |
| 585 """Restart the adb server.""" |
| 586 ret = self.KillAdbServer() |
| 587 if ret != 0: |
| 588 raise errors.MsgException('KillAdbServer: %d' % ret) |
| 589 |
| 590 ret = self.StartAdbServer() |
| 591 if ret != 0: |
| 592 raise errors.MsgException('StartAdbServer: %d' % ret) |
| 593 |
| 594 @staticmethod |
| 595 def KillAdbServer(): |
| 596 """Kill adb server.""" |
| 597 adb_cmd = [constants.GetAdbPath(), 'kill-server'] |
| 598 ret = cmd_helper.RunCmd(adb_cmd) |
| 599 retry = 0 |
| 600 while retry < 3: |
| 601 ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb']) |
| 602 if ret != 0: |
| 603 # pgrep didn't find adb, kill-server succeeded. |
| 604 return 0 |
| 605 retry += 1 |
| 606 time.sleep(retry) |
| 607 return ret |
| 608 |
| 609 def StartAdbServer(self): |
| 610 """Start adb server.""" |
| 611 adb_cmd = ['taskset', '-c', '0', constants.GetAdbPath(), 'start-server'] |
| 612 ret, _ = cmd_helper.GetCmdStatusAndOutput(adb_cmd) |
| 613 retry = 0 |
| 614 while retry < 3: |
| 615 ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb']) |
| 616 if ret == 0: |
| 617 # pgrep found adb, start-server succeeded. |
| 618 # Waiting for device to reconnect before returning success. |
| 619 self._adb.SendCommand('wait-for-device') |
| 620 return 0 |
| 621 retry += 1 |
| 622 time.sleep(retry) |
| 623 return ret |
| 624 |
| 625 def WaitForSystemBootCompleted(self, wait_time): |
| 626 """Waits for targeted system's boot_completed flag to be set. |
| 627 |
| 628 Args: |
| 629 wait_time: time in seconds to wait |
| 630 |
| 631 Raises: |
| 632 WaitForResponseTimedOutError if wait_time elapses and flag still not |
| 633 set. |
| 634 """ |
| 635 logging.info('Waiting for system boot completed...') |
| 636 self._adb.SendCommand('wait-for-device') |
| 637 # Now the device is there, but system not boot completed. |
| 638 # Query the sys.boot_completed flag with a basic command |
| 639 boot_completed = False |
| 640 attempts = 0 |
| 641 wait_period = 5 |
| 642 while not boot_completed and (attempts * wait_period) < wait_time: |
| 643 output = self.system_properties['sys.boot_completed'] |
| 644 output = output.strip() |
| 645 if output == '1': |
| 646 boot_completed = True |
| 647 else: |
| 648 # If 'error: xxx' returned when querying the flag, it means |
| 649 # adb server lost the connection to the emulator, so restart the adb |
| 650 # server. |
| 651 if 'error:' in output: |
| 652 self.RestartAdbServer() |
| 653 time.sleep(wait_period) |
| 654 attempts += 1 |
| 655 if not boot_completed: |
| 656 raise errors.WaitForResponseTimedOutError( |
| 657 'sys.boot_completed flag was not set after %s seconds' % wait_time) |
| 658 |
| 659 def WaitForSdCardReady(self, timeout_time): |
| 660 """Wait for the SD card ready before pushing data into it.""" |
| 661 logging.info('Waiting for SD card ready...') |
| 662 sdcard_ready = False |
| 663 attempts = 0 |
| 664 wait_period = 5 |
| 665 external_storage = self.GetExternalStorage() |
| 666 while not sdcard_ready and attempts * wait_period < timeout_time: |
| 667 output = self.RunShellCommand('ls ' + external_storage) |
| 668 if output: |
| 669 sdcard_ready = True |
| 670 else: |
| 671 time.sleep(wait_period) |
| 672 attempts += 1 |
| 673 if not sdcard_ready: |
| 674 raise errors.WaitForResponseTimedOutError( |
| 675 'SD card not ready after %s seconds' % timeout_time) |
| 676 |
| 677 def GetAndroidToolStatusAndOutput(self, command, lib_path=None, *args, **kw): |
| 678 """Runs a native Android binary, wrapping the command as necessary. |
| 679 |
| 680 This is a specialization of GetShellCommandStatusAndOutput, which is meant |
| 681 for running tools/android/ binaries and handle properly: (1) setting the |
| 682 lib path (for component=shared_library), (2) using the PIE wrapper on ICS. |
| 683 See crbug.com/373219 for more context. |
| 684 |
| 685 Args: |
| 686 command: String containing the command to send. |
| 687 lib_path: (optional) path to the folder containing the dependent libs. |
| 688 Same other arguments of GetCmdStatusAndOutput. |
| 689 """ |
| 690 # The first time this command is run the device is inspected to check |
| 691 # whether a wrapper for running PIE executable is needed (only Android ICS) |
| 692 # or not. The results is cached, so the wrapper is pushed only once. |
| 693 if self._pie_wrapper is None: |
| 694 # None: did not check; '': did check and not needed; '/path': use /path. |
| 695 self._pie_wrapper = '' |
| 696 if self.GetBuildId().startswith('I'): # Ixxxx = Android ICS. |
| 697 run_pie_dist_path = os.path.join(constants.GetOutDirectory(), 'run_pie') |
| 698 assert os.path.exists(run_pie_dist_path), 'Please build run_pie' |
| 699 # The PIE loader must be pushed manually (i.e. no PushIfNeeded) because |
| 700 # PushIfNeeded requires md5sum and md5sum requires the wrapper as well. |
| 701 adb_command = 'push %s %s' % (run_pie_dist_path, PIE_WRAPPER_PATH) |
| 702 assert _HasAdbPushSucceeded(self._adb.SendCommand(adb_command)) |
| 703 self._pie_wrapper = PIE_WRAPPER_PATH |
| 704 |
| 705 if self._pie_wrapper: |
| 706 command = '%s %s' % (self._pie_wrapper, command) |
| 707 if lib_path: |
| 708 command = 'LD_LIBRARY_PATH=%s %s' % (lib_path, command) |
| 709 return self.GetShellCommandStatusAndOutput(command, *args, **kw) |
| 710 |
| 711 # It is tempting to turn this function into a generator, however this is not |
| 712 # possible without using a private (local) adb_shell instance (to ensure no |
| 713 # other command interleaves usage of it), which would defeat the main aim of |
| 714 # being able to reuse the adb shell instance across commands. |
| 715 def RunShellCommand(self, command, timeout_time=20, log_result=False): |
| 716 """Send a command to the adb shell and return the result. |
| 717 |
| 718 Args: |
| 719 command: String containing the shell command to send. |
| 720 timeout_time: Number of seconds to wait for command to respond before |
| 721 retrying, used by AdbInterface.SendShellCommand. |
| 722 log_result: Boolean to indicate whether we should log the result of the |
| 723 shell command. |
| 724 |
| 725 Returns: |
| 726 list containing the lines of output received from running the command |
| 727 """ |
| 728 self._LogShell(command) |
| 729 if "'" in command: |
| 730 command = command.replace('\'', '\'\\\'\'') |
| 731 result = self._adb.SendShellCommand( |
| 732 "'%s'" % command, timeout_time).splitlines() |
| 733 # TODO(b.kelemen): we should really be able to drop the stderr of the |
| 734 # command or raise an exception based on what the caller wants. |
| 735 result = [ l for l in result if not l.startswith('WARNING') ] |
| 736 if ['error: device not found'] == result: |
| 737 raise errors.DeviceUnresponsiveError('device not found') |
| 738 if log_result: |
| 739 self._LogShell('\n'.join(result)) |
| 740 return result |
| 741 |
| 742 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, |
| 743 log_result=False): |
| 744 """See RunShellCommand() above. |
| 745 |
| 746 Returns: |
| 747 The tuple (exit code, list of output lines). |
| 748 """ |
| 749 lines = self.RunShellCommand( |
| 750 command + '; echo %$?', timeout_time, log_result) |
| 751 last_line = lines[-1] |
| 752 status_pos = last_line.rfind('%') |
| 753 assert status_pos >= 0 |
| 754 status = int(last_line[status_pos + 1:]) |
| 755 if status_pos == 0: |
| 756 lines = lines[:-1] |
| 757 else: |
| 758 lines = lines[:-1] + [last_line[:status_pos]] |
| 759 return (status, lines) |
| 760 |
| 761 def KillAll(self, process, signum=9, with_su=False): |
| 762 """Android version of killall, connected via adb. |
| 763 |
| 764 Args: |
| 765 process: name of the process to kill off. |
| 766 signum: signal to use, 9 (SIGKILL) by default. |
| 767 with_su: wether or not to use su to kill the processes. |
| 768 |
| 769 Returns: |
| 770 the number of processes killed |
| 771 """ |
| 772 pids = self.ExtractPid(process) |
| 773 if pids: |
| 774 cmd = 'kill -%d %s' % (signum, ' '.join(pids)) |
| 775 if with_su: |
| 776 self.RunShellCommandWithSU(cmd) |
| 777 else: |
| 778 self.RunShellCommand(cmd) |
| 779 return len(pids) |
| 780 |
| 781 def KillAllBlocking(self, process, timeout_sec, signum=9, with_su=False): |
| 782 """Blocking version of killall, connected via adb. |
| 783 |
| 784 This waits until no process matching the corresponding name appears in ps' |
| 785 output anymore. |
| 786 |
| 787 Args: |
| 788 process: name of the process to kill off |
| 789 timeout_sec: the timeout in seconds |
| 790 signum: same as |KillAll| |
| 791 with_su: same as |KillAll| |
| 792 Returns: |
| 793 the number of processes killed |
| 794 """ |
| 795 processes_killed = self.KillAll(process, signum=signum, with_su=with_su) |
| 796 if processes_killed: |
| 797 elapsed = 0 |
| 798 wait_period = 0.1 |
| 799 # Note that this doesn't take into account the time spent in ExtractPid(). |
| 800 while self.ExtractPid(process) and elapsed < timeout_sec: |
| 801 time.sleep(wait_period) |
| 802 elapsed += wait_period |
| 803 if elapsed >= timeout_sec: |
| 804 return processes_killed - self.ExtractPid(process) |
| 805 return processes_killed |
| 806 |
| 807 @staticmethod |
| 808 def _GetActivityCommand(package, activity, wait_for_completion, action, |
| 809 category, data, extras, trace_file_name, force_stop, |
| 810 flags): |
| 811 """Creates command to start |package|'s activity on the device. |
| 812 |
| 813 Args - as for StartActivity |
| 814 |
| 815 Returns: |
| 816 the command to run on the target to start the activity |
| 817 """ |
| 818 cmd = 'am start -a %s' % action |
| 819 if force_stop: |
| 820 cmd += ' -S' |
| 821 if wait_for_completion: |
| 822 cmd += ' -W' |
| 823 if category: |
| 824 cmd += ' -c %s' % category |
| 825 if package and activity: |
| 826 cmd += ' -n %s/%s' % (package, activity) |
| 827 if data: |
| 828 cmd += ' -d "%s"' % data |
| 829 if extras: |
| 830 for key in extras: |
| 831 value = extras[key] |
| 832 if isinstance(value, str): |
| 833 cmd += ' --es' |
| 834 elif isinstance(value, bool): |
| 835 cmd += ' --ez' |
| 836 elif isinstance(value, int): |
| 837 cmd += ' --ei' |
| 838 else: |
| 839 raise NotImplementedError( |
| 840 'Need to teach StartActivity how to pass %s extras' % type(value)) |
| 841 cmd += ' %s %s' % (key, value) |
| 842 if trace_file_name: |
| 843 cmd += ' --start-profiler ' + trace_file_name |
| 844 if flags: |
| 845 cmd += ' -f %s' % flags |
| 846 return cmd |
| 847 |
| 848 def StartActivity(self, package, activity, wait_for_completion=False, |
| 849 action='android.intent.action.VIEW', |
| 850 category=None, data=None, |
| 851 extras=None, trace_file_name=None, |
| 852 force_stop=False, flags=None): |
| 853 """Starts |package|'s activity on the device. |
| 854 |
| 855 Args: |
| 856 package: Name of package to start (e.g. 'com.google.android.apps.chrome'). |
| 857 activity: Name of activity (e.g. '.Main' or |
| 858 'com.google.android.apps.chrome.Main'). |
| 859 wait_for_completion: wait for the activity to finish launching (-W flag). |
| 860 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. |
| 861 category: string (e.g. "android.intent.category.HOME") |
| 862 data: Data string to pass to activity (e.g. 'http://www.example.com/'). |
| 863 extras: Dict of extras to pass to activity. Values are significant. |
| 864 trace_file_name: If used, turns on and saves the trace to this file name. |
| 865 force_stop: force stop the target app before starting the activity (-S |
| 866 flag). |
| 867 Returns: |
| 868 The output of the underlying command as a list of lines. |
| 869 """ |
| 870 cmd = self._GetActivityCommand(package, activity, wait_for_completion, |
| 871 action, category, data, extras, |
| 872 trace_file_name, force_stop, flags) |
| 873 return self.RunShellCommand(cmd) |
| 874 |
| 875 def StartActivityTimed(self, package, activity, wait_for_completion=False, |
| 876 action='android.intent.action.VIEW', |
| 877 category=None, data=None, |
| 878 extras=None, trace_file_name=None, |
| 879 force_stop=False, flags=None): |
| 880 """Starts |package|'s activity on the device, returning the start time |
| 881 |
| 882 Args - as for StartActivity |
| 883 |
| 884 Returns: |
| 885 A tuple containing: |
| 886 - the output of the underlying command as a list of lines, and |
| 887 - a timestamp string for the time at which the activity started |
| 888 """ |
| 889 cmd = self._GetActivityCommand(package, activity, wait_for_completion, |
| 890 action, category, data, extras, |
| 891 trace_file_name, force_stop, flags) |
| 892 self.StartMonitoringLogcat() |
| 893 out = self.RunShellCommand('log starting activity; ' + cmd) |
| 894 activity_started_re = re.compile('.*starting activity.*') |
| 895 m = self.WaitForLogMatch(activity_started_re, None) |
| 896 assert m |
| 897 start_line = m.group(0) |
| 898 return (out, GetLogTimestamp(start_line, self.GetDeviceYear())) |
| 899 |
| 900 def StartCrashUploadService(self, package): |
| 901 # TODO(frankf): We really need a python wrapper around Intent |
| 902 # to be shared with StartActivity/BroadcastIntent. |
| 903 cmd = ( |
| 904 'am startservice -a %s.crash.ACTION_FIND_ALL -n ' |
| 905 '%s/%s.crash.MinidumpUploadService' % |
| 906 (constants.PACKAGE_INFO['chrome'].package, |
| 907 package, |
| 908 constants.PACKAGE_INFO['chrome'].package)) |
| 909 am_output = self.RunShellCommandWithSU(cmd) |
| 910 assert am_output and 'Starting' in am_output[-1], ( |
| 911 'Service failed to start: %s' % am_output) |
| 912 time.sleep(15) |
| 913 |
| 914 def BroadcastIntent(self, package, intent, *args): |
| 915 """Send a broadcast intent. |
| 916 |
| 917 Args: |
| 918 package: Name of package containing the intent. |
| 919 intent: Name of the intent. |
| 920 args: Optional extra arguments for the intent. |
| 921 """ |
| 922 cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args)) |
| 923 self.RunShellCommand(cmd) |
| 924 |
| 925 def GoHome(self): |
| 926 """Tell the device to return to the home screen. Blocks until completion.""" |
| 927 self.RunShellCommand('am start -W ' |
| 928 '-a android.intent.action.MAIN -c android.intent.category.HOME') |
| 929 |
| 930 def CloseApplication(self, package): |
| 931 """Attempt to close down the application, using increasing violence. |
| 932 |
| 933 Args: |
| 934 package: Name of the process to kill off, e.g. |
| 935 com.google.android.apps.chrome |
| 936 """ |
| 937 self.RunShellCommand('am force-stop ' + package) |
| 938 |
| 939 def GetApplicationPath(self, package): |
| 940 """Get the installed apk path on the device for the given package. |
| 941 |
| 942 Args: |
| 943 package: Name of the package. |
| 944 |
| 945 Returns: |
| 946 Path to the apk on the device if it exists, None otherwise. |
| 947 """ |
| 948 pm_path_output = self.RunShellCommand('pm path ' + package) |
| 949 # The path output contains anything if and only if the package |
| 950 # exists. |
| 951 if pm_path_output: |
| 952 # pm_path_output is of the form: "package:/path/to/foo.apk" |
| 953 return pm_path_output[0].split(':')[1] |
| 954 else: |
| 955 return None |
| 956 |
| 957 def ClearApplicationState(self, package): |
| 958 """Closes and clears all state for the given |package|.""" |
| 959 # Check that the package exists before clearing it. Necessary because |
| 960 # calling pm clear on a package that doesn't exist may never return. |
| 961 pm_path_output = self.RunShellCommand('pm path ' + package) |
| 962 # The path output only contains anything if and only if the package exists. |
| 963 if pm_path_output: |
| 964 self.RunShellCommand('pm clear ' + package) |
| 965 |
| 966 def SendKeyEvent(self, keycode): |
| 967 """Sends keycode to the device. |
| 968 |
| 969 Args: |
| 970 keycode: Numeric keycode to send (see "enum" at top of file). |
| 971 """ |
| 972 self.RunShellCommand('input keyevent %d' % keycode) |
| 973 |
| 974 def _RunMd5Sum(self, host_path, device_path): |
| 975 """Gets the md5sum of a host path and device path. |
| 976 |
| 977 Args: |
| 978 host_path: Path (file or directory) on the host. |
| 979 device_path: Path on the device. |
| 980 |
| 981 Returns: |
| 982 A tuple containing lists of the host and device md5sum results as |
| 983 created by _ParseMd5SumOutput(). |
| 984 """ |
| 985 md5sum_dist_path = os.path.join(constants.GetOutDirectory(), |
| 986 'md5sum_dist') |
| 987 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' |
| 988 md5sum_dist_mtime = os.stat(md5sum_dist_path).st_mtime |
| 989 if (md5sum_dist_path not in self._push_if_needed_cache or |
| 990 self._push_if_needed_cache[md5sum_dist_path] != md5sum_dist_mtime): |
| 991 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) |
| 992 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) |
| 993 self._push_if_needed_cache[md5sum_dist_path] = md5sum_dist_mtime |
| 994 |
| 995 (_, md5_device_output) = self.GetAndroidToolStatusAndOutput( |
| 996 self._util_wrapper + ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path, |
| 997 lib_path=MD5SUM_DEVICE_FOLDER, |
| 998 timeout_time=2 * 60) |
| 999 device_hash_tuples = _ParseMd5SumOutput(md5_device_output) |
| 1000 assert os.path.exists(host_path), 'Local path not found %s' % host_path |
| 1001 md5sum_output = cmd_helper.GetCmdOutput( |
| 1002 [os.path.join(constants.GetOutDirectory(), 'md5sum_bin_host'), |
| 1003 host_path]) |
| 1004 host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines()) |
| 1005 return (host_hash_tuples, device_hash_tuples) |
| 1006 |
| 1007 def GetFilesChanged(self, host_path, device_path, ignore_filenames=False): |
| 1008 """Compares the md5sum of a host path against a device path. |
| 1009 |
| 1010 Note: Ignores extra files on the device. |
| 1011 |
| 1012 Args: |
| 1013 host_path: Path (file or directory) on the host. |
| 1014 device_path: Path on the device. |
| 1015 ignore_filenames: If True only the file contents are considered when |
| 1016 checking whether a file has changed, otherwise the relative path |
| 1017 must also match. |
| 1018 |
| 1019 Returns: |
| 1020 A list of tuples of the form (host_path, device_path) for files whose |
| 1021 md5sums do not match. |
| 1022 """ |
| 1023 |
| 1024 # Md5Sum resolves symbolic links in path names so the calculation of |
| 1025 # relative path names from its output will need the real path names of the |
| 1026 # base directories. Having calculated these they are used throughout the |
| 1027 # function since this makes us less subject to any future changes to Md5Sum. |
| 1028 real_host_path = os.path.realpath(host_path) |
| 1029 real_device_path = self.RunShellCommand('realpath "%s"' % device_path)[0] |
| 1030 |
| 1031 host_hash_tuples, device_hash_tuples = self._RunMd5Sum( |
| 1032 real_host_path, real_device_path) |
| 1033 |
| 1034 if len(host_hash_tuples) > len(device_hash_tuples): |
| 1035 logging.info('%s files do not exist on the device' % |
| 1036 (len(host_hash_tuples) - len(device_hash_tuples))) |
| 1037 |
| 1038 host_rel = [(os.path.relpath(os.path.normpath(t.path), real_host_path), |
| 1039 t.hash) |
| 1040 for t in host_hash_tuples] |
| 1041 |
| 1042 if os.path.isdir(real_host_path): |
| 1043 def RelToRealPaths(rel_path): |
| 1044 return (os.path.join(real_host_path, rel_path), |
| 1045 os.path.join(real_device_path, rel_path)) |
| 1046 else: |
| 1047 assert len(host_rel) == 1 |
| 1048 def RelToRealPaths(_): |
| 1049 return (real_host_path, real_device_path) |
| 1050 |
| 1051 if ignore_filenames: |
| 1052 # If we are ignoring file names, then we want to push any file for which |
| 1053 # a file with an equivalent MD5 sum does not exist on the device. |
| 1054 device_hashes = set([h.hash for h in device_hash_tuples]) |
| 1055 ShouldPush = lambda p, h: h not in device_hashes |
| 1056 else: |
| 1057 # Otherwise, we want to push any file on the host for which a file with |
| 1058 # an equivalent MD5 sum does not exist at the same relative path on the |
| 1059 # device. |
| 1060 device_rel = dict([(os.path.relpath(os.path.normpath(t.path), |
| 1061 real_device_path), |
| 1062 t.hash) |
| 1063 for t in device_hash_tuples]) |
| 1064 ShouldPush = lambda p, h: p not in device_rel or h != device_rel[p] |
| 1065 |
| 1066 return [RelToRealPaths(path) for path, host_hash in host_rel |
| 1067 if ShouldPush(path, host_hash)] |
| 1068 |
| 1069 def PushIfNeeded(self, host_path, device_path): |
| 1070 """Pushes |host_path| to |device_path|. |
| 1071 |
| 1072 Works for files and directories. This method skips copying any paths in |
| 1073 |test_data_paths| that already exist on the device with the same hash. |
| 1074 |
| 1075 All pushed files can be removed by calling RemovePushedFiles(). |
| 1076 """ |
| 1077 MAX_INDIVIDUAL_PUSHES = 50 |
| 1078 if not os.path.exists(host_path): |
| 1079 raise device_errors.CommandFailedError( |
| 1080 'Local path not found %s' % host_path, device=str(self)) |
| 1081 |
| 1082 # See if the file on the host changed since the last push (if any) and |
| 1083 # return early if it didn't. Note that this shortcut assumes that the tests |
| 1084 # on the device don't modify the files. |
| 1085 if not os.path.isdir(host_path): |
| 1086 if host_path in self._push_if_needed_cache: |
| 1087 host_path_mtime = self._push_if_needed_cache[host_path] |
| 1088 if host_path_mtime == os.stat(host_path).st_mtime: |
| 1089 return |
| 1090 |
| 1091 size = host_utils.GetRecursiveDiskUsage(host_path) |
| 1092 self._pushed_files.append(device_path) |
| 1093 self._potential_push_size += size |
| 1094 |
| 1095 if os.path.isdir(host_path): |
| 1096 self.RunShellCommand('mkdir -p "%s"' % device_path) |
| 1097 |
| 1098 changed_files = self.GetFilesChanged(host_path, device_path) |
| 1099 logging.info('Found %d files that need to be pushed to %s', |
| 1100 len(changed_files), device_path) |
| 1101 if not changed_files: |
| 1102 return |
| 1103 |
| 1104 def Push(host, device): |
| 1105 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout |
| 1106 # of 60 seconds which isn't sufficient for a lot of users of this method. |
| 1107 push_command = 'push %s %s' % (host, device) |
| 1108 self._LogShell(push_command) |
| 1109 |
| 1110 # Retry push with increasing backoff if the device is busy. |
| 1111 retry = 0 |
| 1112 while True: |
| 1113 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) |
| 1114 if _HasAdbPushSucceeded(output): |
| 1115 if not os.path.isdir(host_path): |
| 1116 self._push_if_needed_cache[host] = os.stat(host).st_mtime |
| 1117 return |
| 1118 if retry < 3: |
| 1119 retry += 1 |
| 1120 wait_time = 5 * retry |
| 1121 logging.error('Push failed, retrying in %d seconds: %s' % |
| 1122 (wait_time, output)) |
| 1123 time.sleep(wait_time) |
| 1124 else: |
| 1125 raise Exception('Push failed: %s' % output) |
| 1126 |
| 1127 diff_size = 0 |
| 1128 if len(changed_files) <= MAX_INDIVIDUAL_PUSHES: |
| 1129 diff_size = sum(host_utils.GetRecursiveDiskUsage(f[0]) |
| 1130 for f in changed_files) |
| 1131 |
| 1132 # TODO(craigdh): Replace this educated guess with a heuristic that |
| 1133 # approximates the push time for each method. |
| 1134 if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size: |
| 1135 self._actual_push_size += size |
| 1136 Push(host_path, device_path) |
| 1137 else: |
| 1138 for f in changed_files: |
| 1139 Push(f[0], f[1]) |
| 1140 self._actual_push_size += diff_size |
| 1141 |
| 1142 def GetPushSizeInfo(self): |
| 1143 """Get total size of pushes to the device done via PushIfNeeded() |
| 1144 |
| 1145 Returns: |
| 1146 A tuple: |
| 1147 1. Total size of push requests to PushIfNeeded (MB) |
| 1148 2. Total size that was actually pushed (MB) |
| 1149 """ |
| 1150 return (self._potential_push_size, self._actual_push_size) |
| 1151 |
| 1152 def GetFileContents(self, filename, log_result=False): |
| 1153 """Gets contents from the file specified by |filename|.""" |
| 1154 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, |
| 1155 log_result=log_result) |
| 1156 |
| 1157 def SetFileContents(self, filename, contents): |
| 1158 """Writes |contents| to the file specified by |filename|.""" |
| 1159 with tempfile.NamedTemporaryFile() as f: |
| 1160 f.write(contents) |
| 1161 f.flush() |
| 1162 self._adb.Push(f.name, filename) |
| 1163 |
| 1164 def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False): |
| 1165 return self.RunShellCommand('su -c %s' % command, timeout_time, log_result) |
| 1166 |
| 1167 def CanAccessProtectedFileContents(self): |
| 1168 """Returns True if Get/SetProtectedFileContents would work via "su" or adb |
| 1169 shell running as root. |
| 1170 |
| 1171 Devices running user builds don't have adb root, but may provide "su" which |
| 1172 can be used for accessing protected files. |
| 1173 """ |
| 1174 return (self._GetProtectedFileCommandRunner() != None) |
| 1175 |
| 1176 def _GetProtectedFileCommandRunner(self): |
| 1177 """Finds the best method to access protected files on the device. |
| 1178 |
| 1179 Returns: |
| 1180 1. None when privileged files cannot be accessed on the device. |
| 1181 2. Otherwise: A function taking a single parameter: a string with command |
| 1182 line arguments. Running that function executes the command with |
| 1183 the appropriate method. |
| 1184 """ |
| 1185 if self._protected_file_access_method_initialized: |
| 1186 return self._privileged_command_runner |
| 1187 |
| 1188 self._privileged_command_runner = None |
| 1189 self._protected_file_access_method_initialized = True |
| 1190 |
| 1191 for cmd in [self.RunShellCommand, self.RunShellCommandWithSU]: |
| 1192 # Get contents of the auxv vector for the init(8) process from a small |
| 1193 # binary file that always exists on linux and is always read-protected. |
| 1194 contents = cmd('cat /proc/1/auxv') |
| 1195 # The leading 4 or 8-bytes of auxv vector is a_type. There are not many |
| 1196 # reserved a_type values, hence byte 2 must always be '\0' for a realistic |
| 1197 # auxv. See /usr/include/elf.h. |
| 1198 if len(contents) > 0 and (contents[0][2] == '\0'): |
| 1199 self._privileged_command_runner = cmd |
| 1200 break |
| 1201 return self._privileged_command_runner |
| 1202 |
| 1203 def GetProtectedFileContents(self, filename): |
| 1204 """Gets contents from the protected file specified by |filename|. |
| 1205 |
| 1206 This is potentially less efficient than GetFileContents. |
| 1207 """ |
| 1208 command = 'cat "%s" 2> /dev/null' % filename |
| 1209 command_runner = self._GetProtectedFileCommandRunner() |
| 1210 if command_runner: |
| 1211 return command_runner(command) |
| 1212 else: |
| 1213 logging.warning('Could not access protected file: %s' % filename) |
| 1214 return [] |
| 1215 |
| 1216 def SetProtectedFileContents(self, filename, contents): |
| 1217 """Writes |contents| to the protected file specified by |filename|. |
| 1218 |
| 1219 This is less efficient than SetFileContents. |
| 1220 """ |
| 1221 with DeviceTempFile(self) as temp_file: |
| 1222 with DeviceTempFile(self, suffix=".sh") as temp_script: |
| 1223 # Put the contents in a temporary file |
| 1224 self.SetFileContents(temp_file.name, contents) |
| 1225 # Create a script to copy the file contents to its final destination |
| 1226 self.SetFileContents(temp_script.name, |
| 1227 'cat %s > %s' % (temp_file.name, filename)) |
| 1228 |
| 1229 command = 'sh %s' % temp_script.name |
| 1230 command_runner = self._GetProtectedFileCommandRunner() |
| 1231 if command_runner: |
| 1232 return command_runner(command) |
| 1233 else: |
| 1234 logging.warning( |
| 1235 'Could not set contents of protected file: %s' % filename) |
| 1236 |
| 1237 |
| 1238 def RemovePushedFiles(self): |
| 1239 """Removes all files pushed with PushIfNeeded() from the device.""" |
| 1240 for p in self._pushed_files: |
| 1241 self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60) |
| 1242 |
| 1243 def ListPathContents(self, path): |
| 1244 """Lists files in all subdirectories of |path|. |
| 1245 |
| 1246 Args: |
| 1247 path: The path to list. |
| 1248 |
| 1249 Returns: |
| 1250 A dict of {"name": (size, lastmod), ...}. |
| 1251 """ |
| 1252 # Example output: |
| 1253 # /foo/bar: |
| 1254 # -rw-r----- user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt |
| 1255 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' |
| 1256 '(?P<user>[^\s]+)\s+' |
| 1257 '(?P<group>[^\s]+)\s+' |
| 1258 '(?P<size>[^\s]+)\s+' |
| 1259 '(?P<date>[^\s]+)\s+' |
| 1260 '(?P<time>[^\s]+)\s+' |
| 1261 '(?P<filename>[^\s]+)$') |
| 1262 return _GetFilesFromRecursiveLsOutput( |
| 1263 path, self.RunShellCommand('ls -lR %s' % path), re_file, |
| 1264 self.GetUtcOffset()) |
| 1265 |
| 1266 def GetUtcOffset(self): |
| 1267 if not self._device_utc_offset: |
| 1268 self._device_utc_offset = self.RunShellCommand('date +%z')[0] |
| 1269 return self._device_utc_offset |
| 1270 |
| 1271 def SetJavaAssertsEnabled(self, enable): |
| 1272 """Sets or removes the device java assertions property. |
| 1273 |
| 1274 Args: |
| 1275 enable: If True the property will be set. |
| 1276 |
| 1277 Returns: |
| 1278 True if the file was modified (reboot is required for it to take effect). |
| 1279 """ |
| 1280 # First ensure the desired property is persisted. |
| 1281 temp_props_file = tempfile.NamedTemporaryFile() |
| 1282 properties = '' |
| 1283 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name): |
| 1284 with open(temp_props_file.name) as f: |
| 1285 properties = f.read() |
| 1286 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + |
| 1287 r'\s*=\s*all\s*$', re.MULTILINE) |
| 1288 if enable != bool(re.search(re_search, properties)): |
| 1289 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + |
| 1290 r'\s*=\s*\w+\s*$', re.MULTILINE) |
| 1291 properties = re.sub(re_replace, '', properties) |
| 1292 if enable: |
| 1293 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY |
| 1294 |
| 1295 file(temp_props_file.name, 'w').write(properties) |
| 1296 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH) |
| 1297 |
| 1298 # Next, check the current runtime value is what we need, and |
| 1299 # if not, set it and report that a reboot is required. |
| 1300 was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY] |
| 1301 if was_set == enable: |
| 1302 return False |
| 1303 self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or '' |
| 1304 return True |
| 1305 |
| 1306 def GetBuildId(self): |
| 1307 """Returns the build ID of the system (e.g. JRM79C).""" |
| 1308 build_id = self.system_properties['ro.build.id'] |
| 1309 assert build_id |
| 1310 return build_id |
| 1311 |
| 1312 def GetBuildType(self): |
| 1313 """Returns the build type of the system (e.g. eng).""" |
| 1314 build_type = self.system_properties['ro.build.type'] |
| 1315 assert build_type |
| 1316 return build_type |
| 1317 |
| 1318 def GetBuildProduct(self): |
| 1319 """Returns the build product of the device (e.g. maguro).""" |
| 1320 build_product = self.system_properties['ro.build.product'] |
| 1321 assert build_product |
| 1322 return build_product |
| 1323 |
| 1324 def GetProductName(self): |
| 1325 """Returns the product name of the device (e.g. takju).""" |
| 1326 name = self.system_properties['ro.product.name'] |
| 1327 assert name |
| 1328 return name |
| 1329 |
| 1330 def GetBuildFingerprint(self): |
| 1331 """Returns the build fingerprint of the device.""" |
| 1332 build_fingerprint = self.system_properties['ro.build.fingerprint'] |
| 1333 assert build_fingerprint |
| 1334 return build_fingerprint |
| 1335 |
| 1336 def GetDescription(self): |
| 1337 """Returns the description of the system. |
| 1338 |
| 1339 For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys". |
| 1340 """ |
| 1341 description = self.system_properties['ro.build.description'] |
| 1342 assert description |
| 1343 return description |
| 1344 |
| 1345 def GetProductModel(self): |
| 1346 """Returns the name of the product model (e.g. "Galaxy Nexus") """ |
| 1347 model = self.system_properties['ro.product.model'] |
| 1348 assert model |
| 1349 return model |
| 1350 |
| 1351 def GetWifiIP(self): |
| 1352 """Returns the wifi IP on the device.""" |
| 1353 wifi_ip = self.system_properties['dhcp.wlan0.ipaddress'] |
| 1354 # Do not assert here. Devices (e.g. emulators) may not have a WifiIP. |
| 1355 return wifi_ip |
| 1356 |
| 1357 def GetSubscriberInfo(self): |
| 1358 """Returns the device subscriber info (e.g. GSM and device ID) as string.""" |
| 1359 iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo') |
| 1360 # Do not assert here. Devices (e.g. Nakasi on K) may not have iphonesubinfo. |
| 1361 return '\n'.join(iphone_sub) |
| 1362 |
| 1363 def GetBatteryInfo(self): |
| 1364 """Returns a {str: str} dict of battery info (e.g. status, level, etc).""" |
| 1365 battery = self.RunShellCommand('dumpsys battery') |
| 1366 assert battery |
| 1367 battery_info = {} |
| 1368 for line in battery[1:]: |
| 1369 k, _, v = line.partition(': ') |
| 1370 battery_info[k.strip()] = v.strip() |
| 1371 return battery_info |
| 1372 |
| 1373 def GetSetupWizardStatus(self): |
| 1374 """Returns the status of the device setup wizard (e.g. DISABLED).""" |
| 1375 status = self.system_properties['ro.setupwizard.mode'] |
| 1376 # On some devices, the status is empty if not otherwise set. In such cases |
| 1377 # the caller should expect an empty string to be returned. |
| 1378 return status |
| 1379 |
| 1380 def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None): |
| 1381 """Starts monitoring the output of logcat, for use with WaitForLogMatch. |
| 1382 |
| 1383 Args: |
| 1384 clear: If True the existing logcat output will be cleared, to avoiding |
| 1385 matching historical output lurking in the log. |
| 1386 filters: A list of logcat filters to be used. |
| 1387 """ |
| 1388 if clear: |
| 1389 self.RunShellCommand('logcat -c') |
| 1390 args = [] |
| 1391 if self._adb._target_arg: |
| 1392 args += shlex.split(self._adb._target_arg) |
| 1393 args += ['logcat', '-v', 'threadtime'] |
| 1394 if filters: |
| 1395 args.extend(filters) |
| 1396 else: |
| 1397 args.append('*:v') |
| 1398 |
| 1399 if logfile: |
| 1400 logfile = NewLineNormalizer(logfile) |
| 1401 |
| 1402 # Spawn logcat and synchronize with it. |
| 1403 for _ in range(4): |
| 1404 self._logcat = pexpect.spawn(constants.GetAdbPath(), args, timeout=10, |
| 1405 logfile=logfile) |
| 1406 if not clear or self.SyncLogCat(): |
| 1407 break |
| 1408 self._logcat.close(force=True) |
| 1409 else: |
| 1410 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) |
| 1411 sys.exit(1) |
| 1412 |
| 1413 def SyncLogCat(self): |
| 1414 """Synchronize with logcat. |
| 1415 |
| 1416 Synchronize with the monitored logcat so that WaitForLogMatch will only |
| 1417 consider new message that are received after this point in time. |
| 1418 |
| 1419 Returns: |
| 1420 True if the synchronization succeeded. |
| 1421 """ |
| 1422 assert self._logcat |
| 1423 tag = 'logcat_sync_%s' % time.time() |
| 1424 self.RunShellCommand('log ' + tag) |
| 1425 return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0 |
| 1426 |
| 1427 def GetMonitoredLogCat(self): |
| 1428 """Returns an "adb logcat" command as created by pexpected.spawn.""" |
| 1429 if not self._logcat: |
| 1430 self.StartMonitoringLogcat(clear=False) |
| 1431 return self._logcat |
| 1432 |
| 1433 def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10): |
| 1434 """Blocks until a matching line is logged or a timeout occurs. |
| 1435 |
| 1436 Args: |
| 1437 success_re: A compiled re to search each line for. |
| 1438 error_re: A compiled re which, if found, terminates the search for |
| 1439 |success_re|. If None is given, no error condition will be detected. |
| 1440 clear: If True the existing logcat output will be cleared, defaults to |
| 1441 false. |
| 1442 timeout: Timeout in seconds to wait for a log match. |
| 1443 |
| 1444 Raises: |
| 1445 pexpect.TIMEOUT after |timeout| seconds without a match for |success_re| |
| 1446 or |error_re|. |
| 1447 |
| 1448 Returns: |
| 1449 The re match object if |success_re| is matched first or None if |error_re| |
| 1450 is matched first. |
| 1451 """ |
| 1452 logging.info('<<< Waiting for logcat:' + str(success_re.pattern)) |
| 1453 t0 = time.time() |
| 1454 while True: |
| 1455 if not self._logcat: |
| 1456 self.StartMonitoringLogcat(clear) |
| 1457 try: |
| 1458 while True: |
| 1459 # Note this will block for upto the timeout _per log line_, so we need |
| 1460 # to calculate the overall timeout remaining since t0. |
| 1461 time_remaining = t0 + timeout - time.time() |
| 1462 if time_remaining < 0: |
| 1463 raise pexpect.TIMEOUT(self._logcat) |
| 1464 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) |
| 1465 line = self._logcat.match.group(1) |
| 1466 if error_re: |
| 1467 error_match = error_re.search(line) |
| 1468 if error_match: |
| 1469 return None |
| 1470 success_match = success_re.search(line) |
| 1471 if success_match: |
| 1472 return success_match |
| 1473 logging.info('<<< Skipped Logcat Line:' + str(line)) |
| 1474 except pexpect.TIMEOUT: |
| 1475 raise pexpect.TIMEOUT( |
| 1476 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' |
| 1477 'to debug)' % |
| 1478 (timeout, success_re.pattern)) |
| 1479 except pexpect.EOF: |
| 1480 # It seems that sometimes logcat can end unexpectedly. This seems |
| 1481 # to happen during Chrome startup after a reboot followed by a cache |
| 1482 # clean. I don't understand why this happens, but this code deals with |
| 1483 # getting EOF in logcat. |
| 1484 logging.critical('Found EOF in adb logcat. Restarting...') |
| 1485 # Rerun spawn with original arguments. Note that self._logcat.args[0] is |
| 1486 # the path of adb, so we don't want it in the arguments. |
| 1487 self._logcat = pexpect.spawn(constants.GetAdbPath(), |
| 1488 self._logcat.args[1:], |
| 1489 timeout=self._logcat.timeout, |
| 1490 logfile=self._logcat.logfile) |
| 1491 |
| 1492 def StartRecordingLogcat(self, clear=True, filters=None): |
| 1493 """Starts recording logcat output to eventually be saved as a string. |
| 1494 |
| 1495 This call should come before some series of tests are run, with either |
| 1496 StopRecordingLogcat or SearchLogcatRecord following the tests. |
| 1497 |
| 1498 Args: |
| 1499 clear: True if existing log output should be cleared. |
| 1500 filters: A list of logcat filters to be used. |
| 1501 """ |
| 1502 if not filters: |
| 1503 filters = ['*:v'] |
| 1504 if clear: |
| 1505 self._adb.SendCommand('logcat -c') |
| 1506 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg, |
| 1507 ' '.join(filters)) |
| 1508 self._logcat_tmpoutfile = tempfile.NamedTemporaryFile(bufsize=0) |
| 1509 self.logcat_process = subprocess.Popen(logcat_command, shell=True, |
| 1510 stdout=self._logcat_tmpoutfile) |
| 1511 |
| 1512 def GetCurrentRecordedLogcat(self): |
| 1513 """Return the current content of the logcat being recorded. |
| 1514 Call this after StartRecordingLogcat() and before StopRecordingLogcat(). |
| 1515 This can be useful to perform timed polling/parsing. |
| 1516 Returns: |
| 1517 Current logcat output as a single string, or None if |
| 1518 StopRecordingLogcat() was already called. |
| 1519 """ |
| 1520 if not self._logcat_tmpoutfile: |
| 1521 return None |
| 1522 |
| 1523 with open(self._logcat_tmpoutfile.name) as f: |
| 1524 return f.read() |
| 1525 |
| 1526 def StopRecordingLogcat(self): |
| 1527 """Stops an existing logcat recording subprocess and returns output. |
| 1528 |
| 1529 Returns: |
| 1530 The logcat output as a string or an empty string if logcat was not |
| 1531 being recorded at the time. |
| 1532 """ |
| 1533 if not self.logcat_process: |
| 1534 return '' |
| 1535 # Cannot evaluate directly as 0 is a possible value. |
| 1536 # Better to read the self.logcat_process.stdout before killing it, |
| 1537 # Otherwise the communicate may return incomplete output due to pipe break. |
| 1538 if self.logcat_process.poll() is None: |
| 1539 self.logcat_process.kill() |
| 1540 self.logcat_process.wait() |
| 1541 self.logcat_process = None |
| 1542 self._logcat_tmpoutfile.seek(0) |
| 1543 output = self._logcat_tmpoutfile.read() |
| 1544 self._logcat_tmpoutfile.close() |
| 1545 self._logcat_tmpoutfile = None |
| 1546 return output |
| 1547 |
| 1548 @staticmethod |
| 1549 def SearchLogcatRecord(record, message, thread_id=None, proc_id=None, |
| 1550 log_level=None, component=None): |
| 1551 """Searches the specified logcat output and returns results. |
| 1552 |
| 1553 This method searches through the logcat output specified by record for a |
| 1554 certain message, narrowing results by matching them against any other |
| 1555 specified criteria. It returns all matching lines as described below. |
| 1556 |
| 1557 Args: |
| 1558 record: A string generated by Start/StopRecordingLogcat to search. |
| 1559 message: An output string to search for. |
| 1560 thread_id: The thread id that is the origin of the message. |
| 1561 proc_id: The process that is the origin of the message. |
| 1562 log_level: The log level of the message. |
| 1563 component: The name of the component that would create the message. |
| 1564 |
| 1565 Returns: |
| 1566 A list of dictionaries represeting matching entries, each containing keys |
| 1567 thread_id, proc_id, log_level, component, and message. |
| 1568 """ |
| 1569 if thread_id: |
| 1570 thread_id = str(thread_id) |
| 1571 if proc_id: |
| 1572 proc_id = str(proc_id) |
| 1573 results = [] |
| 1574 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$', |
| 1575 re.MULTILINE) |
| 1576 log_list = reg.findall(record) |
| 1577 for (tid, pid, log_lev, comp, msg) in log_list: |
| 1578 if ((not thread_id or thread_id == tid) and |
| 1579 (not proc_id or proc_id == pid) and |
| 1580 (not log_level or log_level == log_lev) and |
| 1581 (not component or component == comp) and msg.find(message) > -1): |
| 1582 match = dict({'thread_id': tid, 'proc_id': pid, |
| 1583 'log_level': log_lev, 'component': comp, |
| 1584 'message': msg}) |
| 1585 results.append(match) |
| 1586 return results |
| 1587 |
| 1588 def ExtractPid(self, process_name): |
| 1589 """Extracts Process Ids for a given process name from Android Shell. |
| 1590 |
| 1591 Args: |
| 1592 process_name: name of the process on the device. |
| 1593 |
| 1594 Returns: |
| 1595 List of all the process ids (as strings) that match the given name. |
| 1596 If the name of a process exactly matches the given name, the pid of |
| 1597 that process will be inserted to the front of the pid list. |
| 1598 """ |
| 1599 pids = [] |
| 1600 for line in self.RunShellCommand('ps', log_result=False): |
| 1601 data = line.split() |
| 1602 try: |
| 1603 if process_name in data[-1]: # name is in the last column |
| 1604 if process_name == data[-1]: |
| 1605 pids.insert(0, data[1]) # PID is in the second column |
| 1606 else: |
| 1607 pids.append(data[1]) |
| 1608 except IndexError: |
| 1609 pass |
| 1610 return pids |
| 1611 |
| 1612 def GetIoStats(self): |
| 1613 """Gets cumulative disk IO stats since boot (for all processes). |
| 1614 |
| 1615 Returns: |
| 1616 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there |
| 1617 was an error. |
| 1618 """ |
| 1619 IoStats = collections.namedtuple( |
| 1620 'IoStats', |
| 1621 ['device', |
| 1622 'num_reads_issued', |
| 1623 'num_reads_merged', |
| 1624 'num_sectors_read', |
| 1625 'ms_spent_reading', |
| 1626 'num_writes_completed', |
| 1627 'num_writes_merged', |
| 1628 'num_sectors_written', |
| 1629 'ms_spent_writing', |
| 1630 'num_ios_in_progress', |
| 1631 'ms_spent_doing_io', |
| 1632 'ms_spent_doing_io_weighted', |
| 1633 ]) |
| 1634 |
| 1635 for line in self.GetFileContents('/proc/diskstats', log_result=False): |
| 1636 fields = line.split() |
| 1637 stats = IoStats._make([fields[2]] + [int(f) for f in fields[3:]]) |
| 1638 if stats.device == 'mmcblk0': |
| 1639 return { |
| 1640 'num_reads': stats.num_reads_issued, |
| 1641 'num_writes': stats.num_writes_completed, |
| 1642 'read_ms': stats.ms_spent_reading, |
| 1643 'write_ms': stats.ms_spent_writing, |
| 1644 } |
| 1645 logging.warning('Could not find disk IO stats.') |
| 1646 return None |
| 1647 |
| 1648 def GetMemoryUsageForPid(self, pid): |
| 1649 """Returns the memory usage for given pid. |
| 1650 |
| 1651 Args: |
| 1652 pid: The pid number of the specific process running on device. |
| 1653 |
| 1654 Returns: |
| 1655 Dict of {metric:usage_kb}, for the process which has specified pid. |
| 1656 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, |
| 1657 Shared_Dirty, Private_Clean, Private_Dirty, VmHWM. |
| 1658 """ |
| 1659 showmap = self.RunShellCommand('showmap %d' % pid) |
| 1660 if not showmap or not showmap[-1].endswith('TOTAL'): |
| 1661 logging.warning('Invalid output for showmap %s', str(showmap)) |
| 1662 return {} |
| 1663 items = showmap[-1].split() |
| 1664 if len(items) != 9: |
| 1665 logging.warning('Invalid TOTAL for showmap %s', str(items)) |
| 1666 return {} |
| 1667 usage_dict = collections.defaultdict(int) |
| 1668 usage_dict.update({ |
| 1669 'Size': int(items[0].strip()), |
| 1670 'Rss': int(items[1].strip()), |
| 1671 'Pss': int(items[2].strip()), |
| 1672 'Shared_Clean': int(items[3].strip()), |
| 1673 'Shared_Dirty': int(items[4].strip()), |
| 1674 'Private_Clean': int(items[5].strip()), |
| 1675 'Private_Dirty': int(items[6].strip()), |
| 1676 }) |
| 1677 peak_value_kb = 0 |
| 1678 for line in self.GetProtectedFileContents('/proc/%s/status' % pid): |
| 1679 if not line.startswith('VmHWM:'): # Format: 'VmHWM: +[0-9]+ kB' |
| 1680 continue |
| 1681 peak_value_kb = int(line.split(':')[1].strip().split(' ')[0]) |
| 1682 break |
| 1683 usage_dict['VmHWM'] = peak_value_kb |
| 1684 if not peak_value_kb: |
| 1685 logging.warning('Could not find memory peak value for pid ' + str(pid)) |
| 1686 |
| 1687 return usage_dict |
| 1688 |
| 1689 def ProcessesUsingDevicePort(self, device_port): |
| 1690 """Lists processes using the specified device port on loopback interface. |
| 1691 |
| 1692 Args: |
| 1693 device_port: Port on device we want to check. |
| 1694 |
| 1695 Returns: |
| 1696 A list of (pid, process_name) tuples using the specified port. |
| 1697 """ |
| 1698 tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False) |
| 1699 tcp_address = '0100007F:%04X' % device_port |
| 1700 pids = [] |
| 1701 for single_connect in tcp_results: |
| 1702 connect_results = single_connect.split() |
| 1703 # Column 1 is the TCP port, and Column 9 is the inode of the socket |
| 1704 if connect_results[1] == tcp_address: |
| 1705 socket_inode = connect_results[9] |
| 1706 socket_name = 'socket:[%s]' % socket_inode |
| 1707 lsof_results = self.RunShellCommand('lsof', log_result=False) |
| 1708 for single_process in lsof_results: |
| 1709 process_results = single_process.split() |
| 1710 # Ignore the line if it has less than nine columns in it, which may |
| 1711 # be the case when a process stops while lsof is executing. |
| 1712 if len(process_results) <= 8: |
| 1713 continue |
| 1714 # Column 0 is the executable name |
| 1715 # Column 1 is the pid |
| 1716 # Column 8 is the Inode in use |
| 1717 if process_results[8] == socket_name: |
| 1718 pids.append((int(process_results[1]), process_results[0])) |
| 1719 break |
| 1720 logging.info('PidsUsingDevicePort: %s', pids) |
| 1721 return pids |
| 1722 |
| 1723 def FileExistsOnDevice(self, file_name): |
| 1724 """Checks whether the given file exists on the device. |
| 1725 |
| 1726 Args: |
| 1727 file_name: Full path of file to check. |
| 1728 |
| 1729 Returns: |
| 1730 True if the file exists, False otherwise. |
| 1731 """ |
| 1732 assert '"' not in file_name, 'file_name cannot contain double quotes' |
| 1733 try: |
| 1734 status = self._adb.SendShellCommand( |
| 1735 '\'test -e "%s"; echo $?\'' % (file_name)) |
| 1736 if 'test: not found' not in status: |
| 1737 return int(status) == 0 |
| 1738 |
| 1739 status = self._adb.SendShellCommand( |
| 1740 '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name)) |
| 1741 return int(status) == 0 |
| 1742 except ValueError: |
| 1743 if IsDeviceAttached(self._device): |
| 1744 raise errors.DeviceUnresponsiveError('Device may be offline.') |
| 1745 |
| 1746 return False |
| 1747 |
| 1748 def IsFileWritableOnDevice(self, file_name): |
| 1749 """Checks whether the given file (or directory) is writable on the device. |
| 1750 |
| 1751 Args: |
| 1752 file_name: Full path of file/directory to check. |
| 1753 |
| 1754 Returns: |
| 1755 True if writable, False otherwise. |
| 1756 """ |
| 1757 assert '"' not in file_name, 'file_name cannot contain double quotes' |
| 1758 try: |
| 1759 status = self._adb.SendShellCommand( |
| 1760 '\'test -w "%s"; echo $?\'' % (file_name)) |
| 1761 if 'test: not found' not in status: |
| 1762 return int(status) == 0 |
| 1763 raise errors.AbortError('"test" binary not found. OS too old.') |
| 1764 |
| 1765 except ValueError: |
| 1766 if IsDeviceAttached(self._device): |
| 1767 raise errors.DeviceUnresponsiveError('Device may be offline.') |
| 1768 |
| 1769 return False |
| 1770 |
| 1771 @staticmethod |
| 1772 def GetTimestamp(): |
| 1773 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime()) |
| 1774 |
| 1775 @staticmethod |
| 1776 def EnsureHostDirectory(host_file): |
| 1777 host_dir = os.path.dirname(os.path.abspath(host_file)) |
| 1778 if not os.path.exists(host_dir): |
| 1779 os.makedirs(host_dir) |
| 1780 |
| 1781 def TakeScreenshot(self, host_file=None): |
| 1782 """Saves a screenshot image to |host_file| on the host. |
| 1783 |
| 1784 Args: |
| 1785 host_file: Absolute path to the image file to store on the host or None to |
| 1786 use an autogenerated file name. |
| 1787 |
| 1788 Returns: |
| 1789 Resulting host file name of the screenshot. |
| 1790 """ |
| 1791 host_file = os.path.abspath(host_file or |
| 1792 'screenshot-%s.png' % self.GetTimestamp()) |
| 1793 self.EnsureHostDirectory(host_file) |
| 1794 device_file = '%s/screenshot.png' % self.GetExternalStorage() |
| 1795 self.RunShellCommand( |
| 1796 '/system/bin/screencap -p %s' % device_file) |
| 1797 self.PullFileFromDevice(device_file, host_file) |
| 1798 self.RunShellCommand('rm -f "%s"' % device_file) |
| 1799 return host_file |
| 1800 |
| 1801 def PullFileFromDevice(self, device_file, host_file): |
| 1802 """Download |device_file| on the device from to |host_file| on the host. |
| 1803 |
| 1804 Args: |
| 1805 device_file: Absolute path to the file to retrieve from the device. |
| 1806 host_file: Absolute path to the file to store on the host. |
| 1807 """ |
| 1808 if not self._adb.Pull(device_file, host_file): |
| 1809 raise device_errors.AdbCommandFailedError( |
| 1810 ['pull', device_file, host_file], 'Failed to pull file from device.') |
| 1811 assert os.path.exists(host_file) |
| 1812 |
| 1813 def SetUtilWrapper(self, util_wrapper): |
| 1814 """Sets a wrapper prefix to be used when running a locally-built |
| 1815 binary on the device (ex.: md5sum_bin). |
| 1816 """ |
| 1817 self._util_wrapper = util_wrapper |
| 1818 |
| 1819 def RunUIAutomatorTest(self, test, test_package, timeout): |
| 1820 """Runs a single uiautomator test. |
| 1821 |
| 1822 Args: |
| 1823 test: Test class/method. |
| 1824 test_package: Name of the test jar. |
| 1825 timeout: Timeout time in seconds. |
| 1826 |
| 1827 Returns: |
| 1828 An instance of am_instrument_parser.TestResult object. |
| 1829 """ |
| 1830 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) |
| 1831 self._LogShell(cmd) |
| 1832 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) |
| 1833 # uiautomator doesn't fully conform to the instrumenation test runner |
| 1834 # convention and doesn't terminate with INSTRUMENTATION_CODE. |
| 1835 # Just assume the first result is valid. |
| 1836 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) |
| 1837 if not test_results: |
| 1838 raise errors.InstrumentationError( |
| 1839 'no test results... device setup correctly?') |
| 1840 return test_results[0] |
| 1841 |
| 1842 def DismissCrashDialogIfNeeded(self): |
| 1843 """Dismiss the error/ANR dialog if present. |
| 1844 |
| 1845 Returns: Name of the crashed package if a dialog is focused, |
| 1846 None otherwise. |
| 1847 """ |
| 1848 re_focus = re.compile( |
| 1849 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') |
| 1850 |
| 1851 def _FindFocusedWindow(): |
| 1852 match = None |
| 1853 for line in self.RunShellCommand('dumpsys window windows'): |
| 1854 match = re.match(re_focus, line) |
| 1855 if match: |
| 1856 break |
| 1857 return match |
| 1858 |
| 1859 match = _FindFocusedWindow() |
| 1860 if not match: |
| 1861 return |
| 1862 package = match.group(2) |
| 1863 logging.warning('Trying to dismiss %s dialog for %s' % match.groups()) |
| 1864 self.SendKeyEvent(KEYCODE_DPAD_RIGHT) |
| 1865 self.SendKeyEvent(KEYCODE_DPAD_RIGHT) |
| 1866 self.SendKeyEvent(KEYCODE_ENTER) |
| 1867 match = _FindFocusedWindow() |
| 1868 if match: |
| 1869 logging.error('Still showing a %s dialog for %s' % match.groups()) |
| 1870 return package |
| 1871 |
| 1872 def EfficientDeviceDirectoryCopy(self, source, dest): |
| 1873 """ Copy a directory efficiently on the device |
| 1874 |
| 1875 Uses a shell script running on the target to copy new and changed files the |
| 1876 source directory to the destination directory and remove added files. This |
| 1877 is in some cases much faster than cp -r. |
| 1878 |
| 1879 Args: |
| 1880 source: absolute path of source directory |
| 1881 dest: absolute path of destination directory |
| 1882 """ |
| 1883 logging.info('In EfficientDeviceDirectoryCopy %s %s', source, dest) |
| 1884 with DeviceTempFile(self, suffix=".sh") as temp_script_file: |
| 1885 host_script_path = os.path.join(constants.DIR_SOURCE_ROOT, |
| 1886 'build', |
| 1887 'android', |
| 1888 'pylib', |
| 1889 'efficient_android_directory_copy.sh') |
| 1890 self._adb.Push(host_script_path, temp_script_file.name) |
| 1891 out = self.RunShellCommand( |
| 1892 'sh %s %s %s' % (temp_script_file.name, source, dest), |
| 1893 timeout_time=120) |
| 1894 if self._device: |
| 1895 device_repr = self._device[-4:] |
| 1896 else: |
| 1897 device_repr = '????' |
| 1898 for line in out: |
| 1899 logging.info('[%s]> %s', device_repr, line) |
| 1900 |
| 1901 def _GetControlUsbChargingCommand(self): |
| 1902 if self._control_usb_charging_command['cached']: |
| 1903 return self._control_usb_charging_command['command'] |
| 1904 self._control_usb_charging_command['cached'] = True |
| 1905 if not self.IsRootEnabled(): |
| 1906 return None |
| 1907 for command in CONTROL_USB_CHARGING_COMMANDS: |
| 1908 # Assert command is valid. |
| 1909 assert 'disable_command' in command |
| 1910 assert 'enable_command' in command |
| 1911 assert 'witness_file' in command |
| 1912 witness_file = command['witness_file'] |
| 1913 if self.FileExistsOnDevice(witness_file): |
| 1914 self._control_usb_charging_command['command'] = command |
| 1915 return command |
| 1916 return None |
| 1917 |
| 1918 def CanControlUsbCharging(self): |
| 1919 return self._GetControlUsbChargingCommand() is not None |
| 1920 |
| 1921 def DisableUsbCharging(self, timeout=10): |
| 1922 command = self._GetControlUsbChargingCommand() |
| 1923 if not command: |
| 1924 raise Exception('Unable to act on usb charging.') |
| 1925 disable_command = command['disable_command'] |
| 1926 t0 = time.time() |
| 1927 # Do not loop directly on self.IsDeviceCharging to cut the number of calls |
| 1928 # to the device. |
| 1929 while True: |
| 1930 if t0 + timeout - time.time() < 0: |
| 1931 raise pexpect.TIMEOUT('Unable to disable USB charging in time: %s' % ( |
| 1932 self.GetBatteryInfo())) |
| 1933 self.RunShellCommand(disable_command) |
| 1934 if not self.IsDeviceCharging(): |
| 1935 break |
| 1936 |
| 1937 def EnableUsbCharging(self, timeout=10): |
| 1938 command = self._GetControlUsbChargingCommand() |
| 1939 if not command: |
| 1940 raise Exception('Unable to act on usb charging.') |
| 1941 disable_command = command['enable_command'] |
| 1942 t0 = time.time() |
| 1943 # Do not loop directly on self.IsDeviceCharging to cut the number of calls |
| 1944 # to the device. |
| 1945 while True: |
| 1946 if t0 + timeout - time.time() < 0: |
| 1947 raise pexpect.TIMEOUT('Unable to enable USB charging in time.') |
| 1948 self.RunShellCommand(disable_command) |
| 1949 if self.IsDeviceCharging(): |
| 1950 break |
| 1951 |
| 1952 def IsDeviceCharging(self): |
| 1953 for line in self.RunShellCommand('dumpsys battery'): |
| 1954 if 'powered: ' in line: |
| 1955 if line.split('powered: ')[1] == 'true': |
| 1956 return True |
| 1957 |
| 1958 |
| 1959 class NewLineNormalizer(object): |
| 1960 """A file-like object to normalize EOLs to '\n'. |
| 1961 |
| 1962 Pexpect runs adb within a pseudo-tty device (see |
| 1963 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written |
| 1964 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate |
| 1965 lines, the log ends up having '\r\r\n' at the end of each line. This |
| 1966 filter replaces the above with a single '\n' in the data stream. |
| 1967 """ |
| 1968 def __init__(self, output): |
| 1969 self._output = output |
| 1970 |
| 1971 def write(self, data): |
| 1972 data = data.replace('\r\r\n', '\n') |
| 1973 self._output.write(data) |
| 1974 |
| 1975 def flush(self): |
| 1976 self._output.flush() |
OLD | NEW |