Mini Shell
# -*- test-case-name: twisted.application.test.test_internet -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for (new code in) L{twisted.application.internet}.
@var AT_LEAST_ONE_ATTEMPT: At least enough seconds for L{ClientService} to make
one attempt.
"""
import pickle
from zope.interface import implementer
from zope.interface.verify import verifyClass
from twisted.application import internet
from twisted.application.internet import (
ClientService,
StreamServerEndpointService,
TimerService,
)
from twisted.internet import task
from twisted.internet.defer import CancelledError, Deferred
from twisted.internet.interfaces import (
IFileDescriptorReceiver,
IHalfCloseableProtocol,
IListeningPort,
IStreamClientEndpoint,
IStreamServerEndpoint,
)
from twisted.internet.protocol import Factory, Protocol
from twisted.internet.task import Clock
from twisted.logger import formatEvent, globalLogPublisher
from twisted.python.failure import Failure
from twisted.test.proto_helpers import StringTransport
from twisted.trial.unittest import SynchronousTestCase, TestCase
def fakeTargetFunction():
"""
A fake target function for testing TimerService which does nothing.
"""
pass
@implementer(IStreamServerEndpoint)
class FakeServer:
"""
In-memory implementation of L{IStreamServerEndpoint}.
@ivar result: The L{Deferred} resulting from the call to C{listen}, after
C{listen} has been called.
@ivar factory: The factory passed to C{listen}.
@ivar cancelException: The exception to errback C{self.result} when it is
cancelled.
@ivar port: The L{IListeningPort} which C{listen}'s L{Deferred} will fire
with.
@ivar listenAttempts: The number of times C{listen} has been invoked.
@ivar failImmediately: If set, the exception to fail the L{Deferred}
returned from C{listen} before it is returned.
"""
result = None
factory = None
failImmediately = None
cancelException = CancelledError()
listenAttempts = 0
def __init__(self):
self.port = FakePort()
def listen(self, factory):
"""
Return a Deferred and store it for future use. (Implementation of
L{IStreamServerEndpoint}).
@param factory: the factory to listen with
@return: a L{Deferred} stored in L{FakeServer.result}
"""
self.listenAttempts += 1
self.factory = factory
self.result = Deferred(canceller=lambda d: d.errback(self.cancelException))
if self.failImmediately is not None:
self.result.errback(self.failImmediately)
return self.result
def startedListening(self):
"""
Test code should invoke this method after causing C{listen} to be
invoked in order to fire the L{Deferred} previously returned from
C{listen}.
"""
self.result.callback(self.port)
def stoppedListening(self):
"""
Test code should invoke this method after causing C{stopListening} to
be invoked on the port fired from the L{Deferred} returned from
C{listen} in order to cause the L{Deferred} returned from
C{stopListening} to fire.
"""
self.port.deferred.callback(None)
verifyClass(IStreamServerEndpoint, FakeServer)
@implementer(IListeningPort)
class FakePort:
"""
Fake L{IListeningPort} implementation.
@ivar deferred: The L{Deferred} returned by C{stopListening}.
"""
deferred = None
def stopListening(self):
"""
Stop listening.
@return: a L{Deferred} stored in L{FakePort.deferred}
"""
self.deferred = Deferred()
return self.deferred
def getHost(self):
# IListeningPort.getHost
pass
def startListening(self):
# IListeningPort.startListening
pass
verifyClass(IStreamServerEndpoint, FakeServer)
class EndpointServiceTests(TestCase):
"""
Tests for L{twisted.application.internet}.
"""
def setUp(self):
"""
Construct a stub server, a stub factory, and a
L{StreamServerEndpointService} to test.
"""
self.fakeServer = FakeServer()
self.factory = Factory()
self.svc = StreamServerEndpointService(self.fakeServer, self.factory)
def test_privilegedStartService(self):
"""
L{StreamServerEndpointService.privilegedStartService} calls its
endpoint's C{listen} method with its factory.
"""
self.svc.privilegedStartService()
self.assertIdentical(self.factory, self.fakeServer.factory)
def test_synchronousRaiseRaisesSynchronously(self, thunk=None):
"""
L{StreamServerEndpointService.startService} should raise synchronously
if the L{Deferred} returned by its wrapped
L{IStreamServerEndpoint.listen} has already fired with an errback and
the L{StreamServerEndpointService}'s C{_raiseSynchronously} flag has
been set. This feature is necessary to preserve compatibility with old
behavior of L{twisted.internet.strports.service}, which is to return a
service which synchronously raises an exception from C{startService}
(so that, among other things, twistd will not start running). However,
since L{IStreamServerEndpoint.listen} may fail asynchronously, it is a
bad idea to rely on this behavior.
@param thunk: If specified, a callable to execute in place of
C{startService}.
"""
self.fakeServer.failImmediately = ZeroDivisionError()
self.svc._raiseSynchronously = True
self.assertRaises(ZeroDivisionError, thunk or self.svc.startService)
def test_synchronousRaisePrivileged(self):
"""
L{StreamServerEndpointService.privilegedStartService} should behave the
same as C{startService} with respect to
L{EndpointServiceTests.test_synchronousRaiseRaisesSynchronously}.
"""
self.test_synchronousRaiseRaisesSynchronously(self.svc.privilegedStartService)
def test_failReportsError(self):
"""
L{StreamServerEndpointService.startService} and
L{StreamServerEndpointService.privilegedStartService} should both log
an exception when the L{Deferred} returned from their wrapped
L{IStreamServerEndpoint.listen} fails.
"""
self.svc.startService()
self.fakeServer.result.errback(ZeroDivisionError())
logged = self.flushLoggedErrors(ZeroDivisionError)
self.assertEqual(len(logged), 1)
def test_asynchronousFailReportsError(self):
"""
L{StreamServerEndpointService.startService} and
L{StreamServerEndpointService.privilegedStartService} should both log
an exception when the L{Deferred} returned from their wrapped
L{IStreamServerEndpoint.listen} fails asynchronously, even if
C{_raiseSynchronously} is set.
"""
self.svc._raiseSynchronously = True
self.svc.startService()
self.fakeServer.result.errback(ZeroDivisionError())
logged = self.flushLoggedErrors(ZeroDivisionError)
self.assertEqual(len(logged), 1)
def test_synchronousFailReportsError(self):
"""
Without the C{_raiseSynchronously} compatibility flag, failing
immediately has the same behavior as failing later; it logs the error.
"""
self.fakeServer.failImmediately = ZeroDivisionError()
self.svc.startService()
logged = self.flushLoggedErrors(ZeroDivisionError)
self.assertEqual(len(logged), 1)
def test_startServiceUnstarted(self):
"""
L{StreamServerEndpointService.startService} sets the C{running} flag,
and calls its endpoint's C{listen} method with its factory, if it
has not yet been started.
"""
self.svc.startService()
self.assertIdentical(self.factory, self.fakeServer.factory)
self.assertEqual(self.svc.running, True)
def test_startServiceStarted(self):
"""
L{StreamServerEndpointService.startService} sets the C{running} flag,
but nothing else, if the service has already been started.
"""
self.test_privilegedStartService()
self.svc.startService()
self.assertEqual(self.fakeServer.listenAttempts, 1)
self.assertEqual(self.svc.running, True)
def test_stopService(self):
"""
L{StreamServerEndpointService.stopService} calls C{stopListening} on
the L{IListeningPort} returned from its endpoint, returns the
C{Deferred} from stopService, and sets C{running} to C{False}.
"""
self.svc.privilegedStartService()
self.fakeServer.startedListening()
# Ensure running gets set to true
self.svc.startService()
result = self.svc.stopService()
l = []
result.addCallback(l.append)
self.assertEqual(len(l), 0)
self.fakeServer.stoppedListening()
self.assertEqual(len(l), 1)
self.assertFalse(self.svc.running)
def test_stopServiceBeforeStartFinished(self):
"""
L{StreamServerEndpointService.stopService} cancels the L{Deferred}
returned by C{listen} if it has not yet fired. No error will be logged
about the cancellation of the listen attempt.
"""
self.svc.privilegedStartService()
result = self.svc.stopService()
l = []
result.addBoth(l.append)
self.assertEqual(l, [None])
self.assertEqual(self.flushLoggedErrors(CancelledError), [])
def test_stopServiceCancelStartError(self):
"""
L{StreamServerEndpointService.stopService} cancels the L{Deferred}
returned by C{listen} if it has not fired yet. An error will be logged
if the resulting exception is not L{CancelledError}.
"""
self.fakeServer.cancelException = ZeroDivisionError()
self.svc.privilegedStartService()
result = self.svc.stopService()
l = []
result.addCallback(l.append)
self.assertEqual(l, [None])
stoppingErrors = self.flushLoggedErrors(ZeroDivisionError)
self.assertEqual(len(stoppingErrors), 1)
class TimerServiceTests(TestCase):
"""
Tests for L{twisted.application.internet.TimerService}.
@type timer: L{TimerService}
@ivar timer: service to test
@type clock: L{task.Clock}
@ivar clock: source of time
@type deferred: L{Deferred}
@ivar deferred: deferred returned by L{TimerServiceTests.call}.
"""
def setUp(self):
"""
Set up a timer service to test.
"""
self.timer = TimerService(2, self.call)
self.clock = self.timer.clock = task.Clock()
self.deferred = Deferred()
def call(self):
"""
Function called by L{TimerService} being tested.
@returns: C{self.deferred}
@rtype: L{Deferred}
"""
return self.deferred
def test_startService(self):
"""
When L{TimerService.startService} is called, it marks itself
as running, creates a L{task.LoopingCall} and starts it.
"""
self.timer.startService()
self.assertTrue(self.timer.running, "Service is started")
self.assertIsInstance(self.timer._loop, task.LoopingCall)
self.assertIdentical(self.clock, self.timer._loop.clock)
self.assertTrue(self.timer._loop.running, "LoopingCall is started")
def test_startServiceRunsCallImmediately(self):
"""
When L{TimerService.startService} is called, it calls the function
immediately.
"""
result = []
self.timer.call = (result.append, (None,), {})
self.timer.startService()
self.assertEqual([None], result)
def test_startServiceUsesGlobalReactor(self):
"""
L{TimerService.startService} uses L{internet._maybeGlobalReactor} to
choose the reactor to pass to L{task.LoopingCall}
uses the global reactor.
"""
otherClock = task.Clock()
def getOtherClock(maybeReactor):
return otherClock
self.patch(internet, "_maybeGlobalReactor", getOtherClock)
self.timer.startService()
self.assertIdentical(otherClock, self.timer._loop.clock)
def test_stopServiceWaits(self):
"""
When L{TimerService.stopService} is called while a call is in progress.
the L{Deferred} returned doesn't fire until after the call finishes.
"""
self.timer.startService()
d = self.timer.stopService()
self.assertNoResult(d)
self.assertEqual(True, self.timer.running)
self.deferred.callback(object())
self.assertIdentical(self.successResultOf(d), None)
def test_stopServiceImmediately(self):
"""
When L{TimerService.stopService} is called while a call isn't in progress.
the L{Deferred} returned has already been fired.
"""
self.timer.startService()
self.deferred.callback(object())
d = self.timer.stopService()
self.assertIdentical(self.successResultOf(d), None)
def test_failedCallLogsError(self):
"""
When function passed to L{TimerService} returns a deferred that errbacks,
the exception is logged, and L{TimerService.stopService} doesn't raise an error.
"""
self.timer.startService()
self.deferred.errback(Failure(ZeroDivisionError()))
errors = self.flushLoggedErrors(ZeroDivisionError)
self.assertEqual(1, len(errors))
d = self.timer.stopService()
self.assertIdentical(self.successResultOf(d), None)
def test_pickleTimerServiceNotPickleLoop(self):
"""
When pickling L{internet.TimerService}, it won't pickle
L{internet.TimerService._loop}.
"""
# We need a pickleable callable to test pickling TimerService. So we
# can't use self.timer
timer = TimerService(1, fakeTargetFunction)
timer.startService()
dumpedTimer = pickle.dumps(timer)
timer.stopService()
loadedTimer = pickle.loads(dumpedTimer)
nothing = object()
value = getattr(loadedTimer, "_loop", nothing)
self.assertIdentical(nothing, value)
def test_pickleTimerServiceNotPickleLoopFinished(self):
"""
When pickling L{internet.TimerService}, it won't pickle
L{internet.TimerService._loopFinished}.
"""
# We need a pickleable callable to test pickling TimerService. So we
# can't use self.timer
timer = TimerService(1, fakeTargetFunction)
timer.startService()
dumpedTimer = pickle.dumps(timer)
timer.stopService()
loadedTimer = pickle.loads(dumpedTimer)
nothing = object()
value = getattr(loadedTimer, "_loopFinished", nothing)
self.assertIdentical(nothing, value)
class ConnectInformation:
"""
Information about C{endpointForTesting}
@ivar connectQueue: a L{list} of L{Deferred} returned from C{connect}. If
these are not already fired, you can fire them with no value and they
will trigger building a factory.
@ivar constructedProtocols: a L{list} of protocols constructed.
@ivar passedFactories: a L{list} of L{IProtocolFactory}; the ones actually
passed to the underlying endpoint / i.e. the reactor.
"""
def __init__(self):
self.connectQueue = []
self.constructedProtocols = []
self.passedFactories = []
def endpointForTesting(fireImmediately=False):
"""
Make a sample endpoint for testing.
@param fireImmediately: If true, fire all L{Deferred}s returned from
C{connect} immedaitely.
@return: a 2-tuple of C{(information, endpoint)}, where C{information} is a
L{ConnectInformation} describing the operations in progress on
C{endpoint}.
"""
@implementer(IStreamClientEndpoint)
class ClientTestEndpoint:
def connect(self, factory):
result = Deferred()
info.passedFactories.append(factory)
@result.addCallback
def createProtocol(ignored):
protocol = factory.buildProtocol(None)
info.constructedProtocols.append(protocol)
transport = StringTransport()
protocol.makeConnection(transport)
return protocol
info.connectQueue.append(result)
if fireImmediately:
result.callback(None)
return result
info = ConnectInformation()
return info, ClientTestEndpoint()
def catchLogs(testCase, logPublisher=globalLogPublisher):
"""
Catch the global log stream.
@param testCase: The test case to add a cleanup to.
@param logPublisher: the log publisher to add and remove observers for.
@return: a 0-argument callable that returns a list of textual log messages
for comparison.
@rtype: L{list} of L{unicode}
"""
logs = []
logPublisher.addObserver(logs.append)
testCase.addCleanup(lambda: logPublisher.removeObserver(logs.append))
return lambda: [formatEvent(event) for event in logs]
AT_LEAST_ONE_ATTEMPT = 100.0
class ClientServiceTests(SynchronousTestCase):
"""
Tests for L{ClientService}.
"""
def makeReconnector(
self, fireImmediately=True, startService=True, protocolType=Protocol, **kw
):
"""
Create a L{ClientService} along with a L{ConnectInformation} indicating
the connections in progress on its endpoint.
@param fireImmediately: Should all of the endpoint connection attempts
fire synchronously?
@type fireImmediately: L{bool}
@param startService: Should the L{ClientService} be started before
being returned?
@type startService: L{bool}
@param protocolType: a 0-argument callable returning a new L{IProtocol}
provider to be used for application-level protocol connections.
@param kw: Arbitrary keyword arguments to be passed on to
L{ClientService}
@return: a 2-tuple of L{ConnectInformation} (for information about test
state) and L{ClientService} (the system under test). The
L{ConnectInformation} has 2 additional attributes;
C{applicationFactory} and C{applicationProtocols}, which refer to
the unwrapped protocol factory and protocol instances passed in to
L{ClientService} respectively.
"""
nkw = {}
nkw.update(clock=Clock())
nkw.update(kw)
clock = nkw["clock"]
cq, endpoint = endpointForTesting(fireImmediately=fireImmediately)
# `endpointForTesting` is totally generic to any LLPI client that uses
# endpoints, and maintains all its state internally; however,
# applicationProtocols and applicationFactory are bonus attributes that
# are only specifically interesitng to tests that use wrapper
# protocols. For now, set them here, externally.
applicationProtocols = cq.applicationProtocols = []
class RememberingFactory(Factory):
protocol = protocolType
def buildProtocol(self, addr):
result = super().buildProtocol(addr)
applicationProtocols.append(result)
return result
cq.applicationFactory = 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(clock.getDelayedCalls(), [])
self.addCleanup(stop)
if startService:
service.startService()
return cq, service
def test_startService(self):
"""
When the service is started, a connection attempt is made.
"""
cq, service = self.makeReconnector(fireImmediately=False)
self.assertEqual(len(cq.connectQueue), 1)
def test_startStopFactory(self):
"""
Although somewhat obscure, L{IProtocolFactory} includes both C{doStart}
and C{doStop} methods; ensure that when these methods are called on the
factory that was passed to the reactor, the factory that was passed
from the application receives them.
"""
cq, service = self.makeReconnector()
firstAppFactory = cq.applicationFactory
self.assertEqual(firstAppFactory.numPorts, 0)
firstPassedFactory = cq.passedFactories[0]
firstPassedFactory.doStart()
self.assertEqual(firstAppFactory.numPorts, 1)
def test_stopServiceWhileConnected(self):
"""
When the service is stopped, no further connect attempts are made. The
returned L{Deferred} fires when all outstanding connections have been
stopped.
"""
cq, service = self.makeReconnector()
d = service.stopService()
self.assertNoResult(d)
protocol = cq.constructedProtocols[0]
self.assertEqual(protocol.transport.disconnecting, True)
protocol.connectionLost(Failure(Exception()))
self.successResultOf(d)
def test_startServiceWaitsForDisconnect(self):
"""
When L{ClientService} is restarted after having been connected, it
waits to start connecting until after having disconnected.
"""
cq, service = self.makeReconnector()
d = service.stopService()
self.assertNoResult(d)
protocol = cq.constructedProtocols[0]
self.assertEqual(protocol.transport.disconnecting, True)
service.startService()
self.assertNoResult(d)
self.assertEqual(len(cq.constructedProtocols), 1)
protocol.connectionLost(Failure(Exception()))
self.assertEqual(len(cq.constructedProtocols), 2)
def test_startServiceWhileStopping(self):
"""
When L{ClientService} is stopping - that is,
L{ClientService.stopService} has been called, but the L{Deferred} it
returns has not fired yet - calling L{startService} will cause a new
connection to be made, and new calls to L{whenConnected} to succeed.
"""
cq, service = self.makeReconnector(fireImmediately=False)
cq.connectQueue[0].callback(None)
first = cq.constructedProtocols[0]
stopped = service.stopService()
self.assertNoResult(stopped)
nextProtocol = service.whenConnected()
self.assertNoResult(nextProtocol)
service.startService()
self.assertNoResult(nextProtocol)
self.assertNoResult(stopped)
self.assertEqual(first.transport.disconnecting, True)
first.connectionLost(Failure(Exception()))
self.successResultOf(stopped)
cq.connectQueue[1].callback(None)
self.assertEqual(len(cq.constructedProtocols), 2)
self.assertIdentical(
self.successResultOf(nextProtocol), cq.applicationProtocols[1]
)
secondStopped = service.stopService()
self.assertNoResult(secondStopped)
def test_startServiceWhileStopped(self):
"""
When L{ClientService} is stopped - that is,
L{ClientService.stopService} has been called and the L{Deferred} it
returns has fired - calling L{startService} will cause a new connection
to be made, and new calls to L{whenConnected} to succeed.
"""
cq, service = self.makeReconnector(fireImmediately=False)
stopped = service.stopService()
self.successResultOf(stopped)
self.failureResultOf(service.whenConnected(), CancelledError)
service.startService()
cq.connectQueue[-1].callback(None)
self.assertIdentical(
cq.applicationProtocols[-1], self.successResultOf(service.whenConnected())
)
def test_interfacesForTransport(self):
"""
If the protocol objects returned by the factory given to
L{ClientService} provide special "marker" interfaces for their
transport - L{IHalfCloseableProtocol} or L{IFileDescriptorReceiver} -
those interfaces will be provided by the protocol objects passed on to
the reactor.
"""
@implementer(IHalfCloseableProtocol, IFileDescriptorReceiver)
class FancyProtocol(Protocol):
"""
Provider of various interfaces.
"""
cq, service = self.makeReconnector(protocolType=FancyProtocol)
reactorFacing = cq.constructedProtocols[0]
self.assertTrue(IFileDescriptorReceiver.providedBy(reactorFacing))
self.assertTrue(IHalfCloseableProtocol.providedBy(reactorFacing))
def test_stopServiceWhileRetrying(self):
"""
When the service is stopped while retrying, the retry is cancelled.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
cq.connectQueue[0].errback(Exception())
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertEqual(len(cq.connectQueue), 2)
d = service.stopService()
cq.connectQueue[1].errback(Exception())
self.successResultOf(d)
def test_stopServiceWhileConnecting(self):
"""
When the service is stopped while initially connecting, the connection
attempt is cancelled.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
self.assertEqual(len(cq.connectQueue), 1)
self.assertNoResult(cq.connectQueue[0])
d = service.stopService()
self.successResultOf(d)
def test_clientConnected(self):
"""
When a client connects, the service keeps a reference to the new
protocol and resets the delay.
"""
clock = Clock()
cq, service = self.makeReconnector(clock=clock)
awaitingProtocol = service.whenConnected()
self.assertEqual(clock.getDelayedCalls(), [])
self.assertIdentical(
self.successResultOf(awaitingProtocol), cq.applicationProtocols[0]
)
def test_clientConnectionFailed(self):
"""
When a client connection fails, the service removes its reference
to the protocol and tries again after a timeout.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
self.assertEqual(len(cq.connectQueue), 1)
cq.connectQueue[0].errback(Failure(Exception()))
whenConnected = service.whenConnected()
self.assertNoResult(whenConnected)
# Don't fail during test tear-down when service shutdown causes all
# waiting connections to fail.
whenConnected.addErrback(lambda ignored: ignored.trap(CancelledError))
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertEqual(len(cq.connectQueue), 2)
def test_clientConnectionLost(self):
"""
When a client connection is lost, the service removes its reference
to the protocol and calls retry.
"""
clock = Clock()
cq, service = self.makeReconnector(clock=clock, fireImmediately=False)
self.assertEqual(len(cq.connectQueue), 1)
cq.connectQueue[0].callback(None)
self.assertEqual(len(cq.connectQueue), 1)
self.assertIdentical(
self.successResultOf(service.whenConnected()), cq.applicationProtocols[0]
)
cq.constructedProtocols[0].connectionLost(Failure(Exception()))
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertEqual(len(cq.connectQueue), 2)
cq.connectQueue[1].callback(None)
self.assertIdentical(
self.successResultOf(service.whenConnected()), cq.applicationProtocols[1]
)
def test_clientConnectionLostWhileStopping(self):
"""
When a client connection is lost while the service is stopping, the
protocol stopping deferred is called and the reference to the protocol
is removed.
"""
clock = Clock()
cq, service = self.makeReconnector(clock=clock)
d = service.stopService()
cq.constructedProtocols[0].connectionLost(Failure(IndentationError()))
self.failureResultOf(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])
def test_whenConnectedLater(self):
"""
L{ClientService.whenConnected} returns a L{Deferred} that fires when a
connection is established.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
a = service.whenConnected()
b = service.whenConnected()
c = service.whenConnected(failAfterFailures=1)
self.assertNoResult(a)
self.assertNoResult(b)
self.assertNoResult(c)
cq.connectQueue[0].callback(None)
resultA = self.successResultOf(a)
resultB = self.successResultOf(b)
resultC = self.successResultOf(c)
self.assertIdentical(resultA, resultB)
self.assertIdentical(resultA, resultC)
self.assertIdentical(resultA, cq.applicationProtocols[0])
def test_whenConnectedFails(self):
"""
L{ClientService.whenConnected} returns a L{Deferred} that fails, if
asked, when some number of connections have failed.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
a0 = service.whenConnected()
a1 = service.whenConnected(failAfterFailures=1)
a2 = service.whenConnected(failAfterFailures=2)
a3 = service.whenConnected(failAfterFailures=3)
self.assertNoResult(a0)
self.assertNoResult(a1)
self.assertNoResult(a2)
self.assertNoResult(a3)
f1 = Failure(Exception())
cq.connectQueue[0].errback(f1)
self.assertNoResult(a0)
self.assertIdentical(self.failureResultOf(a1, Exception), f1)
self.assertNoResult(a2)
self.assertNoResult(a3)
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertEqual(len(cq.connectQueue), 2)
self.assertNoResult(a0)
self.assertNoResult(a2)
self.assertNoResult(a3)
f2 = Failure(Exception())
cq.connectQueue[1].errback(f2)
self.assertNoResult(a0)
self.assertIdentical(self.failureResultOf(a2, Exception), f2)
self.assertNoResult(a3)
AT_LEAST_TWO_ATTEMPTS = AT_LEAST_ONE_ATTEMPT # close enough
clock.advance(AT_LEAST_TWO_ATTEMPTS)
self.assertEqual(len(cq.connectQueue), 3)
self.assertNoResult(a0)
self.assertNoResult(a3)
cq.connectQueue[2].callback(None)
resultA0 = self.successResultOf(a0)
resultA3 = self.successResultOf(a3)
self.assertIdentical(resultA0, resultA3)
self.assertIdentical(resultA0, cq.applicationProtocols[0])
# a new whenConnected Deferred, obtained after we're connected,
# should have fired already, even if failAfterFailures is set
a4 = service.whenConnected(failAfterFailures=1)
resultA4 = self.successResultOf(a4)
self.assertIdentical(resultA0, resultA4)
def test_whenConnectedStopService(self):
"""
L{ClientService.whenConnected} returns a L{Deferred} that fails when
L{ClientService.stopService} is called.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
a = service.whenConnected()
b = service.whenConnected()
c = service.whenConnected(failAfterFailures=1)
self.assertNoResult(a)
self.assertNoResult(b)
self.assertNoResult(c)
service.stopService()
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.failureResultOf(a, CancelledError)
self.failureResultOf(b, CancelledError)
self.failureResultOf(c, CancelledError)
def test_retryCancelled(self):
"""
When L{ClientService.stopService} is called while waiting between
connection attempts, the pending reconnection attempt is cancelled and
the service is stopped immediately.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
cq.connectQueue[0].errback(Exception("no connection"))
d = service.stopService()
self.assertEqual(clock.getDelayedCalls(), [])
self.successResultOf(d)
def test_stopServiceBeforeStartService(self):
"""
Calling L{ClientService.stopService} before
L{ClientService.startService} returns a L{Deferred} that has
already fired with L{None}.
"""
clock = Clock()
_, service = self.makeReconnector(
fireImmediately=False, startService=False, clock=clock
)
d = service.stopService()
self.assertIsNone(self.successResultOf(d))
def test_whenConnectedErrbacksOnStopService(self):
"""
L{ClientService.whenConnected} returns a L{Deferred} that
errbacks with L{CancelledError} if
L{ClientService.stopService} is called between connection
attempts.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
beforeErrbackAndStop = service.whenConnected()
# The protocol fails to connect, and the service is waiting to
# reconnect.
cq.connectQueue[0].errback(Exception("no connection"))
service.stopService()
afterErrbackAndStop = service.whenConnected()
self.assertIsInstance(
self.failureResultOf(beforeErrbackAndStop).value, CancelledError
)
self.assertIsInstance(
self.failureResultOf(afterErrbackAndStop).value, CancelledError
)
def test_stopServiceWhileDisconnecting(self):
"""
Calling L{ClientService.stopService} twice after it has
connected (that is, stopping it while it is disconnecting)
returns a L{Deferred} each time that fires when the
disconnection has completed.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
# The protocol connects
cq.connectQueue[0].callback(None)
# The protocol begins disconnecting
firstStopDeferred = service.stopService()
# The protocol continues disconnecting
secondStopDeferred = service.stopService()
# The protocol is disconnected
cq.constructedProtocols[0].connectionLost(Failure(IndentationError()))
self.successResultOf(firstStopDeferred)
self.successResultOf(secondStopDeferred)
def test_stopServiceWhileRestarting(self):
"""
Calling L{ClientService.stopService} after calling a
reconnection attempt returns a L{Deferred} that fires when the
disconnection has completed.
"""
clock = Clock()
cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
# The protocol connects
cq.connectQueue[0].callback(None)
# The protocol begins disconnecting
firstStopDeferred = service.stopService()
# The protocol begins reconnecting
service.startService()
# The protocol begins disconnecting again
secondStopDeferred = service.stopService()
# The protocol is disconnected
cq.constructedProtocols[0].connectionLost(Failure(IndentationError()))
self.successResultOf(firstStopDeferred)
self.successResultOf(secondStopDeferred)
def test_stopServiceOnStoppedService(self):
"""
Calling L{ClientService.stopService} on a stopped service
returns a L{Deferred} that has already fired with L{None}.
"""
clock = Clock()
_, service = self.makeReconnector(fireImmediately=False, clock=clock)
firstStopDeferred = service.stopService()
secondStopDeferred = service.stopService()
self.assertIsNone(self.successResultOf(firstStopDeferred))
self.assertIsNone(self.successResultOf(secondStopDeferred))
def test_prepareConnectionCalledWhenServiceStarts(self):
"""
The C{prepareConnection} callable is called after
L{ClientService.startService} once the connection is made.
"""
prepares = [0]
def prepareConnection(_proto):
prepares[0] += 1
cq, service = self.makeReconnector(
prepareConnection=prepareConnection, startService=True
)
self.assertEqual(1, prepares[0])
def test_prepareConnectionCalledWithProtocol(self):
"""
The C{prepareConnection} callable is passed the connected protocol
instance.
"""
newProtocols = []
def prepareConnection(proto):
newProtocols.append(proto)
cq, service = self.makeReconnector(
prepareConnection=prepareConnection,
)
self.assertIdentical(cq.constructedProtocols[0], newProtocols[0])
def test_prepareConnectionCalledAfterConnectionMade(self):
"""
The C{prepareConnection} callback is invoked only once a connection is
made.
"""
prepares = [0]
def prepareConnection(_proto):
prepares[0] += 1
clock = Clock()
cq, service = self.makeReconnector(
prepareConnection=prepareConnection, fireImmediately=False, clock=clock
)
cq.connectQueue[0].errback(Exception("connection attempt failed"))
self.assertEqual(0, prepares[0]) # Not called yet.
clock.advance(AT_LEAST_ONE_ATTEMPT)
cq.connectQueue[1].callback(None)
self.assertEqual(1, prepares[0]) # Was called.
def test_prepareConnectionCalledOnReconnect(self):
"""
The C{prepareConnection} callback is invoked each time a connection is
made, including on reconnection.
"""
prepares = [0]
def prepareConnection(_proto):
prepares[0] += 1
clock = Clock()
cq, service = self.makeReconnector(
prepareConnection=prepareConnection, clock=clock
)
self.assertEqual(1, prepares[0]) # Called once.
# Protocol disconnects.
cq.constructedProtocols[0].connectionLost(Failure(IndentationError()))
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertEqual(2, prepares[0]) # Called again.
def test_prepareConnectionReturnValueIgnored(self):
"""
The C{prepareConnection} return value is ignored when it does not
indicate a failure. Even though the callback participates in the
internal new-connection L{Deferred} chain for error propagation
purposes, any successful result does not affect the ultimate return
value.
"""
# Sentinel object returned by prepareConnection.
sentinel = object()
def prepareConnection(proto):
return sentinel
cq, service = self.makeReconnector(prepareConnection=prepareConnection)
result = self.successResultOf(service.whenConnected())
self.assertNotIdentical(sentinel, result)
def test_prepareConnectionReturningADeferred(self):
"""
The C{prepareConnection} callable returns a deferred and calls to
L{ClientService.whenConnected} wait until it fires.
"""
newProtocols = []
newProtocolDeferred = Deferred()
def prepareConnection(proto):
newProtocols.append(proto)
return newProtocolDeferred
cq, service = self.makeReconnector(prepareConnection=prepareConnection)
whenConnectedDeferred = service.whenConnected()
self.assertNoResult(whenConnectedDeferred)
newProtocolDeferred.callback(None)
self.assertIdentical(
cq.applicationProtocols[0], self.successResultOf(whenConnectedDeferred)
)
def test_prepareConnectionThrows(self):
"""
The connection attempt counts as a failure when the
C{prepareConnection} callable throws.
"""
clock = Clock()
def prepareConnection(_proto):
raise IndentationError()
cq, service = self.makeReconnector(
prepareConnection=prepareConnection, clock=clock
)
whenConnectedDeferred = service.whenConnected(failAfterFailures=2)
self.assertNoResult(whenConnectedDeferred)
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertNoResult(whenConnectedDeferred)
clock.advance(AT_LEAST_ONE_ATTEMPT)
self.assertIdentical(
IndentationError, self.failureResultOf(whenConnectedDeferred).type
)
Zerion Mini Shell 1.0