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 |