Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(232)

Unified Diff: build/android/pylib/device/device_utils.py

Issue 1124763003: Update from https://crrev.com/327068 (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: update nacl, buildtools, fix display_change_notifier_unittest Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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)]
+

Powered by Google App Engine
This is Rietveld 408576698