Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(275)

Side by Side Diff: build/android/pylib/android_commands.py

Issue 10867008: Get rid of device/host clock synchronization in android_commands.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address Marcus' comments Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « build/all_android.gyp ('k') | build/android/pylib/base_test_runner.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Provides an interface to communicate with the device via the adb command. 5 """Provides an interface to communicate with the device via the adb command.
6 6
7 Assumes adb binary is currently on system path. 7 Assumes adb binary is currently on system path.
8 """ 8 """
9 9
10 import collections 10 import collections
11 import datetime 11 import datetime
12 import logging 12 import logging
13 import os 13 import os
14 import random 14 import random
15 import re 15 import re
16 import shlex 16 import shlex
17 import subprocess 17 import subprocess
18 import sys 18 import sys
19 import tempfile 19 import tempfile
20 import time 20 import time
21 21
22 import pexpect 22 import pexpect
23 import io_stats_parser 23 import io_stats_parser
24 24
25 # adb_interface.py is under ../../../third_party/android_testrunner/ 25 CHROME_SRC = os.path.join(
26 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', 26 os.path.abspath(os.path.dirname(__file__)), '..', '..', '..')
27 '..', '..', 'third_party', 'android_testrunner')) 27
28 sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner'))
28 import adb_interface 29 import adb_interface
30
29 import cmd_helper 31 import cmd_helper
30 import errors # is under ../../../third_party/android_testrunner/errors.py 32 import errors # is under ../../../third_party/android_testrunner/errors.py
31 33
32 34
33 # Pattern to search for the next whole line of pexpect output and capture it 35 # Pattern to search for the next whole line of pexpect output and capture it
34 # into a match group. We can't use ^ and $ for line start end with pexpect, 36 # into a match group. We can't use ^ and $ for line start end with pexpect,
35 # see http://www.noah.org/python/pexpect/#doc for explanation why. 37 # see http://www.noah.org/python/pexpect/#doc for explanation why.
36 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') 38 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
37 39
38 # Set the adb shell prompt to be a unique marker that will [hopefully] not 40 # Set the adb shell prompt to be a unique marker that will [hopefully] not
(...skipping 20 matching lines...) Expand all
59 61
60 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). 62 # Keycode "enum" suitable for passing to AndroidCommands.SendKey().
61 KEYCODE_HOME = 3 63 KEYCODE_HOME = 3
62 KEYCODE_BACK = 4 64 KEYCODE_BACK = 4
63 KEYCODE_DPAD_UP = 19 65 KEYCODE_DPAD_UP = 19
64 KEYCODE_DPAD_DOWN = 20 66 KEYCODE_DPAD_DOWN = 20
65 KEYCODE_DPAD_RIGHT = 22 67 KEYCODE_DPAD_RIGHT = 22
66 KEYCODE_ENTER = 66 68 KEYCODE_ENTER = 66
67 KEYCODE_MENU = 82 69 KEYCODE_MENU = 82
68 70
71 MD5SUM_DEVICE_PATH = '/data/local/tmp/md5sum_bin'
69 72
70 def GetEmulators(): 73 def GetEmulators():
71 """Returns a list of emulators. Does not filter by status (e.g. offline). 74 """Returns a list of emulators. Does not filter by status (e.g. offline).
72 75
73 Both devices starting with 'emulator' will be returned in below output: 76 Both devices starting with 'emulator' will be returned in below output:
74 77
75 * daemon not running. starting it now on port 5037 * 78 * daemon not running. starting it now on port 5037 *
76 * daemon started successfully * 79 * daemon started successfully *
77 List of devices attached 80 List of devices attached
78 027c10494100b4d7 device 81 027c10494100b4d7 device
(...skipping 27 matching lines...) Expand all
106 emulator-5554 offline 109 emulator-5554 offline
107 """ 110 """
108 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) 111 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
109 devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices'])) 112 devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices']))
110 preferred_device = os.environ.get('ANDROID_SERIAL') 113 preferred_device = os.environ.get('ANDROID_SERIAL')
111 if preferred_device in devices: 114 if preferred_device in devices:
112 devices.remove(preferred_device) 115 devices.remove(preferred_device)
113 devices.insert(0, preferred_device) 116 devices.insert(0, preferred_device)
114 return devices 117 return devices
115 118
116
117 def _GetHostFileInfo(file_name):
118 """Returns a tuple containing size and modified UTC time for file_name."""
119 # The time accuracy on device is only to minute level, remove the second and
120 # microsecond from host results.
121 utc_time = datetime.datetime.utcfromtimestamp(os.path.getmtime(file_name))
122 time_delta = datetime.timedelta(seconds=utc_time.second,
123 microseconds=utc_time.microsecond)
124 return os.path.getsize(file_name), utc_time - time_delta
125
126
127 def ListHostPathContents(path):
128 """Lists files in all subdirectories of |path|.
129
130 Args:
131 path: The path to list.
132
133 Returns:
134 A dict of {"name": (size, lastmod), ...}.
135 """
136 if os.path.isfile(path):
137 return {os.path.basename(path): _GetHostFileInfo(path)}
138 ret = {}
139 for root, dirs, files in os.walk(path):
140 for d in dirs:
141 if d.startswith('.'):
142 dirs.remove(d) # Prune the dir for subsequent iterations.
143 for f in files:
144 if f.startswith('.'):
145 continue
146 full_file_name = os.path.join(root, f)
147 file_name = os.path.relpath(full_file_name, path)
148 ret[file_name] = _GetHostFileInfo(full_file_name)
149 return ret
150
151
152 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None): 119 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
153 """Gets a list of files from `ls` command output. 120 """Gets a list of files from `ls` command output.
154 121
155 Python's os.walk isn't used because it doesn't work over adb shell. 122 Python's os.walk isn't used because it doesn't work over adb shell.
156 123
157 Args: 124 Args:
158 path: The path to list. 125 path: The path to list.
159 ls_output: A list of lines returned by an `ls -lR` command. 126 ls_output: A list of lines returned by an `ls -lR` command.
160 re_file: A compiled regular expression which parses a line into named groups 127 re_file: A compiled regular expression which parses a line into named groups
161 consisting of at minimum "filename", "date", "time", "size" and 128 consisting of at minimum "filename", "date", "time", "size" and
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
194 utc_offset = file_match.group('timezone') 161 utc_offset = file_match.group('timezone')
195 if isinstance(utc_offset, str) and len(utc_offset) == 5: 162 if isinstance(utc_offset, str) and len(utc_offset) == 5:
196 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), 163 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
197 minutes=int(utc_offset[3:5])) 164 minutes=int(utc_offset[3:5]))
198 if utc_offset[0:1] == '-': 165 if utc_offset[0:1] == '-':
199 utc_delta = -utc_delta 166 utc_delta = -utc_delta
200 lastmod -= utc_delta 167 lastmod -= utc_delta
201 files[filename] = (int(file_match.group('size')), lastmod) 168 files[filename] = (int(file_match.group('size')), lastmod)
202 return files 169 return files
203 170
171 def _ComputeFileListHash(md5sum_output):
172 """Returns a list of MD5 strings from the provided md5sum output."""
173 return [line.split(' ')[0] for line in md5sum_output]
174
175 def _HasAdbPushSucceeded(command_output):
176 """Returns whether adb push has succeeded from the provided output."""
177 if not command_output:
178 return False
179 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
180 # Errors look like this: "failed to copy ... "
181 if not re.search('^[0-9]', command_output.splitlines()[-1]):
182 logging.critical('PUSH FAILED: ' + command_output)
183 return False
184 return True
204 185
205 def GetLogTimestamp(log_line, year): 186 def GetLogTimestamp(log_line, year):
206 """Returns the timestamp of the given |log_line| in the given year.""" 187 """Returns the timestamp of the given |log_line| in the given year."""
207 try: 188 try:
208 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), 189 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
209 '%Y-%m-%d %H:%M:%S.%f') 190 '%Y-%m-%d %H:%M:%S.%f')
210 except (ValueError, IndexError): 191 except (ValueError, IndexError):
211 logging.critical('Error reading timestamp from ' + log_line) 192 logging.critical('Error reading timestamp from ' + log_line)
212 return None 193 return None
213 194
214 195
215 class AndroidCommands(object): 196 class AndroidCommands(object):
216 """Helper class for communicating with Android device via adb. 197 """Helper class for communicating with Android device via adb.
217 198
218 Args: 199 Args:
219 device: If given, adb commands are only send to the device of this ID. 200 device: If given, adb commands are only send to the device of this ID.
220 Otherwise commands are sent to all attached devices. 201 Otherwise commands are sent to all attached devices.
221 """ 202 """
222 203
223 def __init__(self, device=None): 204 def __init__(self, device=None):
224 self._adb = adb_interface.AdbInterface() 205 self._adb = adb_interface.AdbInterface()
225 if device: 206 if device:
226 self._adb.SetTargetSerial(device) 207 self._adb.SetTargetSerial(device)
227 self._logcat = None 208 self._logcat = None
228 self._original_governor = None 209 self._original_governor = None
229 self._pushed_files = [] 210 self._pushed_files = []
230 self._device_utc_offset = self.RunShellCommand('date +%z')[0] 211 self._device_utc_offset = self.RunShellCommand('date +%z')[0]
212 self._md5sum_path = ''
231 213
232 def Adb(self): 214 def Adb(self):
233 """Returns our AdbInterface to avoid us wrapping all its methods.""" 215 """Returns our AdbInterface to avoid us wrapping all its methods."""
234 return self._adb 216 return self._adb
235 217
236 def IsRootEnabled(self): 218 def IsRootEnabled(self):
237 """Checks if root is enabled on the device.""" 219 """Checks if root is enabled on the device."""
238 root_test_output = self.RunShellCommand('ls /root') or [''] 220 root_test_output = self.RunShellCommand('ls /root') or ['']
239 return not 'Permission denied' in root_test_output[0] 221 return not 'Permission denied' in root_test_output[0]
240 222
(...skipping 16 matching lines...) Expand all
257 try: 239 try:
258 self._adb.WaitForDevicePm() 240 self._adb.WaitForDevicePm()
259 return # Success 241 return # Success
260 except errors.WaitForResponseTimedOutError as e: 242 except errors.WaitForResponseTimedOutError as e:
261 last_err = e 243 last_err = e
262 logging.warning('Restarting and retrying after timeout: %s', e) 244 logging.warning('Restarting and retrying after timeout: %s', e)
263 retries -= 1 245 retries -= 1
264 self.RestartShell() 246 self.RestartShell()
265 raise last_err # Only reached after max retries, re-raise the last error. 247 raise last_err # Only reached after max retries, re-raise the last error.
266 248
267 def SynchronizeDateTime(self):
268 """Synchronize date/time between host and device."""
269 self._adb.SendShellCommand('date -u %f' % time.time())
270
271 def RestartShell(self): 249 def RestartShell(self):
272 """Restarts the shell on the device. Does not block for it to return.""" 250 """Restarts the shell on the device. Does not block for it to return."""
273 self.RunShellCommand('stop') 251 self.RunShellCommand('stop')
274 self.RunShellCommand('start') 252 self.RunShellCommand('start')
275 253
276 def Reboot(self, full_reboot=True): 254 def Reboot(self, full_reboot=True):
277 """Reboots the device and waits for the package manager to return. 255 """Reboots the device and waits for the package manager to return.
278 256
279 Args: 257 Args:
280 full_reboot: Whether to fully reboot the device or just restart the shell. 258 full_reboot: Whether to fully reboot the device or just restart the shell.
(...skipping 264 matching lines...) Expand 10 before | Expand all | Expand 10 after
545 523
546 Args: 524 Args:
547 keycode: Numeric keycode to send (see "enum" at top of file). 525 keycode: Numeric keycode to send (see "enum" at top of file).
548 """ 526 """
549 self.RunShellCommand('input keyevent %d' % keycode) 527 self.RunShellCommand('input keyevent %d' % keycode)
550 528
551 def PushIfNeeded(self, local_path, device_path): 529 def PushIfNeeded(self, local_path, device_path):
552 """Pushes |local_path| to |device_path|. 530 """Pushes |local_path| to |device_path|.
553 531
554 Works for files and directories. This method skips copying any paths in 532 Works for files and directories. This method skips copying any paths in
555 |test_data_paths| that already exist on the device with the same timestamp 533 |test_data_paths| that already exist on the device with the same hash.
556 and size.
557 534
558 All pushed files can be removed by calling RemovePushedFiles(). 535 All pushed files can be removed by calling RemovePushedFiles().
559 """ 536 """
560 assert os.path.exists(local_path), 'Local path not found %s' % local_path 537 assert os.path.exists(local_path), 'Local path not found %s' % local_path
538
539 if not self._md5sum_path:
540 default_build_type = os.environ.get('BUILD_TYPE', 'Debug')
541 md5sum_path = '%s/out/%s/md5sum_bin' % (CHROME_SRC, default_build_type)
542 if not os.path.exists(md5sum_path):
543 md5sum_path = '%s/out/Release/md5sum_bin' % (CHROME_SRC)
544 if not os.path.exists(md5sum_path):
545 print >>sys.stderr, 'Please build md5sum.'
546 sys.exit(1)
547 if not self.FileExistsOnDevice(MD5SUM_DEVICE_PATH):
548 command = 'push %s %s' % (md5sum_path, MD5SUM_DEVICE_PATH)
549 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
550 self._md5sum_path = md5sum_path
551
561 self._pushed_files.append(device_path) 552 self._pushed_files.append(device_path)
562 553 hashes_on_device = _ComputeFileListHash(
563 # If the path contents are the same, there's nothing to do. 554 self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path))
564 local_contents = ListHostPathContents(local_path) 555 assert os.path.exists(local_path), 'Local path not found %s' % local_path
565 device_contents = self.ListPathContents(device_path) 556 hashes_on_host = _ComputeFileListHash(
566 # Only compare the size and timestamp if only copying a file because 557 subprocess.Popen(
567 # the filename on device can be renamed. 558 '%s_host %s' % (self._md5sum_path, local_path),
568 if os.path.isfile(local_path): 559 stdout=subprocess.PIPE, shell=True).stdout)
569 assert len(local_contents) == 1 560 if hashes_on_device == hashes_on_host:
570 is_equal = local_contents.values() == device_contents.values()
571 else:
572 is_equal = local_contents == device_contents
573 if is_equal:
574 logging.info('%s is up-to-date. Skipping file push.', device_path)
575 return 561 return
576 562
577 # They don't match, so remove everything first and then create it. 563 # They don't match, so remove everything first and then create it.
578 if os.path.isdir(local_path): 564 if os.path.isdir(local_path):
579 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) 565 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60)
580 self.RunShellCommand('mkdir -p %s' % device_path) 566 self.RunShellCommand('mkdir -p %s' % device_path)
581 567
582 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of 568 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of
583 # 60 seconds which isn't sufficient for a lot of users of this method. 569 # 60 seconds which isn't sufficient for a lot of users of this method.
584 push_command = 'push %s %s' % (local_path, device_path) 570 push_command = 'push %s %s' % (local_path, device_path)
585 logging.info('>>> $' + push_command) 571 logging.info('>>> $' + push_command)
586 output = self._adb.SendCommand(push_command, timeout_time=30*60) 572 output = self._adb.SendCommand(push_command, timeout_time=30*60)
587 assert output 573 assert _HasAdbPushSucceeded(output)
588 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" 574
589 # Errors look like this: "failed to copy ... "
590 if not re.search('^[0-9]', output.splitlines()[-1]):
591 logging.critical('PUSH FAILED: ' + output)
592 575
593 def GetFileContents(self, filename, log_result=False): 576 def GetFileContents(self, filename, log_result=False):
594 """Gets contents from the file specified by |filename|.""" 577 """Gets contents from the file specified by |filename|."""
595 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + 578 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' +
596 filename + '"; fi', log_result=log_result) 579 filename + '"; fi', log_result=log_result)
597 580
598 def SetFileContents(self, filename, contents): 581 def SetFileContents(self, filename, contents):
599 """Writes |contents| to the file specified by |filename|.""" 582 """Writes |contents| to the file specified by |filename|."""
600 with tempfile.NamedTemporaryFile() as f: 583 with tempfile.NamedTemporaryFile() as f:
601 f.write(contents) 584 f.write(contents)
(...skipping 385 matching lines...) Expand 10 before | Expand all | Expand 10 after
987 continue 970 continue
988 # Column 0 is the executable name 971 # Column 0 is the executable name
989 # Column 1 is the pid 972 # Column 1 is the pid
990 # Column 8 is the Inode in use 973 # Column 8 is the Inode in use
991 if process_results[8] == socket_name: 974 if process_results[8] == socket_name:
992 pids.append((int(process_results[1]), process_results[0])) 975 pids.append((int(process_results[1]), process_results[0]))
993 break 976 break
994 logging.info('PidsUsingDevicePort: %s', pids) 977 logging.info('PidsUsingDevicePort: %s', pids)
995 return pids 978 return pids
996 979
980 def FileExistsOnDevice(self, file_name):
981 """Checks whether the given (regular) file exists on the device.
982
983 Args:
984 file_name: Full path of file to check.
985
986 Returns:
987 True if the file exists, False otherwise.
988 """
989 assert '"' not in file_name, 'file_name cannot contain double quotes'
990 status = self._adb.SendShellCommand(
991 '\'test -f "%s"; echo $?\'' % (file_name))
992 return int(status) == 0
993
997 def RunMonkey(self, package_name, category=None, throttle=100, seed=None, 994 def RunMonkey(self, package_name, category=None, throttle=100, seed=None,
998 event_count=10000, verbosity=1, extra_args=''): 995 event_count=10000, verbosity=1, extra_args=''):
999 """Runs monkey test for a given package. 996 """Runs monkey test for a given package.
1000 997
1001 Args: 998 Args:
1002 package_name: Allowed package. 999 package_name: Allowed package.
1003 category: A list of allowed categories. 1000 category: A list of allowed categories.
1004 throttle: Delay between events (ms). 1001 throttle: Delay between events (ms).
1005 seed: Seed value for pseduo-random generator. Same seed value 1002 seed: Seed value for pseduo-random generator. Same seed value
1006 generates the same sequence of events. Seed is randomized by 1003 generates the same sequence of events. Seed is randomized by
(...skipping 13 matching lines...) Expand all
1020 ' '.join(['-c %s' % c for c in category]), 1017 ' '.join(['-c %s' % c for c in category]),
1021 '--throttle %d' % throttle, 1018 '--throttle %d' % throttle,
1022 '-s %d' % seed, 1019 '-s %d' % seed,
1023 '-v ' * verbosity, 1020 '-v ' * verbosity,
1024 '--monitor-native-crashes', 1021 '--monitor-native-crashes',
1025 '--kill-process-after-error', 1022 '--kill-process-after-error',
1026 extra_args, 1023 extra_args,
1027 '%d' % event_count] 1024 '%d' % event_count]
1028 return self.RunShellCommand(' '.join(cmd), 1025 return self.RunShellCommand(' '.join(cmd),
1029 timeout_time=event_count*throttle*1.5) 1026 timeout_time=event_count*throttle*1.5)
OLDNEW
« no previous file with comments | « build/all_android.gyp ('k') | build/android/pylib/base_test_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698