~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_test_server.py

(jameinel) Fix the hanging tests, help prevent future hanging,
 and make the tests handle idle connections properly. (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import SocketServer
20
20
import threading
21
21
 
 
22
 
22
23
from bzrlib import (
23
24
    osutils,
24
25
    tests,
30
31
load_tests = load_tests_apply_scenarios
31
32
 
32
33
 
 
34
def portable_socket_pair():
 
35
    """Return a pair of TCP sockets connected to each other.
 
36
 
 
37
    Unlike socket.socketpair, this should work on Windows.
 
38
    """
 
39
    listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
40
    listen_sock.bind(('127.0.0.1', 0))
 
41
    listen_sock.listen(1)
 
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()
 
45
    listen_sock.close()
 
46
    return server_sock, client_sock
 
47
 
 
48
 
33
49
class TCPClient(object):
34
50
 
35
51
    def __init__(self):
61
77
        return self.sock.recv(bufsize)
62
78
 
63
79
 
64
 
class TCPConnectionHandler(SocketServer.StreamRequestHandler):
 
80
class TCPConnectionHandler(SocketServer.BaseRequestHandler):
65
81
 
66
82
    def handle(self):
67
83
        self.done = False
69
85
        while not self.done:
70
86
            self.handle_connection()
71
87
 
 
88
    def readline(self):
 
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):
 
95
            return req
 
96
        raise ValueError('[%r] not a simple line' % (req,))
 
97
 
72
98
    def handle_connection(self):
73
 
        req = self.rfile.readline()
 
99
        req = self.readline()
74
100
        if not req:
75
101
            self.done = True
76
102
        elif req == 'ping\n':
77
 
            self.wfile.write('pong\n')
 
103
            self.request.sendall('pong\n')
78
104
        else:
79
105
            raise ValueError('[%s] not understood' % req)
80
106
 
94
120
            self.server_class = server_class
95
121
        if connection_handler_class is None:
96
122
            connection_handler_class = TCPConnectionHandler
97
 
        server =  test_server.TestingTCPServerInAThread(
 
123
        server = test_server.TestingTCPServerInAThread(
98
124
            ('localhost', 0), self.server_class, connection_handler_class)
99
125
        server.start_server()
100
126
        self.addCleanup(server.stop_server)
158
184
        # The server won't fail until a client connect
159
185
        client = self.get_client()
160
186
        client.connect((server.host, server.port))
 
187
        # We make sure the server wants to handle a request, but the request is
 
188
        # guaranteed to fail. However, the server should make sure that the
 
189
        # connection gets closed, and stop_server should then raise the
 
190
        # original exception.
 
191
        client.write('ping\n')
161
192
        try:
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')
168
 
            client.read()
169
 
        except socket.error:
170
 
            pass
 
193
            self.assertEqual('', client.read())
 
194
        except socket.error, e:
 
195
            # On Windows, failing during 'handle' means we get
 
196
            # 'forced-close-of-connection'. Possibly because we haven't
 
197
            # processed the write request before we close the socket.
 
198
            WSAECONNRESET = 10054
 
199
            if e.errno in (WSAECONNRESET,):
 
200
                pass
171
201
        # Now the server has raised the exception in its own thread
172
202
        self.assertRaises(CantConnect, server.stop_server)
173
203
 
180
210
        class FailingDuringResponseHandler(TCPConnectionHandler):
181
211
 
182
212
            def handle_connection(self):
183
 
                req = self.rfile.readline()
 
213
                req = self.readline()
184
214
                threading.currentThread().set_sync_event(sync)
185
215
                raise FailToRespond()
186
216
 
190
220
        client.connect((server.host, server.port))
191
221
        client.write('ping\n')
192
222
        sync.wait()
 
223
        self.assertEqual('', client.read()) # connection closed
193
224
        self.assertRaises(FailToRespond, server.pending_exception)
194
225
 
195
226
    def test_exception_swallowed_while_serving(self):
215
246
        client.connect((server.host, server.port))
216
247
        # Wait for the exception to propagate.
217
248
        sync.wait()
 
249
        self.assertEqual('', client.read()) # connection closed
218
250
        # The connection wasn't served properly but the exception should have
219
251
        # been swallowed.
220
252
        server.pending_exception()
221
253
 
 
254
    def test_handle_request_closes_if_it_doesnt_process(self):
 
255
        server = self.get_server()
 
256
        client = self.get_client()
 
257
        server.server.serving = False
 
258
        client.connect((server.host, server.port))
 
259
        self.assertEqual('', client.read())
 
260
 
222
261
 
223
262
class TestTestingSmartServer(tests.TestCase):
224
263
 
231
270
        h = server._make_handler(sock)
232
271
        self.assertEqual(test_server._DEFAULT_TESTING_CLIENT_TIMEOUT,
233
272
                         h._client_timeout)
 
273
 
 
274
 
 
275
class FakeServer(object):
 
276
    """Minimal implementation to pass to TestingSmartConnectionHandler"""
 
277
    backing_transport = None
 
278
    root_client_path = '/'
 
279
 
 
280
 
 
281
class TestTestingSmartConnectionHandler(tests.TestCase):
 
282
 
 
283
    def test_connection_timeout_suppressed(self):
 
284
        self.overrideAttr(test_server, '_DEFAULT_TESTING_CLIENT_TIMEOUT', 0.01)
 
285
        s = FakeServer()
 
286
        server_sock, client_sock = portable_socket_pair()
 
287
        # This should timeout quickly, but not generate an exception.
 
288
        handler = test_server.TestingSmartConnectionHandler(server_sock,
 
289
            server_sock.getpeername(), s)
 
290
 
 
291
    def test_connection_shutdown_while_serving_no_error(self):
 
292
        s = FakeServer()
 
293
        server_sock, client_sock = portable_socket_pair()
 
294
        class ShutdownConnectionHandler(
 
295
            test_server.TestingSmartConnectionHandler):
 
296
 
 
297
            def _build_protocol(self):
 
298
                self.finished = True
 
299
                return super(ShutdownConnectionHandler, self)._build_protocol()
 
300
        # This should trigger shutdown after the entering _build_protocol, and
 
301
        # we should exit cleanly, without raising an exception.
 
302
        handler = ShutdownConnectionHandler(server_sock,
 
303
            server_sock.getpeername(), s)