r46817 - lots more edge cases, stop testing internal state

glyph-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: glyph
Date: Thu Feb 18 01:19:13 2016
New Revision: 46817

Modified:
   branches/persistent-client-service-4735-5/twisted/application/internet.py
   branches/persistent-client-service-4735-5/twisted/application/test/test_internet.py

Log:
lots more edge cases, stop testing internal state

Modified: branches/persistent-client-service-4735-5/twisted/application/internet.py
==============================================================================
--- branches/persistent-client-service-4735-5/twisted/application/internet.py	(original)
+++ branches/persistent-client-service-4735-5/twisted/application/internet.py	Thu Feb 18 01:19:13 2016
@@ -525,7 +525,8 @@
 class ClientService(service.Service, object):
     """
     A L{ClientService} maintains a single outgoing connection to a client
-    endpoint, with configurable timeout policies.
+    endpoint, reconnecting after a configurable timeout when a connection
+    fails, either before or after connecting.
     """
 
     _log = Logger()
@@ -564,11 +565,34 @@
         self._connectionInProgress = succeed(None)
         self._loseConnection = lambda: None
 
+        self._currentConnection = None
+        self._awaitingConnected = []
+
+
+    def whenConnected(self):
+        """
+        Retrieve the currently-connected L{Protocol}, or the next one to
+        connect.
+
+        @return: a Deferred that fires with a protocol produced by the factory
+            passed to C{__init__}
+        @rtype: L{Deferred} firing with L{IProtocol} or failing with
+            L{CancelledError} the service is stopped.
+        """
+        if self._currentConnection is not None:
+            return succeed(self._currentConnection)
+        else:
+            # XXX WROOONG
+            return Deferred()
+
 
     def startService(self):
         """
         Start this L{ClientService}, initiating the connection retry loop.
         """
+        if self.running:
+            self._log.warn("Duplicate ClientService.startService {log_source}")
+            return
         super(ClientService, self).startService()
         self._failedAttempts = 0
 
@@ -576,11 +600,16 @@
             self._failedAttempts = 0
             self._loseConnection = protocol.transport.loseConnection
             self._lostDeferred = Deferred()
+            self._currentConnection = protocol._protocol
+            self._awaitingConnected, waiting = [], self._awaitingConnected
+            for w in waiting:
+                w.callback(self._currentConnection)
 
         def clientDisconnect(reason):
+            self._currentConnection = None
             self._loseConnection = lambda: None
             self._lostDeferred.callback(None)
-            # XXX SHOULD BE A retry() HERE
+            retry(reason)
 
         factoryProxy = _DisconnectFactory(self._factory, clientDisconnect)
 
@@ -590,7 +619,9 @@
                                           .addCallback(clientConnect)
                                           .addErrback(retry))
 
-        def retry(error=None):
+        def retry(failure):
+            if not self.running:
+                return
             self._failedAttempts += 1
             delay = self._timeoutForAttempt(self._failedAttempts)
             self._log.info("Scheduling retry {attempt} to connect {endpoint} "

Modified: branches/persistent-client-service-4735-5/twisted/application/test/test_internet.py
==============================================================================
--- branches/persistent-client-service-4735-5/twisted/application/test/test_internet.py	(original)
+++ branches/persistent-client-service-4735-5/twisted/application/test/test_internet.py	Thu Feb 18 01:19:13 2016
@@ -432,6 +432,7 @@
     def __init__(self):
         self.connectQueue = []
         self.constructedProtocols = []
+        self.applicationProtocols = []
 
 
 
@@ -499,12 +500,21 @@
         nkw.update(clock=Clock())
         nkw.update(kw)
         cq, endpoint = endpointForTesting(fireImmediately=fireImmediately)
-        factory = Factory.forProtocol(Protocol)
+        class RememberingFactory(Factory, object):
+            protocol = Protocol
+            def buildProtocol(self, addr):
+                result = super(RememberingFactory, self).buildProtocol(addr)
+                cq.applicationProtocols.append(result)
+                return result
+        factory = RememberingFactory()
         service = ClientService(endpoint, factory, **nkw)
         def stop():
             service._protocol = None
             if service.running:
                 service.stopService()
+            # Ensure that we don't leave any state in the reactor after
+            # stopService.
+            self.assertEqual(service._clock.getDelayedCalls(), [])
         self.addCleanup(stop)
         if startService:
             service.startService()
@@ -568,8 +578,10 @@
         """
         clock = Clock()
         cq, service = self.makeReconnector(clock=clock)
+        awaitingProtocol = service.whenConnected()
         self.assertEqual(clock.getDelayedCalls(), [])
-        self.assertIdentical(service._protocol, cq.constructedProtocols[0])
+        self.assertIdentical(self.successResultOf(awaitingProtocol),
+                             cq.applicationProtocols[0])
 
 
     def test_clientConnectionFailed(self):
@@ -582,7 +594,7 @@
                                            clock=clock)
         self.assertEqual(len(cq.connectQueue), 1)
         cq.connectQueue[0].errback(Failure(Exception()))
-        self.assertIdentical(service._protocol, None)
+        self.assertNoResult(service.whenConnected())
         clock.advance(100.)
         self.assertEqual(len(cq.connectQueue), 2)
 
@@ -592,10 +604,19 @@
         When a client connection is lost, the service removes its reference
         to the protocol and calls retry.
         """
-        cq, service = self.makeReconnector()
-        service.startService()
+        clock = Clock()
+        cq, service = self.makeReconnector(clock=clock, fireImmediately=False)
+        self.assertEquals(len(cq.connectQueue), 1)
+        cq.connectQueue[0].callback(None)
+        self.assertEquals(len(cq.connectQueue), 1)
+        self.assertIdentical(self.successResultOf(service.whenConnected()),
+                             cq.applicationProtocols[0])
         cq.constructedProtocols[0].connectionLost(Failure(Exception()))
-        self.assertIdentical(service._protocol, None)
+        clock.advance(100.)
+        self.assertEquals(len(cq.connectQueue), 2)
+        cq.connectQueue[1].callback(None)
+        self.assertIdentical(self.successResultOf(service.whenConnected()),
+                             cq.applicationProtocols[1])
 
 
     def test_clientConnectionLostWhileStopping(self):
@@ -606,6 +627,23 @@
         """
         cq, service = self.makeReconnector()
         d = service.stopService()
-        cq.constructedProtocols[0].connectionLost(Failure(Exception()))
-        self.assertIdentical(service._protocol, None)
+        cq.constructedProtocols[0].connectionLost(Failure(IndentationError()))
+        self.assertFailure(service.whenConnected(), CancelledError)
         self.assertTrue(d.called)
+
+
+    def test_startTwice(self):
+        """
+        If L{ClientService} is started when it's already started, it will log a
+        complaint and do nothing else (in particular it will not make
+        additional connections).
+        """
+        cq, service = self.makeReconnector(fireImmediately=False,
+                                           startService=False)
+        self.assertEqual(len(cq.connectQueue), 0)
+        service.startService()
+        self.assertEqual(len(cq.connectQueue), 1)
+        messages = catchLogs(self)
+        service.startService()
+        self.assertEqual(len(cq.connectQueue), 1)
+        self.assertIn("Duplicate ClientService.startService", messages()[0])
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.