OLD | NEW |
| (Empty) |
1 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
2 # See LICENSE for details. | |
3 | |
4 from twisted.python.compat import set | |
5 | |
6 from twisted.trial import unittest | |
7 | |
8 from twisted.internet import interfaces, task, reactor, defer, error | |
9 | |
10 # Be compatible with any jerks who used our private stuff | |
11 Clock = task.Clock | |
12 | |
13 from twisted.python import failure | |
14 | |
15 | |
16 class TestableLoopingCall(task.LoopingCall): | |
17 def __init__(self, clock, *a, **kw): | |
18 super(TestableLoopingCall, self).__init__(*a, **kw) | |
19 self.clock = clock | |
20 | |
21 | |
22 | |
23 class TestException(Exception): | |
24 pass | |
25 | |
26 | |
27 | |
28 class ClockTestCase(unittest.TestCase): | |
29 """ | |
30 Test the non-wallclock based clock implementation. | |
31 """ | |
32 def testSeconds(self): | |
33 """ | |
34 Test that the L{seconds} method of the fake clock returns fake time. | |
35 """ | |
36 c = task.Clock() | |
37 self.assertEquals(c.seconds(), 0) | |
38 | |
39 | |
40 def testCallLater(self): | |
41 """ | |
42 Test that calls can be scheduled for later with the fake clock and | |
43 hands back an L{IDelayedCall}. | |
44 """ | |
45 c = task.Clock() | |
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()) | |
50 | |
51 | |
52 def testCallLaterCancelled(self): | |
53 """ | |
54 Test that calls can be cancelled. | |
55 """ | |
56 c = task.Clock() | |
57 call = c.callLater(1, lambda a, b: None, 1, b=2) | |
58 call.cancel() | |
59 self.failIf(call.active()) | |
60 | |
61 | |
62 def test_callLaterOrdering(self): | |
63 """ | |
64 Test that the DelayedCall returned is not one previously | |
65 created. | |
66 """ | |
67 c = task.Clock() | |
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) | |
71 | |
72 | |
73 def testAdvance(self): | |
74 """ | |
75 Test that advancing the clock will fire some calls. | |
76 """ | |
77 events = [] | |
78 c = task.Clock() | |
79 call = c.callLater(2, lambda: events.append(None)) | |
80 c.advance(1) | |
81 self.assertEquals(events, []) | |
82 c.advance(1) | |
83 self.assertEquals(events, [None]) | |
84 self.failIf(call.active()) | |
85 | |
86 | |
87 def testAdvanceCancel(self): | |
88 """ | |
89 Test attemping to cancel the call in a callback. | |
90 | |
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. | |
94 """ | |
95 c = task.Clock() | |
96 def cb(): | |
97 self.assertRaises(error.AlreadyCalled, call.cancel) | |
98 call = c.callLater(1, cb) | |
99 c.advance(1) | |
100 | |
101 | |
102 def testCallLaterDelayed(self): | |
103 """ | |
104 Test that calls can be delayed. | |
105 """ | |
106 events = [] | |
107 c = task.Clock() | |
108 call = c.callLater(1, lambda a, b: events.append((a, b)), 1, b=2) | |
109 call.delay(1) | |
110 self.assertEquals(call.getTime(), 2) | |
111 c.advance(1.5) | |
112 self.assertEquals(events, []) | |
113 c.advance(1.0) | |
114 self.assertEquals(events, [(1, 2)]) | |
115 | |
116 | |
117 def testCallLaterResetLater(self): | |
118 """ | |
119 Test that calls can have their time reset to a later time. | |
120 """ | |
121 events = [] | |
122 c = task.Clock() | |
123 call = c.callLater(2, lambda a, b: events.append((a, b)), 1, b=2) | |
124 c.advance(1) | |
125 call.reset(3) | |
126 self.assertEquals(call.getTime(), 4) | |
127 c.advance(2) | |
128 self.assertEquals(events, []) | |
129 c.advance(1) | |
130 self.assertEquals(events, [(1, 2)]) | |
131 | |
132 | |
133 def testCallLaterResetSooner(self): | |
134 """ | |
135 Test that calls can have their time reset to an earlier time. | |
136 """ | |
137 events = [] | |
138 c = task.Clock() | |
139 call = c.callLater(4, lambda a, b: events.append((a, b)), 1, b=2) | |
140 call.reset(3) | |
141 self.assertEquals(call.getTime(), 3) | |
142 c.advance(3) | |
143 self.assertEquals(events, [(1, 2)]) | |
144 | |
145 | |
146 def test_getDelayedCalls(self): | |
147 """ | |
148 Test that we can get a list of all delayed calls | |
149 """ | |
150 c = task.Clock() | |
151 call = c.callLater(1, lambda x: None) | |
152 call2 = c.callLater(2, lambda x: None) | |
153 | |
154 calls = c.getDelayedCalls() | |
155 | |
156 self.assertEquals(set([call, call2]), set(calls)) | |
157 | |
158 | |
159 def test_getDelayedCallsEmpty(self): | |
160 """ | |
161 Test that we get an empty list from getDelayedCalls on a newly | |
162 constructed Clock. | |
163 """ | |
164 c = task.Clock() | |
165 self.assertEquals(c.getDelayedCalls(), []) | |
166 | |
167 | |
168 def test_providesIReactorTime(self): | |
169 c = task.Clock() | |
170 self.failUnless(interfaces.IReactorTime.providedBy(c), | |
171 "Clock does not provide IReactorTime") | |
172 | |
173 | |
174 class LoopTestCase(unittest.TestCase): | |
175 """ | |
176 Tests for L{task.LoopingCall} based on a fake L{IReactorTime} | |
177 implementation. | |
178 """ | |
179 def test_defaultClock(self): | |
180 """ | |
181 L{LoopingCall}'s default clock should be the reactor. | |
182 """ | |
183 call = task.LoopingCall(lambda: None) | |
184 self.assertEqual(call.clock, reactor) | |
185 | |
186 | |
187 def test_callbackTimeSkips(self): | |
188 """ | |
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. | |
192 """ | |
193 times = [] | |
194 callDuration = None | |
195 clock = task.Clock() | |
196 def aCallback(): | |
197 times.append(clock.seconds()) | |
198 clock.advance(callDuration) | |
199 call = task.LoopingCall(aCallback) | |
200 call.clock = clock | |
201 | |
202 callDuration = 2 | |
203 call.start(0.5) | |
204 self.assertEqual(times, [0]) | |
205 self.assertEqual(clock.seconds(), 2) | |
206 | |
207 # An iteration should have occurred at 2, but since 2 is the present | |
208 # and not the future, it is skipped. | |
209 clock.advance(0) | |
210 self.assertEqual(times, [0]) | |
211 | |
212 # 2.5 is in the future, and is not skipped. | |
213 callDuration = 1 | |
214 clock.advance(0.5) | |
215 self.assertEqual(times, [0, 2.5]) | |
216 self.assertEqual(clock.seconds(), 3.5) | |
217 | |
218 # Another iteration should have occurred, but it is again the | |
219 # present and not the future, so it is skipped as well. | |
220 clock.advance(0) | |
221 self.assertEqual(times, [0, 2.5]) | |
222 | |
223 # 4 is in the future, and is not skipped. | |
224 callDuration = 0 | |
225 clock.advance(0.5) | |
226 self.assertEqual(times, [0, 2.5, 4]) | |
227 self.assertEqual(clock.seconds(), 4) | |
228 | |
229 | |
230 def test_reactorTimeSkips(self): | |
231 """ | |
232 When more time than the defined interval passes between when | |
233 L{LoopingCall} schedules itself to run again and when it actually | |
234 runs again, it should schedule the next call for the next interval | |
235 which is still in the future. | |
236 """ | |
237 times = [] | |
238 clock = task.Clock() | |
239 def aCallback(): | |
240 times.append(clock.seconds()) | |
241 | |
242 call = task.LoopingCall(aCallback) | |
243 call.clock = clock | |
244 | |
245 call.start(0.5) | |
246 self.assertEqual(times, [0]) | |
247 | |
248 clock.advance(2) | |
249 self.assertEqual(times, [0, 2]) | |
250 | |
251 clock.advance(1) | |
252 self.assertEqual(times, [0, 2, 3]) | |
253 | |
254 clock.advance(0) | |
255 self.assertEqual(times, [0, 2, 3]) | |
256 | |
257 | |
258 def testBasicFunction(self): | |
259 # Arrange to have time advanced enough so that our function is | |
260 # called a few times. | |
261 # Only need to go to 2.5 to get 3 calls, since the first call | |
262 # happens before any time has elapsed. | |
263 timings = [0.05, 0.1, 0.1] | |
264 | |
265 clock = task.Clock() | |
266 | |
267 L = [] | |
268 def foo(a, b, c=None, d=None): | |
269 L.append((a, b, c, d)) | |
270 | |
271 lc = TestableLoopingCall(clock, foo, "a", "b", d="d") | |
272 D = lc.start(0.1) | |
273 | |
274 theResult = [] | |
275 def saveResult(result): | |
276 theResult.append(result) | |
277 D.addCallback(saveResult) | |
278 | |
279 clock.pump(timings) | |
280 | |
281 self.assertEquals(len(L), 3, | |
282 "got %d iterations, not 3" % (len(L),)) | |
283 | |
284 for (a, b, c, d) in L: | |
285 self.assertEquals(a, "a") | |
286 self.assertEquals(b, "b") | |
287 self.assertEquals(c, None) | |
288 self.assertEquals(d, "d") | |
289 | |
290 lc.stop() | |
291 self.assertIdentical(theResult[0], lc) | |
292 | |
293 # Make sure it isn't planning to do anything further. | |
294 self.failIf(clock.calls) | |
295 | |
296 | |
297 def testDelayedStart(self): | |
298 timings = [0.05, 0.1, 0.1] | |
299 | |
300 clock = task.Clock() | |
301 | |
302 L = [] | |
303 lc = TestableLoopingCall(clock, L.append, None) | |
304 d = lc.start(0.1, now=False) | |
305 | |
306 theResult = [] | |
307 def saveResult(result): | |
308 theResult.append(result) | |
309 d.addCallback(saveResult) | |
310 | |
311 clock.pump(timings) | |
312 | |
313 self.assertEquals(len(L), 2, | |
314 "got %d iterations, not 2" % (len(L),)) | |
315 lc.stop() | |
316 self.assertIdentical(theResult[0], lc) | |
317 | |
318 self.failIf(clock.calls) | |
319 | |
320 | |
321 def testBadDelay(self): | |
322 lc = task.LoopingCall(lambda: None) | |
323 self.assertRaises(ValueError, lc.start, -1) | |
324 | |
325 | |
326 # Make sure that LoopingCall.stop() prevents any subsequent calls. | |
327 def _stoppingTest(self, delay): | |
328 ran = [] | |
329 def foo(): | |
330 ran.append(None) | |
331 | |
332 clock = task.Clock() | |
333 lc = TestableLoopingCall(clock, foo) | |
334 d = lc.start(delay, now=False) | |
335 lc.stop() | |
336 self.failIf(ran) | |
337 self.failIf(clock.calls) | |
338 | |
339 | |
340 def testStopAtOnce(self): | |
341 return self._stoppingTest(0) | |
342 | |
343 | |
344 def testStoppingBeforeDelayedStart(self): | |
345 return self._stoppingTest(10) | |
346 | |
347 | |
348 | |
349 class ReactorLoopTestCase(unittest.TestCase): | |
350 # Slightly inferior tests which exercise interactions with an actual | |
351 # reactor. | |
352 def testFailure(self): | |
353 def foo(x): | |
354 raise TestException(x) | |
355 | |
356 lc = task.LoopingCall(foo, "bar") | |
357 return self.assertFailure(lc.start(0.1), TestException) | |
358 | |
359 | |
360 def testFailAndStop(self): | |
361 def foo(x): | |
362 lc.stop() | |
363 raise TestException(x) | |
364 | |
365 lc = task.LoopingCall(foo, "bar") | |
366 return self.assertFailure(lc.start(0.1), TestException) | |
367 | |
368 | |
369 def testEveryIteration(self): | |
370 ran = [] | |
371 | |
372 def foo(): | |
373 ran.append(None) | |
374 if len(ran) > 5: | |
375 lc.stop() | |
376 | |
377 lc = task.LoopingCall(foo) | |
378 d = lc.start(0) | |
379 def stopped(ign): | |
380 self.assertEquals(len(ran), 6) | |
381 return d.addCallback(stopped) | |
382 | |
383 | |
384 def testStopAtOnceLater(self): | |
385 # Ensure that even when LoopingCall.stop() is called from a | |
386 # reactor callback, it still prevents any subsequent calls. | |
387 d = defer.Deferred() | |
388 def foo(): | |
389 d.errback(failure.DefaultException( | |
390 "This task also should never get called.")) | |
391 self._lc = task.LoopingCall(foo) | |
392 self._lc.start(1, now=False) | |
393 reactor.callLater(0, self._callback_for_testStopAtOnceLater, d) | |
394 return d | |
395 | |
396 | |
397 def _callback_for_testStopAtOnceLater(self, d): | |
398 self._lc.stop() | |
399 reactor.callLater(0, d.callback, "success") | |
400 | |
401 def testWaitDeferred(self): | |
402 # Tests if the callable isn't scheduled again before the returned | |
403 # deferred has fired. | |
404 timings = [0.2, 0.8] | |
405 clock = task.Clock() | |
406 | |
407 def foo(): | |
408 d = defer.Deferred() | |
409 d.addCallback(lambda _: lc.stop()) | |
410 clock.callLater(1, d.callback, None) | |
411 return d | |
412 | |
413 lc = TestableLoopingCall(clock, foo) | |
414 d = lc.start(0.2) | |
415 clock.pump(timings) | |
416 self.failIf(clock.calls) | |
417 | |
418 def testFailurePropagation(self): | |
419 # Tests if the failure of the errback of the deferred returned by the | |
420 # callable is propagated to the lc errback. | |
421 # | |
422 # To make sure this test does not hang trial when LoopingCall does not | |
423 # wait for the callable's deferred, it also checks there are no | |
424 # calls in the clock's callLater queue. | |
425 timings = [0.3] | |
426 clock = task.Clock() | |
427 | |
428 def foo(): | |
429 d = defer.Deferred() | |
430 clock.callLater(0.3, d.errback, TestException()) | |
431 return d | |
432 | |
433 lc = TestableLoopingCall(clock, foo) | |
434 d = lc.start(1) | |
435 self.assertFailure(d, TestException) | |
436 | |
437 clock.pump(timings) | |
438 self.failIf(clock.calls) | |
439 return d | |
440 | |
441 | |
442 | |
443 class DeferLaterTests(unittest.TestCase): | |
444 """ | |
445 Tests for L{task.deferLater}. | |
446 """ | |
447 def test_callback(self): | |
448 """ | |
449 The L{Deferred} returned by L{task.deferLater} is called back after | |
450 the specified delay with the result of the function passed in. | |
451 """ | |
452 results = [] | |
453 flag = object() | |
454 def callable(foo, bar): | |
455 results.append((foo, bar)) | |
456 return flag | |
457 | |
458 clock = task.Clock() | |
459 d = task.deferLater(clock, 3, callable, 'foo', bar='bar') | |
460 d.addCallback(self.assertIdentical, flag) | |
461 clock.advance(2) | |
462 self.assertEqual(results, []) | |
463 clock.advance(1) | |
464 self.assertEqual(results, [('foo', 'bar')]) | |
465 return d | |
466 | |
467 | |
468 def test_errback(self): | |
469 """ | |
470 The L{Deferred} returned by L{task.deferLater} is errbacked if the | |
471 supplied function raises an exception. | |
472 """ | |
473 def callable(): | |
474 raise TestException() | |
475 | |
476 clock = task.Clock() | |
477 d = task.deferLater(clock, 1, callable) | |
478 clock.advance(1) | |
479 return self.assertFailure(d, TestException) | |
OLD | NEW |