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