Add Apps.AppListSearchQueryLength UMA histogram.
[chromium-blink-merge.git] / build / android / pylib / utils / emulator.py
blob635462f208bed21d53165329c1fd02ec4aa54ef2
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
3 # found in the LICENSE file.
5 """Provides an interface to start and stop Android emulator.
7 Emulator: The class provides the methods to launch/shutdown the emulator with
8 the android virtual device named 'avd_armeabi' .
9 """
11 import logging
12 import os
13 import signal
14 import subprocess
15 import time
17 # TODO(craigdh): Move these pylib dependencies to pylib/utils/.
18 from pylib import cmd_helper
19 from pylib import constants
20 from pylib import pexpect
21 from pylib.device import device_errors
22 from pylib.device import device_utils
23 from pylib.utils import time_profile
25 import errors
26 import run_command
28 # SD card size
29 SDCARD_SIZE = '512M'
31 # Template used to generate config.ini files for the emulator
32 CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1
33 hw.dPad=no
34 hw.lcd.density=320
35 sdcard.size=512M
36 hw.cpu.arch={hw.cpu.arch}
37 hw.device.hash=-708107041
38 hw.camera.back=none
39 disk.dataPartition.size=800M
40 hw.gpu.enabled=yes
41 skin.path=720x1280
42 skin.dynamic=yes
43 hw.keyboard=yes
44 hw.ramSize=1024
45 hw.device.manufacturer=Google
46 hw.sdCard=yes
47 hw.mainKeys=no
48 hw.accelerometer=yes
49 skin.name=720x1280
50 abi.type={abi.type}
51 hw.trackBall=no
52 hw.device.name=Galaxy Nexus
53 hw.battery=yes
54 hw.sensors.proximity=yes
55 image.sysdir.1=system-images/android-{api.level}/{abi.type}/
56 hw.sensors.orientation=yes
57 hw.audioInput=yes
58 hw.camera.front=none
59 hw.gps=yes
60 vm.heapSize=128
61 {extras}"""
63 CONFIG_REPLACEMENTS = {
64 'x86': {
65 '{hw.cpu.arch}': 'x86',
66 '{abi.type}': 'x86',
67 '{extras}': ''
69 'arm': {
70 '{hw.cpu.arch}': 'arm',
71 '{abi.type}': 'armeabi-v7a',
72 '{extras}': 'hw.cpu.model=cortex-a8\n'
74 'mips': {
75 '{hw.cpu.arch}': 'mips',
76 '{abi.type}': 'mips',
77 '{extras}': ''
81 class EmulatorLaunchException(Exception):
82 """Emulator failed to launch."""
83 pass
85 def _KillAllEmulators():
86 """Kill all running emulators that look like ones we started.
88 There are odd 'sticky' cases where there can be no emulator process
89 running but a device slot is taken. A little bot trouble and and
90 we're out of room forever.
91 """
92 emulators = [d for d in device_utils.HealthyDevices() if d.adb.is_emulator]
93 if not emulators:
94 return
95 for e in emulators:
96 e.adb.Emu(['kill'])
97 logging.info('Emulator killing is async; give a few seconds for all to die.')
98 for _ in range(5):
99 if not any(d.adb.is_emulator for d in device_utils.HealthyDevices()):
100 return
101 time.sleep(1)
104 def DeleteAllTempAVDs():
105 """Delete all temporary AVDs which are created for tests.
107 If the test exits abnormally and some temporary AVDs created when testing may
108 be left in the system. Clean these AVDs.
110 avds = device_utils.GetAVDs()
111 if not avds:
112 return
113 for avd_name in avds:
114 if 'run_tests_avd' in avd_name:
115 cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name]
116 cmd_helper.RunCmd(cmd)
117 logging.info('Delete AVD %s' % avd_name)
120 class PortPool(object):
121 """Pool for emulator port starting position that changes over time."""
122 _port_min = 5554
123 _port_max = 5585
124 _port_current_index = 0
126 @classmethod
127 def port_range(cls):
128 """Return a range of valid ports for emulator use.
130 The port must be an even number between 5554 and 5584. Sometimes
131 a killed emulator "hangs on" to a port long enough to prevent
132 relaunch. This is especially true on slow machines (like a bot).
133 Cycling through a port start position helps make us resilient."""
134 ports = range(cls._port_min, cls._port_max, 2)
135 n = cls._port_current_index
136 cls._port_current_index = (n + 1) % len(ports)
137 return ports[n:] + ports[:n]
140 def _GetAvailablePort():
141 """Returns an available TCP port for the console."""
142 used_ports = []
143 emulators = [d for d in device_utils.HealthyDevices() if d.adb.is_emulator]
144 for emulator in emulators:
145 used_ports.append(emulator.adb.GetDeviceSerial().split('-')[1])
146 for port in PortPool.port_range():
147 if str(port) not in used_ports:
148 return port
151 def LaunchTempEmulators(emulator_count, abi, api_level, wait_for_boot=True):
152 """Create and launch temporary emulators and wait for them to boot.
154 Args:
155 emulator_count: number of emulators to launch.
156 abi: the emulator target platform
157 api_level: the api level (e.g., 19 for Android v4.4 - KitKat release)
158 wait_for_boot: whether or not to wait for emulators to boot up
160 Returns:
161 List of emulators.
163 emulators = []
164 for n in xrange(emulator_count):
165 t = time_profile.TimeProfile('Emulator launch %d' % n)
166 # Creates a temporary AVD.
167 avd_name = 'run_tests_avd_%d' % n
168 logging.info('Emulator launch %d with avd_name=%s and api=%d',
169 n, avd_name, api_level)
170 emulator = Emulator(avd_name, abi)
171 emulator.CreateAVD(api_level)
172 emulator.Launch(kill_all_emulators=n == 0)
173 t.Stop()
174 emulators.append(emulator)
175 # Wait for all emulators to boot completed.
176 if wait_for_boot:
177 for emulator in emulators:
178 emulator.ConfirmLaunch(True)
179 return emulators
182 def LaunchEmulator(avd_name, abi):
183 """Launch an existing emulator with name avd_name.
185 Args:
186 avd_name: name of existing emulator
187 abi: the emulator target platform
189 Returns:
190 emulator object.
192 logging.info('Specified emulator named avd_name=%s launched', avd_name)
193 emulator = Emulator(avd_name, abi)
194 emulator.Launch(kill_all_emulators=True)
195 emulator.ConfirmLaunch(True)
196 return emulator
199 class Emulator(object):
200 """Provides the methods to launch/shutdown the emulator.
202 The emulator has the android virtual device named 'avd_armeabi'.
204 The emulator could use any even TCP port between 5554 and 5584 for the
205 console communication, and this port will be part of the device name like
206 'emulator-5554'. Assume it is always True, as the device name is the id of
207 emulator managed in this class.
209 Attributes:
210 emulator: Path of Android's emulator tool.
211 popen: Popen object of the running emulator process.
212 device: Device name of this emulator.
215 # Signals we listen for to kill the emulator on
216 _SIGNALS = (signal.SIGINT, signal.SIGHUP)
218 # Time to wait for an emulator launch, in seconds. This includes
219 # the time to launch the emulator and a wait-for-device command.
220 _LAUNCH_TIMEOUT = 120
222 # Timeout interval of wait-for-device command before bouncing to a a
223 # process life check.
224 _WAITFORDEVICE_TIMEOUT = 5
226 # Time to wait for a "wait for boot complete" (property set on device).
227 _WAITFORBOOT_TIMEOUT = 300
229 def __init__(self, avd_name, abi):
230 """Init an Emulator.
232 Args:
233 avd_name: name of the AVD to create
234 abi: target platform for emulator being created, defaults to x86
236 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk')
237 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
238 self.android = os.path.join(android_sdk_root, 'tools', 'android')
239 self.popen = None
240 self.device_serial = None
241 self.abi = abi
242 self.avd_name = avd_name
244 @staticmethod
245 def _DeviceName():
246 """Return our device name."""
247 port = _GetAvailablePort()
248 return ('emulator-%d' % port, port)
250 def CreateAVD(self, api_level):
251 """Creates an AVD with the given name.
253 Args:
254 api_level: the api level of the image
256 Return avd_name.
259 if self.abi == 'arm':
260 abi_option = 'armeabi-v7a'
261 elif self.abi == 'mips':
262 abi_option = 'mips'
263 else:
264 abi_option = 'x86'
266 api_target = 'android-%s' % api_level
268 avd_command = [
269 self.android,
270 '--silent',
271 'create', 'avd',
272 '--name', self.avd_name,
273 '--abi', abi_option,
274 '--target', api_target,
275 '--sdcard', SDCARD_SIZE,
276 '--force',
278 avd_cmd_str = ' '.join(avd_command)
279 logging.info('Create AVD command: %s', avd_cmd_str)
280 avd_process = pexpect.spawn(avd_cmd_str)
282 # Instead of creating a custom profile, we overwrite config files.
283 avd_process.expect('Do you wish to create a custom hardware profile')
284 avd_process.sendline('no\n')
285 avd_process.expect('Created AVD \'%s\'' % self.avd_name)
287 # Replace current configuration with default Galaxy Nexus config.
288 avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd')
289 ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name)
290 new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name,
291 'config.ini')
293 # Remove config files with defaults to replace with Google's GN settings.
294 os.unlink(ini_file)
295 os.unlink(new_config_ini)
297 # Create new configuration files with Galaxy Nexus by Google settings.
298 with open(ini_file, 'w') as new_ini:
299 new_ini.write('avd.ini.encoding=ISO-8859-1\n')
300 new_ini.write('target=%s\n' % api_target)
301 new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name))
302 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name)
304 custom_config = CONFIG_TEMPLATE
305 replacements = CONFIG_REPLACEMENTS[self.abi]
306 for key in replacements:
307 custom_config = custom_config.replace(key, replacements[key])
308 custom_config = custom_config.replace('{api.level}', str(api_level))
310 with open(new_config_ini, 'w') as new_config_ini:
311 new_config_ini.write(custom_config)
313 return self.avd_name
316 def _DeleteAVD(self):
317 """Delete the AVD of this emulator."""
318 avd_command = [
319 self.android,
320 '--silent',
321 'delete',
322 'avd',
323 '--name', self.avd_name,
325 logging.info('Delete AVD command: %s', ' '.join(avd_command))
326 cmd_helper.RunCmd(avd_command)
329 def Launch(self, kill_all_emulators):
330 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
331 emulator is ready for use.
333 If fails, an exception will be raised.
335 if kill_all_emulators:
336 _KillAllEmulators() # just to be sure
337 self._AggressiveImageCleanup()
338 (self.device_serial, port) = self._DeviceName()
339 emulator_command = [
340 self.emulator,
341 # Speed up emulator launch by 40%. Really.
342 '-no-boot-anim',
343 # The default /data size is 64M.
344 # That's not enough for 8 unit test bundles and their data.
345 '-partition-size', '512',
346 # Use a familiar name and port.
347 '-avd', self.avd_name,
348 '-port', str(port),
349 # Wipe the data. We've seen cases where an emulator gets 'stuck' if we
350 # don't do this (every thousand runs or so).
351 '-wipe-data',
352 # Enable GPU by default.
353 '-gpu', 'on',
354 '-qemu', '-m', '1024',
356 if self.abi == 'x86':
357 emulator_command.extend([
358 # For x86 emulator --enable-kvm will fail early, avoiding accidental
359 # runs in a slow mode (i.e. without hardware virtualization support).
360 '--enable-kvm',
363 logging.info('Emulator launch command: %s', ' '.join(emulator_command))
364 self.popen = subprocess.Popen(args=emulator_command,
365 stderr=subprocess.STDOUT)
366 self._InstallKillHandler()
368 @staticmethod
369 def _AggressiveImageCleanup():
370 """Aggressive cleanup of emulator images.
372 Experimentally it looks like our current emulator use on the bot
373 leaves image files around in /tmp/android-$USER. If a "random"
374 name gets reused, we choke with a 'File exists' error.
375 TODO(jrg): is there a less hacky way to accomplish the same goal?
377 logging.info('Aggressive Image Cleanup')
378 emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
379 if not os.path.exists(emulator_imagedir):
380 return
381 for image in os.listdir(emulator_imagedir):
382 full_name = os.path.join(emulator_imagedir, image)
383 if 'emulator' in full_name:
384 logging.info('Deleting emulator image %s', full_name)
385 os.unlink(full_name)
387 def ConfirmLaunch(self, wait_for_boot=False):
388 """Confirm the emulator launched properly.
390 Loop on a wait-for-device with a very small timeout. On each
391 timeout, check the emulator process is still alive.
392 After confirming a wait-for-device can be successful, make sure
393 it returns the right answer.
395 seconds_waited = 0
396 number_of_waits = 2 # Make sure we can wfd twice
398 device = device_utils.DeviceUtils(self.device_serial)
399 while seconds_waited < self._LAUNCH_TIMEOUT:
400 try:
401 device.adb.WaitForDevice(
402 timeout=self._WAITFORDEVICE_TIMEOUT, retries=1)
403 number_of_waits -= 1
404 if not number_of_waits:
405 break
406 except device_errors.CommandTimeoutError:
407 seconds_waited += self._WAITFORDEVICE_TIMEOUT
408 device.adb.KillServer()
409 self.popen.poll()
410 if self.popen.returncode != None:
411 raise EmulatorLaunchException('EMULATOR DIED')
413 if seconds_waited >= self._LAUNCH_TIMEOUT:
414 raise EmulatorLaunchException('TIMEOUT with wait-for-device')
416 logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
417 if wait_for_boot:
418 # Now that we checked for obvious problems, wait for a boot complete.
419 # Waiting for the package manager is sometimes problematic.
420 device.WaitUntilFullyBooted(timeout=self._WAITFORBOOT_TIMEOUT)
422 def Shutdown(self):
423 """Shuts down the process started by launch."""
424 self._DeleteAVD()
425 if self.popen:
426 self.popen.poll()
427 if self.popen.returncode == None:
428 self.popen.kill()
429 self.popen = None
431 def _ShutdownOnSignal(self, _signum, _frame):
432 logging.critical('emulator _ShutdownOnSignal')
433 for sig in self._SIGNALS:
434 signal.signal(sig, signal.SIG_DFL)
435 self.Shutdown()
436 raise KeyboardInterrupt # print a stack
438 def _InstallKillHandler(self):
439 """Install a handler to kill the emulator when we exit unexpectedly."""
440 for sig in self._SIGNALS:
441 signal.signal(sig, self._ShutdownOnSignal)