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

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

Issue 16408019: Improved error messages from unittest. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 6 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 | Annotate | Revision Log
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 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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698