| Index: build/android/pylib/device/device_utils.py
|
| diff --git a/build/android/pylib/device/device_utils.py b/build/android/pylib/device/device_utils.py
|
| index 9f538e1b0efd9f51ffaa4c1345febe8149edccc6..46aec6fc9df0eb7b94b89628457567a692299b81 100644
|
| --- a/build/android/pylib/device/device_utils.py
|
| +++ b/build/android/pylib/device/device_utils.py
|
| @@ -25,8 +25,10 @@ import zipfile
|
| import pylib.android_commands
|
| from pylib import cmd_helper
|
| from pylib import constants
|
| +from pylib import device_signal
|
| from pylib.device import adb_wrapper
|
| from pylib.device import decorators
|
| +from pylib.device import device_blacklist
|
| from pylib.device import device_errors
|
| from pylib.device import intent
|
| from pylib.device import logcat_monitor
|
| @@ -72,6 +74,7 @@ _CONTROL_CHARGING_COMMANDS = [
|
| },
|
| ]
|
|
|
| +
|
| @decorators.WithExplicitTimeoutAndRetries(
|
| _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
|
| def GetAVDs():
|
| @@ -168,6 +171,7 @@ class DeviceUtils(object):
|
| self._default_timeout = default_timeout
|
| self._default_retries = default_retries
|
| self._cache = {}
|
| + self._client_caches = {}
|
| assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
|
| assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
|
|
|
| @@ -406,7 +410,7 @@ class DeviceUtils(object):
|
| return not self.IsOnline()
|
|
|
| self.adb.Reboot()
|
| - self._cache = {}
|
| + self._ClearCache()
|
| timeout_retry.WaitFor(device_offline, wait_period=1)
|
| if block:
|
| self.WaitUntilFullyBooted(wifi=wifi)
|
| @@ -446,8 +450,8 @@ class DeviceUtils(object):
|
|
|
| @decorators.WithTimeoutAndRetriesFromInstance()
|
| def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
|
| - as_root=False, single_line=False, timeout=None,
|
| - retries=None):
|
| + as_root=False, single_line=False, large_output=False,
|
| + timeout=None, retries=None):
|
| """Run an ADB shell command.
|
|
|
| The command to run |cmd| should be a sequence of program arguments or else
|
| @@ -480,6 +484,8 @@ class DeviceUtils(object):
|
| with root privileges.
|
| single_line: A boolean indicating if only a single line of output is
|
| expected.
|
| + large_output: Uses a work-around for large shell command output. Without
|
| + this large output will be truncated.
|
| timeout: timeout in seconds
|
| retries: number of retries
|
|
|
| @@ -502,15 +508,49 @@ class DeviceUtils(object):
|
| # using double quotes here to allow interpolation of shell variables
|
| return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
|
|
|
| - def do_run(cmd):
|
| + def run(cmd):
|
| + return self.adb.Shell(cmd)
|
| +
|
| + def handle_check_return(cmd):
|
| try:
|
| - return self.adb.Shell(cmd)
|
| + return run(cmd)
|
| except device_errors.AdbCommandFailedError as exc:
|
| if check_return:
|
| raise
|
| else:
|
| return exc.output
|
|
|
| + def handle_large_command(cmd):
|
| + if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
|
| + return handle_check_return(cmd)
|
| + else:
|
| + with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
|
| + self._WriteFileWithPush(script.name, cmd)
|
| + logging.info('Large shell command will be run from file: %s ...',
|
| + cmd[:100])
|
| + return handle_check_return('sh %s' % script.name_quoted)
|
| +
|
| + def handle_large_output(cmd, large_output_mode):
|
| + if large_output_mode:
|
| + with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
|
| + cmd = '%s > %s' % (cmd, large_output_file.name)
|
| + logging.info('Large output mode enabled. Will write output to device '
|
| + ' and read results from file.')
|
| + handle_large_command(cmd)
|
| + return self.ReadFile(large_output_file.name)
|
| + else:
|
| + try:
|
| + return handle_large_command(cmd)
|
| + except device_errors.AdbCommandFailedError as exc:
|
| + if exc.status is None:
|
| + logging.exception('No output found for %s', cmd)
|
| + logging.warning('Attempting to run in large_output mode.')
|
| + logging.warning('Use RunShellCommand(..., large_output=True) for '
|
| + 'shell commands that expect a lot of output.')
|
| + return handle_large_output(cmd, True)
|
| + else:
|
| + raise
|
| +
|
| if not isinstance(cmd, basestring):
|
| cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
|
| if env:
|
| @@ -521,19 +561,9 @@ class DeviceUtils(object):
|
| if as_root and self.NeedsSU():
|
| # "su -c sh -c" allows using shell features in |cmd|
|
| cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd)
|
| - if timeout is None:
|
| - timeout = self._default_timeout
|
|
|
| - if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
|
| - output = do_run(cmd)
|
| - else:
|
| - with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
|
| - self._WriteFileWithPush(script.name, cmd)
|
| - logging.info('Large shell command will be run from file: %s ...',
|
| - cmd[:100])
|
| - output = do_run('sh %s' % script.name_quoted)
|
| + output = handle_large_output(cmd, large_output).splitlines()
|
|
|
| - output = output.splitlines()
|
| if single_line:
|
| if not output:
|
| return ''
|
| @@ -545,38 +575,70 @@ class DeviceUtils(object):
|
| else:
|
| return output
|
|
|
| + def _RunPipedShellCommand(self, script, **kwargs):
|
| + PIPESTATUS_LEADER = 'PIPESTATUS: '
|
| +
|
| + script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER
|
| + kwargs['check_return'] = True
|
| + output = self.RunShellCommand(script, **kwargs)
|
| + pipestatus_line = output[-1]
|
| +
|
| + if not pipestatus_line.startswith(PIPESTATUS_LEADER):
|
| + logging.error('Pipe exit statuses of shell script missing.')
|
| + raise device_errors.AdbShellCommandFailedError(
|
| + script, output, status=None,
|
| + device_serial=self.adb.GetDeviceSerial())
|
| +
|
| + output = output[:-1]
|
| + statuses = [
|
| + int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()]
|
| + if any(statuses):
|
| + raise device_errors.AdbShellCommandFailedError(
|
| + script, output, status=statuses,
|
| + device_serial=self.adb.GetDeviceSerial())
|
| + return output
|
| +
|
| @decorators.WithTimeoutAndRetriesFromInstance()
|
| - def KillAll(self, process_name, signum=9, as_root=False, blocking=False,
|
| - timeout=None, retries=None):
|
| + def KillAll(self, process_name, signum=device_signal.SIGKILL, as_root=False,
|
| + blocking=False, quiet=False, timeout=None, retries=None):
|
| """Kill all processes with the given name on the device.
|
|
|
| Args:
|
| process_name: A string containing the name of the process to kill.
|
| signum: An integer containing the signal number to send to kill. Defaults
|
| - to 9 (SIGKILL).
|
| + to SIGKILL (9).
|
| as_root: A boolean indicating whether the kill should be executed with
|
| root privileges.
|
| blocking: A boolean indicating whether we should wait until all processes
|
| with the given |process_name| are dead.
|
| + quiet: A boolean indicating whether to ignore the fact that no processes
|
| + to kill were found.
|
| timeout: timeout in seconds
|
| retries: number of retries
|
|
|
| + Returns:
|
| + The number of processes attempted to kill.
|
| +
|
| Raises:
|
| - CommandFailedError if no process was killed.
|
| + CommandFailedError if no process was killed and |quiet| is False.
|
| CommandTimeoutError on timeout.
|
| DeviceUnreachableError on missing device.
|
| """
|
| - pids = self._GetPidsImpl(process_name)
|
| + pids = self.GetPids(process_name)
|
| if not pids:
|
| - raise device_errors.CommandFailedError(
|
| - 'No process "%s"' % process_name, str(self))
|
| + if quiet:
|
| + return 0
|
| + else:
|
| + raise device_errors.CommandFailedError(
|
| + 'No process "%s"' % process_name, str(self))
|
|
|
| cmd = ['kill', '-%d' % signum] + pids.values()
|
| self.RunShellCommand(cmd, as_root=as_root, check_return=True)
|
|
|
| if blocking:
|
| + # TODO(perezu): use timeout_retry.WaitFor
|
| wait_period = 0.1
|
| - while self._GetPidsImpl(process_name):
|
| + while self.GetPids(process_name):
|
| time.sleep(wait_period)
|
|
|
| return len(pids)
|
| @@ -791,29 +853,27 @@ class DeviceUtils(object):
|
| if not real_device_path:
|
| return [(host_path, device_path)]
|
|
|
| - host_hash_tuples = md5sum.CalculateHostMd5Sums([real_host_path])
|
| + host_checksums = md5sum.CalculateHostMd5Sums([real_host_path])
|
| device_paths_to_md5 = (
|
| real_device_path if os.path.isfile(real_host_path)
|
| else ('%s/%s' % (real_device_path, os.path.relpath(p, real_host_path))
|
| - for _, p in host_hash_tuples))
|
| - device_hash_tuples = md5sum.CalculateDeviceMd5Sums(
|
| + for p in host_checksums.iterkeys()))
|
| + device_checksums = md5sum.CalculateDeviceMd5Sums(
|
| device_paths_to_md5, self)
|
|
|
| if os.path.isfile(host_path):
|
| - if (not device_hash_tuples
|
| - or device_hash_tuples[0].hash != host_hash_tuples[0].hash):
|
| + host_checksum = host_checksums.get(real_host_path)
|
| + device_checksum = device_checksums.get(real_device_path)
|
| + if host_checksum != device_checksum:
|
| return [(host_path, device_path)]
|
| else:
|
| return []
|
| else:
|
| - device_tuple_dict = dict((d.path, d.hash) for d in device_hash_tuples)
|
| to_push = []
|
| - for host_hash, host_abs_path in (
|
| - (h.hash, h.path) for h in host_hash_tuples):
|
| + for host_abs_path, host_checksum in host_checksums.iteritems():
|
| device_abs_path = '%s/%s' % (
|
| real_device_path, os.path.relpath(host_abs_path, real_host_path))
|
| - if (device_abs_path not in device_tuple_dict
|
| - or device_tuple_dict[device_abs_path] != host_hash):
|
| + if (device_checksums.get(device_abs_path) != host_checksum):
|
| to_push.append((host_abs_path, device_abs_path))
|
| return to_push
|
|
|
| @@ -991,7 +1051,7 @@ class DeviceUtils(object):
|
| else:
|
| logging.warning('Could not determine size of %s.', device_path)
|
|
|
| - if size is None or size <= self._MAX_ADB_OUTPUT_LENGTH:
|
| + if 0 < size <= self._MAX_ADB_OUTPUT_LENGTH:
|
| return _JoinLines(self.RunShellCommand(
|
| ['cat', device_path], as_root=as_root, check_return=True))
|
| elif as_root and self.NeedsSU():
|
| @@ -1326,11 +1386,19 @@ class DeviceUtils(object):
|
| CommandTimeoutError on timeout.
|
| DeviceUnreachableError on missing device.
|
| """
|
| - return self._GetPidsImpl(process_name)
|
| -
|
| - def _GetPidsImpl(self, process_name):
|
| procs_pids = {}
|
| - for line in self.RunShellCommand('ps', check_return=True):
|
| + try:
|
| + ps_output = self._RunPipedShellCommand(
|
| + 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name))
|
| + except device_errors.AdbShellCommandFailedError as e:
|
| + if e.status and isinstance(e.status, list) and not e.status[0]:
|
| + # If ps succeeded but grep failed, there were no processes with the
|
| + # given name.
|
| + return procs_pids
|
| + else:
|
| + raise
|
| +
|
| + for line in ps_output:
|
| try:
|
| ps_data = line.split()
|
| if process_name in ps_data[-1]:
|
| @@ -1402,10 +1470,8 @@ class DeviceUtils(object):
|
| 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean',
|
| 'Private_Dirty')
|
|
|
| - showmap_out = self.RunShellCommand(
|
| - ['showmap', str(pid)], as_root=True, check_return=True)
|
| - if not showmap_out:
|
| - raise device_errors.CommandFailedError('No output from showmap')
|
| + showmap_out = self._RunPipedShellCommand(
|
| + 'showmap %d | grep TOTAL' % int(pid), as_root=True)
|
|
|
| split_totals = showmap_out[-1].split()
|
| if (not split_totals
|
| @@ -1439,155 +1505,6 @@ class DeviceUtils(object):
|
| return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs)
|
|
|
| @decorators.WithTimeoutAndRetriesFromInstance()
|
| - def GetBatteryInfo(self, timeout=None, retries=None):
|
| - """Gets battery info for the device.
|
| -
|
| - Args:
|
| - timeout: timeout in seconds
|
| - retries: number of retries
|
| - Returns:
|
| - A dict containing various battery information as reported by dumpsys
|
| - battery.
|
| - """
|
| - result = {}
|
| - # Skip the first line, which is just a header.
|
| - for line in self.RunShellCommand(
|
| - ['dumpsys', 'battery'], check_return=True)[1:]:
|
| - # If usb charging has been disabled, an extra line of header exists.
|
| - if 'UPDATES STOPPED' in line:
|
| - logging.warning('Dumpsys battery not receiving updates. '
|
| - 'Run dumpsys battery reset if this is in error.')
|
| - elif ':' not in line:
|
| - logging.warning('Unknown line found in dumpsys battery.')
|
| - logging.warning(line)
|
| - else:
|
| - k, v = line.split(': ', 1)
|
| - result[k.strip()] = v.strip()
|
| - return result
|
| -
|
| - @decorators.WithTimeoutAndRetriesFromInstance()
|
| - def GetCharging(self, timeout=None, retries=None):
|
| - """Gets the charging state of the device.
|
| -
|
| - Args:
|
| - timeout: timeout in seconds
|
| - retries: number of retries
|
| - Returns:
|
| - True if the device is charging, false otherwise.
|
| - """
|
| - battery_info = self.GetBatteryInfo()
|
| - for k in ('AC powered', 'USB powered', 'Wireless powered'):
|
| - if (k in battery_info and
|
| - battery_info[k].lower() in ('true', '1', 'yes')):
|
| - return True
|
| - return False
|
| -
|
| - @decorators.WithTimeoutAndRetriesFromInstance()
|
| - def SetCharging(self, enabled, timeout=None, retries=None):
|
| - """Enables or disables charging on the device.
|
| -
|
| - Args:
|
| - enabled: A boolean indicating whether charging should be enabled or
|
| - disabled.
|
| - timeout: timeout in seconds
|
| - retries: number of retries
|
| - """
|
| - if 'charging_config' not in self._cache:
|
| - for c in _CONTROL_CHARGING_COMMANDS:
|
| - if self.FileExists(c['witness_file']):
|
| - self._cache['charging_config'] = c
|
| - break
|
| - else:
|
| - raise device_errors.CommandFailedError(
|
| - 'Unable to find charging commands.')
|
| -
|
| - if enabled:
|
| - command = self._cache['charging_config']['enable_command']
|
| - else:
|
| - command = self._cache['charging_config']['disable_command']
|
| -
|
| - def set_and_verify_charging():
|
| - self.RunShellCommand(command, check_return=True)
|
| - return self.GetCharging() == enabled
|
| -
|
| - timeout_retry.WaitFor(set_and_verify_charging, wait_period=1)
|
| -
|
| - # TODO(rnephew): Make private when all use cases can use the context manager.
|
| - @decorators.WithTimeoutAndRetriesFromInstance()
|
| - def DisableBatteryUpdates(self, timeout=None, retries=None):
|
| - """ Resets battery data and makes device appear like it is not
|
| - charging so that it will collect power data since last charge.
|
| -
|
| - Args:
|
| - timeout: timeout in seconds
|
| - retries: number of retries
|
| - """
|
| - def battery_updates_disabled():
|
| - return self.GetCharging() is False
|
| -
|
| - self.RunShellCommand(
|
| - ['dumpsys', 'batterystats', '--reset'], check_return=True)
|
| - battery_data = self.RunShellCommand(
|
| - ['dumpsys', 'batterystats', '--charged', '--checkin'],
|
| - check_return=True)
|
| - ROW_TYPE_INDEX = 3
|
| - PWI_POWER_INDEX = 5
|
| - for line in battery_data:
|
| - l = line.split(',')
|
| - if (len(l) > PWI_POWER_INDEX and l[ROW_TYPE_INDEX] == 'pwi'
|
| - and l[PWI_POWER_INDEX] != 0):
|
| - raise device_errors.CommandFailedError(
|
| - 'Non-zero pmi value found after reset.')
|
| - self.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '0'],
|
| - check_return=True)
|
| - timeout_retry.WaitFor(battery_updates_disabled, wait_period=1)
|
| -
|
| - # TODO(rnephew): Make private when all use cases can use the context manager.
|
| - @decorators.WithTimeoutAndRetriesFromInstance()
|
| - def EnableBatteryUpdates(self, timeout=None, retries=None):
|
| - """ Restarts device charging so that dumpsys no longer collects power data.
|
| -
|
| - Args:
|
| - timeout: timeout in seconds
|
| - retries: number of retries
|
| - """
|
| - def battery_updates_enabled():
|
| - return self.GetCharging() is True
|
| -
|
| - self.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '1'],
|
| - check_return=True)
|
| - self.RunShellCommand(['dumpsys', 'battery', 'reset'], check_return=True)
|
| - timeout_retry.WaitFor(battery_updates_enabled, wait_period=1)
|
| -
|
| - @contextlib.contextmanager
|
| - def BatteryMeasurement(self, timeout=None, retries=None):
|
| - """Context manager that enables battery data collection. It makes
|
| - the device appear to stop charging so that dumpsys will start collecting
|
| - power data since last charge. Once the with block is exited, charging is
|
| - resumed and power data since last charge is no longer collected.
|
| -
|
| - Only for devices L and higher.
|
| -
|
| - Example usage:
|
| - with BatteryMeasurement():
|
| - browser_actions()
|
| - get_power_data() # report usage within this block
|
| - after_measurements() # Anything that runs after power
|
| - # measurements are collected
|
| -
|
| - Args:
|
| - timeout: timeout in seconds
|
| - retries: number of retries
|
| - """
|
| - if self.build_version_sdk < constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP:
|
| - raise device_errors.CommandFailedError('Device must be L or higher.')
|
| - try:
|
| - self.DisableBatteryUpdates(timeout=timeout, retries=retries)
|
| - yield
|
| - finally:
|
| - self.EnableBatteryUpdates(timeout=timeout, retries=retries)
|
| -
|
| - @decorators.WithTimeoutAndRetriesFromInstance()
|
| def GetDevicePieWrapper(self, timeout=None, retries=None):
|
| """Gets the absolute path to the run_pie wrapper on the device.
|
|
|
| @@ -1622,12 +1539,24 @@ class DeviceUtils(object):
|
|
|
| return self._cache['run_pie']
|
|
|
| + def GetClientCache(self, client_name):
|
| + """Returns client cache."""
|
| + if client_name not in self._client_caches:
|
| + self._client_caches[client_name] = {}
|
| + return self._client_caches[client_name]
|
| +
|
| + def _ClearCache(self):
|
| + """Clears all caches."""
|
| + for client in self._client_caches:
|
| + self._client_caches[client].clear()
|
| + self._cache.clear()
|
| +
|
| @classmethod
|
| def parallel(cls, devices=None, async=False):
|
| """Creates a Parallelizer to operate over the provided list of devices.
|
|
|
| If |devices| is either |None| or an empty list, the Parallelizer will
|
| - operate over all attached devices.
|
| + operate over all attached devices that have not been blacklisted.
|
|
|
| Args:
|
| devices: A list of either DeviceUtils instances or objects from
|
| @@ -1640,11 +1569,25 @@ class DeviceUtils(object):
|
| A Parallelizer operating over |devices|.
|
| """
|
| if not devices:
|
| - devices = adb_wrapper.AdbWrapper.GetDevices()
|
| + devices = cls.HealthyDevices()
|
| if not devices:
|
| raise device_errors.NoDevicesError()
|
| +
|
| devices = [d if isinstance(d, cls) else cls(d) for d in devices]
|
| if async:
|
| return parallelizer.Parallelizer(devices)
|
| else:
|
| return parallelizer.SyncParallelizer(devices)
|
| +
|
| + @classmethod
|
| + def HealthyDevices(cls):
|
| + blacklist = device_blacklist.ReadBlacklist()
|
| + def blacklisted(adb):
|
| + if adb.GetDeviceSerial() in blacklist:
|
| + logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial())
|
| + return True
|
| + return False
|
| +
|
| + return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
|
| + if not blacklisted(adb)]
|
| +
|
|
|