| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 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 import BaseHTTPServer |
| 6 import errno |
| 5 import json | 7 import json |
| 6 import optparse | 8 import optparse |
| 7 import os | 9 import os |
| 10 import re |
| 11 import socket |
| 12 import SocketServer |
| 8 import struct | 13 import struct |
| 9 import sys | 14 import sys |
| 10 import warnings | 15 import warnings |
| 11 | 16 |
| 12 # Ignore deprecation warnings, they make our output more cluttered. | 17 # Ignore deprecation warnings, they make our output more cluttered. |
| 13 warnings.filterwarnings("ignore", category=DeprecationWarning) | 18 warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 14 | 19 |
| 15 if sys.platform == 'win32': | 20 if sys.platform == 'win32': |
| 16 import msvcrt | 21 import msvcrt |
| 17 | 22 |
| 23 # Using debug() seems to cause hangs on XP: see http://crbug.com/64515. |
| 24 debug_output = sys.stderr |
| 25 def debug(string): |
| 26 debug_output.write(string + "\n") |
| 27 debug_output.flush() |
| 28 |
| 18 | 29 |
| 19 class Error(Exception): | 30 class Error(Exception): |
| 20 """Error class for this module.""" | 31 """Error class for this module.""" |
| 21 | 32 |
| 22 | 33 |
| 23 class OptionError(Error): | 34 class OptionError(Error): |
| 24 """Error for bad command line options.""" | 35 """Error for bad command line options.""" |
| 25 | 36 |
| 26 | 37 |
| 27 class FileMultiplexer(object): | 38 class FileMultiplexer(object): |
| 28 def __init__(self, fd1, fd2) : | 39 def __init__(self, fd1, fd2) : |
| 29 self.__fd1 = fd1 | 40 self.__fd1 = fd1 |
| 30 self.__fd2 = fd2 | 41 self.__fd2 = fd2 |
| 31 | 42 |
| 32 def __del__(self) : | 43 def __del__(self) : |
| 33 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr: | 44 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr: |
| 34 self.__fd1.close() | 45 self.__fd1.close() |
| 35 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr: | 46 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr: |
| 36 self.__fd2.close() | 47 self.__fd2.close() |
| 37 | 48 |
| 38 def write(self, text) : | 49 def write(self, text) : |
| 39 self.__fd1.write(text) | 50 self.__fd1.write(text) |
| 40 self.__fd2.write(text) | 51 self.__fd2.write(text) |
| 41 | 52 |
| 42 def flush(self) : | 53 def flush(self) : |
| 43 self.__fd1.flush() | 54 self.__fd1.flush() |
| 44 self.__fd2.flush() | 55 self.__fd2.flush() |
| 45 | 56 |
| 46 | 57 |
| 58 class ClientRestrictingServerMixIn: |
| 59 """Implements verify_request to limit connections to our configured IP |
| 60 address.""" |
| 61 |
| 62 def verify_request(self, _request, client_address): |
| 63 return client_address[0] == self.server_address[0] |
| 64 |
| 65 |
| 66 class BrokenPipeHandlerMixIn: |
| 67 """Allows the server to deal with "broken pipe" errors (which happen if the |
| 68 browser quits with outstanding requests, like for the favicon). This mix-in |
| 69 requires the class to derive from SocketServer.BaseServer and not override its |
| 70 handle_error() method. """ |
| 71 |
| 72 def handle_error(self, request, client_address): |
| 73 value = sys.exc_info()[1] |
| 74 if isinstance(value, socket.error): |
| 75 err = value.args[0] |
| 76 if sys.platform in ('win32', 'cygwin'): |
| 77 # "An established connection was aborted by the software in your host." |
| 78 pipe_err = 10053 |
| 79 else: |
| 80 pipe_err = errno.EPIPE |
| 81 if err == pipe_err: |
| 82 print "testserver.py: Broken pipe" |
| 83 return |
| 84 SocketServer.BaseServer.handle_error(self, request, client_address) |
| 85 |
| 86 |
| 87 class StoppableHTTPServer(BaseHTTPServer.HTTPServer): |
| 88 """This is a specialization of BaseHTTPServer to allow it |
| 89 to be exited cleanly (by setting its "stop" member to True).""" |
| 90 |
| 91 def serve_forever(self): |
| 92 self.stop = False |
| 93 self.nonce_time = None |
| 94 while not self.stop: |
| 95 self.handle_request() |
| 96 self.socket.close() |
| 97 |
| 98 |
| 47 def MultiplexerHack(std_fd, log_fd): | 99 def MultiplexerHack(std_fd, log_fd): |
| 48 """Creates a FileMultiplexer that will write to both specified files. | 100 """Creates a FileMultiplexer that will write to both specified files. |
| 49 | 101 |
| 50 When running on Windows XP bots, stdout and stderr will be invalid file | 102 When running on Windows XP bots, stdout and stderr will be invalid file |
| 51 handles, so log_fd will be returned directly. (This does not occur if you | 103 handles, so log_fd will be returned directly. (This does not occur if you |
| 52 run the test suite directly from a console, but only if the output of the | 104 run the test suite directly from a console, but only if the output of the |
| 53 test executable is redirected.) | 105 test executable is redirected.) |
| 54 """ | 106 """ |
| 55 if std_fd.fileno() <= 0: | 107 if std_fd.fileno() <= 0: |
| 56 return log_fd | 108 return log_fd |
| 57 return FileMultiplexer(std_fd, log_fd) | 109 return FileMultiplexer(std_fd, log_fd) |
| 58 | 110 |
| 59 | 111 |
| 112 class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| 113 |
| 114 def __init__(self, request, client_address, socket_server, |
| 115 connect_handlers, get_handlers, head_handlers, post_handlers, |
| 116 put_handlers): |
| 117 self._connect_handlers = connect_handlers |
| 118 self._get_handlers = get_handlers |
| 119 self._head_handlers = head_handlers |
| 120 self._post_handlers = post_handlers |
| 121 self._put_handlers = put_handlers |
| 122 BaseHTTPServer.BaseHTTPRequestHandler.__init__( |
| 123 self, request, client_address, socket_server) |
| 124 |
| 125 def log_request(self, *args, **kwargs): |
| 126 # Disable request logging to declutter test log output. |
| 127 pass |
| 128 |
| 129 def _ShouldHandleRequest(self, handler_name): |
| 130 """Determines if the path can be handled by the handler. |
| 131 |
| 132 We consider a handler valid if the path begins with the |
| 133 handler name. It can optionally be followed by "?*", "/*". |
| 134 """ |
| 135 |
| 136 pattern = re.compile('%s($|\?|/).*' % handler_name) |
| 137 return pattern.match(self.path) |
| 138 |
| 139 def do_CONNECT(self): |
| 140 for handler in self._connect_handlers: |
| 141 if handler(): |
| 142 return |
| 143 |
| 144 def do_GET(self): |
| 145 for handler in self._get_handlers: |
| 146 if handler(): |
| 147 return |
| 148 |
| 149 def do_HEAD(self): |
| 150 for handler in self._head_handlers: |
| 151 if handler(): |
| 152 return |
| 153 |
| 154 def do_POST(self): |
| 155 for handler in self._post_handlers: |
| 156 if handler(): |
| 157 return |
| 158 |
| 159 def do_PUT(self): |
| 160 for handler in self._put_handlers: |
| 161 if handler(): |
| 162 return |
| 163 |
| 164 |
| 60 class TestServerRunner(object): | 165 class TestServerRunner(object): |
| 61 """Runs a test server and communicates with the controlling C++ test code. | 166 """Runs a test server and communicates with the controlling C++ test code. |
| 62 | 167 |
| 63 Subclasses should override the create_server method to create their server | 168 Subclasses should override the create_server method to create their server |
| 64 object, and the add_options method to add their own options. | 169 object, and the add_options method to add their own options. |
| 65 """ | 170 """ |
| 66 | 171 |
| 67 def __init__(self): | 172 def __init__(self): |
| 68 self.option_parser = optparse.OptionParser() | 173 self.option_parser = optparse.OptionParser() |
| 69 self.add_options() | 174 self.add_options() |
| 70 | 175 |
| 71 def main(self): | 176 def main(self): |
| 72 self.options, self.args = self.option_parser.parse_args() | 177 self.options, self.args = self.option_parser.parse_args() |
| 73 | 178 |
| 74 logfile = open('testserver.log', 'w') | 179 logfile = open(self.options.log_file, 'w') |
| 75 sys.stderr = MultiplexerHack(sys.stderr, logfile) | 180 sys.stderr = MultiplexerHack(sys.stderr, logfile) |
| 76 if self.options.log_to_console: | 181 if self.options.log_to_console: |
| 77 sys.stdout = MultiplexerHack(sys.stdout, logfile) | 182 sys.stdout = MultiplexerHack(sys.stdout, logfile) |
| 78 else: | 183 else: |
| 79 sys.stdout = logfile | 184 sys.stdout = logfile |
| 80 | 185 |
| 81 server_data = { | 186 server_data = { |
| 82 'host': self.options.host, | 187 'host': self.options.host, |
| 83 } | 188 } |
| 84 self.server = self.create_server(server_data) | 189 self.server = self.create_server(server_data) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 101 | 206 |
| 102 def add_options(self): | 207 def add_options(self): |
| 103 self.option_parser.add_option('--startup-pipe', type='int', | 208 self.option_parser.add_option('--startup-pipe', type='int', |
| 104 dest='startup_pipe', | 209 dest='startup_pipe', |
| 105 help='File handle of pipe to parent process') | 210 help='File handle of pipe to parent process') |
| 106 self.option_parser.add_option('--log-to-console', action='store_const', | 211 self.option_parser.add_option('--log-to-console', action='store_const', |
| 107 const=True, default=False, | 212 const=True, default=False, |
| 108 dest='log_to_console', | 213 dest='log_to_console', |
| 109 help='Enables or disables sys.stdout logging ' | 214 help='Enables or disables sys.stdout logging ' |
| 110 'to the console.') | 215 'to the console.') |
| 216 self.option_parser.add_option('--log-file', default='testserver.log', |
| 217 dest='log_file', |
| 218 help='The name of the server log file.') |
| 111 self.option_parser.add_option('--port', default=0, type='int', | 219 self.option_parser.add_option('--port', default=0, type='int', |
| 112 help='Port used by the server. If ' | 220 help='Port used by the server. If ' |
| 113 'unspecified, the server will listen on an ' | 221 'unspecified, the server will listen on an ' |
| 114 'ephemeral port.') | 222 'ephemeral port.') |
| 115 self.option_parser.add_option('--host', default='127.0.0.1', | 223 self.option_parser.add_option('--host', default='127.0.0.1', |
| 116 dest='host', | 224 dest='host', |
| 117 help='Hostname or IP upon which the server ' | 225 help='Hostname or IP upon which the server ' |
| 118 'will listen. Client connections will also ' | 226 'will listen. Client connections will also ' |
| 119 'only be allowed from this address.') | 227 'only be allowed from this address.') |
| 228 self.option_parser.add_option('--data-dir', dest='data_dir', |
| 229 help='Directory from which to read the ' |
| 230 'files.') |
| 120 | 231 |
| 121 def _notify_startup_complete(self, server_data): | 232 def _notify_startup_complete(self, server_data): |
| 122 # Notify the parent that we've started. (BaseServer subclasses | 233 # Notify the parent that we've started. (BaseServer subclasses |
| 123 # bind their sockets on construction.) | 234 # bind their sockets on construction.) |
| 124 if self.options.startup_pipe is not None: | 235 if self.options.startup_pipe is not None: |
| 125 server_data_json = json.dumps(server_data) | 236 server_data_json = json.dumps(server_data) |
| 126 server_data_len = len(server_data_json) | 237 server_data_len = len(server_data_json) |
| 127 print 'sending server_data: %s (%d bytes)' % ( | 238 print 'sending server_data: %s (%d bytes)' % ( |
| 128 server_data_json, server_data_len) | 239 server_data_json, server_data_len) |
| 129 if sys.platform == 'win32': | 240 if sys.platform == 'win32': |
| 130 fd = msvcrt.open_osfhandle(self.options.startup_pipe, 0) | 241 fd = msvcrt.open_osfhandle(self.options.startup_pipe, 0) |
| 131 else: | 242 else: |
| 132 fd = self.options.startup_pipe | 243 fd = self.options.startup_pipe |
| 133 startup_pipe = os.fdopen(fd, "w") | 244 startup_pipe = os.fdopen(fd, "w") |
| 134 # First write the data length as an unsigned 4-byte value. This | 245 # First write the data length as an unsigned 4-byte value. This |
| 135 # is _not_ using network byte ordering since the other end of the | 246 # is _not_ using network byte ordering since the other end of the |
| 136 # pipe is on the same machine. | 247 # pipe is on the same machine. |
| 137 startup_pipe.write(struct.pack('=L', server_data_len)) | 248 startup_pipe.write(struct.pack('=L', server_data_len)) |
| 138 startup_pipe.write(server_data_json) | 249 startup_pipe.write(server_data_json) |
| 139 startup_pipe.close() | 250 startup_pipe.close() |
| OLD | NEW |