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

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: Created 7 years 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<Either<T, Pair<dynamic, StackTrace>>>();
Bob Nystrom 2013/12/21 00:11:34 Instead of Either<Pair>, how about a concrete type
nweiz 2014/01/07 03:16:39 Good idea. Done.
47
48 /// All values emitted by this stream so far.
Bob Nystrom 2013/12/21 00:11:34 This is confusing. "Emitted" implies a push model,
nweiz 2014/01/07 03:16:39 "Consumed" is also weird because it sounds like it
49 ///
50 /// This does not include values emitted by the underlying stream but not yet
51 /// emitted through [next].
52 List<T> get values => new UnmodifiableListView(_values);
53 final _values = 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 {
Bob Nystrom 2013/12/21 00:11:34 The distinction in names here is a bit ambiguous.
nweiz 2014/01/07 03:16:39 Done.
59 var list = new List<T>.from(_values);
60 list.addAll(_pendingValues.where((value) => value.isFirst)
61 .map((value) => value.first));
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 bool _nextPending = false;
Bob Nystrom 2013/12/21 00:11:34 How about _isNextPending? "_nextPending" sound to
nweiz 2014/01/07 03:16:39 Done.
70
71 /// Creates a new scheduled stream wrapping [stream].
72 ScheduledStream(Stream<T> stream)
73 : _stream = stream.asBroadcastStream() {
74 _subscription = _stream.listen((value) {
75 if (_hasNextCompleter != null) {
76 _hasNextCompleter.complete(true);
77 _hasNextCompleter = null;
78 }
79
80 if (_nextCompleter != null) {
81 _nextCompleter.complete(value);
82 _values.add(value);
83 _nextCompleter = null;
84 _nextPending = false;
85 } else {
86 _pendingValues.add(new Either.withFirst(value));
87 }
88 }, onError: (error, stackTrace) {
89 if (_hasNextCompleter != null) {
90 _hasNextCompleter.completeError(error, stackTrace);
91 _hasNextCompleter = null;
92 }
93
94 if (_nextCompleter != null) {
95 _nextCompleter.completeError(error, stackTrace);
96 _nextCompleter = null;
97 } else {
98 _pendingValues.add(new Either.withSecond(new Pair(error, stackTrace)));
99 }
100 }, onDone: _onDone);
101 }
102
103 /// Enqueue an expectation that [streamMatcher] will match the value(s)
104 /// emitted by the stream at this point in the schedule.
105 ///
106 /// If [streamMatcher] is a [StreamMatcher], it will match the stream as a
107 /// whole. If it's a [Matcher] or another object, it will match the next value
108 /// emitted by the stream (as though it were a [nextValue] matcher).
109 ///
110 /// This call is scheduled; the expectation won't be added until the schedule
111 /// reaches this point, and the schedule won't continue until the matcher has
112 /// matched the stream.
113 void expect(streamMatcher) {
114 streamMatcher = new StreamMatcher.wrap(streamMatcher);
115 var description = 'stream emits $streamMatcher';
116 schedule(() {
117 return streamMatcher.tryMatch(stream).then((description) {
118 if (description == null) return;
119
120 var expected = prefixLines(streamMatcher.toString(),
121 firstPrefix: 'Expected: ',
122 prefix: ' | ');
123
124 var actual = prefixLines(stream.allValues.map((value) {
125 return prefixLines(value.toString(), firstPrefix: '* ');
126 }).join('\n'),
127 firstPrefix: ' Emitted: ',
128 prefix: ' ');
129
130 var which = '';
131 if (description.length > 0) {
132 which = '\n' + prefixLines(description.toString(),
133 firstPrefix: ' Which: ',
134 prefix: ' | ');
135 }
136
137 fail("$expected\n$actual$which");
138 });
139 }, description);
140 }
141
142 /// Returns a Future that completes to the next value emitted by this stream.
143 ///
144 /// It's a [StateError] to call [next] when another call's Future has not yet
145 /// completed, or when the stream has no more values. The latter can be
146 /// checked using [hasNext].
147 Future<T> next() {
148 if (_nextPending) {
149 return new Future.error(
150 new StateError("There's already a pending call to "
151 "ScheduledStream.next."),
152 new Chain.current());
153 }
154
155 if (_pendingValues.isNotEmpty) {
156 _nextPending = true;
157
158 return _pendingValues.removeFirst().match((value) {
159 _values.add(value);
160 return new Future.value(value);
161 }, (pair) => Chain.track(new Future.error(pair.first, pair.last)))
162 .whenComplete(() {
Bob Nystrom 2013/12/21 00:11:34 It took me a long time to parse this and realize t
nweiz 2014/01/07 03:16:39 I changed this as part of replacing Either with Fa
163 _nextPending = false;
164 });
165 } else if (_isDone) {
166 return new Future.error(
167 new StateError("ScheduledStream has no more elements."),
168 new Chain.current());
169 }
170
171 _nextPending = true;
172 _nextCompleter = new Completer();
173 return _nextCompleter.future;
174 }
175
176 /// Returns a Future that completes to a boolean indicating whether the stream
177 /// has additional values or not.
178 Future<bool> get hasNext {
179 if (_hasNextCompleter != null) return _hasNextCompleter.future;
180
181 if (_pendingValues.isNotEmpty) {
182 return _pendingValues.first.match(
183 (value) => new Future.value(true),
184 (pair) => Chain.track(new Future.error(pair.first, pair.last)));
185 } else if (_isDone) {
186 return new Future.value(false);
187 }
188
189 _hasNextCompleter = new Completer();
190 return _hasNextCompleter.future;
191 }
192
193 /// Returns a fork of this stream.
194 ///
195 /// The fork begins at the same point [this] is at. Values can be read from it
196 /// without consuming values in [this]. If [this] is closed, the fork will be
197 /// closed at whatever point it's currently at.
198 ScheduledStream<T> fork() {
199 var controller = new StreamController<T>();
200 for (var value in _pendingValues) {
201 value.match(controller.add,
202 (pair) => controller.addError(pair.first, pair.last));
203 }
204
205 if (_isDone) {
206 controller.close();
207 } else {
208 _stream.pipe(controller);
209 }
210
211 var fork = new ScheduledStream<T>(controller.stream);
212 _forks.add(fork);
213 return fork;
214 }
215
216 /// Closes this stream.
217 ///
218 /// This cancels the subscription to the underlying stream and acts as though
219 /// [this] was closed immediately after the current position, regardless of
220 /// whether the underlying stream has emitted additional events.
221 void close() {
222 _subscription.cancel();
223 _pendingValues.clear();
224
225 for (var fork in _forks) {
226 fork.close();
227 }
Bob Nystrom 2013/12/21 00:11:34 Just to be nice to the GC, may as well clear _fork
nweiz 2014/01/07 03:16:39 Done.
228
229 if (!_isDone) _onDone();
230 }
231
232 /// Handles a "done" event from the underlying stream, as well as [this] being
233 /// closed.
234 void _onDone() {
235 if (_hasNextCompleter != null) {
236 _hasNextCompleter.complete(false);
237 _hasNextCompleter = null;
238 }
239
240 if (_nextCompleter != null) {
241 _nextCompleter.completeError(
242 new StateError("ScheduledStream has no more elements."),
243 new Chain.current());
244 _nextCompleter = null;
245 }
Bob Nystrom 2013/12/21 00:11:34 Probably want to clear _nextPending here too.
nweiz 2014/01/07 03:16:39 Done.
246
247 _isDone = true;
248 }
249 }
OLDNEW
« no previous file with comments | « no previous file | pkg/scheduled_test/lib/src/stream_matcher.dart » ('j') | pkg/scheduled_test/lib/src/stream_matcher.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698