OLD | NEW |
---|---|
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 """Provides an interface to start and stop Android emulator. |
8 | 8 |
9 Assumes system environment ANDROID_NDK_ROOT has been set. | 9 Assumes system environment ANDROID_NDK_ROOT has been set. |
10 | 10 |
11 Emulator: The class provides the methods to launch/shutdown the emulator with | 11 Emulator: The class provides the methods to launch/shutdown the emulator with |
12 the android virtual device named 'avd_armeabi' . | 12 the android virtual device named 'avd_armeabi' . |
13 """ | 13 """ |
14 | 14 |
15 import logging | 15 import logging |
16 import os | 16 import os |
17 import shutil | |
17 import signal | 18 import signal |
18 import subprocess | 19 import subprocess |
frankf
2013/04/09 22:32:14
No longer used. Please run gpylint.
navabi
2013/04/09 22:40:05
That's not true. It is still used in this file (ju
| |
19 import sys | 20 import sys |
20 import time | 21 import time |
21 | 22 |
22 import time_profile | 23 import time_profile |
23 # TODO(craigdh): Move these pylib dependencies to pylib/utils/. | 24 # TODO(craigdh): Move these pylib dependencies to pylib/utils/. |
24 from pylib import android_commands | 25 from pylib import android_commands |
25 from pylib import cmd_helper | 26 from pylib import cmd_helper |
26 from pylib import constants | 27 from pylib import constants |
28 from pylib import pexpect | |
27 | 29 |
28 import errors | 30 import errors |
29 import run_command | 31 import run_command |
30 | 32 |
31 # Android API level | 33 # Android API level |
32 API_TARGET = 'android-%s' % constants.ANDROID_SDK_VERSION | 34 API_TARGET = 'android-%s' % constants.ANDROID_SDK_VERSION |
33 | 35 |
34 | 36 |
35 class EmulatorLaunchException(Exception): | 37 class EmulatorLaunchException(Exception): |
36 """Emulator failed to launch.""" | 38 """Emulator failed to launch.""" |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
153 # the time to launch the emulator and a wait-for-device command. | 155 # the time to launch the emulator and a wait-for-device command. |
154 _LAUNCH_TIMEOUT = 120 | 156 _LAUNCH_TIMEOUT = 120 |
155 | 157 |
156 # Timeout interval of wait-for-device command before bouncing to a a | 158 # Timeout interval of wait-for-device command before bouncing to a a |
157 # process life check. | 159 # process life check. |
158 _WAITFORDEVICE_TIMEOUT = 5 | 160 _WAITFORDEVICE_TIMEOUT = 5 |
159 | 161 |
160 # Time to wait for a "wait for boot complete" (property set on device). | 162 # Time to wait for a "wait for boot complete" (property set on device). |
161 _WAITFORBOOT_TIMEOUT = 300 | 163 _WAITFORBOOT_TIMEOUT = 300 |
162 | 164 |
163 def __init__(self, avd_name, abi='x86'): | 165 def __init__(self, avd_name, abi): |
164 """Init an Emulator. | 166 """Init an Emulator. |
165 | 167 |
166 Args: | 168 Args: |
167 avd_name: name of the AVD to create | 169 avd_name: name of the AVD to create |
168 abi: target platform for emulator being created | 170 abi: target platform for emulator being created |
169 """ | 171 """ |
170 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, | 172 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, |
171 'android_tools', 'sdk') | 173 'android_tools', 'sdk') |
172 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') | 174 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') |
173 self.android = os.path.join(android_sdk_root, 'tools', 'android') | 175 self.android = os.path.join(android_sdk_root, 'tools', 'android') |
174 self.popen = None | 176 self.popen = None |
175 self.device = None | 177 self.device = None |
176 self.abi = abi | 178 self.abi = abi |
177 self.avd_name = avd_name | 179 self.avd_name = avd_name |
178 self._CreateAVD() | 180 self._CreateAVD() |
179 | 181 |
180 def _DeviceName(self): | 182 def _DeviceName(self): |
181 """Return our device name.""" | 183 """Return our device name.""" |
182 port = _GetAvailablePort() | 184 port = _GetAvailablePort() |
183 return ('emulator-%d' % port, port) | 185 return ('emulator-%d' % port, port) |
184 | 186 |
185 def _CreateAVD(self): | 187 def _CreateAVD(self): |
186 """Creates an AVD with the given name. | 188 """Creates an AVD with the given name. |
187 | 189 |
188 Return avd_name. | 190 Return avd_name. |
189 """ | 191 """ |
192 | |
193 if self.abi == 'arm': | |
194 abi_option = 'armeabi-v7a' | |
195 else: | |
196 abi_option = 'x86' | |
197 | |
190 avd_command = [ | 198 avd_command = [ |
191 self.android, | 199 self.android, |
192 '--silent', | 200 '--silent', |
193 'create', 'avd', | 201 'create', 'avd', |
194 '--name', self.avd_name, | 202 '--name', self.avd_name, |
195 '--abi', self.abi, | 203 '--abi', abi_option, |
196 '--target', API_TARGET, | 204 '--target', API_TARGET, |
197 '-c', '128M', | |
198 '--force', | 205 '--force', |
199 ] | 206 ] |
200 avd_process = subprocess.Popen(args=avd_command, | 207 avd_cmd_str = ' '.join(avd_command) |
201 stdin=subprocess.PIPE, | 208 logging.info('Create AVD command: %s', avd_cmd_str) |
202 stdout=subprocess.PIPE, | 209 avd_process = pexpect.spawn(avd_cmd_str) |
203 stderr=subprocess.STDOUT) | 210 |
204 avd_process.stdin.write('no\n') | 211 # Instead of creating a custom profile, we overwrite config files. |
205 avd_process.wait() | 212 avd_process.expect('Do you wish to create a custom hardware profile') |
206 logging.info('Create AVD command: %s', ' '.join(avd_command)) | 213 avd_process.sendline('no\n') |
214 avd_process.expect('Created AVD \'%s\'' % self.avd_name) | |
215 | |
216 # Setup test device as default Galaxy Nexus AVD | |
217 avd_config_dir = os.path.join(constants.CHROME_DIR, 'build', 'android', | |
218 'avd_configs') | |
219 avd_config_ini = os.path.join(avd_config_dir, | |
220 'AVD_for_Galaxy_Nexus_by_Google_%s.avd' % | |
221 self.abi, 'config.ini') | |
222 | |
223 # Replace current configuration with default Galaxy Nexus config. | |
224 avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd') | |
225 ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name) | |
226 new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name, | |
227 'config.ini') | |
228 | |
229 # Remove config files with defaults to replace with Google's GN settings. | |
230 os.unlink(ini_file) | |
231 os.unlink(new_config_ini) | |
232 | |
233 # Create new configuration files with Galaxy Nexus by Google settings. | |
234 with open(ini_file, 'w') as new_ini: | |
235 new_ini.write('avd.ini.encoding=ISO-8859-1\n') | |
236 new_ini.write('target=%s\n' % API_TARGET) | |
237 new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name)) | |
238 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name) | |
239 | |
240 shutil.copy(avd_config_ini, new_config_ini) | |
207 return self.avd_name | 241 return self.avd_name |
208 | 242 |
243 | |
209 def _DeleteAVD(self): | 244 def _DeleteAVD(self): |
210 """Delete the AVD of this emulator.""" | 245 """Delete the AVD of this emulator.""" |
211 avd_command = [ | 246 avd_command = [ |
212 self.android, | 247 self.android, |
213 '--silent', | 248 '--silent', |
214 'delete', | 249 'delete', |
215 'avd', | 250 'avd', |
216 '--name', self.avd_name, | 251 '--name', self.avd_name, |
217 ] | 252 ] |
218 avd_process = subprocess.Popen(args=avd_command, | |
219 stdout=subprocess.PIPE, | |
220 stderr=subprocess.STDOUT) | |
221 logging.info('Delete AVD command: %s', ' '.join(avd_command)) | 253 logging.info('Delete AVD command: %s', ' '.join(avd_command)) |
222 avd_process.wait() | 254 cmd_helper.GetCmdOutput(avd_command) |
frankf
2013/04/09 22:37:29
Also just do cmd_helper.RunCmd if you don't need t
navabi
2013/04/09 22:44:33
Done. (along with two other instances).
| |
255 | |
223 | 256 |
224 def Launch(self, kill_all_emulators): | 257 def Launch(self, kill_all_emulators): |
225 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the | 258 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the |
226 emulator is ready for use. | 259 emulator is ready for use. |
227 | 260 |
228 If fails, an exception will be raised. | 261 If fails, an exception will be raised. |
229 """ | 262 """ |
230 if kill_all_emulators: | 263 if kill_all_emulators: |
231 _KillAllEmulators() # just to be sure | 264 _KillAllEmulators() # just to be sure |
232 self._AggressiveImageCleanup() | 265 self._AggressiveImageCleanup() |
(...skipping 17 matching lines...) Expand all Loading... | |
250 ] | 283 ] |
251 if self.abi == 'x86': | 284 if self.abi == 'x86': |
252 emulator_command.extend([ | 285 emulator_command.extend([ |
253 # For x86 emulator --enable-kvm will fail early, avoiding accidental | 286 # For x86 emulator --enable-kvm will fail early, avoiding accidental |
254 # runs in a slow mode (i.e. without hardware virtualization support). | 287 # runs in a slow mode (i.e. without hardware virtualization support). |
255 '--enable-kvm', | 288 '--enable-kvm', |
256 ]) | 289 ]) |
257 | 290 |
258 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) | 291 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) |
259 self.popen = subprocess.Popen(args=emulator_command, | 292 self.popen = subprocess.Popen(args=emulator_command, |
260 stderr=subprocess.STDOUT) | 293 stderr=subprocess.STDOUT) |
navabi
2013/04/09 22:40:05
This one is not as easy to remove because we poll
| |
261 self._InstallKillHandler() | 294 self._InstallKillHandler() |
262 | 295 |
263 def _AggressiveImageCleanup(self): | 296 def _AggressiveImageCleanup(self): |
264 """Aggressive cleanup of emulator images. | 297 """Aggressive cleanup of emulator images. |
265 | 298 |
266 Experimentally it looks like our current emulator use on the bot | 299 Experimentally it looks like our current emulator use on the bot |
267 leaves image files around in /tmp/android-$USER. If a "random" | 300 leaves image files around in /tmp/android-$USER. If a "random" |
268 name gets reused, we choke with a 'File exists' error. | 301 name gets reused, we choke with a 'File exists' error. |
269 TODO(jrg): is there a less hacky way to accomplish the same goal? | 302 TODO(jrg): is there a less hacky way to accomplish the same goal? |
270 """ | 303 """ |
(...skipping 23 matching lines...) Expand all Loading... | |
294 run_command.RunCommand(adb_cmd, | 327 run_command.RunCommand(adb_cmd, |
295 timeout_time=self._WAITFORDEVICE_TIMEOUT, | 328 timeout_time=self._WAITFORDEVICE_TIMEOUT, |
296 retry_count=1) | 329 retry_count=1) |
297 number_of_waits -= 1 | 330 number_of_waits -= 1 |
298 if not number_of_waits: | 331 if not number_of_waits: |
299 break | 332 break |
300 except errors.WaitForResponseTimedOutError as e: | 333 except errors.WaitForResponseTimedOutError as e: |
301 seconds_waited += self._WAITFORDEVICE_TIMEOUT | 334 seconds_waited += self._WAITFORDEVICE_TIMEOUT |
302 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') | 335 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') |
303 run_command.RunCommand(adb_cmd) | 336 run_command.RunCommand(adb_cmd) |
304 self.popen.poll() | 337 self.popen.poll() |
navabi
2013/04/09 22:40:05
here will poll the subprocess.
| |
305 if self.popen.returncode != None: | 338 if self.popen.returncode != None: |
306 raise EmulatorLaunchException('EMULATOR DIED') | 339 raise EmulatorLaunchException('EMULATOR DIED') |
307 if seconds_waited >= self._LAUNCH_TIMEOUT: | 340 if seconds_waited >= self._LAUNCH_TIMEOUT: |
308 raise EmulatorLaunchException('TIMEOUT with wait-for-device') | 341 raise EmulatorLaunchException('TIMEOUT with wait-for-device') |
309 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) | 342 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) |
310 if wait_for_boot: | 343 if wait_for_boot: |
311 # Now that we checked for obvious problems, wait for a boot complete. | 344 # Now that we checked for obvious problems, wait for a boot complete. |
312 # Waiting for the package manager is sometimes problematic. | 345 # Waiting for the package manager is sometimes problematic. |
313 a = android_commands.AndroidCommands(self.device) | 346 a = android_commands.AndroidCommands(self.device) |
314 a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT) | 347 a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT) |
315 | 348 |
316 def Shutdown(self): | 349 def Shutdown(self): |
317 """Shuts down the process started by launch.""" | 350 """Shuts down the process started by launch.""" |
318 self._DeleteAVD() | 351 self._DeleteAVD() |
319 if self.popen: | 352 if self.popen: |
320 self.popen.poll() | 353 self.popen.poll() |
navabi
2013/04/09 22:40:05
here too.
| |
321 if self.popen.returncode == None: | 354 if self.popen.returncode == None: |
322 self.popen.kill() | 355 self.popen.kill() |
323 self.popen = None | 356 self.popen = None |
324 | 357 |
325 def _ShutdownOnSignal(self, signum, frame): | 358 def _ShutdownOnSignal(self, signum, frame): |
326 logging.critical('emulator _ShutdownOnSignal') | 359 logging.critical('emulator _ShutdownOnSignal') |
327 for sig in self._SIGNALS: | 360 for sig in self._SIGNALS: |
328 signal.signal(sig, signal.SIG_DFL) | 361 signal.signal(sig, signal.SIG_DFL) |
329 self.Shutdown() | 362 self.Shutdown() |
330 raise KeyboardInterrupt # print a stack | 363 raise KeyboardInterrupt # print a stack |
331 | 364 |
332 def _InstallKillHandler(self): | 365 def _InstallKillHandler(self): |
333 """Install a handler to kill the emulator when we exit unexpectedly.""" | 366 """Install a handler to kill the emulator when we exit unexpectedly.""" |
334 for sig in self._SIGNALS: | 367 for sig in self._SIGNALS: |
335 signal.signal(sig, self._ShutdownOnSignal) | 368 signal.signal(sig, self._ShutdownOnSignal) |
OLD | NEW |