~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_server.py

(gz) Change minimum required testtools version for selftest to 0.9.5 for
 unicode fixes (Martin [gz])

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2010, 2011 Canonical Ltd
 
1
# Copyright (C) 2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
import errno
18
18
import socket
19
19
import SocketServer
 
20
import select
20
21
import sys
21
22
import threading
22
23
 
23
24
 
24
25
from bzrlib import (
25
 
    cethread,
26
26
    osutils,
27
27
    transport,
28
28
    urlutils,
243
243
        raise NotImplementedError
244
244
 
245
245
 
246
 
class TestThread(cethread.CatchingExceptionThread):
 
246
class ThreadWithException(threading.Thread):
 
247
    """A catching exception thread.
 
248
 
 
249
    If an exception occurs during the thread execution, it's caught and
 
250
    re-raised when the thread is joined().
 
251
    """
 
252
 
 
253
    def __init__(self, *args, **kwargs):
 
254
        # There are cases where the calling thread must wait, yet, if an
 
255
        # exception occurs, the event should be set so the caller is not
 
256
        # blocked. The main example is a calling thread that want to wait for
 
257
        # the called thread to be in a given state before continuing.
 
258
        try:
 
259
            event = kwargs.pop('event')
 
260
        except KeyError:
 
261
            # If the caller didn't pass a specific event, create our own
 
262
            event = threading.Event()
 
263
        super(ThreadWithException, self).__init__(*args, **kwargs)
 
264
        self.set_ready_event(event)
 
265
        self.exception = None
 
266
        self.ignored_exceptions = None # see set_ignored_exceptions
 
267
 
 
268
    # compatibility thunk for python-2.4 and python-2.5...
 
269
    if sys.version_info < (2, 6):
 
270
        name = property(threading.Thread.getName, threading.Thread.setName)
 
271
 
 
272
    def set_ready_event(self, event):
 
273
        """Set the ``ready`` event used to synchronize exception catching.
 
274
 
 
275
        When the thread uses an event to synchronize itself with another thread
 
276
        (setting it when the other thread can wake up from a ``wait`` call),
 
277
        the event must be set after catching an exception or the other thread
 
278
        will hang.
 
279
 
 
280
        Some threads require multiple events and should set the relevant one
 
281
        when appropriate.
 
282
        """
 
283
        self.ready = event
 
284
 
 
285
    def set_ignored_exceptions(self, ignored):
 
286
        """Declare which exceptions will be ignored.
 
287
 
 
288
        :param ignored: Can be either:
 
289
           - None: all exceptions will be raised,
 
290
           - an exception class: the instances of this class will be ignored,
 
291
           - a tuple of exception classes: the instances of any class of the
 
292
             list will be ignored,
 
293
           - a callable: that will be passed the exception object
 
294
             and should return True if the exception should be ignored
 
295
        """
 
296
        if ignored is None:
 
297
            self.ignored_exceptions = None
 
298
        elif isinstance(ignored, (Exception, tuple)):
 
299
            self.ignored_exceptions = lambda e: isinstance(e, ignored)
 
300
        else:
 
301
            self.ignored_exceptions = ignored
 
302
 
 
303
    def run(self):
 
304
        """Overrides Thread.run to capture any exception."""
 
305
        self.ready.clear()
 
306
        try:
 
307
            try:
 
308
                super(ThreadWithException, self).run()
 
309
            except:
 
310
                self.exception = sys.exc_info()
 
311
        finally:
 
312
            # Make sure the calling thread is released
 
313
            self.ready.set()
 
314
 
247
315
 
248
316
    def join(self, timeout=5):
249
 
        """Overrides to use a default timeout.
 
317
        """Overrides Thread.join to raise any exception caught.
 
318
 
 
319
 
 
320
        Calling join(timeout=0) will raise the caught exception or return None
 
321
        if the thread is still alive.
250
322
 
251
323
        The default timeout is set to 5 and should expire only when a thread
252
324
        serving a client connection is hung.
253
325
        """
254
 
        super(TestThread, self).join(timeout)
 
326
        super(ThreadWithException, self).join(timeout)
 
327
        if self.exception is not None:
 
328
            exc_class, exc_value, exc_tb = self.exception
 
329
            self.exception = None # The exception should be raised only once
 
330
            if (self.ignored_exceptions is None
 
331
                or not self.ignored_exceptions(exc_value)):
 
332
                # Raise non ignored exceptions
 
333
                raise exc_class, exc_value, exc_tb
255
334
        if timeout and self.isAlive():
256
335
            # The timeout expired without joining the thread, the thread is
257
336
            # therefore stucked and that's a failure as far as the test is
264
343
            sys.stderr.write('thread %s hung\n' % (self.name,))
265
344
            #raise AssertionError('thread %s hung' % (self.name,))
266
345
 
 
346
    def pending_exception(self):
 
347
        """Raise the caught exception.
 
348
 
 
349
        This does nothing if no exception occurred.
 
350
        """
 
351
        self.join(timeout=0)
 
352
 
267
353
 
268
354
class TestingTCPServerMixin:
269
355
    """Mixin to support running SocketServer.TCPServer in a thread.
437
523
        """Start a new thread to process the request."""
438
524
        started = threading.Event()
439
525
        stopped = threading.Event()
440
 
        t = TestThread(
441
 
            sync_event=stopped,
 
526
        t = ThreadWithException(
 
527
            event=stopped,
442
528
            name='%s -> %s' % (client_address, self.server_address),
443
529
            target = self.process_request_thread,
444
530
            args = (started, stopped, request, client_address))
445
531
        # Update the client description
446
532
        self.clients.pop()
447
533
        self.clients.append((request, client_address, t))
448
 
        # Propagate the exception handler since we must use the same one as
449
 
        # TestingTCPServer for connections running in their own threads.
 
534
        # Propagate the exception handler since we must use the same one for
 
535
        # connections running in their own threads than TestingTCPServer.
450
536
        t.set_ignored_exceptions(self.ignored_exceptions)
451
537
        t.start()
452
538
        started.wait()
504
590
 
505
591
    def start_server(self):
506
592
        self.server = self.create_server()
507
 
        self._server_thread = TestThread(
508
 
            sync_event=self.server.started,
 
593
        self._server_thread = ThreadWithException(
 
594
            event=self.server.started,
509
595
            target=self.run_server)
510
596
        self._server_thread.start()
511
597
        # Wait for the server thread to start (i.e release the lock)
521
607
        self._server_thread.pending_exception()
522
608
        # From now on, we'll use a different event to ensure the server can set
523
609
        # its exception
524
 
        self._server_thread.set_sync_event(self.server.stopped)
 
610
        self._server_thread.set_ready_event(self.server.stopped)
525
611
 
526
612
    def run_server(self):
527
613
        self.server.serve()
548
634
                # server thread, it may happen that it's not blocked or even
549
635
                # not started.
550
636
                pass
551
 
            # We start shutting down the clients while the server itself is
 
637
            # We start shutting down the client while the server itself is
552
638
            # shutting down.
553
639
            self.server.stop_client_connections()
554
640
            # Now we wait for the thread running self.server.serve() to finish
609
695
        server.SmartTCPServer.__init__(self, backing_transport,
610
696
                                       root_client_path)
611
697
    def serve(self):
 
698
        # FIXME: No test are exercising the hooks for the test server
 
699
        # -- vila 20100618
612
700
        self.run_server_started_hooks()
613
701
        try:
614
702
            TestingThreadingTCPServer.serve(self)
716
804
        """Get a backing transport from a server we are decorating."""
717
805
        url = 'readonly+' + backing_transport_server.get_url()
718
806
        return transport.get_transport(url)
 
807
 
 
808
 
 
809
 
 
810