3

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.

Sardathrion - against SE abuse
  • 17,269
  • 27
  • 101
  • 156
  • 1
    A "cut-down version" of the test code is not good enough; it needs to be minimal so we don't get bored reading about irrelevant details, but it also needs to be complete enough so we can actually run it to see what your problem is. This example is especially incomplete because you're not even showing how you're constructing your `LoopingCall`, which is really the most important part of the problem here. See http://sscce.org/ for more details on how to construct a good example. – Glyph Jan 30 '13 at 18:51
  • @Glyph: Right, I have added a toy example of my problem. I did fix the reactor unclean error by realising that *one should never have deferred running after the tearDown() method*. I must have read that a dozen time and not got it... I get it now. ^_~ – Sardathrion - against SE abuse Jan 31 '13 at 09:58

1 Answers1

2

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!

Sardathrion - against SE abuse
  • 17,269
  • 27
  • 101
  • 156
  • Sorry for bringing this up again but, could you give more details on how did you solve point (1)? I'm having this related issue: http://stackoverflow.com/q/30100752/1336939. Thanks! – synack May 22 '15 at 23:21