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 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
51 """Restarts the adb server. | 51 """Restarts the adb server. |
52 | 52 |
53 Raises: | 53 Raises: |
54 CommandFailedError if we fail to kill or restart the server. | 54 CommandFailedError if we fail to kill or restart the server. |
55 """ | 55 """ |
56 pylib.android_commands.AndroidCommands().RestartAdbServer() | 56 pylib.android_commands.AndroidCommands().RestartAdbServer() |
57 | 57 |
58 | 58 |
59 class DeviceUtils(object): | 59 class DeviceUtils(object): |
60 | 60 |
61 _MAX_ADB_COMMAND_LENGTH = 512 | |
61 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') | 62 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') |
62 | 63 |
63 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, | 64 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, |
64 default_retries=_DEFAULT_RETRIES): | 65 default_retries=_DEFAULT_RETRIES): |
65 """DeviceUtils constructor. | 66 """DeviceUtils constructor. |
66 | 67 |
67 Args: | 68 Args: |
68 device: Either a device serial, an existing AdbWrapper instance, an | 69 device: Either a device serial, an existing AdbWrapper instance, an |
69 an existing AndroidCommands instance, or nothing. | 70 an existing AndroidCommands instance, or nothing. |
70 default_timeout: An integer containing the default number of seconds to | 71 default_timeout: An integer containing the default number of seconds to |
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
362 if should_install: | 363 if should_install: |
363 try: | 364 try: |
364 out = self.old_interface.Install(apk_path, reinstall=reinstall) | 365 out = self.old_interface.Install(apk_path, reinstall=reinstall) |
365 for line in out.splitlines(): | 366 for line in out.splitlines(): |
366 if 'Failure' in line: | 367 if 'Failure' in line: |
367 raise device_errors.CommandFailedError(line.strip(), str(self)) | 368 raise device_errors.CommandFailedError(line.strip(), str(self)) |
368 except AssertionError as e: | 369 except AssertionError as e: |
369 raise device_errors.CommandFailedError( | 370 raise device_errors.CommandFailedError( |
370 str(e), str(self)), None, sys.exc_info()[2] | 371 str(e), str(self)), None, sys.exc_info()[2] |
371 | 372 |
373 def _PrepareShellCommand(self, cmd, cwd=None, env=None, as_root=False): | |
374 def env_quote(key, value): | |
375 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): | |
376 raise KeyError('Invalid shell variable name %r' % key) | |
377 # using double quotes here to allow interpolation of shell variables | |
378 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) | |
379 | |
380 if not isinstance(cmd, basestring): | |
381 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) | |
382 if env: | |
383 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) | |
384 cmd = '%s %s' % (env, cmd) | |
385 if cwd: | |
386 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) | |
387 if as_root and self.NeedsSU(): | |
388 # "su -c sh -c" allows using shell features in |cmd| | |
389 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd) | |
390 return cmd | |
391 | |
372 @decorators.WithTimeoutAndRetriesFromInstance() | 392 @decorators.WithTimeoutAndRetriesFromInstance() |
373 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, | 393 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, |
374 as_root=False, single_line=False, | 394 as_root=False, single_line=False, |
375 timeout=None, retries=None): | 395 timeout=None, retries=None): |
376 """Run an ADB shell command. | 396 """Run an ADB shell command. |
377 | 397 |
378 The command to run |cmd| should be a sequence of program arguments or else | 398 The command to run |cmd| should be a sequence of program arguments or else |
379 a single string. | 399 a single string. |
380 | 400 |
381 When |cmd| is a sequence, it is assumed to contain the name of the command | 401 When |cmd| is a sequence, it is assumed to contain the name of the command |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
414 (with the optional newline at the end stripped). | 434 (with the optional newline at the end stripped). |
415 | 435 |
416 Raises: | 436 Raises: |
417 AdbCommandFailedError if check_return is True and the exit code of | 437 AdbCommandFailedError if check_return is True and the exit code of |
418 the command run on the device is non-zero. | 438 the command run on the device is non-zero. |
419 CommandFailedError if single_line is True but the output contains two or | 439 CommandFailedError if single_line is True but the output contains two or |
420 more lines. | 440 more lines. |
421 CommandTimeoutError on timeout. | 441 CommandTimeoutError on timeout. |
422 DeviceUnreachableError on missing device. | 442 DeviceUnreachableError on missing device. |
423 """ | 443 """ |
424 def env_quote(key, value): | 444 def do_run(cmd): |
425 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): | 445 try: |
426 raise KeyError('Invalid shell variable name %r' % key) | 446 return self.adb.Shell(cmd) |
427 # using double quotes here to allow interpolation of shell variables | 447 except device_errors.AdbCommandFailedError as exc: |
428 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) | 448 if check_return: |
449 raise | |
450 else: | |
451 return exc.output | |
429 | 452 |
430 if not isinstance(cmd, basestring): | |
431 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) | |
432 if env: | |
433 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) | |
434 cmd = '%s %s' % (env, cmd) | |
435 if cwd: | |
436 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) | |
437 if as_root and self.NeedsSU(): | |
438 # "su -c sh -c" allows using shell features in |cmd| | |
439 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd) | |
440 if timeout is None: | 453 if timeout is None: |
441 timeout = self._default_timeout | 454 timeout = self._default_timeout |
442 | 455 |
443 try: | 456 cmd = self._PrepareShellCommand(cmd, cwd=cwd, env=env, as_root=as_root) |
444 output = self.adb.Shell(cmd) | 457 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: |
445 except device_errors.AdbCommandFailedError as e: | 458 output = do_run(cmd) |
446 if check_return: | 459 else: |
447 raise | 460 with device_temp_file.DeviceTempFile(self, suffix='.sh') as script: |
448 else: | 461 self.PushContents(cmd, script.name) |
449 output = e.output | 462 output = do_run('sh %s' % script.name_quoted) |
450 | 463 |
451 output = output.splitlines() | 464 output = output.splitlines() |
452 if single_line: | 465 if single_line: |
453 if not output: | 466 if not output: |
454 return '' | 467 return '' |
455 elif len(output) == 1: | 468 elif len(output) == 1: |
456 return output[0] | 469 return output[0] |
457 else: | 470 else: |
458 msg = 'one line of output was expected, but got: %s' | 471 msg = 'one line of output was expected, but got: %s' |
459 raise device_errors.CommandFailedError(msg % output, str(self)) | 472 raise device_errors.CommandFailedError(msg % output, str(self)) |
(...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
877 # the implementation switch, and if file not found should raise exception. | 890 # the implementation switch, and if file not found should raise exception. |
878 if as_root: | 891 if as_root: |
879 if not self.old_interface.CanAccessProtectedFileContents(): | 892 if not self.old_interface.CanAccessProtectedFileContents(): |
880 raise device_errors.CommandFailedError( | 893 raise device_errors.CommandFailedError( |
881 'Cannot read from %s with root privileges.' % device_path) | 894 'Cannot read from %s with root privileges.' % device_path) |
882 return self.old_interface.GetProtectedFileContents(device_path) | 895 return self.old_interface.GetProtectedFileContents(device_path) |
883 else: | 896 else: |
884 return self.old_interface.GetFileContents(device_path) | 897 return self.old_interface.GetFileContents(device_path) |
885 | 898 |
886 @decorators.WithTimeoutAndRetriesFromInstance() | 899 @decorators.WithTimeoutAndRetriesFromInstance() |
900 def PushContents(self, contents, device_path, timeout=None, retries=None): | |
perezju
2014/11/26 10:33:29
Turns out we need to push contents in quite a few
| |
901 """Pushes |contents| to a file on the device. | |
902 | |
903 Note: If the contents are small, or root access is needed, then |WriteFile| | |
904 is a better alternative. | |
905 | |
906 Args: | |
907 contents: A string containing the data to write to the device. | |
908 device_path: A string containing the absolute path to the file to write | |
909 on the device. | |
910 timeout: timeout in seconds | |
911 retries: number of retries | |
912 | |
913 Raises: | |
914 CommandFailedError if the file could not be written on the device. | |
915 CommandTimeoutError on timeout. | |
916 DeviceUnreachableError on missing device. | |
917 """ | |
918 with tempfile.NamedTemporaryFile() as host_temp: | |
919 host_temp.write(contents) | |
920 host_temp.flush() | |
921 self.adb.Push(host_temp.name, device_path) | |
922 | |
923 @decorators.WithTimeoutAndRetriesFromInstance() | |
887 def WriteFile(self, device_path, contents, as_root=False, force_push=False, | 924 def WriteFile(self, device_path, contents, as_root=False, force_push=False, |
888 timeout=None, retries=None): | 925 timeout=None, retries=None): |
889 """Writes |contents| to a file on the device. | 926 """Writes |contents| to a file on the device. |
890 | 927 |
891 Args: | 928 Args: |
892 device_path: A string containing the absolute path to the file to write | 929 device_path: A string containing the absolute path to the file to write |
893 on the device. | 930 on the device. |
894 contents: A string containing the data to write to the device. | 931 contents: A string containing the data to write to the device. |
895 as_root: A boolean indicating whether the write should be executed with | 932 as_root: A boolean indicating whether the write should be executed with |
896 root privileges (if available). | 933 root privileges (if available). |
897 force_push: A boolean indicating whether to force the operation to be | 934 force_push: A boolean indicating whether to force the operation to be |
898 performed by pushing a file to the device. The default is, when the | 935 performed by pushing a file to the device. The default is, when the |
899 contents are short, to pass the contents using a shell script instead. | 936 contents are short, to pass the contents using a shell script instead. |
900 timeout: timeout in seconds | 937 timeout: timeout in seconds |
901 retries: number of retries | 938 retries: number of retries |
902 | 939 |
903 Raises: | 940 Raises: |
904 CommandFailedError if the file could not be written on the device. | 941 CommandFailedError if the file could not be written on the device. |
905 CommandTimeoutError on timeout. | 942 CommandTimeoutError on timeout. |
906 DeviceUnreachableError on missing device. | 943 DeviceUnreachableError on missing device. |
907 """ | 944 """ |
908 if len(contents) < 512 and not force_push: | 945 if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH: |
909 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), | 946 cmd = self._PrepareShellCommand( |
910 cmd_helper.SingleQuote(device_path)) | 947 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), |
911 self.RunShellCommand(cmd, as_root=as_root, check_return=True) | 948 cmd_helper.SingleQuote(device_path)), |
949 as_root=as_root) | |
950 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: | |
951 self.RunShellCommand(cmd, check_return=True) | |
952 return | |
perezju
2014/11/26 10:33:28
There was a small chance that the contents are sma
| |
953 if as_root and self.NeedsSU(): | |
954 with device_temp_file.DeviceTempFile(self) as device_temp: | |
955 self.PushContents(contents, device_temp.name) | |
956 # Here we need 'cp' rather than 'mv' because the temp and | |
957 # destination files might be on different file systems (e.g. | |
958 # on internal storage and an external sd card) | |
959 self.RunShellCommand(['cp', device_temp.name, device_path], | |
960 as_root=True, check_return=True) | |
912 else: | 961 else: |
913 with tempfile.NamedTemporaryFile() as host_temp: | 962 self.PushContents(contents, device_path) |
914 host_temp.write(contents) | |
915 host_temp.flush() | |
916 if as_root and self.NeedsSU(): | |
917 with device_temp_file.DeviceTempFile(self) as device_temp: | |
918 self.adb.Push(host_temp.name, device_temp.name) | |
919 # Here we need 'cp' rather than 'mv' because the temp and | |
920 # destination files might be on different file systems (e.g. | |
921 # on internal storage and an external sd card) | |
922 self.RunShellCommand(['cp', device_temp.name, device_path], | |
923 as_root=True, check_return=True) | |
924 else: | |
925 self.adb.Push(host_temp.name, device_path) | |
926 | 963 |
927 @decorators.WithTimeoutAndRetriesFromInstance() | 964 @decorators.WithTimeoutAndRetriesFromInstance() |
928 def Ls(self, device_path, timeout=None, retries=None): | 965 def Ls(self, device_path, timeout=None, retries=None): |
929 """Lists the contents of a directory on the device. | 966 """Lists the contents of a directory on the device. |
930 | 967 |
931 Args: | 968 Args: |
932 device_path: A string containing the path of the directory on the device | 969 device_path: A string containing the path of the directory on the device |
933 to list. | 970 to list. |
934 timeout: timeout in seconds | 971 timeout: timeout in seconds |
935 retries: number of retries | 972 retries: number of retries |
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1156 Returns: | 1193 Returns: |
1157 A Parallelizer operating over |devices|. | 1194 A Parallelizer operating over |devices|. |
1158 """ | 1195 """ |
1159 if not devices or len(devices) == 0: | 1196 if not devices or len(devices) == 0: |
1160 devices = pylib.android_commands.GetAttachedDevices() | 1197 devices = pylib.android_commands.GetAttachedDevices() |
1161 parallelizer_type = (parallelizer.Parallelizer if async | 1198 parallelizer_type = (parallelizer.Parallelizer if async |
1162 else parallelizer.SyncParallelizer) | 1199 else parallelizer.SyncParallelizer) |
1163 return parallelizer_type([ | 1200 return parallelizer_type([ |
1164 d if isinstance(d, DeviceUtils) else DeviceUtils(d) | 1201 d if isinstance(d, DeviceUtils) else DeviceUtils(d) |
1165 for d in devices]) | 1202 for d in devices]) |
OLD | NEW |