Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides a variety of device interactions based on adb. | 5 """Provides a variety of device interactions based on adb. |
| 6 | 6 |
| 7 Eventually, this will be based on adb_wrapper. | 7 Eventually, this will be based on adb_wrapper. |
| 8 """ | 8 """ |
| 9 # pylint: disable=W0613 | 9 # pylint: disable=W0613 |
| 10 | 10 |
| 11 import multiprocessing | 11 import multiprocessing |
| 12 import os | 12 import os |
| 13 import pipes | 13 import pipes |
| 14 import re | |
| 14 import sys | 15 import sys |
| 15 import tempfile | 16 import tempfile |
| 16 import time | 17 import time |
| 17 import zipfile | 18 import zipfile |
| 18 | 19 |
| 19 import pylib.android_commands | 20 import pylib.android_commands |
| 20 from pylib.device import adb_wrapper | 21 from pylib.device import adb_wrapper |
| 21 from pylib.device import decorators | 22 from pylib.device import decorators |
| 22 from pylib.device import device_errors | 23 from pylib.device import device_errors |
| 23 from pylib.device.commands import install_commands | 24 from pylib.device.commands import install_commands |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 79 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) | 80 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) |
| 80 self.old_interface = device | 81 self.old_interface = device |
| 81 elif not device: | 82 elif not device: |
| 82 self.adb = adb_wrapper.AdbWrapper('') | 83 self.adb = adb_wrapper.AdbWrapper('') |
| 83 self.old_interface = pylib.android_commands.AndroidCommands() | 84 self.old_interface = pylib.android_commands.AndroidCommands() |
| 84 else: | 85 else: |
| 85 raise ValueError('Unsupported type passed for argument "device"') | 86 raise ValueError('Unsupported type passed for argument "device"') |
| 86 self._commands_installed = False | 87 self._commands_installed = False |
| 87 self._default_timeout = default_timeout | 88 self._default_timeout = default_timeout |
| 88 self._default_retries = default_retries | 89 self._default_retries = default_retries |
| 90 self._cache = {} | |
| 89 assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)) | 91 assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)) |
| 90 assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR)) | 92 assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR)) |
| 91 | 93 |
| 92 @decorators.WithTimeoutAndRetriesFromInstance() | 94 @decorators.WithTimeoutAndRetriesFromInstance() |
| 93 def IsOnline(self, timeout=None, retries=None): | 95 def IsOnline(self, timeout=None, retries=None): |
| 94 """Checks whether the device is online. | 96 """Checks whether the device is online. |
| 95 | 97 |
| 96 Args: | 98 Args: |
| 97 timeout: timeout in seconds | 99 timeout: timeout in seconds |
| 98 retries: number of retries | 100 retries: number of retries |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 137 retries: number of retries | 139 retries: number of retries |
| 138 | 140 |
| 139 Raises: | 141 Raises: |
| 140 CommandFailedError if root could not be enabled. | 142 CommandFailedError if root could not be enabled. |
| 141 CommandTimeoutError on timeout. | 143 CommandTimeoutError on timeout. |
| 142 """ | 144 """ |
| 143 if not self.old_interface.EnableAdbRoot(): | 145 if not self.old_interface.EnableAdbRoot(): |
| 144 raise device_errors.CommandFailedError( | 146 raise device_errors.CommandFailedError( |
| 145 'Could not enable root.', device=str(self)) | 147 'Could not enable root.', device=str(self)) |
| 146 | 148 |
| 147 @decorators.WithTimeoutAndRetriesFromInstance() | 149 def GetExternalStoragePath(self, timeout=_DEFAULT_TIMEOUT, |
|
jbudorick
2014/10/14 16:55:19
I'm not crazy about moving the timeout/retry bound
| |
| 148 def GetExternalStoragePath(self, timeout=None, retries=None): | 150 retries=_DEFAULT_RETRIES): |
| 149 """Get the device's path to its SD card. | 151 """Get the device's path to its SD card. |
| 150 | 152 |
| 151 Args: | 153 Args: |
| 152 timeout: timeout in seconds | 154 timeout: timeout in seconds |
| 153 retries: number of retries | 155 retries: number of retries |
| 154 | 156 |
| 155 Returns: | 157 Returns: |
| 156 The device's path to its SD card. | 158 The device's path to its SD card. |
| 157 | 159 |
| 158 Raises: | 160 Raises: |
| 159 CommandFailedError if the external storage path could not be determined. | 161 CommandFailedError if the external storage path could not be determined. |
| 160 CommandTimeoutError on timeout. | 162 CommandTimeoutError on timeout. |
| 161 DeviceUnreachableError on missing device. | 163 DeviceUnreachableError on missing device. |
| 162 """ | 164 """ |
| 163 return self._GetExternalStoragePathImpl() | 165 if 'external_storage' in self._cache: |
| 166 return self._cache['external_storage'] | |
| 164 | 167 |
| 165 def _GetExternalStoragePathImpl(self): | 168 value = self._NewRunShellImpl('echo $EXTERNAL_STORAGE', timeout=timeout, |
| 166 try: | 169 retries=retries).rstrip() |
| 167 return self.old_interface.GetExternalStorage() | 170 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
| |
| 168 except AssertionError as e: | 171 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', |
| 169 raise device_errors.CommandFailedError( | 172 str(self)) |
| 170 str(e), device=str(self)), None, sys.exc_info()[2] | 173 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.
| |
| 174 return value | |
| 171 | 175 |
| 172 @decorators.WithTimeoutAndRetriesFromInstance() | 176 @decorators.WithTimeoutAndRetriesFromInstance() |
| 173 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): | 177 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): |
| 174 """Wait for the device to fully boot. | 178 """Wait for the device to fully boot. |
| 175 | 179 |
| 176 This means waiting for the device to boot, the package manager to be | 180 This means waiting for the device to boot, the package manager to be |
| 177 available, and the SD card to be ready. It can optionally mean waiting | 181 available, and the SD card to be ready. It can optionally mean waiting |
| 178 for wifi to come up, too. | 182 for wifi to come up, too. |
| 179 | 183 |
| 180 Args: | 184 Args: |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 319 if check_return: | 323 if check_return: |
| 320 code, output = self.old_interface.GetShellCommandStatusAndOutput( | 324 code, output = self.old_interface.GetShellCommandStatusAndOutput( |
| 321 cmd, timeout_time=timeout) | 325 cmd, timeout_time=timeout) |
| 322 if int(code) != 0: | 326 if int(code) != 0: |
| 323 raise device_errors.AdbCommandFailedError( | 327 raise device_errors.AdbCommandFailedError( |
| 324 cmd.split(), 'Nonzero exit code (%d)' % code, device=str(self)) | 328 cmd.split(), 'Nonzero exit code (%d)' % code, device=str(self)) |
| 325 else: | 329 else: |
| 326 output = self.old_interface.RunShellCommand(cmd, timeout_time=timeout) | 330 output = self.old_interface.RunShellCommand(cmd, timeout_time=timeout) |
| 327 return output | 331 return output |
| 328 | 332 |
| 333 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
| |
| 334 timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 335 """Proposed new interface to run ADB shell commands. | |
| 336 | |
| 337 When the command to run |cmd| is given as a string, it will be interpreted | |
| 338 and run by the shell on the device. | |
| 339 | |
| 340 Alternatively, one can supply |cmd| as a sequence containing the name of | |
| 341 the command to run followed by its arguments. In this case, arguments are | |
| 342 passed to the command exactly as given, without any further processing by | |
| 343 the shell. This allows to easily pass arguments containing spaces or | |
| 344 special characters without having to worry about getting quoting right. | |
| 345 | |
| 346 Args: | |
| 347 cmd: A string with the full command to run on the device, or a sequence | |
| 348 containing the command and its arguments. | |
| 349 cwd: The device directory in which the command should be run. | |
| 350 env: The environment variables with which the command should be run. | |
| 351 as_root: A boolean indicating whether the shell command should be run | |
| 352 with root privileges. | |
| 353 timeout: timeout in seconds | |
| 354 retries: number of retries | |
| 355 | |
| 356 Returns: | |
| 357 The output of the command as a single string. | |
| 358 | |
| 359 Raises: | |
| 360 CommandFailedError if the exit status of the command run is non-zero. | |
| 361 CommandTimeoutError on timeout. | |
| 362 """ | |
| 363 def env_quote(key, value): | |
| 364 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.
| |
| 365 raise KeyError('Invalid shell variable name %r' % key) | |
| 366 return '%s=%s' % (key, pipes.quote(value)) | |
| 367 | |
| 368 if not isinstance(cmd, basestring): | |
| 369 cmd = ' '.join(pipes.quote(s) for s in cmd) | |
| 370 if as_root and not self._HasRootImpl(): | |
| 371 cmd = 'su -c %s' % cmd | |
| 372 if env is not None: | |
| 373 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.
| |
| 374 if cwd is not None: | |
| 375 cmd = 'cd %s && %s' % (pipes.quote(cwd), cmd) | |
| 376 return self.adb.Shell(cmd, expect_rc=0, timeout=timeout, retries=retries) | |
| 377 | |
| 329 @decorators.WithTimeoutAndRetriesFromInstance() | 378 @decorators.WithTimeoutAndRetriesFromInstance() |
| 330 def KillAll(self, process_name, signum=9, as_root=False, blocking=False, | 379 def KillAll(self, process_name, signum=9, as_root=False, blocking=False, |
| 331 timeout=None, retries=None): | 380 timeout=None, retries=None): |
| 332 """Kill all processes with the given name on the device. | 381 """Kill all processes with the given name on the device. |
| 333 | 382 |
| 334 Args: | 383 Args: |
| 335 process_name: A string containing the name of the process to kill. | 384 process_name: A string containing the name of the process to kill. |
| 336 signum: An integer containing the signal number to send to kill. Defaults | 385 signum: An integer containing the signal number to send to kill. Defaults |
| 337 to 9 (SIGKILL). | 386 to 9 (SIGKILL). |
| 338 as_root: A boolean indicating whether the kill should be executed with | 387 as_root: A boolean indicating whether the kill should be executed with |
| (...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 615 | 664 |
| 616 self._InstallCommands() | 665 self._InstallCommands() |
| 617 | 666 |
| 618 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: | 667 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: |
| 619 zip_proc = multiprocessing.Process( | 668 zip_proc = multiprocessing.Process( |
| 620 target=DeviceUtils._CreateDeviceZip, | 669 target=DeviceUtils._CreateDeviceZip, |
| 621 args=(zip_file.name, files)) | 670 args=(zip_file.name, files)) |
| 622 zip_proc.start() | 671 zip_proc.start() |
| 623 zip_proc.join() | 672 zip_proc.join() |
| 624 | 673 |
| 625 zip_on_device = '%s/tmp.zip' % self._GetExternalStoragePathImpl() | 674 zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath() |
| 626 try: | 675 try: |
| 627 self.adb.Push(zip_file.name, zip_on_device) | 676 self.adb.Push(zip_file.name, zip_on_device) |
| 628 self._RunShellCommandImpl( | 677 self._RunShellCommandImpl( |
| 629 ['unzip', zip_on_device], | 678 ['unzip', zip_on_device], |
| 630 as_root=True, check_return=True, | 679 as_root=True, check_return=True, |
| 631 env={'PATH': '$PATH:%s' % install_commands.BIN_DIR}) | 680 env={'PATH': '$PATH:%s' % install_commands.BIN_DIR}) |
| 632 finally: | 681 finally: |
| 633 if zip_proc.is_alive(): | 682 if zip_proc.is_alive(): |
| 634 zip_proc.terminate() | 683 zip_proc.terminate() |
| 635 if self._IsOnlineImpl(): | 684 if self._IsOnlineImpl(): |
| (...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 965 A Parallelizer operating over |devices|. | 1014 A Parallelizer operating over |devices|. |
| 966 """ | 1015 """ |
| 967 if not devices or len(devices) == 0: | 1016 if not devices or len(devices) == 0: |
| 968 devices = pylib.android_commands.GetAttachedDevices() | 1017 devices = pylib.android_commands.GetAttachedDevices() |
| 969 parallelizer_type = (parallelizer.Parallelizer if async | 1018 parallelizer_type = (parallelizer.Parallelizer if async |
| 970 else parallelizer.SyncParallelizer) | 1019 else parallelizer.SyncParallelizer) |
| 971 return parallelizer_type([ | 1020 return parallelizer_type([ |
| 972 d if isinstance(d, DeviceUtils) else DeviceUtils(d) | 1021 d if isinstance(d, DeviceUtils) else DeviceUtils(d) |
| 973 for d in devices]) | 1022 for d in devices]) |
| 974 | 1023 |
| OLD | NEW |