| 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 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 204 self._device = device | 209 self._device = device |
| 205 self._logcat = None | 210 self._logcat = None |
| 206 self.logcat_process = None | 211 self.logcat_process = None |
| 207 self._logcat_tmpoutfile = None | 212 self._logcat_tmpoutfile = None |
| 208 self._pushed_files = [] | 213 self._pushed_files = [] |
| 209 self._device_utc_offset = self.RunShellCommand('date +%z')[0] | 214 self._device_utc_offset = self.RunShellCommand('date +%z')[0] |
| 210 self._md5sum_path = '' | 215 self._md5sum_path = '' |
| 211 self._external_storage = '' | 216 self._external_storage = '' |
| 212 self._util_wrapper = '' | 217 self._util_wrapper = '' |
| 213 | 218 |
| 219 def _LogShell(self, cmd): |
| 220 """Logs the adb shell command.""" |
| 221 if self._device: |
| 222 device_repr = self._device[-4:] |
| 223 else: |
| 224 device_repr = '????' |
| 225 logging.info('[%s]> %s', device_repr, cmd) |
| 226 |
| 214 def Adb(self): | 227 def Adb(self): |
| 215 """Returns our AdbInterface to avoid us wrapping all its methods.""" | 228 """Returns our AdbInterface to avoid us wrapping all its methods.""" |
| 216 return self._adb | 229 return self._adb |
| 217 | 230 |
| 218 def IsOnline(self): | 231 def IsOnline(self): |
| 219 """Checks whether the device is online. | 232 """Checks whether the device is online. |
| 220 | 233 |
| 221 Returns: | 234 Returns: |
| 222 True if device is in 'device' mode, False otherwise. | 235 True if device is in 'device' mode, False otherwise. |
| 223 """ | 236 """ |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 """Uninstalls the specified package from the device. | 323 """Uninstalls the specified package from the device. |
| 311 | 324 |
| 312 Args: | 325 Args: |
| 313 package: Name of the package to remove. | 326 package: Name of the package to remove. |
| 314 | 327 |
| 315 Returns: | 328 Returns: |
| 316 A status string returned by adb uninstall | 329 A status string returned by adb uninstall |
| 317 """ | 330 """ |
| 318 uninstall_command = 'uninstall %s' % package | 331 uninstall_command = 'uninstall %s' % package |
| 319 | 332 |
| 320 logging.info('>>> $' + uninstall_command) | 333 self._LogShell(uninstall_command) |
| 321 return self._adb.SendCommand(uninstall_command, timeout_time=60) | 334 return self._adb.SendCommand(uninstall_command, timeout_time=60) |
| 322 | 335 |
| 323 def Install(self, package_file_path, reinstall=False): | 336 def Install(self, package_file_path, reinstall=False): |
| 324 """Installs the specified package to the device. | 337 """Installs the specified package to the device. |
| 325 | 338 |
| 326 Args: | 339 Args: |
| 327 package_file_path: Path to .apk file to install. | 340 package_file_path: Path to .apk file to install. |
| 328 reinstall: Reinstall an existing apk, keeping the data. | 341 reinstall: Reinstall an existing apk, keeping the data. |
| 329 | 342 |
| 330 Returns: | 343 Returns: |
| 331 A status string returned by adb install | 344 A status string returned by adb install |
| 332 """ | 345 """ |
| 333 assert os.path.isfile(package_file_path), ('<%s> is not file' % | 346 assert os.path.isfile(package_file_path), ('<%s> is not file' % |
| 334 package_file_path) | 347 package_file_path) |
| 335 | 348 |
| 336 install_cmd = ['install'] | 349 install_cmd = ['install'] |
| 337 | 350 |
| 338 if reinstall: | 351 if reinstall: |
| 339 install_cmd.append('-r') | 352 install_cmd.append('-r') |
| 340 | 353 |
| 341 install_cmd.append(package_file_path) | 354 install_cmd.append(package_file_path) |
| 342 install_cmd = ' '.join(install_cmd) | 355 install_cmd = ' '.join(install_cmd) |
| 343 | 356 |
| 344 logging.info('>>> $' + install_cmd) | 357 self._LogShell(install_cmd) |
| 345 return self._adb.SendCommand(install_cmd, | 358 return self._adb.SendCommand(install_cmd, |
| 346 timeout_time=2 * 60, | 359 timeout_time=2 * 60, |
| 347 retry_count=0) | 360 retry_count=0) |
| 348 | 361 |
| 349 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, | 362 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, |
| 350 reboots_on_failure=2): | 363 reboots_on_failure=2): |
| 351 """Installs specified package and reboots device on timeouts. | 364 """Installs specified package and reboots device on timeouts. |
| 352 | 365 |
| 353 Args: | 366 Args: |
| 354 apk_path: Path to .apk file to install. | 367 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 | 478 command: String containing the shell command to send. Must not include |
| 466 the single quotes as we use them to escape the whole command. | 479 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 | 480 timeout_time: Number of seconds to wait for command to respond before |
| 468 retrying, used by AdbInterface.SendShellCommand. | 481 retrying, used by AdbInterface.SendShellCommand. |
| 469 log_result: Boolean to indicate whether we should log the result of the | 482 log_result: Boolean to indicate whether we should log the result of the |
| 470 shell command. | 483 shell command. |
| 471 | 484 |
| 472 Returns: | 485 Returns: |
| 473 list containing the lines of output received from running the command | 486 list containing the lines of output received from running the command |
| 474 """ | 487 """ |
| 475 logging.info('>>> $' + command) | 488 self._LogShell(command) |
| 476 if "'" in command: logging.warning(command + " contains ' quotes") | 489 if "'" in command: logging.warning(command + " contains ' quotes") |
| 477 result = self._adb.SendShellCommand( | 490 result = self._adb.SendShellCommand( |
| 478 "'%s'" % command, timeout_time).splitlines() | 491 "'%s'" % command, timeout_time).splitlines() |
| 479 if ['error: device not found'] == result: | 492 if ['error: device not found'] == result: |
| 480 raise errors.DeviceUnresponsiveError('device not found') | 493 raise errors.DeviceUnresponsiveError('device not found') |
| 481 if log_result: | 494 if log_result: |
| 482 logging.info('\n>>> '.join(result)) | 495 self._LogShell('\n'.join(result)) |
| 483 return result | 496 return result |
| 484 | 497 |
| 485 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, | 498 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, |
| 486 log_result=False): | 499 log_result=False): |
| 487 """See RunShellCommand() above. | 500 """See RunShellCommand() above. |
| 488 | 501 |
| 489 Returns: | 502 Returns: |
| 490 The tuple (exit code, list of output lines). | 503 The tuple (exit code, list of output lines). |
| 491 """ | 504 """ |
| 492 lines = self.RunShellCommand( | 505 lines = self.RunShellCommand( |
| (...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 690 return | 703 return |
| 691 | 704 |
| 692 # They don't match, so remove everything first and then create it. | 705 # They don't match, so remove everything first and then create it. |
| 693 if os.path.isdir(local_path): | 706 if os.path.isdir(local_path): |
| 694 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) | 707 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) |
| 695 self.RunShellCommand('mkdir -p %s' % device_path) | 708 self.RunShellCommand('mkdir -p %s' % device_path) |
| 696 | 709 |
| 697 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 710 # 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. | 711 # 60 seconds which isn't sufficient for a lot of users of this method. |
| 699 push_command = 'push %s %s' % (local_path, device_path) | 712 push_command = 'push %s %s' % (local_path, device_path) |
| 700 logging.info('>>> $' + push_command) | 713 self._LogShell(push_command) |
| 701 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) | 714 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) |
| 702 assert _HasAdbPushSucceeded(output) | 715 assert _HasAdbPushSucceeded(output) |
| 703 | 716 |
| 704 | 717 |
| 705 def GetFileContents(self, filename, log_result=False): | 718 def GetFileContents(self, filename, log_result=False): |
| 706 """Gets contents from the file specified by |filename|.""" | 719 """Gets contents from the file specified by |filename|.""" |
| 707 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, | 720 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, |
| 708 log_result=log_result) | 721 log_result=log_result) |
| 709 | 722 |
| 710 def SetFileContents(self, filename, contents): | 723 def SetFileContents(self, filename, contents): |
| (...skipping 535 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1246 | 1259 |
| 1247 Args: | 1260 Args: |
| 1248 test: Test class/method. | 1261 test: Test class/method. |
| 1249 test_package: Name of the test jar. | 1262 test_package: Name of the test jar. |
| 1250 timeout: Timeout time in seconds. | 1263 timeout: Timeout time in seconds. |
| 1251 | 1264 |
| 1252 Returns: | 1265 Returns: |
| 1253 An instance of am_instrument_parser.TestResult object. | 1266 An instance of am_instrument_parser.TestResult object. |
| 1254 """ | 1267 """ |
| 1255 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) | 1268 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) |
| 1256 logging.info('>>> $' + cmd) | 1269 self._LogShell(cmd) |
| 1257 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) | 1270 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) |
| 1258 # uiautomator doesn't fully conform to the instrumenation test runner | 1271 # uiautomator doesn't fully conform to the instrumenation test runner |
| 1259 # convention and doesn't terminate with INSTRUMENTATION_CODE. | 1272 # convention and doesn't terminate with INSTRUMENTATION_CODE. |
| 1260 # Just assume the first result is valid. | 1273 # Just assume the first result is valid. |
| 1261 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) | 1274 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) |
| 1262 return test_results[0] | 1275 return test_results[0] |
| 1263 | 1276 |
| 1264 | 1277 |
| 1265 class NewLineNormalizer(object): | 1278 class NewLineNormalizer(object): |
| 1266 """A file-like object to normalize EOLs to '\n'. | 1279 """A file-like object to normalize EOLs to '\n'. |
| 1267 | 1280 |
| 1268 Pexpect runs adb within a pseudo-tty device (see | 1281 Pexpect runs adb within a pseudo-tty device (see |
| 1269 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written | 1282 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 | 1283 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 | 1284 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. | 1285 filter replaces the above with a single '\n' in the data stream. |
| 1273 """ | 1286 """ |
| 1274 def __init__(self, output): | 1287 def __init__(self, output): |
| 1275 self._output = output | 1288 self._output = output |
| 1276 | 1289 |
| 1277 def write(self, data): | 1290 def write(self, data): |
| 1278 data = data.replace('\r\r\n', '\n') | 1291 data = data.replace('\r\r\n', '\n') |
| 1279 self._output.write(data) | 1292 self._output.write(data) |
| 1280 | 1293 |
| 1281 def flush(self): | 1294 def flush(self): |
| 1282 self._output.flush() | 1295 self._output.flush() |
| OLD | NEW |