# Copyright (c) 2001-2004 Twisted Matrix Laboratories. # See LICENSE for details. from twisted.trial import unittest from twisted.internet import interfaces, task, reactor, defer, error # Be compatible with any jerks who used our private stuff Clock = task.Clock from twisted.python import failure class TestableLoopingCall(task.LoopingCall): def __init__(self, clock, *a, **kw): super(TestableLoopingCall, self).__init__(*a, **kw) self._callLater = lambda delay: clock.callLater(delay, self) self._seconds = clock.seconds class TestException(Exception): pass class ClockTestCase(unittest.TestCase): """ Test the non-wallclock based clock implementation. """ def testSeconds(self): """ Test that the L{seconds} method of the fake clock returns fake time. """ c = task.Clock() self.assertEquals(c.seconds(), 0) def testCallLater(self): """ Test that calls can be scheduled for later with the fake clock and hands back an L{IDelayedCall}. """ c = task.Clock() call = c.callLater(1, lambda a, b: None, 1, b=2) self.failUnless(interfaces.IDelayedCall.providedBy(call)) self.assertEquals(call.getTime(), 1) self.failUnless(call.active()) def testCallLaterCancelled(self): """ Test that calls can be cancelled. """ c = task.Clock() call = c.callLater(1, lambda a, b: None, 1, b=2) call.cancel() self.failIf(call.active()) def testAdvance(self): """ Test that advancing the clock will fire some calls. """ events = [] c = task.Clock() call = c.callLater(2, lambda: events.append(None)) c.advance(1) self.assertEquals(events, []) c.advance(1) self.assertEquals(events, [None]) self.failIf(call.active()) def testAdvanceCancel(self): """ Test attemping to cancel the call in a callback. AlreadyCalled should be raised, not for example a ValueError from removing the call from Clock.calls. This requires call.called to be set before the callback is called. """ c = task.Clock() def cb(): self.assertRaises(error.AlreadyCalled, call.cancel) call = c.callLater(1, cb) c.advance(1) def testCallLaterDelayed(self): """ Test that calls can be delayed. """ events = [] c = task.Clock() call = c.callLater(1, lambda a, b: events.append((a, b)), 1, b=2) call.delay(1) self.assertEquals(call.getTime(), 2) c.advance(1.5) self.assertEquals(events, []) c.advance(1.0) self.assertEquals(events, [(1, 2)]) def testCallLaterResetLater(self): """ Test that calls can have their time reset to a later time. """ events = [] c = task.Clock() call = c.callLater(2, lambda a, b: events.append((a, b)), 1, b=2) c.advance(1) call.reset(3) self.assertEquals(call.getTime(), 4) c.advance(2) self.assertEquals(events, []) c.advance(1) self.assertEquals(events, [(1, 2)]) def testCallLaterResetSooner(self): """ Test that calls can have their time reset to an earlier time. """ events = [] c = task.Clock() call = c.callLater(4, lambda a, b: events.append((a, b)), 1, b=2) call.reset(3) self.assertEquals(call.getTime(), 3) c.advance(3) self.assertEquals(events, [(1, 2)]) class LoopTestCase(unittest.TestCase): def testBasicFunction(self): # Arrange to have time advanced enough so that our function is # called a few times. # Only need to go to 2.5 to get 3 calls, since the first call # happens before any time has elapsed. timings = [0.05, 0.1, 0.1] clock = task.Clock() L = [] def foo(a, b, c=None, d=None): L.append((a, b, c, d)) lc = TestableLoopingCall(clock, foo, "a", "b", d="d") D = lc.start(0.1) theResult = [] def saveResult(result): theResult.append(result) D.addCallback(saveResult) clock.pump(timings) self.assertEquals(len(L), 3, "got %d iterations, not 3" % (len(L),)) for (a, b, c, d) in L: self.assertEquals(a, "a") self.assertEquals(b, "b") self.assertEquals(c, None) self.assertEquals(d, "d") lc.stop() self.assertIdentical(theResult[0], lc) # Make sure it isn't planning to do anything further. self.failIf(clock.calls) def testDelayedStart(self): timings = [0.05, 0.1, 0.1] clock = task.Clock() L = [] lc = TestableLoopingCall(clock, L.append, None) d = lc.start(0.1, now=False) theResult = [] def saveResult(result): theResult.append(result) d.addCallback(saveResult) clock.pump(timings) self.assertEquals(len(L), 2, "got %d iterations, not 2" % (len(L),)) lc.stop() self.assertIdentical(theResult[0], lc) self.failIf(clock.calls) def testBadDelay(self): lc = task.LoopingCall(lambda: None) self.assertRaises(ValueError, lc.start, -1) # Make sure that LoopingCall.stop() prevents any subsequent calls. def _stoppingTest(self, delay): ran = [] def foo(): ran.append(None) clock = task.Clock() lc = TestableLoopingCall(clock, foo) d = lc.start(delay, now=False) lc.stop() self.failIf(ran) self.failIf(clock.calls) def testStopAtOnce(self): return self._stoppingTest(0) def testStoppingBeforeDelayedStart(self): return self._stoppingTest(10) class ReactorLoopTestCase(unittest.TestCase): # Slightly inferior tests which exercise interactions with an actual # reactor. def testFailure(self): def foo(x): raise TestException(x) lc = task.LoopingCall(foo, "bar") return self.assertFailure(lc.start(0.1), TestException) def testFailAndStop(self): def foo(x): lc.stop() raise TestException(x) lc = task.LoopingCall(foo, "bar") return self.assertFailure(lc.start(0.1), TestException) def testEveryIteration(self): ran = [] def foo(): ran.append(None) if len(ran) > 5: lc.stop() lc = task.LoopingCall(foo) d = lc.start(0) def stopped(ign): self.assertEquals(len(ran), 6) return d.addCallback(stopped) def testStopAtOnceLater(self): # Ensure that even when LoopingCall.stop() is called from a # reactor callback, it still prevents any subsequent calls. d = defer.Deferred() def foo(): d.errback(failure.DefaultException( "This task also should never get called.")) self._lc = task.LoopingCall(foo) self._lc.start(1, now=False) reactor.callLater(0, self._callback_for_testStopAtOnceLater, d) return d def _callback_for_testStopAtOnceLater(self, d): self._lc.stop() reactor.callLater(0, d.callback, "success") def testWaitDeferred(self): # Tests if the callable isn't scheduled again before the returned # deferred has fired. timings = [0.2, 0.8] clock = task.Clock() def foo(): d = defer.Deferred() d.addCallback(lambda _: lc.stop()) clock.callLater(1, d.callback, None) return d lc = TestableLoopingCall(clock, foo) d = lc.start(0.2) clock.pump(timings) self.failIf(clock.calls) def testFailurePropagation(self): # Tests if the failure of the errback of the deferred returned by the # callable is propagated to the lc errback. # # To make sure this test does not hang trial when LoopingCall does not # wait for the callable's deferred, it also checks there are no # calls in the clock's callLater queue. timings = [0.3] clock = task.Clock() def foo(): d = defer.Deferred() clock.callLater(0.3, d.errback, TestException()) return d lc = TestableLoopingCall(clock, foo) d = lc.start(1) self.assertFailure(d, TestException) clock.pump(timings) self.failIf(clock.calls) return d