| Index: tests/lib/async/zone_timer_task_test.dart
|
| diff --git a/tests/lib/async/zone_timer_task_test.dart b/tests/lib/async/zone_timer_task_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..310f7ca510a5b6fa5ecf0ac24247e712a11240aa
|
| --- /dev/null
|
| +++ b/tests/lib/async/zone_timer_task_test.dart
|
| @@ -0,0 +1,515 @@
|
| +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +// Tests timer tasks.
|
| +
|
| +import 'package:expect/expect.dart';
|
| +import 'package:async_helper/async_helper.dart';
|
| +import 'dart:async';
|
| +import 'dart:collection';
|
| +
|
| +class MyTimerSpecification implements SingleShotTimerTaskSpecification {
|
| + final Function callback;
|
| + final Duration duration;
|
| +
|
| + MyTimerSpecification(this.callback, this.duration);
|
| +
|
| + bool get isOneShot => true;
|
| + String get name => "test.timer-override";
|
| +}
|
| +
|
| +class MyPeriodicTimerSpecification implements PeriodicTimerTaskSpecification {
|
| + final Function callback;
|
| + final Duration duration;
|
| +
|
| + MyPeriodicTimerSpecification(this.callback, this.duration);
|
| +
|
| + bool get isOneShot => true;
|
| + String get name => "test.periodic-timer-override";
|
| +}
|
| +
|
| +/// Makes sure things are working in a simple setting.
|
| +/// No interceptions, changes, ...
|
| +Future testTimerTask() {
|
| + List log = [];
|
| +
|
| + var testCompleter = new Completer();
|
| + asyncStart();
|
| +
|
| + int taskIdCounter = 0;
|
| +
|
| + Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
|
| + TaskCreate create, TaskSpecification specification) {
|
| + var taskMap = self['taskMap'];
|
| + var taskIdMap = self['taskIdMap'];
|
| + if (specification is SingleShotTimerTaskSpecification) {
|
| + log.add("create enter "
|
| + "zone: ${self['name']} "
|
| + "spec-duration: ${specification.duration} "
|
| + "spec-oneshot?: ${specification.isOneShot}");
|
| + var result = parent.createTask(zone, create, specification);
|
| + taskMap[result] = specification;
|
| + taskIdMap[specification] = taskIdCounter++;
|
| + log.add("create leave");
|
| + return result;
|
| + } else if (specification is PeriodicTimerTaskSpecification) {
|
| + log.add("create enter "
|
| + "zone: ${self['name']} "
|
| + "spec-duration: ${specification.duration} "
|
| + "spec-oneshot?: ${specification.isOneShot}");
|
| + var result = parent.createTask(zone, create, specification);
|
| + taskMap[result] = specification;
|
| + taskIdMap[specification] = taskIdCounter++;
|
| + log.add("create leave");
|
| + return result;
|
| + }
|
| + return parent.createTask(zone, create, specification);
|
| + }
|
| +
|
| + void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
|
| + Object task, Object arg) {
|
| + var taskMap = self['taskMap'];
|
| + var taskIdMap = self['taskIdMap'];
|
| + if (taskMap.containsKey(task)) {
|
| + var spec = taskMap[task];
|
| + log.add("run enter "
|
| + "zone: ${self['name']} "
|
| + "task-id: ${taskIdMap[spec]} "
|
| + "arg: $arg");
|
| + parent.runTask(zone, run, task, arg);
|
| + log.add("run leave");
|
| + return;
|
| + }
|
| + parent.runTask(zone, run, task, arg);
|
| + }
|
| +
|
| + runZoned(() async {
|
| + var completer0 = new Completer();
|
| + Timer.run(() {
|
| + completer0.complete("done");
|
| + });
|
| + await completer0.future;
|
| +
|
| + Expect.listEquals([
|
| + 'create enter zone: custom zone spec-duration: 0:00:00.000000 '
|
| + 'spec-oneshot?: true',
|
| + 'create leave',
|
| + 'run enter zone: custom zone task-id: 0 arg: null',
|
| + 'run leave'
|
| + ], log);
|
| + log.clear();
|
| +
|
| + var completer1 = new Completer();
|
| + var counter1 = 0;
|
| + new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
|
| + if (counter1++ > 1) {
|
| + timer.cancel();
|
| + completer1.complete("done");
|
| + }
|
| + });
|
| + await completer1.future;
|
| +
|
| + Expect.listEquals([
|
| + 'create enter zone: custom zone spec-duration: 0:00:00.005000 '
|
| + 'spec-oneshot?: false',
|
| + 'create leave',
|
| + 'run enter zone: custom zone task-id: 1 arg: null',
|
| + 'run leave',
|
| + 'run enter zone: custom zone task-id: 1 arg: null',
|
| + 'run leave',
|
| + 'run enter zone: custom zone task-id: 1 arg: null',
|
| + 'run leave'
|
| + ], log);
|
| + log.clear();
|
| +
|
| + testCompleter.complete("done");
|
| + asyncEnd();
|
| + },
|
| + zoneValues: {'name': 'custom zone', 'taskMap': {}, 'taskIdMap': {}},
|
| + zoneSpecification: new ZoneSpecification(
|
| + createTask: createTaskHandler,
|
| + runTask: runTaskHandler));
|
| +
|
| + return testCompleter.future;
|
| +}
|
| +
|
| +/// More complicated zone, that intercepts...
|
| +Future testTimerTask2() {
|
| + List log = [];
|
| +
|
| + var testCompleter = new Completer();
|
| + asyncStart();
|
| +
|
| + int taskIdCounter = 0;
|
| +
|
| + Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
|
| + TaskCreate create, TaskSpecification specification) {
|
| + var taskMap = self['taskMap'];
|
| + var taskIdMap = self['taskIdMap'];
|
| + if (specification is SingleShotTimerTaskSpecification) {
|
| + log.add("create enter "
|
| + "zone: ${self['name']} "
|
| + "spec-duration: ${specification.duration} "
|
| + "spec-oneshot?: ${specification.isOneShot}");
|
| + var mySpec = new MyTimerSpecification(specification.callback,
|
| + specification.duration + const Duration(milliseconds: 2));
|
| + var result = parent.createTask(zone, create, mySpec);
|
| + taskMap[result] = specification;
|
| + taskIdMap[specification] = taskIdCounter++;
|
| + log.add("create leave");
|
| + return result;
|
| + } else if (specification is PeriodicTimerTaskSpecification) {
|
| + log.add("create enter "
|
| + "zone: ${self['name']} "
|
| + "spec-duration: ${specification.duration} "
|
| + "spec-oneshot?: ${specification.isOneShot}");
|
| + var mySpec = new MyPeriodicTimerSpecification(specification.callback,
|
| + specification.duration + const Duration(milliseconds: 2));
|
| + var result = parent.createTask(zone, create, specification);
|
| + taskMap[result] = specification;
|
| + taskIdMap[specification] = taskIdCounter++;
|
| + log.add("create leave");
|
| + return result;
|
| + }
|
| + return parent.createTask(zone, create, specification);
|
| + }
|
| +
|
| + void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
|
| + Object task, Object arg) {
|
| + var taskMap = self['taskMap'];
|
| + var taskIdMap = self['taskIdMap'];
|
| + if (taskMap.containsKey(task)) {
|
| + var spec = taskMap[task];
|
| + log.add("run enter "
|
| + "zone: ${self['name']} "
|
| + "task-id: ${taskIdMap[spec]} "
|
| + "arg: $arg");
|
| + parent.runTask(zone, run, task, arg);
|
| + log.add("run leave");
|
| + return;
|
| + }
|
| + parent.runTask(zone, run, task, arg);
|
| + }
|
| +
|
| + runZoned(() async {
|
| + var completer0 = new Completer();
|
| + Timer.run(() {
|
| + completer0.complete("done");
|
| + });
|
| + await completer0.future;
|
| +
|
| + // No visible change (except for the zone name) in the log, compared to the
|
| + // simple invocations.
|
| + Expect.listEquals([
|
| + 'create enter zone: outer-zone spec-duration: 0:00:00.000000 '
|
| + 'spec-oneshot?: true',
|
| + 'create leave',
|
| + 'run enter zone: outer-zone task-id: 0 arg: null',
|
| + 'run leave'
|
| + ], log);
|
| + log.clear();
|
| +
|
| + var completer1 = new Completer();
|
| + var counter1 = 0;
|
| + new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
|
| + if (counter1++ > 1) {
|
| + timer.cancel();
|
| + completer1.complete("done");
|
| + }
|
| + });
|
| + await completer1.future;
|
| +
|
| + // No visible change (except for the zone nome) in the log, compared to the
|
| + // simple invocations.
|
| + Expect.listEquals([
|
| + 'create enter zone: outer-zone spec-duration: 0:00:00.005000 '
|
| + 'spec-oneshot?: false',
|
| + 'create leave',
|
| + 'run enter zone: outer-zone task-id: 1 arg: null',
|
| + 'run leave',
|
| + 'run enter zone: outer-zone task-id: 1 arg: null',
|
| + 'run leave',
|
| + 'run enter zone: outer-zone task-id: 1 arg: null',
|
| + 'run leave'
|
| + ], log);
|
| + log.clear();
|
| +
|
| + var nestedCompleter = new Completer();
|
| +
|
| + runZoned(() async {
|
| + var completer0 = new Completer();
|
| + Timer.run(() {
|
| + completer0.complete("done");
|
| + });
|
| + await completer0.future;
|
| +
|
| + // The outer zone sees the duration change of the inner zone.
|
| + Expect.listEquals([
|
| + 'create enter zone: inner-zone spec-duration: 0:00:00.000000 '
|
| + 'spec-oneshot?: true',
|
| + 'create enter zone: outer-zone spec-duration: 0:00:00.002000 '
|
| + 'spec-oneshot?: true',
|
| + 'create leave',
|
| + 'create leave',
|
| + 'run enter zone: inner-zone task-id: 3 arg: null',
|
| + 'run enter zone: outer-zone task-id: 2 arg: null',
|
| + 'run leave',
|
| + 'run leave'
|
| + ], log);
|
| + log.clear();
|
| +
|
| + var completer1 = new Completer();
|
| + var counter1 = 0;
|
| + new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
|
| + if (counter1++ > 1) {
|
| + timer.cancel();
|
| + completer1.complete("done");
|
| + }
|
| + });
|
| + await completer1.future;
|
| +
|
| + // The outer zone sees the duration change of the inner zone.
|
| + Expect.listEquals([
|
| + 'create enter zone: inner-zone spec-duration: 0:00:00.005000 '
|
| + 'spec-oneshot?: false',
|
| + 'create enter zone: outer-zone spec-duration: 0:00:00.005000 '
|
| + 'spec-oneshot?: false',
|
| + 'create leave',
|
| + 'create leave',
|
| + 'run enter zone: inner-zone task-id: 5 arg: null',
|
| + 'run enter zone: outer-zone task-id: 4 arg: null',
|
| + 'run leave',
|
| + 'run leave',
|
| + 'run enter zone: inner-zone task-id: 5 arg: null',
|
| + 'run enter zone: outer-zone task-id: 4 arg: null',
|
| + 'run leave',
|
| + 'run leave',
|
| + 'run enter zone: inner-zone task-id: 5 arg: null',
|
| + 'run enter zone: outer-zone task-id: 4 arg: null',
|
| + 'run leave',
|
| + 'run leave'
|
| + ], log);
|
| + log.clear();
|
| +
|
| + nestedCompleter.complete("done");
|
| + },
|
| + zoneValues: {'name': 'inner-zone', 'taskMap': {}, 'taskIdMap': {}},
|
| + zoneSpecification: new ZoneSpecification(
|
| + createTask: createTaskHandler,
|
| + runTask: runTaskHandler));
|
| +
|
| + await nestedCompleter.future;
|
| + testCompleter.complete("done");
|
| + asyncEnd();
|
| + },
|
| + zoneValues: {'name': 'outer-zone', 'taskMap': {}, 'taskIdMap': {}},
|
| + zoneSpecification: new ZoneSpecification(
|
| + createTask: createTaskHandler,
|
| + runTask: runTaskHandler));
|
| +
|
| + return testCompleter.future;
|
| +}
|
| +
|
| +class TimerEntry {
|
| + final int time;
|
| + final SimulatedTimer timer;
|
| +
|
| + TimerEntry(this.time, this.timer);
|
| +}
|
| +
|
| +class SimulatedTimer implements Timer {
|
| + static int _idCounter = 0;
|
| +
|
| + Zone _zone;
|
| + final int _id = _idCounter++;
|
| + final Duration _duration;
|
| + final Function _callback;
|
| + final bool _isPeriodic;
|
| + bool _isActive = true;
|
| +
|
| + SimulatedTimer(this._zone, this._duration, this._callback, this._isPeriodic);
|
| +
|
| + bool get isActive => _isActive;
|
| +
|
| + void cancel() {
|
| + _isActive = false;
|
| + }
|
| +
|
| + void _run() {
|
| + if (!isActive) return;
|
| + _zone.runTask(_runTimer, this, null);
|
| + }
|
| +
|
| + static void _runTimer(SimulatedTimer timer, _) {
|
| + if (timer._isPeriodic) {
|
| + timer._callback(timer);
|
| + } else {
|
| + timer._callback();
|
| + }
|
| + }
|
| +}
|
| +
|
| +testSimulatedTimer() {
|
| + List log = [];
|
| +
|
| + var currentTime = 0;
|
| + // Using a simple list as queue. Not very efficient, but the test has only
|
| + // very few timers running at the same time.
|
| + var queue = new DoubleLinkedQueue<TimerEntry>();
|
| +
|
| + // Schedules the given callback at now + duration.
|
| + void schedule(int scheduledTime, SimulatedTimer timer) {
|
| + log.add("scheduling timer ${timer._id} for $scheduledTime");
|
| + if (queue.isEmpty) {
|
| + queue.add(new TimerEntry(scheduledTime, timer));
|
| + } else {
|
| + DoubleLinkedQueueEntry current = queue.firstEntry();
|
| + while (current != null) {
|
| + if (current.element.time <= scheduledTime) {
|
| + current = current.nextEntry();
|
| + } else {
|
| + current.prepend(new TimerEntry(scheduledTime, timer));
|
| + break;
|
| + }
|
| + }
|
| + if (current == null) {
|
| + queue.add(new TimerEntry(scheduledTime, timer));
|
| + }
|
| + }
|
| + }
|
| +
|
| + void runQueue() {
|
| + while (queue.isNotEmpty) {
|
| + var item = queue.removeFirst();
|
| + // If multiple callbacks were scheduled at the same time, increment the
|
| + // current time instead of staying at the same time.
|
| + currentTime = item.time > currentTime ? item.time : currentTime + 1;
|
| + SimulatedTimer timer = item.timer;
|
| + log.add("running timer ${timer._id} at $currentTime "
|
| + "(active?: ${timer.isActive})");
|
| + if (!timer.isActive) continue;
|
| + if (timer._isPeriodic) {
|
| + schedule(currentTime + timer._duration.inMilliseconds, timer);
|
| + }
|
| + item.timer._run();
|
| + }
|
| + }
|
| +
|
| + SimulatedTimer createSimulatedOneShotTimer(
|
| + SingleShotTimerTaskSpecification spec, Zone zone) {
|
| + var timer = new SimulatedTimer(zone, spec.duration, spec.callback, false);
|
| + schedule(currentTime + spec.duration.inMilliseconds, timer);
|
| + return timer;
|
| + }
|
| +
|
| + SimulatedTimer createSimulatedPeriodicTimer(
|
| + PeriodicTimerTaskSpecification spec, Zone zone) {
|
| + var timer = new SimulatedTimer(zone, spec.duration, spec.callback, true);
|
| + schedule(currentTime + spec.duration.inMilliseconds, timer);
|
| + return timer;
|
| + }
|
| +
|
| + Object createSimulatedTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
|
| + TaskCreate create, TaskSpecification specification) {
|
| + var taskMap = self['taskMap'];
|
| + var taskIdMap = self['taskIdMap'];
|
| + if (specification is SingleShotTimerTaskSpecification) {
|
| + log.add("create enter "
|
| + "zone: ${self['name']} "
|
| + "spec-duration: ${specification.duration} "
|
| + "spec-oneshot?: ${specification.isOneShot}");
|
| + var result =
|
| + parent.createTask(zone, createSimulatedOneShotTimer, specification);
|
| + log.add("create leave");
|
| + return result;
|
| + }
|
| + if (specification is PeriodicTimerTaskSpecification) {
|
| + log.add("create enter "
|
| + "zone: ${self['name']} "
|
| + "spec-duration: ${specification.duration} "
|
| + "spec-oneshot?: ${specification.isOneShot}");
|
| + var result =
|
| + parent.createTask(zone, createSimulatedPeriodicTimer, specification);
|
| + log.add("create leave");
|
| + return result;
|
| + }
|
| + return parent.createTask(zone, create, specification);
|
| + }
|
| +
|
| + runZoned(() {
|
| + Timer.run(() {
|
| + log.add("running Timer.run");
|
| + });
|
| +
|
| + var timer0;
|
| +
|
| + new Timer(const Duration(milliseconds: 10), () {
|
| + log.add("running Timer(10)");
|
| + timer0.cancel();
|
| + log.add("canceled timer0");
|
| + });
|
| +
|
| + timer0 = new Timer(const Duration(milliseconds: 15), () {
|
| + log.add("running Timer(15)");
|
| + });
|
| +
|
| + var counter1 = 0;
|
| + new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
|
| + log.add("running periodic timer $counter1");
|
| + if (counter1++ > 1) {
|
| + timer.cancel();
|
| + }
|
| + });
|
| + },
|
| + zoneSpecification:
|
| + new ZoneSpecification(createTask: createSimulatedTaskHandler));
|
| +
|
| + runQueue();
|
| +
|
| + Expect.listEquals([
|
| + 'create enter zone: null spec-duration: 0:00:00.000000 spec-oneshot?: true',
|
| + 'scheduling timer 0 for 0',
|
| + 'create leave',
|
| + 'create enter zone: null spec-duration: 0:00:00.010000 spec-oneshot?: true',
|
| + 'scheduling timer 1 for 10',
|
| + 'create leave',
|
| + 'create enter zone: null spec-duration: 0:00:00.015000 spec-oneshot?: true',
|
| + 'scheduling timer 2 for 15',
|
| + 'create leave',
|
| + 'create enter zone: null spec-duration: 0:00:00.005000 '
|
| + 'spec-oneshot?: false',
|
| + 'scheduling timer 3 for 5',
|
| + 'create leave',
|
| + 'running timer 0 at 1 (active?: true)',
|
| + 'running Timer.run',
|
| + 'running timer 3 at 5 (active?: true)',
|
| + 'scheduling timer 3 for 10',
|
| + 'running periodic timer 0',
|
| + 'running timer 1 at 10 (active?: true)',
|
| + 'running Timer(10)',
|
| + 'canceled timer0',
|
| + 'running timer 3 at 11 (active?: true)',
|
| + 'scheduling timer 3 for 16',
|
| + 'running periodic timer 1',
|
| + 'running timer 2 at 15 (active?: false)',
|
| + 'running timer 3 at 16 (active?: true)',
|
| + 'scheduling timer 3 for 21',
|
| + 'running periodic timer 2',
|
| + 'running timer 3 at 21 (active?: false)'
|
| + ], log);
|
| + log.clear();
|
| +}
|
| +
|
| +runTests() async {
|
| + await testTimerTask();
|
| + await testTimerTask2();
|
| + testSimulatedTimer();
|
| +}
|
| +
|
| +main() {
|
| + asyncStart();
|
| + runTests().then((_) {
|
| + asyncEnd();
|
| + });
|
| +}
|
|
|