diff options
author | Chris Craik <ccraik@google.com> | 2017-06-02 13:39:51 -0700 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2017-06-05 14:16:20 -0700 |
commit | 576fd2a645575980f16b6731dab0f4f150100657 (patch) | |
tree | a15abed94b111e7805acbf515dce9584025c0688 /catapult/devil | |
parent | da2dcf2d9eb1a53a9532af894e7c65698b955c22 (diff) | |
download | chromium-trace-576fd2a645575980f16b6731dab0f4f150100657.tar.gz |
Update to latest catapult (6171fd4d)
Test: ./systrace.py
Change-Id: I32e0de8056cf76f834af6de6e2cb1ff1bc845a69
Diffstat (limited to 'catapult/devil')
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') |