android_commands.py revision 868fa2fe829687343ffae624259930155e16dbd8
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 3# found in the LICENSE file. 4 5"""Provides an interface to communicate with the device via the adb command. 6 7Assumes adb binary is currently on system path. 8""" 9 10import collections 11import datetime 12import logging 13import os 14import re 15import shlex 16import subprocess 17import sys 18import tempfile 19import time 20 21import cmd_helper 22import constants 23import io_stats_parser 24try: 25 import pexpect 26except: 27 pexpect = None 28 29sys.path.append(os.path.join( 30 constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) 31import adb_interface 32import am_instrument_parser 33import errors 34 35 36# Pattern to search for the next whole line of pexpect output and capture it 37# into a match group. We can't use ^ and $ for line start end with pexpect, 38# see http://www.noah.org/python/pexpect/#doc for explanation why. 39PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') 40 41# Set the adb shell prompt to be a unique marker that will [hopefully] not 42# appear at the start of any line of a command's output. 43SHELL_PROMPT = '~+~PQ\x17RS~+~' 44 45# Java properties file 46LOCAL_PROPERTIES_PATH = '/data/local.prop' 47 48# Property in /data/local.prop that controls Java assertions. 49JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' 50 51MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$') 52NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*' 53 '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$') 54 55# Keycode "enum" suitable for passing to AndroidCommands.SendKey(). 56KEYCODE_HOME = 3 57KEYCODE_BACK = 4 58KEYCODE_DPAD_UP = 19 59KEYCODE_DPAD_DOWN = 20 60KEYCODE_DPAD_RIGHT = 22 61KEYCODE_ENTER = 66 62KEYCODE_MENU = 82 63 64MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/' 65MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin' 66MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER 67 68def GetEmulators(): 69 """Returns a list of emulators. Does not filter by status (e.g. offline). 70 71 Both devices starting with 'emulator' will be returned in below output: 72 73 * daemon not running. starting it now on port 5037 * 74 * daemon started successfully * 75 List of devices attached 76 027c10494100b4d7 device 77 emulator-5554 offline 78 emulator-5558 device 79 """ 80 re_device = re.compile('^emulator-[0-9]+', re.MULTILINE) 81 devices = re_device.findall(cmd_helper.GetCmdOutput([constants.ADB_PATH, 82 'devices'])) 83 return devices 84 85 86def GetAVDs(): 87 """Returns a list of AVDs.""" 88 re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE) 89 avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd'])) 90 return avds 91 92 93def GetAttachedDevices(): 94 """Returns a list of attached, online android devices. 95 96 If a preferred device has been set with ANDROID_SERIAL, it will be first in 97 the returned list. 98 99 Example output: 100 101 * daemon not running. starting it now on port 5037 * 102 * daemon started successfully * 103 List of devices attached 104 027c10494100b4d7 device 105 emulator-5554 offline 106 """ 107 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) 108 devices = re_device.findall(cmd_helper.GetCmdOutput([constants.ADB_PATH, 109 'devices'])) 110 preferred_device = os.environ.get('ANDROID_SERIAL') 111 if preferred_device in devices: 112 devices.remove(preferred_device) 113 devices.insert(0, preferred_device) 114 return devices 115 116 117def IsDeviceAttached(device): 118 return device in GetAttachedDevices() 119 120 121def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): 122 """Gets a list of files from `ls` command output. 123 124 Python's os.walk isn't used because it doesn't work over adb shell. 125 126 Args: 127 path: The path to list. 128 ls_output: A list of lines returned by an `ls -lR` command. 129 re_file: A compiled regular expression which parses a line into named groups 130 consisting of at minimum "filename", "date", "time", "size" and 131 optionally "timezone". 132 utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a 133 2-digit string giving the number of UTC offset hours, and MM is a 134 2-digit string giving the number of UTC offset minutes. If the input 135 utc_offset is None, will try to look for the value of "timezone" if it 136 is specified in re_file. 137 138 Returns: 139 A dict of {"name": (size, lastmod), ...} where: 140 name: The file name relative to |path|'s directory. 141 size: The file size in bytes (0 for directories). 142 lastmod: The file last modification date in UTC. 143 """ 144 re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path)) 145 path_dir = os.path.dirname(path) 146 147 current_dir = '' 148 files = {} 149 for line in ls_output: 150 directory_match = re_directory.match(line) 151 if directory_match: 152 current_dir = directory_match.group('dir') 153 continue 154 file_match = re_file.match(line) 155 if file_match: 156 filename = os.path.join(current_dir, file_match.group('filename')) 157 if filename.startswith(path_dir): 158 filename = filename[len(path_dir) + 1:] 159 lastmod = datetime.datetime.strptime( 160 file_match.group('date') + ' ' + file_match.group('time')[:5], 161 '%Y-%m-%d %H:%M') 162 if not utc_offset and 'timezone' in re_file.groupindex: 163 utc_offset = file_match.group('timezone') 164 if isinstance(utc_offset, str) and len(utc_offset) == 5: 165 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), 166 minutes=int(utc_offset[3:5])) 167 if utc_offset[0:1] == '-': 168 utc_delta = -utc_delta 169 lastmod -= utc_delta 170 files[filename] = (int(file_match.group('size')), lastmod) 171 return files 172 173 174def _ComputeFileListHash(md5sum_output): 175 """Returns a list of MD5 strings from the provided md5sum output.""" 176 return [line.split(' ')[0] for line in md5sum_output] 177 178 179def _HasAdbPushSucceeded(command_output): 180 """Returns whether adb push has succeeded from the provided output.""" 181 if not command_output: 182 return False 183 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" 184 # Errors look like this: "failed to copy ... " 185 if not re.search('^[0-9]', command_output.splitlines()[-1]): 186 logging.critical('PUSH FAILED: ' + command_output) 187 return False 188 return True 189 190 191def GetLogTimestamp(log_line, year): 192 """Returns the timestamp of the given |log_line| in the given year.""" 193 try: 194 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), 195 '%Y-%m-%d %H:%M:%S.%f') 196 except (ValueError, IndexError): 197 logging.critical('Error reading timestamp from ' + log_line) 198 return None 199 200 201class AndroidCommands(object): 202 """Helper class for communicating with Android device via adb. 203 204 Args: 205 device: If given, adb commands are only send to the device of this ID. 206 Otherwise commands are sent to all attached devices. 207 """ 208 209 def __init__(self, device=None): 210 adb_dir = os.path.dirname(constants.ADB_PATH) 211 if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep): 212 # Required by third_party/android_testrunner to call directly 'adb'. 213 os.environ['PATH'] += os.pathsep + adb_dir 214 self._adb = adb_interface.AdbInterface() 215 if device: 216 self._adb.SetTargetSerial(device) 217 self._device = device 218 self._logcat = None 219 self.logcat_process = None 220 self._logcat_tmpoutfile = None 221 self._pushed_files = [] 222 self._device_utc_offset = self.RunShellCommand('date +%z')[0] 223 self._md5sum_build_dir = '' 224 self._external_storage = '' 225 self._util_wrapper = '' 226 227 def _LogShell(self, cmd): 228 """Logs the adb shell command.""" 229 if self._device: 230 device_repr = self._device[-4:] 231 else: 232 device_repr = '????' 233 logging.info('[%s]> %s', device_repr, cmd) 234 235 def Adb(self): 236 """Returns our AdbInterface to avoid us wrapping all its methods.""" 237 return self._adb 238 239 def IsOnline(self): 240 """Checks whether the device is online. 241 242 Returns: 243 True if device is in 'device' mode, False otherwise. 244 """ 245 out = self._adb.SendCommand('get-state') 246 return out.strip() == 'device' 247 248 def IsRootEnabled(self): 249 """Checks if root is enabled on the device.""" 250 root_test_output = self.RunShellCommand('ls /root') or [''] 251 return not 'Permission denied' in root_test_output[0] 252 253 def EnableAdbRoot(self): 254 """Enables adb root on the device. 255 256 Returns: 257 True: if output from executing adb root was as expected. 258 False: otherwise. 259 """ 260 if self.GetBuildType() == 'user': 261 logging.warning("Can't enable root in production builds with type user") 262 return False 263 else: 264 return_value = self._adb.EnableAdbRoot() 265 # EnableAdbRoot inserts a call for wait-for-device only when adb logcat 266 # output matches what is expected. Just to be safe add a call to 267 # wait-for-device. 268 self._adb.SendCommand('wait-for-device') 269 return return_value 270 271 def GetDeviceYear(self): 272 """Returns the year information of the date on device.""" 273 return self.RunShellCommand('date +%Y')[0] 274 275 def GetExternalStorage(self): 276 if not self._external_storage: 277 self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0] 278 assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE' 279 return self._external_storage 280 281 def WaitForDevicePm(self): 282 """Blocks until the device's package manager is available. 283 284 To workaround http://b/5201039, we restart the shell and retry if the 285 package manager isn't back after 120 seconds. 286 287 Raises: 288 errors.WaitForResponseTimedOutError after max retries reached. 289 """ 290 last_err = None 291 retries = 3 292 while retries: 293 try: 294 self._adb.WaitForDevicePm() 295 return # Success 296 except errors.WaitForResponseTimedOutError as e: 297 last_err = e 298 logging.warning('Restarting and retrying after timeout: %s', e) 299 retries -= 1 300 self.RestartShell() 301 raise last_err # Only reached after max retries, re-raise the last error. 302 303 def RestartShell(self): 304 """Restarts the shell on the device. Does not block for it to return.""" 305 self.RunShellCommand('stop') 306 self.RunShellCommand('start') 307 308 def Reboot(self, full_reboot=True): 309 """Reboots the device and waits for the package manager to return. 310 311 Args: 312 full_reboot: Whether to fully reboot the device or just restart the shell. 313 """ 314 # TODO(torne): hive can't reboot the device either way without breaking the 315 # connection; work out if we can handle this better 316 if os.environ.get('USING_HIVE'): 317 logging.warning('Ignoring reboot request as we are on hive') 318 return 319 if full_reboot or not self.IsRootEnabled(): 320 self._adb.SendCommand('reboot') 321 timeout = 300 322 else: 323 self.RestartShell() 324 timeout = 120 325 # To run tests we need at least the package manager and the sd card (or 326 # other external storage) to be ready. 327 self.WaitForDevicePm() 328 self.WaitForSdCardReady(timeout) 329 330 def Uninstall(self, package): 331 """Uninstalls the specified package from the device. 332 333 Args: 334 package: Name of the package to remove. 335 336 Returns: 337 A status string returned by adb uninstall 338 """ 339 uninstall_command = 'uninstall %s' % package 340 341 self._LogShell(uninstall_command) 342 return self._adb.SendCommand(uninstall_command, timeout_time=60) 343 344 def Install(self, package_file_path, reinstall=False): 345 """Installs the specified package to the device. 346 347 Args: 348 package_file_path: Path to .apk file to install. 349 reinstall: Reinstall an existing apk, keeping the data. 350 351 Returns: 352 A status string returned by adb install 353 """ 354 assert os.path.isfile(package_file_path), ('<%s> is not file' % 355 package_file_path) 356 357 install_cmd = ['install'] 358 359 if reinstall: 360 install_cmd.append('-r') 361 362 install_cmd.append(package_file_path) 363 install_cmd = ' '.join(install_cmd) 364 365 self._LogShell(install_cmd) 366 return self._adb.SendCommand(install_cmd, 367 timeout_time=2 * 60, 368 retry_count=0) 369 370 def ManagedInstall(self, apk_path, keep_data=False, package_name=None, 371 reboots_on_failure=2): 372 """Installs specified package and reboots device on timeouts. 373 374 Args: 375 apk_path: Path to .apk file to install. 376 keep_data: Reinstalls instead of uninstalling first, preserving the 377 application data. 378 package_name: Package name (only needed if keep_data=False). 379 reboots_on_failure: number of time to reboot if package manager is frozen. 380 381 Returns: 382 A status string returned by adb install 383 """ 384 reboots_left = reboots_on_failure 385 while True: 386 try: 387 if not keep_data: 388 assert package_name 389 self.Uninstall(package_name) 390 install_status = self.Install(apk_path, reinstall=keep_data) 391 if 'Success' in install_status: 392 return install_status 393 except errors.WaitForResponseTimedOutError: 394 print '@@@STEP_WARNINGS@@@' 395 logging.info('Timeout on installing %s on device %s', apk_path, 396 self._device) 397 398 if reboots_left <= 0: 399 raise Exception('Install failure') 400 401 # Force a hard reboot on last attempt 402 self.Reboot(full_reboot=(reboots_left == 1)) 403 reboots_left -= 1 404 405 def MakeSystemFolderWritable(self): 406 """Remounts the /system folder rw.""" 407 out = self._adb.SendCommand('remount') 408 if out.strip() != 'remount succeeded': 409 raise errors.MsgException('Remount failed: %s' % out) 410 411 def RestartAdbServer(self): 412 """Restart the adb server.""" 413 self.KillAdbServer() 414 self.StartAdbServer() 415 416 def KillAdbServer(self): 417 """Kill adb server.""" 418 adb_cmd = [constants.ADB_PATH, 'kill-server'] 419 return cmd_helper.RunCmd(adb_cmd) 420 421 def StartAdbServer(self): 422 """Start adb server.""" 423 adb_cmd = [constants.ADB_PATH, 'start-server'] 424 return cmd_helper.RunCmd(adb_cmd) 425 426 def WaitForSystemBootCompleted(self, wait_time): 427 """Waits for targeted system's boot_completed flag to be set. 428 429 Args: 430 wait_time: time in seconds to wait 431 432 Raises: 433 WaitForResponseTimedOutError if wait_time elapses and flag still not 434 set. 435 """ 436 logging.info('Waiting for system boot completed...') 437 self._adb.SendCommand('wait-for-device') 438 # Now the device is there, but system not boot completed. 439 # Query the sys.boot_completed flag with a basic command 440 boot_completed = False 441 attempts = 0 442 wait_period = 5 443 while not boot_completed and (attempts * wait_period) < wait_time: 444 output = self._adb.SendShellCommand('getprop sys.boot_completed', 445 retry_count=1) 446 output = output.strip() 447 if output == '1': 448 boot_completed = True 449 else: 450 # If 'error: xxx' returned when querying the flag, it means 451 # adb server lost the connection to the emulator, so restart the adb 452 # server. 453 if 'error:' in output: 454 self.RestartAdbServer() 455 time.sleep(wait_period) 456 attempts += 1 457 if not boot_completed: 458 raise errors.WaitForResponseTimedOutError( 459 'sys.boot_completed flag was not set after %s seconds' % wait_time) 460 461 def WaitForSdCardReady(self, timeout_time): 462 """Wait for the SD card ready before pushing data into it.""" 463 logging.info('Waiting for SD card ready...') 464 sdcard_ready = False 465 attempts = 0 466 wait_period = 5 467 external_storage = self.GetExternalStorage() 468 while not sdcard_ready and attempts * wait_period < timeout_time: 469 output = self.RunShellCommand('ls ' + external_storage) 470 if output: 471 sdcard_ready = True 472 else: 473 time.sleep(wait_period) 474 attempts += 1 475 if not sdcard_ready: 476 raise errors.WaitForResponseTimedOutError( 477 'SD card not ready after %s seconds' % timeout_time) 478 479 # It is tempting to turn this function into a generator, however this is not 480 # possible without using a private (local) adb_shell instance (to ensure no 481 # other command interleaves usage of it), which would defeat the main aim of 482 # being able to reuse the adb shell instance across commands. 483 def RunShellCommand(self, command, timeout_time=20, log_result=False): 484 """Send a command to the adb shell and return the result. 485 486 Args: 487 command: String containing the shell command to send. Must not include 488 the single quotes as we use them to escape the whole command. 489 timeout_time: Number of seconds to wait for command to respond before 490 retrying, used by AdbInterface.SendShellCommand. 491 log_result: Boolean to indicate whether we should log the result of the 492 shell command. 493 494 Returns: 495 list containing the lines of output received from running the command 496 """ 497 self._LogShell(command) 498 if "'" in command: logging.warning(command + " contains ' quotes") 499 result = self._adb.SendShellCommand( 500 "'%s'" % command, timeout_time).splitlines() 501 if ['error: device not found'] == result: 502 raise errors.DeviceUnresponsiveError('device not found') 503 if log_result: 504 self._LogShell('\n'.join(result)) 505 return result 506 507 def GetShellCommandStatusAndOutput(self, command, timeout_time=20, 508 log_result=False): 509 """See RunShellCommand() above. 510 511 Returns: 512 The tuple (exit code, list of output lines). 513 """ 514 lines = self.RunShellCommand( 515 command + '; echo %$?', timeout_time, log_result) 516 last_line = lines[-1] 517 status_pos = last_line.rfind('%') 518 assert status_pos >= 0 519 status = int(last_line[status_pos + 1:]) 520 if status_pos == 0: 521 lines = lines[:-1] 522 else: 523 lines = lines[:-1] + [last_line[:status_pos]] 524 return (status, lines) 525 526 def KillAll(self, process): 527 """Android version of killall, connected via adb. 528 529 Args: 530 process: name of the process to kill off 531 532 Returns: 533 the number of processes killed 534 """ 535 pids = self.ExtractPid(process) 536 if pids: 537 self.RunShellCommand('kill -9 ' + ' '.join(pids)) 538 return len(pids) 539 540 def KillAllBlocking(self, process, timeout_sec): 541 """Blocking version of killall, connected via adb. 542 543 This waits until no process matching the corresponding name appears in ps' 544 output anymore. 545 546 Args: 547 process: name of the process to kill off 548 timeout_sec: the timeout in seconds 549 550 Returns: 551 the number of processes killed 552 """ 553 processes_killed = self.KillAll(process) 554 if processes_killed: 555 elapsed = 0 556 wait_period = 0.1 557 # Note that this doesn't take into account the time spent in ExtractPid(). 558 while self.ExtractPid(process) and elapsed < timeout_sec: 559 time.sleep(wait_period) 560 elapsed += wait_period 561 if elapsed >= timeout_sec: 562 return 0 563 return processes_killed 564 565 def _GetActivityCommand(self, package, activity, wait_for_completion, action, 566 category, data, extras, trace_file_name, force_stop): 567 """Creates command to start |package|'s activity on the device. 568 569 Args - as for StartActivity 570 571 Returns: 572 the command to run on the target to start the activity 573 """ 574 cmd = 'am start -a %s' % action 575 if force_stop: 576 cmd += ' -S' 577 if wait_for_completion: 578 cmd += ' -W' 579 if category: 580 cmd += ' -c %s' % category 581 if package and activity: 582 cmd += ' -n %s/%s' % (package, activity) 583 if data: 584 cmd += ' -d "%s"' % data 585 if extras: 586 for key in extras: 587 value = extras[key] 588 if isinstance(value, str): 589 cmd += ' --es' 590 elif isinstance(value, bool): 591 cmd += ' --ez' 592 elif isinstance(value, int): 593 cmd += ' --ei' 594 else: 595 raise NotImplementedError( 596 'Need to teach StartActivity how to pass %s extras' % type(value)) 597 cmd += ' %s %s' % (key, value) 598 if trace_file_name: 599 cmd += ' --start-profiler ' + trace_file_name 600 return cmd 601 602 def StartActivity(self, package, activity, wait_for_completion=False, 603 action='android.intent.action.VIEW', 604 category=None, data=None, 605 extras=None, trace_file_name=None, 606 force_stop=False): 607 """Starts |package|'s activity on the device. 608 609 Args: 610 package: Name of package to start (e.g. 'com.google.android.apps.chrome'). 611 activity: Name of activity (e.g. '.Main' or 612 'com.google.android.apps.chrome.Main'). 613 wait_for_completion: wait for the activity to finish launching (-W flag). 614 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. 615 category: string (e.g. "android.intent.category.HOME") 616 data: Data string to pass to activity (e.g. 'http://www.example.com/'). 617 extras: Dict of extras to pass to activity. Values are significant. 618 trace_file_name: If used, turns on and saves the trace to this file name. 619 force_stop: force stop the target app before starting the activity (-S 620 flag). 621 """ 622 cmd = self._GetActivityCommand(package, activity, wait_for_completion, 623 action, category, data, extras, 624 trace_file_name, force_stop) 625 self.RunShellCommand(cmd) 626 627 def StartActivityTimed(self, package, activity, wait_for_completion=False, 628 action='android.intent.action.VIEW', 629 category=None, data=None, 630 extras=None, trace_file_name=None, 631 force_stop=False): 632 """Starts |package|'s activity on the device, returning the start time 633 634 Args - as for StartActivity 635 636 Returns: 637 a timestamp string for the time at which the activity started 638 """ 639 cmd = self._GetActivityCommand(package, activity, wait_for_completion, 640 action, category, data, extras, 641 trace_file_name, force_stop) 642 self.StartMonitoringLogcat() 643 self.RunShellCommand('log starting activity; ' + cmd) 644 activity_started_re = re.compile('.*starting activity.*') 645 m = self.WaitForLogMatch(activity_started_re, None) 646 assert m 647 start_line = m.group(0) 648 return GetLogTimestamp(start_line, self.GetDeviceYear()) 649 650 def GoHome(self): 651 """Tell the device to return to the home screen. Blocks until completion.""" 652 self.RunShellCommand('am start -W ' 653 '-a android.intent.action.MAIN -c android.intent.category.HOME') 654 655 def CloseApplication(self, package): 656 """Attempt to close down the application, using increasing violence. 657 658 Args: 659 package: Name of the process to kill off, e.g. 660 com.google.android.apps.chrome 661 """ 662 self.RunShellCommand('am force-stop ' + package) 663 664 def GetApplicationPath(self, package): 665 """Get the installed apk path on the device for the given package. 666 667 Args: 668 package: Name of the package. 669 670 Returns: 671 Path to the apk on the device if it exists, None otherwise. 672 """ 673 pm_path_output = self.RunShellCommand('pm path ' + package) 674 # The path output contains anything if and only if the package 675 # exists. 676 if pm_path_output: 677 # pm_path_output is of the form: "package:/path/to/foo.apk" 678 return pm_path_output[0].split(':')[1] 679 else: 680 return None 681 682 def ClearApplicationState(self, package): 683 """Closes and clears all state for the given |package|.""" 684 # Check that the package exists before clearing it. Necessary because 685 # calling pm clear on a package that doesn't exist may never return. 686 pm_path_output = self.RunShellCommand('pm path ' + package) 687 # The path output only contains anything if and only if the package exists. 688 if pm_path_output: 689 self.RunShellCommand('pm clear ' + package) 690 691 def SendKeyEvent(self, keycode): 692 """Sends keycode to the device. 693 694 Args: 695 keycode: Numeric keycode to send (see "enum" at top of file). 696 """ 697 self.RunShellCommand('input keyevent %d' % keycode) 698 699 def CheckMd5Sum(self, local_path, device_path, ignore_paths=False): 700 """Compares the md5sum of a local path against a device path. 701 702 Args: 703 local_path: Path (file or directory) on the host. 704 device_path: Path on the device. 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 Returns: 709 True if the md5sums match. 710 """ 711 assert os.path.exists(local_path), 'Local path not found %s' % local_path 712 713 if not self._md5sum_build_dir: 714 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') 715 build_dir = '%s/%s/' % ( 716 cmd_helper.OutDirectory().get(), default_build_type) 717 md5sum_dist_path = '%s/md5sum_dist' % build_dir 718 if not os.path.exists(md5sum_dist_path): 719 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() 720 md5sum_dist_path = '%s/md5sum_dist' % build_dir 721 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' 722 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) 723 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) 724 self._md5sum_build_dir = build_dir 725 726 self._pushed_files.append(device_path) 727 hashes_on_device = _ComputeFileListHash( 728 self.RunShellCommand(MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + 729 ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path)) 730 assert os.path.exists(local_path), 'Local path not found %s' % local_path 731 md5sum_output = cmd_helper.GetCmdOutput( 732 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) 733 hashes_on_host = _ComputeFileListHash(md5sum_output.splitlines()) 734 735 if ignore_paths: 736 hashes_on_device = [h.split()[0] for h in hashes_on_device] 737 hashes_on_host = [h.split()[0] for h in hashes_on_host] 738 739 return hashes_on_device == hashes_on_host 740 741 def PushIfNeeded(self, local_path, device_path): 742 """Pushes |local_path| to |device_path|. 743 744 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. 746 747 All pushed files can be removed by calling RemovePushedFiles(). 748 """ 749 if self.CheckMd5Sum(local_path, device_path): 750 return 751 752 # They don't match, so remove everything first and then create it. 753 if os.path.isdir(local_path): 754 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) 755 self.RunShellCommand('mkdir -p %s' % device_path) 756 757 # 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. 759 push_command = 'push %s %s' % (local_path, device_path) 760 self._LogShell(push_command) 761 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) 762 assert _HasAdbPushSucceeded(output) 763 764 765 def GetFileContents(self, filename, log_result=False): 766 """Gets contents from the file specified by |filename|.""" 767 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename, 768 log_result=log_result) 769 770 def SetFileContents(self, filename, contents): 771 """Writes |contents| to the file specified by |filename|.""" 772 with tempfile.NamedTemporaryFile() as f: 773 f.write(contents) 774 f.flush() 775 self._adb.Push(f.name, filename) 776 777 _TEMP_FILE_BASE_FMT = 'temp_file_%d' 778 _TEMP_SCRIPT_FILE_BASE_FMT = 'temp_script_file_%d.sh' 779 780 def _GetDeviceTempFileName(self, base_name): 781 i = 0 782 while self.FileExistsOnDevice( 783 self.GetExternalStorage() + '/' + base_name % i): 784 i += 1 785 return self.GetExternalStorage() + '/' + base_name % i 786 787 def CanAccessProtectedFileContents(self): 788 """Returns True if Get/SetProtectedFileContents would work via "su". 789 790 Devices running user builds don't have adb root, but may provide "su" which 791 can be used for accessing protected files. 792 """ 793 r = self.RunShellCommand('su -c cat /dev/null') 794 return r == [] or r[0].strip() == '' 795 796 def GetProtectedFileContents(self, filename, log_result=False): 797 """Gets contents from the protected file specified by |filename|. 798 799 This is less efficient than GetFileContents, but will work for protected 800 files and device files. 801 """ 802 # Run the script as root 803 return self.RunShellCommand('su -c cat "%s" 2> /dev/null' % filename) 804 805 def SetProtectedFileContents(self, filename, contents): 806 """Writes |contents| to the protected file specified by |filename|. 807 808 This is less efficient than SetFileContents, but will work for protected 809 files and device files. 810 """ 811 temp_file = self._GetDeviceTempFileName(AndroidCommands._TEMP_FILE_BASE_FMT) 812 temp_script = self._GetDeviceTempFileName( 813 AndroidCommands._TEMP_SCRIPT_FILE_BASE_FMT) 814 815 # Put the contents in a temporary file 816 self.SetFileContents(temp_file, contents) 817 # Create a script to copy the file contents to its final destination 818 self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename)) 819 # Run the script as root 820 self.RunShellCommand('su -c sh %s' % temp_script) 821 # And remove the temporary files 822 self.RunShellCommand('rm ' + temp_file) 823 self.RunShellCommand('rm ' + temp_script) 824 825 def RemovePushedFiles(self): 826 """Removes all files pushed with PushIfNeeded() from the device.""" 827 for p in self._pushed_files: 828 self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60) 829 830 def ListPathContents(self, path): 831 """Lists files in all subdirectories of |path|. 832 833 Args: 834 path: The path to list. 835 836 Returns: 837 A dict of {"name": (size, lastmod), ...}. 838 """ 839 # Example output: 840 # /foo/bar: 841 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt 842 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' 843 '(?P<user>[^\s]+)\s+' 844 '(?P<group>[^\s]+)\s+' 845 '(?P<size>[^\s]+)\s+' 846 '(?P<date>[^\s]+)\s+' 847 '(?P<time>[^\s]+)\s+' 848 '(?P<filename>[^\s]+)$') 849 return _GetFilesFromRecursiveLsOutput( 850 path, self.RunShellCommand('ls -lR %s' % path), re_file, 851 self._device_utc_offset) 852 853 def SetJavaAssertsEnabled(self, enable): 854 """Sets or removes the device java assertions property. 855 856 Args: 857 enable: If True the property will be set. 858 859 Returns: 860 True if the file was modified (reboot is required for it to take effect). 861 """ 862 # First ensure the desired property is persisted. 863 temp_props_file = tempfile.NamedTemporaryFile() 864 properties = '' 865 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name): 866 properties = file(temp_props_file.name).read() 867 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + 868 r'\s*=\s*all\s*$', re.MULTILINE) 869 if enable != bool(re.search(re_search, properties)): 870 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) + 871 r'\s*=\s*\w+\s*$', re.MULTILINE) 872 properties = re.sub(re_replace, '', properties) 873 if enable: 874 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY 875 876 file(temp_props_file.name, 'w').write(properties) 877 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH) 878 879 # Next, check the current runtime value is what we need, and 880 # if not, set it and report that a reboot is required. 881 was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY) 882 if was_set == enable: 883 return False 884 885 self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY, 886 enable and 'all' or '')) 887 return True 888 889 def GetBuildId(self): 890 """Returns the build ID of the system (e.g. JRM79C).""" 891 build_id = self.RunShellCommand('getprop ro.build.id')[0] 892 assert build_id 893 return build_id 894 895 def GetBuildType(self): 896 """Returns the build type of the system (e.g. eng).""" 897 build_type = self.RunShellCommand('getprop ro.build.type')[0] 898 assert build_type 899 return build_type 900 901 def GetProductModel(self): 902 """Returns the namve of the product model (e.g. "Galaxy Nexus") """ 903 model = self.RunShellCommand('getprop ro.product.model')[0] 904 assert model 905 return model 906 907 def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None): 908 """Starts monitoring the output of logcat, for use with WaitForLogMatch. 909 910 Args: 911 clear: If True the existing logcat output will be cleared, to avoiding 912 matching historical output lurking in the log. 913 filters: A list of logcat filters to be used. 914 """ 915 if clear: 916 self.RunShellCommand('logcat -c') 917 args = [] 918 if self._adb._target_arg: 919 args += shlex.split(self._adb._target_arg) 920 args += ['logcat', '-v', 'threadtime'] 921 if filters: 922 args.extend(filters) 923 else: 924 args.append('*:v') 925 926 if logfile: 927 logfile = NewLineNormalizer(logfile) 928 929 # Spawn logcat and syncronize with it. 930 for _ in range(4): 931 self._logcat = pexpect.spawn(constants.ADB_PATH, args, timeout=10, 932 logfile=logfile) 933 self.RunShellCommand('log startup_sync') 934 if self._logcat.expect(['startup_sync', pexpect.EOF, 935 pexpect.TIMEOUT]) == 0: 936 break 937 self._logcat.close(force=True) 938 else: 939 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) 940 sys.exit(1) 941 942 def GetMonitoredLogCat(self): 943 """Returns an "adb logcat" command as created by pexpected.spawn.""" 944 if not self._logcat: 945 self.StartMonitoringLogcat(clear=False) 946 return self._logcat 947 948 def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10): 949 """Blocks until a matching line is logged or a timeout occurs. 950 951 Args: 952 success_re: A compiled re to search each line for. 953 error_re: A compiled re which, if found, terminates the search for 954 |success_re|. If None is given, no error condition will be detected. 955 clear: If True the existing logcat output will be cleared, defaults to 956 false. 957 timeout: Timeout in seconds to wait for a log match. 958 959 Raises: 960 pexpect.TIMEOUT after |timeout| seconds without a match for |success_re| 961 or |error_re|. 962 963 Returns: 964 The re match object if |success_re| is matched first or None if |error_re| 965 is matched first. 966 """ 967 logging.info('<<< Waiting for logcat:' + str(success_re.pattern)) 968 t0 = time.time() 969 while True: 970 if not self._logcat: 971 self.StartMonitoringLogcat(clear) 972 try: 973 while True: 974 # Note this will block for upto the timeout _per log line_, so we need 975 # to calculate the overall timeout remaining since t0. 976 time_remaining = t0 + timeout - time.time() 977 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) 978 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) 979 line = self._logcat.match.group(1) 980 if error_re: 981 error_match = error_re.search(line) 982 if error_match: 983 return None 984 success_match = success_re.search(line) 985 if success_match: 986 return success_match 987 logging.info('<<< Skipped Logcat Line:' + str(line)) 988 except pexpect.TIMEOUT: 989 raise pexpect.TIMEOUT( 990 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' 991 'to debug)' % 992 (timeout, success_re.pattern)) 993 except pexpect.EOF: 994 # It seems that sometimes logcat can end unexpectedly. This seems 995 # to happen during Chrome startup after a reboot followed by a cache 996 # clean. I don't understand why this happens, but this code deals with 997 # getting EOF in logcat. 998 logging.critical('Found EOF in adb logcat. Restarting...') 999 # Rerun spawn with original arguments. Note that self._logcat.args[0] is 1000 # the path of adb, so we don't want it in the arguments. 1001 self._logcat = pexpect.spawn(constants.ADB_PATH, 1002 self._logcat.args[1:], 1003 timeout=self._logcat.timeout, 1004 logfile=self._logcat.logfile) 1005 1006 def StartRecordingLogcat(self, clear=True, filters=['*:v']): 1007 """Starts recording logcat output to eventually be saved as a string. 1008 1009 This call should come before some series of tests are run, with either 1010 StopRecordingLogcat or SearchLogcatRecord following the tests. 1011 1012 Args: 1013 clear: True if existing log output should be cleared. 1014 filters: A list of logcat filters to be used. 1015 """ 1016 if clear: 1017 self._adb.SendCommand('logcat -c') 1018 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg, 1019 ' '.join(filters)) 1020 self._logcat_tmpoutfile = tempfile.TemporaryFile(bufsize=0) 1021 self.logcat_process = subprocess.Popen(logcat_command, shell=True, 1022 stdout=self._logcat_tmpoutfile) 1023 1024 def StopRecordingLogcat(self): 1025 """Stops an existing logcat recording subprocess and returns output. 1026 1027 Returns: 1028 The logcat output as a string or an empty string if logcat was not 1029 being recorded at the time. 1030 """ 1031 if not self.logcat_process: 1032 return '' 1033 # Cannot evaluate directly as 0 is a possible value. 1034 # Better to read the self.logcat_process.stdout before killing it, 1035 # Otherwise the communicate may return incomplete output due to pipe break. 1036 if self.logcat_process.poll() is None: 1037 self.logcat_process.kill() 1038 self.logcat_process.wait() 1039 self.logcat_process = None 1040 self._logcat_tmpoutfile.seek(0) 1041 output = self._logcat_tmpoutfile.read() 1042 self._logcat_tmpoutfile.close() 1043 return output 1044 1045 def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None, 1046 log_level=None, component=None): 1047 """Searches the specified logcat output and returns results. 1048 1049 This method searches through the logcat output specified by record for a 1050 certain message, narrowing results by matching them against any other 1051 specified criteria. It returns all matching lines as described below. 1052 1053 Args: 1054 record: A string generated by Start/StopRecordingLogcat to search. 1055 message: An output string to search for. 1056 thread_id: The thread id that is the origin of the message. 1057 proc_id: The process that is the origin of the message. 1058 log_level: The log level of the message. 1059 component: The name of the component that would create the message. 1060 1061 Returns: 1062 A list of dictionaries represeting matching entries, each containing keys 1063 thread_id, proc_id, log_level, component, and message. 1064 """ 1065 if thread_id: 1066 thread_id = str(thread_id) 1067 if proc_id: 1068 proc_id = str(proc_id) 1069 results = [] 1070 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$', 1071 re.MULTILINE) 1072 log_list = reg.findall(record) 1073 for (tid, pid, log_lev, comp, msg) in log_list: 1074 if ((not thread_id or thread_id == tid) and 1075 (not proc_id or proc_id == pid) and 1076 (not log_level or log_level == log_lev) and 1077 (not component or component == comp) and msg.find(message) > -1): 1078 match = dict({'thread_id': tid, 'proc_id': pid, 1079 'log_level': log_lev, 'component': comp, 1080 'message': msg}) 1081 results.append(match) 1082 return results 1083 1084 def ExtractPid(self, process_name): 1085 """Extracts Process Ids for a given process name from Android Shell. 1086 1087 Args: 1088 process_name: name of the process on the device. 1089 1090 Returns: 1091 List of all the process ids (as strings) that match the given name. 1092 If the name of a process exactly matches the given name, the pid of 1093 that process will be inserted to the front of the pid list. 1094 """ 1095 pids = [] 1096 for line in self.RunShellCommand('ps', log_result=False): 1097 data = line.split() 1098 try: 1099 if process_name in data[-1]: # name is in the last column 1100 if process_name == data[-1]: 1101 pids.insert(0, data[1]) # PID is in the second column 1102 else: 1103 pids.append(data[1]) 1104 except IndexError: 1105 pass 1106 return pids 1107 1108 def GetIoStats(self): 1109 """Gets cumulative disk IO stats since boot (for all processes). 1110 1111 Returns: 1112 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there 1113 was an error. 1114 """ 1115 for line in self.GetFileContents('/proc/diskstats', log_result=False): 1116 stats = io_stats_parser.ParseIoStatsLine(line) 1117 if stats.device == 'mmcblk0': 1118 return { 1119 'num_reads': stats.num_reads_issued, 1120 'num_writes': stats.num_writes_completed, 1121 'read_ms': stats.ms_spent_reading, 1122 'write_ms': stats.ms_spent_writing, 1123 } 1124 logging.warning('Could not find disk IO stats.') 1125 return None 1126 1127 def GetMemoryUsageForPid(self, pid): 1128 """Returns the memory usage for given pid. 1129 1130 Args: 1131 pid: The pid number of the specific process running on device. 1132 1133 Returns: 1134 A tuple containg: 1135 [0]: Dict of {metric:usage_kb}, for the process which has specified pid. 1136 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, 1137 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, 1138 KernelPageSize, MMUPageSize, Nvidia (tablet only). 1139 [1]: Detailed /proc/[PID]/smaps information. 1140 """ 1141 usage_dict = collections.defaultdict(int) 1142 smaps = collections.defaultdict(dict) 1143 current_smap = '' 1144 for line in self.GetProtectedFileContents('/proc/%s/smaps' % pid, 1145 log_result=False): 1146 items = line.split() 1147 # See man 5 proc for more details. The format is: 1148 # address perms offset dev inode pathname 1149 if len(items) > 5: 1150 current_smap = ' '.join(items[5:]) 1151 elif len(items) > 3: 1152 current_smap = ' '.join(items[3:]) 1153 match = re.match(MEMORY_INFO_RE, line) 1154 if match: 1155 key = match.group('key') 1156 usage_kb = int(match.group('usage_kb')) 1157 usage_dict[key] += usage_kb 1158 if key not in smaps[current_smap]: 1159 smaps[current_smap][key] = 0 1160 smaps[current_smap][key] += usage_kb 1161 if not usage_dict or not any(usage_dict.values()): 1162 # Presumably the process died between ps and calling this method. 1163 logging.warning('Could not find memory usage for pid ' + str(pid)) 1164 1165 for line in self.GetProtectedFileContents('/d/nvmap/generic-0/clients', 1166 log_result=False): 1167 match = re.match(NVIDIA_MEMORY_INFO_RE, line) 1168 if match and match.group('pid') == pid: 1169 usage_bytes = int(match.group('usage_bytes')) 1170 usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB 1171 break 1172 1173 return (usage_dict, smaps) 1174 1175 def GetMemoryUsageForPackage(self, package): 1176 """Returns the memory usage for all processes whose name contains |pacakge|. 1177 1178 Args: 1179 package: A string holding process name to lookup pid list for. 1180 1181 Returns: 1182 A tuple containg: 1183 [0]: Dict of {metric:usage_kb}, summed over all pids associated with 1184 |name|. 1185 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, 1186 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, 1187 KernelPageSize, MMUPageSize, Nvidia (tablet only). 1188 [1]: a list with detailed /proc/[PID]/smaps information. 1189 """ 1190 usage_dict = collections.defaultdict(int) 1191 pid_list = self.ExtractPid(package) 1192 smaps = collections.defaultdict(dict) 1193 1194 for pid in pid_list: 1195 usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid) 1196 smaps[pid] = smaps_per_pid 1197 for (key, value) in usage_dict_per_pid.items(): 1198 usage_dict[key] += value 1199 1200 return usage_dict, smaps 1201 1202 def ProcessesUsingDevicePort(self, device_port): 1203 """Lists processes using the specified device port on loopback interface. 1204 1205 Args: 1206 device_port: Port on device we want to check. 1207 1208 Returns: 1209 A list of (pid, process_name) tuples using the specified port. 1210 """ 1211 tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False) 1212 tcp_address = '0100007F:%04X' % device_port 1213 pids = [] 1214 for single_connect in tcp_results: 1215 connect_results = single_connect.split() 1216 # Column 1 is the TCP port, and Column 9 is the inode of the socket 1217 if connect_results[1] == tcp_address: 1218 socket_inode = connect_results[9] 1219 socket_name = 'socket:[%s]' % socket_inode 1220 lsof_results = self.RunShellCommand('lsof', log_result=False) 1221 for single_process in lsof_results: 1222 process_results = single_process.split() 1223 # Ignore the line if it has less than nine columns in it, which may 1224 # be the case when a process stops while lsof is executing. 1225 if len(process_results) <= 8: 1226 continue 1227 # Column 0 is the executable name 1228 # Column 1 is the pid 1229 # Column 8 is the Inode in use 1230 if process_results[8] == socket_name: 1231 pids.append((int(process_results[1]), process_results[0])) 1232 break 1233 logging.info('PidsUsingDevicePort: %s', pids) 1234 return pids 1235 1236 def FileExistsOnDevice(self, file_name): 1237 """Checks whether the given file exists on the device. 1238 1239 Args: 1240 file_name: Full path of file to check. 1241 1242 Returns: 1243 True if the file exists, False otherwise. 1244 """ 1245 assert '"' not in file_name, 'file_name cannot contain double quotes' 1246 try: 1247 status = self._adb.SendShellCommand( 1248 '\'test -e "%s"; echo $?\'' % (file_name)) 1249 if 'test: not found' not in status: 1250 return int(status) == 0 1251 1252 status = self._adb.SendShellCommand( 1253 '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name)) 1254 return int(status) == 0 1255 except ValueError: 1256 if IsDeviceAttached(self._device): 1257 raise errors.DeviceUnresponsiveError('Device may be offline.') 1258 1259 return False 1260 1261 def TakeScreenshot(self, host_file): 1262 """Saves a screenshot image to |host_file| on the host. 1263 1264 Args: 1265 host_file: Absolute path to the image file to store on the host. 1266 """ 1267 host_dir = os.path.dirname(host_file) 1268 if not os.path.exists(host_dir): 1269 os.makedirs(host_dir) 1270 device_file = '%s/screenshot.png' % self.GetExternalStorage() 1271 self.RunShellCommand('/system/bin/screencap -p %s' % device_file) 1272 assert self._adb.Pull(device_file, host_file) 1273 assert os.path.exists(host_file) 1274 1275 def SetUtilWrapper(self, util_wrapper): 1276 """Sets a wrapper prefix to be used when running a locally-built 1277 binary on the device (ex.: md5sum_bin). 1278 """ 1279 self._util_wrapper = util_wrapper 1280 1281 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): 1282 """Runs a single instrumentation test. 1283 1284 Args: 1285 test: Test class/method. 1286 test_package: Package name of test apk. 1287 instr_args: Extra key/value to pass to am instrument. 1288 timeout: Timeout time in seconds. 1289 1290 Returns: 1291 An instance of am_instrument_parser.TestResult object. 1292 """ 1293 instrumentation_path = ('%s/android.test.InstrumentationTestRunner' % 1294 test_package) 1295 args_with_filter = dict(instr_args) 1296 args_with_filter['class'] = test 1297 logging.info(args_with_filter) 1298 (raw_results, _) = self._adb.StartInstrumentation( 1299 instrumentation_path=instrumentation_path, 1300 instrumentation_args=args_with_filter, 1301 timeout_time=timeout) 1302 assert len(raw_results) == 1 1303 return raw_results[0] 1304 1305 def RunUIAutomatorTest(self, test, test_package, timeout): 1306 """Runs a single uiautomator test. 1307 1308 Args: 1309 test: Test class/method. 1310 test_package: Name of the test jar. 1311 timeout: Timeout time in seconds. 1312 1313 Returns: 1314 An instance of am_instrument_parser.TestResult object. 1315 """ 1316 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test) 1317 self._LogShell(cmd) 1318 output = self._adb.SendShellCommand(cmd, timeout_time=timeout) 1319 # uiautomator doesn't fully conform to the instrumenation test runner 1320 # convention and doesn't terminate with INSTRUMENTATION_CODE. 1321 # Just assume the first result is valid. 1322 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output) 1323 if not test_results: 1324 raise errors.InstrumentationError( 1325 'no test results... device setup correctly?') 1326 return test_results[0] 1327 1328 1329class NewLineNormalizer(object): 1330 """A file-like object to normalize EOLs to '\n'. 1331 1332 Pexpect runs adb within a pseudo-tty device (see 1333 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written 1334 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate 1335 lines, the log ends up having '\r\r\n' at the end of each line. This 1336 filter replaces the above with a single '\n' in the data stream. 1337 """ 1338 def __init__(self, output): 1339 self._output = output 1340 1341 def write(self, data): 1342 data = data.replace('\r\r\n', '\n') 1343 self._output.write(data) 1344 1345 def flush(self): 1346 self._output.flush() 1347