1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4"""
5Test code for basic Factory classes.
6"""
7
8from __future__ import division, absolute_import
9
10import pickle
11
12from twisted.trial.unittest import TestCase
13
14from twisted.internet.task import Clock
15from twisted.internet.protocol import ReconnectingClientFactory, Protocol
16
17
18class FakeConnector(object):
19    """
20    A fake connector class, to be used to mock connections failed or lost.
21    """
22
23    def stopConnecting(self):
24        pass
25
26
27    def connect(self):
28        pass
29
30
31
32class ReconnectingFactoryTestCase(TestCase):
33    """
34    Tests for L{ReconnectingClientFactory}.
35    """
36
37    def test_stopTryingWhenConnected(self):
38        """
39        If a L{ReconnectingClientFactory} has C{stopTrying} called while it is
40        connected, it does not subsequently attempt to reconnect if the
41        connection is later lost.
42        """
43        class NoConnectConnector(object):
44            def stopConnecting(self):
45                raise RuntimeError("Shouldn't be called, we're connected.")
46            def connect(self):
47                raise RuntimeError("Shouldn't be reconnecting.")
48
49        c = ReconnectingClientFactory()
50        c.protocol = Protocol
51        # Let's pretend we've connected:
52        c.buildProtocol(None)
53        # Now we stop trying, then disconnect:
54        c.stopTrying()
55        c.clientConnectionLost(NoConnectConnector(), None)
56        self.assertFalse(c.continueTrying)
57
58
59    def test_stopTryingDoesNotReconnect(self):
60        """
61        Calling stopTrying on a L{ReconnectingClientFactory} doesn't attempt a
62        retry on any active connector.
63        """
64        class FactoryAwareFakeConnector(FakeConnector):
65            attemptedRetry = False
66
67            def stopConnecting(self):
68                """
69                Behave as though an ongoing connection attempt has now
70                failed, and notify the factory of this.
71                """
72                f.clientConnectionFailed(self, None)
73
74            def connect(self):
75                """
76                Record an attempt to reconnect, since this is what we
77                are trying to avoid.
78                """
79                self.attemptedRetry = True
80
81        f = ReconnectingClientFactory()
82        f.clock = Clock()
83
84        # simulate an active connection - stopConnecting on this connector should
85        # be triggered when we call stopTrying
86        f.connector = FactoryAwareFakeConnector()
87        f.stopTrying()
88
89        # make sure we never attempted to retry
90        self.assertFalse(f.connector.attemptedRetry)
91        self.assertFalse(f.clock.getDelayedCalls())
92
93
94    def test_serializeUnused(self):
95        """
96        A L{ReconnectingClientFactory} which hasn't been used for anything
97        can be pickled and unpickled and end up with the same state.
98        """
99        original = ReconnectingClientFactory()
100        reconstituted = pickle.loads(pickle.dumps(original))
101        self.assertEqual(original.__dict__, reconstituted.__dict__)
102
103
104    def test_serializeWithClock(self):
105        """
106        The clock attribute of L{ReconnectingClientFactory} is not serialized,
107        and the restored value sets it to the default value, the reactor.
108        """
109        clock = Clock()
110        original = ReconnectingClientFactory()
111        original.clock = clock
112        reconstituted = pickle.loads(pickle.dumps(original))
113        self.assertIdentical(reconstituted.clock, None)
114
115
116    def test_deserializationResetsParameters(self):
117        """
118        A L{ReconnectingClientFactory} which is unpickled does not have an
119        L{IConnector} and has its reconnecting timing parameters reset to their
120        initial values.
121        """
122        factory = ReconnectingClientFactory()
123        factory.clientConnectionFailed(FakeConnector(), None)
124        self.addCleanup(factory.stopTrying)
125
126        serialized = pickle.dumps(factory)
127        unserialized = pickle.loads(serialized)
128        self.assertEqual(unserialized.connector, None)
129        self.assertEqual(unserialized._callID, None)
130        self.assertEqual(unserialized.retries, 0)
131        self.assertEqual(unserialized.delay, factory.initialDelay)
132        self.assertEqual(unserialized.continueTrying, True)
133
134
135    def test_parametrizedClock(self):
136        """
137        The clock used by L{ReconnectingClientFactory} can be parametrized, so
138        that one can cleanly test reconnections.
139        """
140        clock = Clock()
141        factory = ReconnectingClientFactory()
142        factory.clock = clock
143
144        factory.clientConnectionLost(FakeConnector(), None)
145        self.assertEqual(len(clock.calls), 1)
146