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 |