| 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 |