Question

Within my code, I use task.LoopingCall() to run some deferred function every second. I want to make sure that that function returns the right values for a certain number of things. So, I thought I could use a task.clock() and call the advance() method on it. However, I am not getting the right number of responses expected.

Any idea what I am doing wrong?

Here is a test code to show what I mean. First is the server:

from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
from twisted.internet import task
import time

class Chat(LineReceiver):

    def __init__(self):
        self.echo = None

    def connectionMade(self):
        self.echo = task.LoopingCall(self.echo_print)
        self.echo.start(1)

    def connectionLost(self, reason='whatever'):
        if self.echo is not None and self.echo.running:
            self.echo.stop()

    def lineReceived(self, line):
        if line == 'stop':
            self.echo.stop()

    def echo_print (self):
        self.sendLine("Echo")

class ChatFactory(Factory):

    def __init__(self):
        pass

    def buildProtocol(self, addr):
        return Chat()

if __name__ == "__main__":
    reactor.listenTCP(8123, ChatFactory())
    reactor.run()

And now the test case:

from twisted.internet import task, base
from twisted.trial import unittest
from twisted.test import proto_helpers
from chat import ChatFactory

class TestChat (unittest.TestCase):

    def setUp (self):
        self.factory = ChatFactory()
        self.clock = task.Clock()
        self.proto = self.factory.buildProtocol(('127.0.0.1', 0))
        self.tr = proto_helpers.StringTransport()
        self.proto.callLater = self.clock.callLater
        self.proto.makeConnection(self.tr)

    def tearDown (self):
        if self.proto:
            self.proto.connectionLost()

    def test_echo (self):
        self.proto.dataReceived('ook\n')
        seconds_elapsed = 5
        self.clock.advance(seconds_elapsed)
        expected = 'Echo\r\n' * seconds_elapsed
        self.assertEqual(self.tr.value(), expected)

When I run py.test on this, I get:

E           FailTest: not equal:
E           a = 'Echo\r\n'
E           b = 'Echo\r\nEcho\r\nEcho\r\nEcho\r\nEcho\r\n'

Note that adding import time; time.sleep(5) does indeed make the test pass. So, I suspect that the problem is that the task.clock is not used correctly.

Was it helpful?

Solution

I believe that I have found the problems.

  1. LoopingCall is using the reactor by default. I needed to set it up so that it used my own clock via the class variable clock. See task.clock class documentation.
  2. self.clock.advance(x) sets the clock to be at time x. It does not go through (x-1, x-2, ..., now) and thus any deferred that should run on those intermediate steps will not run. Hence, the error in the test is correct behaviour. Calling self.clock.advance(1) within a loop starting at 0 and ending in seconds_elapsed did have the desired effect.

The Twisted section on unit tests is worth reading a few times so you get familiar with what is going on. If you have more problems, look at the twisted internal unit tests!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top