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

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: Get rid of $STRIP in md5sum.gyp 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
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
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 = '%s/out/Debug/md5sum' % (CHROME_SRC)
213 if not os.path.exists(self._md5sum_path):
214 self._md5sum_path = '%s/out/Release/md5sum' % (chrome_src_path)
215 # Push the md5sum binary to the device if needed.
216 if self._md5sum_path and not self.FileExistsOnDevice(MD5SUM_DEVICE_PATH):
217 command = 'push %s %s' % (self._md5sum_path, MD5SUM_DEVICE_PATH)
218 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
231 219
232 def Adb(self): 220 def Adb(self):
233 """Returns our AdbInterface to avoid us wrapping all its methods.""" 221 """Returns our AdbInterface to avoid us wrapping all its methods."""
234 return self._adb 222 return self._adb
235 223
236 def IsRootEnabled(self): 224 def IsRootEnabled(self):
237 """Checks if root is enabled on the device.""" 225 """Checks if root is enabled on the device."""
238 root_test_output = self.RunShellCommand('ls /root') or [''] 226 root_test_output = self.RunShellCommand('ls /root') or ['']
239 return not 'Permission denied' in root_test_output[0] 227 return not 'Permission denied' in root_test_output[0]
240 228
(...skipping 16 matching lines...) Expand all
257 try: 245 try:
258 self._adb.WaitForDevicePm() 246 self._adb.WaitForDevicePm()
259 return # Success 247 return # Success
260 except errors.WaitForResponseTimedOutError as e: 248 except errors.WaitForResponseTimedOutError as e:
261 last_err = e 249 last_err = e
262 logging.warning('Restarting and retrying after timeout: %s', e) 250 logging.warning('Restarting and retrying after timeout: %s', e)
263 retries -= 1 251 retries -= 1
264 self.RestartShell() 252 self.RestartShell()
265 raise last_err # Only reached after max retries, re-raise the last error. 253 raise last_err # Only reached after max retries, re-raise the last error.
266 254
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): 255 def RestartShell(self):
272 """Restarts the shell on the device. Does not block for it to return.""" 256 """Restarts the shell on the device. Does not block for it to return."""
273 self.RunShellCommand('stop') 257 self.RunShellCommand('stop')
274 self.RunShellCommand('start') 258 self.RunShellCommand('start')
275 259
276 def Reboot(self, full_reboot=True): 260 def Reboot(self, full_reboot=True):
277 """Reboots the device and waits for the package manager to return. 261 """Reboots the device and waits for the package manager to return.
278 262
279 Args: 263 Args:
280 full_reboot: Whether to fully reboot the device or just restart the shell. 264 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 529
546 Args: 530 Args:
547 keycode: Numeric keycode to send (see "enum" at top of file). 531 keycode: Numeric keycode to send (see "enum" at top of file).
548 """ 532 """
549 self.RunShellCommand('input keyevent %d' % keycode) 533 self.RunShellCommand('input keyevent %d' % keycode)
550 534
551 def PushIfNeeded(self, local_path, device_path): 535 def PushIfNeeded(self, local_path, device_path):
552 """Pushes |local_path| to |device_path|. 536 """Pushes |local_path| to |device_path|.
553 537
554 Works for files and directories. This method skips copying any paths in 538 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 539 |test_data_paths| that already exist on the device with the same md5.
Peter Beverloo 2012/08/30 12:45:14 nit: s/md5/hash?
Philippe 2012/08/31 11:57:39 Done.
556 and size.
557 540
558 All pushed files can be removed by calling RemovePushedFiles(). 541 All pushed files can be removed by calling RemovePushedFiles().
559 """ 542 """
560 assert os.path.exists(local_path), 'Local path not found %s' % local_path 543 assert os.path.exists(local_path), 'Local path not found %s' % local_path
544 if not os.path.exists(self._md5sum_path):
545 print >>sys.stderr, "Please build md5sum ('make md5sum')"
546 sys.exit(1)
Peter Beverloo 2012/08/30 12:45:14 Should we do this check just once in the construct
Philippe 2012/08/31 11:57:39 This is what I did initially. But Isaac said that
547
561 self._pushed_files.append(device_path) 548 self._pushed_files.append(device_path)
562 549 hashes_on_device = _ComputeFileListHash(
563 # If the path contents are the same, there's nothing to do. 550 self.RunShellCommand(MD5SUM_DEVICE_PATH + ' ' + device_path))
564 local_contents = ListHostPathContents(local_path) 551 assert os.path.exists(local_path), 'Local path not found %s' % local_path
565 device_contents = self.ListPathContents(device_path) 552 hashes_on_host = _ComputeFileListHash(
566 # Only compare the size and timestamp if only copying a file because 553 cmd_helper.GetCmdOutput(
567 # the filename on device can be renamed. 554 '%s_host_bin %s' % (self._md5sum_path, local_path), shell=True))
568 if os.path.isfile(local_path): 555 if hashes_on_device == hashes_on_host:
569 assert len(local_contents) == 1
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 556 return
576 557
577 # They don't match, so remove everything first and then create it. 558 # They don't match, so remove everything first and then create it.
578 if os.path.isdir(local_path): 559 if os.path.isdir(local_path):
579 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) 560 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60)
580 self.RunShellCommand('mkdir -p %s' % device_path) 561 self.RunShellCommand('mkdir -p %s' % device_path)
581 562
582 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of 563 # 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. 564 # 60 seconds which isn't sufficient for a lot of users of this method.
584 push_command = 'push %s %s' % (local_path, device_path) 565 push_command = 'push %s %s' % (local_path, device_path)
585 logging.info('>>> $' + push_command) 566 logging.info('>>> $' + push_command)
586 output = self._adb.SendCommand(push_command, timeout_time=30*60) 567 output = self._adb.SendCommand(push_command, timeout_time=30*60)
587 assert output 568 assert _HasAdbPushSucceeded(output)
588 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" 569
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 570
593 def GetFileContents(self, filename, log_result=False): 571 def GetFileContents(self, filename, log_result=False):
594 """Gets contents from the file specified by |filename|.""" 572 """Gets contents from the file specified by |filename|."""
595 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + 573 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' +
596 filename + '"; fi', log_result=log_result) 574 filename + '"; fi', log_result=log_result)
597 575
598 def SetFileContents(self, filename, contents): 576 def SetFileContents(self, filename, contents):
599 """Writes |contents| to the file specified by |filename|.""" 577 """Writes |contents| to the file specified by |filename|."""
600 with tempfile.NamedTemporaryFile() as f: 578 with tempfile.NamedTemporaryFile() as f:
601 f.write(contents) 579 f.write(contents)
(...skipping 385 matching lines...) Expand 10 before | Expand all | Expand 10 after
987 continue 965 continue
988 # Column 0 is the executable name 966 # Column 0 is the executable name
989 # Column 1 is the pid 967 # Column 1 is the pid
990 # Column 8 is the Inode in use 968 # Column 8 is the Inode in use
991 if process_results[8] == socket_name: 969 if process_results[8] == socket_name:
992 pids.append((int(process_results[1]), process_results[0])) 970 pids.append((int(process_results[1]), process_results[0]))
993 break 971 break
994 logging.info('PidsUsingDevicePort: %s', pids) 972 logging.info('PidsUsingDevicePort: %s', pids)
995 return pids 973 return pids
996 974
975 def FileExistsOnDevice(self, file_name):
976 """Checks whether the given (regular) file exists on the device.
977
978 Args:
979 file_name: Full path of file to check.
980
981 Returns:
982 True if the file exists, False otherwise.
983 """
984 assert '"' not in file_name, 'file_name cannot contain double quotes'
985 status = self._adb.SendShellCommand(
986 '\'test -f "%s"; echo $?\'' % (file_name))
987 return int(status) == 0
988
997 def RunMonkey(self, package_name, category=None, throttle=100, seed=None, 989 def RunMonkey(self, package_name, category=None, throttle=100, seed=None,
998 event_count=10000, verbosity=1, extra_args=''): 990 event_count=10000, verbosity=1, extra_args=''):
999 """Runs monkey test for a given package. 991 """Runs monkey test for a given package.
1000 992
1001 Args: 993 Args:
1002 package_name: Allowed package. 994 package_name: Allowed package.
1003 category: A list of allowed categories. 995 category: A list of allowed categories.
1004 throttle: Delay between events (ms). 996 throttle: Delay between events (ms).
1005 seed: Seed value for pseduo-random generator. Same seed value 997 seed: Seed value for pseduo-random generator. Same seed value
1006 generates the same sequence of events. Seed is randomized by 998 generates the same sequence of events. Seed is randomized by
(...skipping 11 matching lines...) Expand all
1018 cmd = ['monkey', 1010 cmd = ['monkey',
1019 '-p %s' % package_name, 1011 '-p %s' % package_name,
1020 ' '.join(['-c %s' % c for c in category]), 1012 ' '.join(['-c %s' % c for c in category]),
1021 '--throttle %d' % throttle, 1013 '--throttle %d' % throttle,
1022 '-s %d' % seed, 1014 '-s %d' % seed,
1023 '-v ' * verbosity, 1015 '-v ' * verbosity,
1024 extra_args, 1016 extra_args,
1025 '%s' % event_count] 1017 '%s' % event_count]
1026 return self.RunShellCommand(' '.join(cmd), 1018 return self.RunShellCommand(' '.join(cmd),
1027 timeout_time=event_count*throttle*1.5) 1019 timeout_time=event_count*throttle*1.5)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698