blob: e2a5fea35df38506876135f41534dbbfb33a1221 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001# 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.
4
5"""Provides an interface to start and stop Android emulator.
6
7 Emulator: The class provides the methods to launch/shutdown the emulator with
8 the android virtual device named 'avd_armeabi' .
9"""
10
11import logging
12import os
13import signal
14import subprocess
15import time
16
17from devil.android import device_errors
18from devil.android import device_utils
19from devil.android.sdk import adb_wrapper
20from devil.utils import cmd_helper
21from pylib import constants
22from pylib import pexpect
23from pylib.utils import time_profile
24
25# Default sdcard size in the format of [amount][unit]
26DEFAULT_SDCARD_SIZE = '512M'
27# Default internal storage (MB) of emulator image
28DEFAULT_STORAGE_SIZE = '1024M'
29
30# Each emulator has 60 secs of wait time for launching
31_BOOT_WAIT_INTERVALS = 6
32_BOOT_WAIT_INTERVAL_TIME = 10
33
34# Path for avd files and avd dir
35_BASE_AVD_DIR = os.path.expanduser(os.path.join('~', '.android', 'avd'))
36_TOOLS_ANDROID_PATH = os.path.join(constants.ANDROID_SDK_ROOT,
37 'tools', 'android')
38
39# Template used to generate config.ini files for the emulator
40CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1
41hw.dPad=no
42hw.lcd.density=320
43sdcard.size={sdcard.size}
44hw.cpu.arch={hw.cpu.arch}
45hw.device.hash=-708107041
46hw.camera.back=none
47disk.dataPartition.size=800M
48hw.gpu.enabled={gpu}
49skin.path=720x1280
50skin.dynamic=yes
51hw.keyboard=yes
52hw.ramSize=1024
53hw.device.manufacturer=Google
54hw.sdCard=yes
55hw.mainKeys=no
56hw.accelerometer=yes
57skin.name=720x1280
58abi.type={abi.type}
59hw.trackBall=no
60hw.device.name=Galaxy Nexus
61hw.battery=yes
62hw.sensors.proximity=yes
63image.sysdir.1=system-images/android-{api.level}/default/{abi.type}/
64hw.sensors.orientation=yes
65hw.audioInput=yes
66hw.camera.front=none
67hw.gps=yes
68vm.heapSize=128
69{extras}"""
70
71CONFIG_REPLACEMENTS = {
72 'x86': {
73 '{hw.cpu.arch}': 'x86',
74 '{abi.type}': 'x86',
75 '{extras}': ''
76 },
77 'arm': {
78 '{hw.cpu.arch}': 'arm',
79 '{abi.type}': 'armeabi-v7a',
80 '{extras}': 'hw.cpu.model=cortex-a8\n'
81 },
82 'mips': {
83 '{hw.cpu.arch}': 'mips',
84 '{abi.type}': 'mips',
85 '{extras}': ''
86 }
87}
88
89class EmulatorLaunchException(Exception):
90 """Emulator failed to launch."""
91 pass
92
93def WaitForEmulatorLaunch(num):
94 """Wait for emulators to finish booting
95
96 Emulators on bots are launch with a separate background process, to avoid
97 running tests before the emulators are fully booted, this function waits for
98 a number of emulators to finish booting
99
100 Arg:
101 num: the amount of emulators to wait.
102 """
103 for _ in range(num*_BOOT_WAIT_INTERVALS):
104 emulators = [device_utils.DeviceUtils(a)
105 for a in adb_wrapper.AdbWrapper.Devices()
106 if a.is_emulator]
107 if len(emulators) >= num:
108 logging.info('All %d emulators launched', num)
109 return
110 logging.info(
111 'Waiting for %d emulators, %d of them already launched', num,
112 len(emulators))
113 time.sleep(_BOOT_WAIT_INTERVAL_TIME)
114 raise Exception("Expected %d emulators, %d launched within time limit" %
115 (num, len(emulators)))
116
117def KillAllEmulators():
118 """Kill all running emulators that look like ones we started.
119
120 There are odd 'sticky' cases where there can be no emulator process
121 running but a device slot is taken. A little bot trouble and we're out of
122 room forever.
123 """
124 logging.info('Killing all existing emulators and existing the program')
125 emulators = [device_utils.DeviceUtils(a)
126 for a in adb_wrapper.AdbWrapper.Devices()
127 if a.is_emulator]
128 if not emulators:
129 return
130 for e in emulators:
131 e.adb.Emu(['kill'])
132 logging.info('Emulator killing is async; give a few seconds for all to die.')
133 for _ in range(10):
134 if not any(a.is_emulator for a in adb_wrapper.AdbWrapper.Devices()):
135 return
136 time.sleep(1)
137
138
139def DeleteAllTempAVDs():
140 """Delete all temporary AVDs which are created for tests.
141
142 If the test exits abnormally and some temporary AVDs created when testing may
143 be left in the system. Clean these AVDs.
144 """
145 logging.info('Deleting all the avd files')
146 avds = device_utils.GetAVDs()
147 if not avds:
148 return
149 for avd_name in avds:
150 if 'run_tests_avd' in avd_name:
151 cmd = [_TOOLS_ANDROID_PATH, '-s', 'delete', 'avd', '--name', avd_name]
152 cmd_helper.RunCmd(cmd)
153 logging.info('Delete AVD %s', avd_name)
154
155
156class PortPool(object):
157 """Pool for emulator port starting position that changes over time."""
158 _port_min = 5554
159 _port_max = 5585
160 _port_current_index = 0
161
162 @classmethod
163 def port_range(cls):
164 """Return a range of valid ports for emulator use.
165
166 The port must be an even number between 5554 and 5584. Sometimes
167 a killed emulator "hangs on" to a port long enough to prevent
168 relaunch. This is especially true on slow machines (like a bot).
169 Cycling through a port start position helps make us resilient."""
170 ports = range(cls._port_min, cls._port_max, 2)
171 n = cls._port_current_index
172 cls._port_current_index = (n + 1) % len(ports)
173 return ports[n:] + ports[:n]
174
175
176def _GetAvailablePort():
177 """Returns an available TCP port for the console."""
178 used_ports = []
179 emulators = [device_utils.DeviceUtils(a)
180 for a in adb_wrapper.AdbWrapper.Devices()
181 if a.is_emulator]
182 for emulator in emulators:
183 used_ports.append(emulator.adb.GetDeviceSerial().split('-')[1])
184 for port in PortPool.port_range():
185 if str(port) not in used_ports:
186 return port
187
188
189def LaunchTempEmulators(emulator_count, abi, api_level, enable_kvm=False,
190 kill_and_launch=True, sdcard_size=DEFAULT_SDCARD_SIZE,
191 storage_size=DEFAULT_STORAGE_SIZE, wait_for_boot=True,
192 headless=False):
193 """Create and launch temporary emulators and wait for them to boot.
194
195 Args:
196 emulator_count: number of emulators to launch.
197 abi: the emulator target platform
198 api_level: the api level (e.g., 19 for Android v4.4 - KitKat release)
199 wait_for_boot: whether or not to wait for emulators to boot up
200 headless: running emulator with no ui
201
202 Returns:
203 List of emulators.
204 """
205 emulators = []
206 for n in xrange(emulator_count):
207 t = time_profile.TimeProfile('Emulator launch %d' % n)
208 # Creates a temporary AVD.
209 avd_name = 'run_tests_avd_%d' % n
210 logging.info('Emulator launch %d with avd_name=%s and api=%d',
211 n, avd_name, api_level)
212 emulator = Emulator(avd_name, abi, enable_kvm=enable_kvm,
213 sdcard_size=sdcard_size, storage_size=storage_size,
214 headless=headless)
215 emulator.CreateAVD(api_level)
216 emulator.Launch(kill_all_emulators=(n == 0 and kill_and_launch))
217 t.Stop()
218 emulators.append(emulator)
219 # Wait for all emulators to boot completed.
220 if wait_for_boot:
221 for emulator in emulators:
222 emulator.ConfirmLaunch(True)
223 logging.info('All emulators are fully booted')
224 return emulators
225
226
227def LaunchEmulator(avd_name, abi, kill_and_launch=True, enable_kvm=False,
228 sdcard_size=DEFAULT_SDCARD_SIZE,
229 storage_size=DEFAULT_STORAGE_SIZE, headless=False):
230 """Launch an existing emulator with name avd_name.
231
232 Args:
233 avd_name: name of existing emulator
234 abi: the emulator target platform
235 headless: running emulator with no ui
236
237 Returns:
238 emulator object.
239 """
240 logging.info('Specified emulator named avd_name=%s launched', avd_name)
241 emulator = Emulator(avd_name, abi, enable_kvm=enable_kvm,
242 sdcard_size=sdcard_size, storage_size=storage_size,
243 headless=headless)
244 emulator.Launch(kill_all_emulators=kill_and_launch)
245 emulator.ConfirmLaunch(True)
246 return emulator
247
248
249class Emulator(object):
250 """Provides the methods to launch/shutdown the emulator.
251
252 The emulator has the android virtual device named 'avd_armeabi'.
253
254 The emulator could use any even TCP port between 5554 and 5584 for the
255 console communication, and this port will be part of the device name like
256 'emulator-5554'. Assume it is always True, as the device name is the id of
257 emulator managed in this class.
258
259 Attributes:
260 emulator: Path of Android's emulator tool.
261 popen: Popen object of the running emulator process.
262 device: Device name of this emulator.
263 """
264
265 # Signals we listen for to kill the emulator on
266 _SIGNALS = (signal.SIGINT, signal.SIGHUP)
267
268 # Time to wait for an emulator launch, in seconds. This includes
269 # the time to launch the emulator and a wait-for-device command.
270 _LAUNCH_TIMEOUT = 120
271
272 # Timeout interval of wait-for-device command before bouncing to a a
273 # process life check.
274 _WAITFORDEVICE_TIMEOUT = 5
275
276 # Time to wait for a 'wait for boot complete' (property set on device).
277 _WAITFORBOOT_TIMEOUT = 300
278
279 def __init__(self, avd_name, abi, enable_kvm=False,
280 sdcard_size=DEFAULT_SDCARD_SIZE,
281 storage_size=DEFAULT_STORAGE_SIZE, headless=False):
282 """Init an Emulator.
283
284 Args:
285 avd_name: name of the AVD to create
286 abi: target platform for emulator being created, defaults to x86
287 """
288 android_sdk_root = constants.ANDROID_SDK_ROOT
289 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
290 self.android = _TOOLS_ANDROID_PATH
291 self.popen = None
292 self.device_serial = None
293 self.abi = abi
294 self.avd_name = avd_name
295 self.sdcard_size = sdcard_size
296 self.storage_size = storage_size
297 self.enable_kvm = enable_kvm
298 self.headless = headless
299
300 @staticmethod
301 def _DeviceName():
302 """Return our device name."""
303 port = _GetAvailablePort()
304 return ('emulator-%d' % port, port)
305
306 def CreateAVD(self, api_level):
307 """Creates an AVD with the given name.
308
309 Args:
310 api_level: the api level of the image
311
312 Return avd_name.
313 """
314
315 if self.abi == 'arm':
316 abi_option = 'armeabi-v7a'
317 elif self.abi == 'mips':
318 abi_option = 'mips'
319 else:
320 abi_option = 'x86'
321
322 api_target = 'android-%s' % api_level
323
324 avd_command = [
325 self.android,
326 '--silent',
327 'create', 'avd',
328 '--name', self.avd_name,
329 '--abi', abi_option,
330 '--target', api_target,
331 '--sdcard', self.sdcard_size,
332 '--force',
333 ]
334 avd_cmd_str = ' '.join(avd_command)
335 logging.info('Create AVD command: %s', avd_cmd_str)
336 avd_process = pexpect.spawn(avd_cmd_str)
337
338 # Instead of creating a custom profile, we overwrite config files.
339 avd_process.expect('Do you wish to create a custom hardware profile')
340 avd_process.sendline('no\n')
341 avd_process.expect('Created AVD \'%s\'' % self.avd_name)
342
343 # Replace current configuration with default Galaxy Nexus config.
344 ini_file = os.path.join(_BASE_AVD_DIR, '%s.ini' % self.avd_name)
345 new_config_ini = os.path.join(_BASE_AVD_DIR, '%s.avd' % self.avd_name,
346 'config.ini')
347
348 # Remove config files with defaults to replace with Google's GN settings.
349 os.unlink(ini_file)
350 os.unlink(new_config_ini)
351
352 # Create new configuration files with Galaxy Nexus by Google settings.
353 with open(ini_file, 'w') as new_ini:
354 new_ini.write('avd.ini.encoding=ISO-8859-1\n')
355 new_ini.write('target=%s\n' % api_target)
356 new_ini.write('path=%s/%s.avd\n' % (_BASE_AVD_DIR, self.avd_name))
357 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name)
358
359 custom_config = CONFIG_TEMPLATE
360 replacements = CONFIG_REPLACEMENTS[self.abi]
361 for key in replacements:
362 custom_config = custom_config.replace(key, replacements[key])
363 custom_config = custom_config.replace('{api.level}', str(api_level))
364 custom_config = custom_config.replace('{sdcard.size}', self.sdcard_size)
365 custom_config.replace('{gpu}', 'no' if self.headless else 'yes')
366
367 with open(new_config_ini, 'w') as new_config_ini:
368 new_config_ini.write(custom_config)
369
370 return self.avd_name
371
372
373 def _DeleteAVD(self):
374 """Delete the AVD of this emulator."""
375 avd_command = [
376 self.android,
377 '--silent',
378 'delete',
379 'avd',
380 '--name', self.avd_name,
381 ]
382 logging.info('Delete AVD command: %s', ' '.join(avd_command))
383 cmd_helper.RunCmd(avd_command)
384
385 def ResizeAndWipeAvd(self, storage_size):
386 """Wipes old AVD and creates new AVD of size |storage_size|.
387
388 This serves as a work around for '-partition-size' and '-wipe-data'
389 """
390 userdata_img = os.path.join(_BASE_AVD_DIR, '%s.avd' % self.avd_name,
391 'userdata.img')
392 userdata_qemu_img = os.path.join(_BASE_AVD_DIR, '%s.avd' % self.avd_name,
393 'userdata-qemu.img')
394 resize_cmd = ['resize2fs', userdata_img, '%s' % storage_size]
395 logging.info('Resizing userdata.img to ideal size')
396 cmd_helper.RunCmd(resize_cmd)
397 wipe_cmd = ['cp', userdata_img, userdata_qemu_img]
398 logging.info('Replacing userdata-qemu.img with the new userdata.img')
399 cmd_helper.RunCmd(wipe_cmd)
400
401 def Launch(self, kill_all_emulators):
402 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
403 emulator is ready for use.
404
405 If fails, an exception will be raised.
406 """
407 if kill_all_emulators:
408 KillAllEmulators() # just to be sure
409 self._AggressiveImageCleanup()
410 (self.device_serial, port) = self._DeviceName()
411 self.ResizeAndWipeAvd(storage_size=self.storage_size)
412 emulator_command = [
413 self.emulator,
414 # Speed up emulator launch by 40%. Really.
415 '-no-boot-anim',
416 ]
417 if self.headless:
418 emulator_command.extend([
419 '-no-skin',
420 '-no-audio',
421 '-no-window'
422 ])
423 else:
424 emulator_command.extend([
425 '-gpu', 'on'
426 ])
427 emulator_command.extend([
428 # Use a familiar name and port.
429 '-avd', self.avd_name,
430 '-port', str(port),
431 # all the argument after qemu are sub arguments for qemu
432 '-qemu', '-m', '1024',
433 ])
434 if self.abi == 'x86' and self.enable_kvm:
435 emulator_command.extend([
436 # For x86 emulator --enable-kvm will fail early, avoiding accidental
437 # runs in a slow mode (i.e. without hardware virtualization support).
438 '--enable-kvm',
439 ])
440
441 logging.info('Emulator launch command: %s', ' '.join(emulator_command))
442 self.popen = subprocess.Popen(args=emulator_command,
443 stderr=subprocess.STDOUT)
444 self._InstallKillHandler()
445
446 @staticmethod
447 def _AggressiveImageCleanup():
448 """Aggressive cleanup of emulator images.
449
450 Experimentally it looks like our current emulator use on the bot
451 leaves image files around in /tmp/android-$USER. If a "random"
452 name gets reused, we choke with a 'File exists' error.
453 TODO(jrg): is there a less hacky way to accomplish the same goal?
454 """
455 logging.info('Aggressive Image Cleanup')
456 emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
457 if not os.path.exists(emulator_imagedir):
458 return
459 for image in os.listdir(emulator_imagedir):
460 full_name = os.path.join(emulator_imagedir, image)
461 if 'emulator' in full_name:
462 logging.info('Deleting emulator image %s', full_name)
463 os.unlink(full_name)
464
465 def ConfirmLaunch(self, wait_for_boot=False):
466 """Confirm the emulator launched properly.
467
468 Loop on a wait-for-device with a very small timeout. On each
469 timeout, check the emulator process is still alive.
470 After confirming a wait-for-device can be successful, make sure
471 it returns the right answer.
472 """
473 seconds_waited = 0
474 number_of_waits = 2 # Make sure we can wfd twice
475
476 device = device_utils.DeviceUtils(self.device_serial)
477 while seconds_waited < self._LAUNCH_TIMEOUT:
478 try:
479 device.adb.WaitForDevice(
480 timeout=self._WAITFORDEVICE_TIMEOUT, retries=1)
481 number_of_waits -= 1
482 if not number_of_waits:
483 break
484 except device_errors.CommandTimeoutError:
485 seconds_waited += self._WAITFORDEVICE_TIMEOUT
486 device.adb.KillServer()
487 self.popen.poll()
488 if self.popen.returncode != None:
489 raise EmulatorLaunchException('EMULATOR DIED')
490
491 if seconds_waited >= self._LAUNCH_TIMEOUT:
492 raise EmulatorLaunchException('TIMEOUT with wait-for-device')
493
494 logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
495 if wait_for_boot:
496 # Now that we checked for obvious problems, wait for a boot complete.
497 # Waiting for the package manager is sometimes problematic.
498 device.WaitUntilFullyBooted(timeout=self._WAITFORBOOT_TIMEOUT)
499 logging.info('%s is now fully booted', self.avd_name)
500
501 def Shutdown(self):
502 """Shuts down the process started by launch."""
503 self._DeleteAVD()
504 if self.popen:
505 self.popen.poll()
506 if self.popen.returncode == None:
507 self.popen.kill()
508 self.popen = None
509
510 def _ShutdownOnSignal(self, _signum, _frame):
511 logging.critical('emulator _ShutdownOnSignal')
512 for sig in self._SIGNALS:
513 signal.signal(sig, signal.SIG_DFL)
514 self.Shutdown()
515 raise KeyboardInterrupt # print a stack
516
517 def _InstallKillHandler(self):
518 """Install a handler to kill the emulator when we exit unexpectedly."""
519 for sig in self._SIGNALS:
520 signal.signal(sig, self._ShutdownOnSignal)