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 logging | 11 import logging |
12 import multiprocessing | 12 import multiprocessing |
13 import os | 13 import os |
14 import re | 14 import re |
15 import sys | 15 import sys |
16 import tempfile | 16 import tempfile |
17 import time | 17 import time |
18 import zipfile | 18 import zipfile |
19 | 19 |
20 import pylib.android_commands | 20 import pylib.android_commands |
21 from pylib import cmd_helper | 21 from pylib import cmd_helper |
22 from pylib.device import adb_wrapper | 22 from pylib.device import adb_wrapper |
23 from pylib.device import decorators | 23 from pylib.device import decorators |
24 from pylib.device import device_errors | 24 from pylib.device import device_errors |
25 from pylib.device.commands import install_commands | 25 from pylib.device.commands import install_commands |
26 from pylib.utils import apk_helper | 26 from pylib.utils import apk_helper |
| 27 from pylib.utils import device_temp_file |
27 from pylib.utils import host_utils | 28 from pylib.utils import host_utils |
| 29 from pylib.utils import md5sum |
28 from pylib.utils import parallelizer | 30 from pylib.utils import parallelizer |
29 from pylib.utils import timeout_retry | 31 from pylib.utils import timeout_retry |
30 | 32 |
31 _DEFAULT_TIMEOUT = 30 | 33 _DEFAULT_TIMEOUT = 30 |
32 _DEFAULT_RETRIES = 3 | 34 _DEFAULT_RETRIES = 3 |
33 | 35 |
34 | 36 |
35 @decorators.WithExplicitTimeoutAndRetries( | 37 @decorators.WithExplicitTimeoutAndRetries( |
36 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) | 38 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) |
37 def GetAVDs(): | 39 def GetAVDs(): |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
85 self.old_interface = device | 87 self.old_interface = device |
86 elif not device: | 88 elif not device: |
87 self.adb = adb_wrapper.AdbWrapper('') | 89 self.adb = adb_wrapper.AdbWrapper('') |
88 self.old_interface = pylib.android_commands.AndroidCommands() | 90 self.old_interface = pylib.android_commands.AndroidCommands() |
89 else: | 91 else: |
90 raise ValueError('Unsupported type passed for argument "device"') | 92 raise ValueError('Unsupported type passed for argument "device"') |
91 self._commands_installed = None | 93 self._commands_installed = None |
92 self._default_timeout = default_timeout | 94 self._default_timeout = default_timeout |
93 self._default_retries = default_retries | 95 self._default_retries = default_retries |
94 self._cache = {} | 96 self._cache = {} |
95 assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)) | 97 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) |
96 assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR)) | 98 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) |
97 | 99 |
98 @decorators.WithTimeoutAndRetriesFromInstance() | 100 @decorators.WithTimeoutAndRetriesFromInstance() |
99 def IsOnline(self, timeout=None, retries=None): | 101 def IsOnline(self, timeout=None, retries=None): |
100 """Checks whether the device is online. | 102 """Checks whether the device is online. |
101 | 103 |
102 Args: | 104 Args: |
103 timeout: timeout in seconds | 105 timeout: timeout in seconds |
104 retries: number of retries | 106 retries: number of retries |
105 | 107 |
106 Returns: | 108 Returns: |
(...skipping 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
422 DeviceUnreachableError on missing device. | 424 DeviceUnreachableError on missing device. |
423 """ | 425 """ |
424 def env_quote(key, value): | 426 def env_quote(key, value): |
425 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): | 427 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): |
426 raise KeyError('Invalid shell variable name %r' % key) | 428 raise KeyError('Invalid shell variable name %r' % key) |
427 # using double quotes here to allow interpolation of shell variables | 429 # using double quotes here to allow interpolation of shell variables |
428 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) | 430 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) |
429 | 431 |
430 if not isinstance(cmd, basestring): | 432 if not isinstance(cmd, basestring): |
431 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) | 433 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) |
432 if as_root and self.NeedsSU(): | |
433 cmd = 'su -c %s' % cmd | |
434 if env: | 434 if env: |
435 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) | 435 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) |
436 cmd = '%s %s' % (env, cmd) | 436 cmd = '%s %s' % (env, cmd) |
437 if cwd: | 437 if cwd: |
438 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) | 438 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) |
| 439 if as_root and self.NeedsSU(): |
| 440 # "su -c sh -c" allows using shell features in |cmd| |
| 441 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd) |
439 if timeout is None: | 442 if timeout is None: |
440 timeout = self._default_timeout | 443 timeout = self._default_timeout |
441 | 444 |
442 try: | 445 try: |
443 output = self.adb.Shell(cmd) | 446 output = self.adb.Shell(cmd) |
444 except device_errors.AdbShellCommandFailedError as e: | 447 except device_errors.AdbShellCommandFailedError as e: |
445 if check_return: | 448 if check_return: |
446 raise | 449 raise |
447 else: | 450 else: |
448 output = e.output | 451 output = e.output |
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
692 def _GetChangedFilesImpl(self, host_path, device_path): | 695 def _GetChangedFilesImpl(self, host_path, device_path): |
693 real_host_path = os.path.realpath(host_path) | 696 real_host_path = os.path.realpath(host_path) |
694 try: | 697 try: |
695 real_device_path = self.RunShellCommand( | 698 real_device_path = self.RunShellCommand( |
696 ['realpath', device_path], single_line=True, check_return=True) | 699 ['realpath', device_path], single_line=True, check_return=True) |
697 except device_errors.CommandFailedError: | 700 except device_errors.CommandFailedError: |
698 real_device_path = None | 701 real_device_path = None |
699 if not real_device_path: | 702 if not real_device_path: |
700 return [(host_path, device_path)] | 703 return [(host_path, device_path)] |
701 | 704 |
702 # TODO(jbudorick): Move the md5 logic up into DeviceUtils or base | 705 host_hash_tuples = md5sum.CalculateHostMd5Sums([real_host_path]) |
703 # this function on mtime. | 706 device_paths_to_md5 = ( |
704 # pylint: disable=W0212 | 707 real_device_path if os.path.isfile(real_host_path) |
705 host_hash_tuples, device_hash_tuples = self.old_interface._RunMd5Sum( | 708 else ('%s/%s' % (real_device_path, os.path.relpath(p, real_host_path)) |
706 real_host_path, real_device_path) | 709 for _, p in host_hash_tuples)) |
707 # pylint: enable=W0212 | 710 device_hash_tuples = md5sum.CalculateDeviceMd5Sums( |
| 711 device_paths_to_md5, self) |
708 | 712 |
709 if os.path.isfile(host_path): | 713 if os.path.isfile(host_path): |
710 if (not device_hash_tuples | 714 if (not device_hash_tuples |
711 or device_hash_tuples[0].hash != host_hash_tuples[0].hash): | 715 or device_hash_tuples[0].hash != host_hash_tuples[0].hash): |
712 return [(host_path, device_path)] | 716 return [(host_path, device_path)] |
713 else: | 717 else: |
714 return [] | 718 return [] |
715 else: | 719 else: |
716 device_tuple_dict = dict((d.path, d.hash) for d in device_hash_tuples) | 720 device_tuple_dict = dict((d.path, d.hash) for d in device_hash_tuples) |
717 to_push = [] | 721 to_push = [] |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
758 COMPRESSION_RATIO = 2.0 # unitless | 762 COMPRESSION_RATIO = 2.0 # unitless |
759 | 763 |
760 adb_call_time = ADB_CALL_PENALTY * adb_calls | 764 adb_call_time = ADB_CALL_PENALTY * adb_calls |
761 adb_push_setup_time = ADB_PUSH_PENALTY * file_count | 765 adb_push_setup_time = ADB_PUSH_PENALTY * file_count |
762 if is_zipping: | 766 if is_zipping: |
763 zip_time = ZIP_PENALTY + byte_count / ZIP_RATE | 767 zip_time = ZIP_PENALTY + byte_count / ZIP_RATE |
764 transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) | 768 transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) |
765 else: | 769 else: |
766 zip_time = 0 | 770 zip_time = 0 |
767 transfer_time = byte_count / TRANSFER_RATE | 771 transfer_time = byte_count / TRANSFER_RATE |
768 return (adb_call_time + adb_push_setup_time + zip_time + transfer_time) | 772 return adb_call_time + adb_push_setup_time + zip_time + transfer_time |
769 | 773 |
770 def _PushChangedFilesIndividually(self, files): | 774 def _PushChangedFilesIndividually(self, files): |
771 for h, d in files: | 775 for h, d in files: |
772 self.adb.Push(h, d) | 776 self.adb.Push(h, d) |
773 | 777 |
774 def _PushChangedFilesZipped(self, files): | 778 def _PushChangedFilesZipped(self, files): |
775 if not files: | 779 if not files: |
776 return | 780 return |
777 | 781 |
778 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: | 782 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
875 # the implementation switch, and if file not found should raise exception. | 879 # the implementation switch, and if file not found should raise exception. |
876 if as_root: | 880 if as_root: |
877 if not self.old_interface.CanAccessProtectedFileContents(): | 881 if not self.old_interface.CanAccessProtectedFileContents(): |
878 raise device_errors.CommandFailedError( | 882 raise device_errors.CommandFailedError( |
879 'Cannot read from %s with root privileges.' % device_path) | 883 'Cannot read from %s with root privileges.' % device_path) |
880 return self.old_interface.GetProtectedFileContents(device_path) | 884 return self.old_interface.GetProtectedFileContents(device_path) |
881 else: | 885 else: |
882 return self.old_interface.GetFileContents(device_path) | 886 return self.old_interface.GetFileContents(device_path) |
883 | 887 |
884 @decorators.WithTimeoutAndRetriesFromInstance() | 888 @decorators.WithTimeoutAndRetriesFromInstance() |
885 def WriteFile(self, device_path, contents, as_root=False, timeout=None, | 889 def WriteFile(self, device_path, contents, as_root=False, force_push=False, |
886 retries=None): | 890 timeout=None, retries=None): |
887 """Writes |contents| to a file on the device. | 891 """Writes |contents| to a file on the device. |
888 | 892 |
889 Args: | 893 Args: |
890 device_path: A string containing the absolute path to the file to write | 894 device_path: A string containing the absolute path to the file to write |
891 on the device. | 895 on the device. |
892 contents: A string containing the data to write to the device. | 896 contents: A string containing the data to write to the device. |
893 as_root: A boolean indicating whether the write should be executed with | 897 as_root: A boolean indicating whether the write should be executed with |
894 root privileges. | 898 root privileges (if available). |
| 899 force_push: A boolean indicating whether to force the operation to be |
| 900 performed by pushing a file to the device. The default is, when the |
| 901 contents are short, to pass the contents using a shell script instead. |
895 timeout: timeout in seconds | 902 timeout: timeout in seconds |
896 retries: number of retries | 903 retries: number of retries |
897 | 904 |
898 Raises: | 905 Raises: |
899 CommandFailedError if the file could not be written on the device. | 906 CommandFailedError if the file could not be written on the device. |
900 CommandTimeoutError on timeout. | 907 CommandTimeoutError on timeout. |
901 DeviceUnreachableError on missing device. | 908 DeviceUnreachableError on missing device. |
902 """ | 909 """ |
903 if as_root: | 910 if len(contents) < 512 and not force_push: |
904 if not self.old_interface.CanAccessProtectedFileContents(): | 911 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), |
905 raise device_errors.CommandFailedError( | 912 cmd_helper.SingleQuote(device_path)) |
906 'Cannot write to %s with root privileges.' % device_path) | 913 self.RunShellCommand(cmd, as_root=as_root, check_return=True) |
907 self.old_interface.SetProtectedFileContents(device_path, contents) | |
908 else: | 914 else: |
909 self.old_interface.SetFileContents(device_path, contents) | 915 with tempfile.NamedTemporaryFile() as host_temp: |
910 | 916 host_temp.write(contents) |
911 @decorators.WithTimeoutAndRetriesFromInstance() | 917 host_temp.flush() |
912 def WriteTextFile(self, device_path, text, as_root=False, timeout=None, | 918 if as_root and self.NeedsSU(): |
913 retries=None): | 919 with device_temp_file.DeviceTempFile(self) as device_temp: |
914 """Writes |text| to a file on the device. | 920 self.adb.Push(host_temp.name, device_temp.name) |
915 | 921 # Here we need 'cp' rather than 'mv' because the temp and |
916 Assuming that |text| is a small string, this is typically more efficient | 922 # destination files might be on different file systems (e.g. |
917 than |WriteFile|, as no files are pushed into the device. | 923 # on internal storage and an external sd card) |
918 | 924 self.RunShellCommand(['cp', device_temp.name, device_path], |
919 Args: | 925 as_root=True, check_return=True) |
920 device_path: A string containing the absolute path to the file to write | 926 else: |
921 on the device. | 927 self.adb.Push(host_temp.name, device_path) |
922 text: A short string of text to write to the file on the device. | |
923 as_root: A boolean indicating whether the write should be executed with | |
924 root privileges. | |
925 timeout: timeout in seconds | |
926 retries: number of retries | |
927 | |
928 Raises: | |
929 CommandFailedError if the file could not be written on the device. | |
930 CommandTimeoutError on timeout. | |
931 DeviceUnreachableError on missing device. | |
932 """ | |
933 cmd = 'echo %s > %s' % (cmd_helper.SingleQuote(text), | |
934 cmd_helper.SingleQuote(device_path)) | |
935 self.RunShellCommand(cmd, as_root=as_root, check_return=True) | |
936 | 928 |
937 @decorators.WithTimeoutAndRetriesFromInstance() | 929 @decorators.WithTimeoutAndRetriesFromInstance() |
938 def Ls(self, device_path, timeout=None, retries=None): | 930 def Ls(self, device_path, timeout=None, retries=None): |
939 """Lists the contents of a directory on the device. | 931 """Lists the contents of a directory on the device. |
940 | 932 |
941 Args: | 933 Args: |
942 device_path: A string containing the path of the directory on the device | 934 device_path: A string containing the path of the directory on the device |
943 to list. | 935 to list. |
944 timeout: timeout in seconds | 936 timeout: timeout in seconds |
945 retries: number of retries | 937 retries: number of retries |
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1166 Returns: | 1158 Returns: |
1167 A Parallelizer operating over |devices|. | 1159 A Parallelizer operating over |devices|. |
1168 """ | 1160 """ |
1169 if not devices or len(devices) == 0: | 1161 if not devices or len(devices) == 0: |
1170 devices = pylib.android_commands.GetAttachedDevices() | 1162 devices = pylib.android_commands.GetAttachedDevices() |
1171 parallelizer_type = (parallelizer.Parallelizer if async | 1163 parallelizer_type = (parallelizer.Parallelizer if async |
1172 else parallelizer.SyncParallelizer) | 1164 else parallelizer.SyncParallelizer) |
1173 return parallelizer_type([ | 1165 return parallelizer_type([ |
1174 d if isinstance(d, DeviceUtils) else DeviceUtils(d) | 1166 d if isinstance(d, DeviceUtils) else DeviceUtils(d) |
1175 for d in devices]) | 1167 for d in devices]) |
OLD | NEW |