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

Side by Side Diff: build/android/pylib/cmd_helper.py

Issue 300063017: Fix silent failures in test runner that lead to long timeouts (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Unbreak windows. Created 6 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | build/android/pylib/perf/test_runner.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 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 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 """A wrapper for subprocess to make calling shell commands easier.""" 5 """A wrapper for subprocess to make calling shell commands easier."""
6 6
7 import logging 7 import logging
8 import os
8 import pipes 9 import pipes
10 import select
9 import signal 11 import signal
12 import StringIO
10 import subprocess 13 import subprocess
11 import tempfile 14 import time
12 15
13 from pylib.utils import timeout_retry 16 # fcntl is not available on Windows.
17 try:
18 import fcntl
19 except ImportError:
20 fcntl = None
14 21
15 22
16 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): 23 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
17 return subprocess.Popen( 24 return subprocess.Popen(
18 args=args, cwd=cwd, stdout=stdout, stderr=stderr, 25 args=args, cwd=cwd, stdout=stdout, stderr=stderr,
19 shell=shell, close_fds=True, env=env, 26 shell=shell, close_fds=True, env=env,
20 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) 27 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL))
21 28
22 29
23 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): 30 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
81 elif shell: 88 elif shell:
82 raise Exception('array args must be run with shell=False') 89 raise Exception('array args must be run with shell=False')
83 else: 90 else:
84 args_repr = ' '.join(map(pipes.quote, args)) 91 args_repr = ' '.join(map(pipes.quote, args))
85 92
86 s = '[host]' 93 s = '[host]'
87 if cwd: 94 if cwd:
88 s += ':' + cwd 95 s += ':' + cwd
89 s += '> ' + args_repr 96 s += '> ' + args_repr
90 logging.info(s) 97 logging.info(s)
91 tmpout = tempfile.TemporaryFile(bufsize=0) 98 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
92 tmperr = tempfile.TemporaryFile(bufsize=0) 99 shell=shell, cwd=cwd)
93 exit_code = Call(args, cwd=cwd, stdout=tmpout, stderr=tmperr, shell=shell) 100 stdout, stderr = pipe.communicate()
94 tmperr.seek(0) 101
95 stderr = tmperr.read()
96 tmperr.close()
97 if stderr: 102 if stderr:
98 logging.critical(stderr) 103 logging.critical(stderr)
99 tmpout.seek(0)
100 stdout = tmpout.read()
101 tmpout.close()
102 if len(stdout) > 4096: 104 if len(stdout) > 4096:
103 logging.debug('Truncated output:') 105 logging.debug('Truncated output:')
104 logging.debug(stdout[:4096]) 106 logging.debug(stdout[:4096])
105 return (exit_code, stdout) 107 return (pipe.returncode, stdout)
106 108
107 109
108 def GetCmdStatusAndOutputWithTimeoutAndRetries(args, timeout, retries): 110 class TimeoutError(Exception):
109 """Executes a subprocess with a timeout and retries. 111 """Module-specific timeout exception."""
112 pass
113
114
115 def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
116 logfile=None):
117 """Executes a subprocess with a timeout.
110 118
111 Args: 119 Args:
112 args: List of arguments to the program, the program to execute is the first 120 args: List of arguments to the program, the program to execute is the first
113 element. 121 element.
114 timeout: the timeout in seconds. 122 timeout: the timeout in seconds or None to wait forever.
115 retries: the number of retries. 123 cwd: If not None, the subprocess's current directory will be changed to
124 |cwd| before it's executed.
125 shell: Whether to execute args as a shell command.
126 logfile: Optional file-like object that will receive output from the
127 command as it is running.
116 128
117 Returns: 129 Returns:
118 The 2-tuple (exit code, output). 130 The 2-tuple (exit code, output).
119 """ 131 """
120 return timeout_retry.Run(GetCmdStatusAndOutput, timeout, retries, [args]) 132 assert fcntl, 'fcntl module is required'
133 process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
134 stderr=subprocess.STDOUT)
135 try:
136 end_time = (time.time() + timeout) if timeout else None
137 poll_interval = 1
138 buffer_size = 4096
139 child_fd = process.stdout.fileno()
140 output = StringIO.StringIO()
141
142 # Enable non-blocking reads from the child's stdout.
143 fl = fcntl.fcntl(child_fd, fcntl.F_GETFL)
144 fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
145
146 while True:
147 if end_time and time.time() > end_time:
148 raise TimeoutError
149 read_fds, _, _ = select.select([child_fd], [], [], poll_interval)
150 if child_fd in read_fds:
151 data = os.read(child_fd, buffer_size)
152 if not data:
153 break
154 if logfile:
155 logfile.write(data)
156 output.write(data)
157 if process.poll() is not None:
158 break
159 finally:
160 try:
161 # Make sure the process doesn't stick around if we fail with an
162 # exception.
163 process.kill()
164 except OSError:
165 pass
166 process.wait()
167 return process.returncode, output.getvalue()
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/perf/test_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698