Chromium Code Reviews| 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 part of matcher; | 5 part of matcher; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Returns a matcher that matches empty strings, maps or iterables | 8 * Returns a matcher that matches empty strings, maps or iterables |
| 9 * (including collections). | 9 * (including collections). |
| 10 */ | 10 */ |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 103 } | 103 } |
| 104 var expectedIterator = expected.iterator; | 104 var expectedIterator = expected.iterator; |
| 105 var actualIterator = actual.iterator; | 105 var actualIterator = actual.iterator; |
| 106 var position = 0; | 106 var position = 0; |
| 107 String reason = null; | 107 String reason = null; |
| 108 while (reason == null) { | 108 while (reason == null) { |
| 109 if (expectedIterator.moveNext()) { | 109 if (expectedIterator.moveNext()) { |
| 110 if (actualIterator.moveNext()) { | 110 if (actualIterator.moveNext()) { |
| 111 Description r = matcher(expectedIterator.current, | 111 Description r = matcher(expectedIterator.current, |
| 112 actualIterator.current, | 112 actualIterator.current, |
| 113 'mismatch at position ${position}', | 113 'has mismatch at position ${position}: ', |
| 114 depth); | 114 depth); |
| 115 if (r != null) reason = r.toString(); | 115 if (r != null) reason = r.toString(); |
| 116 ++position; | 116 ++position; |
| 117 } else { | 117 } else { |
| 118 reason = 'shorter than expected'; | 118 reason = 'shorter than expected'; |
| 119 } | 119 } |
| 120 } else if (actualIterator.moveNext()) { | 120 } else if (actualIterator.moveNext()) { |
| 121 reason = 'longer than expected'; | 121 reason = 'longer than expected'; |
| 122 } else { | 122 } else { |
| 123 return null; | 123 return null; |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 137 } else { | 137 } else { |
| 138 if (expected is Iterable && canRecurse) { | 138 if (expected is Iterable && canRecurse) { |
| 139 String r = _compareIterables(expected, actual, | 139 String r = _compareIterables(expected, actual, |
| 140 _recursiveMatch, depth+1); | 140 _recursiveMatch, depth+1); |
| 141 if (r != null) reason = new StringDescription(r); | 141 if (r != null) reason = new StringDescription(r); |
| 142 } else if (expected is Map && canRecurse) { | 142 } else if (expected is Map && canRecurse) { |
| 143 if (actual is !Map) { | 143 if (actual is !Map) { |
| 144 reason = new StringDescription('expected a map'); | 144 reason = new StringDescription('expected a map'); |
| 145 } else { | 145 } else { |
| 146 var err = (expected.length == actual.length) ? '' : | 146 var err = (expected.length == actual.length) ? '' : |
| 147 'different map lengths; '; | 147 'has different length and '; |
| 148 for (var key in expected.keys) { | 148 for (var key in expected.keys) { |
| 149 if (!actual.containsKey(key)) { | 149 if (!actual.containsKey(key)) { |
| 150 reason = new StringDescription(err); | 150 reason = new StringDescription(err); |
| 151 reason.add('missing map key '); | 151 if (location.length > 0) { |
| 152 reason.add(' ').add(location); | |
| 153 } | |
| 154 reason.add('is missing map key '); | |
| 152 reason.addDescriptionOf(key); | 155 reason.addDescriptionOf(key); |
| 153 break; | 156 break; |
| 154 } | 157 } |
| 155 } | 158 } |
| 156 if (reason == null) { | 159 if (reason == null) { |
| 157 for (var key in actual.keys) { | 160 for (var key in actual.keys) { |
| 158 if (!expected.containsKey(key)) { | 161 if (!expected.containsKey(key)) { |
| 159 reason = new StringDescription(err); | 162 reason = new StringDescription(err); |
| 160 reason.add('extra map key '); | 163 if (location.length > 0) { |
| 164 reason.add(' ').add(location); | |
| 165 } | |
| 166 reason.add('has extra map key '); | |
| 161 reason.addDescriptionOf(key); | 167 reason.addDescriptionOf(key); |
| 162 break; | 168 break; |
| 163 } | 169 } |
| 164 } | 170 } |
| 165 if (reason == null) { | 171 if (reason == null) { |
| 166 for (var key in expected.keys) { | 172 for (var key in expected.keys) { |
| 167 reason = _recursiveMatch(expected[key], actual[key], | 173 reason = _recursiveMatch(expected[key], actual[key], |
| 168 'with key <${key}> ${location}', depth+1); | 174 'with key <${key}> ${location}', depth+1); |
| 169 if (reason != null) { | 175 if (reason != null) { |
| 170 break; | 176 break; |
| 171 } | 177 } |
| 172 } | 178 } |
| 173 } | 179 } |
| 174 } | 180 } |
| 175 } | 181 } |
| 176 } else { | 182 } else { |
| 177 reason = new StringDescription(); | 183 reason = new StringDescription(); |
| 184 if (location.length > 0) { | |
| 185 reason.add(' ').add(location).add(' '); | |
| 186 } | |
| 178 // If we have recursed, show the expected value too; if not, | 187 // If we have recursed, show the expected value too; if not, |
| 179 // expect() will show it for us. | 188 // expect() will show it for us. |
| 180 if (depth > 0) { | 189 if (depth > 0) { |
| 181 reason.add('expected '); | 190 reason.add('expected '); |
| 182 reason.addDescriptionOf(expected).add(' but '); | 191 reason.addDescriptionOf(expected).add(' but '); |
| 183 } | 192 } |
| 184 reason.add('was '); | 193 if (reason.length > 0) { |
| 185 reason.addDescriptionOf(actual); | 194 // Don't do a 'is <value> reason with no other useful |
| 195 // context; the Actual value will provide that info. | |
| 196 reason.add('is '); | |
| 197 reason.addDescriptionOf(actual); | |
| 198 } | |
| 186 } | 199 } |
| 187 } | 200 } |
| 188 if (reason != null && location.length > 0) { | |
| 189 reason.add(' ').add(location); | |
| 190 } | |
| 191 return reason; | 201 return reason; |
| 192 } | 202 } |
| 193 | 203 |
| 194 String _match(expected, actual) { | 204 String _match(expected, actual) { |
| 195 Description reason = _recursiveMatch(expected, actual, '', 0); | 205 Description reason = _recursiveMatch(expected, actual, '', 0); |
| 196 return reason == null ? null : reason.toString(); | 206 return reason == null ? null : reason.toString(); |
| 197 } | 207 } |
| 198 | 208 |
| 199 // TODO(gram) - see if we can make use of matchState here to avoid | 209 // TODO(gram) - see if we can make use of matchState here to avoid |
| 200 // recursing again in describeMismatch. | 210 // recursing again in describeMismatch. |
| 201 bool matches(item, MatchState matchState) => _match(_expected, item) == null; | 211 bool matches(item, MatchState matchState) => _match(_expected, item) == null; |
| 202 | 212 |
| 203 Description describe(Description description) => | 213 Description describe(Description description) => |
| 204 description.addDescriptionOf(_expected); | 214 description.addDescriptionOf(_expected); |
| 205 | 215 |
| 206 Description describeMismatch(item, Description mismatchDescription, | 216 Description describeMismatch(item, Description mismatchDescription, |
| 207 MatchState matchState, bool verbose) => | 217 MatchState matchState, bool verbose) { |
| 208 mismatchDescription.add(_match(_expected, item)); | 218 var reason = _match(_expected, item); |
| 219 // If we didn't get a good reason, that would normally be a | |
| 220 // simple 'is <value>' message. We only add that if the mismatch | |
| 221 // description is non empty (so we are supplementing the mismatch | |
| 222 // description). | |
| 223 if (reason.length == 0 && mismatchDescription.length > 0) { | |
| 224 mismatchDescription.add('is ').addDescriptionOf(item); | |
| 225 } else { | |
| 226 mismatchDescription.add(reason); | |
| 227 } | |
| 228 return mismatchDescription; | |
| 229 } | |
| 209 } | 230 } |
| 210 | 231 |
| 211 /** A special equality matcher for strings. */ | 232 /** A special equality matcher for strings. */ |
| 212 class _StringEqualsMatcher extends BaseMatcher { | 233 class _StringEqualsMatcher extends BaseMatcher { |
| 213 final String _value; | 234 final String _value; |
| 214 | 235 |
| 215 _StringEqualsMatcher(this._value); | 236 _StringEqualsMatcher(this._value); |
| 216 | 237 |
| 217 bool get showActualValue => true; | 238 bool get showActualValue => true; |
| 218 | 239 |
| 219 bool matches(item, MatchState mismatchState) => _value == item; | 240 bool matches(item, MatchState mismatchState) => _value == item; |
| 220 | 241 |
| 221 Description describe(Description description) => | 242 Description describe(Description description) => |
| 222 description.addDescriptionOf(_value); | 243 description.addDescriptionOf(_value); |
| 223 | 244 |
| 224 Description describeMismatch(item, Description mismatchDescription, | 245 Description describeMismatch(item, Description mismatchDescription, |
| 225 MatchState matchState, bool verbose) { | 246 MatchState matchState, bool verbose) { |
| 226 if (item is! String) { | 247 if (item is! String) { |
| 227 return mismatchDescription.addDescriptionOf(item).add(' not a string'); | 248 return mismatchDescription.addDescriptionOf(item).add('is not a string'); |
| 228 } else { | 249 } else { |
| 229 var buff = new StringBuffer(); | 250 var buff = new StringBuffer(); |
| 230 buff.write('Strings are not equal.'); | 251 buff.write('is different.'); |
| 231 var escapedItem = _escape(item); | 252 var escapedItem = _escape(item); |
| 232 var escapedValue = _escape(_value); | 253 var escapedValue = _escape(_value); |
| 233 int minLength = escapedItem.length < escapedValue.length ? | 254 int minLength = escapedItem.length < escapedValue.length ? |
| 234 escapedItem.length : escapedValue.length; | 255 escapedItem.length : escapedValue.length; |
| 235 int start; | 256 int start; |
| 236 for (start = 0; start < minLength; start++) { | 257 for (start = 0; start < minLength; start++) { |
| 237 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { | 258 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { |
| 238 break; | 259 break; |
| 239 } | 260 } |
| 240 } | 261 } |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 413 'exception' :e, | 434 'exception' :e, |
| 414 'stack': s | 435 'stack': s |
| 415 }; | 436 }; |
| 416 return false; | 437 return false; |
| 417 } | 438 } |
| 418 } | 439 } |
| 419 } | 440 } |
| 420 | 441 |
| 421 Description describe(Description description) { | 442 Description describe(Description description) { |
| 422 if (_matcher == null) { | 443 if (_matcher == null) { |
| 423 return description.add("throws an exception"); | 444 return description.add("throws"); |
| 424 } else { | 445 } else { |
| 425 return description.add('throws an exception which matches '). | 446 return description.add('throws '). |
| 426 addDescriptionOf(_matcher); | 447 addDescriptionOf(_matcher); |
| 427 } | 448 } |
| 428 } | 449 } |
| 429 | 450 |
| 430 Description describeMismatch(item, Description mismatchDescription, | 451 Description describeMismatch(item, Description mismatchDescription, |
| 431 MatchState matchState, | 452 MatchState matchState, |
| 432 bool verbose) { | 453 bool verbose) { |
| 433 if (item is! Function && item is! Future) { | 454 if (item is! Function && item is! Future) { |
| 434 return mismatchDescription.add(' not a Function or Future'); | 455 return mismatchDescription.add('is not a Function or Future'); |
| 435 } else if (_matcher == null || matchState.state == null) { | 456 } else if (_matcher == null || matchState.state == null) { |
| 436 return mismatchDescription.add(' no exception'); | 457 return mismatchDescription.add('did not throw'); |
| 437 } else { | 458 } else { |
| 438 mismatchDescription. | 459 mismatchDescription. |
| 439 add(' exception ').addDescriptionOf(matchState.state['exception']); | 460 add('threw ').addDescriptionOf(matchState.state['exception']); |
| 440 if (verbose) { | 461 if (verbose) { |
| 441 mismatchDescription.add(' at '). | 462 mismatchDescription.add(' at '). |
| 442 add(matchState.state['stack'].toString()); | 463 add(matchState.state['stack'].toString()); |
| 443 } | 464 } |
| 444 mismatchDescription.add(' does not match ').addDescriptionOf(_matcher); | 465 return mismatchDescription; |
| 445 return mismatchDescription; | |
| 446 } | 466 } |
| 447 } | 467 } |
| 448 } | 468 } |
| 449 | 469 |
| 450 class _ReturnsNormally extends BaseMatcher { | 470 class _ReturnsNormally extends BaseMatcher { |
| 451 const _ReturnsNormally(); | 471 const _ReturnsNormally(); |
| 452 | 472 |
| 453 bool matches(f, MatchState matchState) { | 473 bool matches(f, MatchState matchState) { |
| 454 try { | 474 try { |
| 455 f(); | 475 f(); |
| 456 return true; | 476 return true; |
| 457 } catch (e, s) { | 477 } catch (e, s) { |
| 458 matchState.state = { | 478 matchState.state = { |
| 459 'exception' : e, | 479 'exception' : e, |
| 460 'stack': s | 480 'stack': s |
| 461 }; | 481 }; |
| 462 return false; | 482 return false; |
| 463 } | 483 } |
| 464 } | 484 } |
| 465 | 485 |
| 466 Description describe(Description description) => | 486 Description describe(Description description) => |
| 467 description.add("return normally"); | 487 description.add("return normally"); |
| 468 | 488 |
| 469 Description describeMismatch(item, Description mismatchDescription, | 489 Description describeMismatch(item, Description mismatchDescription, |
| 470 MatchState matchState, | 490 MatchState matchState, |
| 471 bool verbose) { | 491 bool verbose) { |
| 472 mismatchDescription.add(' threw '). | 492 mismatchDescription.add('threw '). |
| 473 addDescriptionOf(matchState.state['exception']); | 493 addDescriptionOf(matchState.state['exception']); |
| 474 if (verbose) { | 494 if (verbose) { |
| 475 mismatchDescription.add(' at '). | 495 mismatchDescription.add(' at '). |
| 476 add(matchState.state['stack'].toString()); | 496 add(matchState.state['stack'].toString()); |
| 477 } | 497 } |
| 478 return mismatchDescription; | 498 return mismatchDescription; |
| 479 } | 499 } |
| 480 } | 500 } |
| 481 | 501 |
| 482 /* | 502 /* |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 624 * that matches [matcher]. | 644 * that matches [matcher]. |
| 625 */ | 645 */ |
| 626 Matcher hasLength(matcher) => | 646 Matcher hasLength(matcher) => |
| 627 new _HasLength(wrapMatcher(matcher)); | 647 new _HasLength(wrapMatcher(matcher)); |
| 628 | 648 |
| 629 class _HasLength extends BaseMatcher { | 649 class _HasLength extends BaseMatcher { |
| 630 final Matcher _matcher; | 650 final Matcher _matcher; |
| 631 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; | 651 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; |
| 632 | 652 |
| 633 bool matches(item, MatchState matchState) { | 653 bool matches(item, MatchState matchState) { |
| 634 return _matcher.matches(item.length, matchState); | 654 try { |
| 655 // We want to generate a different description if there is no length | |
|
Siggi Cherem (dart-lang)
2013/06/12 00:34:45
adapt the comment? (made sense in describeMismatch
gram
2013/06/13 19:06:15
Done.
| |
| 656 // property. This is harmless code that will throw if no length property | |
| 657 // but subtle enough that an optimizer shouldn't strip it out. | |
| 658 if (item.length * item.length >= 0) { | |
| 659 return _matcher.matches(item.length, matchState); | |
| 660 } | |
| 661 } catch (e) { | |
| 662 return false; | |
| 663 } | |
| 635 } | 664 } |
| 636 | 665 |
| 637 Description describe(Description description) => | 666 Description describe(Description description) => |
| 638 description.add('an object with length of '). | 667 description.add('an object with length of '). |
| 639 addDescriptionOf(_matcher); | 668 addDescriptionOf(_matcher); |
| 640 | 669 |
| 641 Description describeMismatch(item, Description mismatchDescription, | 670 Description describeMismatch(item, Description mismatchDescription, |
| 642 MatchState matchState, bool verbose) { | 671 MatchState matchState, bool verbose) { |
| 643 try { | 672 try { |
| 644 // We want to generate a different description if there is no length | 673 // We want to generate a different description if there is no length |
| 645 // property. This is harmless code that will throw if no length property | 674 // property. This is harmless code that will throw if no length property |
| 646 // but subtle enough that an optimizer shouldn't strip it out. | 675 // but subtle enough that an optimizer shouldn't strip it out. |
| 647 if (item.length * item.length >= 0) { | 676 if (item.length * item.length >= 0) { |
| 648 return mismatchDescription.add('had length of '). | 677 return mismatchDescription.add('has length of '). |
| 649 addDescriptionOf(item.length); | 678 addDescriptionOf(item.length); |
| 650 } | 679 } |
| 651 } catch (e) { | 680 } catch (e) { |
| 652 return mismatchDescription.add('had no length property'); | 681 return mismatchDescription.add('has no length property'); |
| 653 } | 682 } |
| 654 } | 683 } |
| 655 } | 684 } |
| 656 | 685 |
| 657 /** | 686 /** |
| 658 * Returns a matcher that matches if the match argument contains | 687 * Returns a matcher that matches if the match argument contains |
| 659 * the expected value. For [String]s this means substring matching; | 688 * the expected value. For [String]s this means substring matching; |
| 660 * for [Map]s it means the map has the key, and for [Iterable]s | 689 * for [Map]s it means the map has the key, and for [Iterable]s |
| 661 * (including [Iterable]s) it means the iterable has a matching | 690 * (including [Iterable]s) it means the iterable has a matching |
| 662 * element. In the case of iterables, [expected] can itself be a | 691 * element. In the case of iterables, [expected] can itself be a |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 774 var f = featureValueOf(item); | 803 var f = featureValueOf(item); |
| 775 if (_matcher.matches(f, matchState)) return true; | 804 if (_matcher.matches(f, matchState)) return true; |
| 776 matchState.state = { 'innerState': matchState.state, 'feature': f }; | 805 matchState.state = { 'innerState': matchState.state, 'feature': f }; |
| 777 return false; | 806 return false; |
| 778 } | 807 } |
| 779 | 808 |
| 780 Description describe(Description description) => | 809 Description describe(Description description) => |
| 781 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); | 810 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); |
| 782 | 811 |
| 783 Description describeMismatch(item, Description mismatchDescription, | 812 Description describeMismatch(item, Description mismatchDescription, |
| 784 MatchState matchState, bool verbose) { | 813 MatchState matchState, bool verbose) => |
| 785 mismatchDescription.add(_featureName).add(' '); | 814 mismatchDescription.add('has ').add(_featureName).add(' with value '). |
| 786 _matcher.describeMismatch(matchState.state['feature'], mismatchDescription, | 815 addDescriptionOf(matchState.state['feature']); |
| 787 matchState.state['innerState'], verbose); | |
| 788 return mismatchDescription; | |
| 789 } | |
| 790 } | 816 } |
| OLD | NEW |