OLD | NEW |
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 os |
9 import pipes | 9 import pipes |
10 import select | 10 import select |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
153 if stderr: | 153 if stderr: |
154 logging.critical(stderr) | 154 logging.critical(stderr) |
155 if len(stdout) > 4096: | 155 if len(stdout) > 4096: |
156 logging.debug('Truncated output:') | 156 logging.debug('Truncated output:') |
157 logging.debug(stdout[:4096]) | 157 logging.debug(stdout[:4096]) |
158 return (pipe.returncode, stdout) | 158 return (pipe.returncode, stdout) |
159 | 159 |
160 | 160 |
161 class TimeoutError(Exception): | 161 class TimeoutError(Exception): |
162 """Module-specific timeout exception.""" | 162 """Module-specific timeout exception.""" |
163 pass | 163 |
| 164 def __init__(self, output=None): |
| 165 super(TimeoutError, self).__init__() |
| 166 self._output = output |
| 167 |
| 168 @property |
| 169 def output(self): |
| 170 return self._output |
164 | 171 |
165 | 172 |
166 def _IterProcessStdout(process, timeout=None, buffer_size=4096, | 173 def _IterProcessStdout(process, timeout=None, buffer_size=4096, |
167 poll_interval=1): | 174 poll_interval=1): |
168 assert fcntl, 'fcntl module is required' | 175 assert fcntl, 'fcntl module is required' |
169 try: | 176 try: |
170 # Enable non-blocking reads from the child's stdout. | 177 # Enable non-blocking reads from the child's stdout. |
171 child_fd = process.stdout.fileno() | 178 child_fd = process.stdout.fileno() |
172 fl = fcntl.fcntl(child_fd, fcntl.F_GETFL) | 179 fl = fcntl.fcntl(child_fd, fcntl.F_GETFL) |
173 fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) | 180 fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) |
174 | 181 |
175 end_time = (time.time() + timeout) if timeout else None | 182 end_time = (time.time() + timeout) if timeout else None |
176 while True: | 183 while True: |
177 if end_time and time.time() > end_time: | 184 if end_time and time.time() > end_time: |
178 raise TimeoutError | 185 raise TimeoutError() |
179 read_fds, _, _ = select.select([child_fd], [], [], poll_interval) | 186 read_fds, _, _ = select.select([child_fd], [], [], poll_interval) |
180 if child_fd in read_fds: | 187 if child_fd in read_fds: |
181 data = os.read(child_fd, buffer_size) | 188 data = os.read(child_fd, buffer_size) |
182 if not data: | 189 if not data: |
183 break | 190 break |
184 yield data | 191 yield data |
185 if process.poll() is not None: | 192 if process.poll() is not None: |
186 break | 193 break |
187 finally: | 194 finally: |
188 try: | 195 try: |
(...skipping 20 matching lines...) Expand all Loading... |
209 logfile: Optional file-like object that will receive output from the | 216 logfile: Optional file-like object that will receive output from the |
210 command as it is running. | 217 command as it is running. |
211 | 218 |
212 Returns: | 219 Returns: |
213 The 2-tuple (exit code, output). | 220 The 2-tuple (exit code, output). |
214 """ | 221 """ |
215 _ValidateAndLogCommand(args, cwd, shell) | 222 _ValidateAndLogCommand(args, cwd, shell) |
216 output = StringIO.StringIO() | 223 output = StringIO.StringIO() |
217 process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE, | 224 process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE, |
218 stderr=subprocess.STDOUT) | 225 stderr=subprocess.STDOUT) |
219 for data in _IterProcessStdout(process, timeout=timeout): | 226 try: |
220 if logfile: | 227 for data in _IterProcessStdout(process, timeout=timeout): |
221 logfile.write(data) | 228 if logfile: |
222 output.write(data) | 229 logfile.write(data) |
| 230 output.write(data) |
| 231 except TimeoutError: |
| 232 raise TimeoutError(output.getvalue()) |
| 233 |
223 return process.returncode, output.getvalue() | 234 return process.returncode, output.getvalue() |
224 | 235 |
225 | 236 |
226 def IterCmdOutputLines(args, timeout=None, cwd=None, shell=False, | 237 def IterCmdOutputLines(args, timeout=None, cwd=None, shell=False, |
227 check_status=True): | 238 check_status=True): |
228 """Executes a subprocess and continuously yields lines from its output. | 239 """Executes a subprocess and continuously yields lines from its output. |
229 | 240 |
230 Args: | 241 Args: |
231 args: List of arguments to the program, the program to execute is the first | 242 args: List of arguments to the program, the program to execute is the first |
232 element. | 243 element. |
(...skipping 19 matching lines...) Expand all Loading... |
252 buffer_output += data | 263 buffer_output += data |
253 has_incomplete_line = buffer_output[-1] not in '\r\n' | 264 has_incomplete_line = buffer_output[-1] not in '\r\n' |
254 lines = buffer_output.splitlines() | 265 lines = buffer_output.splitlines() |
255 buffer_output = lines.pop() if has_incomplete_line else '' | 266 buffer_output = lines.pop() if has_incomplete_line else '' |
256 for line in lines: | 267 for line in lines: |
257 yield line | 268 yield line |
258 if buffer_output: | 269 if buffer_output: |
259 yield buffer_output | 270 yield buffer_output |
260 if check_status and process.returncode: | 271 if check_status and process.returncode: |
261 raise subprocess.CalledProcessError(process.returncode, cmd) | 272 raise subprocess.CalledProcessError(process.returncode, cmd) |
OLD | NEW |