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' .
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
32 # Template used to generate config.ini files for the emulator
33 CONFIG_TEMPLATE
= """avd.ini.encoding=ISO-8859-1
37 hw.cpu.arch={hw.cpu.arch}
38 hw.device.hash=-708107041
40 disk.dataPartition.size=800M
46 hw.device.manufacturer=Google
53 hw.device.name=Galaxy Nexus
55 hw.sensors.proximity=yes
56 image.sysdir.1=system-images/android-{api.level}/{abi.type}/
57 hw.sensors.orientation=yes
64 CONFIG_REPLACEMENTS
= {
66 '{hw.cpu.arch}': 'x86',
71 '{hw.cpu.arch}': 'arm',
72 '{abi.type}': 'armeabi-v7a',
73 '{extras}': 'hw.cpu.model=cortex-a8\n'
76 '{hw.cpu.arch}': 'mips',
82 class EmulatorLaunchException(Exception):
83 """Emulator failed to launch."""
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.
93 emulators
= android_commands
.GetAttachedDevices(hardware
=False)
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.')
100 if not android_commands
.GetAttachedDevices(hardware
=False):
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()
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."""
125 _port_current_index
= 0
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."""
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
:
152 def LaunchEmulators(emulator_count
, abi
, api_level
, wait_for_boot
=True):
153 """Launch multiple emulators and wait for them to boot.
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
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)
174 emulators
.append(emulator
)
175 # Wait for all emulators to boot completed.
177 for emulator
in emulators
:
178 emulator
.ConfirmLaunch(True)
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.
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
):
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')
226 self
.avd_name
= avd_name
227 self
.api_level
= api_level
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.
242 if self
.abi
== 'arm':
243 abi_option
= 'armeabi-v7a'
244 elif self
.abi
== 'mips':
249 api_target
= 'android-%s' % self
.api_level
255 '--name', self
.avd_name
,
257 '--target', api_target
,
258 '--sdcard', SDCARD_SIZE
,
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
,
276 # Remove config files with defaults to replace with Google's GN settings.
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
)
299 def _DeleteAVD(self
):
300 """Delete the AVD of this emulator."""
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
()
324 # Speed up emulator launch by 40%. Really.
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
,
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).
335 # Enable GPU by default.
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).
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
()
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
):
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
)
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.
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
:
383 run_command
.RunCommand(adb_cmd
,
384 timeout_time
=self
._WAITFORDEVICE
_TIMEOUT
,
387 if not number_of_waits
:
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
)
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
)
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
)
406 """Shuts down the process started by launch."""
410 if self
.popen
.returncode
== 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
)
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
)