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

Side by Side Diff: lib/src/core_matchers.dart

Issue 840133003: matcher: fixed status file, formatting, tweaks to readme (Closed) Base URL: https://github.com/dart-lang/matcher.git@master
Patch Set: nits Created 5 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
« no previous file with comments | « lib/mirror_matchers.dart ('k') | lib/src/description.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library matcher.core_matchers; 5 library matcher.core_matchers;
6 6
7 import 'description.dart'; 7 import 'description.dart';
8 import 'interfaces.dart'; 8 import 'interfaces.dart';
9 import 'util.dart'; 9 import 'util.dart';
10 10
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
99 99
100 /// Returns a matcher that matches if the value is structurally equal to 100 /// Returns a matcher that matches if the value is structurally equal to
101 /// [expected]. 101 /// [expected].
102 /// 102 ///
103 /// If [expected] is a [Matcher], then it matches using that. Otherwise it tests 103 /// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
104 /// for equality using `==` on the expected value. 104 /// for equality using `==` on the expected value.
105 /// 105 ///
106 /// For [Iterable]s and [Map]s, this will recursively match the elements. To 106 /// For [Iterable]s and [Map]s, this will recursively match the elements. To
107 /// handle cyclic structures a recursion depth [limit] can be provided. The 107 /// handle cyclic structures a recursion depth [limit] can be provided. The
108 /// default limit is 100. [Set]s will be compared order-independently. 108 /// default limit is 100. [Set]s will be compared order-independently.
109 Matcher equals(expected, [int limit=100]) => 109 Matcher equals(expected, [int limit = 100]) => expected is String
110 expected is String 110 ? new _StringEqualsMatcher(expected)
111 ? new _StringEqualsMatcher(expected) 111 : new _DeepMatcher(expected, limit);
112 : new _DeepMatcher(expected, limit);
113 112
114 class _DeepMatcher extends Matcher { 113 class _DeepMatcher extends Matcher {
115 final _expected; 114 final _expected;
116 final int _limit; 115 final int _limit;
117 var count; 116 var count;
118 117
119 _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit; 118 _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
120 119
121 // Returns a pair (reason, location) 120 // Returns a pair (reason, location)
122 List _compareIterables(expected, actual, matcher, depth, location) { 121 List _compareIterables(expected, actual, matcher, depth, location) {
123 if (actual is! Iterable) return ['is not Iterable', location]; 122 if (actual is! Iterable) return ['is not Iterable', location];
124 123
125 var expectedIterator = expected.iterator; 124 var expectedIterator = expected.iterator;
126 var actualIterator = actual.iterator; 125 var actualIterator = actual.iterator;
127 for (var index = 0; ; index++) { 126 for (var index = 0; ; index++) {
128 // Advance in lockstep. 127 // Advance in lockstep.
129 var expectedNext = expectedIterator.moveNext(); 128 var expectedNext = expectedIterator.moveNext();
130 var actualNext = actualIterator.moveNext(); 129 var actualNext = actualIterator.moveNext();
131 130
132 // If we reached the end of both, we succeeded. 131 // If we reached the end of both, we succeeded.
133 if (!expectedNext && !actualNext) return null; 132 if (!expectedNext && !actualNext) return null;
134 133
135 // Fail if their lengths are different. 134 // Fail if their lengths are different.
136 var newLocation = '${location}[${index}]'; 135 var newLocation = '${location}[${index}]';
137 if (!expectedNext) return ['longer than expected', newLocation]; 136 if (!expectedNext) return ['longer than expected', newLocation];
138 if (!actualNext) return ['shorter than expected', newLocation]; 137 if (!actualNext) return ['shorter than expected', newLocation];
139 138
140 // Match the elements. 139 // Match the elements.
141 var rp = matcher(expectedIterator.current, actualIterator.current, 140 var rp = matcher(
142 newLocation, depth); 141 expectedIterator.current, actualIterator.current, newLocation, depth);
143 if (rp != null) return rp; 142 if (rp != null) return rp;
144 } 143 }
145 } 144 }
146 145
147 List _compareSets(Set expected, actual, matcher, depth, location) { 146 List _compareSets(Set expected, actual, matcher, depth, location) {
148 if (actual is! Iterable) return ['is not Iterable', location]; 147 if (actual is! Iterable) return ['is not Iterable', location];
149 actual = actual.toSet(); 148 actual = actual.toSet();
150 149
151 for (var expectedElement in expected) { 150 for (var expectedElement in expected) {
152 if (actual.every((actualElement) => 151 if (actual.every((actualElement) =>
(...skipping 17 matching lines...) Expand all
170 var matchState = {}; 169 var matchState = {};
171 if (expected.matches(actual, matchState)) return null; 170 if (expected.matches(actual, matchState)) return null;
172 171
173 var description = new StringDescription(); 172 var description = new StringDescription();
174 expected.describe(description); 173 expected.describe(description);
175 return ['does not match $description', location]; 174 return ['does not match $description', location];
176 } else { 175 } else {
177 // Otherwise, test for equality. 176 // Otherwise, test for equality.
178 try { 177 try {
179 if (expected == actual) return null; 178 if (expected == actual) return null;
180 } catch (e, s) { 179 } catch (e) {
181 // TODO(gram): Add a test for this case. 180 // TODO(gram): Add a test for this case.
182 return ['== threw "$e"', location]; 181 return ['== threw "$e"', location];
183 } 182 }
184 } 183 }
185 184
186 if (depth > _limit) return ['recursion depth limit exceeded', location]; 185 if (depth > _limit) return ['recursion depth limit exceeded', location];
187 186
188 // If _limit is 1 we can only recurse one level into object. 187 // If _limit is 1 we can only recurse one level into object.
189 if (depth == 0 || _limit > 1) { 188 if (depth == 0 || _limit > 1) {
190 if (expected is Set) { 189 if (expected is Set) {
191 return _compareSets(expected, actual, _recursiveMatch, depth + 1, 190 return _compareSets(
192 location); 191 expected, actual, _recursiveMatch, depth + 1, location);
193 } else if (expected is Iterable) { 192 } else if (expected is Iterable) {
194 return _compareIterables(expected, actual, _recursiveMatch, depth + 1, 193 return _compareIterables(
195 location); 194 expected, actual, _recursiveMatch, depth + 1, location);
196 } else if (expected is Map) { 195 } else if (expected is Map) {
197 if (actual is! Map) return ['expected a map', location]; 196 if (actual is! Map) return ['expected a map', location];
198 197
199 var err = (expected.length == actual.length) ? '' : 198 var err = (expected.length == actual.length)
200 'has different length and '; 199 ? ''
200 : 'has different length and ';
201 for (var key in expected.keys) { 201 for (var key in expected.keys) {
202 if (!actual.containsKey(key)) { 202 if (!actual.containsKey(key)) {
203 return ["${err}is missing map key '$key'", location]; 203 return ["${err}is missing map key '$key'", location];
204 } 204 }
205 } 205 }
206 206
207 for (var key in actual.keys) { 207 for (var key in actual.keys) {
208 if (!expected.containsKey(key)) { 208 if (!expected.containsKey(key)) {
209 return ["${err}has extra map key '$key'", location]; 209 return ["${err}has extra map key '$key'", location];
210 } 210 }
211 } 211 }
212 212
213 for (var key in expected.keys) { 213 for (var key in expected.keys) {
214 var rp = _recursiveMatch(expected[key], actual[key], 214 var rp = _recursiveMatch(
215 "${location}['${key}']", depth + 1); 215 expected[key], actual[key], "${location}['${key}']", depth + 1);
216 if (rp != null) return rp; 216 if (rp != null) return rp;
217 } 217 }
218 218
219 return null; 219 return null;
220 } 220 }
221 } 221 }
222 222
223 var description = new StringDescription(); 223 var description = new StringDescription();
224 224
225 // If we have recursed, show the expected value too; if not, expect() will 225 // If we have recursed, show the expected value too; if not, expect() will
226 // show it for us. 226 // show it for us.
227 if (depth > 0) { 227 if (depth > 0) {
228 description.add('was '). 228 description
229 addDescriptionOf(actual). 229 .add('was ')
230 add(' instead of '). 230 .addDescriptionOf(actual)
231 addDescriptionOf(expected); 231 .add(' instead of ')
232 .addDescriptionOf(expected);
232 return [description.toString(), location]; 233 return [description.toString(), location];
233 } 234 }
234 235
235 // We're not adding any value to the actual value. 236 // We're not adding any value to the actual value.
236 return ["", location]; 237 return ["", location];
237 } 238 }
238 239
239 String _match(expected, actual, Map matchState) { 240 String _match(expected, actual, Map matchState) {
240 var rp = _recursiveMatch(expected, actual, '', 0); 241 var rp = _recursiveMatch(expected, actual, '', 0);
241 if (rp == null) return null; 242 if (rp == null) return null;
242 var reason; 243 var reason;
243 if (rp[0].length > 0) { 244 if (rp[0].length > 0) {
244 if (rp[1].length > 0) { 245 if (rp[1].length > 0) {
245 reason = "${rp[0]} at location ${rp[1]}"; 246 reason = "${rp[0]} at location ${rp[1]}";
246 } else { 247 } else {
247 reason = rp[0]; 248 reason = rp[0];
248 } 249 }
249 } else { 250 } else {
250 reason = ''; 251 reason = '';
251 } 252 }
252 // Cache the failure reason in the matchState. 253 // Cache the failure reason in the matchState.
253 addStateInfo(matchState, {'reason': reason}); 254 addStateInfo(matchState, {'reason': reason});
254 return reason; 255 return reason;
255 } 256 }
256 257
257 bool matches(item, Map matchState) => 258 bool matches(item, Map matchState) =>
258 _match(_expected, item, matchState) == null; 259 _match(_expected, item, matchState) == null;
259 260
260 Description describe(Description description) => 261 Description describe(Description description) =>
261 description.addDescriptionOf(_expected); 262 description.addDescriptionOf(_expected);
262 263
263 Description describeMismatch(item, Description mismatchDescription, 264 Description describeMismatch(
264 Map matchState, bool verbose) { 265 item, Description mismatchDescription, Map matchState, bool verbose) {
265 var reason = matchState['reason']; 266 var reason = matchState['reason'];
266 // If we didn't get a good reason, that would normally be a 267 // If we didn't get a good reason, that would normally be a
267 // simple 'is <value>' message. We only add that if the mismatch 268 // simple 'is <value>' message. We only add that if the mismatch
268 // description is non empty (so we are supplementing the mismatch 269 // description is non empty (so we are supplementing the mismatch
269 // description). 270 // description).
270 if (reason.length == 0 && mismatchDescription.length > 0) { 271 if (reason.length == 0 && mismatchDescription.length > 0) {
271 mismatchDescription.add('is ').addDescriptionOf(item); 272 mismatchDescription.add('is ').addDescriptionOf(item);
272 } else { 273 } else {
273 mismatchDescription.add(reason); 274 mismatchDescription.add(reason);
274 } 275 }
275 return mismatchDescription; 276 return mismatchDescription;
276 } 277 }
277 } 278 }
278 279
279 /// A special equality matcher for strings. 280 /// A special equality matcher for strings.
280 class _StringEqualsMatcher extends Matcher { 281 class _StringEqualsMatcher extends Matcher {
281 final String _value; 282 final String _value;
282 283
283 _StringEqualsMatcher(this._value); 284 _StringEqualsMatcher(this._value);
284 285
285 bool get showActualValue => true; 286 bool get showActualValue => true;
286 287
287 bool matches(item, Map matchState) => _value == item; 288 bool matches(item, Map matchState) => _value == item;
288 289
289 Description describe(Description description) => 290 Description describe(Description description) =>
290 description.addDescriptionOf(_value); 291 description.addDescriptionOf(_value);
291 292
292 Description describeMismatch(item, Description mismatchDescription, 293 Description describeMismatch(
293 Map matchState, bool verbose) { 294 item, Description mismatchDescription, Map matchState, bool verbose) {
294 if (item is! String) { 295 if (item is! String) {
295 return mismatchDescription.addDescriptionOf(item).add('is not a string'); 296 return mismatchDescription.addDescriptionOf(item).add('is not a string');
296 } else { 297 } else {
297 var buff = new StringBuffer(); 298 var buff = new StringBuffer();
298 buff.write('is different.'); 299 buff.write('is different.');
299 var escapedItem = _escape(item); 300 var escapedItem = _escape(item);
300 var escapedValue = _escape(_value); 301 var escapedValue = _escape(_value);
301 int minLength = escapedItem.length < escapedValue.length ? 302 int minLength = escapedItem.length < escapedValue.length
302 escapedItem.length : escapedValue.length; 303 ? escapedItem.length
304 : escapedValue.length;
303 int start; 305 int start;
304 for (start = 0; start < minLength; start++) { 306 for (start = 0; start < minLength; start++) {
305 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { 307 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
306 break; 308 break;
307 } 309 }
308 } 310 }
309 if (start == minLength) { 311 if (start == minLength) {
310 if (escapedValue.length < escapedItem.length) { 312 if (escapedValue.length < escapedItem.length) {
311 buff.write(' Both strings start the same, but the given value also' 313 buff.write(' Both strings start the same, but the given value also'
312 ' has the following trailing characters: '); 314 ' has the following trailing characters: ');
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
409 return true; 411 return true;
410 } catch (e, s) { 412 } catch (e, s) {
411 addStateInfo(matchState, {'exception': e, 'stack': s}); 413 addStateInfo(matchState, {'exception': e, 'stack': s});
412 return false; 414 return false;
413 } 415 }
414 } 416 }
415 417
416 Description describe(Description description) => 418 Description describe(Description description) =>
417 description.add("return normally"); 419 description.add("return normally");
418 420
419 Description describeMismatch(item, Description mismatchDescription, 421 Description describeMismatch(
420 Map matchState, 422 item, Description mismatchDescription, Map matchState, bool verbose) {
421 bool verbose) {
422 mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']); 423 mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
423 if (verbose) { 424 if (verbose) {
424 mismatchDescription.add(' at ').add(matchState['stack'].toString()); 425 mismatchDescription.add(' at ').add(matchState['stack'].toString());
425 } 426 }
426 return mismatchDescription; 427 return mismatchDescription;
427 } 428 }
428 } 429 }
429 430
430 /* 431 /*
431 * Matchers for different exception types. Ideally we should just be able to 432 * Matchers for different exception types. Ideally we should just be able to
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
483 // This is harmless code that will throw if no length property 484 // This is harmless code that will throw if no length property
484 // but subtle enough that an optimizer shouldn't strip it out. 485 // but subtle enough that an optimizer shouldn't strip it out.
485 if (item.length * item.length >= 0) { 486 if (item.length * item.length >= 0) {
486 return _matcher.matches(item.length, matchState); 487 return _matcher.matches(item.length, matchState);
487 } 488 }
488 } catch (e) {} 489 } catch (e) {}
489 return false; 490 return false;
490 } 491 }
491 492
492 Description describe(Description description) => 493 Description describe(Description description) =>
493 description.add('an object with length of '). 494 description.add('an object with length of ').addDescriptionOf(_matcher);
494 addDescriptionOf(_matcher);
495 495
496 Description describeMismatch(item, Description mismatchDescription, 496 Description describeMismatch(
497 Map matchState, bool verbose) { 497 item, Description mismatchDescription, Map matchState, bool verbose) {
498 try { 498 try {
499 // We want to generate a different description if there is no length 499 // We want to generate a different description if there is no length
500 // property; we use the same trick as in matches(). 500 // property; we use the same trick as in matches().
501 if (item.length * item.length >= 0) { 501 if (item.length * item.length >= 0) {
502 return mismatchDescription.add('has length of '). 502 return mismatchDescription
503 addDescriptionOf(item.length); 503 .add('has length of ')
504 .addDescriptionOf(item.length);
504 } 505 }
505 } catch (e) {} 506 } catch (e) {}
506 return mismatchDescription.add('has no length property'); 507 return mismatchDescription.add('has no length property');
507 } 508 }
508 } 509 }
509 510
510 /// Returns a matcher that matches if the match argument contains the expected 511 /// Returns a matcher that matches if the match argument contains the expected
511 /// value. 512 /// value.
512 /// 513 ///
513 /// For [String]s this means substring matching; 514 /// For [String]s this means substring matching;
(...skipping 18 matching lines...) Expand all
532 } 533 }
533 } else if (item is Map) { 534 } else if (item is Map) {
534 return item.containsKey(_expected); 535 return item.containsKey(_expected);
535 } 536 }
536 return false; 537 return false;
537 } 538 }
538 539
539 Description describe(Description description) => 540 Description describe(Description description) =>
540 description.add('contains ').addDescriptionOf(_expected); 541 description.add('contains ').addDescriptionOf(_expected);
541 542
542 Description describeMismatch(item, Description mismatchDescription, 543 Description describeMismatch(
543 Map matchState, bool verbose) { 544 item, Description mismatchDescription, Map matchState, bool verbose) {
544 if (item is String || item is Iterable || item is Map) { 545 if (item is String || item is Iterable || item is Map) {
545 return super.describeMismatch(item, mismatchDescription, matchState, 546 return super.describeMismatch(
546 verbose); 547 item, mismatchDescription, matchState, verbose);
547 } else { 548 } else {
548 return mismatchDescription.add('is not a string, map or iterable'); 549 return mismatchDescription.add('is not a string, map or iterable');
549 } 550 }
550 } 551 }
551 } 552 }
552 553
553 /// Returns a matcher that matches if the match argument is in 554 /// Returns a matcher that matches if the match argument is in
554 /// the expected value. This is the converse of [contains]. 555 /// the expected value. This is the converse of [contains].
555 Matcher isIn(expected) => new _In(expected); 556 Matcher isIn(expected) => new _In(expected);
556 557
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
630 bool matches(item, Map matchState) { 631 bool matches(item, Map matchState) {
631 var f = featureValueOf(item); 632 var f = featureValueOf(item);
632 if (_matcher.matches(f, matchState)) return true; 633 if (_matcher.matches(f, matchState)) return true;
633 addStateInfo(matchState, {'feature': f}); 634 addStateInfo(matchState, {'feature': f});
634 return false; 635 return false;
635 } 636 }
636 637
637 Description describe(Description description) => 638 Description describe(Description description) =>
638 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); 639 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
639 640
640 Description describeMismatch(item, Description mismatchDescription, 641 Description describeMismatch(
641 Map matchState, bool verbose) { 642 item, Description mismatchDescription, Map matchState, bool verbose) {
642 mismatchDescription.add('has ').add(_featureName).add(' with value '). 643 mismatchDescription
643 addDescriptionOf(matchState['feature']); 644 .add('has ')
645 .add(_featureName)
646 .add(' with value ')
647 .addDescriptionOf(matchState['feature']);
644 var innerDescription = new StringDescription(); 648 var innerDescription = new StringDescription();
645 _matcher.describeMismatch(matchState['feature'], innerDescription, 649 _matcher.describeMismatch(
646 matchState['state'], verbose); 650 matchState['feature'], innerDescription, matchState['state'], verbose);
647 if (innerDescription.length > 0) { 651 if (innerDescription.length > 0) {
648 mismatchDescription.add(' which ').add(innerDescription.toString()); 652 mismatchDescription.add(' which ').add(innerDescription.toString());
649 } 653 }
650 return mismatchDescription; 654 return mismatchDescription;
651 } 655 }
652 } 656 }
OLDNEW
« no previous file with comments | « lib/mirror_matchers.dart ('k') | lib/src/description.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698