summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2010-04-27 16:31:00 +0100
committerSteve Block <steveblock@google.com>2010-05-11 14:42:12 +0100
commitdcc8cf2e65d1aa555cce12431a16547e66b469ee (patch)
tree92a8d65cd5383bca9749f5327fb5e440563926e6 /WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
parentccac38a6b48843126402088a309597e682f40fe6 (diff)
downloadexternal_webkit-dcc8cf2e65d1aa555cce12431a16547e66b469ee.zip
external_webkit-dcc8cf2e65d1aa555cce12431a16547e66b469ee.tar.gz
external_webkit-dcc8cf2e65d1aa555cce12431a16547e66b469ee.tar.bz2
Merge webkit.org at r58033 : Initial merge by git
Change-Id: If006c38561af287c50cd578d251629b51e4d8cd1
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py')
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py223
1 files changed, 223 insertions, 0 deletions
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
new file mode 100644
index 0000000..f1c6d73
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the Google name nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Package that implements the ServerProcess wrapper class"""
+
+import fcntl
+import logging
+import os
+import select
+import signal
+import subprocess
+import sys
+import time
+
+_log = logging.getLogger("webkitpy.layout_tests.port.server_process")
+
+
+class ServerProcess:
+ """This class provides a wrapper around a subprocess that
+ implements a simple request/response usage model. The primary benefit
+ is that reading responses takes a timeout, so that we don't ever block
+ indefinitely. The class also handles transparently restarting processes
+ as necessary to keep issuing commands."""
+
+ def __init__(self, port_obj, name, cmd, env=None):
+ self._port = port_obj
+ self._name = name
+ self._cmd = cmd
+ self._env = env
+ self._reset()
+
+ def _reset(self):
+ self._proc = None
+ self._output = ''
+ self.crashed = False
+ self.timed_out = False
+ self.error = ''
+
+ def _start(self):
+ if self._proc:
+ raise ValueError("%s already running" % self._name)
+ self._reset()
+ close_fds = sys.platform not in ('win32', 'cygwin')
+ self._proc = subprocess.Popen(self._cmd, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ close_fds=close_fds,
+ env=self._env)
+ fd = self._proc.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+ fd = self._proc.stderr.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ def handle_interrupt(self):
+ """This routine checks to see if the process crashed or exited
+ because of a keyboard interrupt and raises KeyboardInterrupt
+ accordingly."""
+ if self.crashed:
+ # This is hex code 0xc000001d, which is used for abrupt
+ # termination. This happens if we hit ctrl+c from the prompt
+ # and we happen to be waiting on the DumpRenderTree.
+ # sdoyon: Not sure for which OS and in what circumstances the
+ # above code is valid. What works for me under Linux to detect
+ # ctrl+c is for the subprocess returncode to be negative
+ # SIGINT. And that agrees with the subprocess documentation.
+ if (-1073741510 == self._proc.returncode or
+ - signal.SIGINT == self._proc.returncode):
+ raise KeyboardInterrupt
+ return
+
+ def poll(self):
+ """Check to see if the underlying process is running; returns None
+ if it still is (wrapper around subprocess.poll)."""
+ if self._proc:
+ return self._proc.poll()
+ return None
+
+ def returncode(self):
+ """Returns the exit code from the subprcoess; returns None if the
+ process hasn't exited (this is a wrapper around subprocess.returncode).
+ """
+ if self._proc:
+ return self._proc.returncode
+ return None
+
+ def write(self, input):
+ """Write a request to the subprocess. The subprocess is (re-)start()'ed
+ if is not already running."""
+ if not self._proc:
+ self._start()
+ self._proc.stdin.write(input)
+
+ def read_line(self, timeout):
+ """Read a single line from the subprocess, waiting until the deadline.
+ If the deadline passes, the call times out. Note that even if the
+ subprocess has crashed or the deadline has passed, if there is output
+ pending, it will be returned.
+
+ Args:
+ timeout: floating-point number of seconds the call is allowed
+ to block for. A zero or negative number will attempt to read
+ any existing data, but will not block. There is no way to
+ block indefinitely.
+ Returns:
+ output: data returned, if any. If no data is available and the
+ call times out or crashes, an empty string is returned. Note
+ that the returned string includes the newline ('\n')."""
+ return self._read(timeout, size=0)
+
+ def read(self, timeout, size):
+ """Attempts to read size characters from the subprocess, waiting until
+ the deadline passes. If the deadline passes, any available data will be
+ returned. Note that even if the deadline has passed or if the
+ subprocess has crashed, any available data will still be returned.
+
+ Args:
+ timeout: floating-point number of seconds the call is allowed
+ to block for. A zero or negative number will attempt to read
+ any existing data, but will not block. There is no way to
+ block indefinitely.
+ size: amount of data to read. Must be a postive integer.
+ Returns:
+ output: data returned, if any. If no data is available, an empty
+ string is returned.
+ """
+ if size <= 0:
+ raise ValueError('ServerProcess.read() called with a '
+ 'non-positive size: %d ' % size)
+ return self._read(timeout, size)
+
+ def _read(self, timeout, size):
+ """Internal routine that actually does the read."""
+ index = -1
+ out_fd = self._proc.stdout.fileno()
+ err_fd = self._proc.stderr.fileno()
+ select_fds = (out_fd, err_fd)
+ deadline = time.time() + timeout
+ while not self.timed_out and not self.crashed:
+ if self._proc.poll() != None:
+ self.crashed = True
+ self.handle_interrupt()
+
+ now = time.time()
+ if now > deadline:
+ self.timed_out = True
+
+ # Check to see if we have any output we can return.
+ if size and len(self._output) >= size:
+ index = size
+ elif size == 0:
+ index = self._output.find('\n') + 1
+
+ if index or self.crashed or self.timed_out:
+ output = self._output[0:index]
+ self._output = self._output[index:]
+ return output
+
+ # Nope - wait for more data.
+ (read_fds, write_fds, err_fds) = select.select(select_fds, [],
+ select_fds,
+ deadline - now)
+ try:
+ if out_fd in read_fds:
+ self._output += self._proc.stdout.read()
+ if err_fd in read_fds:
+ self.error += self._proc.stderr.read()
+ except IOError, e:
+ pass
+
+ def stop(self):
+ """Stop (shut down) the subprocess), if it is running."""
+ pid = self._proc.pid
+ self._proc.stdin.close()
+ self._proc.stdout.close()
+ if self._proc.stderr:
+ self._proc.stderr.close()
+ if sys.platform not in ('win32', 'cygwin'):
+ # Closing stdin/stdout/stderr hangs sometimes on OS X,
+ # (see restart(), above), and anyway we don't want to hang
+ # the harness if DumpRenderTree is buggy, so we wait a couple
+ # seconds to give DumpRenderTree a chance to clean up, but then
+ # force-kill the process if necessary.
+ KILL_TIMEOUT = 3.0
+ timeout = time.time() + KILL_TIMEOUT
+ while self._proc.poll() is None and time.time() < timeout:
+ time.sleep(0.1)
+ if self._proc.poll() is None:
+ _log.warning('stopping %s timed out, killing it' %
+ self._name)
+ null = open(os.devnull, "w")
+ subprocess.Popen(["kill", "-9",
+ str(self._proc.pid)], stderr=null)
+ null.close()
+ _log.warning('killed')
+ self._reset()