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
3 # found in the LICENSE file.
19 # Ignore deprecation warnings, they make our output more cluttered.
20 warnings
.filterwarnings("ignore", category
=DeprecationWarning)
22 if sys
.platform
== 'win32':
25 # Using debug() seems to cause hangs on XP: see http://crbug.com/64515.
26 debug_output
= sys
.stderr
28 debug_output
.write(string
+ "\n")
32 class Error(Exception):
33 """Error class for this module."""
36 class OptionError(Error
):
37 """Error for bad command line options."""
40 class FileMultiplexer(object):
41 def __init__(self
, fd1
, fd2
) :
46 if self
.__fd
1 != sys
.stdout
and self
.__fd
1 != sys
.stderr
:
48 if self
.__fd
2 != sys
.stdout
and self
.__fd
2 != sys
.stderr
:
51 def write(self
, text
) :
52 self
.__fd
1.write(text
)
53 self
.__fd
2.write(text
)
60 class ClientRestrictingServerMixIn
:
61 """Implements verify_request to limit connections to our configured IP
64 def verify_request(self
, _request
, client_address
):
65 return client_address
[0] == self
.server_address
[0]
68 class BrokenPipeHandlerMixIn
:
69 """Allows the server to deal with "broken pipe" errors (which happen if the
70 browser quits with outstanding requests, like for the favicon). This mix-in
71 requires the class to derive from SocketServer.BaseServer and not override its
72 handle_error() method. """
74 def handle_error(self
, request
, client_address
):
75 value
= sys
.exc_info()[1]
76 if isinstance(value
, tlslite
.errors
.TLSClosedConnectionError
):
77 print "testserver.py: Closed connection"
79 if isinstance(value
, socket
.error
):
81 if sys
.platform
in ('win32', 'cygwin'):
82 # "An established connection was aborted by the software in your host."
85 pipe_err
= errno
.EPIPE
87 print "testserver.py: Broken pipe"
89 if err
== errno
.ECONNRESET
:
90 print "testserver.py: Connection reset by peer"
92 SocketServer
.BaseServer
.handle_error(self
, request
, client_address
)
95 class StoppableHTTPServer(BaseHTTPServer
.HTTPServer
):
96 """This is a specialization of BaseHTTPServer to allow it
97 to be exited cleanly (by setting its "stop" member to True)."""
99 def serve_forever(self
):
101 self
.nonce_time
= None
103 self
.handle_request()
107 def MultiplexerHack(std_fd
, log_fd
):
108 """Creates a FileMultiplexer that will write to both specified files.
110 When running on Windows XP bots, stdout and stderr will be invalid file
111 handles, so log_fd will be returned directly. (This does not occur if you
112 run the test suite directly from a console, but only if the output of the
113 test executable is redirected.)
115 if std_fd
.fileno() <= 0:
117 return FileMultiplexer(std_fd
, log_fd
)
120 class BasePageHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
122 def __init__(self
, request
, client_address
, socket_server
,
123 connect_handlers
, get_handlers
, head_handlers
, post_handlers
,
125 self
._connect
_handlers
= connect_handlers
126 self
._get
_handlers
= get_handlers
127 self
._head
_handlers
= head_handlers
128 self
._post
_handlers
= post_handlers
129 self
._put
_handlers
= put_handlers
130 BaseHTTPServer
.BaseHTTPRequestHandler
.__init
__(
131 self
, request
, client_address
, socket_server
)
133 def log_request(self
, *args
, **kwargs
):
134 # Disable request logging to declutter test log output.
137 def _ShouldHandleRequest(self
, handler_name
):
138 """Determines if the path can be handled by the handler.
140 We consider a handler valid if the path begins with the
141 handler name. It can optionally be followed by "?*", "/*".
144 pattern
= re
.compile('%s($|\?|/).*' % handler_name
)
145 return pattern
.match(self
.path
)
147 def do_CONNECT(self
):
148 for handler
in self
._connect
_handlers
:
153 for handler
in self
._get
_handlers
:
158 for handler
in self
._head
_handlers
:
163 for handler
in self
._post
_handlers
:
168 for handler
in self
._put
_handlers
:
173 class TestServerRunner(object):
174 """Runs a test server and communicates with the controlling C++ test code.
176 Subclasses should override the create_server method to create their server
177 object, and the add_options method to add their own options.
181 self
.option_parser
= optparse
.OptionParser()
185 self
.options
, self
.args
= self
.option_parser
.parse_args()
187 logfile
= open(self
.options
.log_file
, 'w')
189 # http://crbug.com/248796 : Error logs streamed to normal sys.stderr will be
190 # written to HTTP response payload when remote test server is used.
191 # For this reason, some tests like ResourceFetcherTests.ResourceFetcher404
192 # were failing on Android because remote test server is being used there.
193 # To fix them, we need to use sys.stdout as sys.stderr if remote test server
195 if self
.options
.on_remote_server
:
196 sys
.stderr
= sys
.stdout
198 sys
.stderr
= MultiplexerHack(sys
.stderr
, logfile
)
199 if self
.options
.log_to_console
:
200 sys
.stdout
= MultiplexerHack(sys
.stdout
, logfile
)
205 'host': self
.options
.host
,
207 self
.server
= self
.create_server(server_data
)
208 self
._notify
_startup
_complete
(server_data
)
211 def create_server(self
, server_data
):
212 """Creates a server object and returns it.
214 Must populate server_data['port'], and can set additional server_data
215 elements if desired."""
216 raise NotImplementedError()
218 def run_server(self
):
220 self
.server
.serve_forever()
221 except KeyboardInterrupt:
222 print 'shutting down server'
223 self
.server
.stop
= True
225 def add_options(self
):
226 self
.option_parser
.add_option('--startup-pipe', type='int',
228 help='File handle of pipe to parent process')
229 self
.option_parser
.add_option('--log-to-console', action
='store_const',
230 const
=True, default
=False,
231 dest
='log_to_console',
232 help='Enables or disables sys.stdout logging '
234 self
.option_parser
.add_option('--log-file', default
='testserver.log',
236 help='The name of the server log file.')
237 self
.option_parser
.add_option('--port', default
=0, type='int',
238 help='Port used by the server. If '
239 'unspecified, the server will listen on an '
241 self
.option_parser
.add_option('--host', default
='127.0.0.1',
243 help='Hostname or IP upon which the server '
244 'will listen. Client connections will also '
245 'only be allowed from this address.')
246 self
.option_parser
.add_option('--data-dir', dest
='data_dir',
247 help='Directory from which to read the '
249 self
.option_parser
.add_option('--on-remote-server', action
='store_const',
250 const
=True, default
=False,
251 dest
='on_remote_server',
252 help='Whether remote server is being used or '
255 def _notify_startup_complete(self
, server_data
):
256 # Notify the parent that we've started. (BaseServer subclasses
257 # bind their sockets on construction.)
258 if self
.options
.startup_pipe
is not None:
259 server_data_json
= json
.dumps(server_data
)
260 server_data_len
= len(server_data_json
)
261 print 'sending server_data: %s (%d bytes)' % (
262 server_data_json
, server_data_len
)
263 if sys
.platform
== 'win32':
264 fd
= msvcrt
.open_osfhandle(self
.options
.startup_pipe
, 0)
266 fd
= self
.options
.startup_pipe
267 startup_pipe
= os
.fdopen(fd
, "w")
268 # First write the data length as an unsigned 4-byte value. This
269 # is _not_ using network byte ordering since the other end of the
270 # pipe is on the same machine.
271 startup_pipe
.write(struct
.pack('=L', server_data_len
))
272 startup_pipe
.write(server_data_json
)