| OLD | NEW |
| 1 # Copyright (C) 2010 Google Inc. All rights reserved. | 1 # Copyright (C) 2010 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 def quote_data(data): | 65 def quote_data(data): |
| 66 txt = repr(data).replace('\\n', '\\n\n')[1:-1] | 66 txt = repr(data).replace('\\n', '\\n\n')[1:-1] |
| 67 lines = [] | 67 lines = [] |
| 68 for l in txt.splitlines(): | 68 for l in txt.splitlines(): |
| 69 m = _trailing_spaces_re.match(l) | 69 m = _trailing_spaces_re.match(l) |
| 70 if m: | 70 if m: |
| 71 l = m.group(1) + m.group(2).replace(' ', '\x20') | 71 l = m.group(1) + m.group(2).replace(' ', '\x20') |
| 72 lines.append(l) | 72 lines.append(l) |
| 73 return lines | 73 return lines |
| 74 | 74 |
| 75 |
| 75 class ServerProcess(object): | 76 class ServerProcess(object): |
| 77 |
| 76 """This class provides a wrapper around a subprocess that | 78 """This class provides a wrapper around a subprocess that |
| 77 implements a simple request/response usage model. The primary benefit | 79 implements a simple request/response usage model. The primary benefit |
| 78 is that reading responses takes a deadline, so that we don't ever block | 80 is that reading responses takes a deadline, so that we don't ever block |
| 79 indefinitely. The class also handles transparently restarting processes | 81 indefinitely. The class also handles transparently restarting processes |
| 80 as necessary to keep issuing commands.""" | 82 as necessary to keep issuing commands.""" |
| 81 | 83 |
| 82 def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False,
treat_no_data_as_crash=False, | 84 def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False,
treat_no_data_as_crash=False, |
| 83 logging=False): | 85 logging=False): |
| 84 self._port = port_obj | 86 self._port = port_obj |
| 85 self._name = name # Should be the command name (e.g. content_shell, ima
ge_diff) | 87 self._name = name # Should be the command name (e.g. content_shell, ima
ge_diff) |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 120 self._output = str() # bytesarray() once we require Python 2.6 | 122 self._output = str() # bytesarray() once we require Python 2.6 |
| 121 self._error = str() # bytesarray() once we require Python 2.6 | 123 self._error = str() # bytesarray() once we require Python 2.6 |
| 122 self._crashed = False | 124 self._crashed = False |
| 123 self.timed_out = False | 125 self.timed_out = False |
| 124 | 126 |
| 125 def process_name(self): | 127 def process_name(self): |
| 126 return self._name | 128 return self._name |
| 127 | 129 |
| 128 def _start(self): | 130 def _start(self): |
| 129 if self._proc: | 131 if self._proc: |
| 130 raise ValueError("%s already running" % self._name) | 132 raise ValueError('%s already running' % self._name) |
| 131 self._reset() | 133 self._reset() |
| 132 # close_fds is a workaround for http://bugs.python.org/issue2320 | 134 # close_fds is a workaround for http://bugs.python.org/issue2320 |
| 133 close_fds = not self._host.platform.is_win() | 135 close_fds = not self._host.platform.is_win() |
| 134 if self._logging: | 136 if self._logging: |
| 135 env_str = '' | 137 env_str = '' |
| 136 if self._env: | 138 if self._env: |
| 137 env_str += '\n'.join("%s=%s" % (k, v) for k, v in self._env.item
s()) + '\n' | 139 env_str += '\n'.join('%s=%s' % (k, v) for k, v in self._env.item
s()) + '\n' |
| 138 _log.info('CMD: \n%s%s\n', env_str, _quote_cmd(self._cmd)) | 140 _log.info('CMD: \n%s%s\n', env_str, _quote_cmd(self._cmd)) |
| 139 self._proc = self._host.executive.popen(self._cmd, stdin=self._host.exec
utive.PIPE, | 141 self._proc = self._host.executive.popen(self._cmd, stdin=self._host.exec
utive.PIPE, |
| 140 stdout=self._host.executive.PIPE, | 142 stdout=self._host.executive.PIPE
, |
| 141 stderr=self._host.executive.PIPE, | 143 stderr=self._host.executive.PIPE
, |
| 142 close_fds=close_fds, | 144 close_fds=close_fds, |
| 143 env=self._env, | 145 env=self._env, |
| 144 universal_newlines=self._universal_newlines) | 146 universal_newlines=self._univers
al_newlines) |
| 145 self._pid = self._proc.pid | 147 self._pid = self._proc.pid |
| 146 fd = self._proc.stdout.fileno() | 148 fd = self._proc.stdout.fileno() |
| 147 if not self._use_win32_apis: | 149 if not self._use_win32_apis: |
| 148 fl = fcntl.fcntl(fd, fcntl.F_GETFL) | 150 fl = fcntl.fcntl(fd, fcntl.F_GETFL) |
| 149 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) | 151 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) |
| 150 fd = self._proc.stderr.fileno() | 152 fd = self._proc.stderr.fileno() |
| 151 fl = fcntl.fcntl(fd, fcntl.F_GETFL) | 153 fl = fcntl.fcntl(fd, fcntl.F_GETFL) |
| 152 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) | 154 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) |
| 153 | 155 |
| 154 def _handle_possible_interrupt(self): | 156 def _handle_possible_interrupt(self): |
| (...skipping 16 matching lines...) Expand all Loading... |
| 171 return None | 173 return None |
| 172 | 174 |
| 173 def write(self, bytes): | 175 def write(self, bytes): |
| 174 """Write a request to the subprocess. The subprocess is (re-)start()'ed | 176 """Write a request to the subprocess. The subprocess is (re-)start()'ed |
| 175 if is not already running.""" | 177 if is not already running.""" |
| 176 if not self._proc: | 178 if not self._proc: |
| 177 self._start() | 179 self._start() |
| 178 try: | 180 try: |
| 179 self._log_data(' IN', bytes) | 181 self._log_data(' IN', bytes) |
| 180 self._proc.stdin.write(bytes) | 182 self._proc.stdin.write(bytes) |
| 181 except IOError, e: | 183 except IOError as e: |
| 182 self.stop(0.0) | 184 self.stop(0.0) |
| 183 # stop() calls _reset(), so we have to set crashed to True after cal
ling stop(). | 185 # stop() calls _reset(), so we have to set crashed to True after cal
ling stop(). |
| 184 self._crashed = True | 186 self._crashed = True |
| 185 | 187 |
| 186 def _pop_stdout_line_if_ready(self): | 188 def _pop_stdout_line_if_ready(self): |
| 187 index_after_newline = self._output.find('\n') + 1 | 189 index_after_newline = self._output.find('\n') + 1 |
| 188 if index_after_newline > 0: | 190 if index_after_newline > 0: |
| 189 return self._pop_output_bytes(index_after_newline) | 191 return self._pop_output_bytes(index_after_newline) |
| 190 return None | 192 return None |
| 191 | 193 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 208 def retrieve_bytes_from_buffers(): | 210 def retrieve_bytes_from_buffers(): |
| 209 stdout_line = self._pop_stdout_line_if_ready() | 211 stdout_line = self._pop_stdout_line_if_ready() |
| 210 if stdout_line: | 212 if stdout_line: |
| 211 return stdout_line, None | 213 return stdout_line, None |
| 212 stderr_line = self._pop_stderr_line_if_ready() | 214 stderr_line = self._pop_stderr_line_if_ready() |
| 213 if stderr_line: | 215 if stderr_line: |
| 214 return None, stderr_line | 216 return None, stderr_line |
| 215 return None # Instructs the caller to keep waiting. | 217 return None # Instructs the caller to keep waiting. |
| 216 | 218 |
| 217 return_value = self._read(deadline, retrieve_bytes_from_buffers) | 219 return_value = self._read(deadline, retrieve_bytes_from_buffers) |
| 218 # FIXME: This is a bit of a hack around the fact that _read normally onl
y returns one value, but this caller wants it to return two. | 220 # FIXME: This is a bit of a hack around the fact that _read normally onl
y |
| 221 # returns one value, but this caller wants it to return two. |
| 219 if return_value is None: | 222 if return_value is None: |
| 220 return None, None | 223 return None, None |
| 221 return return_value | 224 return return_value |
| 222 | 225 |
| 223 def read_stdout(self, deadline, size): | 226 def read_stdout(self, deadline, size): |
| 224 if size <= 0: | 227 if size <= 0: |
| 225 raise ValueError('ServerProcess.read() called with a non-positive si
ze: %d ' % size) | 228 raise ValueError('ServerProcess.read() called with a non-positive si
ze: %d ' % size) |
| 226 | 229 |
| 227 def retrieve_bytes_from_stdout_buffer(): | 230 def retrieve_bytes_from_stdout_buffer(): |
| 228 if len(self._output) >= size: | 231 if len(self._output) >= size: |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 261 if self._proc.stdout.closed or self._proc.stderr.closed: | 264 if self._proc.stdout.closed or self._proc.stderr.closed: |
| 262 # If the process crashed and is using FIFOs, like Chromium Android,
the | 265 # If the process crashed and is using FIFOs, like Chromium Android,
the |
| 263 # stdout and stderr pipes will be closed. | 266 # stdout and stderr pipes will be closed. |
| 264 return | 267 return |
| 265 | 268 |
| 266 out_fd = self._proc.stdout.fileno() | 269 out_fd = self._proc.stdout.fileno() |
| 267 err_fd = self._proc.stderr.fileno() | 270 err_fd = self._proc.stderr.fileno() |
| 268 select_fds = (out_fd, err_fd) | 271 select_fds = (out_fd, err_fd) |
| 269 try: | 272 try: |
| 270 read_fds, _, _ = select.select(select_fds, [], select_fds, max(deadl
ine - time.time(), 0)) | 273 read_fds, _, _ = select.select(select_fds, [], select_fds, max(deadl
ine - time.time(), 0)) |
| 271 except select.error, e: | 274 except select.error as e: |
| 272 # We can ignore EINVAL since it's likely the process just crashed an
d we'll | 275 # We can ignore EINVAL since it's likely the process just crashed an
d we'll |
| 273 # figure that out the next time through the loop in _read(). | 276 # figure that out the next time through the loop in _read(). |
| 274 if e.args[0] == errno.EINVAL: | 277 if e.args[0] == errno.EINVAL: |
| 275 return | 278 return |
| 276 raise | 279 raise |
| 277 | 280 |
| 278 try: | 281 try: |
| 279 # Note that we may get no data during read() even though | 282 # Note that we may get no data during read() even though |
| 280 # select says we got something; see the select() man page | 283 # select says we got something; see the select() man page |
| 281 # on linux. I don't know if this happens on Mac OS and | 284 # on linux. I don't know if this happens on Mac OS and |
| 282 # other Unixen as well, but we don't bother special-casing | 285 # other Unixen as well, but we don't bother special-casing |
| 283 # Linux because it's relatively harmless either way. | 286 # Linux because it's relatively harmless either way. |
| 284 if out_fd in read_fds: | 287 if out_fd in read_fds: |
| 285 data = self._proc.stdout.read() | 288 data = self._proc.stdout.read() |
| 286 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): | 289 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): |
| 287 self._crashed = True | 290 self._crashed = True |
| 288 self._log_data('OUT', data) | 291 self._log_data('OUT', data) |
| 289 self._output += data | 292 self._output += data |
| 290 | 293 |
| 291 if err_fd in read_fds: | 294 if err_fd in read_fds: |
| 292 data = self._proc.stderr.read() | 295 data = self._proc.stderr.read() |
| 293 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): | 296 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): |
| 294 self._crashed = True | 297 self._crashed = True |
| 295 self._log_data('ERR', data) | 298 self._log_data('ERR', data) |
| 296 self._error += data | 299 self._error += data |
| 297 except IOError, e: | 300 except IOError as e: |
| 298 # We can ignore the IOErrors because we will detect if the subporces
s crashed | 301 # We can ignore the IOErrors because we will detect if the subporces
s crashed |
| 299 # the next time through the loop in _read() | 302 # the next time through the loop in _read() |
| 300 pass | 303 pass |
| 301 | 304 |
| 302 def _wait_for_data_and_update_buffers_using_win32_apis(self, deadline): | 305 def _wait_for_data_and_update_buffers_using_win32_apis(self, deadline): |
| 303 # See http://code.activestate.com/recipes/440554-module-to-allow-asynchr
onous-subprocess-use-on-win/ | 306 # See http://code.activestate.com/recipes/440554-module-to-allow-asynchr
onous-subprocess-use-on-win/ |
| 304 # and http://docs.activestate.com/activepython/2.6/pywin32/modules.html | 307 # and http://docs.activestate.com/activepython/2.6/pywin32/modules.html |
| 305 # for documentation on all of these win32-specific modules. | 308 # for documentation on all of these win32-specific modules. |
| 306 now = time.time() | 309 now = time.time() |
| 307 out_fh = msvcrt.get_osfhandle(self._proc.stdout.fileno()) | 310 out_fh = msvcrt.get_osfhandle(self._proc.stdout.fileno()) |
| (...skipping 12 matching lines...) Expand all Loading... |
| 320 time.sleep(0.01) | 323 time.sleep(0.01) |
| 321 now = time.time() | 324 now = time.time() |
| 322 return | 325 return |
| 323 | 326 |
| 324 def _non_blocking_read_win32(self, handle): | 327 def _non_blocking_read_win32(self, handle): |
| 325 try: | 328 try: |
| 326 _, avail, _ = win32pipe.PeekNamedPipe(handle, 0) | 329 _, avail, _ = win32pipe.PeekNamedPipe(handle, 0) |
| 327 if avail > 0: | 330 if avail > 0: |
| 328 _, buf = win32file.ReadFile(handle, avail, None) | 331 _, buf = win32file.ReadFile(handle, avail, None) |
| 329 return buf | 332 return buf |
| 330 except Exception, e: | 333 except Exception as e: |
| 331 if e[0] not in (109, errno.ESHUTDOWN): # 109 == win32 ERROR_BROKEN_
PIPE | 334 if e[0] not in (109, errno.ESHUTDOWN): # 109 == win32 ERROR_BROKEN_
PIPE |
| 332 raise | 335 raise |
| 333 return None | 336 return None |
| 334 | 337 |
| 335 def has_crashed(self): | 338 def has_crashed(self): |
| 336 if not self._crashed and self.poll(): | 339 if not self._crashed and self.poll(): |
| 337 self._crashed = True | 340 self._crashed = True |
| 338 self._handle_possible_interrupt() | 341 self._handle_possible_interrupt() |
| 339 return self._crashed | 342 return self._crashed |
| 340 | 343 |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 405 self._proc.wait() | 408 self._proc.wait() |
| 406 | 409 |
| 407 def replace_outputs(self, stdout, stderr): | 410 def replace_outputs(self, stdout, stderr): |
| 408 assert self._proc | 411 assert self._proc |
| 409 if stdout: | 412 if stdout: |
| 410 self._proc.stdout.close() | 413 self._proc.stdout.close() |
| 411 self._proc.stdout = stdout | 414 self._proc.stdout = stdout |
| 412 if stderr: | 415 if stderr: |
| 413 self._proc.stderr.close() | 416 self._proc.stderr.close() |
| 414 self._proc.stderr = stderr | 417 self._proc.stderr = stderr |
| OLD | NEW |