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 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
31 # Template used to generate config.ini files for the emulator
32 CONFIG_TEMPLATE
= """avd.ini.encoding=ISO-8859-1
36 hw.cpu.arch={hw.cpu.arch}
37 hw.device.hash=-708107041
39 disk.dataPartition.size=800M
45 hw.device.manufacturer=Google
52 hw.device.name=Galaxy Nexus
54 hw.sensors.proximity=yes
55 image.sysdir.1=system-images/android-{api.level}/{abi.type}/
56 hw.sensors.orientation=yes
63 CONFIG_REPLACEMENTS
= {
65 '{hw.cpu.arch}': 'x86',
70 '{hw.cpu.arch}': 'arm',
71 '{abi.type}': 'armeabi-v7a',
72 '{extras}': 'hw.cpu.model=cortex-a8\n'
75 '{hw.cpu.arch}': 'mips',
81 class EmulatorLaunchException(Exception):
82 """Emulator failed to launch."""
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.
92 emulators
= [d
for d
in device_utils
.HealthyDevices() if d
.adb
.is_emulator
]
97 logging
.info('Emulator killing is async; give a few seconds for all to die.')
99 if not any(d
.adb
.is_emulator
for d
in device_utils
.HealthyDevices()):
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()
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."""
124 _port_current_index
= 0
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."""
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
:
151 def LaunchTempEmulators(emulator_count
, abi
, api_level
, wait_for_boot
=True):
152 """Create and launch temporary emulators and wait for them to boot.
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
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)
174 emulators
.append(emulator
)
175 # Wait for all emulators to boot completed.
177 for emulator
in emulators
:
178 emulator
.ConfirmLaunch(True)
182 def LaunchEmulator(avd_name
, abi
):
183 """Launch an existing emulator with name avd_name.
186 avd_name: name of existing emulator
187 abi: the emulator target platform
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)
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.
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
):
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')
240 self
.device_serial
= None
242 self
.avd_name
= avd_name
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.
254 api_level: the api level of the image
259 if self
.abi
== 'arm':
260 abi_option
= 'armeabi-v7a'
261 elif self
.abi
== 'mips':
266 api_target
= 'android-%s' % api_level
272 '--name', self
.avd_name
,
274 '--target', api_target
,
275 '--sdcard', SDCARD_SIZE
,
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
,
293 # Remove config files with defaults to replace with Google's GN settings.
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
)
316 def _DeleteAVD(self
):
317 """Delete the AVD of this emulator."""
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
()
341 # Speed up emulator launch by 40%. Really.
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
,
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).
352 # Enable GPU by default.
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).
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
()
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
):
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
)
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.
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
:
401 device
.adb
.WaitForDevice(
402 timeout
=self
._WAITFORDEVICE
_TIMEOUT
, retries
=1)
404 if not number_of_waits
:
406 except device_errors
.CommandTimeoutError
:
407 seconds_waited
+= self
._WAITFORDEVICE
_TIMEOUT
408 device
.adb
.KillServer()
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
)
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
)
423 """Shuts down the process started by launch."""
427 if self
.popen
.returncode
== 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
)
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
)