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?