Rev 5263: Test errors during server life. in file:///home/vila/src/bzr/experimental/leaking-tests/
Vincent Ladeuil
v.ladeuil+lp at free.fr
Wed Jun 2 18:51:51 BST 2010
At file:///home/vila/src/bzr/experimental/leaking-tests/
------------------------------------------------------------
revno: 5263
revision-id: v.ladeuil+lp at free.fr-20100602175151-3ftaxftor7ls5lye
parent: v.ladeuil+lp at free.fr-20100602154508-g5ojvqupg1ioq41a
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: propagate-exceptions
timestamp: Wed 2010-06-02 19:51:51 +0200
message:
Test errors during server life.
* bzrlib/tests/test_test_server.py:
(TCPClient.disconnect): Be more robust on errors.
(TestTestingServerInAThread.get_server): Add some optional
parameters.
(TestTestingServerInAThread.test_server_fails_to_start)
(TestTestingServerInAThread.test_server_fails_while_serving_or_stoping):
Test exception catching in during the significant steps.
* bzrlib/tests/test_server.py:
(TestingTCPServer.handle_error): Override the default behavior, we
want the exception to be raised.
(TestingTCPServerInAThread.start_server): Once the server is
started, we need another event to sync with the main thread.
(TestingTCPServerInAThread.stop_server): Don't forget to join the
thread !
-------------- next part --------------
=== modified file 'bzrlib/tests/test_server.py'
--- a/bzrlib/tests/test_server.py 2010-06-02 15:45:08 +0000
+++ b/bzrlib/tests/test_server.py 2010-06-02 17:51:51 +0000
@@ -249,8 +249,11 @@
# If the caller didn't pass a specific event, create our own
event = threading.Event()
super(ThreadWithException, self).__init__(*args, **kwargs)
+ self.set_event(event)
+ self.exception = None
+
+ def set_event(self, event):
self.ready = event
- self.exception = None
def run(self):
"""Overrides Thread.run to capture any exception."""
@@ -269,7 +272,7 @@
Calling join(timeout=0) will raise the caught exception or return None
- is the thread is still alive.
+ if the thread is still alive.
"""
# Note that we don't care about the timeout parameter here: either the
# thread has raised an exception and it should be raised (and join()
@@ -334,6 +337,11 @@
if sys.version < (2, 5):
self.server_address = self.socket.getsockname()
+ def handle_error(self, request, client_address):
+ # Stop serving and re-raise the last exception seen
+ self.serving.clear()
+ raise
+
class TestingTCPServerInAThread(object):
@@ -341,11 +349,19 @@
self.server_class = server_class
self.request_handler_class = request_handler_class
self.server_address = server_address
+ self.server = None
def create_server(self):
return self.server_class(self.server_address,
self.request_handler_class)
+ def pending_exception(self):
+ """Re-raise the exception raised by the server in its own thread.
+
+ This does nothing if no exception occurred.
+ """
+ self._server_thread.join(timeout=0)
+
def start_server(self):
self.server = self.create_server()
self._server_thread = ThreadWithException(
@@ -356,7 +372,10 @@
# Get the real address, especially the port
self.server_address = self.server.server_address
# If an exception occured during the server start, it will get raised
- self._server_thread.join(timeout=0)
+ self.pending_exception()
+ # From now on, we'll use a different event to ensure the server can set
+ # its exception
+ self._server_thread.set_event(self.server.stopped)
def run_server(self):
self.server.serve()
@@ -364,11 +383,6 @@
def stop_server(self):
if self.server is None:
return
- if self.server.serving is None:
- # If the server wasn't properly started, there is nothing to
- # shutdown.
- self.server = None
- return
# The server has been started successfully, shut it down now
# As soon as we stop serving, no more connection are accepted except
# one to get out of the blocking listen.
@@ -390,8 +404,13 @@
# will not process a single byte on that socket to avoid
# complications (SSL starts with a handshake for example).
last_conn.close()
- # Make sure we can be called twice safely
- self.server = None
+ try:
+ # Check for any exception that could have occurred in the server
+ # thread
+ self._server_thread.join()
+ finally:
+ # Make sure we can be called twice safely
+ self.server = None
class SmartTCPServer_for_testing(server.SmartTCPServer):
=== modified file 'bzrlib/tests/test_test_server.py'
--- a/bzrlib/tests/test_test_server.py 2010-06-02 15:45:08 +0000
+++ b/bzrlib/tests/test_test_server.py 2010-06-02 17:51:51 +0000
@@ -14,6 +14,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import errno
import socket
import SocketServer
@@ -36,8 +37,15 @@
def disconnect(self):
if self.sock is not None:
- self.sock.shutdown(socket.SHUT_RDWR)
- self.sock.close()
+ try:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ self.sock.close()
+ except socket.error, e:
+ if e[0] in (errno.EBADF, errno.ENOTCONN):
+ # Right, the socket is already down
+ pass
+ else:
+ raise
self.sock = None
def write(self, s):
@@ -64,13 +72,15 @@
else:
raise ValueError('[%s] not understood' % req)
-
class TestTestingServerInAThread(tests.TestCase):
- def get_server(self):
+ def get_server(self, server_class=None, connection_handler_class=None):
+ if server_class is None:
+ server_class = test_server.TestingTCPServer
+ if connection_handler_class is None:
+ connection_handler_class = TCPConnectionHandler
server = test_server.TestingTCPServerInAThread(
- ('localhost', 0), test_server.TestingTCPServer,
- TCPConnectionHandler)
+ ('localhost', 0), server_class, connection_handler_class)
server.start_server()
self.addCleanup(server.stop_server)
return server
@@ -90,7 +100,6 @@
client = self.get_client()
self.assertRaises(socket.error, client.connect, server.server_address)
-
def test_client_talks_server_respond(self):
server = self.get_server()
client = self.get_client()
@@ -98,3 +107,43 @@
self.assertIs(None, client.write('ping\n'))
resp = client.read()
self.assertEquals('pong\n', resp)
+
+ def test_server_fails_to_start(self):
+ class CantStart(Exception):
+ pass
+
+ class CantStartServer(test_server.TestingTCPServer):
+
+ def server_bind(self):
+ raise CantStart()
+
+ # The exception is raised in the main thread
+ self.assertRaises(CantStart,
+ self.get_server, server_class=CantStartServer)
+
+ def test_server_fails_while_serving_or_stoping(self):
+ class ServerFailure(Exception):
+ pass
+
+ class FailingConnectionHandler(TCPConnectionHandler):
+
+ def handle(self):
+ raise ServerFailure()
+
+ server = self.get_server(
+ connection_handler_class=FailingConnectionHandler)
+ # The server won't fail until a client connect
+ client = self.get_client()
+ client.connect(server.server_address)
+ try:
+ # Now we must force the server to answer by sending the request and
+ # waiting for some answer. But since we don't control when the
+ # server thread will be given cycles, we don't control either
+ # whether our reads or writes may hang.
+ client.sock.settimeout(0.1)
+ client.write('ping\n')
+ client.read()
+ except socket.error:
+ pass
+ # Now the server has raise the exception in its own thread
+ self.assertRaises(ServerFailure, server.stop_server)
More information about the bazaar-commits
mailing list