OLD | NEW |
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 import 'package:stack_trace/stack_trace.dart'; |
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 |
11 /// Returns a matcher that matches the isEmpty property. | 11 /// Returns a matcher that matches the isEmpty property. |
12 const Matcher isEmpty = const _Empty(); | 12 const Matcher isEmpty = const _Empty(); |
13 | 13 |
14 class _Empty extends Matcher { | 14 class _Empty extends Matcher { |
15 const _Empty(); | 15 const _Empty(); |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
116 var count; | 116 var count; |
117 | 117 |
118 _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit; | 118 _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit; |
119 | 119 |
120 // Returns a pair (reason, location) | 120 // Returns a pair (reason, location) |
121 List _compareIterables(expected, actual, matcher, depth, location) { | 121 List _compareIterables(expected, actual, matcher, depth, location) { |
122 if (actual is! Iterable) return ['is not Iterable', location]; | 122 if (actual is! Iterable) return ['is not Iterable', location]; |
123 | 123 |
124 var expectedIterator = expected.iterator; | 124 var expectedIterator = expected.iterator; |
125 var actualIterator = actual.iterator; | 125 var actualIterator = actual.iterator; |
126 for (var index = 0; ; index++) { | 126 for (var index = 0;; index++) { |
127 // Advance in lockstep. | 127 // Advance in lockstep. |
128 var expectedNext = expectedIterator.moveNext(); | 128 var expectedNext = expectedIterator.moveNext(); |
129 var actualNext = actualIterator.moveNext(); | 129 var actualNext = actualIterator.moveNext(); |
130 | 130 |
131 // If we reached the end of both, we succeeded. | 131 // If we reached the end of both, we succeeded. |
132 if (!expectedNext && !actualNext) return null; | 132 if (!expectedNext && !actualNext) return null; |
133 | 133 |
134 // Fail if their lengths are different. | 134 // Fail if their lengths are different. |
135 var newLocation = '${location}[${index}]'; | 135 var newLocation = '${location}[${index}]'; |
136 if (!expectedNext) return ['longer than expected', newLocation]; | 136 if (!expectedNext) return ['longer than expected', newLocation]; |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
256 } | 256 } |
257 | 257 |
258 bool matches(item, Map matchState) => | 258 bool matches(item, Map matchState) => |
259 _match(_expected, item, matchState) == null; | 259 _match(_expected, item, matchState) == null; |
260 | 260 |
261 Description describe(Description description) => | 261 Description describe(Description description) => |
262 description.addDescriptionOf(_expected); | 262 description.addDescriptionOf(_expected); |
263 | 263 |
264 Description describeMismatch( | 264 Description describeMismatch( |
265 item, Description mismatchDescription, Map matchState, bool verbose) { | 265 item, Description mismatchDescription, Map matchState, bool verbose) { |
266 var reason = matchState['reason']; | 266 var reason = matchState['reason'] ?? ''; |
267 // 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 |
268 // simple 'is <value>' message. We only add that if the mismatch | 268 // simple 'is <value>' message. We only add that if the mismatch |
269 // description is non empty (so we are supplementing the mismatch | 269 // description is non empty (so we are supplementing the mismatch |
270 // description). | 270 // description). |
271 if (reason.length == 0 && mismatchDescription.length > 0) { | 271 if (reason.length == 0 && mismatchDescription.length > 0) { |
272 mismatchDescription.add('is ').addDescriptionOf(item); | 272 mismatchDescription.add('is ').addDescriptionOf(item); |
273 } else { | 273 } else { |
274 mismatchDescription.add(reason); | 274 mismatchDescription.add(reason); |
275 } | 275 } |
276 return mismatchDescription; | 276 return mismatchDescription; |
(...skipping 26 matching lines...) Expand all Loading... |
303 ? escapedItem.length | 303 ? escapedItem.length |
304 : escapedValue.length; | 304 : escapedValue.length; |
305 var start = 0; | 305 var start = 0; |
306 for (; start < minLength; start++) { | 306 for (; start < minLength; start++) { |
307 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { | 307 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { |
308 break; | 308 break; |
309 } | 309 } |
310 } | 310 } |
311 if (start == minLength) { | 311 if (start == minLength) { |
312 if (escapedValue.length < escapedItem.length) { | 312 if (escapedValue.length < escapedItem.length) { |
313 buff.write(' Both strings start the same, but the given value also' | 313 buff.write(' Both strings start the same, but the actual value also' |
314 ' has the following trailing characters: '); | 314 ' has the following trailing characters: '); |
315 _writeTrailing(buff, escapedItem, escapedValue.length); | 315 _writeTrailing(buff, escapedItem, escapedValue.length); |
316 } else { | 316 } else { |
317 buff.write(' Both strings start the same, but the given value is' | 317 buff.write(' Both strings start the same, but the actual value is' |
318 ' missing the following trailing characters: '); | 318 ' missing the following trailing characters: '); |
319 _writeTrailing(buff, escapedValue, escapedItem.length); | 319 _writeTrailing(buff, escapedValue, escapedItem.length); |
320 } | 320 } |
321 } else { | 321 } else { |
322 buff.write('\nExpected: '); | 322 buff.write('\nExpected: '); |
323 _writeLeading(buff, escapedValue, start); | 323 _writeLeading(buff, escapedValue, start); |
324 _writeTrailing(buff, escapedValue, start); | 324 _writeTrailing(buff, escapedValue, start); |
325 buff.write('\n Actual: '); | 325 buff.write('\n Actual: '); |
326 _writeLeading(buff, escapedItem, start); | 326 _writeLeading(buff, escapedItem, start); |
327 _writeTrailing(buff, escapedItem, start); | 327 _writeTrailing(buff, escapedItem, start); |
(...skipping 28 matching lines...) Expand all Loading... |
356 /// A matcher that matches any value. | 356 /// A matcher that matches any value. |
357 const Matcher anything = const _IsAnything(); | 357 const Matcher anything = const _IsAnything(); |
358 | 358 |
359 class _IsAnything extends Matcher { | 359 class _IsAnything extends Matcher { |
360 const _IsAnything(); | 360 const _IsAnything(); |
361 bool matches(item, Map matchState) => true; | 361 bool matches(item, Map matchState) => true; |
362 Description describe(Description description) => description.add('anything'); | 362 Description describe(Description description) => description.add('anything'); |
363 } | 363 } |
364 | 364 |
365 /// Returns a matcher that matches if an object is an instance | 365 /// Returns a matcher that matches if an object is an instance |
366 /// of [type] (or a subtype). | 366 /// of [T] (or a subtype). |
367 /// | 367 /// |
368 /// As types are not first class objects in Dart we can only | 368 /// As types are not first class objects in Dart we can only |
369 /// approximate this test by using a generic wrapper class. | 369 /// approximate this test by using a generic wrapper class. |
370 /// | 370 /// |
371 /// For example, to test whether 'bar' is an instance of type | 371 /// For example, to test whether 'bar' is an instance of type |
372 /// 'Foo', we would write: | 372 /// 'Foo', we would write: |
373 /// | 373 /// |
374 /// expect(bar, new isInstanceOf<Foo>()); | 374 /// expect(bar, new isInstanceOf<Foo>()); |
375 class isInstanceOf<T> extends Matcher { | 375 class isInstanceOf<T> extends Matcher { |
376 const isInstanceOf(); | 376 const isInstanceOf(); |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
523 } | 523 } |
524 return false; | 524 return false; |
525 } | 525 } |
526 | 526 |
527 Description describe(Description description) => | 527 Description describe(Description description) => |
528 description.add('contains ').addDescriptionOf(_expected); | 528 description.add('contains ').addDescriptionOf(_expected); |
529 | 529 |
530 Description describeMismatch( | 530 Description describeMismatch( |
531 item, Description mismatchDescription, Map matchState, bool verbose) { | 531 item, Description mismatchDescription, Map matchState, bool verbose) { |
532 if (item is String || item is Iterable || item is Map) { | 532 if (item is String || item is Iterable || item is Map) { |
533 return super.describeMismatch( | 533 return super |
534 item, mismatchDescription, matchState, verbose); | 534 .describeMismatch(item, mismatchDescription, matchState, verbose); |
535 } else { | 535 } else { |
536 return mismatchDescription.add('is not a string, map or iterable'); | 536 return mismatchDescription.add('is not a string, map or iterable'); |
537 } | 537 } |
538 } | 538 } |
539 } | 539 } |
540 | 540 |
541 /// Returns a matcher that matches if the match argument is in | 541 /// Returns a matcher that matches if the match argument is in |
542 /// the expected value. This is the converse of [contains]. | 542 /// the expected value. This is the converse of [contains]. |
543 Matcher isIn(expected) => new _In(expected); | 543 Matcher isIn(expected) => new _In(expected); |
544 | 544 |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
609 final String _featureName; | 609 final String _featureName; |
610 final Matcher _matcher; | 610 final Matcher _matcher; |
611 | 611 |
612 CustomMatcher(this._featureDescription, this._featureName, matcher) | 612 CustomMatcher(this._featureDescription, this._featureName, matcher) |
613 : this._matcher = wrapMatcher(matcher); | 613 : this._matcher = wrapMatcher(matcher); |
614 | 614 |
615 /// Override this to extract the interesting feature. | 615 /// Override this to extract the interesting feature. |
616 featureValueOf(actual) => actual; | 616 featureValueOf(actual) => actual; |
617 | 617 |
618 bool matches(item, Map matchState) { | 618 bool matches(item, Map matchState) { |
619 var f = featureValueOf(item); | 619 try { |
620 if (_matcher.matches(f, matchState)) return true; | 620 var f = featureValueOf(item); |
621 addStateInfo(matchState, {'feature': f}); | 621 if (_matcher.matches(f, matchState)) return true; |
| 622 addStateInfo(matchState, {'custom.feature': f}); |
| 623 } catch (exception, stack) { |
| 624 addStateInfo(matchState, { |
| 625 'custom.exception': exception.toString(), |
| 626 'custom.stack': new Chain.forTrace(stack) |
| 627 .foldFrames( |
| 628 (frame) => |
| 629 frame.package == 'test' || |
| 630 frame.package == 'stream_channel' || |
| 631 frame.package == 'matcher', |
| 632 terse: true) |
| 633 .toString() |
| 634 }); |
| 635 } |
622 return false; | 636 return false; |
623 } | 637 } |
624 | 638 |
625 Description describe(Description description) => | 639 Description describe(Description description) => |
626 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); | 640 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); |
627 | 641 |
628 Description describeMismatch( | 642 Description describeMismatch( |
629 item, Description mismatchDescription, Map matchState, bool verbose) { | 643 item, Description mismatchDescription, Map matchState, bool verbose) { |
| 644 if (matchState['custom.exception'] != null) { |
| 645 mismatchDescription |
| 646 .add('threw ') |
| 647 .addDescriptionOf(matchState['custom.exception']) |
| 648 .add('\n') |
| 649 .add(matchState['custom.stack'].toString()); |
| 650 return mismatchDescription; |
| 651 } |
| 652 |
630 mismatchDescription | 653 mismatchDescription |
631 .add('has ') | 654 .add('has ') |
632 .add(_featureName) | 655 .add(_featureName) |
633 .add(' with value ') | 656 .add(' with value ') |
634 .addDescriptionOf(matchState['feature']); | 657 .addDescriptionOf(matchState['custom.feature']); |
635 var innerDescription = new StringDescription(); | 658 var innerDescription = new StringDescription(); |
636 _matcher.describeMismatch( | 659 |
637 matchState['feature'], innerDescription, matchState['state'], verbose); | 660 _matcher.describeMismatch(matchState['custom.feature'], innerDescription, |
| 661 matchState['state'], verbose); |
| 662 |
638 if (innerDescription.length > 0) { | 663 if (innerDescription.length > 0) { |
639 mismatchDescription.add(' which ').add(innerDescription.toString()); | 664 mismatchDescription.add(' which ').add(innerDescription.toString()); |
640 } | 665 } |
641 return mismatchDescription; | 666 return mismatchDescription; |
642 } | 667 } |
643 } | 668 } |
OLD | NEW |