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 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
102 emulator-5554 offline | 102 emulator-5554 offline |
103 """ | 103 """ |
104 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) | 104 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) |
105 devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) | 105 devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) |
106 preferred_device = os.environ.get('ANDROID_SERIAL') | 106 preferred_device = os.environ.get('ANDROID_SERIAL') |
107 if preferred_device in devices: | 107 if preferred_device in devices: |
108 devices.remove(preferred_device) | 108 devices.remove(preferred_device) |
109 devices.insert(0, preferred_device) | 109 devices.insert(0, preferred_device) |
110 return devices | 110 return devices |
111 | 111 |
112 | |
112 def IsDeviceAttached(device): | 113 def IsDeviceAttached(device): |
113 return device in GetAttachedDevices() | 114 return device in GetAttachedDevices() |
114 | 115 |
116 | |
115 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): | 117 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): |
116 """Gets a list of files from `ls` command output. | 118 """Gets a list of files from `ls` command output. |
117 | 119 |
118 Python's os.walk isn't used because it doesn't work over adb shell. | 120 Python's os.walk isn't used because it doesn't work over adb shell. |
119 | 121 |
120 Args: | 122 Args: |
121 path: The path to list. | 123 path: The path to list. |
122 ls_output: A list of lines returned by an `ls -lR` command. | 124 ls_output: A list of lines returned by an `ls -lR` command. |
123 re_file: A compiled regular expression which parses a line into named groups | 125 re_file: A compiled regular expression which parses a line into named groups |
124 consisting of at minimum "filename", "date", "time", "size" and | 126 consisting of at minimum "filename", "date", "time", "size" and |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
157 utc_offset = file_match.group('timezone') | 159 utc_offset = file_match.group('timezone') |
158 if isinstance(utc_offset, str) and len(utc_offset) == 5: | 160 if isinstance(utc_offset, str) and len(utc_offset) == 5: |
159 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), | 161 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), |
160 minutes=int(utc_offset[3:5])) | 162 minutes=int(utc_offset[3:5])) |
161 if utc_offset[0:1] == '-': | 163 if utc_offset[0:1] == '-': |
162 utc_delta = -utc_delta | 164 utc_delta = -utc_delta |
163 lastmod -= utc_delta | 165 lastmod -= utc_delta |
164 files[filename] = (int(file_match.group('size')), lastmod) | 166 files[filename] = (int(file_match.group('size')), lastmod) |
165 return files | 167 return files |
166 | 168 |
169 | |
167 def _ComputeFileListHash(md5sum_output): | 170 def _ComputeFileListHash(md5sum_output): |
168 """Returns a list of MD5 strings from the provided md5sum output.""" | 171 """Returns a list of MD5 strings from the provided md5sum output.""" |
169 return [line.split(' ')[0] for line in md5sum_output] | 172 return [line.split(' ')[0] for line in md5sum_output] |
170 | 173 |
174 | |
171 def _HasAdbPushSucceeded(command_output): | 175 def _HasAdbPushSucceeded(command_output): |
172 """Returns whether adb push has succeeded from the provided output.""" | 176 """Returns whether adb push has succeeded from the provided output.""" |
173 if not command_output: | 177 if not command_output: |
174 return False | 178 return False |
175 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" | 179 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
176 # Errors look like this: "failed to copy ... " | 180 # Errors look like this: "failed to copy ... " |
177 if not re.search('^[0-9]', command_output.splitlines()[-1]): | 181 if not re.search('^[0-9]', command_output.splitlines()[-1]): |
178 logging.critical('PUSH FAILED: ' + command_output) | 182 logging.critical('PUSH FAILED: ' + command_output) |
179 return False | 183 return False |
180 return True | 184 return True |
181 | 185 |
186 | |
182 def GetLogTimestamp(log_line, year): | 187 def GetLogTimestamp(log_line, year): |
183 """Returns the timestamp of the given |log_line| in the given year.""" | 188 """Returns the timestamp of the given |log_line| in the given year.""" |
184 try: | 189 try: |
185 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), | 190 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), |
186 '%Y-%m-%d %H:%M:%S.%f') | 191 '%Y-%m-%d %H:%M:%S.%f') |
187 except (ValueError, IndexError): | 192 except (ValueError, IndexError): |
188 logging.critical('Error reading timestamp from ' + log_line) | 193 logging.critical('Error reading timestamp from ' + log_line) |
189 return None | 194 return None |
190 | 195 |
191 | 196 |
197 def _LogShell(cmd): | |
198 """Logs the adb shell command.""" | |
199 logging.info('[device]> %s', cmd) | |
Isaac (away)
2013/04/10 19:12:53
It'd be great to include device serial number here
craigdh
2013/04/10 20:39:49
The logging format was just recently changed to in
frankf
2013/04/10 21:29:42
I added this anyways in case we do something on on
| |
200 | |
201 | |
192 class AndroidCommands(object): | 202 class AndroidCommands(object): |
193 """Helper class for communicating with Android device via adb. | 203 """Helper class for communicating with Android device via adb. |
194 | 204 |
195 Args: | 205 Args: |
196 device: If given, adb commands are only send to the device of this ID. | 206 device: If given, adb commands are only send to the device of this ID. |
197 Otherwise commands are sent to all attached devices. | 207 Otherwise commands are sent to all attached devices. |
198 """ | 208 """ |
199 | 209 |
200 def __init__(self, device=None): | 210 def __init__(self, device=None): |
201 self._adb = adb_interface.AdbInterface() | 211 self._adb = adb_interface.AdbInterface() |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
310 """Uninstalls the specified package from the device. | 320 """Uninstalls the specified package from the device. |
311 | 321 |
312 Args: | 322 Args: |
313 package: Name of the package to remove. | 323 package: Name of the package to remove. |
314 | 324 |
315 Returns: | 325 Returns: |
316 A status string returned by adb uninstall | 326 A status string returned by adb uninstall |
317 """ | 327 """ |
318 uninstall_command = 'uninstall %s' % package | 328 uninstall_command = 'uninstall %s' % package |
319 | 329 |
320 logging.info('>>> $' + uninstall_command) | 330 _LogShell(uninstall_command) |
321 return self._adb.SendCommand(uninstall_command, timeout_time=60) | 331 return self._adb.SendCommand(uninstall_command, timeout_time=60) |
322 | 332 |
323 def Install(self, package_file_path, reinstall=False): | 333 def Install(self, package_file_path, reinstall=False): |
324 """Installs the specified package to the device. | 334 """Installs the specified package to the device. |
325 | 335 |
326 Args: | 336 Args: |
327 package_file_path: Path to .apk file to install. | 337 package_file_path: Path to .apk file to install. |
328 reinstall: Reinstall an existing apk, keeping the data. | 338 reinstall: Reinstall an existing apk, keeping the data. |
329 | 339 |
330 Returns: | 340 Returns: |
331 A status string returned by adb install | 341 A status string returned by adb install |
332 """ | 342 """ |
333 assert os.path.isfile(package_file_path), ('<%s> is not file' % | 343 assert os.path.isfile(package_file_path), ('<%s> is not file' % |
334 package_file_path) | 344 package_file_path) |
335 | 345 |
336 install_cmd = ['install'] | 346 install_cmd = ['install'] |
337 | 347 |
338 if reinstall: | 348 if reinstall: |
339 install_cmd.append('-r') | 349 install_cmd.append('-r') |
340 | 350 |
341 install_cmd.append(package_file_path) | 351 install_cmd.append(package_file_path) |
342 install_cmd = ' '.join(install_cmd) | 352 install_cmd = ' '.join(install_cmd) |
343 | 353 |
344 logging.info('>>> $' + install_cmd) | 354 _LogShell(install_cmd) |
345 return self._adb.SendCommand(install_cmd, | 355 return self._adb.SendCommand(install_cmd, |
346 timeout_time=2 * 60, | 356 timeout_time=2 * 60, |
347 retry_count=0) | 357 retry_count=0) |
348 | 358 |
349 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, | 359 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, |
350 reboots_on_failure=2): | 360 reboots_on_failure=2): |
351 """Installs specified package and reboots device on timeouts. | 361 """Installs specified package and reboots device on timeouts. |
352 | 362 |
353 Args: | 363 Args: |
354 apk_path: Path to .apk file to install. | 364 apk_path: Path to .apk file to install. |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
465 command: String containing the shell command to send. Must not include | 475 command: String containing the shell command to send. Must not include |
466 the single quotes as we use them to escape the whole command. | 476 the single quotes as we use them to escape the whole command. |
467 timeout_time: Number of seconds to wait for command to respond before | 477 timeout_time: Number of seconds to wait for command to respond before |
468 retrying, used by AdbInterface.SendShellCommand. | 478 retrying, used by AdbInterface.SendShellCommand. |
469 log_result: Boolean to indicate whether we should log the result of the | 479 log_result: Boolean to indicate whether we should log the result of the |
470 shell command. | 480 shell command. |
471 | 481 |
472 Returns: | 482 Returns: |
473 list containing the lines of output received from running the command | 483 list containing the lines of output received from running the command |
474 """ | 484 """ |
475 logging.info('>>> $' + command) | 485 _LogShell(command) |
476 if "'" in command: logging.warning(command + " contains ' quotes") | 486 if "'" in command: logging.warning(command + " contains ' quotes") |
477 result = self._adb.SendShellCommand( | 487 result = self._adb.SendShellCommand( |
478 "'%s'" % command, timeout_time).splitlines() | 488 "'%s'" % command, timeout_time).splitlines() |
479 if ['error: device not found'] == result: | 489 if ['error: device not found'] == result: |
480 raise errors.DeviceUnresponsiveError('device not found') | 490 raise errors.DeviceUnresponsiveError('device not found') |
481 if log_result: | 491 if log_result: |
482 logging.info('\n>>> '.join(result)) | 492 _LogShell('\n'.join(result)) |
483 return result | 493 return result |
484 | 494 |
485 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, | 495 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, |
486 log_result=False): | 496 log_result=False): |
487 """See RunShellCommand() above. | 497 """See RunShellCommand() above. |
488 | 498 |
489 Returns: | 499 Returns: |
490 The tuple (exit code, list of output lines). | 500 The tuple (exit code, list of output lines). |
491 """ | 501 """ |
492 lines = self.RunShellCommand( | 502 lines = self.RunShellCommand( |
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
690 return | 700 return |
691 | 701 |
692 # They don't match, so remove everything first and then create it. | 702 # They don't match, so remove everything first and then create it. |
693 if os.path.isdir(local_path): | 703 if os.path.isdir(local_path): |
694 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) | 704 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) |
695 self.RunShellCommand('mkdir -p %s' % device_path) | 705 self.RunShellCommand('mkdir -p %s' % device_path) |
696 | 706 |
697 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 707 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of |
698 # 60 seconds which isn't sufficient for a lot of users of this method. | 708 # 60 seconds which isn't sufficient for a lot of users of this method. |
699 push_command = 'push %s %s' % (local_path, device_path) | 709 push_command = 'push %s %s' % (local_path, device_path) |
700 logging.info('>>> $' + push_command) | 710 _LogShell(push_command) |
701 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) | 711 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) |
702 assert _HasAdbPushSucceeded(output) | 712 assert _HasAdbPushSucceeded(output) |
703 | 713 |
704 | 714 |
705 def GetFileContents(self, filename, log_result=False): | 715 def GetFileContents(self, filename, log_result=False): |
706 """Gets contents from the file specified by |filename|.""" | 716 """Gets contents from the file specified by |filename|.""" |
707 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, | 717 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, |
708 log_result=log_result) | 718 log_result=log_result) |
709 | 719 |
710 def SetFileContents(self, filename, contents): | 720 def SetFileContents(self, filename, contents): |
(...skipping 535 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1246 | 1256 |
1247 Args: | 1257 Args: |
1248 test: Test class/method. | 1258 test: Test class/method. |
1249 test_package: Name of the test jar. | 1259 test_package: Name of the test jar. |
1250 timeout: Timeout time in seconds. | 1260 timeout: Timeout time in seconds. |
1251 | 1261 |
1252 Returns: | 1262 Returns: |
1253 An instance of am_instrument_parser.TestResult object. | 1263 An instance of am_instrument_parser.TestResult object. |
1254 """ | 1264 """ |
1255 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) | 1265 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) |
1256 logging.info('>>> $' + cmd) | 1266 _LogShell(cmd) |
1257 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) | 1267 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) |
1258 # uiautomator doesn't fully conform to the instrumenation test runner | 1268 # uiautomator doesn't fully conform to the instrumenation test runner |
1259 # convention and doesn't terminate with INSTRUMENTATION_CODE. | 1269 # convention and doesn't terminate with INSTRUMENTATION_CODE. |
1260 # Just assume the first result is valid. | 1270 # Just assume the first result is valid. |
1261 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) | 1271 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) |
1262 return test_results[0] | 1272 return test_results[0] |
1263 | 1273 |
1264 | 1274 |
1265 class NewLineNormalizer(object): | 1275 class NewLineNormalizer(object): |
1266 """A file-like object to normalize EOLs to '\n'. | 1276 """A file-like object to normalize EOLs to '\n'. |
1267 | 1277 |
1268 Pexpect runs adb within a pseudo-tty device (see | 1278 Pexpect runs adb within a pseudo-tty device (see |
1269 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written | 1279 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written |
1270 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate | 1280 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate |
1271 lines, the log ends up having '\r\r\n' at the end of each line. This | 1281 lines, the log ends up having '\r\r\n' at the end of each line. This |
1272 filter replaces the above with a single '\n' in the data stream. | 1282 filter replaces the above with a single '\n' in the data stream. |
1273 """ | 1283 """ |
1274 def __init__(self, output): | 1284 def __init__(self, output): |
1275 self._output = output | 1285 self._output = output |
1276 | 1286 |
1277 def write(self, data): | 1287 def write(self, data): |
1278 data = data.replace('\r\r\n', '\n') | 1288 data = data.replace('\r\r\n', '\n') |
1279 self._output.write(data) | 1289 self._output.write(data) |
1280 | 1290 |
1281 def flush(self): | 1291 def flush(self): |
1282 self._output.flush() | 1292 self._output.flush() |
OLD | NEW |