Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | #!/usr/bin/env python |
michael@0 | 2 | # |
michael@0 | 3 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
michael@0 | 4 | # Use of this source code is governed by a BSD-style license that can be |
michael@0 | 5 | # found in the LICENSE file. |
michael@0 | 6 | |
michael@0 | 7 | """Provides an interface to start and stop Android emulator. |
michael@0 | 8 | |
michael@0 | 9 | Assumes system environment ANDROID_NDK_ROOT has been set. |
michael@0 | 10 | |
michael@0 | 11 | Emulator: The class provides the methods to launch/shutdown the emulator with |
michael@0 | 12 | the android virtual device named 'avd_armeabi' . |
michael@0 | 13 | """ |
michael@0 | 14 | |
michael@0 | 15 | import logging |
michael@0 | 16 | import os |
michael@0 | 17 | import signal |
michael@0 | 18 | import subprocess |
michael@0 | 19 | import sys |
michael@0 | 20 | import time |
michael@0 | 21 | |
michael@0 | 22 | from pylib import android_commands |
michael@0 | 23 | from pylib import cmd_helper |
michael@0 | 24 | |
michael@0 | 25 | # adb_interface.py is under ../../third_party/android_testrunner/ |
michael@0 | 26 | sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', |
michael@0 | 27 | '..', 'third_party', 'android_testrunner')) |
michael@0 | 28 | import adb_interface |
michael@0 | 29 | import errors |
michael@0 | 30 | import run_command |
michael@0 | 31 | |
michael@0 | 32 | class EmulatorLaunchException(Exception): |
michael@0 | 33 | """Emulator failed to launch.""" |
michael@0 | 34 | pass |
michael@0 | 35 | |
michael@0 | 36 | def _KillAllEmulators(): |
michael@0 | 37 | """Kill all running emulators that look like ones we started. |
michael@0 | 38 | |
michael@0 | 39 | There are odd 'sticky' cases where there can be no emulator process |
michael@0 | 40 | running but a device slot is taken. A little bot trouble and and |
michael@0 | 41 | we're out of room forever. |
michael@0 | 42 | """ |
michael@0 | 43 | emulators = android_commands.GetEmulators() |
michael@0 | 44 | if not emulators: |
michael@0 | 45 | return |
michael@0 | 46 | for emu_name in emulators: |
michael@0 | 47 | cmd_helper.GetCmdOutput(['adb', '-s', emu_name, 'emu', 'kill']) |
michael@0 | 48 | logging.info('Emulator killing is async; give a few seconds for all to die.') |
michael@0 | 49 | for i in range(5): |
michael@0 | 50 | if not android_commands.GetEmulators(): |
michael@0 | 51 | return |
michael@0 | 52 | time.sleep(1) |
michael@0 | 53 | |
michael@0 | 54 | |
michael@0 | 55 | def DeleteAllTempAVDs(): |
michael@0 | 56 | """Delete all temporary AVDs which are created for tests. |
michael@0 | 57 | |
michael@0 | 58 | If the test exits abnormally and some temporary AVDs created when testing may |
michael@0 | 59 | be left in the system. Clean these AVDs. |
michael@0 | 60 | """ |
michael@0 | 61 | avds = android_commands.GetAVDs() |
michael@0 | 62 | if not avds: |
michael@0 | 63 | return |
michael@0 | 64 | for avd_name in avds: |
michael@0 | 65 | if 'run_tests_avd' in avd_name: |
michael@0 | 66 | cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name] |
michael@0 | 67 | cmd_helper.GetCmdOutput(cmd) |
michael@0 | 68 | logging.info('Delete AVD %s' % avd_name) |
michael@0 | 69 | |
michael@0 | 70 | |
michael@0 | 71 | class PortPool(object): |
michael@0 | 72 | """Pool for emulator port starting position that changes over time.""" |
michael@0 | 73 | _port_min = 5554 |
michael@0 | 74 | _port_max = 5585 |
michael@0 | 75 | _port_current_index = 0 |
michael@0 | 76 | |
michael@0 | 77 | @classmethod |
michael@0 | 78 | def port_range(cls): |
michael@0 | 79 | """Return a range of valid ports for emulator use. |
michael@0 | 80 | |
michael@0 | 81 | The port must be an even number between 5554 and 5584. Sometimes |
michael@0 | 82 | a killed emulator "hangs on" to a port long enough to prevent |
michael@0 | 83 | relaunch. This is especially true on slow machines (like a bot). |
michael@0 | 84 | Cycling through a port start position helps make us resilient.""" |
michael@0 | 85 | ports = range(cls._port_min, cls._port_max, 2) |
michael@0 | 86 | n = cls._port_current_index |
michael@0 | 87 | cls._port_current_index = (n + 1) % len(ports) |
michael@0 | 88 | return ports[n:] + ports[:n] |
michael@0 | 89 | |
michael@0 | 90 | |
michael@0 | 91 | def _GetAvailablePort(): |
michael@0 | 92 | """Returns an available TCP port for the console.""" |
michael@0 | 93 | used_ports = [] |
michael@0 | 94 | emulators = android_commands.GetEmulators() |
michael@0 | 95 | for emulator in emulators: |
michael@0 | 96 | used_ports.append(emulator.split('-')[1]) |
michael@0 | 97 | for port in PortPool.port_range(): |
michael@0 | 98 | if str(port) not in used_ports: |
michael@0 | 99 | return port |
michael@0 | 100 | |
michael@0 | 101 | |
michael@0 | 102 | class Emulator(object): |
michael@0 | 103 | """Provides the methods to lanuch/shutdown the emulator. |
michael@0 | 104 | |
michael@0 | 105 | The emulator has the android virtual device named 'avd_armeabi'. |
michael@0 | 106 | |
michael@0 | 107 | The emulator could use any even TCP port between 5554 and 5584 for the |
michael@0 | 108 | console communication, and this port will be part of the device name like |
michael@0 | 109 | 'emulator-5554'. Assume it is always True, as the device name is the id of |
michael@0 | 110 | emulator managed in this class. |
michael@0 | 111 | |
michael@0 | 112 | Attributes: |
michael@0 | 113 | emulator: Path of Android's emulator tool. |
michael@0 | 114 | popen: Popen object of the running emulator process. |
michael@0 | 115 | device: Device name of this emulator. |
michael@0 | 116 | """ |
michael@0 | 117 | |
michael@0 | 118 | # Signals we listen for to kill the emulator on |
michael@0 | 119 | _SIGNALS = (signal.SIGINT, signal.SIGHUP) |
michael@0 | 120 | |
michael@0 | 121 | # Time to wait for an emulator launch, in seconds. This includes |
michael@0 | 122 | # the time to launch the emulator and a wait-for-device command. |
michael@0 | 123 | _LAUNCH_TIMEOUT = 120 |
michael@0 | 124 | |
michael@0 | 125 | # Timeout interval of wait-for-device command before bouncing to a a |
michael@0 | 126 | # process life check. |
michael@0 | 127 | _WAITFORDEVICE_TIMEOUT = 5 |
michael@0 | 128 | |
michael@0 | 129 | # Time to wait for a "wait for boot complete" (property set on device). |
michael@0 | 130 | _WAITFORBOOT_TIMEOUT = 300 |
michael@0 | 131 | |
michael@0 | 132 | def __init__(self, new_avd_name, fast_and_loose): |
michael@0 | 133 | """Init an Emulator. |
michael@0 | 134 | |
michael@0 | 135 | Args: |
michael@0 | 136 | nwe_avd_name: If set, will create a new temporary AVD. |
michael@0 | 137 | fast_and_loose: Loosen up the rules for reliable running for speed. |
michael@0 | 138 | Intended for quick testing or re-testing. |
michael@0 | 139 | |
michael@0 | 140 | """ |
michael@0 | 141 | try: |
michael@0 | 142 | android_sdk_root = os.environ['ANDROID_SDK_ROOT'] |
michael@0 | 143 | except KeyError: |
michael@0 | 144 | logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' |
michael@0 | 145 | 'emulator.') |
michael@0 | 146 | raise |
michael@0 | 147 | self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') |
michael@0 | 148 | self.android = os.path.join(android_sdk_root, 'tools', 'android') |
michael@0 | 149 | self.popen = None |
michael@0 | 150 | self.device = None |
michael@0 | 151 | self.default_avd = True |
michael@0 | 152 | self.fast_and_loose = fast_and_loose |
michael@0 | 153 | self.abi = 'armeabi-v7a' |
michael@0 | 154 | self.avd = 'avd_armeabi' |
michael@0 | 155 | if 'x86' in os.environ.get('TARGET_PRODUCT', ''): |
michael@0 | 156 | self.abi = 'x86' |
michael@0 | 157 | self.avd = 'avd_x86' |
michael@0 | 158 | if new_avd_name: |
michael@0 | 159 | self.default_avd = False |
michael@0 | 160 | self.avd = self._CreateAVD(new_avd_name) |
michael@0 | 161 | |
michael@0 | 162 | def _DeviceName(self): |
michael@0 | 163 | """Return our device name.""" |
michael@0 | 164 | port = _GetAvailablePort() |
michael@0 | 165 | return ('emulator-%d' % port, port) |
michael@0 | 166 | |
michael@0 | 167 | def _CreateAVD(self, avd_name): |
michael@0 | 168 | """Creates an AVD with the given name. |
michael@0 | 169 | |
michael@0 | 170 | Return avd_name. |
michael@0 | 171 | """ |
michael@0 | 172 | avd_command = [ |
michael@0 | 173 | self.android, |
michael@0 | 174 | '--silent', |
michael@0 | 175 | 'create', 'avd', |
michael@0 | 176 | '--name', avd_name, |
michael@0 | 177 | '--abi', self.abi, |
michael@0 | 178 | '--target', 'android-16', |
michael@0 | 179 | '-c', '128M', |
michael@0 | 180 | '--force', |
michael@0 | 181 | ] |
michael@0 | 182 | avd_process = subprocess.Popen(args=avd_command, |
michael@0 | 183 | stdin=subprocess.PIPE, |
michael@0 | 184 | stdout=subprocess.PIPE, |
michael@0 | 185 | stderr=subprocess.STDOUT) |
michael@0 | 186 | avd_process.stdin.write('no\n') |
michael@0 | 187 | avd_process.wait() |
michael@0 | 188 | logging.info('Create AVD command: %s', ' '.join(avd_command)) |
michael@0 | 189 | return avd_name |
michael@0 | 190 | |
michael@0 | 191 | def _DeleteAVD(self): |
michael@0 | 192 | """Delete the AVD of this emulator.""" |
michael@0 | 193 | avd_command = [ |
michael@0 | 194 | self.android, |
michael@0 | 195 | '--silent', |
michael@0 | 196 | 'delete', |
michael@0 | 197 | 'avd', |
michael@0 | 198 | '--name', self.avd, |
michael@0 | 199 | ] |
michael@0 | 200 | avd_process = subprocess.Popen(args=avd_command, |
michael@0 | 201 | stdout=subprocess.PIPE, |
michael@0 | 202 | stderr=subprocess.STDOUT) |
michael@0 | 203 | logging.info('Delete AVD command: %s', ' '.join(avd_command)) |
michael@0 | 204 | avd_process.wait() |
michael@0 | 205 | |
michael@0 | 206 | def Launch(self, kill_all_emulators): |
michael@0 | 207 | """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the |
michael@0 | 208 | emulator is ready for use. |
michael@0 | 209 | |
michael@0 | 210 | If fails, an exception will be raised. |
michael@0 | 211 | """ |
michael@0 | 212 | if kill_all_emulators: |
michael@0 | 213 | _KillAllEmulators() # just to be sure |
michael@0 | 214 | if not self.fast_and_loose: |
michael@0 | 215 | self._AggressiveImageCleanup() |
michael@0 | 216 | (self.device, port) = self._DeviceName() |
michael@0 | 217 | emulator_command = [ |
michael@0 | 218 | self.emulator, |
michael@0 | 219 | # Speed up emulator launch by 40%. Really. |
michael@0 | 220 | '-no-boot-anim', |
michael@0 | 221 | # The default /data size is 64M. |
michael@0 | 222 | # That's not enough for 8 unit test bundles and their data. |
michael@0 | 223 | '-partition-size', '512', |
michael@0 | 224 | # Enable GPU by default. |
michael@0 | 225 | '-gpu', 'on', |
michael@0 | 226 | # Use a familiar name and port. |
michael@0 | 227 | '-avd', self.avd, |
michael@0 | 228 | '-port', str(port)] |
michael@0 | 229 | if not self.fast_and_loose: |
michael@0 | 230 | emulator_command.extend([ |
michael@0 | 231 | # Wipe the data. We've seen cases where an emulator |
michael@0 | 232 | # gets 'stuck' if we don't do this (every thousand runs or |
michael@0 | 233 | # so). |
michael@0 | 234 | '-wipe-data', |
michael@0 | 235 | ]) |
michael@0 | 236 | logging.info('Emulator launch command: %s', ' '.join(emulator_command)) |
michael@0 | 237 | self.popen = subprocess.Popen(args=emulator_command, |
michael@0 | 238 | stderr=subprocess.STDOUT) |
michael@0 | 239 | self._InstallKillHandler() |
michael@0 | 240 | |
michael@0 | 241 | def _AggressiveImageCleanup(self): |
michael@0 | 242 | """Aggressive cleanup of emulator images. |
michael@0 | 243 | |
michael@0 | 244 | Experimentally it looks like our current emulator use on the bot |
michael@0 | 245 | leaves image files around in /tmp/android-$USER. If a "random" |
michael@0 | 246 | name gets reused, we choke with a 'File exists' error. |
michael@0 | 247 | TODO(jrg): is there a less hacky way to accomplish the same goal? |
michael@0 | 248 | """ |
michael@0 | 249 | logging.info('Aggressive Image Cleanup') |
michael@0 | 250 | emulator_imagedir = '/tmp/android-%s' % os.environ['USER'] |
michael@0 | 251 | if not os.path.exists(emulator_imagedir): |
michael@0 | 252 | return |
michael@0 | 253 | for image in os.listdir(emulator_imagedir): |
michael@0 | 254 | full_name = os.path.join(emulator_imagedir, image) |
michael@0 | 255 | if 'emulator' in full_name: |
michael@0 | 256 | logging.info('Deleting emulator image %s', full_name) |
michael@0 | 257 | os.unlink(full_name) |
michael@0 | 258 | |
michael@0 | 259 | def ConfirmLaunch(self, wait_for_boot=False): |
michael@0 | 260 | """Confirm the emulator launched properly. |
michael@0 | 261 | |
michael@0 | 262 | Loop on a wait-for-device with a very small timeout. On each |
michael@0 | 263 | timeout, check the emulator process is still alive. |
michael@0 | 264 | After confirming a wait-for-device can be successful, make sure |
michael@0 | 265 | it returns the right answer. |
michael@0 | 266 | """ |
michael@0 | 267 | seconds_waited = 0 |
michael@0 | 268 | number_of_waits = 2 # Make sure we can wfd twice |
michael@0 | 269 | adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device') |
michael@0 | 270 | while seconds_waited < self._LAUNCH_TIMEOUT: |
michael@0 | 271 | try: |
michael@0 | 272 | run_command.RunCommand(adb_cmd, |
michael@0 | 273 | timeout_time=self._WAITFORDEVICE_TIMEOUT, |
michael@0 | 274 | retry_count=1) |
michael@0 | 275 | number_of_waits -= 1 |
michael@0 | 276 | if not number_of_waits: |
michael@0 | 277 | break |
michael@0 | 278 | except errors.WaitForResponseTimedOutError as e: |
michael@0 | 279 | seconds_waited += self._WAITFORDEVICE_TIMEOUT |
michael@0 | 280 | adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') |
michael@0 | 281 | run_command.RunCommand(adb_cmd) |
michael@0 | 282 | self.popen.poll() |
michael@0 | 283 | if self.popen.returncode != None: |
michael@0 | 284 | raise EmulatorLaunchException('EMULATOR DIED') |
michael@0 | 285 | if seconds_waited >= self._LAUNCH_TIMEOUT: |
michael@0 | 286 | raise EmulatorLaunchException('TIMEOUT with wait-for-device') |
michael@0 | 287 | logging.info('Seconds waited on wait-for-device: %d', seconds_waited) |
michael@0 | 288 | if wait_for_boot: |
michael@0 | 289 | # Now that we checked for obvious problems, wait for a boot complete. |
michael@0 | 290 | # Waiting for the package manager is sometimes problematic. |
michael@0 | 291 | a = android_commands.AndroidCommands(self.device) |
michael@0 | 292 | a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT) |
michael@0 | 293 | |
michael@0 | 294 | def Shutdown(self): |
michael@0 | 295 | """Shuts down the process started by launch.""" |
michael@0 | 296 | if not self.default_avd: |
michael@0 | 297 | self._DeleteAVD() |
michael@0 | 298 | if self.popen: |
michael@0 | 299 | self.popen.poll() |
michael@0 | 300 | if self.popen.returncode == None: |
michael@0 | 301 | self.popen.kill() |
michael@0 | 302 | self.popen = None |
michael@0 | 303 | |
michael@0 | 304 | def _ShutdownOnSignal(self, signum, frame): |
michael@0 | 305 | logging.critical('emulator _ShutdownOnSignal') |
michael@0 | 306 | for sig in self._SIGNALS: |
michael@0 | 307 | signal.signal(sig, signal.SIG_DFL) |
michael@0 | 308 | self.Shutdown() |
michael@0 | 309 | raise KeyboardInterrupt # print a stack |
michael@0 | 310 | |
michael@0 | 311 | def _InstallKillHandler(self): |
michael@0 | 312 | """Install a handler to kill the emulator when we exit unexpectedly.""" |
michael@0 | 313 | for sig in self._SIGNALS: |
michael@0 | 314 | signal.signal(sig, self._ShutdownOnSignal) |
michael@0 | 315 | |
michael@0 | 316 | def main(argv): |
michael@0 | 317 | Emulator(None, True).Launch(True) |
michael@0 | 318 | |
michael@0 | 319 | |
michael@0 | 320 | if __name__ == '__main__': |
michael@0 | 321 | main(sys.argv) |