[android] Adds AdbWrapper which is the base of the eventual AndroidCommands replacement.
BUG=318387 TEST=included NOTRY=True Review URL: https://codereview.chromium.org/64553012 git-svn-id: http://src.chromium.org/svn/trunk/src/build@238123 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
Родитель
47c25416ff
Коммит
c134c467a4
|
@ -0,0 +1,423 @@
|
||||||
|
# Copyright 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.
|
||||||
|
|
||||||
|
"""This module wraps Android's adb tool.
|
||||||
|
|
||||||
|
This is a thin wrapper around the adb interface. Any additional complexity
|
||||||
|
should be delegated to a higher level (ex. DeviceUtils).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
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
|
||||||
|
does NOT check the return code of shell commands.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The output of the command.
|
||||||
|
"""
|
||||||
|
cmd = ['adb'] + arg_list
|
||||||
|
|
||||||
|
# This method runs inside the timeout/retries.
|
||||||
|
def RunCmd():
|
||||||
|
exit_code, output = cmd_helper.GetCmdStatusAndOutput(cmd)
|
||||||
|
if exit_code != 0:
|
||||||
|
raise CommandFailedError(
|
||||||
|
cmd, 'returned non-zero exit code %s, output: %s' %
|
||||||
|
(exit_code, output))
|
||||||
|
# This catches some errors, including when the device drops offline;
|
||||||
|
# unfortunately adb is very inconsistent with error reporting so many
|
||||||
|
# command failures present differently.
|
||||||
|
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] + 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__, 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
AdbWrapper instances.
|
||||||
|
"""
|
||||||
|
output = cls._AdbCmd(['devices'], timeout, retries)
|
||||||
|
lines = [line.split() for line in output.split('\n')]
|
||||||
|
return [AdbWrapper(line[0]) for line in lines
|
||||||
|
if len(line) == 2 and line[1] == 'device']
|
||||||
|
|
||||||
|
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=60*5, 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
"""
|
||||||
|
_VerifyLocalFileExists(local)
|
||||||
|
self._DeviceAdbCmd(['push', local, remote], timeout, retries)
|
||||||
|
|
||||||
|
def Pull(self, remote, local, timeout=60*5, 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
"""
|
||||||
|
self._DeviceAdbCmd(['pull', remote, local], 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
|
||||||
|
this value.
|
||||||
|
timeout: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
"""
|
||||||
|
self._DeviceAdbCmd(['forward', str(local), str(remote)], timeout, retries)
|
||||||
|
|
||||||
|
def JDWP(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
|
||||||
|
"""List of PIDs of processes hosting a JDWP transport.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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=60*2, retries=_DEFAULT_RETRIES):
|
||||||
|
"""Install an apk on the device.
|
||||||
|
|
||||||
|
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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
"""
|
||||||
|
cmd = ['uninstall']
|
||||||
|
if keep_data:
|
||||||
|
cmd.append('-k')
|
||||||
|
cmd.append(package)
|
||||||
|
output = self._DeviceAdbCmd(cmd, timeout, retries)
|
||||||
|
if 'Failure' in output:
|
||||||
|
raise CommandFailedError(cmd, output)
|
||||||
|
|
||||||
|
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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
"""
|
||||||
|
_VerifyLocalFileExists(path)
|
||||||
|
self._DeviceAdbCmd(['restore'] + [path], timeout, retries)
|
||||||
|
|
||||||
|
def WaitForDevice(self, timeout=60*5, retries=_DEFAULT_RETRIES):
|
||||||
|
"""Block until the device is online.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
One of 'offline', 'bootloader', or 'device'.
|
||||||
|
"""
|
||||||
|
return self._DeviceAdbCmd(['get-state'], timeout, retries).strip()
|
||||||
|
|
||||||
|
def GetDevPath(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
|
||||||
|
"""Gets the device path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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=60*5,
|
||||||
|
retries=_DEFAULT_RETRIES):
|
||||||
|
"""Reboots the device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
to_bootloader: (optional) If set reboots to the bootloader.
|
||||||
|
timeout: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) 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: (optional) Timeout per try in seconds.
|
||||||
|
retries: (optional) Number of retries to attempt.
|
||||||
|
"""
|
||||||
|
output = self._DeviceAdbCmd(['root'], timeout, retries)
|
||||||
|
if 'cannot' in output:
|
||||||
|
raise CommandFailedError(['root'], output)
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Copyright 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.
|
||||||
|
|
||||||
|
"""Tests for the AdbWrapper class."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import adb_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdbWrapper(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
devices = adb_wrapper.AdbWrapper.GetDevices()
|
||||||
|
assert devices, 'A device must be attached'
|
||||||
|
self._adb = devices[0]
|
||||||
|
self._adb.WaitForDevice()
|
||||||
|
|
||||||
|
def _MakeTempFile(self, contents):
|
||||||
|
"""Make a temporary file with the given contents.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
contents: string to write to the temporary file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The absolute path to the file.
|
||||||
|
"""
|
||||||
|
fi, path = tempfile.mkstemp()
|
||||||
|
with os.fdopen(fi, 'wb') as f:
|
||||||
|
f.write('foo')
|
||||||
|
return path
|
||||||
|
|
||||||
|
def testShell(self):
|
||||||
|
output = self._adb.Shell('echo test', expect_rc=0)
|
||||||
|
self.assertEqual(output.strip(), 'test')
|
||||||
|
output = self._adb.Shell('echo test')
|
||||||
|
self.assertEqual(output.strip(), 'test')
|
||||||
|
self.assertRaises(adb_wrapper.CommandFailedError, self._adb.Shell,
|
||||||
|
'echo test', expect_rc=1)
|
||||||
|
|
||||||
|
def testPushPull(self):
|
||||||
|
path = self._MakeTempFile('foo')
|
||||||
|
device_path = '/data/local/tmp/testfile.txt'
|
||||||
|
local_tmpdir = os.path.dirname(path)
|
||||||
|
self._adb.Push(path, device_path)
|
||||||
|
self.assertEqual(self._adb.Shell('cat %s' % device_path), 'foo')
|
||||||
|
self._adb.Pull(device_path, local_tmpdir)
|
||||||
|
with open(os.path.join(local_tmpdir, 'testfile.txt'), 'r') as f:
|
||||||
|
self.assertEqual(f.read(), 'foo')
|
||||||
|
|
||||||
|
def testInstall(self):
|
||||||
|
path = self._MakeTempFile('foo')
|
||||||
|
self.assertRaises(adb_wrapper.CommandFailedError, self._adb.Install, path)
|
||||||
|
|
||||||
|
def testForward(self):
|
||||||
|
self.assertRaises(adb_wrapper.CommandFailedError, self._adb.Forward, 0, 0)
|
||||||
|
|
||||||
|
def testUninstall(self):
|
||||||
|
self.assertRaises(adb_wrapper.CommandFailedError, self._adb.Uninstall,
|
||||||
|
'some.nonexistant.package')
|
||||||
|
|
||||||
|
def testRebootWaitForDevice(self):
|
||||||
|
self._adb.Reboot()
|
||||||
|
print 'waiting for device to reboot...'
|
||||||
|
while self._adb.GetState() == 'device':
|
||||||
|
time.sleep(1)
|
||||||
|
self._adb.WaitForDevice()
|
||||||
|
self.assertEqual(self._adb.GetState(), 'device')
|
||||||
|
print 'waiting for package manager...'
|
||||||
|
while 'package:' not in self._adb.Shell('pm path android'):
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def testRootRemount(self):
|
||||||
|
self._adb.Root()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self._adb.Shell('start')
|
||||||
|
break
|
||||||
|
except adb_wrapper.CommandFailedError:
|
||||||
|
time.sleep(1)
|
||||||
|
self._adb.Remount()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Загрузка…
Ссылка в новой задаче