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

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: fixed docstring 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
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, fast_and_loose):
133 """Init an Emulator.
134
135 Args:
136 nwe_avd_name: If set, will create a new temporary AVD.
137 fast_and_loose: Loosen up the rules for reliable running for speed.
138 Intended for quick testing or re-testing.
139
140 """
141 try:
142 android_sdk_root = os.environ['ANDROID_SDK_ROOT']
143 except KeyError:
144 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on '
145 'emulator.')
146 raise
147 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
148 self.android = os.path.join(android_sdk_root, 'tools', 'android')
149 self.popen = None
150 self.device = None
151 self.default_avd = True
152 self.fast_and_loose = fast_and_loose
153 self.abi = 'armeabi-v7a'
154 self.avd = 'avd_armeabi'
155 if 'x86' in os.environ.get('TARGET_PRODUCT', ''):
156 self.abi = 'x86'
157 self.avd = 'avd_x86'
158 if new_avd_name:
159 self.default_avd = False
160 self.avd = self._CreateAVD(new_avd_name)
161
162 def _DeviceName(self):
163 """Return our device name."""
164 port = _GetAvailablePort()
165 return ('emulator-%d' % port, port)
166
167 def _CreateAVD(self, avd_name):
168 """Creates an AVD with the given name.
169
170 Return avd_name.
171 """
172 avd_command = [
173 self.android,
174 '--silent',
175 'create', 'avd',
176 '--name', avd_name,
177 '--abi', self.abi,
178 '--target', 'android-16',
179 '-c', '128M',
180 '--force',
181 ]
182 avd_process = subprocess.Popen(args=avd_command,
183 stdin=subprocess.PIPE,
184 stdout=subprocess.PIPE,
185 stderr=subprocess.STDOUT)
186 avd_process.stdin.write('no\n')
187 avd_process.wait()
188 logging.info('Create AVD command: %s', ' '.join(avd_command))
189 return avd_name
190
191 def _DeleteAVD(self):
192 """Delete the AVD of this emulator."""
193 avd_command = [
194 self.android,
195 '--silent',
196 'delete',
197 'avd',
198 '--name', self.avd,
199 ]
200 avd_process = subprocess.Popen(args=avd_command,
201 stdout=subprocess.PIPE,
202 stderr=subprocess.STDOUT)
203 logging.info('Delete AVD command: %s', ' '.join(avd_command))
204 avd_process.wait()
205
206 def Launch(self, kill_all_emulators):
207 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
208 emulator is ready for use.
209
210 If fails, an exception will be raised.
211 """
212 if kill_all_emulators:
213 _KillAllEmulators() # just to be sure
214 if not self.fast_and_loose:
215 self._AggressiveImageCleanup()
216 (self.device, port) = self._DeviceName()
217 emulator_command = [
218 self.emulator,
219 # Speed up emulator launch by 40%. Really.
220 '-no-boot-anim',
221 # The default /data size is 64M.
222 # That's not enough for 8 unit test bundles and their data.
223 '-partition-size', '512',
224 # Enable GPU by default.
225 '-gpu', 'on',
226 # Use a familiar name and port.
227 '-avd', self.avd,
228 '-port', str(port)]
229 if not self.fast_and_loose:
230 emulator_command.extend([
231 # Wipe the data. We've seen cases where an emulator
232 # gets 'stuck' if we don't do this (every thousand runs or
233 # so).
234 '-wipe-data',
235 ])
236 logging.info('Emulator launch command: %s', ' '.join(emulator_command))
237 self.popen = subprocess.Popen(args=emulator_command,
238 stderr=subprocess.STDOUT)
239 self._InstallKillHandler()
240
241 def _AggressiveImageCleanup(self):
242 """Aggressive cleanup of emulator images.
243
244 Experimentally it looks like our current emulator use on the bot
245 leaves image files around in /tmp/android-$USER. If a "random"
246 name gets reused, we choke with a 'File exists' error.
247 TODO(jrg): is there a less hacky way to accomplish the same goal?
248 """
249 logging.info('Aggressive Image Cleanup')
250 emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
251 if not os.path.exists(emulator_imagedir):
252 return
253 for image in os.listdir(emulator_imagedir):
254 full_name = os.path.join(emulator_imagedir, image)
255 if 'emulator' in full_name:
256 logging.info('Deleting emulator image %s', full_name)
257 os.unlink(full_name)
258
259 def ConfirmLaunch(self, wait_for_boot=False):
260 """Confirm the emulator launched properly.
261
262 Loop on a wait-for-device with a very small timeout. On each
263 timeout, check the emulator process is still alive.
264 After confirming a wait-for-device can be successful, make sure
265 it returns the right answer.
266 """
267 seconds_waited = 0
268 number_of_waits = 2 # Make sure we can wfd twice
269 adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device')
270 while seconds_waited < self._LAUNCH_TIMEOUT:
271 try:
272 run_command.RunCommand(adb_cmd,
273 timeout_time=self._WAITFORDEVICE_TIMEOUT,
274 retry_count=1)
275 number_of_waits -= 1
276 if not number_of_waits:
277 break
278 except errors.WaitForResponseTimedOutError as e:
279 seconds_waited += self._WAITFORDEVICE_TIMEOUT
280 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server')
281 run_command.RunCommand(adb_cmd)
282 self.popen.poll()
283 if self.popen.returncode != None:
284 raise EmulatorLaunchException('EMULATOR DIED')
285 if seconds_waited >= self._LAUNCH_TIMEOUT:
286 raise EmulatorLaunchException('TIMEOUT with wait-for-device')
287 logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
288 if wait_for_boot:
289 # Now that we checked for obvious problems, wait for a boot complete.
290 # Waiting for the package manager is sometimes problematic.
291 a = android_commands.AndroidCommands(self.device)
292 a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT)
293
294 def Shutdown(self):
295 """Shuts down the process started by launch."""
296 if not self.default_avd:
297 self._DeleteAVD()
298 if self.popen:
299 self.popen.poll()
300 if self.popen.returncode == None:
301 self.popen.kill()
302 self.popen = None
303
304 def _ShutdownOnSignal(self, signum, frame):
305 logging.critical('emulator _ShutdownOnSignal')
306 for sig in self._SIGNALS:
307 signal.signal(sig, signal.SIG_DFL)
308 self.Shutdown()
309 raise KeyboardInterrupt # print a stack
310
311 def _InstallKillHandler(self):
312 """Install a handler to kill the emulator when we exit unexpectedly."""
313 for sig in self._SIGNALS:
314 signal.signal(sig, self._ShutdownOnSignal)
315
316 def main(argv): 18 def main(argv):
317 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)
318 29
319 30
320 if __name__ == '__main__': 31 if __name__ == '__main__':
321 main(sys.argv) 32 main(sys.argv)
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/utils/emulator.py » ('j') | build/android/pylib/utils/emulator.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698