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. 4 5"""Provides an interface to start and stop Android emulator. 6 7Assumes system environment ANDROID_NDK_ROOT has been set. 8 9 Emulator: The class provides the methods to launch/shutdown the emulator with 10 the android virtual device named 'avd_armeabi' . 11""" 12 13import logging 14import os 15import shutil 16import signal 17import subprocess 18import sys 19import time 20 21import time_profile 22# TODO(craigdh): Move these pylib dependencies to pylib/utils/. 23from pylib import android_commands 24from pylib import cmd_helper 25from pylib import constants 26from pylib import pexpect 27 28import errors 29import run_command 30 31# SD card size 32SDCARD_SIZE = '512M' 33 34# Template used to generate config.ini files for the emulator 35CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1 36hw.dPad=no 37hw.lcd.density=320 38sdcard.size=512M 39hw.cpu.arch={hw.cpu.arch} 40hw.device.hash=-708107041 41hw.camera.back=none 42disk.dataPartition.size=800M 43hw.gpu.enabled=yes 44skin.path=720x1280 45skin.dynamic=yes 46hw.keyboard=yes 47hw.ramSize=1024 48hw.device.manufacturer=Google 49hw.sdCard=yes 50hw.mainKeys=no 51hw.accelerometer=yes 52skin.name=720x1280 53abi.type={abi.type} 54hw.trackBall=no 55hw.device.name=Galaxy Nexus 56hw.battery=yes 57hw.sensors.proximity=yes 58image.sysdir.1=system-images/android-{api.level}/{abi.type}/ 59hw.sensors.orientation=yes 60hw.audioInput=yes 61hw.camera.front=none 62hw.gps=yes 63vm.heapSize=128 64{extras}""" 65 66CONFIG_REPLACEMENTS = { 67 'x86': { 68 '{hw.cpu.arch}': 'x86', 69 '{abi.type}': 'x86', 70 '{extras}': '' 71 }, 72 'arm': { 73 '{hw.cpu.arch}': 'arm', 74 '{abi.type}': 'armeabi-v7a', 75 '{extras}': 'hw.cpu.model=cortex-a8\n' 76 }, 77 'mips': { 78 '{hw.cpu.arch}': 'mips', 79 '{abi.type}': 'mips', 80 '{extras}': '' 81 } 82} 83 84class EmulatorLaunchException(Exception): 85 """Emulator failed to launch.""" 86 pass 87 88def _KillAllEmulators(): 89 """Kill all running emulators that look like ones we started. 90 91 There are odd 'sticky' cases where there can be no emulator process 92 running but a device slot is taken. A little bot trouble and and 93 we're out of room forever. 94 """ 95 emulators = android_commands.GetAttachedDevices(hardware=False) 96 if not emulators: 97 return 98 for emu_name in emulators: 99 cmd_helper.RunCmd(['adb', '-s', emu_name, 'emu', 'kill']) 100 logging.info('Emulator killing is async; give a few seconds for all to die.') 101 for i in range(5): 102 if not android_commands.GetAttachedDevices(hardware=False): 103 return 104 time.sleep(1) 105 106 107def DeleteAllTempAVDs(): 108 """Delete all temporary AVDs which are created for tests. 109 110 If the test exits abnormally and some temporary AVDs created when testing may 111 be left in the system. Clean these AVDs. 112 """ 113 avds = android_commands.GetAVDs() 114 if not avds: 115 return 116 for avd_name in avds: 117 if 'run_tests_avd' in avd_name: 118 cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name] 119 cmd_helper.RunCmd(cmd) 120 logging.info('Delete AVD %s' % avd_name) 121 122 123class PortPool(object): 124 """Pool for emulator port starting position that changes over time.""" 125 _port_min = 5554 126 _port_max = 5585 127 _port_current_index = 0 128 129 @classmethod 130 def port_range(cls): 131 """Return a range of valid ports for emulator use. 132 133 The port must be an even number between 5554 and 5584. Sometimes 134 a killed emulator "hangs on" to a port long enough to prevent 135 relaunch. This is especially true on slow machines (like a bot). 136 Cycling through a port start position helps make us resilient.""" 137 ports = range(cls._port_min, cls._port_max, 2) 138 n = cls._port_current_index 139 cls._port_current_index = (n + 1) % len(ports) 140 return ports[n:] + ports[:n] 141 142 143def _GetAvailablePort(): 144 """Returns an available TCP port for the console.""" 145 used_ports = [] 146 emulators = android_commands.GetAttachedDevices(hardware=False) 147 for emulator in emulators: 148 used_ports.append(emulator.split('-')[1]) 149 for port in PortPool.port_range(): 150 if str(port) not in used_ports: 151 return port 152 153 154def LaunchEmulators(emulator_count, abi, api_level, wait_for_boot=True): 155 """Launch multiple emulators and wait for them to boot. 156 157 Args: 158 emulator_count: number of emulators to launch. 159 abi: the emulator target platform 160 api_level: the api level (e.g., 19 for Android v4.4 - KitKat release) 161 wait_for_boot: whether or not to wait for emulators to boot up 162 163 Returns: 164 List of emulators. 165 """ 166 emulators = [] 167 for n in xrange(emulator_count): 168 t = time_profile.TimeProfile('Emulator launch %d' % n) 169 # Creates a temporary AVD. 170 avd_name = 'run_tests_avd_%d' % n 171 logging.info('Emulator launch %d with avd_name=%s and api=%d', 172 n, avd_name, api_level) 173 emulator = Emulator(avd_name, abi, api_level) 174 emulator.Launch(kill_all_emulators=n == 0) 175 t.Stop() 176 emulators.append(emulator) 177 # Wait for all emulators to boot completed. 178 if wait_for_boot: 179 for emulator in emulators: 180 emulator.ConfirmLaunch(True) 181 return emulators 182 183 184class Emulator(object): 185 """Provides the methods to launch/shutdown the emulator. 186 187 The emulator has the android virtual device named 'avd_armeabi'. 188 189 The emulator could use any even TCP port between 5554 and 5584 for the 190 console communication, and this port will be part of the device name like 191 'emulator-5554'. Assume it is always True, as the device name is the id of 192 emulator managed in this class. 193 194 Attributes: 195 emulator: Path of Android's emulator tool. 196 popen: Popen object of the running emulator process. 197 device: Device name of this emulator. 198 """ 199 200 # Signals we listen for to kill the emulator on 201 _SIGNALS = (signal.SIGINT, signal.SIGHUP) 202 203 # Time to wait for an emulator launch, in seconds. This includes 204 # the time to launch the emulator and a wait-for-device command. 205 _LAUNCH_TIMEOUT = 120 206 207 # Timeout interval of wait-for-device command before bouncing to a a 208 # process life check. 209 _WAITFORDEVICE_TIMEOUT = 5 210 211 # Time to wait for a "wait for boot complete" (property set on device). 212 _WAITFORBOOT_TIMEOUT = 300 213 214 def __init__(self, avd_name, abi, api_level): 215 """Init an Emulator. 216 217 Args: 218 avd_name: name of the AVD to create 219 abi: target platform for emulator being created, defaults to x86 220 api_level: the api level of the image 221 """ 222 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk') 223 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') 224 self.android = os.path.join(android_sdk_root, 'tools', 'android') 225 self.popen = None 226 self.device = None 227 self.abi = abi 228 self.avd_name = avd_name 229 self.api_level = api_level 230 self._CreateAVD() 231 232 def _DeviceName(self): 233 """Return our device name.""" 234 port = _GetAvailablePort() 235 return ('emulator-%d' % port, port) 236 237 def _CreateAVD(self): 238 """Creates an AVD with the given name. 239 240 Return avd_name. 241 """ 242 243 if self.abi == 'arm': 244 abi_option = 'armeabi-v7a' 245 elif self.abi == 'mips': 246 abi_option = 'mips' 247 else: 248 abi_option = 'x86' 249 250 api_target = 'android-%s' % self.api_level 251 252 avd_command = [ 253 self.android, 254 '--silent', 255 'create', 'avd', 256 '--name', self.avd_name, 257 '--abi', abi_option, 258 '--target', api_target, 259 '--sdcard', SDCARD_SIZE, 260 '--force', 261 ] 262 avd_cmd_str = ' '.join(avd_command) 263 logging.info('Create AVD command: %s', avd_cmd_str) 264 avd_process = pexpect.spawn(avd_cmd_str) 265 266 # Instead of creating a custom profile, we overwrite config files. 267 avd_process.expect('Do you wish to create a custom hardware profile') 268 avd_process.sendline('no\n') 269 avd_process.expect('Created AVD \'%s\'' % self.avd_name) 270 271 # Replace current configuration with default Galaxy Nexus config. 272 avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd') 273 ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name) 274 new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name, 275 'config.ini') 276 277 # Remove config files with defaults to replace with Google's GN settings. 278 os.unlink(ini_file) 279 os.unlink(new_config_ini) 280 281 # Create new configuration files with Galaxy Nexus by Google settings. 282 with open(ini_file, 'w') as new_ini: 283 new_ini.write('avd.ini.encoding=ISO-8859-1\n') 284 new_ini.write('target=%s\n' % api_target) 285 new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name)) 286 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name) 287 288 custom_config = CONFIG_TEMPLATE 289 replacements = CONFIG_REPLACEMENTS[self.abi] 290 for key in replacements: 291 custom_config = custom_config.replace(key, replacements[key]) 292 custom_config = custom_config.replace('{api.level}', str(self.api_level)) 293 294 with open(new_config_ini, 'w') as new_config_ini: 295 new_config_ini.write(custom_config) 296 297 return self.avd_name 298 299 300 def _DeleteAVD(self): 301 """Delete the AVD of this emulator.""" 302 avd_command = [ 303 self.android, 304 '--silent', 305 'delete', 306 'avd', 307 '--name', self.avd_name, 308 ] 309 logging.info('Delete AVD command: %s', ' '.join(avd_command)) 310 cmd_helper.RunCmd(avd_command) 311 312 313 def Launch(self, kill_all_emulators): 314 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the 315 emulator is ready for use. 316 317 If fails, an exception will be raised. 318 """ 319 if kill_all_emulators: 320 _KillAllEmulators() # just to be sure 321 self._AggressiveImageCleanup() 322 (self.device, port) = self._DeviceName() 323 emulator_command = [ 324 self.emulator, 325 # Speed up emulator launch by 40%. Really. 326 '-no-boot-anim', 327 # The default /data size is 64M. 328 # That's not enough for 8 unit test bundles and their data. 329 '-partition-size', '512', 330 # Use a familiar name and port. 331 '-avd', self.avd_name, 332 '-port', str(port), 333 # Wipe the data. We've seen cases where an emulator gets 'stuck' if we 334 # don't do this (every thousand runs or so). 335 '-wipe-data', 336 # Enable GPU by default. 337 '-gpu', 'on', 338 '-qemu', '-m', '1024', 339 ] 340 if self.abi == 'x86': 341 emulator_command.extend([ 342 # For x86 emulator --enable-kvm will fail early, avoiding accidental 343 # runs in a slow mode (i.e. without hardware virtualization support). 344 '--enable-kvm', 345 ]) 346 347 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) 348 self.popen = subprocess.Popen(args=emulator_command, 349 stderr=subprocess.STDOUT) 350 self._InstallKillHandler() 351 352 def _AggressiveImageCleanup(self): 353 """Aggressive cleanup of emulator images. 354 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? 359 """ 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) 369 370 def ConfirmLaunch(self, wait_for_boot=False): 371 """Confirm the emulator launched properly. 372 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. 377 """ 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 as e: 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) 404 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 413 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 420 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) 425