1
# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
2
# See LICENSE for details.
4
from twisted.python.compat import set
6
from twisted.trial import unittest
8
from twisted.internet import interfaces, task, reactor, defer, error
10
# Be compatible with any jerks who used our private stuff
13
from twisted.python import failure
16
class TestableLoopingCall(task.LoopingCall):
17
def __init__(self, clock, *a, **kw):
18
super(TestableLoopingCall, self).__init__(*a, **kw)
23
class TestException(Exception):
28
class ClockTestCase(unittest.TestCase):
30
Test the non-wallclock based clock implementation.
32
def testSeconds(self):
34
Test that the L{seconds} method of the fake clock returns fake time.
37
self.assertEquals(c.seconds(), 0)
40
def testCallLater(self):
42
Test that calls can be scheduled for later with the fake clock and
43
hands back an L{IDelayedCall}.
46
call = c.callLater(1, lambda a, b: None, 1, b=2)
47
self.failUnless(interfaces.IDelayedCall.providedBy(call))
48
self.assertEquals(call.getTime(), 1)
49
self.failUnless(call.active())
52
def testCallLaterCancelled(self):
54
Test that calls can be cancelled.
57
call = c.callLater(1, lambda a, b: None, 1, b=2)
59
self.failIf(call.active())
62
def test_callLaterOrdering(self):
64
Test that the DelayedCall returned is not one previously
68
call1 = c.callLater(10, lambda a, b: None, 1, b=2)
69
call2 = c.callLater(1, lambda a, b: None, 3, b=4)
70
self.failIf(call1 is call2)
73
def testAdvance(self):
75
Test that advancing the clock will fire some calls.
79
call = c.callLater(2, lambda: events.append(None))
81
self.assertEquals(events, [])
83
self.assertEquals(events, [None])
84
self.failIf(call.active())
87
def testAdvanceCancel(self):
89
Test attemping to cancel the call in a callback.
91
AlreadyCalled should be raised, not for example a ValueError from
92
removing the call from Clock.calls. This requires call.called to be
93
set before the callback is called.
97
self.assertRaises(error.AlreadyCalled, call.cancel)
98
call = c.callLater(1, cb)
102
def testCallLaterDelayed(self):
104
Test that calls can be delayed.
108
call = c.callLater(1, lambda a, b: events.append((a, b)), 1, b=2)
110
self.assertEquals(call.getTime(), 2)
112
self.assertEquals(events, [])
114
self.assertEquals(events, [(1, 2)])
117
def testCallLaterResetLater(self):
119
Test that calls can have their time reset to a later time.
123
call = c.callLater(2, lambda a, b: events.append((a, b)), 1, b=2)
126
self.assertEquals(call.getTime(), 4)
128
self.assertEquals(events, [])
130
self.assertEquals(events, [(1, 2)])
133
def testCallLaterResetSooner(self):
135
Test that calls can have their time reset to an earlier time.
139
call = c.callLater(4, lambda a, b: events.append((a, b)), 1, b=2)
141
self.assertEquals(call.getTime(), 3)
143
self.assertEquals(events, [(1, 2)])
146
def test_getDelayedCalls(self):
148
Test that we can get a list of all delayed calls
151
call = c.callLater(1, lambda x: None)
152
call2 = c.callLater(2, lambda x: None)
154
calls = c.getDelayedCalls()
156
self.assertEquals(set([call, call2]), set(calls))
159
def test_getDelayedCallsEmpty(self):
161
Test that we get an empty list from getDelayedCalls on a newly
165
self.assertEquals(c.getDelayedCalls(), [])
168
def test_providesIReactorTime(self):
170
self.failUnless(interfaces.IReactorTime.providedBy(c),
171
"Clock does not provide IReactorTime")
174
class LoopTestCase(unittest.TestCase):
176
Tests for L{task.LoopingCall} based on a fake L{IReactorTime}
179
def test_defaultClock(self):
181
L{LoopingCall}'s default clock should be the reactor.
183
call = task.LoopingCall(lambda: None)
184
self.assertEqual(call.clock, reactor)
187
def test_callbackTimeSkips(self):
189
When more time than the defined interval passes during the execution
190
of a callback, L{LoopingCall} should schedule the next call for the
191
next interval which is still in the future.
197
times.append(clock.seconds())
198
clock.advance(callDuration)
199
call = task.LoopingCall(aCallback)
202
# Start a LoopingCall with a 0.5 second increment, and immediately call
207
# Verify that the callable was called, and since it was immediate, with
209
self.assertEqual(times, [0])
211
# The callback should have advanced the clock by the callDuration.
212
self.assertEqual(clock.seconds(), callDuration)
214
# An iteration should have occurred at 2, but since 2 is the present
215
# and not the future, it is skipped.
218
self.assertEqual(times, [0])
220
# 2.5 is in the future, and is not skipped.
223
self.assertEqual(times, [0, 2.5])
224
self.assertEqual(clock.seconds(), 3.5)
226
# Another iteration should have occurred, but it is again the
227
# present and not the future, so it is skipped as well.
229
self.assertEqual(times, [0, 2.5])
231
# 4 is in the future, and is not skipped.
234
self.assertEqual(times, [0, 2.5, 4])
235
self.assertEqual(clock.seconds(), 4)
238
def test_reactorTimeSkips(self):
240
When more time than the defined interval passes between when
241
L{LoopingCall} schedules itself to run again and when it actually
242
runs again, it should schedule the next call for the next interval
243
which is still in the future.
248
times.append(clock.seconds())
250
# Start a LoopingCall that tracks the time passed, with a 0.5 second
252
call = task.LoopingCall(aCallback)
256
# Initially, no time should have passed!
257
self.assertEqual(times, [0])
259
# Advance the clock by 2 seconds (2 seconds should have passed)
261
self.assertEqual(times, [0, 2])
263
# Advance the clock by 1 second (3 total should have passed)
265
self.assertEqual(times, [0, 2, 3])
267
# Advance the clock by 0 seconds (this should have no effect!)
269
self.assertEqual(times, [0, 2, 3])
272
def test_reactorTimeCountSkips(self):
274
When L{LoopingCall} schedules itself to run again, if more than the
275
specified interval has passed, it should schedule the next call for the
276
next interval which is still in the future. If it was created
277
using L{LoopingCall.withCount}, a positional argument will be
278
inserted at the beginning of the argument list, indicating the number
279
of calls that should have been made.
283
def aCallback(numCalls):
284
times.append((clock.seconds(), numCalls))
286
# Start a LoopingCall that tracks the time passed, and the number of
287
# skips, with a 0.5 second increment.
288
call = task.LoopingCall.withCount(aCallback)
291
REALISTIC_DELAY = 0.01
294
# Initially, no seconds should have passed, and one calls should have
296
self.assertEqual(times, [(0, 1)])
298
# After the interval (plus a small delay, to account for the time that
299
# the reactor takes to wake up and process the LoopingCall), we should
300
# still have only made one call.
301
clock.advance(INTERVAL + REALISTIC_DELAY)
302
self.assertEqual(times, [(0, 1), (INTERVAL + REALISTIC_DELAY, 1)])
304
# After advancing the clock by three intervals (plus a small delay to
305
# account for the reactor), we should have skipped two calls; one less
306
# than the number of intervals which have completely elapsed. Along
307
# with the call we did actually make, the final number of calls is 3.
308
clock.advance((3 * INTERVAL) + REALISTIC_DELAY)
309
self.assertEqual(times,
310
[(0, 1), (INTERVAL + REALISTIC_DELAY, 1),
311
((4 * INTERVAL) + (2 * REALISTIC_DELAY), 3)])
313
# Advancing the clock by 0 seconds should not cause any changes!
315
self.assertEqual(times,
316
[(0, 1), (INTERVAL + REALISTIC_DELAY, 1),
317
((4 * INTERVAL) + (2 * REALISTIC_DELAY), 3)])
320
def test_countLengthyIntervalCounts(self):
322
L{LoopingCall.withCount} counts only calls that were expected to be
323
made. So, if more than one, but less than two intervals pass between
324
invocations, it won't increase the count above 1. For example, a
325
L{LoopingCall} with interval T expects to be invoked at T, 2T, 3T, etc.
326
However, the reactor takes some time to get around to calling it, so in
327
practice it will be called at T+something, 2T+something, 3T+something;
328
and due to other things going on in the reactor, "something" is
329
variable. It won't increase the count unless "something" is greater
330
than T. So if the L{LoopingCall} is invoked at T, 2.75T, and 3T,
331
the count has not increased, even though the distance between
332
invocation 1 and invocation 2 is 1.75T.
336
def aCallback(count):
337
times.append((clock.seconds(), count))
339
# Start a LoopingCall that tracks the time passed, and the number of
340
# calls, with a 0.5 second increment.
341
call = task.LoopingCall.withCount(aCallback)
344
REALISTIC_DELAY = 0.01
346
self.assertEqual(times.pop(), (0, 1))
348
# About one interval... So far, so good
349
clock.advance(INTERVAL + REALISTIC_DELAY)
350
self.assertEqual(times.pop(), (INTERVAL + REALISTIC_DELAY, 1))
352
# Oh no, something delayed us for a while.
353
clock.advance(INTERVAL * 1.75)
354
self.assertEqual(times.pop(), ((2.75 * INTERVAL) + REALISTIC_DELAY, 1))
356
# Back on track! We got invoked when we expected this time.
357
clock.advance(INTERVAL * 0.25)
358
self.assertEqual(times.pop(), ((3.0 * INTERVAL) + REALISTIC_DELAY, 1))
361
def testBasicFunction(self):
362
# Arrange to have time advanced enough so that our function is
363
# called a few times.
364
# Only need to go to 2.5 to get 3 calls, since the first call
365
# happens before any time has elapsed.
366
timings = [0.05, 0.1, 0.1]
371
def foo(a, b, c=None, d=None):
372
L.append((a, b, c, d))
374
lc = TestableLoopingCall(clock, foo, "a", "b", d="d")
378
def saveResult(result):
379
theResult.append(result)
380
D.addCallback(saveResult)
384
self.assertEquals(len(L), 3,
385
"got %d iterations, not 3" % (len(L),))
387
for (a, b, c, d) in L:
388
self.assertEquals(a, "a")
389
self.assertEquals(b, "b")
390
self.assertEquals(c, None)
391
self.assertEquals(d, "d")
394
self.assertIdentical(theResult[0], lc)
396
# Make sure it isn't planning to do anything further.
397
self.failIf(clock.calls)
400
def testDelayedStart(self):
401
timings = [0.05, 0.1, 0.1]
406
lc = TestableLoopingCall(clock, L.append, None)
407
d = lc.start(0.1, now=False)
410
def saveResult(result):
411
theResult.append(result)
412
d.addCallback(saveResult)
416
self.assertEquals(len(L), 2,
417
"got %d iterations, not 2" % (len(L),))
419
self.assertIdentical(theResult[0], lc)
421
self.failIf(clock.calls)
424
def testBadDelay(self):
425
lc = task.LoopingCall(lambda: None)
426
self.assertRaises(ValueError, lc.start, -1)
429
# Make sure that LoopingCall.stop() prevents any subsequent calls.
430
def _stoppingTest(self, delay):
436
lc = TestableLoopingCall(clock, foo)
437
d = lc.start(delay, now=False)
440
self.failIf(clock.calls)
443
def testStopAtOnce(self):
444
return self._stoppingTest(0)
447
def testStoppingBeforeDelayedStart(self):
448
return self._stoppingTest(10)
452
class ReactorLoopTestCase(unittest.TestCase):
453
# Slightly inferior tests which exercise interactions with an actual
455
def testFailure(self):
457
raise TestException(x)
459
lc = task.LoopingCall(foo, "bar")
460
return self.assertFailure(lc.start(0.1), TestException)
463
def testFailAndStop(self):
466
raise TestException(x)
468
lc = task.LoopingCall(foo, "bar")
469
return self.assertFailure(lc.start(0.1), TestException)
472
def testEveryIteration(self):
480
lc = task.LoopingCall(foo)
483
self.assertEquals(len(ran), 6)
484
return d.addCallback(stopped)
487
def testStopAtOnceLater(self):
488
# Ensure that even when LoopingCall.stop() is called from a
489
# reactor callback, it still prevents any subsequent calls.
492
d.errback(failure.DefaultException(
493
"This task also should never get called."))
494
self._lc = task.LoopingCall(foo)
495
self._lc.start(1, now=False)
496
reactor.callLater(0, self._callback_for_testStopAtOnceLater, d)
500
def _callback_for_testStopAtOnceLater(self, d):
502
reactor.callLater(0, d.callback, "success")
504
def testWaitDeferred(self):
505
# Tests if the callable isn't scheduled again before the returned
506
# deferred has fired.
512
d.addCallback(lambda _: lc.stop())
513
clock.callLater(1, d.callback, None)
516
lc = TestableLoopingCall(clock, foo)
519
self.failIf(clock.calls)
521
def testFailurePropagation(self):
522
# Tests if the failure of the errback of the deferred returned by the
523
# callable is propagated to the lc errback.
525
# To make sure this test does not hang trial when LoopingCall does not
526
# wait for the callable's deferred, it also checks there are no
527
# calls in the clock's callLater queue.
533
clock.callLater(0.3, d.errback, TestException())
536
lc = TestableLoopingCall(clock, foo)
538
self.assertFailure(d, TestException)
541
self.failIf(clock.calls)
545
def test_deferredWithCount(self):
547
In the case that the function passed to L{LoopingCall.withCount}
548
returns a deferred, which does not fire before the next interval
549
elapses, the function should not be run again. And if a function call
550
is skipped in this fashion, the appropriate count should be
553
testClock = task.Clock()
557
def countTracker(possibleCount):
558
# Keep a list of call counts
559
deferredCounts.append(possibleCount)
560
# Return a deferred, but only on the first request
561
if len(deferredCounts) == 1:
566
# Start a looping call for our countTracker function
567
# Set the increment to 0.2, and do not call the function on startup.
568
lc = task.LoopingCall.withCount(countTracker)
570
d = lc.start(0.2, now=False)
572
# Confirm that nothing has happened yet.
573
self.assertEquals(deferredCounts, [])
575
# Advance the clock by 0.2 and then 0.4;
576
testClock.pump([0.2, 0.4])
577
# We should now have exactly one count (of 1 call)
578
self.assertEquals(len(deferredCounts), 1)
580
# Fire the deferred, and advance the clock by another 0.2
582
testClock.pump([0.2])
583
# We should now have exactly 2 counts...
584
self.assertEquals(len(deferredCounts), 2)
585
# The first count should be 1 (one call)
586
# The second count should be 3 (calls were missed at about 0.6 and 0.8)
587
self.assertEquals(deferredCounts, [1, 3])
591
class DeferLaterTests(unittest.TestCase):
593
Tests for L{task.deferLater}.
595
def test_callback(self):
597
The L{Deferred} returned by L{task.deferLater} is called back after
598
the specified delay with the result of the function passed in.
602
def callable(foo, bar):
603
results.append((foo, bar))
607
d = task.deferLater(clock, 3, callable, 'foo', bar='bar')
608
d.addCallback(self.assertIdentical, flag)
610
self.assertEqual(results, [])
612
self.assertEqual(results, [('foo', 'bar')])
616
def test_errback(self):
618
The L{Deferred} returned by L{task.deferLater} is errbacked if the
619
supplied function raises an exception.
622
raise TestException()
625
d = task.deferLater(clock, 1, callable)
627
return self.assertFailure(d, TestException)