30
31
load_tests = load_tests_apply_scenarios
34
def portable_socket_pair():
35
"""Return a pair of TCP sockets connected to each other.
37
Unlike socket.socketpair, this should work on Windows.
39
listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
40
listen_sock.bind(('127.0.0.1', 0))
42
client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
43
client_sock.connect(listen_sock.getsockname())
44
server_sock, addr = listen_sock.accept()
46
return server_sock, client_sock
33
49
class TCPClient(object):
35
51
def __init__(self):
69
85
while not self.done:
70
86
self.handle_connection()
89
# TODO: We should be buffering any extra data sent, etc. However, in
90
# practice, we don't send extra content, so we haven't bothered
91
# to implement it yet.
92
req = self.request.recv(4096)
93
# An empty string is allowed, to indicate the end of the connection
94
if not req or (req.endswith('\n') and req.count('\n') == 1):
96
raise ValueError('[%r] not a simple line' % (req,))
72
98
def handle_connection(self):
73
req = self.rfile.readline()
76
102
elif req == 'ping\n':
77
self.wfile.write('pong\n')
103
self.request.sendall('pong\n')
79
105
raise ValueError('[%s] not understood' % req)
87
113
('TestingTCPServer', 'TestingThreadingTCPServer')]
92
115
def get_server(self, server_class=None, connection_handler_class=None):
93
116
if server_class is not None:
94
117
self.server_class = server_class
95
118
if connection_handler_class is None:
96
119
connection_handler_class = TCPConnectionHandler
97
server = test_server.TestingTCPServerInAThread(
120
server = test_server.TestingTCPServerInAThread(
98
121
('localhost', 0), self.server_class, connection_handler_class)
99
122
server.start_server()
100
123
self.addCleanup(server.stop_server)
158
181
# The server won't fail until a client connect
159
182
client = self.get_client()
160
183
client.connect((server.host, server.port))
184
# We make sure the server wants to handle a request, but the request is
185
# guaranteed to fail. However, the server should make sure that the
186
# connection gets closed, and stop_server should then raise the
187
# original exception.
188
client.write('ping\n')
162
# Now we must force the server to answer by sending the request and
163
# waiting for some answer. But since we don't control when the
164
# server thread will be given cycles, we don't control either
165
# whether our reads or writes may hang.
166
client.sock.settimeout(0.1)
167
client.write('ping\n')
190
self.assertEqual('', client.read())
191
except socket.error, e:
192
# On Windows, failing during 'handle' means we get
193
# 'forced-close-of-connection'. Possibly because we haven't
194
# processed the write request before we close the socket.
195
WSAECONNRESET = 10054
196
if e.errno in (WSAECONNRESET,):
171
198
# Now the server has raised the exception in its own thread
172
199
self.assertRaises(CantConnect, server.stop_server)
174
201
def test_server_crash_while_responding(self):
175
sync = threading.Event()
202
# We want to ensure the exception has been caught
203
caught = threading.Event()
205
# The thread that will serve the client, this needs to be an attribute
206
# so the handler below can modify it when it's executed (it's
207
# instantiated when the request is processed)
208
self.connection_thread = None
177
210
class FailToRespond(Exception):
180
213
class FailingDuringResponseHandler(TCPConnectionHandler):
182
def handle_connection(self):
183
req = self.rfile.readline()
184
threading.currentThread().set_sync_event(sync)
215
def handle_connection(request):
216
req = request.readline()
217
# Capture the thread and make it use 'caught' so we can wait on
218
# the even that will be set when the exception is caught. We
219
# also capture the thread to know where to look.
220
self.connection_thread = threading.currentThread()
221
self.connection_thread.set_sync_event(caught)
185
222
raise FailToRespond()
187
224
server = self.get_server(
189
226
client = self.get_client()
190
227
client.connect((server.host, server.port))
191
228
client.write('ping\n')
193
self.assertRaises(FailToRespond, server.pending_exception)
229
# Wait for the exception to be caught
231
self.assertEqual('', client.read()) # connection closed
232
# Check that the connection thread did catch the exception,
233
# http://pad.lv/869366 was wrongly checking the server thread which
234
# works for TestingTCPServer where the connection is handled in the
235
# same thread than the server one but is racy for
236
# TestingThreadingTCPServer where the server thread may be in a
237
# blocking accept() call (or not).
239
self.connection_thread.pending_exception()
240
except FailToRespond:
241
# Great, the test succeeded
244
# If the exception is not in the connection thread anymore, it's in
246
server.server.stopped.wait()
247
# The exception is available now
248
self.assertRaises(FailToRespond, server.pending_exception)
195
250
def test_exception_swallowed_while_serving(self):
196
sync = threading.Event()
251
# We need to ensure the exception has been caught
252
caught = threading.Event()
254
# The thread that will serve the client, this needs to be an attribute
255
# so the handler below can access it when it's executed (it's
256
# instantiated when the request is processed)
257
self.connection_thread = None
198
258
class CantServe(Exception):
201
261
class FailingWhileServingConnectionHandler(TCPConnectionHandler):
204
# We want to sync with the thread that is serving the
206
threading.currentThread().set_sync_event(sync)
264
# Capture the thread and make it use 'caught' so we can wait on
265
# the even that will be set when the exception is caught. We
266
# also capture the thread to know where to look.
267
self.connection_thread = threading.currentThread()
268
self.connection_thread.set_sync_event(caught)
207
269
raise CantServe()
209
271
server = self.get_server(
213
275
client = self.get_client()
214
276
# Connect to the server so the exception is raised there
215
277
client.connect((server.host, server.port))
216
# Wait for the exception to propagate.
278
# Wait for the exception to be caught
280
self.assertEqual('', client.read()) # connection closed
218
281
# The connection wasn't served properly but the exception should have
282
# been swallowed (see test_server_crash_while_responding remark about
283
# http://pad.lv/869366 explaining why we can't check the server thread
284
# here). More precisely, the exception *has* been caught and captured
285
# but it is cleared when joining the thread (or trying to acquire the
286
# exception) and as such won't propagate to the server thread.
287
self.connection_thread.pending_exception()
220
288
server.pending_exception()
290
def test_handle_request_closes_if_it_doesnt_process(self):
291
server = self.get_server()
292
client = self.get_client()
293
server.server.serving = False
294
client.connect((server.host, server.port))
295
self.assertEqual('', client.read())
223
298
class TestTestingSmartServer(tests.TestCase):
231
306
h = server._make_handler(sock)
232
307
self.assertEqual(test_server._DEFAULT_TESTING_CLIENT_TIMEOUT,
233
308
h._client_timeout)
311
class FakeServer(object):
312
"""Minimal implementation to pass to TestingSmartConnectionHandler"""
313
backing_transport = None
314
root_client_path = '/'
317
class TestTestingSmartConnectionHandler(tests.TestCase):
319
def test_connection_timeout_suppressed(self):
320
self.overrideAttr(test_server, '_DEFAULT_TESTING_CLIENT_TIMEOUT', 0.01)
322
server_sock, client_sock = portable_socket_pair()
323
# This should timeout quickly, but not generate an exception.
324
handler = test_server.TestingSmartConnectionHandler(server_sock,
325
server_sock.getpeername(), s)
327
def test_connection_shutdown_while_serving_no_error(self):
329
server_sock, client_sock = portable_socket_pair()
330
class ShutdownConnectionHandler(
331
test_server.TestingSmartConnectionHandler):
333
def _build_protocol(self):
335
return super(ShutdownConnectionHandler, self)._build_protocol()
336
# This should trigger shutdown after the entering _build_protocol, and
337
# we should exit cleanly, without raising an exception.
338
handler = ShutdownConnectionHandler(server_sock,
339
server_sock.getpeername(), s)