Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: quiver/lib/testing/src/async/fake_async.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « quiver/lib/testing/src/async/async.dart ('k') | quiver/lib/testing/src/equality/equality.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 part of quiver.testing.async;
16
17 /// A mechanism to make time-dependent units testable.
18 ///
19 /// Test code can be passed as a callback to [run], which causes it to be run in
20 /// a [Zone] which fakes timer and microtask creation, such that they are run
21 /// during calls to [elapse] which simulates the asynchronous passage of time.
22 ///
23 /// The synchronous passage of time (blocking or expensive calls) can also be
24 /// simulated using [elapseBlocking].
25 ///
26 /// To allow the unit under test to tell time, it can receive a [Clock] as a
27 /// dependency, and default it to [const Clock()] in production, but then use
28 /// [clock] in test code.
29 ///
30 /// Example:
31 ///
32 /// test('testedFunc', () {
33 /// new FakeAsync().run((async) {
34 /// testedFunc(clock: async.getClock(initialTime));
35 /// async.elapse(duration);
36 /// expect(...)
37 /// });
38 /// });
39 abstract class FakeAsync {
40 factory FakeAsync() = _FakeAsync;
41
42 FakeAsync._();
43
44 /// Returns a fake [Clock] whose time can is elapsed by calls to [elapse] and
45 /// [elapseBlocking].
46 ///
47 /// The returned clock starts at [initialTime], and calls to [elapse] and
48 /// [elapseBlocking] advance the clock, even if they occured before the call
49 /// to this method.
50 ///
51 /// The clock can be passed as a dependency to the unit under test.
52 Clock getClock(DateTime initialTime);
53
54 /// Simulates the asynchronous passage of time.
55 ///
56 /// **This should only be called from within the zone used by [run].**
57 ///
58 /// If [duration] is negative, the returned future completes with an
59 /// [ArgumentError].
60 ///
61 /// If a previous call to [elapse] has not yet completed, throws a
62 /// [StateError].
63 ///
64 /// Any Timers created within the zone used by [run] which are to expire
65 /// at or before the new time after [duration] has elapsed are run.
66 /// The microtask queue is processed surrounding each timer. When a timer is
67 /// run, the [clock] will have been advanced by the timer's specified
68 /// duration. Calls to [elapseBlocking] from within these timers and
69 /// microtasks which cause the [clock] to elapse more than the specified
70 /// [duration], can cause more timers to expire and thus be called.
71 ///
72 /// Once all expired timers are processed, the [clock] is advanced (if
73 /// necessary) to the time this method was called + [duration].
74 void elapse(Duration duration);
75
76 /// Simulates the synchronous passage of time, resulting from blocking or
77 /// expensive calls.
78 ///
79 /// Neither timers nor microtasks are run during this call. Upon return, the
80 /// [clock] will have been advanced by [duration].
81 ///
82 /// If [duration] is negative, throws an [ArgumentError].
83 void elapseBlocking(Duration duration);
84
85 /// Runs [callback] in a [Zone] with fake timer and microtask scheduling.
86 ///
87 /// Uses
88 /// [ZoneSpecification.createTimer], [ZoneSpecification.createPeriodicTimer],
89 /// and [ZoneSpecification.scheduleMicrotask] to store callbacks for later
90 /// execution within the zone via calls to [elapse].
91 ///
92 /// [callback] is called with `this` as argument.
93 run(callback(FakeAsync self));
94
95 /// Runs all remaining microtasks, including those scheduled as a result of
96 /// running them, until there are no more microtasks scheduled.
97 ///
98 /// Does not run timers.
99 void flushMicrotasks();
100
101 /// Runs all timers until no timers remain (subject to [flushPeriodicTimers]
102 /// option), including those scheduled as a result of running them.
103 ///
104 /// [timeout] lets you set the maximum amount of time the flushing will take.
105 /// Throws a [StateError] if the [timeout] is exceeded. The default timeout
106 /// is 1 hour. [timeout] is relative to the elapsed time.
107 void flushTimers({Duration timeout: const Duration(hours: 1),
108 bool flushPeriodicTimers: true});
109
110 /// The number of created periodic timers that have not been canceled.
111 int get periodicTimerCount;
112
113 /// The number of pending non periodic timers that have not been canceled.
114 int get nonPeriodicTimerCount;
115
116 /// The number of pending microtasks.
117 int get microtaskCount;
118 }
119
120 class _FakeAsync extends FakeAsync {
121 Duration _elapsed = Duration.ZERO;
122 Duration _elapsingTo;
123 Queue<Function> _microtasks = new Queue();
124 Set<_FakeTimer> _timers = new Set<_FakeTimer>();
125
126 _FakeAsync() : super._() {
127 _elapsed;
128 }
129
130 @override
131 Clock getClock(DateTime initialTime) =>
132 new Clock(() => initialTime.add(_elapsed));
133
134 @override
135 void elapse(Duration duration) {
136 if (duration.inMicroseconds < 0) {
137 throw new ArgumentError('Cannot call elapse with negative duration');
138 }
139 if (_elapsingTo != null) {
140 throw new StateError('Cannot elapse until previous elapse is complete.');
141 }
142 _elapsingTo = _elapsed + duration;
143 _drainTimersWhile((_FakeTimer next) => next._nextCall <= _elapsingTo);
144 _elapseTo(_elapsingTo);
145 _elapsingTo = null;
146 }
147
148 @override
149 void elapseBlocking(Duration duration) {
150 if (duration.inMicroseconds < 0) {
151 throw new ArgumentError('Cannot call elapse with negative duration');
152 }
153 _elapsed += duration;
154 if (_elapsingTo != null && _elapsed > _elapsingTo) {
155 _elapsingTo = _elapsed;
156 }
157 }
158
159 @override
160 void flushMicrotasks() {
161 _drainMicrotasks();
162 }
163
164 @override
165 void flushTimers({Duration timeout: const Duration(hours: 1),
166 bool flushPeriodicTimers: true}) {
167 final absoluteTimeout = _elapsed + timeout;
168 _drainTimersWhile((_FakeTimer timer) {
169 if (timer._nextCall > absoluteTimeout) {
170 throw new StateError(
171 'Exceeded timeout ${timeout} while flushing timers');
172 }
173 if (flushPeriodicTimers) {
174 return _timers.isNotEmpty;
175 } else {
176 // translation: keep draining while non-periodic timers exist
177 return _timers.any((_FakeTimer timer) => !timer._isPeriodic);
178 }
179 });
180 }
181
182 @override
183 run(callback(FakeAsync self)) {
184 if (_zone == null) {
185 _zone = Zone.current.fork(specification: _zoneSpec);
186 }
187 return _zone.runGuarded(() => callback(this));
188 }
189 Zone _zone;
190
191 @override
192 int get periodicTimerCount =>
193 _timers.where((_FakeTimer timer) => timer._isPeriodic).length;
194
195 @override
196 int get nonPeriodicTimerCount =>
197 _timers.where((_FakeTimer timer) => !timer._isPeriodic).length;
198
199 @override
200 int get microtaskCount => _microtasks.length;
201
202 ZoneSpecification get _zoneSpec => new ZoneSpecification(
203 createTimer: (_, __, ___, Duration duration, Function callback) {
204 return _createTimer(duration, callback, false);
205 }, createPeriodicTimer: (_, __, ___, Duration duration, Function callback) {
206 return _createTimer(duration, callback, true);
207 }, scheduleMicrotask: (_, __, ___, Function microtask) {
208 _microtasks.add(microtask);
209 });
210
211 _drainTimersWhile(bool predicate(_FakeTimer)) {
212 _drainMicrotasks();
213 _FakeTimer next;
214 while ((next = _getNextTimer()) != null && predicate(next)) {
215 _runTimer(next);
216 _drainMicrotasks();
217 }
218 }
219
220 _elapseTo(Duration to) {
221 if (to > _elapsed) {
222 _elapsed = to;
223 }
224 }
225
226 Timer _createTimer(Duration duration, Function callback, bool isPeriodic) {
227 var timer = new _FakeTimer._(duration, callback, isPeriodic, this);
228 _timers.add(timer);
229 return timer;
230 }
231
232 _FakeTimer _getNextTimer() {
233 return min(_timers,
234 (timer1, timer2) => timer1._nextCall.compareTo(timer2._nextCall));
235 }
236
237 _runTimer(_FakeTimer timer) {
238 assert(timer.isActive);
239 _elapseTo(timer._nextCall);
240 if (timer._isPeriodic) {
241 timer._callback(timer);
242 timer._nextCall += timer._duration;
243 } else {
244 timer._callback();
245 _timers.remove(timer);
246 }
247 }
248
249 _drainMicrotasks() {
250 while (_microtasks.isNotEmpty) {
251 _microtasks.removeFirst()();
252 }
253 }
254
255 _hasTimer(_FakeTimer timer) => _timers.contains(timer);
256
257 _cancelTimer(_FakeTimer timer) => _timers.remove(timer);
258 }
259
260 class _FakeTimer implements Timer {
261 final Duration _duration;
262 final Function _callback;
263 final bool _isPeriodic;
264 final _FakeAsync _time;
265 Duration _nextCall;
266
267 // TODO: In browser JavaScript, timers can only run every 4 milliseconds once
268 // sufficiently nested:
269 // http://www.w3.org/TR/html5/webappapis.html#timer-nesting-level
270 // Without some sort of delay this can lead to infinitely looping timers.
271 // What do the dart VM and dart2js timers do here?
272 static const _minDuration = Duration.ZERO;
273
274 _FakeTimer._(Duration duration, this._callback, this._isPeriodic, this._time)
275 : _duration = duration < _minDuration ? _minDuration : duration {
276 _nextCall = _time._elapsed + _duration;
277 }
278
279 bool get isActive => _time._hasTimer(this);
280
281 cancel() => _time._cancelTimer(this);
282 }
OLDNEW
« no previous file with comments | « quiver/lib/testing/src/async/async.dart ('k') | quiver/lib/testing/src/equality/equality.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698