[Android] Lint pylib/utils.
[chromium-blink-merge.git] / build / android / pylib / utils / emulator.py
blob462d569ff2b4c91555adf9f62e9bdae326778623
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 Assumes system environment ANDROID_NDK_ROOT has been set.
9 Emulator: The class provides the methods to launch/shutdown the emulator with
10 the android virtual device named 'avd_armeabi' .
11 """
13 import logging
14 import os
15 import signal
16 import subprocess
17 import time
19 # TODO(craigdh): Move these pylib dependencies to pylib/utils/.
20 from pylib import android_commands
21 from pylib import cmd_helper
22 from pylib import constants
23 from pylib import pexpect
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 = android_commands.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 LaunchEmulators(emulator_count, abi, api_level, wait_for_boot=True):
153 """Launch multiple 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, 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 class Emulator(object):
183 """Provides the methods to launch/shutdown the emulator.
185 The emulator has the android virtual device named 'avd_armeabi'.
187 The emulator could use any even TCP port between 5554 and 5584 for the
188 console communication, and this port will be part of the device name like
189 'emulator-5554'. Assume it is always True, as the device name is the id of
190 emulator managed in this class.
192 Attributes:
193 emulator: Path of Android's emulator tool.
194 popen: Popen object of the running emulator process.
195 device: Device name of this emulator.
198 # Signals we listen for to kill the emulator on
199 _SIGNALS = (signal.SIGINT, signal.SIGHUP)
201 # Time to wait for an emulator launch, in seconds. This includes
202 # the time to launch the emulator and a wait-for-device command.
203 _LAUNCH_TIMEOUT = 120
205 # Timeout interval of wait-for-device command before bouncing to a a
206 # process life check.
207 _WAITFORDEVICE_TIMEOUT = 5
209 # Time to wait for a "wait for boot complete" (property set on device).
210 _WAITFORBOOT_TIMEOUT = 300
212 def __init__(self, avd_name, abi, api_level):
213 """Init an Emulator.
215 Args:
216 avd_name: name of the AVD to create
217 abi: target platform for emulator being created, defaults to x86
218 api_level: the api level of the image
220 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk')
221 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
222 self.android = os.path.join(android_sdk_root, 'tools', 'android')
223 self.popen = None
224 self.device = None
225 self.abi = abi
226 self.avd_name = avd_name
227 self.api_level = api_level
228 self._CreateAVD()
230 @staticmethod
231 def _DeviceName():
232 """Return our device name."""
233 port = _GetAvailablePort()
234 return ('emulator-%d' % port, port)
236 def _CreateAVD(self):
237 """Creates an AVD with the given name.
239 Return avd_name.
242 if self.abi == 'arm':
243 abi_option = 'armeabi-v7a'
244 elif self.abi == 'mips':
245 abi_option = 'mips'
246 else:
247 abi_option = 'x86'
249 api_target = 'android-%s' % self.api_level
251 avd_command = [
252 self.android,
253 '--silent',
254 'create', 'avd',
255 '--name', self.avd_name,
256 '--abi', abi_option,
257 '--target', api_target,
258 '--sdcard', SDCARD_SIZE,
259 '--force',
261 avd_cmd_str = ' '.join(avd_command)
262 logging.info('Create AVD command: %s', avd_cmd_str)
263 avd_process = pexpect.spawn(avd_cmd_str)
265 # Instead of creating a custom profile, we overwrite config files.
266 avd_process.expect('Do you wish to create a custom hardware profile')
267 avd_process.sendline('no\n')
268 avd_process.expect('Created AVD \'%s\'' % self.avd_name)
270 # Replace current configuration with default Galaxy Nexus config.
271 avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd')
272 ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name)
273 new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name,
274 'config.ini')
276 # Remove config files with defaults to replace with Google's GN settings.
277 os.unlink(ini_file)
278 os.unlink(new_config_ini)
280 # Create new configuration files with Galaxy Nexus by Google settings.
281 with open(ini_file, 'w') as new_ini:
282 new_ini.write('avd.ini.encoding=ISO-8859-1\n')
283 new_ini.write('target=%s\n' % api_target)
284 new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name))
285 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name)
287 custom_config = CONFIG_TEMPLATE
288 replacements = CONFIG_REPLACEMENTS[self.abi]
289 for key in replacements:
290 custom_config = custom_config.replace(key, replacements[key])
291 custom_config = custom_config.replace('{api.level}', str(self.api_level))
293 with open(new_config_ini, 'w') as new_config_ini:
294 new_config_ini.write(custom_config)
296 return self.avd_name
299 def _DeleteAVD(self):
300 """Delete the AVD of this emulator."""
301 avd_command = [
302 self.android,
303 '--silent',
304 'delete',
305 'avd',
306 '--name', self.avd_name,
308 logging.info('Delete AVD command: %s', ' '.join(avd_command))
309 cmd_helper.RunCmd(avd_command)
312 def Launch(self, kill_all_emulators):
313 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
314 emulator is ready for use.
316 If fails, an exception will be raised.
318 if kill_all_emulators:
319 _KillAllEmulators() # just to be sure
320 self._AggressiveImageCleanup()
321 (self.device, port) = self._DeviceName()
322 emulator_command = [
323 self.emulator,
324 # Speed up emulator launch by 40%. Really.
325 '-no-boot-anim',
326 # The default /data size is 64M.
327 # That's not enough for 8 unit test bundles and their data.
328 '-partition-size', '512',
329 # Use a familiar name and port.
330 '-avd', self.avd_name,
331 '-port', str(port),
332 # Wipe the data. We've seen cases where an emulator gets 'stuck' if we
333 # don't do this (every thousand runs or so).
334 '-wipe-data',
335 # Enable GPU by default.
336 '-gpu', 'on',
337 '-qemu', '-m', '1024',
339 if self.abi == 'x86':
340 emulator_command.extend([
341 # For x86 emulator --enable-kvm will fail early, avoiding accidental
342 # runs in a slow mode (i.e. without hardware virtualization support).
343 '--enable-kvm',
346 logging.info('Emulator launch command: %s', ' '.join(emulator_command))
347 self.popen = subprocess.Popen(args=emulator_command,
348 stderr=subprocess.STDOUT)
349 self._InstallKillHandler()
351 @staticmethod
352 def _AggressiveImageCleanup():
353 """Aggressive cleanup of emulator images.
355 Experimentally it looks like our current emulator use on the bot
356 leaves image files around in /tmp/android-$USER. If a "random"
357 name gets reused, we choke with a 'File exists' error.
358 TODO(jrg): is there a less hacky way to accomplish the same goal?
360 logging.info('Aggressive Image Cleanup')
361 emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
362 if not os.path.exists(emulator_imagedir):
363 return
364 for image in os.listdir(emulator_imagedir):
365 full_name = os.path.join(emulator_imagedir, image)
366 if 'emulator' in full_name:
367 logging.info('Deleting emulator image %s', full_name)
368 os.unlink(full_name)
370 def ConfirmLaunch(self, wait_for_boot=False):
371 """Confirm the emulator launched properly.
373 Loop on a wait-for-device with a very small timeout. On each
374 timeout, check the emulator process is still alive.
375 After confirming a wait-for-device can be successful, make sure
376 it returns the right answer.
378 seconds_waited = 0
379 number_of_waits = 2 # Make sure we can wfd twice
380 adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device')
381 while seconds_waited < self._LAUNCH_TIMEOUT:
382 try:
383 run_command.RunCommand(adb_cmd,
384 timeout_time=self._WAITFORDEVICE_TIMEOUT,
385 retry_count=1)
386 number_of_waits -= 1
387 if not number_of_waits:
388 break
389 except errors.WaitForResponseTimedOutError:
390 seconds_waited += self._WAITFORDEVICE_TIMEOUT
391 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server')
392 run_command.RunCommand(adb_cmd)
393 self.popen.poll()
394 if self.popen.returncode != None:
395 raise EmulatorLaunchException('EMULATOR DIED')
396 if seconds_waited >= self._LAUNCH_TIMEOUT:
397 raise EmulatorLaunchException('TIMEOUT with wait-for-device')
398 logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
399 if wait_for_boot:
400 # Now that we checked for obvious problems, wait for a boot complete.
401 # Waiting for the package manager is sometimes problematic.
402 a = android_commands.AndroidCommands(self.device)
403 a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT)
405 def Shutdown(self):
406 """Shuts down the process started by launch."""
407 self._DeleteAVD()
408 if self.popen:
409 self.popen.poll()
410 if self.popen.returncode == None:
411 self.popen.kill()
412 self.popen = None
414 def _ShutdownOnSignal(self, _signum, _frame):
415 logging.critical('emulator _ShutdownOnSignal')
416 for sig in self._SIGNALS:
417 signal.signal(sig, signal.SIG_DFL)
418 self.Shutdown()
419 raise KeyboardInterrupt # print a stack
421 def _InstallKillHandler(self):
422 """Install a handler to kill the emulator when we exit unexpectedly."""
423 for sig in self._SIGNALS:
424 signal.signal(sig, self._ShutdownOnSignal)