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