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 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
54 | 54 |
55 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). | 55 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). |
56 KEYCODE_HOME = 3 | 56 KEYCODE_HOME = 3 |
57 KEYCODE_BACK = 4 | 57 KEYCODE_BACK = 4 |
58 KEYCODE_DPAD_UP = 19 | 58 KEYCODE_DPAD_UP = 19 |
59 KEYCODE_DPAD_DOWN = 20 | 59 KEYCODE_DPAD_DOWN = 20 |
60 KEYCODE_DPAD_RIGHT = 22 | 60 KEYCODE_DPAD_RIGHT = 22 |
61 KEYCODE_ENTER = 66 | 61 KEYCODE_ENTER = 66 |
62 KEYCODE_MENU = 82 | 62 KEYCODE_MENU = 82 |
63 | 63 |
64 MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/' | |
65 MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin' | |
66 MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER | |
67 | |
68 def GetEmulators(): | 64 def GetEmulators(): |
69 """Returns a list of emulators. Does not filter by status (e.g. offline). | 65 """Returns a list of emulators. Does not filter by status (e.g. offline). |
70 | 66 |
71 Both devices starting with 'emulator' will be returned in below output: | 67 Both devices starting with 'emulator' will be returned in below output: |
72 | 68 |
73 * daemon not running. starting it now on port 5037 * | 69 * daemon not running. starting it now on port 5037 * |
74 * daemon started successfully * | 70 * daemon started successfully * |
75 List of devices attached | 71 List of devices attached |
76 027c10494100b4d7 device | 72 027c10494100b4d7 device |
77 emulator-5554 offline | 73 emulator-5554 offline |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
169 lastmod -= utc_delta | 165 lastmod -= utc_delta |
170 files[filename] = (int(file_match.group('size')), lastmod) | 166 files[filename] = (int(file_match.group('size')), lastmod) |
171 return files | 167 return files |
172 | 168 |
173 | 169 |
174 def _ComputeFileListHash(md5sum_output): | 170 def _ComputeFileListHash(md5sum_output): |
175 """Returns a list of MD5 strings from the provided md5sum output.""" | 171 """Returns a list of MD5 strings from the provided md5sum output.""" |
176 return [line.split(' ')[0] for line in md5sum_output] | 172 return [line.split(' ')[0] for line in md5sum_output] |
177 | 173 |
178 | 174 |
175 def _ComputeFileListTimesModified(timemodified_output): | |
176 """Returns a list of tuples from the provided timemodified output. | |
177 | |
178 Args: | |
179 timemodified_output: output directly from timemodified binary. | |
180 | |
181 Returns: | |
182 List of namedtuples (time, path). | |
183 """ | |
184 TimeAndPath = collections.namedtuple('TimeAndPath', ['time', 'path']) | |
185 split_lines = [line.split(' ') for line in timemodified_output] | |
186 assert all(len(s) == 2 for s in split_lines), 'Invalid timemodified output' | |
187 return [TimeAndPath._make(s) for s in split_lines] | |
188 | |
189 | |
179 def _HasAdbPushSucceeded(command_output): | 190 def _HasAdbPushSucceeded(command_output): |
180 """Returns whether adb push has succeeded from the provided output.""" | 191 """Returns whether adb push has succeeded from the provided output.""" |
181 # TODO(frankf): We should look at the return code instead of the command | 192 # TODO(frankf): We should look at the return code instead of the command |
182 # output for many of the commands in this file. | 193 # output for many of the commands in this file. |
183 if not command_output: | 194 if not command_output: |
184 return True | 195 return True |
185 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" | 196 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
186 # Errors look like this: "failed to copy ... " | 197 # Errors look like this: "failed to copy ... " |
187 if not re.search('^[0-9]', command_output.splitlines()[-1]): | 198 if not re.search('^[0-9]', command_output.splitlines()[-1]): |
188 logging.critical('PUSH FAILED: ' + command_output) | 199 logging.critical('PUSH FAILED: ' + command_output) |
(...skipping 29 matching lines...) Expand all Loading... | |
218 self._adb.SetTargetSerial(device) | 229 self._adb.SetTargetSerial(device) |
219 self._device = device | 230 self._device = device |
220 self._logcat = None | 231 self._logcat = None |
221 self.logcat_process = None | 232 self.logcat_process = None |
222 self._logcat_tmpoutfile = None | 233 self._logcat_tmpoutfile = None |
223 self._pushed_files = [] | 234 self._pushed_files = [] |
224 self._device_utc_offset = None | 235 self._device_utc_offset = None |
225 self._potential_push_size = 0 | 236 self._potential_push_size = 0 |
226 self._actual_push_size = 0 | 237 self._actual_push_size = 0 |
227 self._md5sum_build_dir = '' | 238 self._md5sum_build_dir = '' |
239 self._timemodified_build_dir = '' | |
228 self._external_storage = '' | 240 self._external_storage = '' |
229 self._util_wrapper = '' | 241 self._util_wrapper = '' |
230 | 242 |
231 def _LogShell(self, cmd): | 243 def _LogShell(self, cmd): |
232 """Logs the adb shell command.""" | 244 """Logs the adb shell command.""" |
233 if self._device: | 245 if self._device: |
234 device_repr = self._device[-4:] | 246 device_repr = self._device[-4:] |
235 else: | 247 else: |
236 device_repr = '????' | 248 device_repr = '????' |
237 logging.info('[%s]> %s', device_repr, cmd) | 249 logging.info('[%s]> %s', device_repr, cmd) |
(...skipping 467 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
705 | 717 |
706 Args: | 718 Args: |
707 local_path: Path (file or directory) on the host. | 719 local_path: Path (file or directory) on the host. |
708 device_path: Path on the device. | 720 device_path: Path on the device. |
709 ignore_paths: If False, both the md5sum and the relative paths/names of | 721 ignore_paths: If False, both the md5sum and the relative paths/names of |
710 files must match. If True, only the md5sum must match. | 722 files must match. If True, only the md5sum must match. |
711 | 723 |
712 Returns: | 724 Returns: |
713 True if the md5sums match. | 725 True if the md5sums match. |
714 """ | 726 """ |
727 MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/' | |
728 MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin' | |
729 MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER | |
715 if not self._md5sum_build_dir: | 730 if not self._md5sum_build_dir: |
716 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') | 731 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') |
717 build_dir = '%s/%s/' % ( | 732 build_dir = '%s/%s/' % ( |
718 cmd_helper.OutDirectory().get(), default_build_type) | 733 cmd_helper.OutDirectory().get(), default_build_type) |
719 md5sum_dist_path = '%s/md5sum_dist' % build_dir | 734 md5sum_dist_path = '%s/md5sum_dist' % build_dir |
720 if not os.path.exists(md5sum_dist_path): | 735 if not os.path.exists(md5sum_dist_path): |
721 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() | 736 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() |
722 md5sum_dist_path = '%s/md5sum_dist' % build_dir | 737 md5sum_dist_path = '%s/md5sum_dist' % build_dir |
723 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' | 738 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' |
724 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) | 739 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) |
725 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) | 740 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) |
726 self._md5sum_build_dir = build_dir | 741 self._md5sum_build_dir = build_dir |
727 | 742 |
728 hashes_on_device = _ComputeFileListHash( | 743 hashes_on_device = _ComputeFileListHash( |
729 self.RunShellCommand(MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + | 744 self.RunShellCommand(MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + |
730 ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path)) | 745 ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path)) |
731 assert os.path.exists(local_path), 'Local path not found %s' % local_path | 746 assert os.path.exists(local_path), 'Local path not found %s' % local_path |
732 md5sum_output = cmd_helper.GetCmdOutput( | 747 md5sum_output = cmd_helper.GetCmdOutput( |
733 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) | 748 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) |
734 hashes_on_host = _ComputeFileListHash(md5sum_output.splitlines()) | 749 hashes_on_host = _ComputeFileListHash(md5sum_output.splitlines()) |
735 | 750 |
736 if ignore_paths: | 751 if ignore_paths: |
737 hashes_on_device = [h.split()[0] for h in hashes_on_device] | 752 hashes_on_device = [h.split()[0] for h in hashes_on_device] |
738 hashes_on_host = [h.split()[0] for h in hashes_on_host] | 753 hashes_on_host = [h.split()[0] for h in hashes_on_host] |
739 | 754 |
740 return hashes_on_device == hashes_on_host | 755 return hashes_on_device == hashes_on_host |
741 | 756 |
757 def AreFilesUnsynchronized(self, local_path, device_path): | |
bulach
2013/07/15 18:35:20
nit: make this internal, "_" prefix
craigdh
2013/07/15 19:06:50
Done.
| |
758 """Compares the time modified of a local path against a device path. | |
bulach
2013/07/15 18:35:20
could probably add a note saying that files transf
craigdh
2013/07/15 19:06:50
Done.
| |
759 | |
760 Args: | |
761 local_path: Path (file or directory) on the host. | |
762 device_path: Path (file or directory) on the device. | |
763 | |
764 Returns: | |
765 True if the time the files were modified match. | |
766 """ | |
767 TIMEMODIFIED_DEVICE_DIRECTORY = (constants.TEST_EXECUTABLE_DIR + | |
768 '/timemodified/') | |
769 TIMEMODIFIED_DEVICE_PATH = (TIMEMODIFIED_DEVICE_DIRECTORY + | |
770 'timemodified_bin') | |
771 TIMEMODIFIED_LD_LIBRARY_PATH = ('LD_LIBRARY_PATH=%s' % | |
772 TIMEMODIFIED_DEVICE_DIRECTORY) | |
773 assert os.path.exists(local_path), 'Local path not found %s' % local_path | |
774 | |
775 if not self._timemodified_build_dir: | |
776 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') | |
777 build_dir = '%s/%s/' % ( | |
778 cmd_helper.OutDirectory().get(), default_build_type) | |
779 host_timemodified_dist_path = '%s/timemodified_dist' % build_dir | |
780 if not os.path.exists(host_timemodified_dist_path): | |
781 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() | |
782 host_timemodified_dist_path = '%s/timemodified_dist' % build_dir | |
783 assert os.path.exists(host_timemodified_dist_path), 'Build timemodified' | |
784 command = 'push %s %s' % (host_timemodified_dist_path, | |
785 TIMEMODIFIED_DEVICE_DIRECTORY) | |
786 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) | |
787 self._timemodified_build_dir = build_dir | |
788 | |
789 self._pushed_files.append(device_path) | |
790 device_timemodified_output = self.RunShellCommand( | |
791 TIMEMODIFIED_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + | |
792 TIMEMODIFIED_DEVICE_PATH + ' ' + device_path) | |
793 device_time_tuples = _ComputeFileListTimesModified( | |
794 device_timemodified_output) | |
795 assert os.path.exists(local_path), 'Local path not found %s' % local_path | |
796 host_timemodified_output = cmd_helper.GetCmdOutput( | |
797 ['%s/timemodified_bin_host' % self._timemodified_build_dir, local_path]) | |
798 host_time_tuples = _ComputeFileListTimesModified( | |
799 host_timemodified_output.splitlines()) | |
800 | |
801 if len(device_time_tuples) < len(host_time_tuples): | |
802 return True | |
803 | |
804 # Ignore extra files on the device. | |
805 if len(device_time_tuples) > len(host_time_tuples): | |
806 host_files = [os.path.relpath(os.path.normpath(p.path), | |
807 os.path.normpath(local_path)) for p in host_time_tuples] | |
808 | |
809 def _host_has(fname): | |
810 return any(path in fname for path in host_files) | |
811 | |
812 times_on_device = [t.time for t in device_time_tuples if | |
813 _host_has(t.path)] | |
814 else: | |
815 times_on_device = [t.time for t in device_time_tuples] | |
816 | |
817 # Compare timestamps between host and device files. | |
818 times_on_host = [t.time for t in host_time_tuples] | |
819 times_on_device.sort() | |
820 times_on_host.sort() | |
821 return any(int(dtime) < int(htime) for dtime, htime in | |
bulach
2013/07/15 18:35:20
should we check for != instead? i.e., a file modif
craigdh
2013/07/15 19:06:50
Done.
| |
822 zip(times_on_device, times_on_host)) | |
823 | |
742 def PushIfNeeded(self, local_path, device_path): | 824 def PushIfNeeded(self, local_path, device_path): |
743 """Pushes |local_path| to |device_path|. | 825 """Pushes |local_path| to |device_path|. |
744 | 826 |
745 Works for files and directories. This method skips copying any paths in | 827 Works for files and directories. This method skips copying any paths in |
746 |test_data_paths| that already exist on the device with the same hash. | 828 |test_data_paths| that already exist on the device with the same modified |
829 time. | |
747 | 830 |
748 All pushed files can be removed by calling RemovePushedFiles(). | 831 All pushed files can be removed by calling RemovePushedFiles(). |
749 """ | 832 """ |
750 assert os.path.exists(local_path), 'Local path not found %s' % local_path | 833 assert os.path.exists(local_path), 'Local path not found %s' % local_path |
751 size = int(cmd_helper.GetCmdOutput(['du', '-sb', local_path]).split()[0]) | 834 size = int(cmd_helper.GetCmdOutput(['du', '-sb', local_path]).split()[0]) |
752 self._pushed_files.append(device_path) | 835 self._pushed_files.append(device_path) |
753 self._potential_push_size += size | 836 self._potential_push_size += size |
754 | 837 |
755 if self.CheckMd5Sum(local_path, device_path): | 838 if not self.AreFilesUnsynchronized(local_path, device_path): |
756 return | 839 return |
757 | 840 |
758 self._actual_push_size += size | 841 self._actual_push_size += size |
759 # They don't match, so remove everything first and then create it. | 842 # They don't match, so remove everything first and then create it. |
760 if os.path.isdir(local_path): | 843 if os.path.isdir(local_path): |
761 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) | 844 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) |
762 self.RunShellCommand('mkdir -p %s' % device_path) | 845 self.RunShellCommand('mkdir -p %s' % device_path) |
763 | 846 |
764 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 847 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of |
765 # 60 seconds which isn't sufficient for a lot of users of this method. | 848 # 60 seconds which isn't sufficient for a lot of users of this method. |
(...skipping 531 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1297 host_dir = os.path.dirname(host_file) | 1380 host_dir = os.path.dirname(host_file) |
1298 if not os.path.exists(host_dir): | 1381 if not os.path.exists(host_dir): |
1299 os.makedirs(host_dir) | 1382 os.makedirs(host_dir) |
1300 device_file = '%s/screenshot.png' % self.GetExternalStorage() | 1383 device_file = '%s/screenshot.png' % self.GetExternalStorage() |
1301 self.RunShellCommand('/system/bin/screencap -p %s' % device_file) | 1384 self.RunShellCommand('/system/bin/screencap -p %s' % device_file) |
1302 assert self._adb.Pull(device_file, host_file) | 1385 assert self._adb.Pull(device_file, host_file) |
1303 assert os.path.exists(host_file) | 1386 assert os.path.exists(host_file) |
1304 | 1387 |
1305 def SetUtilWrapper(self, util_wrapper): | 1388 def SetUtilWrapper(self, util_wrapper): |
1306 """Sets a wrapper prefix to be used when running a locally-built | 1389 """Sets a wrapper prefix to be used when running a locally-built |
1307 binary on the device (ex.: md5sum_bin). | 1390 binary on the device (ex.: timemodified_bin). |
1308 """ | 1391 """ |
1309 self._util_wrapper = util_wrapper | 1392 self._util_wrapper = util_wrapper |
1310 | 1393 |
1311 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): | 1394 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): |
1312 """Runs a single instrumentation test. | 1395 """Runs a single instrumentation test. |
1313 | 1396 |
1314 Args: | 1397 Args: |
1315 test: Test class/method. | 1398 test: Test class/method. |
1316 test_package: Package name of test apk. | 1399 test_package: Package name of test apk. |
1317 instr_args: Extra key/value to pass to am instrument. | 1400 instr_args: Extra key/value to pass to am instrument. |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1367 """ | 1450 """ |
1368 def __init__(self, output): | 1451 def __init__(self, output): |
1369 self._output = output | 1452 self._output = output |
1370 | 1453 |
1371 def write(self, data): | 1454 def write(self, data): |
1372 data = data.replace('\r\r\n', '\n') | 1455 data = data.replace('\r\r\n', '\n') |
1373 self._output.write(data) | 1456 self._output.write(data) |
1374 | 1457 |
1375 def flush(self): | 1458 def flush(self): |
1376 self._output.flush() | 1459 self._output.flush() |
OLD | NEW |