aboutsummaryrefslogtreecommitdiff
path: root/catapult/devil
diff options
context:
space:
mode:
authorChris Craik <ccraik@google.com>2017-06-02 13:39:51 -0700
committerChris Craik <ccraik@google.com>2017-06-05 14:16:20 -0700
commit576fd2a645575980f16b6731dab0f4f150100657 (patch)
treea15abed94b111e7805acbf515dce9584025c0688 /catapult/devil
parentda2dcf2d9eb1a53a9532af894e7c65698b955c22 (diff)
downloadchromium-trace-576fd2a645575980f16b6731dab0f4f150100657.tar.gz
Update to latest catapult (6171fd4d)
Test: ./systrace.py Change-Id: I32e0de8056cf76f834af6de6e2cb1ff1bc845a69
Diffstat (limited to 'catapult/devil')
-rw-r--r--catapult/devil/devil/android/battery_utils.py12
-rw-r--r--catapult/devil/devil/android/device_errors.py16
-rw-r--r--catapult/devil/devil/android/device_test_case.py4
-rw-r--r--catapult/devil/devil/android/device_utils.py43
-rwxr-xr-xcatapult/devil/devil/android/device_utils_devicetest.py9
-rwxr-xr-xcatapult/devil/devil/android/device_utils_test.py26
-rw-r--r--catapult/devil/devil/android/forwarder.py9
-rw-r--r--catapult/devil/devil/android/sdk/adb_wrapper.py26
-rwxr-xr-xcatapult/devil/devil/android/sdk/adb_wrapper_devicetest.py8
-rw-r--r--catapult/devil/devil/android/settings.py40
-rwxr-xr-xcatapult/devil/devil/android/tools/device_monitor.py27
-rwxr-xr-xcatapult/devil/devil/android/tools/device_recovery.py9
-rwxr-xr-xcatapult/devil/devil/android/tools/device_status.py9
-rwxr-xr-xcatapult/devil/devil/android/tools/provision_devices.py118
-rw-r--r--catapult/devil/devil/devil_dependencies.json8
-rw-r--r--catapult/devil/devil/utils/cmd_helper.py119
-rwxr-xr-xcatapult/devil/devil/utils/cmd_helper_test.py4
-rwxr-xr-xcatapult/devil/devil/utils/find_usb_devices.py8
-rw-r--r--catapult/devil/devil/utils/usb_hubs.py31
19 files changed, 358 insertions, 168 deletions
diff --git a/catapult/devil/devil/android/battery_utils.py b/catapult/devil/devil/android/battery_utils.py
index 3b225aa1..068c187c 100644
--- a/catapult/devil/devil/android/battery_utils.py
+++ b/catapult/devil/devil/android/battery_utils.py
@@ -140,6 +140,18 @@ _DEVICE_PROFILES = [
'voltage': '/sys/class/power_supply/max77843-fuelgauge/voltage_now',
'current': '/sys/class/power_supply/max77843-charger/current_now',
},
+ { # Cherry Mobile One
+ 'name': ['W6210 (4560MMX_b fingerprint)'],
+ 'enable_command': (
+ 'echo "0 0" > /proc/mtk_battery_cmd/current_cmd && '
+ 'dumpsys battery reset'),
+ 'disable_command': (
+ 'echo "0 1" > /proc/mtk_battery_cmd/current_cmd && '
+ 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
+ 'charge_counter': None,
+ 'voltage': None,
+ 'current': None,
+},
]
# The list of useful dumpsys columns.
diff --git a/catapult/devil/devil/android/device_errors.py b/catapult/devil/devil/android/device_errors.py
index 568e4974..57f36150 100644
--- a/catapult/devil/devil/android/device_errors.py
+++ b/catapult/devil/devil/android/device_errors.py
@@ -4,6 +4,22 @@
"""
Exception classes raised by AdbWrapper and DeviceUtils.
+
+The class hierarchy for device exceptions is:
+
+ base_error.BaseError
+ +-- CommandFailedError
+ | +-- AdbCommandFailedError
+ | | +-- AdbShellCommandFailedError
+ | +-- FastbootCommandFailedError
+ | +-- DeviceVersionError
+ | +-- DeviceChargingError
+ +-- CommandTimeoutError
+ +-- DeviceUnreachableError
+ +-- NoDevicesError
+ +-- MultipleDevicesError
+ +-- NoAdbError
+
"""
from devil import base_error
diff --git a/catapult/devil/devil/android/device_test_case.py b/catapult/devil/devil/android/device_test_case.py
index b995fa6f..1148b544 100644
--- a/catapult/devil/devil/android/device_test_case.py
+++ b/catapult/devil/devil/android/device_test_case.py
@@ -22,7 +22,8 @@ def PrepareDevices(*_args):
d.WaitUntilFullyBooted(timeout=5, retries=0)
live_devices.append(str(d))
except (device_errors.CommandFailedError,
- device_errors.CommandTimeoutError):
+ device_errors.CommandTimeoutError,
+ device_errors.DeviceUnreachableError):
pass
with _devices_lock:
_devices.update(set(live_devices))
@@ -51,4 +52,3 @@ class DeviceTestCase(unittest.TestCase):
with _devices_lock:
_devices.add(self.serial)
_devices_condition.notify()
-
diff --git a/catapult/devil/devil/android/device_utils.py b/catapult/devil/devil/android/device_utils.py
index 7ba1b514..51f71949 100644
--- a/catapult/devil/devil/android/device_utils.py
+++ b/catapult/devil/devil/android/device_utils.py
@@ -39,7 +39,6 @@ from devil.android import logcat_monitor
from devil.android import md5sum
from devil.android.constants import chrome
from devil.android.sdk import adb_wrapper
-from devil.android.sdk import gce_adb_wrapper
from devil.android.sdk import intent
from devil.android.sdk import keyevent
from devil.android.sdk import split_select
@@ -131,7 +130,6 @@ _CURRENT_FOCUS_CRASH_RE = re.compile(
r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
_GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]')
-_IPV4_ADDRESS_RE = re.compile(r'([0-9]{1,3}\.){3}[0-9]{1,3}\:[0-9]{4,5}')
# Regex to parse the long (-l) output of 'ls' command, c.f.
# https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446
@@ -254,18 +252,11 @@ def _JoinLines(lines):
return ''.join(s for line in lines for s in (line, '\n'))
-def _IsGceInstance(serial):
- return _IPV4_ADDRESS_RE.match(serial)
-
-
def _CreateAdbWrapper(device):
- if _IsGceInstance(str(device)):
- return gce_adb_wrapper.GceAdbWrapper(str(device))
+ if isinstance(device, adb_wrapper.AdbWrapper):
+ return device
else:
- if isinstance(device, adb_wrapper.AdbWrapper):
- return device
- else:
- return adb_wrapper.AdbWrapper(device)
+ return adb_wrapper.AdbWrapper(device)
def _FormatPartialOutputError(output):
@@ -453,13 +444,27 @@ class DeviceUtils(object):
CommandFailedError if root could not be enabled.
CommandTimeoutError on timeout.
"""
- if self.IsUserBuild():
- raise device_errors.CommandFailedError(
- 'Cannot enable root in user builds.', str(self))
if 'needs_su' in self._cache:
del self._cache['needs_su']
- self.adb.Root()
- self.WaitUntilFullyBooted()
+
+ try:
+ self.adb.Root()
+ except device_errors.AdbCommandFailedError:
+ if self.IsUserBuild():
+ raise device_errors.CommandFailedError(
+ 'Unable to root device with user build.', str(self))
+ else:
+ raise # Failed probably due to some other reason.
+
+ def device_online_with_root():
+ try:
+ self.adb.WaitForDevice()
+ return self.GetProp('service.adb.root', cache=False) == '1'
+ except (device_errors.AdbCommandFailedError,
+ device_errors.DeviceUnreachableError):
+ return False
+
+ timeout_retry.WaitFor(device_online_with_root, wait_period=1)
@decorators.WithTimeoutAndRetriesFromInstance()
def IsUserBuild(self, timeout=None, retries=None):
@@ -1190,8 +1195,8 @@ class DeviceUtils(object):
CommandTimeoutError on timeout.
DeviceUnreachableError on missing device.
"""
- cmd = 'p=%s;if [[ "$(ps)" = *$p* ]]; then am force-stop $p; fi'
- self.RunShellCommand(cmd % package, shell=True, check_return=True)
+ if self.GetPids(package):
+ self.RunShellCommand(['am', 'force-stop', package], check_return=True)
@decorators.WithTimeoutAndRetriesFromInstance()
def ClearApplicationState(
diff --git a/catapult/devil/devil/android/device_utils_devicetest.py b/catapult/devil/devil/android/device_utils_devicetest.py
index e69cc909..932e2782 100755
--- a/catapult/devil/devil/android/device_utils_devicetest.py
+++ b/catapult/devil/devil/android/device_utils_devicetest.py
@@ -224,6 +224,15 @@ class DeviceUtilsPushDeleteFilesTest(device_test_case.DeviceTestCase):
new_adbd_pid = get_adbd_pid()
self.assertNotEqual(old_adbd_pid, new_adbd_pid)
+ def testEnableRoot(self):
+ self.device.SetProp('service.adb.root', '0')
+ self.device.RestartAdbd()
+ self.assertFalse(self.device.HasRoot())
+ self.assertIn(self.device.GetProp('service.adb.root'), ('', '0'))
+ self.device.EnableRoot()
+ self.assertTrue(self.device.HasRoot())
+ self.assertEquals(self.device.GetProp('service.adb.root'), '1')
+
if __name__ == '__main__':
unittest.main()
diff --git a/catapult/devil/devil/android/device_utils_test.py b/catapult/devil/devil/android/device_utils_test.py
index 24902096..ebd3c628 100755
--- a/catapult/devil/devil/android/device_utils_test.py
+++ b/catapult/devil/devil/android/device_utils_test.py
@@ -315,22 +315,23 @@ class DeviceUtilsEnableRootTest(DeviceUtilsTest):
def testEnableRoot_succeeds(self):
with self.assertCalls(
- (self.call.device.IsUserBuild(), False),
- self.call.adb.Root(),
- self.call.device.WaitUntilFullyBooted()):
+ self.call.adb.Root(),
+ self.call.adb.WaitForDevice(),
+ (self.call.device.GetProp('service.adb.root', cache=False), '1')):
self.device.EnableRoot()
def testEnableRoot_userBuild(self):
with self.assertCalls(
+ (self.call.adb.Root(), self.AdbCommandError()),
(self.call.device.IsUserBuild(), True)):
with self.assertRaises(device_errors.CommandFailedError):
self.device.EnableRoot()
def testEnableRoot_rootFails(self):
with self.assertCalls(
- (self.call.device.IsUserBuild(), False),
- (self.call.adb.Root(), self.CommandError())):
- with self.assertRaises(device_errors.CommandFailedError):
+ (self.call.adb.Root(), self.AdbCommandError()),
+ (self.call.device.IsUserBuild(), False)):
+ with self.assertRaises(device_errors.AdbCommandFailedError):
self.device.EnableRoot()
@@ -1539,10 +1540,17 @@ class DeviceUtilsGoHomeTest(DeviceUtilsTest):
class DeviceUtilsForceStopTest(DeviceUtilsTest):
def testForceStop(self):
+ with self.assertCalls(
+ (self.call.device.GetPids('test.package'), {'test.package': [1111]}),
+ (self.call.device.RunShellCommand(
+ ['am', 'force-stop', 'test.package'],
+ check_return=True),
+ ['Success'])):
+ self.device.ForceStop('test.package')
+
+ def testForceStop_NoProcessFound(self):
with self.assertCall(
- self.call.adb.Shell('p=test.package;if [[ "$(ps)" = *$p* ]]; then '
- 'am force-stop $p; fi'),
- ''):
+ self.call.device.GetPids('test.package'), {}):
self.device.ForceStop('test.package')
diff --git a/catapult/devil/devil/android/forwarder.py b/catapult/devil/devil/android/forwarder.py
index 244f555a..76c56ecc 100644
--- a/catapult/devil/devil/android/forwarder.py
+++ b/catapult/devil/devil/android/forwarder.py
@@ -46,7 +46,8 @@ def _LogMapFailureDiagnostics(device):
logger.info('Last 50 lines of logcat:')
for logcat_line in device.adb.Logcat(dump=True)[-50:]:
logger.info(' %s', logcat_line)
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
# Grabbing the device forwarder log is also best-effort. Ignore all errors.
logger.warning('Failed to get the contents of the logcat.')
@@ -57,7 +58,8 @@ def _LogMapFailureDiagnostics(device):
for line in ps_out:
if 'device_forwarder' in line:
logger.info(' %s', line)
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.warning('Failed to list currently running device_forwarder '
'instances.')
@@ -154,7 +156,8 @@ class Forwarder(object):
if exit_code != 0:
try:
instance._KillDeviceLocked(device, tool)
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
# We don't want the failure to kill the device forwarder to
# supersede the original failure to map.
logging.warning(
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper.py b/catapult/devil/devil/android/sdk/adb_wrapper.py
index 7f6b8d95..e2ca0139 100644
--- a/catapult/devil/devil/android/sdk/adb_wrapper.py
+++ b/catapult/devil/devil/android/sdk/adb_wrapper.py
@@ -37,6 +37,7 @@ DEFAULT_RETRIES = 2
_ADB_VERSION_RE = re.compile(r'Android Debug Bridge version (\d+\.\d+\.\d+)')
_EMULATOR_RE = re.compile(r'^emulator-[0-9]+$')
+_DEVICE_NOT_FOUND_RE = re.compile(r"error: device '(?P<serial>.+)' not found")
_READY_STATE = 'device'
_VERITY_DISABLE_RE = re.compile(r'Verity (already )?disabled')
_VERITY_ENABLE_RE = re.compile(r'Verity (already )?enabled')
@@ -135,7 +136,7 @@ class AdbWrapper(object):
Example of use:
with PersistentShell('123456789') as pshell:
pshell.RunCommand('which ls')
- pshell.RunCommandAndClose('echo TEST')
+ pshell.RunCommand('echo TEST', close=True)
'''
def __init__(self, serial):
"""Initialization function:
@@ -253,14 +254,17 @@ class AdbWrapper(object):
else:
raise
- if status != 0:
- raise device_errors.AdbCommandFailedError(
- args, output, status, device_serial)
- # 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.startswith('error:'):
- raise device_errors.AdbCommandFailedError(args, output)
+ # Best effort to catch errors from adb; unfortunately adb is very
+ # inconsistent with error reporting so many command failures present
+ # differently.
+ if status != 0 or (check_error and output.startswith('error:')):
+ m = _DEVICE_NOT_FOUND_RE.match(output)
+ if m is not None and m.group('serial') == device_serial:
+ raise device_errors.DeviceUnreachableError(device_serial)
+ else:
+ raise device_errors.AdbCommandFailedError(
+ args, output, status, device_serial)
+
return output
# pylint: enable=unused-argument
@@ -625,7 +629,9 @@ class AdbWrapper(object):
if not allow_rebind:
cmd.append('--no-rebind')
cmd.extend([str(local), str(remote)])
- self._RunDeviceAdbCmd(cmd, timeout, retries)
+ output = self._RunDeviceAdbCmd(cmd, timeout, retries).strip()
+ if output:
+ logger.warning('Unexpected output from "adb forward": %s', output)
def ForwardRemove(self, local, timeout=DEFAULT_TIMEOUT,
retries=DEFAULT_RETRIES):
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py b/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py
index d97d56a1..b0ccb24a 100755
--- a/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py
+++ b/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py
@@ -38,6 +38,11 @@ class TestAdbWrapper(device_test_case.DeviceTestCase):
f.write(contents)
return path
+ def testDeviceUnreachable(self):
+ with self.assertRaises(device_errors.DeviceUnreachableError):
+ bad_adb = adb_wrapper.AdbWrapper('device_gone')
+ bad_adb.Shell('echo test')
+
def testShell(self):
output = self._adb.Shell('echo test', expect_status=0)
self.assertEqual(output.strip(), 'test')
@@ -46,7 +51,6 @@ class TestAdbWrapper(device_test_case.DeviceTestCase):
with self.assertRaises(device_errors.AdbCommandFailedError):
self._adb.Shell('echo test', expect_status=1)
- @unittest.skip("https://github.com/catapult-project/catapult/issues/2574")
def testPersistentShell(self):
# We need to access the device serial number here in order
# to create the persistent shell.
@@ -109,7 +113,7 @@ class TestAdbWrapper(device_test_case.DeviceTestCase):
try:
self._adb.Shell('start')
break
- except device_errors.AdbCommandFailedError:
+ except device_errors.DeviceUnreachableError:
time.sleep(1)
self._adb.Remount()
diff --git a/catapult/devil/devil/android/settings.py b/catapult/devil/devil/android/settings.py
index 886b2661..d053d2a7 100644
--- a/catapult/devil/devil/android/settings.py
+++ b/catapult/devil/devil/android/settings.py
@@ -138,31 +138,25 @@ class ContentSettings(dict):
raise ValueError('Unsupported type %s' % type(value))
def iteritems(self):
- # Example row:
- # 'Row: 0 _id=13, name=logging_id2, value=-1fccbaa546705b05'
for row in self._device.RunShellCommand(
['content', 'query', '--uri', 'content://%s' % self._table],
check_return=True, as_root=True):
- fields = row.split(', ')
- key = None
- value = None
- for field in fields:
- k, _, v = field.partition('=')
- if k == 'name':
- key = v
- elif k == 'value':
- value = v
+ key, value = _ParseContentRow(row)
if not key:
continue
- if not value:
- value = ''
yield key, value
def __getitem__(self, key):
- return self._device.RunShellCommand(
+ query_row = self._device.RunShellCommand(
['content', 'query', '--uri', 'content://%s' % self._table,
'--where', "name='%s'" % key],
- check_return=True, as_root=True).strip()
+ check_return=True, as_root=True, single_line=True)
+ parsed_key, parsed_value = _ParseContentRow(query_row)
+ if parsed_key is None:
+ raise KeyError('key=%s not found' % key)
+ if parsed_key != key:
+ raise KeyError('Expected key=%s, but got key=%s' % (key, parsed_key))
+ return parsed_value
def __setitem__(self, key, value):
if key in self:
@@ -271,3 +265,19 @@ commit transaction;""" % {
['sqlite3', db, cmd], check_return=True, as_root=True)
if output_msg:
logger.info(' '.join(output_msg))
+
+
+def _ParseContentRow(row):
+ """Parse key, value entries from a row string."""
+ # Example row:
+ # 'Row: 0 _id=13, name=logging_id2, value=-1fccbaa546705b05'
+ fields = row.split(', ')
+ key = None
+ value = ''
+ for field in fields:
+ k, _, v = field.partition('=')
+ if k == 'name':
+ key = v
+ elif k == 'value':
+ value = v
+ return key, value
diff --git a/catapult/devil/devil/android/tools/device_monitor.py b/catapult/devil/devil/android/tools/device_monitor.py
index 49214a92..d0f7521c 100755
--- a/catapult/devil/devil/android/tools/device_monitor.py
+++ b/catapult/devil/devil/android/tools/device_monitor.py
@@ -43,20 +43,14 @@ CPU_TEMP_SENSORS = [
]
DEVICE_FILE_VERSION = 1
-# TODO(bpastene): Remove the old file once sysmon has been updated to read the
-# new status file.
-DEVICE_FILES = [
- os.path.join(os.path.expanduser('~'), 'android_device_status.json'),
- os.path.join(
- os.path.expanduser('~'), '.android',
- '%s__android_device_status.json' % socket.gethostname().split('.')[0]
- ),
-]
+DEVICE_FILE = os.path.join(
+ os.path.expanduser('~'), '.android',
+ '%s__android_device_status.json' % socket.gethostname().split('.')[0])
MEM_INFO_REGEX = re.compile(r'.*?\:\s*(\d+)\s*kB') # ex: 'MemTotal: 185735 kB'
-def get_device_status(device):
+def get_device_status_unsafe(device):
"""Polls the given device for various info.
Returns: A dict of the following format:
@@ -159,6 +153,14 @@ def get_device_status(device):
return status
+def get_device_status(device):
+ try:
+ status = get_device_status_unsafe(device)
+ except device_errors.DeviceUnreachableError:
+ status = {'state': 'offline'}
+ return status
+
+
def get_all_status(blacklist):
status_dict = {
'version': DEVICE_FILE_VERSION,
@@ -220,9 +222,8 @@ def main(argv):
while True:
start = time.time()
status_dict = get_all_status(blacklist)
- for device_file in DEVICE_FILES:
- with open(device_file, 'wb') as f:
- json.dump(status_dict, f, indent=2, sort_keys=True)
+ with open(DEVICE_FILE, 'wb') as f:
+ json.dump(status_dict, f, indent=2, sort_keys=True)
logging.info('Got status of all devices in %.2fs.', time.time() - start)
time.sleep(60)
diff --git a/catapult/devil/devil/android/tools/device_recovery.py b/catapult/devil/devil/android/tools/device_recovery.py
index 57857b1e..80c78d25 100755
--- a/catapult/devil/devil/android/tools/device_recovery.py
+++ b/catapult/devil/devil/android/tools/device_recovery.py
@@ -64,7 +64,8 @@ def RecoverDevice(device, blacklist, should_reboot=lambda device: True):
try:
device.WaitUntilFullyBooted(retries=0)
except (device_errors.CommandTimeoutError,
- device_errors.CommandFailedError):
+ device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failure while waiting for %s. '
'Attempting to recover.', str(device))
try:
@@ -83,7 +84,8 @@ def RecoverDevice(device, blacklist, should_reboot=lambda device: True):
# exception willbe thrown at that level.
device.adb.Shell('echo b > /proc/sysrq-trigger', expect_status=None,
timeout=5, retries=0)
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failed to reboot %s.', str(device))
if blacklist:
blacklist.Extend([device.adb.GetDeviceSerial()],
@@ -97,7 +99,8 @@ def RecoverDevice(device, blacklist, should_reboot=lambda device: True):
try:
device.WaitUntilFullyBooted(
retries=0, timeout=device.REBOOT_DEFAULT_TIMEOUT)
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failure while waiting for %s.', str(device))
if blacklist:
blacklist.Extend([device.adb.GetDeviceSerial()],
diff --git a/catapult/devil/devil/android/tools/device_status.py b/catapult/devil/devil/android/tools/device_status.py
index 167d66c4..159c6c5d 100755
--- a/catapult/devil/devil/android/tools/device_status.py
+++ b/catapult/devil/devil/android/tools/device_status.py
@@ -51,7 +51,8 @@ def _BatteryStatus(device, blacklist):
if blacklist:
blacklist.Extend([device.adb.GetDeviceSerial()], reason='low_battery')
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failed to get battery information for %s',
str(device))
@@ -66,7 +67,8 @@ def _IMEISlice(device):
m = _RE_DEVICE_ID.match(l)
if m:
imei_slice = m.group(1)[-6:]
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failed to get IMEI slice for %s', str(device))
return imei_slice
@@ -143,7 +145,8 @@ def DeviceStatus(devices, blacklist):
'wifi_ip': wifi_ip,
})
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failure while getting device status for %s.',
str(device))
if blacklist:
diff --git a/catapult/devil/devil/android/tools/provision_devices.py b/catapult/devil/devil/android/tools/provision_devices.py
index 7374290c..9359f113 100755
--- a/catapult/devil/devil/android/tools/provision_devices.py
+++ b/catapult/devil/devil/android/tools/provision_devices.py
@@ -37,7 +37,6 @@ from devil.android import device_errors
from devil.android import device_temp_file
from devil.android import device_utils
from devil.android import settings
-from devil.android.constants import chrome
from devil.android.sdk import adb_wrapper
from devil.android.sdk import intent
from devil.android.sdk import keyevent
@@ -86,11 +85,13 @@ def ProvisionDevices(
reboot_timeout=None,
remove_system_webview=False,
system_app_remove_list=None,
+ system_package_remove_list=None,
wipe=True):
blacklist = (device_blacklist.Blacklist(blacklist_file)
if blacklist_file
else None)
system_app_remove_list = system_app_remove_list or []
+ system_package_remove_list = system_package_remove_list or []
try:
devices = script_common.GetDevices(devices, blacklist)
except device_errors.NoDevicesError:
@@ -118,7 +119,7 @@ def ProvisionDevices(
if max_battery_temp:
steps.append(ProvisionStep(
- lambda d: WaitForTemperature(d, max_battery_temp)))
+ lambda d: WaitForBatteryTemperature(d, max_battery_temp)))
if min_battery_level:
steps.append(ProvisionStep(
@@ -127,9 +128,10 @@ def ProvisionDevices(
if remove_system_webview:
system_app_remove_list.extend(_SYSTEM_WEBVIEW_NAMES)
- if system_app_remove_list:
+ if system_app_remove_list or system_package_remove_list:
steps.append(ProvisionStep(
- lambda d: RemoveSystemApps(d, system_app_remove_list)))
+ lambda d: RemoveSystemApps(
+ d, system_app_remove_list, system_package_remove_list)))
steps.append(ProvisionStep(SetDate))
steps.append(ProvisionStep(CheckExternalStorage))
@@ -170,7 +172,8 @@ def ProvisionDevice(device, steps, blacklist, reboot_timeout=None):
if blacklist:
blacklist.Extend([str(device)], reason='provision_timeout')
- except device_errors.CommandFailedError:
+ except (device_errors.CommandFailedError,
+ device_errors.DeviceUnreachableError):
logger.exception('Failed to provision device %s. Adding to blacklist.',
str(device))
if blacklist:
@@ -182,9 +185,12 @@ def Wipe(device, adb_key_files=None):
device.build_version_sdk >= version_codes.MARSHMALLOW):
WipeChromeData(device)
- package = "com.google.android.gms"
- version_name = device.GetApplicationVersion(package)
- logger.info("Version name for %s is %s", package, version_name)
+ package = 'com.google.android.gms'
+ if device.GetApplicationPaths(package):
+ version_name = device.GetApplicationVersion(package)
+ logger.info('Version name for %s is %s', package, version_name)
+ else:
+ logger.info('Package %s is not installed', package)
else:
WipeDevice(device, adb_key_files)
@@ -208,16 +214,14 @@ def WipeChromeData(device):
"""
try:
if device.IsUserBuild():
- _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX,
- chrome.PACKAGE_INFO['chrome_stable'].package)
+ _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX)
device.RunShellCommand('rm -rf %s/*' % device.GetExternalStoragePath(),
shell=True, check_return=True)
device.RunShellCommand('rm -rf /data/local/tmp/*',
shell=True, check_return=True)
else:
device.EnableRoot()
- _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX,
- chrome.PACKAGE_INFO['chrome_stable'].package)
+ _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX)
_WipeUnderDirIfMatch(device, '/data/app-lib/', _CHROME_PACKAGE_REGEX)
_WipeUnderDirIfMatch(device, '/data/tombstones/', _TOMBSTONE_REGEX)
@@ -232,7 +236,7 @@ def WipeChromeData(device):
'Attempting to continue.')
-def _UninstallIfMatch(device, pattern, app_to_keep):
+def _UninstallIfMatch(device, pattern):
installed_packages = device.RunShellCommand(
['pm', 'list', 'packages'], check_return=True)
installed_system_packages = [
@@ -240,9 +244,8 @@ def _UninstallIfMatch(device, pattern, app_to_keep):
['pm', 'list', 'packages', '-s'], check_return=True)]
for package_output in installed_packages:
package = package_output.split(":")[1]
- if pattern.match(package) and not package == app_to_keep:
- if not device.IsUserBuild() or package not in installed_system_packages:
- device.Uninstall(package)
+ if pattern.match(package) and package not in installed_system_packages:
+ device.Uninstall(package)
def _WipeUnderDirIfMatch(device, path, pattern):
@@ -349,43 +352,55 @@ def DisableNetwork(device):
def DisableSystemChrome(device):
# The system chrome version on the device interferes with some tests.
device.RunShellCommand(['pm', 'disable', 'com.android.chrome'],
- check_return=True)
+ as_root=True, check_return=True)
+
+
+def _FindSystemPackagePaths(device, system_package_list):
+ found_paths = []
+ for system_package in system_package_list:
+ found_paths.extend(device.GetApplicationPaths(system_package))
+ return [p for p in found_paths if p.startswith('/system/')]
-def _RemoveSystemApp(device, system_app):
+def _FindSystemAppPaths(device, system_app_list):
found_paths = []
- for directory in _SYSTEM_APP_DIRECTORIES:
- path = os.path.join(directory, system_app)
- if device.PathExists(path):
- found_paths.append(path)
- if not found_paths:
- logger.warning('Could not find install location for system app %s',
- system_app)
- device.RemovePath(found_paths, force=True, recursive=True)
-
-def RemoveSystemApps(device, system_app_remove_list):
+ for system_app in system_app_list:
+ for directory in _SYSTEM_APP_DIRECTORIES:
+ path = os.path.join(directory, system_app)
+ if device.PathExists(path):
+ found_paths.append(path)
+ return found_paths
+
+
+def RemoveSystemApps(
+ device, system_app_remove_list, system_package_remove_list):
"""Attempts to remove the provided system apps from the given device.
Arguments:
device: The device to remove the system apps from.
system_app_remove_list: A list of app names to remove, e.g.
['WebViewGoogle', 'GoogleVrCore']
+ system_package_remove_list: A list of app packages to remove, e.g.
+ ['com.google.android.webview']
"""
device.EnableRoot()
if device.HasRoot():
- # Disable Marshmallow's Verity security feature
- if device.build_version_sdk >= version_codes.MARSHMALLOW:
- logger.info('Disabling Verity on %s', device.serial)
- device.adb.DisableVerity()
- device.Reboot()
- device.WaitUntilFullyBooted()
- device.EnableRoot()
-
- device.adb.Remount()
- device.RunShellCommand(['stop'], check_return=True)
- for system_app in system_app_remove_list:
- _RemoveSystemApp(device, system_app)
- device.RunShellCommand(['start'], check_return=True)
+ system_app_paths = (
+ _FindSystemAppPaths(device, system_app_remove_list) +
+ _FindSystemPackagePaths(device, system_package_remove_list))
+ if system_app_paths:
+ # Disable Marshmallow's Verity security feature
+ if device.build_version_sdk >= version_codes.MARSHMALLOW:
+ logger.info('Disabling Verity on %s', device.serial)
+ device.adb.DisableVerity()
+ device.Reboot()
+ device.WaitUntilFullyBooted()
+ device.EnableRoot()
+
+ device.adb.Remount()
+ device.RunShellCommand(['stop'], check_return=True)
+ device.RemovePath(system_app_paths, force=True, recursive=True)
+ device.RunShellCommand(['start'], check_return=True)
else:
raise device_errors.CommandFailedError(
'Failed to remove system apps from non-rooted device', str(device))
@@ -432,7 +447,7 @@ def WaitForCharge(device, min_battery_level):
battery.ChargeDeviceToLevel(min_battery_level)
-def WaitForTemperature(device, max_battery_temp):
+def WaitForBatteryTemperature(device, max_battery_temp):
try:
battery = battery_utils.BatteryUtils(device)
battery.LetBatteryCoolToTemperature(max_battery_temp)
@@ -482,8 +497,11 @@ def SetDate(device):
raise device_errors.CommandFailedError(
'Failed to set date & time.', device_serial=str(device))
device.EnableRoot()
+ # The following intent can take a bit to complete when ran shortly after
+ # device boot-up.
device.BroadcastIntent(
- intent.Intent(action='android.intent.action.TIME_SET'))
+ intent.Intent(action='android.intent.action.TIME_SET'),
+ timeout=180)
def LogDeviceProperties(device):
@@ -550,7 +568,8 @@ def main(raw_args):
help='disable Java property asserts and JNI checking')
parser.add_argument(
'--disable-system-chrome', action='store_true',
- help='Disable the system chrome from devices.')
+ help='DEPRECATED: use --remove-system-packages com.android.google '
+ 'Disable the system chrome from devices.')
parser.add_argument(
'--emulators', action='store_true',
help='provision only emulators and ignore usb devices '
@@ -572,10 +591,16 @@ def main(raw_args):
'(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
parser.add_argument(
'--remove-system-apps', nargs='*', dest='system_app_remove_list',
- help='the names of system apps to remove')
+ help='DEPRECATED: use --remove-system-packages instead. '
+ 'The names of system apps to remove. ')
+ parser.add_argument(
+ '--remove-system-packages', nargs='*', dest='system_package_remove_list',
+ help='The names of system packages to remove.')
parser.add_argument(
'--remove-system-webview', action='store_true',
- help='Remove the system webview from devices.')
+ help='DEPRECATED: use --remove-system-packages '
+ 'com.google.android.webview com.android.webview '
+ 'Remove the system webview from devices.')
parser.add_argument(
'--skip-wipe', action='store_true', default=False,
help='do not wipe device data during provisioning')
@@ -627,6 +652,7 @@ def main(raw_args):
reboot_timeout=args.reboot_timeout,
remove_system_webview=args.remove_system_webview,
system_app_remove_list=args.system_app_remove_list,
+ system_package_remove_list=args.system_package_remove_list,
wipe=not args.skip_wipe and not args.emulators)
except (device_errors.DeviceUnreachableError, device_errors.NoDevicesError):
logging.exception('Unable to provision local devices.')
diff --git a/catapult/devil/devil/devil_dependencies.json b/catapult/devil/devil/devil_dependencies.json
index e5522930..bed6fe10 100644
--- a/catapult/devil/devil/devil_dependencies.json
+++ b/catapult/devil/devil/devil_dependencies.json
@@ -66,11 +66,11 @@
"cloud_storage_bucket": "chromium-telemetry",
"file_info": {
"android_arm64-v8a": {
- "cloud_storage_hash": "90cc60cefb497512d95d774045146754c477b433",
+ "cloud_storage_hash": "f222268d8442979240d1b18de00911a49e548daa",
"download_path": "../bin/deps/android/arm64-v8a/bin/forwarder_device"
},
"android_armeabi-v7a": {
- "cloud_storage_hash": "3a5ade8fbaffea3b0670bffaa845288db5e4d567",
+ "cloud_storage_hash": "c15267bf01c26eb0aea4f61c780bbba460c5c981",
"download_path": "../bin/deps/android/armeabi-v7a/bin/forwarder_device"
}
}
@@ -80,7 +80,7 @@
"cloud_storage_bucket": "chromium-telemetry",
"file_info": {
"linux2_x86_64": {
- "cloud_storage_hash": "63653293098d7fe17aa115b147c8d9aa253b0912",
+ "cloud_storage_hash": "8fe69994b670f028484eed475dbffc838c8a57f7",
"download_path": "../bin/deps/linux2/x86_64/forwarder_host"
}
}
@@ -124,4 +124,4 @@
}
}
}
-}
+} \ No newline at end of file
diff --git a/catapult/devil/devil/utils/cmd_helper.py b/catapult/devil/devil/utils/cmd_helper.py
index 06c105fc..0b6ccd9b 100644
--- a/catapult/devil/devil/utils/cmd_helper.py
+++ b/catapult/devil/devil/utils/cmd_helper.py
@@ -15,11 +15,6 @@ import subprocess
import sys
import time
-# fcntl is not available on Windows.
-try:
- import fcntl
-except ImportError:
- fcntl = None
logger = logging.getLogger(__name__)
@@ -96,13 +91,15 @@ def ShrinkToSnippet(cmd_parts, var_name, var_value):
def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
# preexec_fn isn't supported on windows.
if sys.platform == 'win32':
+ close_fds = (stdout is None and stderr is None)
preexec_fn = None
else:
+ close_fds = True
preexec_fn = lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)
return subprocess.Popen(
args=args, cwd=cwd, stdout=stdout, stderr=stderr,
- shell=shell, close_fds=True, env=env, preexec_fn=preexec_fn)
+ shell=shell, close_fds=close_fds, env=env, preexec_fn=preexec_fn)
def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
@@ -219,31 +216,11 @@ class TimeoutError(Exception):
return self._output
-def _IterProcessStdout(process, iter_timeout=None, timeout=None,
- buffer_size=4096, poll_interval=1):
- """Iterate over a process's stdout.
-
- This is intentionally not public.
-
- Args:
- process: The process in question.
- iter_timeout: An optional length of time, in seconds, to wait in
- between each iteration. If no output is received in the given
- time, this generator will yield None.
- timeout: An optional length of time, in seconds, during which
- the process must finish. If it fails to do so, a TimeoutError
- will be raised.
- buffer_size: The maximum number of bytes to read (and thus yield) at once.
- poll_interval: The length of time to wait in calls to `select.select`.
- If iter_timeout is set, the remaining length of time in the iteration
- may take precedence.
- Raises:
- TimeoutError: if timeout is set and the process does not complete.
- Yields:
- basestrings of data or None.
- """
-
- assert fcntl, 'fcntl module is required'
+def _IterProcessStdoutFcntl(
+ process, iter_timeout=None, timeout=None, buffer_size=4096,
+ poll_interval=1):
+ """An fcntl-based implementation of _IterProcessStdout."""
+ import fcntl
try:
# Enable non-blocking reads from the child's stdout.
child_fd = process.stdout.fileno()
@@ -287,6 +264,86 @@ def _IterProcessStdout(process, iter_timeout=None, timeout=None,
process.wait()
+def _IterProcessStdoutQueue(
+ process, iter_timeout=None, timeout=None, buffer_size=4096,
+ poll_interval=1):
+ """A Queue.Queue-based implementation of _IterProcessStdout.
+
+ TODO(jbudorick): Evaluate whether this is a suitable replacement for
+ _IterProcessStdoutFcntl on all platforms.
+ """
+ # pylint: disable=unused-argument
+ import Queue
+ import threading
+
+ stdout_queue = Queue.Queue()
+
+ def read_process_stdout():
+ # TODO(jbudorick): Pick an appropriate read size here.
+ while True:
+ try:
+ output_chunk = os.read(process.stdout.fileno(), buffer_size)
+ except IOError:
+ break
+ stdout_queue.put(output_chunk, True)
+ if not output_chunk and process.poll() is not None:
+ break
+
+ reader_thread = threading.Thread(target=read_process_stdout)
+ reader_thread.start()
+
+ end_time = (time.time() + timeout) if timeout else None
+
+ try:
+ while True:
+ if end_time and time.time() > end_time:
+ raise TimeoutError()
+ try:
+ s = stdout_queue.get(True, iter_timeout)
+ if not s:
+ break
+ yield s
+ except Queue.Empty:
+ yield None
+ finally:
+ try:
+ if process.returncode is None:
+ # Make sure the process doesn't stick around if we fail with an
+ # exception.
+ process.kill()
+ except OSError:
+ pass
+ process.wait()
+ reader_thread.join()
+
+
+_IterProcessStdout = (
+ _IterProcessStdoutQueue
+ if sys.platform == 'win32'
+ else _IterProcessStdoutFcntl)
+"""Iterate over a process's stdout.
+
+This is intentionally not public.
+
+Args:
+ process: The process in question.
+ iter_timeout: An optional length of time, in seconds, to wait in
+ between each iteration. If no output is received in the given
+ time, this generator will yield None.
+ timeout: An optional length of time, in seconds, during which
+ the process must finish. If it fails to do so, a TimeoutError
+ will be raised.
+ buffer_size: The maximum number of bytes to read (and thus yield) at once.
+ poll_interval: The length of time to wait in calls to `select.select`.
+ If iter_timeout is set, the remaining length of time in the iteration
+ may take precedence.
+Raises:
+ TimeoutError: if timeout is set and the process does not complete.
+Yields:
+ basestrings of data or None.
+"""
+
+
def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
logfile=None):
"""Executes a subprocess with a timeout.
diff --git a/catapult/devil/devil/utils/cmd_helper_test.py b/catapult/devil/devil/utils/cmd_helper_test.py
index 783c4137..b7fc8ee3 100755
--- a/catapult/devil/devil/utils/cmd_helper_test.py
+++ b/catapult/devil/devil/utils/cmd_helper_test.py
@@ -7,6 +7,7 @@
import unittest
import subprocess
+import sys
import time
from devil import devil_env
@@ -175,11 +176,12 @@ class _MockProcess(object):
# Set up but *do not start* the mocks.
self._mocks = [
- mock.patch('fcntl.fcntl'),
mock.patch('os.read', new=mock_read),
mock.patch('select.select', new=mock_select),
mock.patch('time.time', new=mock_time),
]
+ if sys.platform != 'win32':
+ self._mocks.append(mock.patch('fcntl.fcntl'))
def __enter__(self):
for m in self._mocks:
diff --git a/catapult/devil/devil/utils/find_usb_devices.py b/catapult/devil/devil/utils/find_usb_devices.py
index 0e0f4d56..28a2eb86 100755
--- a/catapult/devil/devil/utils/find_usb_devices.py
+++ b/catapult/devil/devil/utils/find_usb_devices.py
@@ -3,9 +3,15 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import argparse
+import os
import re
import sys
-import argparse
+
+if __name__ == '__main__':
+ sys.path.append(
+ os.path.abspath(os.path.join(os.path.dirname(__file__),
+ '..', '..')))
from devil.utils import cmd_helper
from devil.utils import usb_hubs
diff --git a/catapult/devil/devil/utils/usb_hubs.py b/catapult/devil/devil/utils/usb_hubs.py
index 1b9566a3..b7186940 100644
--- a/catapult/devil/devil/utils/usb_hubs.py
+++ b/catapult/devil/devil/utils/usb_hubs.py
@@ -17,6 +17,11 @@ KEEDOX_LAYOUT = {1:1,
3:3,
4:{1:4, 2:5, 3:6, 4:7}}
+VIA_LAYOUT = {1:1,
+ 2:2,
+ 3:3,
+ 4:{1:4, 2:5, 3:6, 4:7}}
+
class HubType(object):
def __init__(self, id_func, port_mapping):
"""Defines a type of hub.
@@ -139,6 +144,17 @@ def _is_keedox_hub(node):
return False
return '0bda:5411' in node.PortToDevice(4).desc
+def _is_via_hub(node):
+ """Check if a node is a Via Labs hub.
+ The topology of this device is a 4-port hub,
+ with another 4-port hub connected on port 4.
+ """
+ if '2109:2812' not in node.desc:
+ return False
+ if not node.HasPort(4):
+ return False
+ return '2109:2812' in node.PortToDevice(4).desc
+
PLUGABLE_7PORT = HubType(_is_plugable_7port_hub, PLUGABLE_7PORT_LAYOUT)
PLUGABLE_7PORT_USB3_PART2 = HubType(_is_plugable_7port_usb3_part2_hub,
@@ -146,20 +162,23 @@ PLUGABLE_7PORT_USB3_PART2 = HubType(_is_plugable_7port_usb3_part2_hub,
PLUGABLE_7PORT_USB3_PART3 = HubType(_is_plugable_7port_usb3_part3_hub,
PLUGABLE_7PORT_USB3_LAYOUT)
KEEDOX = HubType(_is_keedox_hub, KEEDOX_LAYOUT)
+VIA = HubType(_is_via_hub, VIA_LAYOUT)
ALL_HUBS = [PLUGABLE_7PORT,
PLUGABLE_7PORT_USB3_PART2,
PLUGABLE_7PORT_USB3_PART3,
- KEEDOX]
+ KEEDOX,
+ VIA]
def GetHubType(type_name):
if type_name == 'plugable_7port':
return PLUGABLE_7PORT
- if type_name == 'plugable_7port_usb3_part2':
+ elif type_name == 'plugable_7port_usb3_part2':
return PLUGABLE_7PORT_USB3_PART2
- if type_name == 'plugable_7port_usb3_part3':
+ elif type_name == 'plugable_7port_usb3_part3':
return PLUGABLE_7PORT_USB3_PART3
- if type_name == 'keedox':
+ elif type_name == 'keedox':
return KEEDOX
- else:
- raise ValueError('Invalid hub type')
+ elif type_name == 'via':
+ return VIA
+ raise ValueError('Invalid hub type')