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

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: 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.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 a value that matches [matcher].
63 ///
64 /// [matcher] can be a [Matcher] or an [Object], but not a [StreamMatcher].
65 StreamMatcher consumeThrough(matcher) => new _ConsumeThroughMatcher(matcher);
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) =>
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);
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 implements 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");
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 implements StreamMatcher {
114 final int _n;
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');
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 implements 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 implements 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");
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 implements 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).whenComplete(stream1.close),
225 _matcher2.tryMatch(stream2).whenComplete(stream2.close)
226 ]).then((descriptions) {
227 var description1 = descriptions.first;
228 var description2 = descriptions.last;
229
230 // If both matchers matched, use the one that consumed more of the stream.
231 if (description1 == null && description2 == null) {
232 if (stream1.emittedValues.length >= stream2.emittedValues.length) {
233 return _matcher1.tryMatch(stream);
234 } else {
235 return _matcher2.tryMatch(stream);
236 }
237 } else if (description1 == null) {
238 return _matcher1.tryMatch(stream);
239 } else if (description2 == null) {
240 return _matcher2.tryMatch(stream);
241 } else {
242 return new StringDescription('both\n')
243 .add(prefixLines(description1.toString(), prefix: ' '))
244 .add('\nand\n')
245 .add(prefixLines(description2.toString(), prefix: ' '))
246 .toString();
247 }
248 });
249 }
250
251 String toString() {
252 return new StringDescription('either\n')
253 .add(prefixLines(_matcher1.toString(), prefix: ' '))
254 .add('\nor\n')
255 .add(prefixLines(_matcher2.toString(), prefix: ' '))
256 .toString();
257 }
258 }
259
260 /// See [allow].
261 class _AllowMatcher implements StreamMatcher {
262 final StreamMatcher _matcher;
263
264 _AllowMatcher(streamMatcher)
265 : _matcher = new StreamMatcher.wrap(streamMatcher);
266
267 Future<Description> tryMatch(ScheduledStream stream) {
268 var fork = stream.fork();
269 return _matcher.tryMatch(fork).whenComplete(fork.close).then((description) {
270 if (description != null) return null;
271 return _matcher.tryMatch(stream);
272 });
273 }
274
275 String toString() {
276 return new StringDescription('allow\n')
277 .add(prefixLines(_matcher.toString()))
278 .toString();
279 }
280 }
281
282 /// See [isDone].
283 class _IsDoneMatcher implements StreamMatcher {
284 _IsDoneMatcher();
285
286 Future<Description> tryMatch(ScheduledStream stream) {
287 return stream.hasNext.then((hasNext) {
288 if (!hasNext) return null;
289 return new StringDescription("stream wasn't finished");
290 });
291 }
292
293 String toString() => 'is done';
294 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698