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 |