~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/test/test_tcp.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
 
8
8
"""Generic TCP tests."""
9
9
 
10
 
import socket, time
 
10
import socket, random, errno
 
11
 
11
12
from zope.interface import implements
 
13
 
12
14
from twisted.trial import unittest
13
15
from twisted.python import log
14
16
 
18
20
from twisted.internet.interfaces import IHalfCloseableProtocol
19
21
from twisted.protocols import policies
20
22
 
21
 
 
22
23
def loopUntil(predicate, interval=0):
23
24
    from twisted.internet import task
24
25
    d = defer.Deferred()
255
256
        clientF = MyClientFactory()
256
257
        # XXX we assume no one is listening on TCP port 69
257
258
        reactor.connectTCP("127.0.0.1", 69, clientF, timeout=5)
258
 
        start = time.time()
259
259
        def check(ignored):
260
260
            clientF.reason.trap(error.ConnectionRefusedError)
261
261
        return clientF.failDeferred.addCallback(check)
262
 
        #self.assert_(time.time() - start < 0.1)
 
262
 
 
263
 
 
264
    def test_connectionRefusedErrorNumber(self):
 
265
        """
 
266
        Assert that the error number of the ConnectionRefusedError is
 
267
        ECONNREFUSED, and not some other socket related error.
 
268
        """
 
269
 
 
270
        # Bind a number of ports in the operating system.  We will attempt
 
271
        # to connect to these in turn immediately after closing them, in the
 
272
        # hopes that no one else has bound them in the mean time.  Any
 
273
        # connection which succeeds is ignored and causes us to move on to
 
274
        # the next port.  As soon as a connection attempt fails, we move on
 
275
        # to making an assertion about how it failed.  If they all succeed,
 
276
        # the test will fail.
 
277
 
 
278
        # It would be nice to have a simpler, reliable way to cause a
 
279
        # connection failure from the platform.
 
280
        #
 
281
        # On Linux (2.6.15), connecting to port 0 always fails.  FreeBSD
 
282
        # (5.4) rejects the connection attempt with EADDRNOTAVAIL.
 
283
        #
 
284
        # On FreeBSD (5.4), listening on a port and then repeatedly
 
285
        # connecting to it without ever accepting any connections eventually
 
286
        # leads to an ECONNREFUSED.  On Linux (2.6.15), a seemingly
 
287
        # unbounded number of connections succeed.
 
288
 
 
289
        serverSockets = []
 
290
        for i in xrange(10):
 
291
            serverSocket = socket.socket()
 
292
            serverSocket.bind(('127.0.0.1', 0))
 
293
            serverSocket.listen(1)
 
294
            serverSockets.append(serverSocket)
 
295
        random.shuffle(serverSockets)
 
296
 
 
297
        clientCreator = protocol.ClientCreator(reactor, protocol.Protocol)
 
298
 
 
299
        def tryConnectFailure():
 
300
            def connected(proto):
 
301
                """
 
302
                Darn.  Kill it and try again, if there are any tries left.
 
303
                """
 
304
                proto.transport.loseConnection()
 
305
                if serverSockets:
 
306
                    return tryConnectFailure()
 
307
                self.fail("Could not fail to connect - could not test errno for that case.")
 
308
 
 
309
            serverSocket = serverSockets.pop()
 
310
            serverHost, serverPort = serverSocket.getsockname()
 
311
            serverSocket.close()
 
312
 
 
313
            connectDeferred = clientCreator.connectTCP(serverHost, serverPort)
 
314
            connectDeferred.addCallback(connected)
 
315
            return connectDeferred
 
316
 
 
317
        refusedDeferred = tryConnectFailure()
 
318
        self.assertFailure(refusedDeferred, error.ConnectionRefusedError)
 
319
        def connRefused(exc):
 
320
            self.assertEqual(exc.osError, errno.ECONNREFUSED)
 
321
        refusedDeferred.addCallback(connRefused)
 
322
        def cleanup(passthrough):
 
323
            while serverSockets:
 
324
                serverSockets.pop().close()
 
325
            return passthrough
 
326
        refusedDeferred.addBoth(cleanup)
 
327
        return refusedDeferred
 
328
 
263
329
 
264
330
    def testConnectByServiceFail(self):
265
331
        try:
679
745
        self.master._connectionMade()
680
746
        self.master.ports.append(self.transport)
681
747
 
 
748
 
 
749
 
682
750
class NoopProtocol(protocol.Protocol):
683
751
    def connectionMade(self):
684
752
        self.d = defer.Deferred()
687
755
    def connectionLost(self, reason):
688
756
        self.d.callback(True)
689
757
 
690
 
class ProperlyCloseFilesTestCase(PortCleanerUpper):
691
 
 
692
 
    numberRounds = 2048
693
 
    timeLimit = 200
694
 
 
695
 
    def setUp(self):
696
 
        PortCleanerUpper.setUp(self)
697
 
        self.serverConns = []
698
 
        f = protocol.ServerFactory()
699
 
        f.protocol = NoopProtocol
700
 
        f.protocol.master = self
701
 
 
702
 
        self.listener = reactor.listenTCP(0, f, interface="127.0.0.1")
703
 
        self.ports.append(self.listener)
704
 
        
705
 
        self.clientF = f = protocol.ClientFactory()
706
 
        f.protocol = ConnectionLosingProtocol
707
 
        f.protocol.master = self
708
 
        
709
 
        def connector():
710
 
            p = self.listener.getHost().port
711
 
            return reactor.connectTCP('127.0.0.1', p, f)
712
 
        self.connector = connector
713
 
 
714
 
        self.totalConnections = 0
715
 
 
716
 
    def tearDown(self):
717
 
        # Wait until all the protocols on the server-side of this test have
718
 
        # been disconnected, to avoid leaving junk in the reactor.
719
 
        d = defer.gatherResults(self.serverConns)
720
 
        d.addBoth(lambda x : PortCleanerUpper.tearDown(self))
721
 
        return d
722
 
    
723
 
    def testProperlyCloseFiles(self):
724
 
        self.deferred = defer.Deferred()
725
 
        self.connector()
726
 
        self.deferred.addCallback(
727
 
            lambda x : self.failUnlessEqual(self.totalConnections,
728
 
                                            self.numberRounds))
729
 
        return self.deferred
730
 
 
731
 
    def _connectionMade(self):
732
 
        self.totalConnections += 1
733
 
        if self.totalConnections<self.numberRounds:
734
 
            self.connector()
735
 
        else:
736
 
            self.deferred.callback(None)
737
 
 
 
758
 
 
759
 
 
760
class ConnectionLostNotifyingProtocol(protocol.Protocol):
 
761
    """
 
762
    Protocol which fires a Deferred which was previously passed to
 
763
    its initializer when the connection is lost.
 
764
    """
 
765
    def __init__(self, onConnectionLost):
 
766
        self.onConnectionLost = onConnectionLost
 
767
 
 
768
 
 
769
    def connectionLost(self, reason):
 
770
        self.onConnectionLost.callback(self)
 
771
 
 
772
 
 
773
 
 
774
class HandleSavingProtocol(ConnectionLostNotifyingProtocol):
 
775
    """
 
776
    Protocol which grabs the platform-specific socket handle and
 
777
    saves it as an attribute on itself when the connection is
 
778
    established.
 
779
    """
 
780
    def makeConnection(self, transport):
 
781
        """
 
782
        Save the platform-specific socket handle for future
 
783
        introspection.
 
784
        """
 
785
        self.handle = transport.getHandle()
 
786
        return protocol.Protocol.makeConnection(self, transport)
 
787
 
 
788
 
 
789
 
 
790
class ProperlyCloseFilesMixin:
 
791
    """
 
792
    Tests for platform resources properly being cleaned up.
 
793
    """
 
794
    def createServer(self, address, portNumber, factory):
 
795
        """
 
796
        Bind a server port to which connections will be made.  The server
 
797
        should use the given protocol factory.
 
798
 
 
799
        @return: The L{IListeningPort} for the server created.
 
800
        """
 
801
        raise NotImplementedError()
 
802
 
 
803
 
 
804
    def connectClient(self, address, portNumber, clientCreator):
 
805
        """
 
806
        Establish a connection to the given address using the given
 
807
        L{ClientCreator} instance.
 
808
 
 
809
        @return: A Deferred which will fire with the connected protocol instance.
 
810
        """
 
811
        raise NotImplementedError()
 
812
 
 
813
 
 
814
    def getHandleExceptionType(self):
 
815
        """
 
816
        Return the exception class which will be raised when an operation is
 
817
        attempted on a closed platform handle.
 
818
        """
 
819
        raise NotImplementedError()
 
820
 
 
821
 
 
822
    def getHandleErrorCode(self):
 
823
        """
 
824
        Return the errno expected to result from writing to a closed
 
825
        platform socket handle.
 
826
        """
 
827
        # These platforms have been seen to give EBADF:
 
828
        #
 
829
        #  Linux 2.4.26, Linux 2.6.15, OS X 10.4, FreeBSD 5.4
 
830
        #  Windows 2000 SP 4, Windows XP SP 2
 
831
        return errno.EBADF
 
832
 
 
833
 
 
834
    def test_properlyCloseFiles(self):
 
835
        """
 
836
        Test that lost connections properly have their underlying socket
 
837
        resources cleaned up.
 
838
        """
 
839
        onServerConnectionLost = defer.Deferred()
 
840
        serverFactory = protocol.ServerFactory()
 
841
        serverFactory.protocol = lambda: ConnectionLostNotifyingProtocol(
 
842
            onServerConnectionLost)
 
843
        serverPort = self.createServer('127.0.0.1', 0, serverFactory)
 
844
 
 
845
        onClientConnectionLost = defer.Deferred()
 
846
        serverAddr = serverPort.getHost()
 
847
        clientCreator = protocol.ClientCreator(
 
848
            reactor, lambda: HandleSavingProtocol(onClientConnectionLost))
 
849
        clientDeferred = self.connectClient(
 
850
            serverAddr.host, serverAddr.port, clientCreator)
 
851
 
 
852
        def clientConnected(client):
 
853
            """
 
854
            Disconnect the client.  Return a Deferred which fires when both
 
855
            the client and the server have received disconnect notification.
 
856
            """
 
857
            client.transport.loseConnection()
 
858
            return defer.gatherResults([
 
859
                onClientConnectionLost, onServerConnectionLost])
 
860
        clientDeferred.addCallback(clientConnected)
 
861
 
 
862
        def clientDisconnected((client, server)):
 
863
            """
 
864
            Verify that the underlying platform socket handle has been
 
865
            cleaned up.
 
866
            """
 
867
            expectedErrorCode = self.getHandleErrorCode()
 
868
            err = self.assertRaises(
 
869
                self.getHandleExceptionType(), client.handle.send, 'bytes')
 
870
            self.assertEqual(err.args[0], expectedErrorCode)
 
871
        clientDeferred.addCallback(clientDisconnected)
 
872
 
 
873
        def cleanup(passthrough):
 
874
            """
 
875
            Shut down the server port.  Return a Deferred which fires when
 
876
            this has completed.
 
877
            """
 
878
            result = defer.maybeDeferred(serverPort.stopListening)
 
879
            result.addCallback(lambda ign: passthrough)
 
880
            return result
 
881
        clientDeferred.addBoth(cleanup)
 
882
 
 
883
        return clientDeferred
 
884
 
 
885
 
 
886
 
 
887
class ProperlyCloseFilesTestCase(unittest.TestCase, ProperlyCloseFilesMixin):
 
888
    def createServer(self, address, portNumber, factory):
 
889
        return reactor.listenTCP(portNumber, factory, interface=address)
 
890
 
 
891
 
 
892
    def connectClient(self, address, portNumber, clientCreator):
 
893
        return clientCreator.connectTCP(address, portNumber)
 
894
 
 
895
 
 
896
    def getHandleExceptionType(self):
 
897
        return socket.error
738
898
 
739
899
 
740
900
class AProtocol(protocol.Protocol):
1071
1231
        self.client.transport.loseConnection()
1072
1232
        return self.p.stopListening()
1073
1233
 
1074
 
    def _delayDeferred(self, time, arg=None):
1075
 
        from twisted.internet import reactor
1076
 
        d = defer.Deferred()
1077
 
        reactor.callLater(time, d.callback, arg)
1078
 
        return d
1079
 
        
1080
1234
    def testNoNotification(self):
1081
 
        client = self.client
1082
 
        f = self.f
1083
 
        client.transport.write("hello")
1084
 
        w = client.transport.write
1085
 
        client.transport.loseWriteConnection()
1086
 
        d = self._delayDeferred(0.2, f.protocol)
1087
 
        d.addCallback(lambda x : self.assertEqual(f.protocol.data, 'hello'))
1088
 
        d.addCallback(lambda x : self.assertEqual(f.protocol.closed, True))
1089
 
        return d
 
1235
        """
 
1236
        TCP protocols support half-close connections, but not all of them
 
1237
        support being notified of write closes.  In this case, test that
 
1238
        half-closing the connection causes the peer's connection to be 
 
1239
        closed.
 
1240
        """
 
1241
        self.client.transport.write("hello")
 
1242
        self.client.transport.loseWriteConnection()
 
1243
        self.f.protocol.closedDeferred = d = defer.Deferred()
 
1244
        self.client.closedDeferred = d2 = defer.Deferred()
 
1245
        d.addCallback(lambda x:
 
1246
                      self.assertEqual(self.f.protocol.data, 'hello'))
 
1247
        d.addCallback(lambda x: self.assertEqual(self.f.protocol.closed, True))
 
1248
        return defer.gatherResults([d, d2])
1090
1249
 
1091
1250
    def testShutdownException(self):
1092
 
        client = self.client
1093
 
        f = self.f
1094
 
        f.protocol.transport.loseConnection()
1095
 
        client.transport.write("X")
1096
 
        client.transport.loseWriteConnection()
1097
 
        d = self._delayDeferred(0.2, f.protocol)
1098
 
        d.addCallback(lambda x : self.failUnlessEqual(x.closed, True))
1099
 
        return d
 
1251
        """
 
1252
        If the other side has already closed its connection, 
 
1253
        loseWriteConnection should pass silently.
 
1254
        """
 
1255
        self.f.protocol.transport.loseConnection()
 
1256
        self.client.transport.write("X")
 
1257
        self.client.transport.loseWriteConnection()
 
1258
        self.f.protocol.closedDeferred = d = defer.Deferred()
 
1259
        self.client.closedDeferred = d2 = defer.Deferred()
 
1260
        d.addCallback(lambda x:
 
1261
                      self.failUnlessEqual(self.f.protocol.closed, True))
 
1262
        return defer.gatherResults([d, d2])
 
1263
 
1100
1264
 
1101
1265
class HalfClose3TestCase(PortCleanerUpper):
1102
1266
    """Test half-closing connections where notification code has bugs."""