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

Side by Side Diff: mojo/tools/mopy/android.py

Issue 1143223005: Report apptest_runner device not found infra failures. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Simplify changes. Created 5 years, 6 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 | « mojo/tools/apptest_runner.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import atexit 5 import atexit
6 import itertools 6 import itertools
7 import logging 7 import logging
8 import os 8 import os
9 import shutil 9 import shutil
10 import signal 10 import signal
11 import subprocess 11 import subprocess
12 import sys 12 import sys
13 import tempfile 13 import tempfile
14 import threading 14 import threading
15 import time 15 import time
16 import urlparse 16 import urlparse
17 17
18 from .paths import Paths 18 from .paths import Paths
19 19
20 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 20 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
21 os.pardir, 'build', 'android')) 21 os.pardir, 'build', 'android'))
22 from pylib import constants 22 from pylib import constants
23 from pylib.base import base_test_runner 23 from pylib.base import base_test_runner
24 from pylib.device import device_errors 24 from pylib.device import device_errors
25 from pylib.device import device_utils 25 from pylib.device import device_utils
26 from pylib.utils import base_error
26 from pylib.utils import apk_helper 27 from pylib.utils import apk_helper
27 28
28 29
29 # Tags used by the mojo shell application logs. 30 # Tags used by the mojo shell application logs.
30 LOGCAT_TAGS = [ 31 LOGCAT_TAGS = [
31 'AndroidHandler', 32 'AndroidHandler',
32 'MojoFileHelper', 33 'MojoFileHelper',
33 'MojoMain', 34 'MojoMain',
34 'MojoShellActivity', 35 'MojoShellActivity',
35 'MojoShellApplication', 36 'MojoShellApplication',
(...skipping 11 matching lines...) Expand all
47 48
48 class AndroidShell(object): 49 class AndroidShell(object):
49 """ 50 """
50 Used to set up and run a given mojo shell binary on an Android device. 51 Used to set up and run a given mojo shell binary on an Android device.
51 |config| is the mopy.config.Config for the build. 52 |config| is the mopy.config.Config for the build.
52 """ 53 """
53 def __init__(self, config): 54 def __init__(self, config):
54 self.adb_path = constants.GetAdbPath() 55 self.adb_path = constants.GetAdbPath()
55 self.paths = Paths(config) 56 self.paths = Paths(config)
56 self.device = None 57 self.device = None
58 self.shell_args = []
57 self.target_package = apk_helper.GetPackageName(self.paths.apk_path) 59 self.target_package = apk_helper.GetPackageName(self.paths.apk_path)
58 # This is used by decive_utils.Install to check if the apk needs updating. 60 # This is used by decive_utils.Install to check if the apk needs updating.
59 constants.SetOutputDirectory(self.paths.build_dir) 61 constants.SetOutputDirectory(self.paths.build_dir)
60 62
61 # TODO(msw): Use pylib's adb_wrapper and device_utils instead. 63 # TODO(msw): Use pylib's adb_wrapper and device_utils instead.
62 def _CreateADBCommand(self, args): 64 def _CreateADBCommand(self, args):
63 adb_command = [self.adb_path, '-s', self.device.adb.GetDeviceSerial()] 65 adb_command = [self.adb_path, '-s', self.device.adb.GetDeviceSerial()]
64 adb_command.extend(args) 66 adb_command.extend(args)
65 logging.getLogger().debug("Command: %s", " ".join(adb_command)) 67 logging.getLogger().debug("Command: %s", " ".join(adb_command))
66 return adb_command 68 return adb_command
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
124 return [] 126 return []
125 127
126 original_values = list(itertools.chain( 128 original_values = list(itertools.chain(
127 *map(lambda x: x[len(MAPPING_PREFIX):].split(','), map_parameters))) 129 *map(lambda x: x[len(MAPPING_PREFIX):].split(','), map_parameters)))
128 sorted(original_values) 130 sorted(original_values)
129 result = [] 131 result = []
130 for value in original_values: 132 for value in original_values:
131 result.append(self._StartHttpServerForOriginMapping(value)) 133 result.append(self._StartHttpServerForOriginMapping(value))
132 return [MAPPING_PREFIX + ','.join(result)] 134 return [MAPPING_PREFIX + ','.join(result)]
133 135
134 def PrepareShellRun(self, origin=None, device=None, gdb=False): 136 def InitShell(self, origin='localhost', device=None):
135 """ 137 """
136 Prepares for StartShell: runs adb as root and installs the apk as needed. 138 Runs adb as root, starts an origin server, and installs the apk as needed.
137 If the origin specified is 'localhost', a local http server will be set up 139 |origin| is the origin for mojo: URLs; if its value is 'localhost', a local
138 to serve files from the build directory along with port forwarding. 140 http server will be set up to serve files from the build directory.
139 |device| is the device to run on, if multiple devices are connected. 141 |device| is the target device to run on, if multiple devices are connected.
140 Returns arguments that should be appended to shell argument list. 142 Returns 0 on success or a non-zero exit code on a terminal failure.
141 """ 143 """
142 devices = device_utils.DeviceUtils.HealthyDevices() 144 try:
143 if device: 145 devices = device_utils.DeviceUtils.HealthyDevices()
144 self.device = next((d for d in devices if d == device), None) 146 if device:
145 if not self.device: 147 self.device = next((d for d in devices if d == device), None)
146 raise device_errors.DeviceUnreachableError(device) 148 if not self.device:
147 elif devices: 149 raise device_errors.DeviceUnreachableError(device)
148 self.device = devices[0] 150 elif devices:
149 else: 151 self.device = devices[0]
150 raise device_errors.NoDevicesError() 152 else:
153 raise device_errors.NoDevicesError()
151 154
152 logging.getLogger().debug("Using device: %s", self.device) 155 logging.getLogger().debug("Using device: %s", self.device)
153 self.device.EnableRoot() 156 # Clean the logs on the device to avoid displaying prior activity.
157 subprocess.check_call(self._CreateADBCommand(['logcat', '-c']))
158 self.device.EnableRoot()
159 self.device.Install(self.paths.apk_path)
160 except base_error.BaseError as e:
161 # Report "device not found" as infra failures. See http://crbug.com/493900
162 print "Exception in AndroidShell.InitShell:\n%s" % str(e)
163 if e.is_infra_error or "error: device not found" in str(e):
164 return constants.INFRA_EXIT_CODE
165 return constants.ERROR_EXIT_CODE
154 166
155 # TODO(msw): Install fails often, retry as needed; http://crbug.com/493900
156 try:
157 self.device.Install(self.paths.apk_path)
158 except device_errors.CommandFailedError as e:
159 logging.getLogger().error("APK install failed:\n%s", str(e))
160 self.device.Install(self.paths.apk_path)
161
162 extra_args = []
163 if origin is 'localhost': 167 if origin is 'localhost':
164 origin = self._StartHttpServerForDirectory(self.paths.build_dir) 168 origin = self._StartHttpServerForDirectory(self.paths.build_dir)
165 if origin: 169 if origin:
166 extra_args.append("--origin=" + origin) 170 self.shell_args.append("--origin=" + origin)
167 171 return 0
168 if gdb:
169 # Remote debugging needs a port forwarded.
170 self.device.adb.Forward('tcp:5039', 'tcp:5039')
171
172 return extra_args
173 172
174 def _GetProcessId(self, process): 173 def _GetProcessId(self, process):
175 """Returns the process id of the process on the remote device.""" 174 """Returns the process id of the process on the remote device."""
176 while True: 175 while True:
177 line = process.stdout.readline() 176 line = process.stdout.readline()
178 pid_command = 'launcher waiting for GDB. pid: ' 177 pid_command = 'launcher waiting for GDB. pid: '
179 index = line.find(pid_command) 178 index = line.find(pid_command)
180 if index != -1: 179 if index != -1:
181 return line[index + len(pid_command):].strip() 180 return line[index + len(pid_command):].strip()
182 return 0 181 return 0
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
222 time.sleep(1) 221 time.sleep(1)
223 222
224 local_gdb_process = subprocess.Popen([self._GetLocalGdbPath(), 223 local_gdb_process = subprocess.Popen([self._GetLocalGdbPath(),
225 "-x", 224 "-x",
226 gdbinit_path], 225 gdbinit_path],
227 cwd=temp_dir) 226 cwd=temp_dir)
228 atexit.register(_ExitIfNeeded, local_gdb_process) 227 atexit.register(_ExitIfNeeded, local_gdb_process)
229 local_gdb_process.wait() 228 local_gdb_process.wait()
230 signal.signal(signal.SIGINT, signal.SIG_DFL) 229 signal.signal(signal.SIGINT, signal.SIG_DFL)
231 230
232 def StartShell(self, arguments, stdout, on_application_stop, gdb=False): 231 def StartShell(self, arguments, stdout, on_fifo_closed, gdb=False):
233 """ 232 """
234 Starts the shell with the given arguments, directing output to |stdout|. 233 Starts the shell with the given |arguments|, directing output to |stdout|.
235 The |arguments| list must contain the "--origin=" arg from PrepareShellRun. 234 |on_fifo_closed| will be run if the FIFO can't be found or when it's closed.
235 |gdb| is a flag that attaches gdb to the device's remote process on startup.
236 """ 236 """
237 assert self.device
238 arguments += self.shell_args
239
237 cmd = self._CreateADBCommand([ 240 cmd = self._CreateADBCommand([
238 'shell', 241 'shell',
239 'am', 242 'am',
240 'start', 243 'start',
241 '-S', 244 '-S',
242 '-a', 'android.intent.action.VIEW', 245 '-a', 'android.intent.action.VIEW',
243 '-n', '%s/%s.MojoShellActivity' % (self.target_package, 246 '-n', '%s/%s.MojoShellActivity' % (self.target_package,
244 'org.chromium.mojo.shell')]) 247 'org.chromium.mojo.shell')])
245 248
246 logcat_process = None 249 logcat_process = None
247 if gdb: 250 if gdb:
248 arguments += ['--wait-for-debugger'] 251 arguments.append('--wait-for-debugger')
252 # Remote debugging needs a port forwarded.
253 self.device.adb.Forward('tcp:5039', 'tcp:5039')
249 logcat_process = self.ShowLogs(stdout=subprocess.PIPE) 254 logcat_process = self.ShowLogs(stdout=subprocess.PIPE)
250 255
251 fifo_path = "/data/data/%s/stdout.fifo" % self.target_package 256 fifo_path = "/data/data/%s/stdout.fifo" % self.target_package
252 subprocess.check_call(self._CreateADBCommand( 257 subprocess.check_call(self._CreateADBCommand(
253 ['shell', 'rm', '-f', fifo_path])) 258 ['shell', 'rm', '-f', fifo_path]))
254 arguments.append('--fifo-path=%s' % fifo_path) 259 arguments.append('--fifo-path=%s' % fifo_path)
255 max_attempts = 200 if '--wait-for-debugger' in arguments else 5 260 max_attempts = 200 if '--wait-for-debugger' in arguments else 5
256 self._ReadFifo(fifo_path, stdout, on_application_stop, max_attempts) 261 self._ReadFifo(fifo_path, stdout, on_fifo_closed, max_attempts)
257 262
258 # Extract map-origin args and add the extras array with commas escaped. 263 # Extract map-origin args and add the extras array with commas escaped.
259 parameters = [a for a in arguments if not a.startswith(MAPPING_PREFIX)] 264 parameters = [a for a in arguments if not a.startswith(MAPPING_PREFIX)]
260 map_parameters = [a for a in arguments if a.startswith(MAPPING_PREFIX)] 265 map_parameters = [a for a in arguments if a.startswith(MAPPING_PREFIX)]
261 parameters += self._StartHttpServerForOriginMappings(map_parameters) 266 parameters += self._StartHttpServerForOriginMappings(map_parameters)
262 parameters = [p.replace(',', '\,') for p in parameters] 267 parameters = [p.replace(',', '\,') for p in parameters]
263 if parameters: 268 cmd += ['--esa', 'org.chromium.mojo.shell.extras', ','.join(parameters)]
264 cmd += ['--esa', 'org.chromium.mojo.shell.extras', ','.join(parameters)]
265 269
266 atexit.register(self.StopShell) 270 atexit.register(self.StopShell)
267 with open(os.devnull, 'w') as devnull: 271 with open(os.devnull, 'w') as devnull:
268 cmd_process = subprocess.Popen(cmd, stdout=devnull) 272 cmd_process = subprocess.Popen(cmd, stdout=devnull)
269 if logcat_process: 273 if logcat_process:
270 self._WaitForProcessIdAndStartGdb(logcat_process) 274 self._WaitForProcessIdAndStartGdb(logcat_process)
271 cmd_process.wait() 275 cmd_process.wait()
272 276
273 def StopShell(self): 277 def StopShell(self):
274 """Stops the mojo shell.""" 278 """Stops the mojo shell."""
275 self.device.ForceStop(self.target_package) 279 self.device.ForceStop(self.target_package)
276 280
277 def CleanLogs(self):
278 """Cleans the logs on the device."""
279 subprocess.check_call(self._CreateADBCommand(['logcat', '-c']))
280
281 def ShowLogs(self, stdout=sys.stdout): 281 def ShowLogs(self, stdout=sys.stdout):
282 """Displays the mojo shell logs and returns the process reading the logs.""" 282 """Displays the mojo shell logs and returns the process reading the logs."""
283 logcat = subprocess.Popen(self._CreateADBCommand([ 283 logcat = subprocess.Popen(self._CreateADBCommand([
284 'logcat', 284 'logcat',
285 '-s', 285 '-s',
286 ' '.join(LOGCAT_TAGS)]), 286 ' '.join(LOGCAT_TAGS)]),
287 stdout=stdout) 287 stdout=stdout)
288 atexit.register(_ExitIfNeeded, logcat) 288 atexit.register(_ExitIfNeeded, logcat)
289 return logcat 289 return logcat
290 290
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 files_to_link = { 330 files_to_link = {
331 'html_viewer.mojo': ['libhtml_viewer', 'html_viewer_library.so'], 331 'html_viewer.mojo': ['libhtml_viewer', 'html_viewer_library.so'],
332 'libmandoline_runner.so': ['mandoline_runner'], 332 'libmandoline_runner.so': ['mandoline_runner'],
333 } 333 }
334 for android_name, so_path in files_to_link.iteritems(): 334 for android_name, so_path in files_to_link.iteritems():
335 src = os.path.join(build_dir, *so_path) 335 src = os.path.join(build_dir, *so_path)
336 if not os.path.isfile(src): 336 if not os.path.isfile(src):
337 print 'Expected file not found', src 337 print 'Expected file not found', src
338 sys.exit(-1) 338 sys.exit(-1)
339 os.symlink(src, os.path.join(dest_dir, android_name)) 339 os.symlink(src, os.path.join(dest_dir, android_name))
OLDNEW
« no previous file with comments | « mojo/tools/apptest_runner.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698