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

Side by Side Diff: pkg/scheduled_test/lib/src/stream_matcher.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.stream_matcher;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import '../scheduled_stream.dart';
11 import '../scheduled_test.dart';
12 import 'utils.dart';
13
14 /// An abstract superclass for matchers that validate and consume zero or more
15 /// values emitted by a [ScheduledStream].
16 ///
17 /// [StreamMatcher]s are most commonly used by passing them to
18 /// [ScheduledStream.expect].
19 abstract class StreamMatcher {
20 /// Wrap a [Matcher], [StreamMatcher] or [Object] in a [StreamMatcher].
21 ///
22 /// If this isn't a [StreamMatcher], a [nextValue] matcher is used.
23 factory StreamMatcher.wrap(matcher) =>
24 matcher is StreamMatcher ? matcher : nextValue(matcher);
25
26 /// Tries to match [this] against [stream].
27 ///
28 /// If the match succeeds, this returns `null`. If it fails, this returns a
29 /// [Description] describing the failure.
30 Future<Description> tryMatch(ScheduledStream stream);
31
32 String toString();
33 }
34
35 /// A matcher that consumes and matches a single value.
36 ///
37 /// [matcher] can be a [Matcher] or an [Object], but not a [StreamMatcher].
38 StreamMatcher nextValue(matcher) => new _NextValueMatcher(matcher);
39
40 /// A matcher that consumes [n] values and matches a list containing those
41 /// objects against [matcher].
42 ///
43 /// [matcher] can be a [Matcher] or an [Object], but not a [StreamMatcher].
44 StreamMatcher nextValues(int n, matcher) => new _NextValuesMatcher(n, matcher);
45
46 /// A matcher that matches several sub-matchers in sequence.
47 ///
48 /// Each element of [streamMatchers] can be a [StreamMatcher], a [Matcher], or
49 /// an [Object].
50 StreamMatcher inOrder(Iterable streamMatchers) {
51 streamMatchers = streamMatchers.toList();
52 if (streamMatchers.length == 1) {
53 return new StreamMatcher.wrap(streamMatchers.first);
54 } else {
55 return new _InOrderMatcher(streamMatchers);
56 }
57 }
58
59 /// A matcher that consumes values emitted by a stream until one matching
60 /// [matcher] is emitted.
61 ///
62 /// This will fail if the stream never emits [matcher]
Bob Nystrom 2013/12/21 00:11:34 "[matcher]" -> "a value that matches [matcher]."
nweiz 2014/01/07 03:16:39 Done.
63 ///
64 /// [matcher] can be a [Matcher] or an [Object], but not a [StreamMatcher].
65 StreamMatcher consumeThrough(matcher) => new _ConsumeThroughMatcher(matcher);
Bob Nystrom 2013/12/21 00:11:34 How about "consumeUntil"?
nweiz 2014/01/07 03:16:39 That's what this was originally, but I wanted it t
66
67 /// A matcher that matches either [streamMatcher1], [streamMatcher2], or both.
68 ///
69 /// If both matchers match the stream, the one that consumed more values will be
70 /// used.
71 ///
72 /// Both [streamMatcher1] and [streamMatcher2] can be a [StreamMatcher], a
73 /// [Matcher], or an [Object].
74 StreamMatcher either(streamMatcher1, streamMatcher2) =>
Bob Nystrom 2013/12/21 00:11:34 My intuition is that "either" would not match both
nweiz 2014/01/07 03:16:39 I think it makes sense for "either" to match both
75 new _EitherMatcher(streamMatcher1, streamMatcher2);
76
77 /// A matcher that consumes [streamMatcher] if it matches, or nothing otherwise.
78 ///
79 /// This matcher will always match a stream. It exists to consume values that
80 /// may or may not be emitted by a stream.
81 ///
82 /// [streamMatcher] can be a [StreamMatcher], a [Matcher], or an [Object].
83 StreamMatcher allow(streamMatcher) => new _AllowMatcher(streamMatcher);
Bob Nystrom 2013/12/21 00:11:34 "skipWhile"?
nweiz 2014/01/07 03:16:39 That implies that it will skip more than one value
84
85 /// A matcher that matches a stream that emits no more values.
86 StreamMatcher get isDone => new _IsDoneMatcher();
87
88 /// See [nextValue].
89 class _NextValueMatcher extends StreamMatcher {
90 final Matcher _matcher;
91
92 _NextValueMatcher(matcher)
93 : _matcher = wrapMatcher(matcher);
94
95 Future<Description> tryMatch(ScheduledStream stream) {
96 return stream.hasNext.then((hasNext) {
97 if (!hasNext) {
98 return new StringDescription("unexpected end of stream");
Bob Nystrom 2013/12/21 00:11:34 Describe the matcher here too.
nweiz 2014/01/07 03:16:39 This description will be provided elsewhere in the
99 }
100 return stream.next().then((value) {
101 var matchState = {};
102 if (_matcher.matches(value, matchState)) return null;
103 return _matcher.describeMismatch(value, new StringDescription(),
104 matchState, false);
105 });
106 });
107 }
108
109 String toString() => _matcher.describe(new StringDescription()).toString();
110 }
111
112 /// See [nextValues].
113 class _NextValuesMatcher extends StreamMatcher {
114 final int _n;
Bob Nystrom 2013/12/21 00:11:34 Doc.
nweiz 2014/01/07 03:16:39 Usually we don't document private instance variabl
115 final Matcher _matcher;
116
117 _NextValuesMatcher(this._n, matcher)
118 : _matcher = wrapMatcher(matcher);
119
120 Future<Description> tryMatch(ScheduledStream stream) {
121 var collectedValues = [];
122 collectValues(count) {
123 if (count == 0) return null;
124
125 return stream.hasNext.then((hasNext) {
126 if (!hasNext) return new StringDescription('unexpected end of stream');
Bob Nystrom 2013/12/21 00:11:34 Describe the expected values here.
nweiz 2014/01/07 03:16:39 See above.
127
128 return stream.next().then((value) {
129 collectedValues.add(value);
130 return collectValues(count - 1);
131 });
132 });
133 }
134
135 return collectValues(_n).then((description) {
136 if (description != null) return description;
137 var matchState = {};
138 if (_matcher.matches(collectedValues, matchState)) return null;
139 return _matcher.describeMismatch(collectedValues, new StringDescription(),
140 matchState, false);
141 });
142 }
143
144 String toString() {
145 return new StringDescription('$_n values that ')
146 .addDescriptionOf(_matcher)
147 .toString();
148 }
149 }
150
151 /// See [inOrder].
152 class _InOrderMatcher extends StreamMatcher {
153 final List<StreamMatcher> _matchers;
154
155 _InOrderMatcher(Iterable streamMatchers)
156 : _matchers = streamMatchers.map((matcher) =>
157 new StreamMatcher.wrap(matcher)).toList();
158
159 Future<Description> tryMatch(ScheduledStream stream) {
160 var matchers = new Queue.from(_matchers);
161
162 matchNext() {
163 if (matchers.isEmpty) return new Future.value();
164 var matcher = matchers.removeFirst();
165 return matcher.tryMatch(stream).then((description) {
166 if (description == null) return matchNext();
167 var newDescription = new StringDescription(
168 'matcher #${_matchers.length - matchers.length} failed');
169 if (description.length != 0) newDescription.add(':\n$description');
170 return newDescription;
171 });
172 }
173
174 return matchNext();
175 }
176
177 String toString() => _matchers
178 .map((matcher) => prefixLines(matcher.toString(), firstPrefix: '* '))
179 .join('\n');
180 }
181
182 /// See [consumeThrough].
183 class _ConsumeThroughMatcher extends StreamMatcher {
184 final Matcher _matcher;
185
186 _ConsumeThroughMatcher(matcher)
187 : _matcher = wrapMatcher(matcher);
188
189 Future<Description> tryMatch(ScheduledStream stream) {
190 consumeNext() {
191 return stream.hasNext.then((hasNext) {
192 if (!hasNext) return new StringDescription("unexpected end of stream");
Bob Nystrom 2013/12/21 00:11:34 Ditto.
nweiz 2014/01/07 03:16:39 See above.
193
194 return stream.next().then((value) {
195 if (_matcher.matches(value, {})) return null;
196 return consumeNext();
197 });
198 });
199 }
200
201 return consumeNext();
202 }
203
204 String toString() {
205 return new StringDescription('values followed by ')
206 .addDescriptionOf(_matcher).toString();
207 }
208 }
209
210 /// See [either].
211 class _EitherMatcher extends StreamMatcher {
212 final StreamMatcher _matcher1;
213 final StreamMatcher _matcher2;
214
215 _EitherMatcher(streamMatcher1, streamMatcher2)
216 : _matcher1 = new StreamMatcher.wrap(streamMatcher1),
217 _matcher2 = new StreamMatcher.wrap(streamMatcher2);
218
219 Future<Description> tryMatch(ScheduledStream stream) {
220 var stream1 = stream.fork();
221 var stream2 = stream.fork();
222
223 return Future.wait([
224 _matcher1.tryMatch(stream1),
225 _matcher2.tryMatch(stream2)
226 ]).whenComplete(() {
227 stream1.close();
228 stream2.close();
Bob Nystrom 2013/12/21 00:11:34 Should these be closed only when *both* futures ha
nweiz 2014/01/07 03:16:39 Done.
229 }).then((descriptions) {
230 var description1 = descriptions.first;
231 var description2 = descriptions.last;
232
233 // If both matchers matched, use the one that consumed more of the stream.
234 if (description1 == null && description2 == null) {
235 if (stream1.values.length >= stream2.values.length) {
236 return _matcher1.match(stream);
237 } else {
238 return _matcher2.match(stream);
239 }
240 } else if (description1 == null) {
241 return _matcher1.match(stream);
242 } else if (description2 == null) {
243 return _matcher2.match(stream);
244 } else {
245 return new StringDescription('both\n')
246 .add(prefixLines(description1.toString(), prefix: ' '))
247 .add('\nand\n')
248 .add(prefixLines(description2.toString(), prefix: ' '))
249 .toString();
250 }
251 });
252 }
253
254 String toString() {
255 return new StringDescription('either\n')
256 .add(prefixLines(_matcher1.toString(), prefix: ' '))
257 .add('\nor\n')
258 .add(prefixLines(_matcher2.toString(), prefix: ' '))
259 .toString();
260 }
261 }
262
263 /// See [allow].
264 class _AllowMatcher extends StreamMatcher {
265 final StreamMatcher _matcher;
266
267 _AllowMatcher(streamMatcher)
268 : _matcher = new StreamMatcher.wrap(streamMatcher);
269
270 Future<Description> tryMatch(ScheduledStream stream) {
271 var fork = stream.fork();
272 return _matcher.tryMatch(fork).whenComplete(fork.close).then((description) {
273 if (description != null) return null;
274 return _matcher.match(stream);
275 });
276 }
277
278 String toString() {
279 return new StringDescription('allow\n')
280 .add(prefixLines(_matcher.toString()))
281 .toString();
282 }
283 }
284
285 /// See [isDone].
286 class _IsDoneMatcher extends StreamMatcher {
287 _IsDoneMatcher();
288
289 Future<Description> tryMatch(ScheduledStream stream) {
290 return stream.hasNext.then((hasNext) {
291 if (!hasNext) return null;
292 return new StringDescription("stream wasn't finished");
293 });
294 }
295
296 String toString() => 'is done';
297 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698