Index: build/android/pylib/device/adb_wrapper.py |
diff --git a/build/android/pylib/device/adb_wrapper.py b/build/android/pylib/device/adb_wrapper.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f6533554bc12b58048396925cd3458491fd476f2 |
--- /dev/null |
+++ b/build/android/pylib/device/adb_wrapper.py |
@@ -0,0 +1,416 @@ |
+# Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Module wrapping Android's adb tool.""" |
+ |
+import errno |
+import os |
+ |
+from pylib import cmd_helper |
+ |
+from pylib.utils import reraiser_thread |
+from pylib.utils import timeout_retry |
+ |
+_DEFAULT_TIMEOUT = 30 |
+_DEFAULT_RETRIES = 2 |
+ |
+ |
+class BaseError(Exception): |
+ """Base exception for all device and command errors.""" |
+ pass |
+ |
+ |
+class CommandFailedError(BaseError): |
+ """Exception for command failures.""" |
+ |
+ def __init__(self, cmd, msg, device=None): |
+ super(CommandFailedError, self).__init__( |
+ (('device %s: ' % device) if device else '') + |
+ 'adb command \'%s\' failed with message: \'%s\'' % (' '.join(cmd), msg)) |
+ |
+ |
+class CommandTimeoutError(BaseError): |
+ """Exception for command timeouts.""" |
+ pass |
+ |
+ |
+def _VerifyLocalFileExists(path): |
+ """Verifies a local file exists. |
+ |
+ Args: |
+ path: path to the local file. |
+ |
+ Raises: |
+ IOError: if the file doesn't exist. |
+ """ |
+ if not os.path.exists(path): |
+ raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), path) |
+ |
+ |
+class AdbWrapper(object): |
+ """A wrapper around a local Android Debug Bridge executable.""" |
+ |
+ def __init__(self, device_serial): |
+ """Initializes the AdbWrapper. |
+ |
+ Args: |
+ device_serial: The device serial number as a string. |
+ """ |
+ self._device_serial = str(device_serial) |
+ |
+ @classmethod |
+ def _AdbCmd(cls, arg_list, timeout, retries, check_error=True): |
+ """Runs an adb command with a timeout and retries. |
+ |
+ Args: |
+ arg_list: a list of arguments to adb. |
+ timeout: timeout in seconds. |
+ retries: number of retries. |
+ check_error: check that the command doesn't return an error message. This |
frankf
2013/11/21 01:12:57
actually, you'll only get 'error:' if device is of
craigdh
2013/11/26 19:00:34
Install already checks for 'Success'
|
+ does NOT check the return code of shell commands. |
+ |
+ Returns: |
+ The output of the command. |
+ """ |
+ cmd = ['adb'] + list(arg_list) |
frankf
2013/11/21 01:12:57
get rid of these conversions as discussed
craigdh
2013/11/26 19:00:34
Done.
|
+ |
+ # This method runs inside the timeout/retries. |
+ def RunCmd(): |
+ exit_code, output = cmd_helper.GetCmdStatusAndOutput(cmd) |
+ if exit_code != 0: |
+ raise CommandFailedError( |
+ 'Command \'%s\' failed with code %d' % (' '.join(cmd), exit_code)) |
+ if check_error and output[:len('error:')] == 'error:': |
+ raise CommandFailedError(arg_list, output) |
+ return output |
+ |
+ try: |
+ return timeout_retry.Run(RunCmd, timeout, retries) |
+ except reraiser_thread.TimeoutError as e: |
+ raise CommandTimeoutError(str(e)) |
+ |
+ def _DeviceAdbCmd(self, arg_list, timeout, retries, check_error=True): |
+ """Runs an adb command on the device associated with this object. |
+ |
+ Args: |
+ arg_list: a list of arguments to adb. |
+ timeout: timeout in seconds. |
+ retries: number of retries. |
+ check_error: check that the command doesn't return an error message. This |
+ does NOT check the return code of shell commands. |
+ |
+ Returns: |
+ The output of the command. |
+ """ |
+ return self._AdbCmd( |
+ ['-s', self._device_serial] + list(arg_list), timeout, retries, |
+ check_error=check_error) |
+ |
+ def __eq__(self, other): |
+ """Consider instances equal if they refer to the same device. |
+ |
+ Args: |
+ other: the instance to compare equality with. |
+ |
+ Returns: |
+ True if the instances are considered equal, false otherwise. |
+ """ |
+ return self._device_serial == str(other) |
+ |
+ def __str__(self): |
+ """The string representation of an instance. |
+ |
+ Returns: |
+ The device serial number as a string. |
+ """ |
+ return self._device_serial |
+ |
+ def __repr__(self): |
+ return '%s(\'%s\')' % (self.__class__.__name__, str(self)) |
+ |
+ # TODO(craigdh): Determine the filter criteria that should be supported. |
+ @classmethod |
+ def GetDevices(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Get the list of active attached devices. |
+ |
+ Args: |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ |
+ Yields: |
+ AdbWrapper instances. |
+ """ |
+ output = cls._AdbCmd(['devices'], timeout, retries) |
+ for line in output.split('\n'): |
+ fields = line.split() |
+ if len(fields) == 2 and fields[1] == 'device': |
+ yield AdbWrapper(fields[0]) |
+ |
+ def GetDeviceSerial(self): |
+ """Gets the device serial number associated with this object. |
+ |
+ Returns: |
+ Device serial number as a string. |
+ """ |
+ return self._device_serial |
+ |
+ def Push(self, local, remote, timeout=_DEFAULT_TIMEOUT, |
frankf
2013/11/21 01:12:57
please add test cases for any method where feasabl
craigdh
2013/11/26 19:00:34
Done. I avoided testing all cases such as file/dir
|
+ retries=_DEFAULT_RETRIES): |
+ """Pushes a file from the host to the device. |
+ |
+ Args: |
+ local: path on the host filesystem. |
+ remote: path on the device filesystem. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ _VerifyLocalFileExists(local) |
+ self._DeviceAdbCmd(['push', local, remote], timeout, retries) |
+ |
+ def Pull(self, remote, local, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Pulls a file from the device to the host. |
+ |
+ Args: |
+ remote: path on the device filesystem. |
+ local: path on the host filesystem. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ self._DeviceAdbCmd(['pull', local, remote], timeout, retries) |
+ _VerifyLocalFileExists(local) |
+ |
+ def Shell(self, command, expect_rc=None, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Runs a shell command on the device. |
+ |
+ Args: |
+ command: the shell command to run. |
+ expect_rc: optional, if set checks that the command's return code matches |
frankf
2013/11/21 01:12:57
(optional) if set, checks...
craigdh
2013/11/26 19:00:34
Done.
|
+ this value. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ |
+ Returns: |
+ The output of the shell command as a string. |
+ |
+ Raises: |
+ CommandFailedError: if the return code doesn't match |expect_rc|. |
+ """ |
+ if expect_rc is None: |
+ actual_command = command |
+ else: |
+ actual_command = '%s; echo $?;' % command |
+ output = self._DeviceAdbCmd( |
+ ['shell', actual_command], timeout, retries, check_error=False) |
+ if expect_rc is not None: |
+ output_end = output.rstrip().rfind('\n') + 1 |
+ rc = output[output_end:].strip() |
+ output = output[:output_end] |
+ if int(rc) != expect_rc: |
+ raise CommandFailedError( |
+ ['shell', command], |
+ 'shell command exited with code: %s' % rc, |
+ self._device_serial) |
+ return output |
+ |
+ def Logcat(self, filter_spec=None, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Get the logcat output. |
+ |
+ Args: |
+ filter_spec: optional spec to filter the logcat. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ |
+ Returns: |
+ logcat output as a string. |
+ """ |
+ cmd = ['logcat'] |
+ if filter_spec is not None: |
+ cmd.append(filter_spec) |
+ return self._DeviceAdbCmd(cmd, timeout, retries, check_error=False) |
+ |
+ def Forward(self, local, remote, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Forward socket connections from the local socket to the remote socket. |
+ |
+ Sockets are specified by one of: |
+ tcp:<port> |
+ localabstract:<unix domain socket name> |
+ localreserved:<unix domain socket name> |
+ localfilesystem:<unix domain socket name> |
+ dev:<character device name> |
+ jdwp:<process pid> (remote only) |
+ |
+ Args: |
+ local: the host socket. |
+ remote: the device socket. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ self._DeviceAdbCmd(['forward', local, remote], timeout, retries) |
+ |
+ def JDWP(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """List of PIDs of processes hosting a JDWP transport. |
+ |
+ Args: |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ |
+ Returns: |
+ A list of PIDs as strings. |
+ """ |
+ return [a.strip() for a in |
+ self._DeviceAdbCmd(['jdwp'], timeout, retries).split('\n')] |
+ |
+ def Install(self, apk_path, forward_lock=False, reinstall=False, |
+ sd_card=False, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Push a package file to the device and install it. |
+ |
+ Args: |
+ apk_path: host path to the APK file. |
+ forward_lock: optional, if set forward-locks the app. |
+ reinstall: optional, if set reinstalls the app, keeping its data. |
+ sd_card: optional, if set installs on the SD card. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ _VerifyLocalFileExists(apk_path) |
+ cmd = ['install'] |
+ if forward_lock: |
+ cmd.append('-l') |
+ if reinstall: |
+ cmd.append('-r') |
+ if sd_card: |
+ cmd.append('-s') |
+ cmd.append(apk_path) |
+ output = self._DeviceAdbCmd(cmd, timeout, retries) |
+ if 'Success' not in output: |
+ raise CommandFailedError(cmd, output) |
+ |
+ def Uninstall(self, package, keep_data=False, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Remove the app |package| from the device. |
+ |
+ Args: |
+ package: the package to uninstall. |
+ keep_data: optional, if set keep the data and cache directories. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ cmd = ['uninstall'] |
+ if keep_data: |
+ cmd.append('-k') |
+ cmd.append(package) |
+ return self._DeviceAdbCmd(cmd, timeout, retries) |
+ |
+ def Backup(self, path, packages=None, apk=False, shared=False, |
+ nosystem=True, include_all=False, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Write an archive of the device's data to |path|. |
+ |
+ Args: |
+ path: local path to store the backup file. |
+ packages: list of to packages to be backed up. |
+ apk: optional, if set include the .apk files in the archive. |
+ shared: optional, if set buckup the device's SD card. |
+ nosystem: optional, if set exclude system applications. |
+ include_all: optional, if set back up all installed applications and |
+ |packages| is optional. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ cmd = ['backup', path] |
+ if apk: |
+ cmd.append('-apk') |
+ if shared: |
+ cmd.append('-shared') |
+ if nosystem: |
+ cmd.append('-nosystem') |
+ if include_all: |
+ cmd.append('-all') |
+ if packages: |
+ cmd.extend(packages) |
+ assert bool(packages) ^ bool(include_all), ( |
+ 'Provide \'packages\' or set \'include_all\' but not both.') |
+ ret = self._DeviceAdbCmd(cmd, timeout, retries) |
+ _VerifyLocalFileExists(path) |
+ return ret |
+ |
+ def Restore(self, path, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Restore device contents from the backup archive. |
+ |
+ Args: |
+ path: host path to the backup archive. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ _VerifyLocalFileExists(path) |
+ self._DeviceAdbCmd(['restore'] + [path], timeout, retries) |
+ |
+ def WaitForDevice(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Block until the device is online. |
+ |
+ Args: |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ self._DeviceAdbCmd(['wait-for-device'], timeout, retries) |
+ |
+ def GetState(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Get device state. |
+ |
+ Args: |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ |
+ Returns: |
+ One of 'offline', 'bootloader', or 'device'. |
+ """ |
+ return self._DeviceAdbCmd(['get-state'], timeout, retries) |
+ |
+ def GetDevPath(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Gets the device path. |
+ |
+ Args: |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ |
+ Returns: |
+ The device path (e.g. usb:3-4) |
+ """ |
+ return self._DeviceAdbCmd(['get-devpath'], timeout, retries) |
+ |
+ def Remount(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Remounts the /system partition on the device read-write.""" |
+ self._DeviceAdbCmd(['remount'], timeout, retries) |
+ |
+ def Reboot(self, to_bootloader=False, timeout=_DEFAULT_TIMEOUT, |
+ retries=_DEFAULT_RETRIES): |
+ """Reboots the device. |
+ |
+ Args: |
+ to_bootloader: optional, if set reboots to the bootloader. |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ if to_bootloader: |
+ cmd = ['reboot-bootloader'] |
+ else: |
+ cmd = ['reboot'] |
+ self._DeviceAdbCmd(cmd, timeout, retries) |
+ |
+ def Root(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Restarts the adbd daemon with root permissions, if possible. |
+ |
+ Args: |
+ timeout: timeout per try in seconds. |
+ retries: number of retries to attempt. |
+ """ |
+ output = self._DeviceAdbCmd('root', timeout, retries) |
+ if 'cannot' in output: |
+ raise CommandFailedError('root', output) |
+ |