| OLD | NEW |
| (Empty) |
| 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 | |
| 7 Emulator: The class provides the methods to launch/shutdown the emulator with | |
| 8 the android virtual device named 'avd_armeabi' . | |
| 9 """ | |
| 10 | |
| 11 import logging | |
| 12 import os | |
| 13 import signal | |
| 14 import subprocess | |
| 15 import time | |
| 16 | |
| 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 | |
| 24 | |
| 25 import errors | |
| 26 import run_command | |
| 27 | |
| 28 # SD card size | |
| 29 SDCARD_SIZE = '512M' | |
| 30 | |
| 31 # Template used to generate config.ini files for the emulator | |
| 32 CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1 | |
| 33 hw.dPad=no | |
| 34 hw.lcd.density=320 | |
| 35 sdcard.size=512M | |
| 36 hw.cpu.arch={hw.cpu.arch} | |
| 37 hw.device.hash=-708107041 | |
| 38 hw.camera.back=none | |
| 39 disk.dataPartition.size=800M | |
| 40 hw.gpu.enabled=yes | |
| 41 skin.path=720x1280 | |
| 42 skin.dynamic=yes | |
| 43 hw.keyboard=yes | |
| 44 hw.ramSize=1024 | |
| 45 hw.device.manufacturer=Google | |
| 46 hw.sdCard=yes | |
| 47 hw.mainKeys=no | |
| 48 hw.accelerometer=yes | |
| 49 skin.name=720x1280 | |
| 50 abi.type={abi.type} | |
| 51 hw.trackBall=no | |
| 52 hw.device.name=Galaxy Nexus | |
| 53 hw.battery=yes | |
| 54 hw.sensors.proximity=yes | |
| 55 image.sysdir.1=system-images/android-{api.level}/{abi.type}/ | |
| 56 hw.sensors.orientation=yes | |
| 57 hw.audioInput=yes | |
| 58 hw.camera.front=none | |
| 59 hw.gps=yes | |
| 60 vm.heapSize=128 | |
| 61 {extras}""" | |
| 62 | |
| 63 CONFIG_REPLACEMENTS = { | |
| 64 'x86': { | |
| 65 '{hw.cpu.arch}': 'x86', | |
| 66 '{abi.type}': 'x86', | |
| 67 '{extras}': '' | |
| 68 }, | |
| 69 'arm': { | |
| 70 '{hw.cpu.arch}': 'arm', | |
| 71 '{abi.type}': 'armeabi-v7a', | |
| 72 '{extras}': 'hw.cpu.model=cortex-a8\n' | |
| 73 }, | |
| 74 'mips': { | |
| 75 '{hw.cpu.arch}': 'mips', | |
| 76 '{abi.type}': 'mips', | |
| 77 '{extras}': '' | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 class EmulatorLaunchException(Exception): | |
| 82 """Emulator failed to launch.""" | |
| 83 pass | |
| 84 | |
| 85 def _KillAllEmulators(): | |
| 86 """Kill all running emulators that look like ones we started. | |
| 87 | |
| 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 we're out of | |
| 90 room forever. | |
| 91 """ | |
| 92 emulators = [d for d in device_utils.DeviceUtils.HealthyDevices() | |
| 93 if d.adb.is_emulator] | |
| 94 if not emulators: | |
| 95 return | |
| 96 for e in emulators: | |
| 97 e.adb.Emu(['kill']) | |
| 98 logging.info('Emulator killing is async; give a few seconds for all to die.') | |
| 99 for _ in range(5): | |
| 100 if not any(d.adb.is_emulator for d | |
| 101 in device_utils.DeviceUtils.HealthyDevices()): | |
| 102 return | |
| 103 time.sleep(1) | |
| 104 | |
| 105 | |
| 106 def DeleteAllTempAVDs(): | |
| 107 """Delete all temporary AVDs which are created for tests. | |
| 108 | |
| 109 If the test exits abnormally and some temporary AVDs created when testing may | |
| 110 be left in the system. Clean these AVDs. | |
| 111 """ | |
| 112 avds = device_utils.GetAVDs() | |
| 113 if not avds: | |
| 114 return | |
| 115 for avd_name in avds: | |
| 116 if 'run_tests_avd' in avd_name: | |
| 117 cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name] | |
| 118 cmd_helper.RunCmd(cmd) | |
| 119 logging.info('Delete AVD %s' % avd_name) | |
| 120 | |
| 121 | |
| 122 class PortPool(object): | |
| 123 """Pool for emulator port starting position that changes over time.""" | |
| 124 _port_min = 5554 | |
| 125 _port_max = 5585 | |
| 126 _port_current_index = 0 | |
| 127 | |
| 128 @classmethod | |
| 129 def port_range(cls): | |
| 130 """Return a range of valid ports for emulator use. | |
| 131 | |
| 132 The port must be an even number between 5554 and 5584. Sometimes | |
| 133 a killed emulator "hangs on" to a port long enough to prevent | |
| 134 relaunch. This is especially true on slow machines (like a bot). | |
| 135 Cycling through a port start position helps make us resilient.""" | |
| 136 ports = range(cls._port_min, cls._port_max, 2) | |
| 137 n = cls._port_current_index | |
| 138 cls._port_current_index = (n + 1) % len(ports) | |
| 139 return ports[n:] + ports[:n] | |
| 140 | |
| 141 | |
| 142 def _GetAvailablePort(): | |
| 143 """Returns an available TCP port for the console.""" | |
| 144 used_ports = [] | |
| 145 emulators = [d for d in device_utils.DeviceUtils.HealthyDevices() | |
| 146 if d.adb.is_emulator] | |
| 147 for emulator in emulators: | |
| 148 used_ports.append(emulator.adb.GetDeviceSerial().split('-')[1]) | |
| 149 for port in PortPool.port_range(): | |
| 150 if str(port) not in used_ports: | |
| 151 return port | |
| 152 | |
| 153 | |
| 154 def LaunchTempEmulators(emulator_count, abi, api_level, wait_for_boot=True): | |
| 155 """Create and launch temporary 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) | |
| 174 emulator.CreateAVD(api_level) | |
| 175 emulator.Launch(kill_all_emulators=n == 0) | |
| 176 t.Stop() | |
| 177 emulators.append(emulator) | |
| 178 # Wait for all emulators to boot completed. | |
| 179 if wait_for_boot: | |
| 180 for emulator in emulators: | |
| 181 emulator.ConfirmLaunch(True) | |
| 182 return emulators | |
| 183 | |
| 184 | |
| 185 def LaunchEmulator(avd_name, abi): | |
| 186 """Launch an existing emulator with name avd_name. | |
| 187 | |
| 188 Args: | |
| 189 avd_name: name of existing emulator | |
| 190 abi: the emulator target platform | |
| 191 | |
| 192 Returns: | |
| 193 emulator object. | |
| 194 """ | |
| 195 logging.info('Specified emulator named avd_name=%s launched', avd_name) | |
| 196 emulator = Emulator(avd_name, abi) | |
| 197 emulator.Launch(kill_all_emulators=True) | |
| 198 emulator.ConfirmLaunch(True) | |
| 199 return emulator | |
| 200 | |
| 201 | |
| 202 class Emulator(object): | |
| 203 """Provides the methods to launch/shutdown the emulator. | |
| 204 | |
| 205 The emulator has the android virtual device named 'avd_armeabi'. | |
| 206 | |
| 207 The emulator could use any even TCP port between 5554 and 5584 for the | |
| 208 console communication, and this port will be part of the device name like | |
| 209 'emulator-5554'. Assume it is always True, as the device name is the id of | |
| 210 emulator managed in this class. | |
| 211 | |
| 212 Attributes: | |
| 213 emulator: Path of Android's emulator tool. | |
| 214 popen: Popen object of the running emulator process. | |
| 215 device: Device name of this emulator. | |
| 216 """ | |
| 217 | |
| 218 # Signals we listen for to kill the emulator on | |
| 219 _SIGNALS = (signal.SIGINT, signal.SIGHUP) | |
| 220 | |
| 221 # Time to wait for an emulator launch, in seconds. This includes | |
| 222 # the time to launch the emulator and a wait-for-device command. | |
| 223 _LAUNCH_TIMEOUT = 120 | |
| 224 | |
| 225 # Timeout interval of wait-for-device command before bouncing to a a | |
| 226 # process life check. | |
| 227 _WAITFORDEVICE_TIMEOUT = 5 | |
| 228 | |
| 229 # Time to wait for a "wait for boot complete" (property set on device). | |
| 230 _WAITFORBOOT_TIMEOUT = 300 | |
| 231 | |
| 232 def __init__(self, avd_name, abi): | |
| 233 """Init an Emulator. | |
| 234 | |
| 235 Args: | |
| 236 avd_name: name of the AVD to create | |
| 237 abi: target platform for emulator being created, defaults to x86 | |
| 238 """ | |
| 239 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk') | |
| 240 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') | |
| 241 self.android = os.path.join(android_sdk_root, 'tools', 'android') | |
| 242 self.popen = None | |
| 243 self.device_serial = None | |
| 244 self.abi = abi | |
| 245 self.avd_name = avd_name | |
| 246 | |
| 247 @staticmethod | |
| 248 def _DeviceName(): | |
| 249 """Return our device name.""" | |
| 250 port = _GetAvailablePort() | |
| 251 return ('emulator-%d' % port, port) | |
| 252 | |
| 253 def CreateAVD(self, api_level): | |
| 254 """Creates an AVD with the given name. | |
| 255 | |
| 256 Args: | |
| 257 api_level: the api level of the image | |
| 258 | |
| 259 Return avd_name. | |
| 260 """ | |
| 261 | |
| 262 if self.abi == 'arm': | |
| 263 abi_option = 'armeabi-v7a' | |
| 264 elif self.abi == 'mips': | |
| 265 abi_option = 'mips' | |
| 266 else: | |
| 267 abi_option = 'x86' | |
| 268 | |
| 269 api_target = 'android-%s' % api_level | |
| 270 | |
| 271 avd_command = [ | |
| 272 self.android, | |
| 273 '--silent', | |
| 274 'create', 'avd', | |
| 275 '--name', self.avd_name, | |
| 276 '--abi', abi_option, | |
| 277 '--target', api_target, | |
| 278 '--sdcard', SDCARD_SIZE, | |
| 279 '--force', | |
| 280 ] | |
| 281 avd_cmd_str = ' '.join(avd_command) | |
| 282 logging.info('Create AVD command: %s', avd_cmd_str) | |
| 283 avd_process = pexpect.spawn(avd_cmd_str) | |
| 284 | |
| 285 # Instead of creating a custom profile, we overwrite config files. | |
| 286 avd_process.expect('Do you wish to create a custom hardware profile') | |
| 287 avd_process.sendline('no\n') | |
| 288 avd_process.expect('Created AVD \'%s\'' % self.avd_name) | |
| 289 | |
| 290 # Replace current configuration with default Galaxy Nexus config. | |
| 291 avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd') | |
| 292 ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name) | |
| 293 new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name, | |
| 294 'config.ini') | |
| 295 | |
| 296 # Remove config files with defaults to replace with Google's GN settings. | |
| 297 os.unlink(ini_file) | |
| 298 os.unlink(new_config_ini) | |
| 299 | |
| 300 # Create new configuration files with Galaxy Nexus by Google settings. | |
| 301 with open(ini_file, 'w') as new_ini: | |
| 302 new_ini.write('avd.ini.encoding=ISO-8859-1\n') | |
| 303 new_ini.write('target=%s\n' % api_target) | |
| 304 new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name)) | |
| 305 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name) | |
| 306 | |
| 307 custom_config = CONFIG_TEMPLATE | |
| 308 replacements = CONFIG_REPLACEMENTS[self.abi] | |
| 309 for key in replacements: | |
| 310 custom_config = custom_config.replace(key, replacements[key]) | |
| 311 custom_config = custom_config.replace('{api.level}', str(api_level)) | |
| 312 | |
| 313 with open(new_config_ini, 'w') as new_config_ini: | |
| 314 new_config_ini.write(custom_config) | |
| 315 | |
| 316 return self.avd_name | |
| 317 | |
| 318 | |
| 319 def _DeleteAVD(self): | |
| 320 """Delete the AVD of this emulator.""" | |
| 321 avd_command = [ | |
| 322 self.android, | |
| 323 '--silent', | |
| 324 'delete', | |
| 325 'avd', | |
| 326 '--name', self.avd_name, | |
| 327 ] | |
| 328 logging.info('Delete AVD command: %s', ' '.join(avd_command)) | |
| 329 cmd_helper.RunCmd(avd_command) | |
| 330 | |
| 331 | |
| 332 def Launch(self, kill_all_emulators): | |
| 333 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the | |
| 334 emulator is ready for use. | |
| 335 | |
| 336 If fails, an exception will be raised. | |
| 337 """ | |
| 338 if kill_all_emulators: | |
| 339 _KillAllEmulators() # just to be sure | |
| 340 self._AggressiveImageCleanup() | |
| 341 (self.device_serial, port) = self._DeviceName() | |
| 342 emulator_command = [ | |
| 343 self.emulator, | |
| 344 # Speed up emulator launch by 40%. Really. | |
| 345 '-no-boot-anim', | |
| 346 # The default /data size is 64M. | |
| 347 # That's not enough for 8 unit test bundles and their data. | |
| 348 '-partition-size', '512', | |
| 349 # Use a familiar name and port. | |
| 350 '-avd', self.avd_name, | |
| 351 '-port', str(port), | |
| 352 # Wipe the data. We've seen cases where an emulator gets 'stuck' if we | |
| 353 # don't do this (every thousand runs or so). | |
| 354 '-wipe-data', | |
| 355 # Enable GPU by default. | |
| 356 '-gpu', 'on', | |
| 357 '-qemu', '-m', '1024', | |
| 358 ] | |
| 359 if self.abi == 'x86': | |
| 360 emulator_command.extend([ | |
| 361 # For x86 emulator --enable-kvm will fail early, avoiding accidental | |
| 362 # runs in a slow mode (i.e. without hardware virtualization support). | |
| 363 '--enable-kvm', | |
| 364 ]) | |
| 365 | |
| 366 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) | |
| 367 self.popen = subprocess.Popen(args=emulator_command, | |
| 368 stderr=subprocess.STDOUT) | |
| 369 self._InstallKillHandler() | |
| 370 | |
| 371 @staticmethod | |
| 372 def _AggressiveImageCleanup(): | |
| 373 """Aggressive cleanup of emulator images. | |
| 374 | |
| 375 Experimentally it looks like our current emulator use on the bot | |
| 376 leaves image files around in /tmp/android-$USER. If a "random" | |
| 377 name gets reused, we choke with a 'File exists' error. | |
| 378 TODO(jrg): is there a less hacky way to accomplish the same goal? | |
| 379 """ | |
| 380 logging.info('Aggressive Image Cleanup') | |
| 381 emulator_imagedir = '/tmp/android-%s' % os.environ['USER'] | |
| 382 if not os.path.exists(emulator_imagedir): | |
| 383 return | |
| 384 for image in os.listdir(emulator_imagedir): | |
| 385 full_name = os.path.join(emulator_imagedir, image) | |
| 386 if 'emulator' in full_name: | |
| 387 logging.info('Deleting emulator image %s', full_name) | |
| 388 os.unlink(full_name) | |
| 389 | |
| 390 def ConfirmLaunch(self, wait_for_boot=False): | |
| 391 """Confirm the emulator launched properly. | |
| 392 | |
| 393 Loop on a wait-for-device with a very small timeout. On each | |
| 394 timeout, check the emulator process is still alive. | |
| 395 After confirming a wait-for-device can be successful, make sure | |
| 396 it returns the right answer. | |
| 397 """ | |
| 398 seconds_waited = 0 | |
| 399 number_of_waits = 2 # Make sure we can wfd twice | |
| 400 | |
| 401 device = device_utils.DeviceUtils(self.device_serial) | |
| 402 while seconds_waited < self._LAUNCH_TIMEOUT: | |
| 403 try: | |
| 404 device.adb.WaitForDevice( | |
| 405 timeout=self._WAITFORDEVICE_TIMEOUT, retries=1) | |
| 406 number_of_waits -= 1 | |
| 407 if not number_of_waits: | |
| 408 break | |
| 409 except device_errors.CommandTimeoutError: | |
| 410 seconds_waited += self._WAITFORDEVICE_TIMEOUT | |
| 411 device.adb.KillServer() | |
| 412 self.popen.poll() | |
| 413 if self.popen.returncode != None: | |
| 414 raise EmulatorLaunchException('EMULATOR DIED') | |
| 415 | |
| 416 if seconds_waited >= self._LAUNCH_TIMEOUT: | |
| 417 raise EmulatorLaunchException('TIMEOUT with wait-for-device') | |
| 418 | |
| 419 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) | |
| 420 if wait_for_boot: | |
| 421 # Now that we checked for obvious problems, wait for a boot complete. | |
| 422 # Waiting for the package manager is sometimes problematic. | |
| 423 device.WaitUntilFullyBooted(timeout=self._WAITFORBOOT_TIMEOUT) | |
| 424 | |
| 425 def Shutdown(self): | |
| 426 """Shuts down the process started by launch.""" | |
| 427 self._DeleteAVD() | |
| 428 if self.popen: | |
| 429 self.popen.poll() | |
| 430 if self.popen.returncode == None: | |
| 431 self.popen.kill() | |
| 432 self.popen = None | |
| 433 | |
| 434 def _ShutdownOnSignal(self, _signum, _frame): | |
| 435 logging.critical('emulator _ShutdownOnSignal') | |
| 436 for sig in self._SIGNALS: | |
| 437 signal.signal(sig, signal.SIG_DFL) | |
| 438 self.Shutdown() | |
| 439 raise KeyboardInterrupt # print a stack | |
| 440 | |
| 441 def _InstallKillHandler(self): | |
| 442 """Install a handler to kill the emulator when we exit unexpectedly.""" | |
| 443 for sig in self._SIGNALS: | |
| 444 signal.signal(sig, self._ShutdownOnSignal) | |
| OLD | NEW |