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