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 |