Index: quiver/lib/testing/src/async/fake_async.dart |
diff --git a/quiver/lib/testing/src/async/fake_async.dart b/quiver/lib/testing/src/async/fake_async.dart |
deleted file mode 100644 |
index eb14769718481cd38685405ce21f0468640ea5bb..0000000000000000000000000000000000000000 |
--- a/quiver/lib/testing/src/async/fake_async.dart |
+++ /dev/null |
@@ -1,282 +0,0 @@ |
-// Copyright 2014 Google Inc. All Rights Reserved. |
-// |
-// Licensed under the Apache License, Version 2.0 (the "License"); |
-// you may not use this file except in compliance with the License. |
-// You may obtain a copy of the License at |
-// |
-// http://www.apache.org/licenses/LICENSE-2.0 |
-// |
-// Unless required by applicable law or agreed to in writing, software |
-// distributed under the License is distributed on an "AS IS" BASIS, |
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
-// See the License for the specific language governing permissions and |
-// limitations under the License. |
- |
-part of quiver.testing.async; |
- |
-/// A mechanism to make time-dependent units testable. |
-/// |
-/// Test code can be passed as a callback to [run], which causes it to be run in |
-/// a [Zone] which fakes timer and microtask creation, such that they are run |
-/// during calls to [elapse] which simulates the asynchronous passage of time. |
-/// |
-/// The synchronous passage of time (blocking or expensive calls) can also be |
-/// simulated using [elapseBlocking]. |
-/// |
-/// To allow the unit under test to tell time, it can receive a [Clock] as a |
-/// dependency, and default it to [const Clock()] in production, but then use |
-/// [clock] in test code. |
-/// |
-/// Example: |
-/// |
-/// test('testedFunc', () { |
-/// new FakeAsync().run((async) { |
-/// testedFunc(clock: async.getClock(initialTime)); |
-/// async.elapse(duration); |
-/// expect(...) |
-/// }); |
-/// }); |
-abstract class FakeAsync { |
- factory FakeAsync() = _FakeAsync; |
- |
- FakeAsync._(); |
- |
- /// Returns a fake [Clock] whose time can is elapsed by calls to [elapse] and |
- /// [elapseBlocking]. |
- /// |
- /// The returned clock starts at [initialTime], and calls to [elapse] and |
- /// [elapseBlocking] advance the clock, even if they occured before the call |
- /// to this method. |
- /// |
- /// The clock can be passed as a dependency to the unit under test. |
- Clock getClock(DateTime initialTime); |
- |
- /// Simulates the asynchronous passage of time. |
- /// |
- /// **This should only be called from within the zone used by [run].** |
- /// |
- /// If [duration] is negative, the returned future completes with an |
- /// [ArgumentError]. |
- /// |
- /// If a previous call to [elapse] has not yet completed, throws a |
- /// [StateError]. |
- /// |
- /// Any Timers created within the zone used by [run] which are to expire |
- /// at or before the new time after [duration] has elapsed are run. |
- /// The microtask queue is processed surrounding each timer. When a timer is |
- /// run, the [clock] will have been advanced by the timer's specified |
- /// duration. Calls to [elapseBlocking] from within these timers and |
- /// microtasks which cause the [clock] to elapse more than the specified |
- /// [duration], can cause more timers to expire and thus be called. |
- /// |
- /// Once all expired timers are processed, the [clock] is advanced (if |
- /// necessary) to the time this method was called + [duration]. |
- void elapse(Duration duration); |
- |
- /// Simulates the synchronous passage of time, resulting from blocking or |
- /// expensive calls. |
- /// |
- /// Neither timers nor microtasks are run during this call. Upon return, the |
- /// [clock] will have been advanced by [duration]. |
- /// |
- /// If [duration] is negative, throws an [ArgumentError]. |
- void elapseBlocking(Duration duration); |
- |
- /// Runs [callback] in a [Zone] with fake timer and microtask scheduling. |
- /// |
- /// Uses |
- /// [ZoneSpecification.createTimer], [ZoneSpecification.createPeriodicTimer], |
- /// and [ZoneSpecification.scheduleMicrotask] to store callbacks for later |
- /// execution within the zone via calls to [elapse]. |
- /// |
- /// [callback] is called with `this` as argument. |
- run(callback(FakeAsync self)); |
- |
- /// Runs all remaining microtasks, including those scheduled as a result of |
- /// running them, until there are no more microtasks scheduled. |
- /// |
- /// Does not run timers. |
- void flushMicrotasks(); |
- |
- /// Runs all timers until no timers remain (subject to [flushPeriodicTimers] |
- /// option), including those scheduled as a result of running them. |
- /// |
- /// [timeout] lets you set the maximum amount of time the flushing will take. |
- /// Throws a [StateError] if the [timeout] is exceeded. The default timeout |
- /// is 1 hour. [timeout] is relative to the elapsed time. |
- void flushTimers({Duration timeout: const Duration(hours: 1), |
- bool flushPeriodicTimers: true}); |
- |
- /// The number of created periodic timers that have not been canceled. |
- int get periodicTimerCount; |
- |
- /// The number of pending non periodic timers that have not been canceled. |
- int get nonPeriodicTimerCount; |
- |
- /// The number of pending microtasks. |
- int get microtaskCount; |
-} |
- |
-class _FakeAsync extends FakeAsync { |
- Duration _elapsed = Duration.ZERO; |
- Duration _elapsingTo; |
- Queue<Function> _microtasks = new Queue(); |
- Set<_FakeTimer> _timers = new Set<_FakeTimer>(); |
- |
- _FakeAsync() : super._() { |
- _elapsed; |
- } |
- |
- @override |
- Clock getClock(DateTime initialTime) => |
- new Clock(() => initialTime.add(_elapsed)); |
- |
- @override |
- void elapse(Duration duration) { |
- if (duration.inMicroseconds < 0) { |
- throw new ArgumentError('Cannot call elapse with negative duration'); |
- } |
- if (_elapsingTo != null) { |
- throw new StateError('Cannot elapse until previous elapse is complete.'); |
- } |
- _elapsingTo = _elapsed + duration; |
- _drainTimersWhile((_FakeTimer next) => next._nextCall <= _elapsingTo); |
- _elapseTo(_elapsingTo); |
- _elapsingTo = null; |
- } |
- |
- @override |
- void elapseBlocking(Duration duration) { |
- if (duration.inMicroseconds < 0) { |
- throw new ArgumentError('Cannot call elapse with negative duration'); |
- } |
- _elapsed += duration; |
- if (_elapsingTo != null && _elapsed > _elapsingTo) { |
- _elapsingTo = _elapsed; |
- } |
- } |
- |
- @override |
- void flushMicrotasks() { |
- _drainMicrotasks(); |
- } |
- |
- @override |
- void flushTimers({Duration timeout: const Duration(hours: 1), |
- bool flushPeriodicTimers: true}) { |
- final absoluteTimeout = _elapsed + timeout; |
- _drainTimersWhile((_FakeTimer timer) { |
- if (timer._nextCall > absoluteTimeout) { |
- throw new StateError( |
- 'Exceeded timeout ${timeout} while flushing timers'); |
- } |
- if (flushPeriodicTimers) { |
- return _timers.isNotEmpty; |
- } else { |
- // translation: keep draining while non-periodic timers exist |
- return _timers.any((_FakeTimer timer) => !timer._isPeriodic); |
- } |
- }); |
- } |
- |
- @override |
- run(callback(FakeAsync self)) { |
- if (_zone == null) { |
- _zone = Zone.current.fork(specification: _zoneSpec); |
- } |
- return _zone.runGuarded(() => callback(this)); |
- } |
- Zone _zone; |
- |
- @override |
- int get periodicTimerCount => |
- _timers.where((_FakeTimer timer) => timer._isPeriodic).length; |
- |
- @override |
- int get nonPeriodicTimerCount => |
- _timers.where((_FakeTimer timer) => !timer._isPeriodic).length; |
- |
- @override |
- int get microtaskCount => _microtasks.length; |
- |
- ZoneSpecification get _zoneSpec => new ZoneSpecification( |
- createTimer: (_, __, ___, Duration duration, Function callback) { |
- return _createTimer(duration, callback, false); |
- }, createPeriodicTimer: (_, __, ___, Duration duration, Function callback) { |
- return _createTimer(duration, callback, true); |
- }, scheduleMicrotask: (_, __, ___, Function microtask) { |
- _microtasks.add(microtask); |
- }); |
- |
- _drainTimersWhile(bool predicate(_FakeTimer)) { |
- _drainMicrotasks(); |
- _FakeTimer next; |
- while ((next = _getNextTimer()) != null && predicate(next)) { |
- _runTimer(next); |
- _drainMicrotasks(); |
- } |
- } |
- |
- _elapseTo(Duration to) { |
- if (to > _elapsed) { |
- _elapsed = to; |
- } |
- } |
- |
- Timer _createTimer(Duration duration, Function callback, bool isPeriodic) { |
- var timer = new _FakeTimer._(duration, callback, isPeriodic, this); |
- _timers.add(timer); |
- return timer; |
- } |
- |
- _FakeTimer _getNextTimer() { |
- return min(_timers, |
- (timer1, timer2) => timer1._nextCall.compareTo(timer2._nextCall)); |
- } |
- |
- _runTimer(_FakeTimer timer) { |
- assert(timer.isActive); |
- _elapseTo(timer._nextCall); |
- if (timer._isPeriodic) { |
- timer._callback(timer); |
- timer._nextCall += timer._duration; |
- } else { |
- timer._callback(); |
- _timers.remove(timer); |
- } |
- } |
- |
- _drainMicrotasks() { |
- while (_microtasks.isNotEmpty) { |
- _microtasks.removeFirst()(); |
- } |
- } |
- |
- _hasTimer(_FakeTimer timer) => _timers.contains(timer); |
- |
- _cancelTimer(_FakeTimer timer) => _timers.remove(timer); |
-} |
- |
-class _FakeTimer implements Timer { |
- final Duration _duration; |
- final Function _callback; |
- final bool _isPeriodic; |
- final _FakeAsync _time; |
- Duration _nextCall; |
- |
- // TODO: In browser JavaScript, timers can only run every 4 milliseconds once |
- // sufficiently nested: |
- // http://www.w3.org/TR/html5/webappapis.html#timer-nesting-level |
- // Without some sort of delay this can lead to infinitely looping timers. |
- // What do the dart VM and dart2js timers do here? |
- static const _minDuration = Duration.ZERO; |
- |
- _FakeTimer._(Duration duration, this._callback, this._isPeriodic, this._time) |
- : _duration = duration < _minDuration ? _minDuration : duration { |
- _nextCall = _time._elapsed + _duration; |
- } |
- |
- bool get isActive => _time._hasTimer(this); |
- |
- cancel() => _time._cancelTimer(this); |
-} |