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