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 |
| 84 # Time to wait for an emulator launch, in seconds. |
| 85 _EMULATOR_LAUNCH_TIMEOUT = 120 |
| 86 |
| 87 # Timeout interval of wait-for-device command before bouncing to a a |
| 88 # process life check. |
| 89 _EMULATOR_WFD_TIMEOUT = 5 |
| 90 |
55 def __init__(self): | 91 def __init__(self): |
56 try: | 92 try: |
57 android_sdk_root = os.environ['ANDROID_SDK_ROOT'] | 93 android_sdk_root = os.environ['ANDROID_SDK_ROOT'] |
58 except KeyError: | 94 except KeyError: |
59 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' | 95 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' |
60 'emulator.') | 96 'emulator.') |
61 raise | 97 raise |
62 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') | 98 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') |
63 self.popen = None | 99 self.popen = None |
64 self.device = None | 100 self.device = None |
65 | 101 |
66 def Reset(self): | 102 def _DeviceName(self): |
67 """Kill a running emulator. | 103 """Return our device name.""" |
68 | 104 port = _GetAvailablePort() |
69 May be needed if the test scripts stopped abnormally and an | 105 return ('emulator-%d' % port, port) |
70 emulator is left around.""" | |
71 adb = adb_interface.AdbInterface() | |
72 logging.info('Killing any existing emulator.') | |
73 adb.SendCommand('emu kill') | |
74 | 106 |
75 def Launch(self): | 107 def Launch(self): |
76 """Launches the emulator and waits for package manager to startup. | 108 """Launches the emulator and waits for package manager to startup. |
77 | 109 |
78 If fails, an exception will be raised. | 110 If fails, an exception will be raised. |
79 """ | 111 """ |
80 port = _GetAvailablePort() | 112 _KillAllEmulators() # just to be sure |
81 self.device = "emulator-%d" % port | 113 (self.device, port) = self._DeviceName() |
82 self.popen = subprocess.Popen(args=[ | 114 self.popen = subprocess.Popen(args=[ |
83 self.emulator, | 115 self.emulator, |
84 # Speed up emulator launch by 40%. Really. | 116 # Speed up emulator launch by 40%. Really. |
85 '-no-boot-anim', | 117 '-no-boot-anim', |
86 # The default /data size is 64M. | 118 # The default /data size is 64M. |
87 # That's not enough for 4 unit test bundles and their data. | 119 # That's not enough for 4 unit test bundles and their data. |
88 '-partition-size', '256', | 120 '-partition-size', '256', |
89 # Use a familiar name and port. | 121 # Use a familiar name and port. |
90 '-avd', 'buildbot', | 122 '-avd', 'buildbot', |
91 '-port', str(port)]) | 123 '-port', str(port)], |
92 # This will not return until device's package manager starts up or an | 124 stderr=subprocess.STDOUT) |
93 # exception is raised. | 125 self._InstallKillHandler() |
94 android_commands.AndroidCommands(self.device, True) | 126 self._ConfirmLaunch() |
| 127 |
| 128 def _ConfirmLaunch(self): |
| 129 """Confirm the emulator launched properly. |
| 130 |
| 131 Loop on a wait-for-device with a very small timeout. On each |
| 132 timeout, check the emulator process is still alive. |
| 133 After confirming a wait-for-device can be successful, make sure |
| 134 it returns the right answer. |
| 135 """ |
| 136 a = android_commands.AndroidCommands(self.device, False) |
| 137 seconds_waited = 0 |
| 138 number_of_waits = 2 # Make sure we can wfd twice |
| 139 adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device') |
| 140 while seconds_waited < self._EMULATOR_LAUNCH_TIMEOUT: |
| 141 try: |
| 142 run_command.RunCommand(adb_cmd, timeout_time=self._EMULATOR_WFD_TIMEOUT, |
| 143 retry_count=1) |
| 144 number_of_waits -= 1 |
| 145 if not number_of_waits: |
| 146 break |
| 147 except errors.WaitForResponseTimedOutError as e: |
| 148 seconds_waited += self._EMULATOR_WFD_TIMEOUT |
| 149 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') |
| 150 run_command.RunCommand(adb_cmd) |
| 151 self.popen.poll() |
| 152 if self.popen.returncode != None: |
| 153 raise EmulatorLaunchException('EMULATOR DIED') |
| 154 if seconds_waited >= self._EMULATOR_LAUNCH_TIMEOUT: |
| 155 raise EmulatorLaunchException('TIMEOUT with wait-for-device') |
| 156 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) |
| 157 # Now that we checked for obvious problems, wait for a boot complete. |
| 158 # Waiting for the package manager has been problematic. |
| 159 a.Adb().WaitForBootComplete() |
95 | 160 |
96 def Shutdown(self): | 161 def Shutdown(self): |
97 """Shuts down the process started by launch.""" | 162 """Shuts down the process started by launch.""" |
98 self.popen.terminate() | 163 if self.popen: |
| 164 self.popen.poll() |
| 165 if self.popen.returncode == None: |
| 166 self.popen.kill() |
| 167 self.popen = None |
99 | 168 |
| 169 def _ShutdownOnSignal(self, signum, frame): |
| 170 logging.critical('emulator _ShutdownOnSignal') |
| 171 for sig in self._SIGNALS: |
| 172 signal.signal(sig, signal.SIG_DFL) |
| 173 self.Shutdown() |
| 174 raise KeyboardInterrupt # print a stack |
| 175 |
| 176 def _InstallKillHandler(self): |
| 177 """Install a handler to kill the emulator when we exit unexpectedly.""" |
| 178 for sig in self._SIGNALS: |
| 179 signal.signal(sig, self._ShutdownOnSignal) |
100 | 180 |
101 def main(argv): | 181 def main(argv): |
102 Emulator().launch() | 182 Emulator().launch() |
103 | 183 |
104 | 184 |
105 if __name__ == '__main__': | 185 if __name__ == '__main__': |
106 main(sys.argv) | 186 main(sys.argv) |
OLD | NEW |