Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |