| 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 |