1

I'm working on implementing a stream protocol and currently trying to setup unit tests before using new implementation further up the application code.

While creating a simple version of the test, I came across the issue of leaving reactor "unclean". I addressed it by implementing following tear down logic:

def tearDown(self):
    def loseConnection(connection):
        connection.transport.loseConnection()

    # Closing all open connections
    for _, connection in self.tcp_transport._connections.iteritems():
        if isinstance(connection, Protocol):
            connection.transport.loseConnection()
        elif isinstance(connection, Deferred):
            connection.addCallback(loseConnection)

Simple test case that work

def test_send_to_new_connection(self):
    # Given
    peerAddr = ('10.22.22.190', 5060)

    # If
    self.tcp_transport.send_to('test', peerAddr)

    # Then
    assert peerAddr in self.tcp_transport._connections
    assert True == isinstance(self.tcp_transport._connections[peerAddr], Deferred)

While extending the test case by manually resolving deferred with a "mock" value (not really mock, just using proto_helpers to create fake protocol), test case starts failing with the same Reactor was unclean message, however in this case it seems that existing tearDown logic is not capble of taking care of the cleanup.

Extended test case that doesn't work

def test_send_to_new_connection(self):
    # Given
    peerAddr = ('10.22.22.190', 5060)

    # If
    self.tcp_transport.send_to('test', peerAddr)

    # Then
    assert peerAddr in self.tcp_transport._connections
    assert True == isinstance(self.tcp_transport._connections[peerAddr], Deferred)

    connection = _string_transport_connection(self.hostAddr, peerAddr, None, self.tcp_transport.connectionMade)

    def assert_cache_updated_on_connection(connection):
        print('--------- SUCCESS ----------')
        peer = connection.transport.getPeer()
        peerAddr = (peer.host, peer.port)

        assert peerAddr in self.tcp_transport._connections
        assert True == isinstance(self.tcp_transport._connections[peerAddr], Protocol)

    def assert_fail(fail):
        print('--------- FAIL ----------')

    self.tcp_transport._connections[peerAddr].addCallback(assert_cache_updated_on_connection)
    self.tcp_transport._connections[peerAddr].addErrback(assert_fail)
    # Forcing deferred to fire with mock connection
    self.tcp_transport._connections[peerAddr].callback(connection)

Detailed error message

DirtyReactorAggregateError: Reactor was unclean.
Selectables:
<<class 'twisted.internet.tcp.Client'> to ('10.22.22.190', 5060) at 361e650>

From this message I'm guessing that there is still a loose client hanging somewhere. My only suspicion lies on the TCP4ClientEndpoint that is being created by code under test, but that never succeeds to connect (due to fake IP address supplied). I'd be happy to clean it up, but the problem is that I don't know how to access it from the deferred:

def _createConnection(self, address):
    endpoint = TCP4ClientEndpoint(reactor, address[0], address[1])
    protocol = TCPProtocol(self.dataReceived, self.cacheConnection)
    d = connectProtocol(endpoint, protocol)
    ...
    return d

So I have access to d since I'm able to resolve it with a mock connection, but how can I access protocol that was initially created to close that connection?

IvanR
  • 533
  • 4
  • 23
  • I suggest you split this up into at least four separate question. – Jean-Paul Calderone Aug 24 '18 at 12:36
  • 1
    fair enough @Jean-PaulCalderone. I've edited this question to focus on one the main problem that causes this test case to fail. I've also created new issue based on one of the original questions. I would really appreciate if you could give it a quick look: https://stackoverflow.com/questions/52005125/twisted-test-both-success-and-error-callback-fire-on-deferred – IvanR Aug 24 '18 at 13:17

0 Answers0