Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Provides an interface to start and stop Android emulator. | 6 """Provides an interface to start and stop Android emulator. |
| 7 | 7 |
| 8 Assumes system environment ANDROID_NDK_ROOT has been set. | 8 Assumes system environment ANDROID_NDK_ROOT has been set. |
| 9 | 9 |
| 10 Emulator: The class provides the methods to launch/shutdown the emulator with | 10 Emulator: The class provides the methods to launch/shutdown the emulator with |
| 11 the android virtual device named 'buildbot' . | 11 the android virtual device named 'buildbot' . |
| 12 """ | 12 """ |
| 13 | 13 |
| 14 import logging | 14 import logging |
| 15 import os | 15 import os |
| 16 import signal | |
| 16 import subprocess | 17 import subprocess |
| 17 import sys | 18 import sys |
| 19 import time | |
| 18 | 20 |
| 19 import android_commands | 21 import android_commands |
| 20 | 22 |
| 21 # adb_interface.py is under ../../third_party/android/testrunner/ | 23 # adb_interface.py is under ../../third_party/android/testrunner/ |
| 22 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', | 24 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', |
| 23 '..', 'third_party', 'android', 'testrunner')) | 25 '..', 'third_party', 'android', 'testrunner')) |
| 24 import adb_interface | 26 import adb_interface |
| 27 import cmd_helper | |
| 28 import errors | |
| 29 import run_command | |
| 25 | 30 |
| 31 class EmulatorLaunchException(Exception): | |
| 32 """Emulator failed to launch.""" | |
| 33 pass | |
| 34 | |
| 35 def _KillAllEmulators(): | |
| 36 """Kill all running emulators that look like ones we started. | |
| 37 | |
| 38 There are odd 'sticky' cases where there can be no emulator process | |
| 39 running but a device slot is taken. A little bot trouble and and | |
| 40 we're out of room forever. | |
| 41 """ | |
| 42 emulators = android_commands.GetEmulators() | |
| 43 if not emulators: | |
| 44 return | |
| 45 for emu_name in emulators: | |
| 46 cmd_helper.GetCmdOutput(['adb', '-s', emu_name, 'emu', 'kill']) | |
| 47 logging.info('Emulator killing is async; give a few seconds for all to die.') | |
| 48 for i in range(5): | |
| 49 if not android_commands.GetEmulators(): | |
| 50 return | |
| 51 time.sleep(1) | |
| 26 | 52 |
| 27 def _GetAvailablePort(): | 53 def _GetAvailablePort(): |
| 28 """Returns an available TCP port for the console.""" | 54 """Returns an available TCP port for the console.""" |
| 29 used_ports = [] | 55 used_ports = [] |
| 30 emulators = android_commands.GetEmulators() | 56 emulators = android_commands.GetEmulators() |
| 31 for emulator in emulators: | 57 for emulator in emulators: |
| 32 used_ports.append(emulator.split('-')[1]) | 58 used_ports.append(emulator.split('-')[1]) |
| 33 # The port must be an even number between 5554 and 5584. | 59 # The port must be an even number between 5554 and 5584. |
| 34 for port in range(5554, 5585, 2): | 60 for port in range(5554, 5585, 2): |
| 35 if str(port) not in used_ports: | 61 if str(port) not in used_ports: |
| 36 return port | 62 return port |
| 37 | 63 |
| 38 | 64 |
| 39 class Emulator(object): | 65 class Emulator(object): |
| 40 """Provides the methods to lanuch/shutdown the emulator. | 66 """Provides the methods to lanuch/shutdown the emulator. |
| 41 | 67 |
| 42 The emulator has the android virtual device named 'buildbot'. | 68 The emulator has the android virtual device named 'buildbot'. |
| 43 | 69 |
| 44 The emulator could use any even TCP port between 5554 and 5584 for the | 70 The emulator could use any even TCP port between 5554 and 5584 for the |
| 45 console communication, and this port will be part of the device name like | 71 console communication, and this port will be part of the device name like |
| 46 'emulator-5554'. Assume it is always True, as the device name is the id of | 72 'emulator-5554'. Assume it is always True, as the device name is the id of |
| 47 emulator managed in this class. | 73 emulator managed in this class. |
| 48 | 74 |
| 49 Attributes: | 75 Attributes: |
| 50 emulator: Path of Android's emulator tool. | 76 emulator: Path of Android's emulator tool. |
| 51 popen: Popen object of the running emulator process. | 77 popen: Popen object of the running emulator process. |
| 52 device: Device name of this emulator. | 78 device: Device name of this emulator. |
| 53 """ | 79 """ |
| 54 | 80 |
| 81 # Signals we listen for to kill the emulator on | |
| 82 _SIGNALS = (signal.SIGINT, signal.SIGHUP) | |
| 83 | |
| 55 def __init__(self): | 84 def __init__(self): |
| 56 try: | 85 try: |
| 57 android_sdk_root = os.environ['ANDROID_SDK_ROOT'] | 86 android_sdk_root = os.environ['ANDROID_SDK_ROOT'] |
| 58 except KeyError: | 87 except KeyError: |
| 59 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' | 88 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' |
| 60 'emulator.') | 89 'emulator.') |
| 61 raise | 90 raise |
| 62 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') | 91 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') |
| 63 self.popen = None | 92 self.popen = None |
| 64 self.device = None | 93 self.device = None |
| 65 | 94 |
| 66 def Reset(self): | 95 def _DeviceName(self): |
| 67 """Kill a running emulator. | 96 """Return our device name.""" |
| 68 | 97 port = _GetAvailablePort() |
| 69 May be needed if the test scripts stopped abnormally and an | 98 return ("emulator-%d" % port, port) |
|
bradn
2011/12/05 04:56:12
single quotes
| |
| 70 emulator is left around.""" | |
| 71 adb = adb_interface.AdbInterface() | |
| 72 logging.info('Killing any existing emulator.') | |
| 73 adb.SendCommand('emu kill') | |
| 74 | 99 |
| 75 def Launch(self): | 100 def Launch(self): |
| 76 """Launches the emulator and waits for package manager to startup. | 101 """Launches the emulator and waits for package manager to startup. |
| 77 | 102 |
| 78 If fails, an exception will be raised. | 103 If fails, an exception will be raised. |
| 79 """ | 104 """ |
| 80 port = _GetAvailablePort() | 105 _KillAllEmulators() # just to be sure |
| 81 self.device = "emulator-%d" % port | 106 (self.device, port) = self._DeviceName() |
| 82 self.popen = subprocess.Popen(args=[ | 107 self.popen = subprocess.Popen(args=[ |
| 83 self.emulator, | 108 self.emulator, |
| 84 # Speed up emulator launch by 40%. Really. | 109 # Speed up emulator launch by 40%. Really. |
| 85 '-no-boot-anim', | 110 '-no-boot-anim', |
| 86 # The default /data size is 64M. | 111 # The default /data size is 64M. |
| 87 # That's not enough for 4 unit test bundles and their data. | 112 # That's not enough for 4 unit test bundles and their data. |
| 88 '-partition-size', '256', | 113 '-partition-size', '256', |
| 89 # Use a familiar name and port. | 114 # Use a familiar name and port. |
| 90 '-avd', 'buildbot', | 115 '-avd', 'buildbot', |
| 91 '-port', str(port)]) | 116 '-port', str(port)], |
| 92 # This will not return until device's package manager starts up or an | 117 stderr=subprocess.STDOUT) |
| 93 # exception is raised. | 118 self._InstallKillHandler() |
| 94 android_commands.AndroidCommands(self.device, True) | 119 self._ConfirmLaunch() |
| 120 | |
| 121 def _ConfirmLaunch(self): | |
| 122 """Confirm the emulator launched properly. | |
| 123 | |
| 124 Loop on a wait-for-device with a very small timeout. On each | |
| 125 timeout, check the emulator process is still alive. | |
| 126 After confirming a wait-for-device can be successful, make sure | |
| 127 it returns the right answer. | |
| 128 """ | |
|
bradn
2011/12/05 04:56:12
in 4
| |
| 129 a = android_commands.AndroidCommands(self.device, False) | |
| 130 seconds_to_wait = 120 | |
| 131 interval = 5 | |
|
bradn
2011/12/05 04:56:12
This the 5 from the above 5 retries?
How about a c
| |
| 132 seconds_waited = 0 | |
| 133 number_of_waits = 2 | |
| 134 adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device') | |
| 135 while seconds_waited < seconds_to_wait: | |
| 136 try: | |
| 137 run_command.RunCommand(adb_cmd, timeout_time=interval, | |
| 138 retry_count=1) | |
| 139 number_of_waits -= 1 | |
| 140 if not number_of_waits: | |
| 141 break | |
| 142 except errors.WaitForResponseTimedOutError as e: | |
| 143 seconds_waited += interval | |
| 144 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') | |
| 145 run_command.RunCommand(adb_cmd) | |
| 146 self.popen.poll() | |
| 147 if self.popen.returncode != None: | |
| 148 raise EmulatorLaunchException('EMULATOR DIED') | |
| 149 if seconds_waited >= seconds_to_wait: | |
| 150 raise EmulatorLaunchException('TIMEOUT with wait-for-device') | |
| 151 logging.info('Seconds waited on wait-for-device: ' + str(seconds_waited)) | |
|
bradn
2011/12/05 04:56:12
logging takes printf like args. %d', seconds_waite
| |
| 152 # Now that we checked for obvious problems, wait for a boot complete. | |
| 153 # Waiting for the package manager has been problematic. | |
| 154 a.Adb().WaitForBootComplete() | |
| 95 | 155 |
| 96 def Shutdown(self): | 156 def Shutdown(self): |
| 97 """Shuts down the process started by launch.""" | 157 """Shuts down the process started by launch.""" |
| 98 self.popen.terminate() | 158 if self.popen: |
| 159 self.popen.poll() | |
| 160 if self.popen.returncode == None: | |
| 161 self.popen.kill() | |
| 162 self.popen = None | |
| 99 | 163 |
| 164 def _ShutdownOnSignal(self, signum, frame): | |
| 165 logging.critical('emulator _ShutdownOnSignal') | |
| 166 for sig in self._SIGNALS: | |
| 167 signal.signal(sig, signal.SIG_DFL) | |
| 168 self.Shutdown() | |
| 169 raise KeyboardInterrupt # print a stack | |
| 170 | |
| 171 def _InstallKillHandler(self): | |
| 172 """Install a handler to kill the emulator when we exit unexpectedly.""" | |
| 173 for sig in self._SIGNALS: | |
| 174 signal.signal(sig, self._ShutdownOnSignal) | |
| 100 | 175 |
| 101 def main(argv): | 176 def main(argv): |
| 102 Emulator().launch() | 177 Emulator().launch() |
| 103 | 178 |
| 104 | 179 |
| 105 if __name__ == '__main__': | 180 if __name__ == '__main__': |
| 106 main(sys.argv) | 181 main(sys.argv) |
| OLD | NEW |