[Android WebView] Fix webview perf bot switchover to use org.chromium.webview_shell...
[chromium-blink-merge.git] / build / android / provision_devices.py
blob259bd55ab24a96f97848db8790cfaca7ec234025
1 #!/usr/bin/env python
3 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
7 """Provisions Android devices with settings required for bots.
9 Usage:
10 ./provision_devices.py [-d <device serial number>]
11 """
13 import argparse
14 import logging
15 import os
16 import posixpath
17 import re
18 import subprocess
19 import sys
20 import time
22 from pylib import constants
23 from pylib import device_settings
24 from pylib.device import battery_utils
25 from pylib.device import device_blacklist
26 from pylib.device import device_errors
27 from pylib.device import device_utils
28 from pylib.utils import run_tests_helper
29 from pylib.utils import timeout_retry
31 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
32 'third_party', 'android_testrunner'))
33 import errors
36 class _DEFAULT_TIMEOUTS(object):
37 # L can take a while to reboot after a wipe.
38 LOLLIPOP = 600
39 PRE_LOLLIPOP = 180
41 HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
44 class _PHASES(object):
45 WIPE = 'wipe'
46 PROPERTIES = 'properties'
47 FINISH = 'finish'
49 ALL = [WIPE, PROPERTIES, FINISH]
52 def ProvisionDevices(options):
53 devices = device_utils.DeviceUtils.HealthyDevices()
54 if options.device:
55 devices = [d for d in devices if d == options.device]
56 if not devices:
57 raise device_errors.DeviceUnreachableError(options.device)
59 parallel_devices = device_utils.DeviceUtils.parallel(devices)
60 parallel_devices.pMap(ProvisionDevice, options)
61 if options.auto_reconnect:
62 _LaunchHostHeartbeat()
63 blacklist = device_blacklist.ReadBlacklist()
64 if all(d in blacklist for d in devices):
65 raise device_errors.NoDevicesError
66 return 0
69 def ProvisionDevice(device, options):
70 if options.reboot_timeout:
71 reboot_timeout = options.reboot_timeout
72 elif (device.build_version_sdk >=
73 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
74 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
75 else:
76 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
78 def should_run_phase(phase_name):
79 return not options.phases or phase_name in options.phases
81 def run_phase(phase_func, reboot=True):
82 device.WaitUntilFullyBooted(timeout=reboot_timeout)
83 phase_func(device, options)
84 if reboot:
85 device.Reboot(False, retries=0)
86 device.adb.WaitForDevice()
88 try:
89 if should_run_phase(_PHASES.WIPE):
90 run_phase(WipeDevice)
92 if should_run_phase(_PHASES.PROPERTIES):
93 run_phase(SetProperties)
95 if should_run_phase(_PHASES.FINISH):
96 run_phase(FinishProvisioning, reboot=False)
98 except (errors.WaitForResponseTimedOutError,
99 device_errors.CommandTimeoutError):
100 logging.exception('Timed out waiting for device %s. Adding to blacklist.',
101 str(device))
102 device_blacklist.ExtendBlacklist([str(device)])
104 except device_errors.CommandFailedError:
105 logging.exception('Failed to provision device %s. Adding to blacklist.',
106 str(device))
107 device_blacklist.ExtendBlacklist([str(device)])
110 def WipeDevice(device, options):
111 """Wipes data from device, keeping only the adb_keys for authorization.
113 After wiping data on a device that has been authorized, adb can still
114 communicate with the device, but after reboot the device will need to be
115 re-authorized because the adb keys file is stored in /data/misc/adb/.
116 Thus, adb_keys file is rewritten so the device does not need to be
117 re-authorized.
119 Arguments:
120 device: the device to wipe
122 if options.skip_wipe:
123 return
125 try:
126 device.EnableRoot()
127 device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
128 if device_authorized:
129 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
130 as_root=True).splitlines()
131 device.RunShellCommand(['wipe', 'data'],
132 as_root=True, check_return=True)
133 device.adb.WaitForDevice()
135 if device_authorized:
136 adb_keys_set = set(adb_keys)
137 for adb_key_file in options.adb_key_files or []:
138 try:
139 with open(adb_key_file, 'r') as f:
140 adb_public_keys = f.readlines()
141 adb_keys_set.update(adb_public_keys)
142 except IOError:
143 logging.warning('Unable to find adb keys file %s.' % adb_key_file)
144 _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
145 except device_errors.CommandFailedError:
146 logging.exception('Possible failure while wiping the device. '
147 'Attempting to continue.')
150 def _WriteAdbKeysFile(device, adb_keys_string):
151 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
152 device.RunShellCommand(['mkdir', '-p', dir_path],
153 as_root=True, check_return=True)
154 device.RunShellCommand(['restorecon', dir_path],
155 as_root=True, check_return=True)
156 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
157 device.RunShellCommand(['restorecon', constants.ADB_KEYS_FILE],
158 as_root=True, check_return=True)
161 def SetProperties(device, options):
162 try:
163 device.EnableRoot()
164 except device_errors.CommandFailedError as e:
165 logging.warning(str(e))
167 _ConfigureLocalProperties(device, options.enable_java_debug)
168 device_settings.ConfigureContentSettings(
169 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
170 if options.disable_location:
171 device_settings.ConfigureContentSettings(
172 device, device_settings.DISABLE_LOCATION_SETTINGS)
173 else:
174 device_settings.ConfigureContentSettings(
175 device, device_settings.ENABLE_LOCATION_SETTINGS)
176 device_settings.SetLockScreenSettings(device)
177 if options.disable_network:
178 device_settings.ConfigureContentSettings(
179 device, device_settings.NETWORK_DISABLED_SETTINGS)
181 if options.min_battery_level is not None:
182 try:
183 battery = battery_utils.BatteryUtils(device)
184 battery.ChargeDeviceToLevel(options.min_battery_level)
185 except device_errors.CommandFailedError as e:
186 logging.exception('Unable to charge device to specified level.')
188 if options.max_battery_temp is not None:
189 try:
190 battery = battery_utils.BatteryUtils(device)
191 battery.LetBatteryCoolToTemperature(options.max_battery_temp)
192 except device_errors.CommandFailedError as e:
193 logging.exception('Unable to let battery cool to specified temperature.')
195 def _ConfigureLocalProperties(device, java_debug=True):
196 """Set standard readonly testing device properties prior to reboot."""
197 local_props = [
198 'persist.sys.usb.config=adb',
199 'ro.monkey=1',
200 'ro.test_harness=1',
201 'ro.audio.silent=1',
202 'ro.setupwizard.mode=DISABLED',
204 if java_debug:
205 local_props.append(
206 '%s=all' % device_utils.DeviceUtils.JAVA_ASSERT_PROPERTY)
207 local_props.append('debug.checkjni=1')
208 try:
209 device.WriteFile(
210 constants.DEVICE_LOCAL_PROPERTIES_PATH,
211 '\n'.join(local_props), as_root=True)
212 # Android will not respect the local props file if it is world writable.
213 device.RunShellCommand(
214 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
215 as_root=True, check_return=True)
216 except device_errors.CommandFailedError:
217 logging.exception('Failed to configure local properties.')
220 def FinishProvisioning(device, options):
221 device.RunShellCommand(
222 ['date', '-s', time.strftime('%Y%m%d.%H%M%S', time.gmtime())],
223 as_root=True, check_return=True)
224 props = device.RunShellCommand('getprop', check_return=True)
225 for prop in props:
226 logging.info(' %s' % prop)
227 if options.auto_reconnect:
228 _PushAndLaunchAdbReboot(device, options.target)
231 def _PushAndLaunchAdbReboot(device, target):
232 """Pushes and launches the adb_reboot binary on the device.
234 Arguments:
235 device: The DeviceUtils instance for the device to which the adb_reboot
236 binary should be pushed.
237 target: The build target (example, Debug or Release) which helps in
238 locating the adb_reboot binary.
240 logging.info('Will push and launch adb_reboot on %s' % str(device))
241 # Kill if adb_reboot is already running.
242 device.KillAll('adb_reboot', blocking=True, timeout=2, quiet=True)
243 # Push adb_reboot
244 logging.info(' Pushing adb_reboot ...')
245 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
246 'out/%s/adb_reboot' % target)
247 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
248 # Launch adb_reboot
249 logging.info(' Launching adb_reboot ...')
250 device.RunShellCommand(
251 ['/data/local/tmp/adb_reboot'],
252 check_return=True)
255 def _LaunchHostHeartbeat():
256 # Kill if existing host_heartbeat
257 KillHostHeartbeat()
258 # Launch a new host_heartbeat
259 logging.info('Spawning host heartbeat...')
260 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
261 'build/android/host_heartbeat.py')])
264 def KillHostHeartbeat():
265 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
266 stdout, _ = ps.communicate()
267 matches = re.findall('\\n.*host_heartbeat.*', stdout)
268 for match in matches:
269 logging.info('An instance of host heart beart running... will kill')
270 pid = re.findall(r'(\S+)', match)[1]
271 subprocess.call(['kill', str(pid)])
274 def main():
275 # Recommended options on perf bots:
276 # --disable-network
277 # TODO(tonyg): We eventually want network on. However, currently radios
278 # can cause perfbots to drain faster than they charge.
279 # --min-battery-level 95
280 # Some perf bots run benchmarks with USB charging disabled which leads
281 # to gradual draining of the battery. We must wait for a full charge
282 # before starting a run in order to keep the devices online.
284 parser = argparse.ArgumentParser(
285 description='Provision Android devices with settings required for bots.')
286 parser.add_argument('-d', '--device', metavar='SERIAL',
287 help='the serial number of the device to be provisioned'
288 ' (the default is to provision all devices attached)')
289 parser.add_argument('--phase', action='append', choices=_PHASES.ALL,
290 dest='phases',
291 help='Phases of provisioning to run. '
292 '(If omitted, all phases will be run.)')
293 parser.add_argument('--skip-wipe', action='store_true', default=False,
294 help="don't wipe device data during provisioning")
295 parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
296 help='when wiping the device, max number of seconds to'
297 ' wait after each reboot '
298 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
299 parser.add_argument('--min-battery-level', type=int, metavar='NUM',
300 help='wait for the device to reach this minimum battery'
301 ' level before trying to continue')
302 parser.add_argument('--disable-location', action='store_true',
303 help='disable Google location services on devices')
304 parser.add_argument('--disable-network', action='store_true',
305 help='disable network access on devices')
306 parser.add_argument('--disable-java-debug', action='store_false',
307 dest='enable_java_debug', default=True,
308 help='disable Java property asserts and JNI checking')
309 parser.add_argument('-t', '--target', default='Debug',
310 help='the build target (default: %(default)s)')
311 parser.add_argument('-r', '--auto-reconnect', action='store_true',
312 help='push binary which will reboot the device on adb'
313 ' disconnections')
314 parser.add_argument('--adb-key-files', type=str, nargs='+',
315 help='list of adb keys to push to device')
316 parser.add_argument('-v', '--verbose', action='count', default=1,
317 help='Log more information.')
318 parser.add_argument('--max-battery-temp', type=int, metavar='NUM',
319 help='Wait for the battery to have this temp or lower.')
320 args = parser.parse_args()
321 constants.SetBuildType(args.target)
323 run_tests_helper.SetLogLevel(args.verbose)
325 return ProvisionDevices(args)
328 if __name__ == '__main__':
329 sys.exit(main())