/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_test_server.py

  • Committer: Vincent Ladeuil
  • Date: 2011-10-07 15:49:08 UTC
  • mto: (6015.33.11 2.4)
  • mto: This revision was merged to the branch mainline in revision 6206.
  • Revision ID: v.ladeuil+lp@free.fr-20111007154908-51te99s60r7hsisd
Less code, more explanations.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2010, 2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import errno
 
18
import socket
 
19
import SocketServer
 
20
import threading
 
21
 
 
22
from bzrlib import (
 
23
    osutils,
 
24
    tests,
 
25
    )
 
26
from bzrlib.tests import test_server
 
27
from bzrlib.tests.scenarios import load_tests_apply_scenarios
 
28
 
 
29
 
 
30
load_tests = load_tests_apply_scenarios
 
31
 
 
32
 
 
33
class TCPClient(object):
 
34
 
 
35
    def __init__(self):
 
36
        self.sock = None
 
37
 
 
38
    def connect(self, addr):
 
39
        if self.sock is not None:
 
40
            raise AssertionError('Already connected to %r'
 
41
                                 % (self.sock.getsockname(),))
 
42
        self.sock = osutils.connect_socket(addr)
 
43
 
 
44
    def disconnect(self):
 
45
        if self.sock is not None:
 
46
            try:
 
47
                self.sock.shutdown(socket.SHUT_RDWR)
 
48
                self.sock.close()
 
49
            except socket.error, e:
 
50
                if e[0] in (errno.EBADF, errno.ENOTCONN):
 
51
                    # Right, the socket is already down
 
52
                    pass
 
53
                else:
 
54
                    raise
 
55
            self.sock = None
 
56
 
 
57
    def write(self, s):
 
58
        return self.sock.sendall(s)
 
59
 
 
60
    def read(self, bufsize=4096):
 
61
        return self.sock.recv(bufsize)
 
62
 
 
63
 
 
64
class TCPConnectionHandler(SocketServer.StreamRequestHandler):
 
65
 
 
66
    def handle(self):
 
67
        self.done = False
 
68
        self.handle_connection()
 
69
        while not self.done:
 
70
            self.handle_connection()
 
71
 
 
72
    def handle_connection(self):
 
73
        req = self.rfile.readline()
 
74
        if not req:
 
75
            self.done = True
 
76
        elif req == 'ping\n':
 
77
            self.wfile.write('pong\n')
 
78
        else:
 
79
            raise ValueError('[%s] not understood' % req)
 
80
 
 
81
 
 
82
class TestTCPServerInAThread(tests.TestCase):
 
83
 
 
84
    scenarios = [ 
 
85
        (name, {'server_class': getattr(test_server, name)})
 
86
        for name in
 
87
        ('TestingTCPServer', 'TestingThreadingTCPServer')]
 
88
 
 
89
    def get_server(self, server_class=None, connection_handler_class=None):
 
90
        if server_class is not None:
 
91
            self.server_class = server_class
 
92
        if connection_handler_class is None:
 
93
            connection_handler_class = TCPConnectionHandler
 
94
        server =  test_server.TestingTCPServerInAThread(
 
95
            ('localhost', 0), self.server_class, connection_handler_class)
 
96
        server.start_server()
 
97
        self.addCleanup(server.stop_server)
 
98
        return server
 
99
 
 
100
    def get_client(self):
 
101
        client = TCPClient()
 
102
        self.addCleanup(client.disconnect)
 
103
        return client
 
104
 
 
105
    def get_server_connection(self, server, conn_rank):
 
106
        return server.server.clients[conn_rank]
 
107
 
 
108
    def assertClientAddr(self, client, server, conn_rank):
 
109
        conn = self.get_server_connection(server, conn_rank)
 
110
        self.assertEquals(client.sock.getsockname(), conn[1])
 
111
 
 
112
    def test_start_stop(self):
 
113
        server = self.get_server()
 
114
        client = self.get_client()
 
115
        server.stop_server()
 
116
        # since the server doesn't accept connections anymore attempting to
 
117
        # connect should fail
 
118
        client = self.get_client()
 
119
        self.assertRaises(socket.error,
 
120
                          client.connect, (server.host, server.port))
 
121
 
 
122
    def test_client_talks_server_respond(self):
 
123
        server = self.get_server()
 
124
        client = self.get_client()
 
125
        client.connect((server.host, server.port))
 
126
        self.assertIs(None, client.write('ping\n'))
 
127
        resp = client.read()
 
128
        self.assertClientAddr(client, server, 0)
 
129
        self.assertEquals('pong\n', resp)
 
130
 
 
131
    def test_server_fails_to_start(self):
 
132
        class CantStart(Exception):
 
133
            pass
 
134
 
 
135
        class CantStartServer(test_server.TestingTCPServer):
 
136
 
 
137
            def server_bind(self):
 
138
                raise CantStart()
 
139
 
 
140
        # The exception is raised in the main thread
 
141
        self.assertRaises(CantStart,
 
142
                          self.get_server, server_class=CantStartServer)
 
143
 
 
144
    def test_server_fails_while_serving_or_stopping(self):
 
145
        class CantConnect(Exception):
 
146
            pass
 
147
 
 
148
        class FailingConnectionHandler(TCPConnectionHandler):
 
149
 
 
150
            def handle(self):
 
151
                raise CantConnect()
 
152
 
 
153
        server = self.get_server(
 
154
            connection_handler_class=FailingConnectionHandler)
 
155
        # The server won't fail until a client connect
 
156
        client = self.get_client()
 
157
        client.connect((server.host, server.port))
 
158
        try:
 
159
            # Now we must force the server to answer by sending the request and
 
160
            # waiting for some answer. But since we don't control when the
 
161
            # server thread will be given cycles, we don't control either
 
162
            # whether our reads or writes may hang.
 
163
            client.sock.settimeout(0.1)
 
164
            client.write('ping\n')
 
165
            client.read()
 
166
        except socket.error:
 
167
            pass
 
168
        # Now the server has raised the exception in its own thread
 
169
        self.assertRaises(CantConnect, server.stop_server)
 
170
 
 
171
    def test_server_crash_while_responding(self):
 
172
        # We want to ensure the exception has been caught
 
173
        caught = threading.Event()
 
174
        caught.clear()
 
175
        # The thread that will serve the client, this needs to be an attribute
 
176
        # so the handler below can modify it when it's executed (it's
 
177
        # instantiated when the request is processed)
 
178
        self.connection_thread = None
 
179
 
 
180
        class FailToRespond(Exception):
 
181
            pass
 
182
 
 
183
        class FailingDuringResponseHandler(TCPConnectionHandler):
 
184
 
 
185
            def handle_connection(request):
 
186
                req = request.rfile.readline()
 
187
                # Capture the thread and make it use 'caught' so we can wait on
 
188
                # the even that will be set when the exception is caught. We
 
189
                # also capture the thread to know where to look.
 
190
                self.connection_thread = threading.currentThread()
 
191
                self.connection_thread.set_sync_event(caught)
 
192
                raise FailToRespond()
 
193
 
 
194
        server = self.get_server(
 
195
            connection_handler_class=FailingDuringResponseHandler)
 
196
        client = self.get_client()
 
197
        client.connect((server.host, server.port))
 
198
        client.write('ping\n')
 
199
        # Wait for the exception to be caught
 
200
        caught.wait()
 
201
        # Check that the connection thread did catch the exception,
 
202
        # http://pad.lv/869366 was wrongly checking the server thread which
 
203
        # works for TestingTCPServer where the connection is handled in the
 
204
        # same thread than the server one but is racy for
 
205
        # TestingThreadingTCPServer where the server thread may be in a
 
206
        # blocking accept() call (or not).
 
207
        try:
 
208
            self.connection_thread.pending_exception()
 
209
        except FailToRespond:
 
210
            # Great, the test succeeded
 
211
            pass
 
212
        else:
 
213
            # If the exception is not in the connection thread anymore, it's in
 
214
            # the server's one. 
 
215
            server.server.stopped.wait()
 
216
            # The exception is available now
 
217
            self.assertRaises(FailToRespond, server.pending_exception)
 
218
 
 
219
    def test_exception_swallowed_while_serving(self):
 
220
        # We need to ensure the exception has been caught
 
221
        caught = threading.Event()
 
222
        caught.clear()
 
223
        # The thread that will serve the client, this needs to be an attribute
 
224
        # so the handler below can access it when it's executed (it's
 
225
        # instantiated when the request is processed)
 
226
        self.connection_thread = None
 
227
        class CantServe(Exception):
 
228
            pass
 
229
 
 
230
        class FailingWhileServingConnectionHandler(TCPConnectionHandler):
 
231
 
 
232
            def handle(request):
 
233
                # Capture the thread and make it use 'caught' so we can wait on
 
234
                # the even that will be set when the exception is caught. We
 
235
                # also capture the thread to know where to look.
 
236
                self.connection_thread = threading.currentThread()
 
237
                self.connection_thread.set_sync_event(caught)
 
238
                raise CantServe()
 
239
 
 
240
        server = self.get_server(
 
241
            connection_handler_class=FailingWhileServingConnectionHandler)
 
242
        # Install the exception swallower
 
243
        server.set_ignored_exceptions(CantServe)
 
244
        client = self.get_client()
 
245
        # Connect to the server so the exception is raised there
 
246
        client.connect((server.host, server.port))
 
247
        # Wait for the exception to be caught
 
248
        caught.wait()
 
249
        # The connection wasn't served properly but the exception should have
 
250
        # been swallowed (see test_server_crash_while_responding remark about
 
251
        # http://pad.lv/869366 explaining why we can't check the server thread
 
252
        # here). More precisely, the exception *has* been caught and captured
 
253
        # but it is cleared when joining the thread (or trying to acquire the
 
254
        # exception) and as such won't propagate to the server thread.
 
255
        self.connection_thread.pending_exception()
 
256
        server.pending_exception()