Rev 5261: Start implementing a TCP server running in its own thread (using in file:///home/vila/src/bzr/experimental/leaking-tests/

Vincent Ladeuil v.ladeuil+lp at free.fr
Wed Jun 2 15:50:04 BST 2010


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

------------------------------------------------------------
revno: 5261
revision-id: v.ladeuil+lp at free.fr-20100602145004-mth2e7a51ox29nw5
parent: v.ladeuil+lp at free.fr-20100602130124-wapaxlsay5csk345
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: propagate-exceptions
timestamp: Wed 2010-06-02 16:50:04 +0200
message:
  Start implementing a TCP server running in its own thread (using
  HttpServer/TestingHTTPServerMixin as a base).
  
  * bzrlib/tests/__init__.py:
  (_test_suite_testmod_names): Add test_test_server.
  
  * bzrlib/tests/test_test_server.py:
  Start testing the server in a thread.
  
  * bzrlib/tests/test_server.py:
  (TestingTCPServerMixin, TestingTCPServer)
  (TestingTCPServerInAThread): Helpers to provide a test server
  running in its own thread.
-------------- next part --------------
=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2010-05-31 21:31:18 +0000
+++ b/bzrlib/tests/__init__.py	2010-06-02 14:50:04 +0000
@@ -3796,6 +3796,7 @@
         'bzrlib.tests.test_switch',
         'bzrlib.tests.test_symbol_versioning',
         'bzrlib.tests.test_tag',
+        'bzrlib.tests.test_test_server',
         'bzrlib.tests.test_testament',
         'bzrlib.tests.test_textfile',
         'bzrlib.tests.test_textmerge',

=== modified file 'bzrlib/tests/http_server.py'
--- a/bzrlib/tests/http_server.py	2010-06-02 13:01:24 +0000
+++ b/bzrlib/tests/http_server.py	2010-06-02 14:50:04 +0000
@@ -316,7 +316,6 @@
         return path
 
 
-# FIXME: This should be called TestingServerInAThread
 class TestingHTTPServerMixin:
 
     def __init__(self, test_case_server):
@@ -466,7 +465,6 @@
                 raise
 
 
-# FIXME: TestingHTTPServerMixin shouldn't be first -- vila 20100531
 class TestingHTTPServer(TestingHTTPServerMixin, SocketServer.TCPServer):
 
     def __init__(self, server_address, request_handler_class,
@@ -483,7 +481,6 @@
         TestingHTTPServerMixin.server_bind(self)
 
 
-# FIXME: TestingHTTPServerMixin shouldn't be first -- vila 20100531
 class TestingThreadingHTTPServer(TestingHTTPServerMixin,
                                  SocketServer.ThreadingTCPServer,
                                  ):

=== modified file 'bzrlib/tests/test_server.py'
--- a/bzrlib/tests/test_server.py	2010-05-26 16:48:55 +0000
+++ b/bzrlib/tests/test_server.py	2010-06-02 14:50:04 +0000
@@ -15,12 +15,14 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
 import socket
+import SocketServer
 import select
 import sys
 import threading
 
 
 from bzrlib import (
+    osutils,
     transport,
     urlutils,
     )
@@ -238,7 +240,7 @@
 
     def __init__(self, *args, **kwargs):
         # There are cases where the calling thread must wait, yet, if an
-        # exception occurs the event should be set so the caller is not
+        # exception occurs, the event should be set so the caller is not
         # blocked. The main example is a calling thread that want to wait for
         # the called thread to be in a given state before continuing.
         try:
@@ -279,6 +281,110 @@
             raise exc_class, exc_value, exc_tb
 
 
+class TestingTCPServerMixin:
+    """Mixin to support running SocketServer.TCPServer in a thread.
+
+
+    Tests are connecting from the main thread, the server has to be run in a
+    separate thread.
+    """
+
+    def __init__(self):
+        self.started = threading.Event()
+        self.serving = threading.Event()
+        self.stopped = threading.Event()
+
+    def serve(self):
+        self.serving.set()
+        self.stopped.clear()
+        # We are listening and ready to accept connections
+        self.started.set()
+        while self.serving.isSet():
+            # Really a connection but the python framework is generic and
+            # call them requests
+            self.handle_request()
+        # Let's close the listening socket
+        self.server_close()
+        self.stopped.set()
+
+    def stop_clients(self):
+        pass
+
+
+class TestingTCPServer(TestingTCPServerMixin, SocketServer.TCPServer):
+
+    def __init__(self, server_address, request_handler_class):
+        TestingTCPServerMixin.__init__(self)
+        SocketServer.TCPServer.__init__(self, server_address,
+                                        request_handler_class)
+
+    def server_bind(self):
+        SocketServer.TCPServer.server_bind(self)
+        # The following has been fixed in 2.5 so we need to provide it for
+        # older python versions.
+        if sys.version < (2, 5):
+            self.server_address = self.socket.getsockname()
+
+
+class TestingTCPServerInAThread(object):
+
+    def __init__(self, server_address, server_class, request_handler_class):
+        self.server_class = server_class
+        self.request_handler_class = request_handler_class
+        self.server_address = server_address
+
+    def create_server(self):
+        return self.server_class(self.server_address,
+                                 self.request_handler_class)
+
+    def start_server(self):
+        self.server = self.create_server()
+        self._server_thread = ThreadWithException(
+            event=self.server.started, target=self.run_server)
+        self._server_thread.start()
+        # Wait for the server thread to start (i.e release the lock)
+        self.server.started.wait()
+        # 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)
+
+    def run_server(self):
+        self.server.serve()
+
+    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.
+        self.server.serving.clear()
+        # The server is listening for a last connection, let's give it:
+        last_conn = None
+        try:
+            last_conn = osutils.connect_socket(self.server.server_address)
+        except socket.error, e:
+            # But ignore connection errors as the point is to unblock the
+            # server thread, it may happen that it's not blocked or even not
+            # started.
+            pass
+        self.server.stop_clients()
+        # Now we wait for the thread running self.server.serve() to finish
+        self.server.stopped.wait()
+        if last_conn is not None:
+            # Close the last connection without trying to use it. The server
+            # 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
+
+
 class SmartTCPServer_for_testing(server.SmartTCPServer):
     """Server suitable for use by transport tests.
 

=== added file 'bzrlib/tests/test_test_server.py'
--- a/bzrlib/tests/test_test_server.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_test_server.py	2010-06-02 14:50:04 +0000
@@ -0,0 +1,62 @@
+# Copyright (C) 2010 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import socket
+import SocketServer
+
+from bzrlib import (
+    osutils,
+    tests,
+    )
+from bzrlib.tests import test_server
+
+class TCPClient(object):
+
+    def __init__(self):
+        self.sock = None
+
+    def connect(self, addr):
+        self.sock = osutils.connect_socket(addr)
+
+    def disconnect(self):
+        if self.sock is not None:
+            self.sock.shutdown(socket.SHUT_RDWR)
+            self.sock.close()
+            self.sock = None
+
+class TCPConnectionHandler(SocketServer.StreamRequestHandler):
+
+    def handle(self):
+        pass
+
+
+class TestTestingServerInAThread(tests.TestCase):
+
+    def test_start_stop(self):
+        server = test_server.TestingTCPServerInAThread(
+            ('localhost', 0), test_server.TestingTCPServer,
+            TCPConnectionHandler)
+        client = TCPClient()
+        server.start_server()
+        self.addCleanup(server.stop_server)
+        client.connect(server.server_address)
+        self.addCleanup(client.disconnect)
+        server.stop_server()
+        # since the server doesn't accept connections anymore attempting to
+        # connect should fail
+        self.assertRaises(socket.error, client.connect, server.server_address)
+
+



More information about the bazaar-commits mailing list