OLD | NEW |
| (Empty) |
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 // Tests timer tasks. | |
6 | |
7 import 'package:expect/expect.dart'; | |
8 import 'package:async_helper/async_helper.dart'; | |
9 import 'dart:async'; | |
10 import 'dart:collection'; | |
11 | |
12 class MyTimerSpecification implements SingleShotTimerTaskSpecification { | |
13 final Function callback; | |
14 final Duration duration; | |
15 | |
16 MyTimerSpecification(this.callback, this.duration); | |
17 | |
18 bool get isOneShot => true; | |
19 String get name => "test.timer-override"; | |
20 } | |
21 | |
22 class MyPeriodicTimerSpecification implements PeriodicTimerTaskSpecification { | |
23 final Function callback; | |
24 final Duration duration; | |
25 | |
26 MyPeriodicTimerSpecification(this.callback, this.duration); | |
27 | |
28 bool get isOneShot => true; | |
29 String get name => "test.periodic-timer-override"; | |
30 } | |
31 | |
32 /// Makes sure things are working in a simple setting. | |
33 /// No interceptions, changes, ... | |
34 Future testTimerTask() { | |
35 List log = []; | |
36 | |
37 var testCompleter = new Completer(); | |
38 asyncStart(); | |
39 | |
40 int taskIdCounter = 0; | |
41 | |
42 Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone, | |
43 TaskCreate create, TaskSpecification specification) { | |
44 var taskMap = self['taskMap']; | |
45 var taskIdMap = self['taskIdMap']; | |
46 if (specification is SingleShotTimerTaskSpecification) { | |
47 log.add("create enter " | |
48 "zone: ${self['name']} " | |
49 "spec-duration: ${specification.duration} " | |
50 "spec-oneshot?: ${specification.isOneShot}"); | |
51 var result = parent.createTask(zone, create, specification); | |
52 taskMap[result] = specification; | |
53 taskIdMap[specification] = taskIdCounter++; | |
54 log.add("create leave"); | |
55 return result; | |
56 } else if (specification is PeriodicTimerTaskSpecification) { | |
57 log.add("create enter " | |
58 "zone: ${self['name']} " | |
59 "spec-duration: ${specification.duration} " | |
60 "spec-oneshot?: ${specification.isOneShot}"); | |
61 var result = parent.createTask(zone, create, specification); | |
62 taskMap[result] = specification; | |
63 taskIdMap[specification] = taskIdCounter++; | |
64 log.add("create leave"); | |
65 return result; | |
66 } | |
67 return parent.createTask(zone, create, specification); | |
68 } | |
69 | |
70 void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run, | |
71 Object task, Object arg) { | |
72 var taskMap = self['taskMap']; | |
73 var taskIdMap = self['taskIdMap']; | |
74 if (taskMap.containsKey(task)) { | |
75 var spec = taskMap[task]; | |
76 log.add("run enter " | |
77 "zone: ${self['name']} " | |
78 "task-id: ${taskIdMap[spec]} " | |
79 "arg: $arg"); | |
80 parent.runTask(zone, run, task, arg); | |
81 log.add("run leave"); | |
82 return; | |
83 } | |
84 parent.runTask(zone, run, task, arg); | |
85 } | |
86 | |
87 runZoned(() async { | |
88 var completer0 = new Completer(); | |
89 Timer.run(() { | |
90 completer0.complete("done"); | |
91 }); | |
92 await completer0.future; | |
93 | |
94 Expect.listEquals([ | |
95 'create enter zone: custom zone spec-duration: 0:00:00.000000 ' | |
96 'spec-oneshot?: true', | |
97 'create leave', | |
98 'run enter zone: custom zone task-id: 0 arg: null', | |
99 'run leave' | |
100 ], log); | |
101 log.clear(); | |
102 | |
103 var completer1 = new Completer(); | |
104 var counter1 = 0; | |
105 new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) { | |
106 if (counter1++ > 1) { | |
107 timer.cancel(); | |
108 completer1.complete("done"); | |
109 } | |
110 }); | |
111 await completer1.future; | |
112 | |
113 Expect.listEquals([ | |
114 'create enter zone: custom zone spec-duration: 0:00:00.005000 ' | |
115 'spec-oneshot?: false', | |
116 'create leave', | |
117 'run enter zone: custom zone task-id: 1 arg: null', | |
118 'run leave', | |
119 'run enter zone: custom zone task-id: 1 arg: null', | |
120 'run leave', | |
121 'run enter zone: custom zone task-id: 1 arg: null', | |
122 'run leave' | |
123 ], log); | |
124 log.clear(); | |
125 | |
126 testCompleter.complete("done"); | |
127 asyncEnd(); | |
128 }, | |
129 zoneValues: {'name': 'custom zone', 'taskMap': {}, 'taskIdMap': {}}, | |
130 zoneSpecification: new ZoneSpecification( | |
131 createTask: createTaskHandler, | |
132 runTask: runTaskHandler)); | |
133 | |
134 return testCompleter.future; | |
135 } | |
136 | |
137 /// More complicated zone, that intercepts... | |
138 Future testTimerTask2() { | |
139 List log = []; | |
140 | |
141 var testCompleter = new Completer(); | |
142 asyncStart(); | |
143 | |
144 int taskIdCounter = 0; | |
145 | |
146 Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone, | |
147 TaskCreate create, TaskSpecification specification) { | |
148 var taskMap = self['taskMap']; | |
149 var taskIdMap = self['taskIdMap']; | |
150 if (specification is SingleShotTimerTaskSpecification) { | |
151 log.add("create enter " | |
152 "zone: ${self['name']} " | |
153 "spec-duration: ${specification.duration} " | |
154 "spec-oneshot?: ${specification.isOneShot}"); | |
155 var mySpec = new MyTimerSpecification(specification.callback, | |
156 specification.duration + const Duration(milliseconds: 2)); | |
157 var result = parent.createTask(zone, create, mySpec); | |
158 taskMap[result] = specification; | |
159 taskIdMap[specification] = taskIdCounter++; | |
160 log.add("create leave"); | |
161 return result; | |
162 } else if (specification is PeriodicTimerTaskSpecification) { | |
163 log.add("create enter " | |
164 "zone: ${self['name']} " | |
165 "spec-duration: ${specification.duration} " | |
166 "spec-oneshot?: ${specification.isOneShot}"); | |
167 var mySpec = new MyPeriodicTimerSpecification(specification.callback, | |
168 specification.duration + const Duration(milliseconds: 2)); | |
169 var result = parent.createTask(zone, create, specification); | |
170 taskMap[result] = specification; | |
171 taskIdMap[specification] = taskIdCounter++; | |
172 log.add("create leave"); | |
173 return result; | |
174 } | |
175 return parent.createTask(zone, create, specification); | |
176 } | |
177 | |
178 void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run, | |
179 Object task, Object arg) { | |
180 var taskMap = self['taskMap']; | |
181 var taskIdMap = self['taskIdMap']; | |
182 if (taskMap.containsKey(task)) { | |
183 var spec = taskMap[task]; | |
184 log.add("run enter " | |
185 "zone: ${self['name']} " | |
186 "task-id: ${taskIdMap[spec]} " | |
187 "arg: $arg"); | |
188 parent.runTask(zone, run, task, arg); | |
189 log.add("run leave"); | |
190 return; | |
191 } | |
192 parent.runTask(zone, run, task, arg); | |
193 } | |
194 | |
195 runZoned(() async { | |
196 var completer0 = new Completer(); | |
197 Timer.run(() { | |
198 completer0.complete("done"); | |
199 }); | |
200 await completer0.future; | |
201 | |
202 // No visible change (except for the zone name) in the log, compared to the | |
203 // simple invocations. | |
204 Expect.listEquals([ | |
205 'create enter zone: outer-zone spec-duration: 0:00:00.000000 ' | |
206 'spec-oneshot?: true', | |
207 'create leave', | |
208 'run enter zone: outer-zone task-id: 0 arg: null', | |
209 'run leave' | |
210 ], log); | |
211 log.clear(); | |
212 | |
213 var completer1 = new Completer(); | |
214 var counter1 = 0; | |
215 new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) { | |
216 if (counter1++ > 1) { | |
217 timer.cancel(); | |
218 completer1.complete("done"); | |
219 } | |
220 }); | |
221 await completer1.future; | |
222 | |
223 // No visible change (except for the zone nome) in the log, compared to the | |
224 // simple invocations. | |
225 Expect.listEquals([ | |
226 'create enter zone: outer-zone spec-duration: 0:00:00.005000 ' | |
227 'spec-oneshot?: false', | |
228 'create leave', | |
229 'run enter zone: outer-zone task-id: 1 arg: null', | |
230 'run leave', | |
231 'run enter zone: outer-zone task-id: 1 arg: null', | |
232 'run leave', | |
233 'run enter zone: outer-zone task-id: 1 arg: null', | |
234 'run leave' | |
235 ], log); | |
236 log.clear(); | |
237 | |
238 var nestedCompleter = new Completer(); | |
239 | |
240 runZoned(() async { | |
241 var completer0 = new Completer(); | |
242 Timer.run(() { | |
243 completer0.complete("done"); | |
244 }); | |
245 await completer0.future; | |
246 | |
247 // The outer zone sees the duration change of the inner zone. | |
248 Expect.listEquals([ | |
249 'create enter zone: inner-zone spec-duration: 0:00:00.000000 ' | |
250 'spec-oneshot?: true', | |
251 'create enter zone: outer-zone spec-duration: 0:00:00.002000 ' | |
252 'spec-oneshot?: true', | |
253 'create leave', | |
254 'create leave', | |
255 'run enter zone: inner-zone task-id: 3 arg: null', | |
256 'run enter zone: outer-zone task-id: 2 arg: null', | |
257 'run leave', | |
258 'run leave' | |
259 ], log); | |
260 log.clear(); | |
261 | |
262 var completer1 = new Completer(); | |
263 var counter1 = 0; | |
264 new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) { | |
265 if (counter1++ > 1) { | |
266 timer.cancel(); | |
267 completer1.complete("done"); | |
268 } | |
269 }); | |
270 await completer1.future; | |
271 | |
272 // The outer zone sees the duration change of the inner zone. | |
273 Expect.listEquals([ | |
274 'create enter zone: inner-zone spec-duration: 0:00:00.005000 ' | |
275 'spec-oneshot?: false', | |
276 'create enter zone: outer-zone spec-duration: 0:00:00.005000 ' | |
277 'spec-oneshot?: false', | |
278 'create leave', | |
279 'create leave', | |
280 'run enter zone: inner-zone task-id: 5 arg: null', | |
281 'run enter zone: outer-zone task-id: 4 arg: null', | |
282 'run leave', | |
283 'run leave', | |
284 'run enter zone: inner-zone task-id: 5 arg: null', | |
285 'run enter zone: outer-zone task-id: 4 arg: null', | |
286 'run leave', | |
287 'run leave', | |
288 'run enter zone: inner-zone task-id: 5 arg: null', | |
289 'run enter zone: outer-zone task-id: 4 arg: null', | |
290 'run leave', | |
291 'run leave' | |
292 ], log); | |
293 log.clear(); | |
294 | |
295 nestedCompleter.complete("done"); | |
296 }, | |
297 zoneValues: {'name': 'inner-zone', 'taskMap': {}, 'taskIdMap': {}}, | |
298 zoneSpecification: new ZoneSpecification( | |
299 createTask: createTaskHandler, | |
300 runTask: runTaskHandler)); | |
301 | |
302 await nestedCompleter.future; | |
303 testCompleter.complete("done"); | |
304 asyncEnd(); | |
305 }, | |
306 zoneValues: {'name': 'outer-zone', 'taskMap': {}, 'taskIdMap': {}}, | |
307 zoneSpecification: new ZoneSpecification( | |
308 createTask: createTaskHandler, | |
309 runTask: runTaskHandler)); | |
310 | |
311 return testCompleter.future; | |
312 } | |
313 | |
314 class TimerEntry { | |
315 final int time; | |
316 final SimulatedTimer timer; | |
317 | |
318 TimerEntry(this.time, this.timer); | |
319 } | |
320 | |
321 class SimulatedTimer implements Timer { | |
322 static int _idCounter = 0; | |
323 | |
324 Zone _zone; | |
325 final int _id = _idCounter++; | |
326 final Duration _duration; | |
327 final Function _callback; | |
328 final bool _isPeriodic; | |
329 bool _isActive = true; | |
330 | |
331 SimulatedTimer(this._zone, this._duration, this._callback, this._isPeriodic); | |
332 | |
333 bool get isActive => _isActive; | |
334 | |
335 void cancel() { | |
336 _isActive = false; | |
337 } | |
338 | |
339 void _run() { | |
340 if (!isActive) return; | |
341 _zone.runTask(_runTimer, this, null); | |
342 } | |
343 | |
344 static void _runTimer(SimulatedTimer timer, _) { | |
345 if (timer._isPeriodic) { | |
346 timer._callback(timer); | |
347 } else { | |
348 timer._callback(); | |
349 } | |
350 } | |
351 } | |
352 | |
353 testSimulatedTimer() { | |
354 List log = []; | |
355 | |
356 var currentTime = 0; | |
357 // Using a simple list as queue. Not very efficient, but the test has only | |
358 // very few timers running at the same time. | |
359 var queue = new DoubleLinkedQueue<TimerEntry>(); | |
360 | |
361 // Schedules the given callback at now + duration. | |
362 void schedule(int scheduledTime, SimulatedTimer timer) { | |
363 log.add("scheduling timer ${timer._id} for $scheduledTime"); | |
364 if (queue.isEmpty) { | |
365 queue.add(new TimerEntry(scheduledTime, timer)); | |
366 } else { | |
367 DoubleLinkedQueueEntry current = queue.firstEntry(); | |
368 while (current != null) { | |
369 if (current.element.time <= scheduledTime) { | |
370 current = current.nextEntry(); | |
371 } else { | |
372 current.prepend(new TimerEntry(scheduledTime, timer)); | |
373 break; | |
374 } | |
375 } | |
376 if (current == null) { | |
377 queue.add(new TimerEntry(scheduledTime, timer)); | |
378 } | |
379 } | |
380 } | |
381 | |
382 void runQueue() { | |
383 while (queue.isNotEmpty) { | |
384 var item = queue.removeFirst(); | |
385 // If multiple callbacks were scheduled at the same time, increment the | |
386 // current time instead of staying at the same time. | |
387 currentTime = item.time > currentTime ? item.time : currentTime + 1; | |
388 SimulatedTimer timer = item.timer; | |
389 log.add("running timer ${timer._id} at $currentTime " | |
390 "(active?: ${timer.isActive})"); | |
391 if (!timer.isActive) continue; | |
392 if (timer._isPeriodic) { | |
393 schedule(currentTime + timer._duration.inMilliseconds, timer); | |
394 } | |
395 item.timer._run(); | |
396 } | |
397 } | |
398 | |
399 SimulatedTimer createSimulatedOneShotTimer( | |
400 SingleShotTimerTaskSpecification spec, Zone zone) { | |
401 var timer = new SimulatedTimer(zone, spec.duration, spec.callback, false); | |
402 schedule(currentTime + spec.duration.inMilliseconds, timer); | |
403 return timer; | |
404 } | |
405 | |
406 SimulatedTimer createSimulatedPeriodicTimer( | |
407 PeriodicTimerTaskSpecification spec, Zone zone) { | |
408 var timer = new SimulatedTimer(zone, spec.duration, spec.callback, true); | |
409 schedule(currentTime + spec.duration.inMilliseconds, timer); | |
410 return timer; | |
411 } | |
412 | |
413 Object createSimulatedTaskHandler(Zone self, ZoneDelegate parent, Zone zone, | |
414 TaskCreate create, TaskSpecification specification) { | |
415 var taskMap = self['taskMap']; | |
416 var taskIdMap = self['taskIdMap']; | |
417 if (specification is SingleShotTimerTaskSpecification) { | |
418 log.add("create enter " | |
419 "zone: ${self['name']} " | |
420 "spec-duration: ${specification.duration} " | |
421 "spec-oneshot?: ${specification.isOneShot}"); | |
422 var result = | |
423 parent.createTask(zone, createSimulatedOneShotTimer, specification); | |
424 log.add("create leave"); | |
425 return result; | |
426 } | |
427 if (specification is PeriodicTimerTaskSpecification) { | |
428 log.add("create enter " | |
429 "zone: ${self['name']} " | |
430 "spec-duration: ${specification.duration} " | |
431 "spec-oneshot?: ${specification.isOneShot}"); | |
432 var result = | |
433 parent.createTask(zone, createSimulatedPeriodicTimer, specification); | |
434 log.add("create leave"); | |
435 return result; | |
436 } | |
437 return parent.createTask(zone, create, specification); | |
438 } | |
439 | |
440 runZoned(() { | |
441 Timer.run(() { | |
442 log.add("running Timer.run"); | |
443 }); | |
444 | |
445 var timer0; | |
446 | |
447 new Timer(const Duration(milliseconds: 10), () { | |
448 log.add("running Timer(10)"); | |
449 timer0.cancel(); | |
450 log.add("canceled timer0"); | |
451 }); | |
452 | |
453 timer0 = new Timer(const Duration(milliseconds: 15), () { | |
454 log.add("running Timer(15)"); | |
455 }); | |
456 | |
457 var counter1 = 0; | |
458 new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) { | |
459 log.add("running periodic timer $counter1"); | |
460 if (counter1++ > 1) { | |
461 timer.cancel(); | |
462 } | |
463 }); | |
464 }, | |
465 zoneSpecification: | |
466 new ZoneSpecification(createTask: createSimulatedTaskHandler)); | |
467 | |
468 runQueue(); | |
469 | |
470 Expect.listEquals([ | |
471 'create enter zone: null spec-duration: 0:00:00.000000 spec-oneshot?: true', | |
472 'scheduling timer 0 for 0', | |
473 'create leave', | |
474 'create enter zone: null spec-duration: 0:00:00.010000 spec-oneshot?: true', | |
475 'scheduling timer 1 for 10', | |
476 'create leave', | |
477 'create enter zone: null spec-duration: 0:00:00.015000 spec-oneshot?: true', | |
478 'scheduling timer 2 for 15', | |
479 'create leave', | |
480 'create enter zone: null spec-duration: 0:00:00.005000 ' | |
481 'spec-oneshot?: false', | |
482 'scheduling timer 3 for 5', | |
483 'create leave', | |
484 'running timer 0 at 1 (active?: true)', | |
485 'running Timer.run', | |
486 'running timer 3 at 5 (active?: true)', | |
487 'scheduling timer 3 for 10', | |
488 'running periodic timer 0', | |
489 'running timer 1 at 10 (active?: true)', | |
490 'running Timer(10)', | |
491 'canceled timer0', | |
492 'running timer 3 at 11 (active?: true)', | |
493 'scheduling timer 3 for 16', | |
494 'running periodic timer 1', | |
495 'running timer 2 at 15 (active?: false)', | |
496 'running timer 3 at 16 (active?: true)', | |
497 'scheduling timer 3 for 21', | |
498 'running periodic timer 2', | |
499 'running timer 3 at 21 (active?: false)' | |
500 ], log); | |
501 log.clear(); | |
502 } | |
503 | |
504 runTests() async { | |
505 await testTimerTask(); | |
506 await testTimerTask2(); | |
507 testSimulatedTimer(); | |
508 } | |
509 | |
510 main() { | |
511 asyncStart(); | |
512 runTests().then((_) { | |
513 asyncEnd(); | |
514 }); | |
515 } | |
OLD | NEW |