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 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
164 if isinstance(utc_offset, str) and len(utc_offset) == 5: | 160 if isinstance(utc_offset, str) and len(utc_offset) == 5: |
165 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), | 161 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), |
166 minutes=int(utc_offset[3:5])) | 162 minutes=int(utc_offset[3:5])) |
167 if utc_offset[0:1] == '-': | 163 if utc_offset[0:1] == '-': |
168 utc_delta = -utc_delta | 164 utc_delta = -utc_delta |
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 _ComputeFileListTimesModified(timemodified_output): |
175 """Returns a list of MD5 strings from the provided md5sum output.""" | 171 """Returns a list of tuples from the provided timemodified output. |
176 return [line.split(' ')[0] for line in md5sum_output] | 172 |
173 Args: | |
174 timemodified_output: output directly from timemodified binary. | |
175 | |
176 Returns: | |
177 List of tuples of the form (time_modified, file_path). | |
178 """ | |
179 return [line.split(' ')[:2] for line in timemodified_output] | |
frankf
2013/07/02 18:45:29
Is this number of spaces important. Can you just d
craigdh
2013/07/02 20:34:15
Yep, important as discussed offline. Added an asse
| |
177 | 180 |
178 | 181 |
179 def _HasAdbPushSucceeded(command_output): | 182 def _HasAdbPushSucceeded(command_output): |
180 """Returns whether adb push has succeeded from the provided output.""" | 183 """Returns whether adb push has succeeded from the provided output.""" |
181 if not command_output: | 184 if not command_output: |
182 return False | 185 return False |
183 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" | 186 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
184 # Errors look like this: "failed to copy ... " | 187 # Errors look like this: "failed to copy ... " |
185 if not re.search('^[0-9]', command_output.splitlines()[-1]): | 188 if not re.search('^[0-9]', command_output.splitlines()[-1]): |
186 logging.critical('PUSH FAILED: ' + command_output) | 189 logging.critical('PUSH FAILED: ' + command_output) |
(...skipping 26 matching lines...) Expand all Loading... | |
213 os.environ['PATH'] += os.pathsep + adb_dir | 216 os.environ['PATH'] += os.pathsep + adb_dir |
214 self._adb = adb_interface.AdbInterface() | 217 self._adb = adb_interface.AdbInterface() |
215 if device: | 218 if device: |
216 self._adb.SetTargetSerial(device) | 219 self._adb.SetTargetSerial(device) |
217 self._device = device | 220 self._device = device |
218 self._logcat = None | 221 self._logcat = None |
219 self.logcat_process = None | 222 self.logcat_process = None |
220 self._logcat_tmpoutfile = None | 223 self._logcat_tmpoutfile = None |
221 self._pushed_files = [] | 224 self._pushed_files = [] |
222 self._device_utc_offset = self.RunShellCommand('date +%z')[0] | 225 self._device_utc_offset = self.RunShellCommand('date +%z')[0] |
223 self._md5sum_build_dir = '' | 226 self._timemodified_build_dir = '' |
224 self._external_storage = '' | 227 self._external_storage = '' |
225 self._util_wrapper = '' | 228 self._util_wrapper = '' |
226 | 229 |
227 def _LogShell(self, cmd): | 230 def _LogShell(self, cmd): |
228 """Logs the adb shell command.""" | 231 """Logs the adb shell command.""" |
229 if self._device: | 232 if self._device: |
230 device_repr = self._device[-4:] | 233 device_repr = self._device[-4:] |
231 else: | 234 else: |
232 device_repr = '????' | 235 device_repr = '????' |
233 logging.info('[%s]> %s', device_repr, cmd) | 236 logging.info('[%s]> %s', device_repr, cmd) |
(...skipping 455 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
689 self.RunShellCommand('pm clear ' + package) | 692 self.RunShellCommand('pm clear ' + package) |
690 | 693 |
691 def SendKeyEvent(self, keycode): | 694 def SendKeyEvent(self, keycode): |
692 """Sends keycode to the device. | 695 """Sends keycode to the device. |
693 | 696 |
694 Args: | 697 Args: |
695 keycode: Numeric keycode to send (see "enum" at top of file). | 698 keycode: Numeric keycode to send (see "enum" at top of file). |
696 """ | 699 """ |
697 self.RunShellCommand('input keyevent %d' % keycode) | 700 self.RunShellCommand('input keyevent %d' % keycode) |
698 | 701 |
699 def CheckMd5Sum(self, local_path, device_path, ignore_paths=False): | 702 def AreFilesUnsynchronized(self, local_path, device_path): |
700 """Compares the md5sum of a local path against a device path. | 703 """Compares the time modified of a local path against a device path. |
701 | 704 |
702 Args: | 705 Args: |
703 local_path: Path (file or directory) on the host. | 706 local_path: Path (file or directory) on the host. |
704 device_path: Path on the device. | 707 device_path: Path on the device. |
frankf
2013/07/02 18:45:29
file or directory?
craigdh
2013/07/02 20:34:15
either. clarified.
| |
705 ignore_paths: If False, both the md5sum and the relative paths/names of | |
706 files must match. If True, only the md5sum must match. | |
707 | 708 |
708 Returns: | 709 Returns: |
709 True if the md5sums match. | 710 True if the time the files were modified match. |
710 """ | 711 """ |
712 TIMEMODIFIED_DEVICE_DIRECTORY = (constants.TEST_EXECUTABLE_DIR + | |
713 '/timemodified/') | |
714 TIMEMODIFIED_DEVICE_PATH = (TIMEMODIFIED_DEVICE_DIRECTORY + | |
715 'timemodified_bin') | |
716 TIMEMODIFIED_LD_LIBRARY_PATH = ('LD_LIBRARY_PATH=%s' % | |
717 TIMEMODIFIED_DEVICE_DIRECTORY) | |
711 assert os.path.exists(local_path), 'Local path not found %s' % local_path | 718 assert os.path.exists(local_path), 'Local path not found %s' % local_path |
712 | 719 |
713 if not self._md5sum_build_dir: | 720 if not self._timemodified_build_dir: |
714 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') | 721 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') |
715 build_dir = '%s/%s/' % ( | 722 build_dir = '%s/%s/' % ( |
716 cmd_helper.OutDirectory().get(), default_build_type) | 723 cmd_helper.OutDirectory().get(), default_build_type) |
717 md5sum_dist_path = '%s/md5sum_dist' % build_dir | 724 host_timemodified_dist_path = '%s/timemodified_dist' % build_dir |
frankf
2013/07/02 18:45:29
Why is this not a constant like the device counter
craigdh
2013/07/02 20:34:15
Because it is determined dynamically at runtime.
| |
718 if not os.path.exists(md5sum_dist_path): | 725 if not os.path.exists(host_timemodified_dist_path): |
719 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() | 726 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() |
720 md5sum_dist_path = '%s/md5sum_dist' % build_dir | 727 host_timemodified_dist_path = '%s/timemodified_dist' % build_dir |
721 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' | 728 assert os.path.exists(host_timemodified_dist_path), 'Build timemodified' |
722 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) | 729 command = 'push %s %s' % (host_timemodified_dist_path, |
730 TIMEMODIFIED_DEVICE_DIRECTORY) | |
723 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) | 731 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) |
724 self._md5sum_build_dir = build_dir | 732 self._timemodified_build_dir = build_dir |
725 | 733 |
726 self._pushed_files.append(device_path) | 734 self._pushed_files.append(device_path) |
727 hashes_on_device = _ComputeFileListHash( | 735 device_timemodified_output = self.RunShellCommand( |
728 self.RunShellCommand(MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + | 736 TIMEMODIFIED_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + |
729 ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path)) | 737 TIMEMODIFIED_DEVICE_PATH + ' ' + device_path) |
738 device_time_tuples = _ComputeFileListTimesModified( | |
739 device_timemodified_output) | |
730 assert os.path.exists(local_path), 'Local path not found %s' % local_path | 740 assert os.path.exists(local_path), 'Local path not found %s' % local_path |
731 md5sum_output = cmd_helper.GetCmdOutput( | 741 host_timemodified_output = cmd_helper.GetCmdOutput( |
732 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) | 742 ['%s/timemodified_bin_host' % self._timemodified_build_dir, local_path]) |
733 hashes_on_host = _ComputeFileListHash(md5sum_output.splitlines()) | 743 host_time_tuples = _ComputeFileListTimesModified( |
frankf
2013/07/02 18:45:29
Can you use named tuples instead of p[0]/[1]
craigdh
2013/07/02 20:34:15
Done.
| |
744 host_timemodified_output.splitlines()) | |
734 | 745 |
735 if ignore_paths: | 746 if len(device_time_tuples) < len(host_time_tuples): |
736 hashes_on_device = [h.split()[0] for h in hashes_on_device] | 747 return True |
737 hashes_on_host = [h.split()[0] for h in hashes_on_host] | |
738 | 748 |
739 return hashes_on_device == hashes_on_host | 749 # Ignore extra files on the device. |
frankf
2013/07/02 18:45:29
Add a TODO to clean up old data on the device.
craigdh
2013/07/02 20:34:15
Done.
| |
750 device_time_tuples = [t for t in device_time_tuples if t[1] != '.'] | |
frankf
2013/07/02 18:45:29
Why are listing this?
craigdh
2013/07/02 20:34:15
Already removed in the patch I uploaded while you
| |
751 if len(device_time_tuples) > len(host_time_tuples): | |
752 host_files = [os.path.relpath(os.path.normpath(p[1]), | |
753 os.path.normpath(local_path)) for p in host_time_tuples] | |
754 def _host_has(fname): | |
frankf
2013/07/02 18:45:29
Use blank lines to improve readability
craigdh
2013/07/02 20:34:15
Done.
| |
755 return any(p in fname for p in host_files) | |
756 times_on_device = [p[0] for p in device_time_tuples if _host_has(p[1])] | |
757 else: | |
758 times_on_device = [t[0] for t in device_time_tuples] | |
759 | |
760 # Compare timestamps between host and device files. | |
761 times_on_host = [t[0] for t in host_time_tuples] | |
762 times_on_device.sort() | |
763 times_on_host.sort() | |
764 return any(int(dtime) < int(htime) for dtime, htime in | |
765 zip(times_on_device, times_on_host)) | |
740 | 766 |
741 def PushIfNeeded(self, local_path, device_path): | 767 def PushIfNeeded(self, local_path, device_path): |
742 """Pushes |local_path| to |device_path|. | 768 """Pushes |local_path| to |device_path|. |
743 | 769 |
744 Works for files and directories. This method skips copying any paths in | 770 Works for files and directories. This method skips copying any paths in |
745 |test_data_paths| that already exist on the device with the same hash. | 771 |test_data_paths| that already exist on the device with the same modified |
772 time. | |
746 | 773 |
747 All pushed files can be removed by calling RemovePushedFiles(). | 774 All pushed files can be removed by calling RemovePushedFiles(). |
748 """ | 775 """ |
749 if self.CheckMd5Sum(local_path, device_path): | 776 if not self.AreFilesUnsynchronized(local_path, device_path): |
750 return | 777 return |
751 | 778 |
752 # They don't match, so remove everything first and then create it. | 779 # They don't match, so remove everything first and then create it. |
753 if os.path.isdir(local_path): | 780 if os.path.isdir(local_path): |
754 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) | 781 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) |
755 self.RunShellCommand('mkdir -p %s' % device_path) | 782 self.RunShellCommand('mkdir -p %s' % device_path) |
756 | 783 |
757 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 784 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of |
758 # 60 seconds which isn't sufficient for a lot of users of this method. | 785 # 60 seconds which isn't sufficient for a lot of users of this method. |
759 push_command = 'push %s %s' % (local_path, device_path) | 786 push_command = 'push %s %s' % (local_path, device_path) |
760 self._LogShell(push_command) | 787 self._LogShell(push_command) |
761 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) | 788 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) |
762 assert _HasAdbPushSucceeded(output) | 789 assert _HasAdbPushSucceeded(output) |
763 | 790 |
764 | |
765 def GetFileContents(self, filename, log_result=False): | 791 def GetFileContents(self, filename, log_result=False): |
766 """Gets contents from the file specified by |filename|.""" | 792 """Gets contents from the file specified by |filename|.""" |
767 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, | 793 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, |
768 log_result=log_result) | 794 log_result=log_result) |
769 | 795 |
770 def SetFileContents(self, filename, contents): | 796 def SetFileContents(self, filename, contents): |
771 """Writes |contents| to the file specified by |filename|.""" | 797 """Writes |contents| to the file specified by |filename|.""" |
772 with tempfile.NamedTemporaryFile() as f: | 798 with tempfile.NamedTemporaryFile() as f: |
773 f.write(contents) | 799 f.write(contents) |
774 f.flush() | 800 f.flush() |
(...skipping 492 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1267 host_dir = os.path.dirname(host_file) | 1293 host_dir = os.path.dirname(host_file) |
1268 if not os.path.exists(host_dir): | 1294 if not os.path.exists(host_dir): |
1269 os.makedirs(host_dir) | 1295 os.makedirs(host_dir) |
1270 device_file = '%s/screenshot.png' % self.GetExternalStorage() | 1296 device_file = '%s/screenshot.png' % self.GetExternalStorage() |
1271 self.RunShellCommand('/system/bin/screencap -p %s' % device_file) | 1297 self.RunShellCommand('/system/bin/screencap -p %s' % device_file) |
1272 assert self._adb.Pull(device_file, host_file) | 1298 assert self._adb.Pull(device_file, host_file) |
1273 assert os.path.exists(host_file) | 1299 assert os.path.exists(host_file) |
1274 | 1300 |
1275 def SetUtilWrapper(self, util_wrapper): | 1301 def SetUtilWrapper(self, util_wrapper): |
1276 """Sets a wrapper prefix to be used when running a locally-built | 1302 """Sets a wrapper prefix to be used when running a locally-built |
1277 binary on the device (ex.: md5sum_bin). | 1303 binary on the device (ex.: timemodified_bin). |
1278 """ | 1304 """ |
1279 self._util_wrapper = util_wrapper | 1305 self._util_wrapper = util_wrapper |
1280 | 1306 |
1281 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): | 1307 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): |
1282 """Runs a single instrumentation test. | 1308 """Runs a single instrumentation test. |
1283 | 1309 |
1284 Args: | 1310 Args: |
1285 test: Test class/method. | 1311 test: Test class/method. |
1286 test_package: Package name of test apk. | 1312 test_package: Package name of test apk. |
1287 instr_args: Extra key/value to pass to am instrument. | 1313 instr_args: Extra key/value to pass to am instrument. |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1337 """ | 1363 """ |
1338 def __init__(self, output): | 1364 def __init__(self, output): |
1339 self._output = output | 1365 self._output = output |
1340 | 1366 |
1341 def write(self, data): | 1367 def write(self, data): |
1342 data = data.replace('\r\r\n', '\n') | 1368 data = data.replace('\r\r\n', '\n') |
1343 self._output.write(data) | 1369 self._output.write(data) |
1344 | 1370 |
1345 def flush(self): | 1371 def flush(self): |
1346 self._output.flush() | 1372 self._output.flush() |
OLD | NEW |