Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: build/android/emulator.py

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

Powered by Google App Engine
This is Rietveld 408576698