OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 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 an interface to communicate with the device via the adb command. | 5 """Provides an interface to communicate with the device via the adb command. |
6 | 6 |
7 Assumes adb binary is currently on system path. | 7 Assumes adb binary is currently on system path. |
8 """ | 8 """ |
9 | 9 |
10 import collections | 10 import collections |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
177 if isinstance(utc_offset, str) and len(utc_offset) == 5: | 177 if isinstance(utc_offset, str) and len(utc_offset) == 5: |
178 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), | 178 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), |
179 minutes=int(utc_offset[3:5])) | 179 minutes=int(utc_offset[3:5])) |
180 if utc_offset[0:1] == '-': | 180 if utc_offset[0:1] == '-': |
181 utc_delta = -utc_delta | 181 utc_delta = -utc_delta |
182 lastmod -= utc_delta | 182 lastmod -= utc_delta |
183 files[filename] = (int(file_match.group('size')), lastmod) | 183 files[filename] = (int(file_match.group('size')), lastmod) |
184 return files | 184 return files |
185 | 185 |
186 | 186 |
187 def _ComputeFileListHash(md5sum_output): | 187 def _ComputeFileListHash(md5sum_output): |
frankf
2013/08/06 00:17:23
Rename this to _ParseMd5sumOutput()
| |
188 """Returns a list of tuples from the provided md5sum output. | 188 """Returns a list of tuples from the provided md5sum output. |
189 | 189 |
190 Args: | 190 Args: |
191 md5sum_output: output directly from md5sum binary. | 191 md5sum_output: output directly from md5sum binary. |
192 | 192 |
193 Returns: | 193 Returns: |
194 List of namedtuples (hash, path). | 194 List of namedtuples (hash, path). |
frankf
2013/08/06 00:17:23
What is path? Is it absolute or relative? Provide
craigdh
2013/08/06 21:17:29
Done.
| |
195 """ | 195 """ |
196 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path']) | 196 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path']) |
197 split_lines = [line.split(' ') for line in md5sum_output] | 197 split_lines = [line.split(' ') for line in md5sum_output] |
198 return [HashAndPath._make(s) for s in split_lines if len(s) == 2] | 198 return [HashAndPath._make(s) for s in split_lines if len(s) == 2] |
199 | 199 |
200 | 200 |
201 def _HasAdbPushSucceeded(command_output): | 201 def _HasAdbPushSucceeded(command_output): |
202 """Returns whether adb push has succeeded from the provided output.""" | 202 """Returns whether adb push has succeeded from the provided output.""" |
203 # TODO(frankf): We should look at the return code instead of the command | 203 # TODO(frankf): We should look at the return code instead of the command |
204 # output for many of the commands in this file. | 204 # output for many of the commands in this file. |
(...skipping 526 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
731 self.RunShellCommand('pm clear ' + package) | 731 self.RunShellCommand('pm clear ' + package) |
732 | 732 |
733 def SendKeyEvent(self, keycode): | 733 def SendKeyEvent(self, keycode): |
734 """Sends keycode to the device. | 734 """Sends keycode to the device. |
735 | 735 |
736 Args: | 736 Args: |
737 keycode: Numeric keycode to send (see "enum" at top of file). | 737 keycode: Numeric keycode to send (see "enum" at top of file). |
738 """ | 738 """ |
739 self.RunShellCommand('input keyevent %d' % keycode) | 739 self.RunShellCommand('input keyevent %d' % keycode) |
740 | 740 |
741 def CheckMd5Sum(self, local_path, device_path): | 741 def _RunMd5Sum(self, host_path, device_path): |
frankf
2013/08/06 00:17:23
Why not inline this in GetFilesChanged?
craigdh
2013/08/06 21:17:29
Could, this seems reusable and keeps the functions
| |
742 """Compares the md5sum of a local path against a device path. | 742 """Gets the md5sum of a host path and device path. |
743 | 743 |
744 Args: | 744 Args: |
745 local_path: Path (file or directory) on the host. | 745 host_path: Path (file or directory) on the host. |
746 device_path: Path on the device. | 746 device_path: Path on the device. |
747 | 747 |
748 Returns: | 748 Returns: |
749 True if the md5sums match. | 749 A tuple containing lists of the host and device md5sum results as |
750 created by _ComputeFileListHash(). | |
750 """ | 751 """ |
751 if not self._md5sum_build_dir: | 752 if not self._md5sum_build_dir: |
752 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') | 753 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') |
753 build_dir = '%s/%s/' % ( | 754 build_dir = '%s/%s/' % ( |
754 cmd_helper.OutDirectory().get(), default_build_type) | 755 cmd_helper.OutDirectory().get(), default_build_type) |
755 md5sum_dist_path = '%s/md5sum_dist' % build_dir | 756 md5sum_dist_path = '%s/md5sum_dist' % build_dir |
756 if not os.path.exists(md5sum_dist_path): | 757 if not os.path.exists(md5sum_dist_path): |
757 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() | 758 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() |
758 md5sum_dist_path = '%s/md5sum_dist' % build_dir | 759 md5sum_dist_path = '%s/md5sum_dist' % build_dir |
759 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' | 760 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' |
760 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) | 761 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) |
761 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) | 762 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) |
762 self._md5sum_build_dir = build_dir | 763 self._md5sum_build_dir = build_dir |
763 | 764 |
764 cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + | 765 cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + |
765 MD5SUM_DEVICE_PATH + ' ' + device_path) | 766 MD5SUM_DEVICE_PATH + ' ' + device_path) |
766 device_hash_tuples = _ComputeFileListHash( | 767 device_hash_tuples = _ComputeFileListHash( |
767 self.RunShellCommand(cmd, timeout_time=2 * 60)) | 768 self.RunShellCommand(cmd, timeout_time=2 * 60)) |
768 assert os.path.exists(local_path), 'Local path not found %s' % local_path | 769 assert os.path.exists(host_path), 'Local path not found %s' % host_path |
769 md5sum_output = cmd_helper.GetCmdOutput( | 770 md5sum_output = cmd_helper.GetCmdOutput( |
770 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) | 771 ['%s/md5sum_bin_host' % self._md5sum_build_dir, host_path]) |
771 host_hash_tuples = _ComputeFileListHash(md5sum_output.splitlines()) | 772 host_hash_tuples = _ComputeFileListHash(md5sum_output.splitlines()) |
773 return (host_hash_tuples, device_hash_tuples) | |
774 | |
775 def GetFilesChanged(self, host_path, device_path): | |
776 """Compares the md5sum of a host path against a device path. | |
777 | |
778 Note: Ignores extra files on the device. | |
779 | |
780 Args: | |
781 host_path: Path (file or directory) on the host. | |
782 device_path: Path on the device. | |
783 | |
784 Returns: | |
785 A list of tuples of the form (host_path, device_path) for files whose | |
786 md5sums do not match. | |
787 """ | |
788 host_hash_tuples, device_hash_tuples = self._RunMd5Sum( | |
789 host_path, device_path) | |
772 | 790 |
773 # Ignore extra files on the device. | 791 # Ignore extra files on the device. |
774 if len(device_hash_tuples) > len(host_hash_tuples): | 792 if len(device_hash_tuples) > len(host_hash_tuples): |
775 host_files = [os.path.relpath(os.path.normpath(p.path), | 793 host_files = [os.path.relpath(os.path.normpath(p.path), |
776 os.path.normpath(local_path)) for p in host_hash_tuples] | 794 os.path.normpath(host_path)) for p in host_hash_tuples] |
777 | 795 |
778 def _host_has(fname): | 796 def HostHas(fname): |
779 return any(path in fname for path in host_files) | 797 return any(path in fname for path in host_files) |
780 | 798 |
781 hashes_on_device = [h.hash for h in device_hash_tuples if | 799 device_hash_tuples = [h for h in device_hash_tuples if HostHas(h.path)] |
782 _host_has(h.path)] | |
783 else: | |
784 hashes_on_device = [h.hash for h in device_hash_tuples] | |
785 | 800 |
786 # Compare md5sums between host and device files. | 801 # Constructs the target device path from a given host path. Don't use when |
787 hashes_on_host = [h.hash for h in host_hash_tuples] | 802 # only a single file is given as the base name given in device_path may |
788 hashes_on_device.sort() | 803 # differ from that in host_path. |
789 hashes_on_host.sort() | 804 def HostToDevicePath(host_path): |
790 return hashes_on_device == hashes_on_host | 805 return os.path.join(os.path.dirname(device_path), os.path.relpath( |
806 host_path, os.path.dirname(os.path.normpath(host_path)))) | |
791 | 807 |
792 def PushIfNeeded(self, local_path, device_path): | 808 device_hashes = [h.hash for h in device_hash_tuples] |
793 """Pushes |local_path| to |device_path|. | 809 return [(t.path, HostToDevicePath(t.path) if os.path.isdir(host_path) else |
810 device_path) | |
811 for t in host_hash_tuples if t.hash not in device_hashes] | |
812 | |
813 def CheckMd5Sum(self, host_path, device_path): | |
frankf
2013/08/06 00:17:23
Can you get rid of this and just call GetFilesChan
craigdh
2013/08/06 21:17:29
Done.
| |
814 """Compares the md5sum of a host path against a device path. | |
815 | |
816 Args: | |
817 host_path: Path (file or directory) on the host. | |
818 device_path: Path on the device. | |
819 | |
820 Returns: | |
821 True if the md5sums match. | |
822 """ | |
823 return not self.GetFilesChanged(host_path, device_path) | |
824 | |
825 def PushIfNeeded(self, host_path, device_path): | |
826 """Pushes |host_path| to |device_path|. | |
794 | 827 |
795 Works for files and directories. This method skips copying any paths in | 828 Works for files and directories. This method skips copying any paths in |
796 |test_data_paths| that already exist on the device with the same hash. | 829 |test_data_paths| that already exist on the device with the same hash. |
797 | 830 |
798 All pushed files can be removed by calling RemovePushedFiles(). | 831 All pushed files can be removed by calling RemovePushedFiles(). |
799 """ | 832 """ |
800 assert os.path.exists(local_path), 'Local path not found %s' % local_path | 833 MAX_INDIVIDUAL_PUSHES = 50 |
801 size = int(cmd_helper.GetCmdOutput(['du', '-sb', local_path]).split()[0]) | 834 assert os.path.exists(host_path), 'Local path not found %s' % host_path |
835 | |
836 def GetHostSize(path): | |
837 return int(cmd_helper.GetCmdOutput(['du', '-sb', path]).split()[0]) | |
838 | |
839 size = GetHostSize(host_path) | |
802 self._pushed_files.append(device_path) | 840 self._pushed_files.append(device_path) |
803 self._potential_push_size += size | 841 self._potential_push_size += size |
804 | 842 |
805 if self.CheckMd5Sum(local_path, device_path): | 843 missing_files = self.GetFilesChanged(host_path, device_path) |
frankf
2013/08/06 00:17:23
this is misleading -> changed_files
craigdh
2013/08/06 21:17:29
Done.
| |
844 if not missing_files: | |
806 return | 845 return |
807 | 846 |
808 self._actual_push_size += size | 847 def Push(host, device): |
809 # They don't match, so remove everything first and then create it. | 848 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout |
810 if os.path.isdir(local_path): | 849 # of 60 seconds which isn't sufficient for a lot of users of this method. |
811 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) | 850 push_command = 'push %s %s' % (host, device) |
812 self.RunShellCommand('mkdir -p %s' % device_path) | 851 self._LogShell(push_command) |
813 | 852 |
814 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 853 # Retry push with increasing backoff if the device is busy. |
815 # 60 seconds which isn't sufficient for a lot of users of this method. | 854 retry = 0 |
816 push_command = 'push %s %s' % (local_path, device_path) | 855 while True: |
817 self._LogShell(push_command) | 856 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) |
857 if _HasAdbPushSucceeded(output): | |
858 return | |
859 if 'resource busy' in output and retry < 3: | |
860 retry += 1 | |
861 wait_time = 5 * retry | |
862 logging.error('Push failed, retrying in %d seconds: %s' % | |
863 (wait_time, output)) | |
864 time.sleep(wait_time) | |
865 else: | |
866 raise Exception('Push failed: %s' % output) | |
818 | 867 |
819 # Retry push with increasing backoff if the device is busy. | 868 diff_size = 0 |
820 retry = 0 | 869 if len(missing_files) <= MAX_INDIVIDUAL_PUSHES: |
821 while True: | 870 diff_size = sum(GetHostSize(f[0]) for f in missing_files) |
822 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) | 871 |
823 if _HasAdbPushSucceeded(output): | 872 # TODO(craigdh): Replace this educated guess with a heuristic that |
824 return | 873 # approximates the push time for each method. |
frankf
2013/08/06 00:17:23
Can you look how adb sync operates: https://androi
craigdh
2013/08/06 21:17:29
Seems to just compare the timestamps. From what I
| |
825 if 'resource busy' in output and retry < 3: | 874 if len(missing_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size: |
826 retry += 1 | 875 # We're pushing everything, remove everything first and then create it. |
827 wait_time = 5 * retry | 876 self._actual_push_size += size |
828 logging.error('Push failed, retrying in %d seconds: %s' % | 877 if os.path.isdir(host_path): |
829 (wait_time, output)) | 878 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) |
830 time.sleep(wait_time) | 879 self.RunShellCommand('mkdir -p %s' % device_path) |
831 else: | 880 Push(host_path, device_path) |
832 raise Exception('Push failed: %s' % output) | 881 else: |
882 for f in missing_files: | |
883 Push(f[0], f[1]) | |
884 self._actual_push_size += diff_size | |
833 | 885 |
834 def GetPushSizeInfo(self): | 886 def GetPushSizeInfo(self): |
835 """Get total size of pushes to the device done via PushIfNeeded() | 887 """Get total size of pushes to the device done via PushIfNeeded() |
836 | 888 |
837 Returns: | 889 Returns: |
838 A tuple: | 890 A tuple: |
839 1. Total size of push requests to PushIfNeeded (MB) | 891 1. Total size of push requests to PushIfNeeded (MB) |
840 2. Total size that was actually pushed (MB) | 892 2. Total size that was actually pushed (MB) |
841 """ | 893 """ |
842 return (self._potential_push_size, self._actual_push_size) | 894 return (self._potential_push_size, self._actual_push_size) |
(...skipping 629 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1472 """ | 1524 """ |
1473 def __init__(self, output): | 1525 def __init__(self, output): |
1474 self._output = output | 1526 self._output = output |
1475 | 1527 |
1476 def write(self, data): | 1528 def write(self, data): |
1477 data = data.replace('\r\r\n', '\n') | 1529 data = data.replace('\r\r\n', '\n') |
1478 self._output.write(data) | 1530 self._output.write(data) |
1479 | 1531 |
1480 def flush(self): | 1532 def flush(self): |
1481 self._output.flush() | 1533 self._output.flush() |
OLD | NEW |