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 |