Index: build/android/pylib/android_commands.py |
diff --git a/build/android/pylib/android_commands.py b/build/android/pylib/android_commands.py |
index 127c2faee68cf4fa791c8c216591338ca60a87bf..238a775aa46f010fe68cf239c4f8d53f5d7924e7 100644 |
--- a/build/android/pylib/android_commands.py |
+++ b/build/android/pylib/android_commands.py |
@@ -22,10 +22,12 @@ import time |
import pexpect |
import io_stats_parser |
-# adb_interface.py is under ../../../third_party/android_testrunner/ |
-sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', |
- '..', '..', 'third_party', 'android_testrunner')) |
+CHROME_SRC = os.path.join( |
+ os.path.abspath(os.path.dirname(__file__)), '..', '..', '..') |
+ |
+sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner')) |
import adb_interface |
+ |
import cmd_helper |
import errors # is under ../../../third_party/android_testrunner/errors.py |
@@ -66,6 +68,7 @@ KEYCODE_DPAD_RIGHT = 22 |
KEYCODE_ENTER = 66 |
KEYCODE_MENU = 82 |
+MD5SUM_DEVICE_PATH = '/data/local/tmp/md5sum_bin' |
def GetEmulators(): |
"""Returns a list of emulators. Does not filter by status (e.g. offline). |
@@ -113,42 +116,6 @@ def GetAttachedDevices(): |
devices.insert(0, preferred_device) |
return devices |
- |
-def _GetHostFileInfo(file_name): |
- """Returns a tuple containing size and modified UTC time for file_name.""" |
- # The time accuracy on device is only to minute level, remove the second and |
- # microsecond from host results. |
- utc_time = datetime.datetime.utcfromtimestamp(os.path.getmtime(file_name)) |
- time_delta = datetime.timedelta(seconds=utc_time.second, |
- microseconds=utc_time.microsecond) |
- return os.path.getsize(file_name), utc_time - time_delta |
- |
- |
-def ListHostPathContents(path): |
- """Lists files in all subdirectories of |path|. |
- |
- Args: |
- path: The path to list. |
- |
- Returns: |
- A dict of {"name": (size, lastmod), ...}. |
- """ |
- if os.path.isfile(path): |
- return {os.path.basename(path): _GetHostFileInfo(path)} |
- ret = {} |
- for root, dirs, files in os.walk(path): |
- for d in dirs: |
- if d.startswith('.'): |
- dirs.remove(d) # Prune the dir for subsequent iterations. |
- for f in files: |
- if f.startswith('.'): |
- continue |
- full_file_name = os.path.join(root, f) |
- file_name = os.path.relpath(full_file_name, path) |
- ret[file_name] = _GetHostFileInfo(full_file_name) |
- return ret |
- |
- |
def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): |
"""Gets a list of files from `ls` command output. |
@@ -201,6 +168,20 @@ def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): |
files[filename] = (int(file_match.group('size')), lastmod) |
return files |
+def _ComputeFileListHash(md5sum_output): |
+ """Returns a list of MD5 strings from the provided md5sum output.""" |
+ return [line.split(' ')[0] for line in md5sum_output] |
+ |
+def _HasAdbPushSucceeded(command_output): |
+ """Returns whether adb push has succeeded from the provided output.""" |
+ if not command_output: |
+ return False |
+ # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
+ # Errors look like this: "failed to copy ... " |
+ if not re.search('^[0-9]', command_output.splitlines()[-1]): |
+ logging.critical('PUSH FAILED: ' + command_output) |
+ return False |
+ return True |
def GetLogTimestamp(log_line, year): |
"""Returns the timestamp of the given |log_line| in the given year.""" |
@@ -228,6 +209,7 @@ class AndroidCommands(object): |
self._original_governor = None |
self._pushed_files = [] |
self._device_utc_offset = self.RunShellCommand('date +%z')[0] |
+ self._md5sum_path = '' |
def Adb(self): |
"""Returns our AdbInterface to avoid us wrapping all its methods.""" |
@@ -264,10 +246,6 @@ class AndroidCommands(object): |
self.RestartShell() |
raise last_err # Only reached after max retries, re-raise the last error. |
- def SynchronizeDateTime(self): |
- """Synchronize date/time between host and device.""" |
- self._adb.SendShellCommand('date -u %f' % time.time()) |
- |
def RestartShell(self): |
"""Restarts the shell on the device. Does not block for it to return.""" |
self.RunShellCommand('stop') |
@@ -552,26 +530,34 @@ class AndroidCommands(object): |
"""Pushes |local_path| to |device_path|. |
Works for files and directories. This method skips copying any paths in |
- |test_data_paths| that already exist on the device with the same timestamp |
- and size. |
+ |test_data_paths| that already exist on the device with the same hash. |
All pushed files can be removed by calling RemovePushedFiles(). |
""" |
assert os.path.exists(local_path), 'Local path not found %s' % local_path |
- self._pushed_files.append(device_path) |
- # If the path contents are the same, there's nothing to do. |
- local_contents = ListHostPathContents(local_path) |
- device_contents = self.ListPathContents(device_path) |
- # Only compare the size and timestamp if only copying a file because |
- # the filename on device can be renamed. |
- if os.path.isfile(local_path): |
- assert len(local_contents) == 1 |
- is_equal = local_contents.values() == device_contents.values() |
- else: |
- is_equal = local_contents == device_contents |
- if is_equal: |
- logging.info('%s is up-to-date. Skipping file push.', device_path) |
+ if not self._md5sum_path: |
+ default_build_type = os.environ.get('BUILD_TYPE', 'Debug') |
+ md5sum_path = '%s/out/%s/md5sum_bin' % (CHROME_SRC, default_build_type) |
+ if not os.path.exists(md5sum_path): |
+ md5sum_path = '%s/out/Release/md5sum_bin' % (CHROME_SRC) |
+ if not os.path.exists(md5sum_path): |
+ print >>sys.stderr, 'Please build md5sum.' |
+ sys.exit(1) |
+ if not self.FileExistsOnDevice(MD5SUM_DEVICE_PATH): |
+ command = 'push %s %s' % (md5sum_path, MD5SUM_DEVICE_PATH) |
+ assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) |
+ self._md5sum_path = md5sum_path |
+ |
+ self._pushed_files.append(device_path) |
+ hashes_on_device = _ComputeFileListHash( |
+ self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path)) |
+ assert os.path.exists(local_path), 'Local path not found %s' % local_path |
+ hashes_on_host = _ComputeFileListHash( |
+ subprocess.Popen( |
+ '%s_host %s' % (self._md5sum_path, local_path), |
+ stdout=subprocess.PIPE, shell=True).stdout) |
+ if hashes_on_device == hashes_on_host: |
return |
# They don't match, so remove everything first and then create it. |
@@ -584,11 +570,8 @@ class AndroidCommands(object): |
push_command = 'push %s %s' % (local_path, device_path) |
logging.info('>>> $' + push_command) |
output = self._adb.SendCommand(push_command, timeout_time=30*60) |
- assert output |
- # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
- # Errors look like this: "failed to copy ... " |
- if not re.search('^[0-9]', output.splitlines()[-1]): |
- logging.critical('PUSH FAILED: ' + output) |
+ assert _HasAdbPushSucceeded(output) |
+ |
def GetFileContents(self, filename, log_result=False): |
"""Gets contents from the file specified by |filename|.""" |
@@ -994,6 +977,20 @@ class AndroidCommands(object): |
logging.info('PidsUsingDevicePort: %s', pids) |
return pids |
+ def FileExistsOnDevice(self, file_name): |
+ """Checks whether the given (regular) file exists on the device. |
+ |
+ Args: |
+ file_name: Full path of file to check. |
+ |
+ Returns: |
+ True if the file exists, False otherwise. |
+ """ |
+ assert '"' not in file_name, 'file_name cannot contain double quotes' |
+ status = self._adb.SendShellCommand( |
+ '\'test -f "%s"; echo $?\'' % (file_name)) |
+ return int(status) == 0 |
+ |
def RunMonkey(self, package_name, category=None, throttle=100, seed=None, |
event_count=10000, verbosity=1, extra_args=''): |
"""Runs monkey test for a given package. |