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' .
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
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
= device_utils
.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 LaunchTempEmulators(emulator_count
, abi
, api_level
, wait_for_boot
=True):
153 """Create and launch temporary 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
)
172 emulator
.CreateAVD(api_level
)
173 emulator
.Launch(kill_all_emulators
=n
== 0)
175 emulators
.append(emulator
)
176 # Wait for all emulators to boot completed.
178 for emulator
in emulators
:
179 emulator
.ConfirmLaunch(True)
183 def LaunchEmulator(avd_name
, abi
):
184 """Launch an existing emulator with name avd_name.
187 avd_name: name of existing emulator
188 abi: the emulator target platform
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)
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.
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
):
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')
241 self
.device_serial
= None
243 self
.avd_name
= avd_name
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.
255 api_level: the api level of the image
260 if self
.abi
== 'arm':
261 abi_option
= 'armeabi-v7a'
262 elif self
.abi
== 'mips':
267 api_target
= 'android-%s' % api_level
273 '--name', self
.avd_name
,
275 '--target', api_target
,
276 '--sdcard', SDCARD_SIZE
,
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
,
294 # Remove config files with defaults to replace with Google's GN settings.
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
)
317 def _DeleteAVD(self
):
318 """Delete the AVD of this emulator."""
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
()
342 # Speed up emulator launch by 40%. Really.
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
,
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).
353 # Enable GPU by default.
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).
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
()
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
):
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
)
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.
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
:
402 device
.adb
.WaitForDevice(
403 timeout
=self
._WAITFORDEVICE
_TIMEOUT
, retries
=1)
405 if not number_of_waits
:
407 except device_errors
.CommandTimeoutError
:
408 seconds_waited
+= self
._WAITFORDEVICE
_TIMEOUT
409 device
.adb
.KillServer()
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
)
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
)
424 """Shuts down the process started by launch."""
428 if self
.popen
.returncode
== 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
)
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
)