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

Side by Side Diff: pkg/scheduled_test/lib/scheduled_stream.dart

Issue 119673002: Add a ScheduledStream class and some stream matchers. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 6 years, 11 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, 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 library scheduled_test.scheduled_stream;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'package:stack_trace/stack_trace.dart';
11
12 import 'scheduled_test.dart';
13 import 'src/stream_matcher.dart';
14 import 'src/utils.dart';
15
16 export 'src/stream_matcher.dart';
17
18 /// A wrapper for streams that supports a pull-based model of retrieving values
19 /// as well as a set of [StreamMatcher]s for testing the values emitted by the
20 /// stream.
21 ///
22 /// The only method on [ScheduledStream] that's actually scheduled is [expect],
23 /// which is the method that users testing streaming code are most likely to
24 /// want to use.
25 class ScheduledStream<T> {
26 /// The underlying stream.
27 final Stream<T> _stream;
28
29 /// The subscription to [_stream].
30 StreamSubscription<T> _subscription;
31
32 /// The completer for emitting a value requested by [next].
33 ///
34 /// If this is non-null, [_pendingValues] will always be empty, since any
35 /// value coming in will be passed to this completer.
36 Completer<T> _nextCompleter;
37
38 /// The completer for emitting a value requested by [hasNext].
39 Completer<bool> _hasNextCompleter;
40
41 /// The set of all streams forked from this one.
42 final _forks = new Set<ScheduledStream<T>>();
43
44 /// The queue of values emitted by [_stream] but not yet emitted through
45 /// [next].
46 final _pendingValues = new Queue<FallibleValue<T>>();
47
48 /// All values emitted by this stream so far.
49 ///
50 /// This does not include values emitted by the underlying stream but not yet
51 /// emitted through [next].
52 List<T> get emittedValues => new UnmodifiableListView(_emittedValues);
53 final _emittedValues = new List<T>();
54
55 /// All values emitted by the underlying stream.
56 ///
57 /// This is intended primarily for providing debugging information.
58 List<T> get allValues {
59 var list = new List<T>.from(_emittedValues);
60 list.addAll(_pendingValues.where((value) => value.hasValue)
61 .map((value) => value.value));
62 return new UnmodifiableListView(list);
63 }
64
65 /// Whether the wrapped stream has been closed.
66 bool _isDone = false;
67
68 /// Whether [next] has been called but has not yet returned.
69 ///
70 /// This is distinct from `_nextCompleter != null` when [next] is called while
71 /// there are pending values available, until the future it returns completes.
72 bool _isNextPending = false;
73
74 /// Creates a new scheduled stream wrapping [stream].
75 ScheduledStream(Stream<T> stream)
76 : _stream = stream.asBroadcastStream() {
77 _subscription = _stream.listen((value) {
78 if (_hasNextCompleter != null) {
79 _hasNextCompleter.complete(true);
80 _hasNextCompleter = null;
81 }
82
83 if (_nextCompleter != null) {
84 _nextCompleter.complete(value);
85 _emittedValues.add(value);
86 _nextCompleter = null;
87 _isNextPending = false;
88 } else {
89 _pendingValues.add(new FallibleValue.withValue(value));
90 }
91 }, onError: (error, stackTrace) {
92 if (_hasNextCompleter != null) {
93 _hasNextCompleter.completeError(error, stackTrace);
94 _hasNextCompleter = null;
95 }
96
97 if (_nextCompleter != null) {
98 _nextCompleter.completeError(error, stackTrace);
99 _nextCompleter = null;
100 } else {
101 _pendingValues.add(new FallibleValue.withError(error, stackTrace));
102 }
103 }, onDone: _onDone);
104 }
105
106 /// Enqueue an expectation that [streamMatcher] will match the value(s)
107 /// emitted by the stream at this point in the schedule.
108 ///
109 /// If [streamMatcher] is a [StreamMatcher], it will match the stream as a
110 /// whole. If it's a [Matcher] or another object, it will match the next value
111 /// emitted by the stream (as though it were a [nextValue] matcher).
112 ///
113 /// This call is scheduled; the expectation won't be added until the schedule
114 /// reaches this point, and the schedule won't continue until the matcher has
115 /// matched the stream.
116 void expect(streamMatcher) {
117 streamMatcher = new StreamMatcher.wrap(streamMatcher);
118 var description = 'stream emits $streamMatcher';
119 schedule(() {
120 return streamMatcher.tryMatch(this).then((description) {
121 if (description == null) return;
122
123 var expected = prefixLines(streamMatcher.toString(),
124 firstPrefix: 'Expected: ',
125 prefix: ' | ');
126
127 var actual = prefixLines(allValues.map((value) {
128 return prefixLines(value.toString(), firstPrefix: '* ');
129 }).join('\n'),
130 firstPrefix: ' Emitted: ',
131 prefix: ' ');
132
133 var which = '';
134 if (description.length > 0) {
135 which = '\n' + prefixLines(description.toString(),
136 firstPrefix: ' Which: ',
137 prefix: ' | ');
138 }
139
140 fail("$expected\n$actual$which");
141 });
142 }, description);
143 }
144
145 /// Returns a Future that completes to the next value emitted by this stream.
146 ///
147 /// It's a [StateError] to call [next] when another call's Future has not yet
148 /// completed, or when the stream has no more values. The latter can be
149 /// checked using [hasNext].
150 Future<T> next() {
151 if (_isNextPending) {
152 return new Future.error(
153 new StateError("There's already a pending call to "
154 "ScheduledStream.next."),
155 new Chain.current());
156 }
157
158 if (_pendingValues.isNotEmpty) {
159 _isNextPending = true;
160
161 return syncFuture(() {
162 var valueOrError = _pendingValues.removeFirst();
163 if (valueOrError.hasValue) {
164 _emittedValues.add(valueOrError.value);
165 return valueOrError.value;
166 } else {
167 return new Chain.track(new Future.error(
168 valueOrError.error, valueOrError.stackTrace));
169 }
170 }).whenComplete(() {
171 _isNextPending = false;
172 });
173 } else if (_isDone) {
174 return new Future.error(
175 new StateError("ScheduledStream has no more elements."),
176 new Chain.current());
177 }
178
179 _isNextPending = true;
180 _nextCompleter = new Completer();
181 return _nextCompleter.future;
182 }
183
184 /// Returns a Future that completes to a boolean indicating whether the stream
185 /// has additional values or not.
186 Future<bool> get hasNext {
187 if (_hasNextCompleter != null) return _hasNextCompleter.future;
188
189 if (_pendingValues.isNotEmpty) {
190 var valueOrError = _pendingValues.first;
191 if (valueOrError.hasValue) return new Future.value(true);
192 return new Future.error(valueOrError.error, valueOrError.stackTrace);
Bob Nystrom 2014/01/08 00:47:40 Given how nicely it maps 1-1, you could just add a
nweiz 2014/01/08 22:15:09 Done.
193 } else if (_isDone) {
194 return new Future.value(false);
195 }
196
197 _hasNextCompleter = new Completer();
198 return _hasNextCompleter.future;
199 }
200
201 /// Returns a fork of this stream.
202 ///
203 /// The fork begins at the same point [this] is at. Values can be read from it
204 /// without consuming values in [this]. If [this] is closed, the fork will be
205 /// closed at whatever point it's currently at.
206 ScheduledStream<T> fork() {
207 var controller = new StreamController<T>();
208 for (var valueOrError in _pendingValues) {
209 if (valueOrError.hasValue) {
210 controller.add(valueOrError.value);
211 } else {
212 controller.addError(valueOrError.error, valueOrError.stackTrace);
213 }
214 }
215
216 if (_isDone) {
217 controller.close();
218 } else {
219 _stream.pipe(controller);
220 }
221
222 var fork = new ScheduledStream<T>(controller.stream);
223 _forks.add(fork);
224 return fork;
225 }
226
227 /// Closes this stream.
228 ///
229 /// This cancels the subscription to the underlying stream and acts as though
230 /// [this] was closed immediately after the current position, regardless of
231 /// whether the underlying stream has emitted additional events.
232 void close() {
233 _subscription.cancel();
234 _pendingValues.clear();
235
236 for (var fork in _forks) {
237 fork.close();
238 }
239 _forks.clear();
240
241 if (!_isDone) _onDone();
242 }
243
244 /// Handles a "done" event from the underlying stream, as well as [this] being
245 /// closed.
246 void _onDone() {
247 if (_hasNextCompleter != null) {
248 _hasNextCompleter.complete(false);
249 _hasNextCompleter = null;
250 }
251
252 if (_nextCompleter != null) {
253 _nextCompleter.completeError(
254 new StateError("ScheduledStream has no more elements."),
255 new Chain.current());
256 _nextCompleter = null;
257 _isNextPending = false;
258 }
259
260 _isDone = true;
261 }
262 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698