michael@0: # Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: """Provides an interface to communicate with the device via the adb command. michael@0: michael@0: Assumes adb binary is currently on system path. michael@0: """ michael@0: michael@0: import collections michael@0: import datetime michael@0: import logging michael@0: import os michael@0: import re michael@0: import shlex michael@0: import subprocess michael@0: import sys michael@0: import tempfile michael@0: import time michael@0: michael@0: import io_stats_parser michael@0: from pylib import pexpect michael@0: michael@0: CHROME_SRC = os.path.join( michael@0: os.path.abspath(os.path.dirname(__file__)), '..', '..', '..') michael@0: michael@0: sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner')) michael@0: import adb_interface michael@0: michael@0: import cmd_helper michael@0: import errors # is under ../../../third_party/android_testrunner/errors.py michael@0: michael@0: michael@0: # Pattern to search for the next whole line of pexpect output and capture it michael@0: # into a match group. We can't use ^ and $ for line start end with pexpect, michael@0: # see http://www.noah.org/python/pexpect/#doc for explanation why. michael@0: PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') michael@0: michael@0: # Set the adb shell prompt to be a unique marker that will [hopefully] not michael@0: # appear at the start of any line of a command's output. michael@0: SHELL_PROMPT = '~+~PQ\x17RS~+~' michael@0: michael@0: # Java properties file michael@0: LOCAL_PROPERTIES_PATH = '/data/local.prop' michael@0: michael@0: # Property in /data/local.prop that controls Java assertions. michael@0: JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' michael@0: michael@0: MEMORY_INFO_RE = re.compile('^(?P\w+):\s+(?P\d+) kB$') michael@0: NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P\S+)\s*(?P\S+)\s*' michael@0: '(?P\d+)\s*(?P\d+)$') michael@0: michael@0: # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). michael@0: KEYCODE_HOME = 3 michael@0: KEYCODE_BACK = 4 michael@0: KEYCODE_DPAD_UP = 19 michael@0: KEYCODE_DPAD_DOWN = 20 michael@0: KEYCODE_DPAD_RIGHT = 22 michael@0: KEYCODE_ENTER = 66 michael@0: KEYCODE_MENU = 82 michael@0: michael@0: MD5SUM_DEVICE_PATH = '/data/local/tmp/md5sum_bin' michael@0: michael@0: def GetEmulators(): michael@0: """Returns a list of emulators. Does not filter by status (e.g. offline). michael@0: michael@0: Both devices starting with 'emulator' will be returned in below output: michael@0: michael@0: * daemon not running. starting it now on port 5037 * michael@0: * daemon started successfully * michael@0: List of devices attached michael@0: 027c10494100b4d7 device michael@0: emulator-5554 offline michael@0: emulator-5558 device michael@0: """ michael@0: re_device = re.compile('^emulator-[0-9]+', re.MULTILINE) michael@0: devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) michael@0: return devices michael@0: michael@0: michael@0: def GetAVDs(): michael@0: """Returns a list of AVDs.""" michael@0: re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE) michael@0: avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd'])) michael@0: return avds michael@0: michael@0: michael@0: def GetAttachedDevices(): michael@0: """Returns a list of attached, online android devices. michael@0: michael@0: If a preferred device has been set with ANDROID_SERIAL, it will be first in michael@0: the returned list. michael@0: michael@0: Example output: michael@0: michael@0: * daemon not running. starting it now on port 5037 * michael@0: * daemon started successfully * michael@0: List of devices attached michael@0: 027c10494100b4d7 device michael@0: emulator-5554 offline michael@0: """ michael@0: re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) michael@0: devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) michael@0: preferred_device = os.environ.get('ANDROID_SERIAL') michael@0: if preferred_device in devices: michael@0: devices.remove(preferred_device) michael@0: devices.insert(0, preferred_device) michael@0: return devices michael@0: michael@0: def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): michael@0: """Gets a list of files from `ls` command output. michael@0: michael@0: Python's os.walk isn't used because it doesn't work over adb shell. michael@0: michael@0: Args: michael@0: path: The path to list. michael@0: ls_output: A list of lines returned by an `ls -lR` command. michael@0: re_file: A compiled regular expression which parses a line into named groups michael@0: consisting of at minimum "filename", "date", "time", "size" and michael@0: optionally "timezone". michael@0: utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a michael@0: 2-digit string giving the number of UTC offset hours, and MM is a michael@0: 2-digit string giving the number of UTC offset minutes. If the input michael@0: utc_offset is None, will try to look for the value of "timezone" if it michael@0: is specified in re_file. michael@0: michael@0: Returns: michael@0: A dict of {"name": (size, lastmod), ...} where: michael@0: name: The file name relative to |path|'s directory. michael@0: size: The file size in bytes (0 for directories). michael@0: lastmod: The file last modification date in UTC. michael@0: """ michael@0: re_directory = re.compile('^%s/(?P[^:]+):$' % re.escape(path)) michael@0: path_dir = os.path.dirname(path) michael@0: michael@0: current_dir = '' michael@0: files = {} michael@0: for line in ls_output: michael@0: directory_match = re_directory.match(line) michael@0: if directory_match: michael@0: current_dir = directory_match.group('dir') michael@0: continue michael@0: file_match = re_file.match(line) michael@0: if file_match: michael@0: filename = os.path.join(current_dir, file_match.group('filename')) michael@0: if filename.startswith(path_dir): michael@0: filename = filename[len(path_dir)+1:] michael@0: lastmod = datetime.datetime.strptime( michael@0: file_match.group('date') + ' ' + file_match.group('time')[:5], michael@0: '%Y-%m-%d %H:%M') michael@0: if not utc_offset and 'timezone' in re_file.groupindex: michael@0: utc_offset = file_match.group('timezone') michael@0: if isinstance(utc_offset, str) and len(utc_offset) == 5: michael@0: utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), michael@0: minutes=int(utc_offset[3:5])) michael@0: if utc_offset[0:1] == '-': michael@0: utc_delta = -utc_delta michael@0: lastmod -= utc_delta michael@0: files[filename] = (int(file_match.group('size')), lastmod) michael@0: return files michael@0: michael@0: def _ComputeFileListHash(md5sum_output): michael@0: """Returns a list of MD5 strings from the provided md5sum output.""" michael@0: return [line.split(' ')[0] for line in md5sum_output] michael@0: michael@0: def _HasAdbPushSucceeded(command_output): michael@0: """Returns whether adb push has succeeded from the provided output.""" michael@0: if not command_output: michael@0: return False michael@0: # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" michael@0: # Errors look like this: "failed to copy ... " michael@0: if not re.search('^[0-9]', command_output.splitlines()[-1]): michael@0: logging.critical('PUSH FAILED: ' + command_output) michael@0: return False michael@0: return True michael@0: michael@0: def GetLogTimestamp(log_line, year): michael@0: """Returns the timestamp of the given |log_line| in the given year.""" michael@0: try: michael@0: return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), michael@0: '%Y-%m-%d %H:%M:%S.%f') michael@0: except (ValueError, IndexError): michael@0: logging.critical('Error reading timestamp from ' + log_line) michael@0: return None michael@0: michael@0: michael@0: class AndroidCommands(object): michael@0: """Helper class for communicating with Android device via adb. michael@0: michael@0: Args: michael@0: device: If given, adb commands are only send to the device of this ID. michael@0: Otherwise commands are sent to all attached devices. michael@0: """ michael@0: michael@0: def __init__(self, device=None): michael@0: self._adb = adb_interface.AdbInterface() michael@0: if device: michael@0: self._adb.SetTargetSerial(device) michael@0: self._logcat = None michael@0: self.logcat_process = None michael@0: self._pushed_files = [] michael@0: self._device_utc_offset = self.RunShellCommand('date +%z')[0] michael@0: self._md5sum_path = '' michael@0: self._external_storage = '' michael@0: michael@0: def Adb(self): michael@0: """Returns our AdbInterface to avoid us wrapping all its methods.""" michael@0: return self._adb michael@0: michael@0: def IsRootEnabled(self): michael@0: """Checks if root is enabled on the device.""" michael@0: root_test_output = self.RunShellCommand('ls /root') or [''] michael@0: return not 'Permission denied' in root_test_output[0] michael@0: michael@0: def EnableAdbRoot(self): michael@0: """Enables adb root on the device. michael@0: michael@0: Returns: michael@0: True: if output from executing adb root was as expected. michael@0: False: otherwise. michael@0: """ michael@0: return_value = self._adb.EnableAdbRoot() michael@0: # EnableAdbRoot inserts a call for wait-for-device only when adb logcat michael@0: # output matches what is expected. Just to be safe add a call to michael@0: # wait-for-device. michael@0: self._adb.SendCommand('wait-for-device') michael@0: return return_value michael@0: michael@0: def GetDeviceYear(self): michael@0: """Returns the year information of the date on device.""" michael@0: return self.RunShellCommand('date +%Y')[0] michael@0: michael@0: def GetExternalStorage(self): michael@0: if not self._external_storage: michael@0: self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0] michael@0: assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE' michael@0: return self._external_storage michael@0: michael@0: def WaitForDevicePm(self): michael@0: """Blocks until the device's package manager is available. michael@0: michael@0: To workaround http://b/5201039, we restart the shell and retry if the michael@0: package manager isn't back after 120 seconds. michael@0: michael@0: Raises: michael@0: errors.WaitForResponseTimedOutError after max retries reached. michael@0: """ michael@0: last_err = None michael@0: retries = 3 michael@0: while retries: michael@0: try: michael@0: self._adb.WaitForDevicePm() michael@0: return # Success michael@0: except errors.WaitForResponseTimedOutError as e: michael@0: last_err = e michael@0: logging.warning('Restarting and retrying after timeout: %s', e) michael@0: retries -= 1 michael@0: self.RestartShell() michael@0: raise last_err # Only reached after max retries, re-raise the last error. michael@0: michael@0: def RestartShell(self): michael@0: """Restarts the shell on the device. Does not block for it to return.""" michael@0: self.RunShellCommand('stop') michael@0: self.RunShellCommand('start') michael@0: michael@0: def Reboot(self, full_reboot=True): michael@0: """Reboots the device and waits for the package manager to return. michael@0: michael@0: Args: michael@0: full_reboot: Whether to fully reboot the device or just restart the shell. michael@0: """ michael@0: # TODO(torne): hive can't reboot the device either way without breaking the michael@0: # connection; work out if we can handle this better michael@0: if os.environ.get('USING_HIVE'): michael@0: logging.warning('Ignoring reboot request as we are on hive') michael@0: return michael@0: if full_reboot or not self.IsRootEnabled(): michael@0: self._adb.SendCommand('reboot') michael@0: timeout = 300 michael@0: else: michael@0: self.RestartShell() michael@0: timeout = 120 michael@0: # To run tests we need at least the package manager and the sd card (or michael@0: # other external storage) to be ready. michael@0: self.WaitForDevicePm() michael@0: self.WaitForSdCardReady(timeout) michael@0: michael@0: def Uninstall(self, package): michael@0: """Uninstalls the specified package from the device. michael@0: michael@0: Args: michael@0: package: Name of the package to remove. michael@0: michael@0: Returns: michael@0: A status string returned by adb uninstall michael@0: """ michael@0: uninstall_command = 'uninstall %s' % package michael@0: michael@0: logging.info('>>> $' + uninstall_command) michael@0: return self._adb.SendCommand(uninstall_command, timeout_time=60) michael@0: michael@0: def Install(self, package_file_path, reinstall=False): michael@0: """Installs the specified package to the device. michael@0: michael@0: Args: michael@0: package_file_path: Path to .apk file to install. michael@0: reinstall: Reinstall an existing apk, keeping the data. michael@0: michael@0: Returns: michael@0: A status string returned by adb install michael@0: """ michael@0: assert os.path.isfile(package_file_path), ('<%s> is not file' % michael@0: package_file_path) michael@0: michael@0: install_cmd = ['install'] michael@0: michael@0: if reinstall: michael@0: install_cmd.append('-r') michael@0: michael@0: install_cmd.append(package_file_path) michael@0: install_cmd = ' '.join(install_cmd) michael@0: michael@0: logging.info('>>> $' + install_cmd) michael@0: return self._adb.SendCommand(install_cmd, timeout_time=2*60, retry_count=0) michael@0: michael@0: def ManagedInstall(self, apk_path, keep_data=False, package_name=None, michael@0: reboots_on_failure=2): michael@0: """Installs specified package and reboots device on timeouts. michael@0: michael@0: Args: michael@0: apk_path: Path to .apk file to install. michael@0: keep_data: Reinstalls instead of uninstalling first, preserving the michael@0: application data. michael@0: package_name: Package name (only needed if keep_data=False). michael@0: reboots_on_failure: number of time to reboot if package manager is frozen. michael@0: michael@0: Returns: michael@0: A status string returned by adb install michael@0: """ michael@0: reboots_left = reboots_on_failure michael@0: while True: michael@0: try: michael@0: if not keep_data: michael@0: assert package_name michael@0: self.Uninstall(package_name) michael@0: install_status = self.Install(apk_path, reinstall=keep_data) michael@0: if 'Success' in install_status: michael@0: return install_status michael@0: except errors.WaitForResponseTimedOutError: michael@0: print '@@@STEP_WARNINGS@@@' michael@0: logging.info('Timeout on installing %s' % apk_path) michael@0: michael@0: if reboots_left <= 0: michael@0: raise Exception('Install failure') michael@0: michael@0: # Force a hard reboot on last attempt michael@0: self.Reboot(full_reboot=(reboots_left == 1)) michael@0: reboots_left -= 1 michael@0: michael@0: def MakeSystemFolderWritable(self): michael@0: """Remounts the /system folder rw.""" michael@0: out = self._adb.SendCommand('remount') michael@0: if out.strip() != 'remount succeeded': michael@0: raise errors.MsgException('Remount failed: %s' % out) michael@0: michael@0: def RestartAdbServer(self): michael@0: """Restart the adb server.""" michael@0: self.KillAdbServer() michael@0: self.StartAdbServer() michael@0: michael@0: def KillAdbServer(self): michael@0: """Kill adb server.""" michael@0: adb_cmd = ['adb', 'kill-server'] michael@0: return cmd_helper.RunCmd(adb_cmd) michael@0: michael@0: def StartAdbServer(self): michael@0: """Start adb server.""" michael@0: adb_cmd = ['adb', 'start-server'] michael@0: return cmd_helper.RunCmd(adb_cmd) michael@0: michael@0: def WaitForSystemBootCompleted(self, wait_time): michael@0: """Waits for targeted system's boot_completed flag to be set. michael@0: michael@0: Args: michael@0: wait_time: time in seconds to wait michael@0: michael@0: Raises: michael@0: WaitForResponseTimedOutError if wait_time elapses and flag still not michael@0: set. michael@0: """ michael@0: logging.info('Waiting for system boot completed...') michael@0: self._adb.SendCommand('wait-for-device') michael@0: # Now the device is there, but system not boot completed. michael@0: # Query the sys.boot_completed flag with a basic command michael@0: boot_completed = False michael@0: attempts = 0 michael@0: wait_period = 5 michael@0: while not boot_completed and (attempts * wait_period) < wait_time: michael@0: output = self._adb.SendShellCommand('getprop sys.boot_completed', michael@0: retry_count=1) michael@0: output = output.strip() michael@0: if output == '1': michael@0: boot_completed = True michael@0: else: michael@0: # If 'error: xxx' returned when querying the flag, it means michael@0: # adb server lost the connection to the emulator, so restart the adb michael@0: # server. michael@0: if 'error:' in output: michael@0: self.RestartAdbServer() michael@0: time.sleep(wait_period) michael@0: attempts += 1 michael@0: if not boot_completed: michael@0: raise errors.WaitForResponseTimedOutError( michael@0: 'sys.boot_completed flag was not set after %s seconds' % wait_time) michael@0: michael@0: def WaitForSdCardReady(self, timeout_time): michael@0: """Wait for the SD card ready before pushing data into it.""" michael@0: logging.info('Waiting for SD card ready...') michael@0: sdcard_ready = False michael@0: attempts = 0 michael@0: wait_period = 5 michael@0: external_storage = self.GetExternalStorage() michael@0: while not sdcard_ready and attempts * wait_period < timeout_time: michael@0: output = self.RunShellCommand('ls ' + external_storage) michael@0: if output: michael@0: sdcard_ready = True michael@0: else: michael@0: time.sleep(wait_period) michael@0: attempts += 1 michael@0: if not sdcard_ready: michael@0: raise errors.WaitForResponseTimedOutError( michael@0: 'SD card not ready after %s seconds' % timeout_time) michael@0: michael@0: # It is tempting to turn this function into a generator, however this is not michael@0: # possible without using a private (local) adb_shell instance (to ensure no michael@0: # other command interleaves usage of it), which would defeat the main aim of michael@0: # being able to reuse the adb shell instance across commands. michael@0: def RunShellCommand(self, command, timeout_time=20, log_result=False): michael@0: """Send a command to the adb shell and return the result. michael@0: michael@0: Args: michael@0: command: String containing the shell command to send. Must not include michael@0: the single quotes as we use them to escape the whole command. michael@0: timeout_time: Number of seconds to wait for command to respond before michael@0: retrying, used by AdbInterface.SendShellCommand. michael@0: log_result: Boolean to indicate whether we should log the result of the michael@0: shell command. michael@0: michael@0: Returns: michael@0: list containing the lines of output received from running the command michael@0: """ michael@0: logging.info('>>> $' + command) michael@0: if "'" in command: logging.warning(command + " contains ' quotes") michael@0: result = self._adb.SendShellCommand( michael@0: "'%s'" % command, timeout_time).splitlines() michael@0: if ['error: device not found'] == result: michael@0: raise errors.DeviceUnresponsiveError('device not found') michael@0: if log_result: michael@0: logging.info('\n>>> '.join(result)) michael@0: return result michael@0: michael@0: def KillAll(self, process): michael@0: """Android version of killall, connected via adb. michael@0: michael@0: Args: michael@0: process: name of the process to kill off michael@0: michael@0: Returns: michael@0: the number of processes killed michael@0: """ michael@0: pids = self.ExtractPid(process) michael@0: if pids: michael@0: self.RunShellCommand('kill ' + ' '.join(pids)) michael@0: return len(pids) michael@0: michael@0: def KillAllBlocking(self, process, timeout_sec): michael@0: """Blocking version of killall, connected via adb. michael@0: michael@0: This waits until no process matching the corresponding name appears in ps' michael@0: output anymore. michael@0: michael@0: Args: michael@0: process: name of the process to kill off michael@0: timeout_sec: the timeout in seconds michael@0: michael@0: Returns: michael@0: the number of processes killed michael@0: """ michael@0: processes_killed = self.KillAll(process) michael@0: if processes_killed: michael@0: elapsed = 0 michael@0: wait_period = 0.1 michael@0: # Note that this doesn't take into account the time spent in ExtractPid(). michael@0: while self.ExtractPid(process) and elapsed < timeout_sec: michael@0: time.sleep(wait_period) michael@0: elapsed += wait_period michael@0: if elapsed >= timeout_sec: michael@0: return 0 michael@0: return processes_killed michael@0: michael@0: def StartActivity(self, package, activity, wait_for_completion=False, michael@0: action='android.intent.action.VIEW', michael@0: category=None, data=None, michael@0: extras=None, trace_file_name=None): michael@0: """Starts |package|'s activity on the device. michael@0: michael@0: Args: michael@0: package: Name of package to start (e.g. 'com.google.android.apps.chrome'). michael@0: activity: Name of activity (e.g. '.Main' or michael@0: 'com.google.android.apps.chrome.Main'). michael@0: wait_for_completion: wait for the activity to finish launching (-W flag). michael@0: action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. michael@0: category: string (e.g. "android.intent.category.HOME") michael@0: data: Data string to pass to activity (e.g. 'http://www.example.com/'). michael@0: extras: Dict of extras to pass to activity. Values are significant. michael@0: trace_file_name: If used, turns on and saves the trace to this file name. michael@0: """ michael@0: cmd = 'am start -a %s' % action michael@0: if wait_for_completion: michael@0: cmd += ' -W' michael@0: if category: michael@0: cmd += ' -c %s' % category michael@0: if package and activity: michael@0: cmd += ' -n %s/%s' % (package, activity) michael@0: if data: michael@0: cmd += ' -d "%s"' % data michael@0: if extras: michael@0: for key in extras: michael@0: value = extras[key] michael@0: if isinstance(value, str): michael@0: cmd += ' --es' michael@0: elif isinstance(value, bool): michael@0: cmd += ' --ez' michael@0: elif isinstance(value, int): michael@0: cmd += ' --ei' michael@0: else: michael@0: raise NotImplementedError( michael@0: 'Need to teach StartActivity how to pass %s extras' % type(value)) michael@0: cmd += ' %s %s' % (key, value) michael@0: if trace_file_name: michael@0: cmd += ' --start-profiler ' + trace_file_name michael@0: self.RunShellCommand(cmd) michael@0: michael@0: def GoHome(self): michael@0: """Tell the device to return to the home screen. Blocks until completion.""" michael@0: self.RunShellCommand('am start -W ' michael@0: '-a android.intent.action.MAIN -c android.intent.category.HOME') michael@0: michael@0: def CloseApplication(self, package): michael@0: """Attempt to close down the application, using increasing violence. michael@0: michael@0: Args: michael@0: package: Name of the process to kill off, e.g. michael@0: com.google.android.apps.chrome michael@0: """ michael@0: self.RunShellCommand('am force-stop ' + package) michael@0: michael@0: def ClearApplicationState(self, package): michael@0: """Closes and clears all state for the given |package|.""" michael@0: self.CloseApplication(package) michael@0: self.RunShellCommand('rm -r /data/data/%s/app_*' % package) michael@0: self.RunShellCommand('rm -r /data/data/%s/cache/*' % package) michael@0: self.RunShellCommand('rm -r /data/data/%s/files/*' % package) michael@0: self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package) michael@0: michael@0: def SendKeyEvent(self, keycode): michael@0: """Sends keycode to the device. michael@0: michael@0: Args: michael@0: keycode: Numeric keycode to send (see "enum" at top of file). michael@0: """ michael@0: self.RunShellCommand('input keyevent %d' % keycode) michael@0: michael@0: def PushIfNeeded(self, local_path, device_path): michael@0: """Pushes |local_path| to |device_path|. michael@0: michael@0: Works for files and directories. This method skips copying any paths in michael@0: |test_data_paths| that already exist on the device with the same hash. michael@0: michael@0: All pushed files can be removed by calling RemovePushedFiles(). michael@0: """ michael@0: assert os.path.exists(local_path), 'Local path not found %s' % local_path michael@0: michael@0: if not self._md5sum_path: michael@0: default_build_type = os.environ.get('BUILD_TYPE', 'Debug') michael@0: md5sum_path = '%s/out/%s/md5sum_bin' % (CHROME_SRC, default_build_type) michael@0: if not os.path.exists(md5sum_path): michael@0: md5sum_path = '%s/out/Release/md5sum_bin' % (CHROME_SRC) michael@0: if not os.path.exists(md5sum_path): michael@0: print >> sys.stderr, 'Please build md5sum.' michael@0: sys.exit(1) michael@0: command = 'push %s %s' % (md5sum_path, MD5SUM_DEVICE_PATH) michael@0: assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) michael@0: self._md5sum_path = md5sum_path michael@0: michael@0: self._pushed_files.append(device_path) michael@0: hashes_on_device = _ComputeFileListHash( michael@0: self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path)) michael@0: assert os.path.exists(local_path), 'Local path not found %s' % local_path michael@0: hashes_on_host = _ComputeFileListHash( michael@0: subprocess.Popen( michael@0: '%s_host %s' % (self._md5sum_path, local_path), michael@0: stdout=subprocess.PIPE, shell=True).stdout) michael@0: if hashes_on_device == hashes_on_host: michael@0: return michael@0: michael@0: # They don't match, so remove everything first and then create it. michael@0: if os.path.isdir(local_path): michael@0: self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) michael@0: self.RunShellCommand('mkdir -p %s' % device_path) michael@0: michael@0: # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of michael@0: # 60 seconds which isn't sufficient for a lot of users of this method. michael@0: push_command = 'push %s %s' % (local_path, device_path) michael@0: logging.info('>>> $' + push_command) michael@0: output = self._adb.SendCommand(push_command, timeout_time=30*60) michael@0: assert _HasAdbPushSucceeded(output) michael@0: michael@0: michael@0: def GetFileContents(self, filename, log_result=False): michael@0: """Gets contents from the file specified by |filename|.""" michael@0: return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + michael@0: filename + '"; fi', log_result=log_result) michael@0: michael@0: def SetFileContents(self, filename, contents): michael@0: """Writes |contents| to the file specified by |filename|.""" michael@0: with tempfile.NamedTemporaryFile() as f: michael@0: f.write(contents) michael@0: f.flush() michael@0: self._adb.Push(f.name, filename) michael@0: michael@0: def RemovePushedFiles(self): michael@0: """Removes all files pushed with PushIfNeeded() from the device.""" michael@0: for p in self._pushed_files: michael@0: self.RunShellCommand('rm -r %s' % p, timeout_time=2*60) michael@0: michael@0: def ListPathContents(self, path): michael@0: """Lists files in all subdirectories of |path|. michael@0: michael@0: Args: michael@0: path: The path to list. michael@0: michael@0: Returns: michael@0: A dict of {"name": (size, lastmod), ...}. michael@0: """ michael@0: # Example output: michael@0: # /foo/bar: michael@0: # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt michael@0: re_file = re.compile('^-(?P[^\s]+)\s+' michael@0: '(?P[^\s]+)\s+' michael@0: '(?P[^\s]+)\s+' michael@0: '(?P[^\s]+)\s+' michael@0: '(?P[^\s]+)\s+' michael@0: '(?P