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