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

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 Isaac's 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'
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
204 175
205 def GetLogTimestamp(log_line, year): 176 def GetLogTimestamp(log_line, year):
206 """Returns the timestamp of the given |log_line| in the given year.""" 177 """Returns the timestamp of the given |log_line| in the given year."""
207 try: 178 try:
208 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), 179 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
209 '%Y-%m-%d %H:%M:%S.%f') 180 '%Y-%m-%d %H:%M:%S.%f')
210 except (ValueError, IndexError): 181 except (ValueError, IndexError):
211 logging.critical('Error reading timestamp from ' + log_line) 182 logging.critical('Error reading timestamp from ' + log_line)
212 return None 183 return None
213 184
214 185
215 class AndroidCommands(object): 186 class AndroidCommands(object):
216 """Helper class for communicating with Android device via adb. 187 """Helper class for communicating with Android device via adb.
217 188
218 Args: 189 Args:
219 device: If given, adb commands are only send to the device of this ID. 190 device: If given, adb commands are only send to the device of this ID.
220 Otherwise commands are sent to all attached devices. 191 Otherwise commands are sent to all attached devices.
221 """ 192 """
222 193
223 def __init__(self, device=None): 194 def __init__(self, device=None):
224 self._adb = adb_interface.AdbInterface() 195 self._adb = adb_interface.AdbInterface()
225 if device: 196 if device:
226 self._adb.SetTargetSerial(device) 197 self._adb.SetTargetSerial(device)
227 self._logcat = None 198 self._logcat = None
228 self._original_governor = None 199 self._original_governor = None
229 self._pushed_files = [] 200 self._pushed_files = []
230 self._device_utc_offset = self.RunShellCommand('date +%z')[0] 201 self._device_utc_offset = self.RunShellCommand('date +%z')[0]
202 self._md5sum_path = '%s/out/Debug/md5sum' % (CHROME_SRC)
203 if not os.path.exists(self._md5sum_path):
204 self._md5sum_path = '%s/out/Release/md5sum' % (chrome_src_path)
205 if not os.path.exists(self._md5sum_path):
206 print >>sys.stderr, 'Please build md5sum (\'make md5sum\')'
Isaac (away) 2012/08/30 06:50:57 nit: you can remove the backslash escape chars if
Philippe 2012/08/30 08:22:12 Done.
207 sys.exit(1)
Isaac (away) 2012/08/30 06:50:57 Most functions in android_commands do not require
Philippe 2012/08/30 08:22:12 Good point. I moved this check to PushIfNeeded().
208 # Push the md5sum binary to the device if needed.
209 if not self.FileExistsOnDevice(MD5SUM_DEVICE_PATH):
210 command = 'push %s %s' % (self._md5sum_path, MD5SUM_DEVICE_PATH)
211 logging.info(command)
212 assert self._adb.SendCommand(command)
Isaac (away) 2012/08/30 06:50:57 Is this assert being used correctly? it will succ
Philippe 2012/08/30 08:22:12 Indeed. I moved the code we had in PushIfNeeded()
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 md5.
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
561 self._pushed_files.append(device_path) 538 self._pushed_files.append(device_path)
562 539
563 # If the path contents are the same, there's nothing to do. 540 hashes_on_device = _ComputeFileListHash(
564 local_contents = ListHostPathContents(local_path) 541 self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path))
565 device_contents = self.ListPathContents(device_path) 542 assert os.path.exists(local_path), 'Local path not found %s' % local_path
566 # Only compare the size and timestamp if only copying a file because 543 hashes_on_host = _ComputeFileListHash(
567 # the filename on device can be renamed. 544 cmd_helper.GetCmdOutput(
568 if os.path.isfile(local_path): 545 '%s_host_bin %s' % (self._md5sum_path, local_path), shell=True))
569 assert len(local_contents) == 1 546 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 547 return
576 548
577 # They don't match, so remove everything first and then create it. 549 # They don't match, so remove everything first and then create it.
578 if os.path.isdir(local_path): 550 if os.path.isdir(local_path):
579 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) 551 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60)
580 self.RunShellCommand('mkdir -p %s' % device_path) 552 self.RunShellCommand('mkdir -p %s' % device_path)
581 553
582 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of 554 # 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. 555 # 60 seconds which isn't sufficient for a lot of users of this method.
584 push_command = 'push %s %s' % (local_path, device_path) 556 push_command = 'push %s %s' % (local_path, device_path)
(...skipping 402 matching lines...) Expand 10 before | Expand all | Expand 10 after
987 continue 959 continue
988 # Column 0 is the executable name 960 # Column 0 is the executable name
989 # Column 1 is the pid 961 # Column 1 is the pid
990 # Column 8 is the Inode in use 962 # Column 8 is the Inode in use
991 if process_results[8] == socket_name: 963 if process_results[8] == socket_name:
992 pids.append((int(process_results[1]), process_results[0])) 964 pids.append((int(process_results[1]), process_results[0]))
993 break 965 break
994 logging.info('PidsUsingDevicePort: %s', pids) 966 logging.info('PidsUsingDevicePort: %s', pids)
995 return pids 967 return pids
996 968
969 def FileExistsOnDevice(self, file_name):
970 """Checks whether the given (regular) file exists on the device.
971
972 Args:
973 file_name: Full path of file to check.
974
975 Returns:
976 True if the file exists, False otherwise.
977 """
978 assert '"' not in file_name, 'file_name cannot contain double quotes'
979 status = self._adb.SendShellCommand(
980 '\'test -f "%s"; echo $?\'' % (file_name))
Philippe 2012/08/29 08:36:45 Do we still need a comment here? I think it's much
Isaac (away) 2012/08/30 06:50:57 this is fine, thanks
Philippe 2012/08/30 08:22:12 Done.
981 return int(status) == 0
982
997 def RunMonkey(self, package_name, category=None, throttle=100, seed=None, 983 def RunMonkey(self, package_name, category=None, throttle=100, seed=None,
998 event_count=10000, verbosity=1, extra_args=''): 984 event_count=10000, verbosity=1, extra_args=''):
999 """Runs monkey test for a given package. 985 """Runs monkey test for a given package.
1000 986
1001 Args: 987 Args:
1002 package_name: Allowed package. 988 package_name: Allowed package.
1003 category: A list of allowed categories. 989 category: A list of allowed categories.
1004 throttle: Delay between events (ms). 990 throttle: Delay between events (ms).
1005 seed: Seed value for pseduo-random generator. Same seed value 991 seed: Seed value for pseduo-random generator. Same seed value
1006 generates the same sequence of events. Seed is randomized by 992 generates the same sequence of events. Seed is randomized by
(...skipping 11 matching lines...) Expand all
1018 cmd = ['monkey', 1004 cmd = ['monkey',
1019 '-p %s' % package_name, 1005 '-p %s' % package_name,
1020 ' '.join(['-c %s' % c for c in category]), 1006 ' '.join(['-c %s' % c for c in category]),
1021 '--throttle %d' % throttle, 1007 '--throttle %d' % throttle,
1022 '-s %d' % seed, 1008 '-s %d' % seed,
1023 '-v ' * verbosity, 1009 '-v ' * verbosity,
1024 extra_args, 1010 extra_args,
1025 '%s' % event_count] 1011 '%s' % event_count]
1026 return self.RunShellCommand(' '.join(cmd), 1012 return self.RunShellCommand(' '.join(cmd),
1027 timeout_time=event_count*throttle*1.5) 1013 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