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 75b2ae06ced358d9863cdda2c5a383cdf6e5298b..5d04a22135221d0eac8ad38f734774e595402520 100644 |
--- a/build/android/pylib/device/device_utils.py |
+++ b/build/android/pylib/device/device_utils.py |
@@ -11,6 +11,7 @@ Eventually, this will be based on adb_wrapper. |
import multiprocessing |
import os |
import pipes |
+import re |
import sys |
import tempfile |
import time |
@@ -86,6 +87,7 @@ class DeviceUtils(object): |
self._commands_installed = False |
self._default_timeout = default_timeout |
self._default_retries = default_retries |
+ self._cache = {} |
assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)) |
assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR)) |
@@ -144,8 +146,8 @@ class DeviceUtils(object): |
raise device_errors.CommandFailedError( |
'Could not enable root.', device=str(self)) |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetExternalStoragePath(self, timeout=None, retries=None): |
+ def GetExternalStoragePath(self, timeout=_DEFAULT_TIMEOUT, |
jbudorick
2014/10/14 16:55:19
I'm not crazy about moving the timeout/retry bound
|
+ retries=_DEFAULT_RETRIES): |
"""Get the device's path to its SD card. |
Args: |
@@ -160,14 +162,16 @@ class DeviceUtils(object): |
CommandTimeoutError on timeout. |
DeviceUnreachableError on missing device. |
""" |
- return self._GetExternalStoragePathImpl() |
+ if 'external_storage' in self._cache: |
+ return self._cache['external_storage'] |
- def _GetExternalStoragePathImpl(self): |
- try: |
- return self.old_interface.GetExternalStorage() |
- except AssertionError as e: |
- raise device_errors.CommandFailedError( |
- str(e), device=str(self)), None, sys.exc_info()[2] |
+ value = self._NewRunShellImpl('echo $EXTERNAL_STORAGE', timeout=timeout, |
+ retries=retries).rstrip() |
+ if not value: |
jbudorick
2014/10/14 16:55:19
I'm not sure that this _has_ to be an error (altho
perezju
2014/10/15 09:22:54
This is what the current implementation does, so n
|
+ raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', |
+ str(self)) |
+ self._cache['external_storage'] = value |
jbudorick
2014/10/14 16:55:19
I like the idea of caching things like this and ce
perezju
2014/10/15 09:22:54
Acknowledged.
|
+ return value |
@decorators.WithTimeoutAndRetriesFromInstance() |
def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): |
@@ -326,6 +330,51 @@ class DeviceUtils(object): |
output = self.old_interface.RunShellCommand(cmd, timeout_time=timeout) |
return output |
+ def _NewRunShellImpl(self, cmd, cwd=None, env=None, as_root=False, |
jbudorick
2014/10/14 16:55:19
I'm not sure about eliminating check_return here.
perezju
2014/10/15 09:22:54
I would argue that we shouldn't make it easy for c
|
+ timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
+ """Proposed new interface to run ADB shell commands. |
+ |
+ When the command to run |cmd| is given as a string, it will be interpreted |
+ and run by the shell on the device. |
+ |
+ Alternatively, one can supply |cmd| as a sequence containing the name of |
+ the command to run followed by its arguments. In this case, arguments are |
+ passed to the command exactly as given, without any further processing by |
+ the shell. This allows to easily pass arguments containing spaces or |
+ special characters without having to worry about getting quoting right. |
+ |
+ Args: |
+ cmd: A string with the full command to run on the device, or a sequence |
+ containing the command and its arguments. |
+ cwd: The device directory in which the command should be run. |
+ env: The environment variables with which the command should be run. |
+ as_root: A boolean indicating whether the shell command should be run |
+ with root privileges. |
+ timeout: timeout in seconds |
+ retries: number of retries |
+ |
+ Returns: |
+ The output of the command as a single string. |
+ |
+ Raises: |
+ CommandFailedError if the exit status of the command run is non-zero. |
+ CommandTimeoutError on timeout. |
+ """ |
+ def env_quote(key, value): |
+ if not re.match('^[a-zA-Z_][a-zA-Z0-9_]*$', key): |
jbudorick
2014/10/14 16:55:19
Compile the regex as a constant and then use that
perezju
2014/10/15 09:22:54
Acknowledged.
|
+ raise KeyError('Invalid shell variable name %r' % key) |
+ return '%s=%s' % (key, pipes.quote(value)) |
+ |
+ if not isinstance(cmd, basestring): |
+ cmd = ' '.join(pipes.quote(s) for s in cmd) |
+ if as_root and not self._HasRootImpl(): |
+ cmd = 'su -c %s' % cmd |
+ if env is not None: |
+ cmd = '%s %s' % (' '.join(env_quote(*kv) for kv in env.iteritems()), cmd) |
jbudorick
2014/10/14 16:55:19
This seems a little more readable to me:
env_qu
perezju
2014/10/15 09:22:54
Acknowledged.
|
+ if cwd is not None: |
+ cmd = 'cd %s && %s' % (pipes.quote(cwd), cmd) |
+ return self.adb.Shell(cmd, expect_rc=0, timeout=timeout, retries=retries) |
+ |
@decorators.WithTimeoutAndRetriesFromInstance() |
def KillAll(self, process_name, signum=9, as_root=False, blocking=False, |
timeout=None, retries=None): |
@@ -622,7 +671,7 @@ class DeviceUtils(object): |
zip_proc.start() |
zip_proc.join() |
- zip_on_device = '%s/tmp.zip' % self._GetExternalStoragePathImpl() |
+ zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath() |
try: |
self.adb.Push(zip_file.name, zip_on_device) |
self._RunShellCommandImpl( |