Rev 5271: Implement an execption handling mechanism that can be injected in ThreadWithException. in file:///home/vila/src/bzr/experimental/leaking-tests/

Vincent Ladeuil v.ladeuil+lp at free.fr
Mon Jun 7 13:55:58 BST 2010


At file:///home/vila/src/bzr/experimental/leaking-tests/

------------------------------------------------------------
revno: 5271
revision-id: v.ladeuil+lp at free.fr-20100607125558-5rdbfk21podzot0x
parent: v.ladeuil+lp at free.fr-20100607094426-fwsjn2x91965lufu
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: propagate-exceptions
timestamp: Mon 2010-06-07 14:55:58 +0200
message:
  Implement an execption handling mechanism that can be injected in ThreadWithException.
  
  * bzrlib/tests/test_test_server.py:
  (TestTCPServerInAThread.test_exception_swallowed_while_serving):
  Check that the expected exception is swallowed.
  
  * bzrlib/tests/test_server.py:
  (ThreadWithException.set_ignored_exceptions): Allows exception
  handlers to be installed.
  (ThreadWithException.join): Respect ignored_exceptions.
  (TestingTCPServerMixin.set_ignored_exceptions)
  (TestingTCPServerMixin._pending_exections): New helpers.
  (TestingThreadingTCPServer.process_request)
  (TestingThreadingTCPServer.set_ignored_exceptions): Propagate
  exception_handler.
  (TestingTCPServerInAThread.set_ignored_exceptions): New helper.
-------------- next part --------------
=== modified file 'bzrlib/tests/test_server.py'
--- a/bzrlib/tests/test_server.py	2010-06-07 09:44:26 +0000
+++ b/bzrlib/tests/test_server.py	2010-06-07 12:55:58 +0000
@@ -252,10 +252,31 @@
         super(ThreadWithException, self).__init__(*args, **kwargs)
         self.set_event(event)
         self.exception = None
+        self.ignored_exceptions = None # see set_ignored_exceptions
 
     def set_event(self, event):
         self.ready = event
 
+    def set_ignored_exceptions(self, ignored):
+        """Declare which exceptions will be ignored.
+
+        :param ignored: Can be either:
+           - None: all exceptions will be raised,
+           - an exception class: the instances of this class will be ignored,
+           - a tuple of exception classes: the instances of any class of the
+             list will be ignored,
+           - a callable: that will be passed exc_class, exc_value
+             and should return True if the exception should be ignored
+        """
+        if ignored is None:
+            self.ignored_exceptions = None
+        elif isinstance(ignored, Exception):
+            self.ignored_exceptions = lambda c, v: c is ignored
+        elif isinstance(ignored, tuple):
+            self.ignored_exceptions = lambda c, v: isinstance(v, ignored)
+        else:
+            self.ignored_exceptions = ignored
+
     def run(self):
         """Overrides Thread.run to capture any exception."""
         self.ready.clear()
@@ -282,7 +303,10 @@
         if self.exception is not None:
             exc_class, exc_value, exc_tb = self.exception
             self.exception = None # The exception should be raised only once
-            raise exc_class, exc_value, exc_tb
+            if (self.ignored_exceptions is None
+                or not self.ignored_exceptions(exc_class, exc_value)):
+                # Raise non ignored exceptions
+                raise exc_class, exc_value, exc_tb
         if timeout and self.isAlive():
             # The timeout expired without joining the thread, the thread is
             # therefore stucked and that's a failure as far as the test is
@@ -312,6 +336,7 @@
         # We collect the resources used by the clients so we can release them
         # when shutting down
         self.clients = []
+        self.ignored_exceptions = None
 
     def server_bind(self):
         # We need to override the SocketServer bind, yet, we still want to use
@@ -378,6 +403,19 @@
             else:
                 raise
 
+    # The following methods are called by the main thread
+
+    def set_ignored_exceptions(self, thread, ignored_exceptions):
+        self.ignored_exceptions = ignored_exceptions
+        thread.set_ignored_exceptions(self.ignored_exceptions)
+
+    def _pending_exception(self, thread):
+        """Raise server uncaught exception.
+
+        Daughter classes can override this if they use daughter threads.
+        """
+        thread.pending_exception()
+
 
 class TestingTCPServer(TestingTCPServerMixin, SocketServer.TCPServer):
 
@@ -398,13 +436,6 @@
         sock, addr = client
         self.shutdown_client_socket(sock)
 
-    def _pending_exception(self, thread):
-        """Raise server uncaught exception.
-
-        Daughter classes can override this if they use daughter threads.
-        """
-        thread.pending_exception()
-
 
 class TestingThreadingTCPServer(TestingTCPServerMixin,
                                 SocketServer.ThreadingTCPServer):
@@ -436,10 +467,13 @@
             event=stopped,
             target = self.process_request_thread,
             args = (started, stopped, request, client_address))
-        t.name = '%s -> %s' % (client_address, self.server_address)
         # Update the client description
         self.clients.pop()
         self.clients.append((request, client_address, t))
+        # Propagate the exception handler since we must the same one for
+        # connections running in their own threads than TestingTCPServer.
+        t.set_ignored_exceptions(self.ignored_exceptions)
+        t.name = '%s -> %s' % (client_address, self.server_address)
         t.start()
         started.wait()
         # If an exception occured during the thread start, it will get raised.
@@ -457,11 +491,19 @@
             # re-raised
             connection_thread.join()
 
+    def set_ignored_exceptions(self, thread, ignored_exceptions):
+        TestingTCPServerMixin.set_ignored_exceptions(self, thread,
+                                                     ignored_exceptions)
+        for sock, addr, connection_thread in self.clients:
+            if connection_thread is not None:
+                connection_thread.set_ignored_exceptions(
+                    self.ignored_exceptions)
+
     def _pending_exception(self, thread):
         for sock, addr, connection_thread in self.clients:
             if connection_thread is not None:
                 connection_thread.pending_exception()
-        super(TestingThreadingTCPServer, self)._pending_exception(thread)
+        TestingTCPServerMixin._pending_exception(self, thread)
 
 
 class TestingTCPServerInAThread(transport.Server):
@@ -472,6 +514,7 @@
         self.request_handler_class = request_handler_class
         self.server_address = server_address
         self.server = None
+        self._server_thread = None
 
     def __repr__(self):
         return "%s%r" % (self.__class__.__name__, self.server_address)
@@ -536,6 +579,11 @@
             # the various threads involved.
             self.server = None
 
+    def set_ignored_exceptions(self, ignored_exceptions):
+        """Install an exception handler for the server."""
+        self.server.set_ignored_exceptions(self._server_thread,
+                                           ignored_exceptions)
+
     def pending_exception(self):
         """Raise uncaught exception in the server."""
         self.server._pending_exception(self._server_thread)

=== modified file 'bzrlib/tests/test_test_server.py'
--- a/bzrlib/tests/test_test_server.py	2010-06-07 09:44:26 +0000
+++ b/bzrlib/tests/test_test_server.py	2010-06-07 12:55:58 +0000
@@ -148,13 +148,13 @@
                           self.get_server, server_class=CantStartServer)
 
     def test_server_fails_while_serving_or_stoping(self):
-        class ServerFailure(Exception):
+        class CantConnect(Exception):
             pass
 
         class FailingConnectionHandler(TCPConnectionHandler):
 
             def handle(self):
-                raise ServerFailure()
+                raise CantConnect()
 
         server = self.get_server(
             connection_handler_class=FailingConnectionHandler)
@@ -172,13 +172,12 @@
         except socket.error:
             pass
         # Now the server has raise the exception in its own thread
-        self.assertRaises(ServerFailure, server.stop_server)
-
+        self.assertRaises(CantConnect, server.stop_server)
 
     def test_server_crash_while_responding(self):
         sync = threading.Event()
         sync.clear()
-        class ServerFailure(Exception):
+        class FailToRespond(Exception):
             pass
 
         class FailingDuringResponseHandler(TCPConnectionHandler):
@@ -186,7 +185,7 @@
             def handle_connection(self):
                 req = self.rfile.readline()
                 sync.set()
-                raise ServerFailure()
+                raise FailToRespond()
 
         server = self.get_server(
             connection_handler_class=FailingDuringResponseHandler)
@@ -194,4 +193,27 @@
         client.connect(server.server_address)
         client.write('ping\n')
         sync.wait()
-        self.assertRaises(ServerFailure, server.pending_exception)
+        self.assertRaises(FailToRespond, server.pending_exception)
+
+    def test_exception_swallowed_while_serving(self):
+        sync = threading.Event()
+        sync.clear()
+        class CantServe(Exception):
+            pass
+
+        class FailingWhileServingConnectionHandler(TCPConnectionHandler):
+
+            def handle(self):
+                sync.set()
+                raise CantServe()
+
+        server = self.get_server(
+            connection_handler_class=FailingWhileServingConnectionHandler)
+        # Install the exception swallower
+        server.set_ignored_exceptions(CantServe)
+        client = self.get_client()
+        client.connect(server.server_address)
+        sync.wait()
+        # The connection wasn't served properly but the exception should have
+        # been swallowed.
+        server.pending_exception()



More information about the bazaar-commits mailing list