Chromium Code Reviews| Index: pkg/unittest/lib/src/core_matchers.dart |
| =================================================================== |
| --- pkg/unittest/lib/src/core_matchers.dart (revision 24002) |
| +++ pkg/unittest/lib/src/core_matchers.dart (working copy) |
| @@ -12,7 +12,7 @@ |
| class _Empty extends BaseMatcher { |
| const _Empty(); |
| - bool matches(item, MatchState matchState) { |
| + bool matches(item, Map matchState) { |
| if (item is Map || item is Iterable) { |
| return item.isEmpty; |
| } else if (item is String) { |
| @@ -33,14 +33,14 @@ |
| class _IsNull extends BaseMatcher { |
| const _IsNull(); |
| - bool matches(item, MatchState matchState) => item == null; |
| + bool matches(item, Map matchState) => item == null; |
| Description describe(Description description) => |
| description.add('null'); |
| } |
| class _IsNotNull extends BaseMatcher { |
| const _IsNotNull(); |
| - bool matches(item, MatchState matchState) => item != null; |
| + bool matches(item, Map matchState) => item != null; |
| Description describe(Description description) => |
| description.add('not null'); |
| } |
| @@ -53,14 +53,14 @@ |
| class _IsTrue extends BaseMatcher { |
| const _IsTrue(); |
| - bool matches(item, MatchState matchState) => item == true; |
| + bool matches(item, Map matchState) => item == true; |
| Description describe(Description description) => |
| description.add('true'); |
| } |
| class _IsFalse extends BaseMatcher { |
| const _IsFalse(); |
| - bool matches(item, MatchState matchState) => item == false; |
| + bool matches(item, Map matchState) => item == false; |
| Description describe(Description description) => |
| description.add('false'); |
| } |
| @@ -74,7 +74,7 @@ |
| class _IsSameAs extends BaseMatcher { |
| final _expected; |
| const _IsSameAs(this._expected); |
| - bool matches(item, MatchState matchState) => identical(item, _expected); |
| + bool matches(item, Map matchState) => identical(item, _expected); |
| // If all types were hashable we could show a hash here. |
| Description describe(Description description) => |
| description.add('same instance as ').addDescriptionOf(_expected); |
| @@ -97,76 +97,77 @@ |
| _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; |
| - String _compareIterables(expected, actual, matcher, depth) { |
| + // Returns a pair (reason, location) |
| + List _compareIterables(expected, actual, matcher, depth, location) { |
| if (actual is !Iterable) { |
| - return 'is not Iterable'; |
| + return ['is not Iterable', location]; |
| } |
| var expectedIterator = expected.iterator; |
| var actualIterator = actual.iterator; |
| - var position = 0; |
| - String reason = null; |
| - while (reason == null) { |
| + var index = 0; |
| + while (true) { |
| if (expectedIterator.moveNext()) { |
| + var newLocation = '${location}[${index}]'; |
| if (actualIterator.moveNext()) { |
| - Description r = matcher(expectedIterator.current, |
| - actualIterator.current, |
| - 'mismatch at position ${position}', |
| + var rp = matcher(expectedIterator.current, |
| + actualIterator.current, newLocation, |
| depth); |
| - if (r != null) reason = r.toString(); |
| - ++position; |
| + if (rp != null) return rp; |
| + ++index; |
| } else { |
| - reason = 'shorter than expected'; |
| + return ['shorter than expected', newLocation]; |
| } |
| } else if (actualIterator.moveNext()) { |
| - reason = 'longer than expected'; |
| + return ['longer than expected', newLocation]; |
| } else { |
| return null; |
| } |
| } |
| - return reason; |
| + return null; |
| } |
| - Description _recursiveMatch(expected, actual, String location, int depth) { |
| - Description reason = null; |
| + List _recursiveMatch(expected, actual, String location, int depth) { |
| + String reason = null; |
| // If _limit is 1 we can only recurse one level into object. |
| bool canRecurse = depth == 0 || _limit > 1; |
| if (expected == actual) { |
| // Do nothing. |
| } else if (depth > _limit) { |
| - reason = new StringDescription('recursion depth limit exceeded'); |
| + reason = 'recursion depth limit exceeded'; |
| } else { |
| if (expected is Iterable && canRecurse) { |
| - String r = _compareIterables(expected, actual, |
| - _recursiveMatch, depth+1); |
| - if (r != null) reason = new StringDescription(r); |
| + List result = _compareIterables(expected, actual, |
| + _recursiveMatch, depth+1, location); |
|
Siggi Cherem (dart-lang)
2013/06/14 01:20:56
nit: fix the spaces around +
|
| + if (result != null) { |
| + reason = result[0]; |
| + location = result[1]; |
| + } |
| } else if (expected is Map && canRecurse) { |
| if (actual is !Map) { |
| - reason = new StringDescription('expected a map'); |
| + reason = 'expected a map'; |
| } else { |
| var err = (expected.length == actual.length) ? '' : |
| - 'different map lengths; '; |
| + 'has different length and '; |
| for (var key in expected.keys) { |
| if (!actual.containsKey(key)) { |
| - reason = new StringDescription(err); |
| - reason.add('missing map key '); |
| - reason.addDescriptionOf(key); |
| + reason = '${err}is missing map key \'$key\''; |
| break; |
| } |
| } |
| if (reason == null) { |
| for (var key in actual.keys) { |
| if (!expected.containsKey(key)) { |
| - reason = new StringDescription(err); |
| - reason.add('extra map key '); |
| - reason.addDescriptionOf(key); |
| + reason = '${err}has extra map key \'$key\''; |
| break; |
| } |
| } |
| if (reason == null) { |
| for (var key in expected.keys) { |
| - reason = _recursiveMatch(expected[key], actual[key], |
| - 'with key <${key}> ${location}', depth+1); |
| - if (reason != null) { |
| + var rp = _recursiveMatch(expected[key], actual[key], |
| + "${location}['${key}']", depth+1); |
| + if (rp != null) { |
| + reason = rp[0]; |
| + location = rp[1]; |
| break; |
| } |
| } |
| @@ -174,38 +175,62 @@ |
| } |
| } |
| } else { |
| - reason = new StringDescription(); |
| + var description = new StringDescription(); |
| // If we have recursed, show the expected value too; if not, |
| // expect() will show it for us. |
| if (depth > 0) { |
| - reason.add('expected '); |
| - reason.addDescriptionOf(expected).add(' but '); |
| + description.add('was '). |
| + addDescriptionOf(actual). |
| + add(' instead of '). |
| + addDescriptionOf(expected); |
| + reason = description.toString(); |
| + } else { |
| + reason = ''; // We're not adding any value to the actual value. |
| } |
| - reason.add('was '); |
| - reason.addDescriptionOf(actual); |
| } |
| } |
| - if (reason != null && location.length > 0) { |
| - reason.add(' ').add(location); |
| + if (reason == null) return null; |
| + return [reason, location]; |
| + } |
| + |
| + String _match(expected, actual, Map matchState) { |
| + var rp = _recursiveMatch(expected, actual, '', 0); |
| + if (rp == null) return null; |
| + var reason; |
| + if (rp[0].length > 0) { |
| + if (rp[1].length > 0) { |
| + reason = "${rp[0]} at location ${rp[1]}"; |
| + } else { |
| + reason = rp[0]; |
| + } |
| + } else { |
| + reason = ''; |
| } |
| + // Cache the failure reason in the matchState. |
| + addStateInfo(matchState, {'reason': reason}); |
| return reason; |
| } |
| - String _match(expected, actual) { |
| - Description reason = _recursiveMatch(expected, actual, '', 0); |
| - return reason == null ? null : reason.toString(); |
| - } |
| + bool matches(item, Map matchState) => |
| + _match(_expected, item, matchState) == null; |
| - // TODO(gram) - see if we can make use of matchState here to avoid |
| - // recursing again in describeMismatch. |
| - bool matches(item, MatchState matchState) => _match(_expected, item) == null; |
| - |
| Description describe(Description description) => |
| description.addDescriptionOf(_expected); |
| Description describeMismatch(item, Description mismatchDescription, |
| - MatchState matchState, bool verbose) => |
| - mismatchDescription.add(_match(_expected, item)); |
| + Map matchState, bool verbose) { |
| + var reason = matchState['reason']; |
| + // If we didn't get a good reason, that would normally be a |
| + // simple 'is <value>' message. We only add that if the mismatch |
| + // description is non empty (so we are supplementing the mismatch |
| + // description). |
| + if (reason.length == 0 && mismatchDescription.length > 0) { |
| + mismatchDescription.add('is ').addDescriptionOf(item); |
| + } else { |
| + mismatchDescription.add(reason); |
| + } |
| + return mismatchDescription; |
| + } |
| } |
| /** A special equality matcher for strings. */ |
| @@ -216,18 +241,18 @@ |
| bool get showActualValue => true; |
| - bool matches(item, MatchState mismatchState) => _value == item; |
| + bool matches(item, Map matchState) => _value == item; |
| Description describe(Description description) => |
| description.addDescriptionOf(_value); |
| Description describeMismatch(item, Description mismatchDescription, |
| - MatchState matchState, bool verbose) { |
| + Map matchState, bool verbose) { |
| if (item is! String) { |
| - return mismatchDescription.addDescriptionOf(item).add(' not a string'); |
| + return mismatchDescription.addDescriptionOf(item).add('is not a string'); |
| } else { |
| var buff = new StringBuffer(); |
| - buff.write('Strings are not equal.'); |
| + buff.write('is different.'); |
| var escapedItem = _escape(item); |
| var escapedValue = _escape(_value); |
| int minLength = escapedItem.length < escapedValue.length ? |
| @@ -257,7 +282,7 @@ |
| _writeTrailing(buff, escapedItem, start); |
| buff.write('\n '); |
| for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' '); |
| - buff.write('^\n Differ at position $start'); |
| + buff.write('^\n Differ at offset $start'); |
| } |
| return mismatchDescription.replace(buff.toString()); |
| @@ -291,7 +316,7 @@ |
| class _IsAnything extends BaseMatcher { |
| const _IsAnything(); |
| - bool matches(item, MatchState matchState) => true; |
| + bool matches(item, Map matchState) => true; |
| Description describe(Description description) => |
| description.add('anything'); |
| } |
| @@ -321,7 +346,7 @@ |
| class isInstanceOf<T> extends BaseMatcher { |
| final String _name; |
| const isInstanceOf([name = 'specified type']) : this._name = name; |
| - bool matches(obj, MatchState matchState) => obj is T; |
| + bool matches(obj, Map matchState) => obj is T; |
| // The description here is lame :-( |
| Description describe(Description description) => |
| description.add('an instance of ${_name}'); |
| @@ -376,7 +401,7 @@ |
| const Throws([Matcher matcher]) : |
| this._matcher = matcher; |
| - bool matches(item, MatchState matchState) { |
| + bool matches(item, Map matchState) { |
| if (item is! Function && item is! Future) return false; |
| if (item is Future) { |
| var done = wrapAsync((fn) => fn()); |
| @@ -409,10 +434,7 @@ |
| if (_matcher == null ||_matcher.matches(e, matchState)) { |
| return true; |
| } else { |
| - matchState.state = { |
| - 'exception' :e, |
| - 'stack': s |
| - }; |
| + addStateInfo(matchState, {'exception': e, 'stack': s}); |
| return false; |
| } |
| } |
| @@ -420,29 +442,26 @@ |
| Description describe(Description description) { |
| if (_matcher == null) { |
| - return description.add("throws an exception"); |
| + return description.add("throws"); |
| } else { |
| - return description.add('throws an exception which matches '). |
| - addDescriptionOf(_matcher); |
| + return description.add('throws ').addDescriptionOf(_matcher); |
| } |
| } |
| Description describeMismatch(item, Description mismatchDescription, |
| - MatchState matchState, |
| + Map matchState, |
| bool verbose) { |
| if (item is! Function && item is! Future) { |
| - return mismatchDescription.add(' not a Function or Future'); |
| - } else if (_matcher == null || matchState.state == null) { |
| - return mismatchDescription.add(' no exception'); |
| + return mismatchDescription.add('is not a Function or Future'); |
| + } else if (_matcher == null || matchState['exception'] == null) { |
| + return mismatchDescription.add('did not throw'); |
| } else { |
| - mismatchDescription. |
| - add(' exception ').addDescriptionOf(matchState.state['exception']); |
| + mismatchDescription. add('threw '). |
| + addDescriptionOf(matchState['exception']); |
| if (verbose) { |
| - mismatchDescription.add(' at '). |
| - add(matchState.state['stack'].toString()); |
| + mismatchDescription.add(' at ').add(matchState['stack'].toString()); |
| } |
| - mismatchDescription.add(' does not match ').addDescriptionOf(_matcher); |
| - return mismatchDescription; |
| + return mismatchDescription; |
| } |
| } |
| } |
| @@ -450,15 +469,12 @@ |
| class _ReturnsNormally extends BaseMatcher { |
| const _ReturnsNormally(); |
| - bool matches(f, MatchState matchState) { |
| + bool matches(f, Map matchState) { |
| try { |
| f(); |
| return true; |
| } catch (e, s) { |
| - matchState.state = { |
| - 'exception' : e, |
| - 'stack': s |
| - }; |
| + addStateInfo(matchState, {'exception': e, 'stack': s}); |
| return false; |
| } |
| } |
| @@ -467,15 +483,13 @@ |
| description.add("return normally"); |
| Description describeMismatch(item, Description mismatchDescription, |
| - MatchState matchState, |
| + Map matchState, |
| bool verbose) { |
| - mismatchDescription.add(' threw '). |
| - addDescriptionOf(matchState.state['exception']); |
| - if (verbose) { |
| - mismatchDescription.add(' at '). |
| - add(matchState.state['stack'].toString()); |
| - } |
| - return mismatchDescription; |
| + mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']); |
| + if (verbose) { |
| + mismatchDescription.add(' at ').add(matchState['stack'].toString()); |
| + } |
| + return mismatchDescription; |
| } |
| } |
| @@ -516,7 +530,7 @@ |
| class _FormatException extends TypeMatcher { |
| const _FormatException() : super("FormatException"); |
| - bool matches(item, MatchState matchState) => item is FormatException; |
| + bool matches(item, Map matchState) => item is FormatException; |
| } |
| /** A matcher for Exceptions. */ |
| @@ -527,7 +541,7 @@ |
| class _Exception extends TypeMatcher { |
| const _Exception() : super("Exception"); |
| - bool matches(item, MatchState matchState) => item is Exception; |
| + bool matches(item, Map matchState) => item is Exception; |
| } |
| /** A matcher for ArgumentErrors. */ |
| @@ -539,7 +553,7 @@ |
| class _ArgumentError extends TypeMatcher { |
| const _ArgumentError() : super("ArgumentError"); |
| - bool matches(item, MatchState matchState) => item is ArgumentError; |
| + bool matches(item, Map matchState) => item is ArgumentError; |
| } |
| /** A matcher for RangeErrors. */ |
| @@ -551,7 +565,7 @@ |
| class _RangeError extends TypeMatcher { |
| const _RangeError() : super("RangeError"); |
| - bool matches(item, MatchState matchState) => item is RangeError; |
| + bool matches(item, Map matchState) => item is RangeError; |
| } |
| /** A matcher for NoSuchMethodErrors. */ |
| @@ -563,7 +577,7 @@ |
| class _NoSuchMethodError extends TypeMatcher { |
| const _NoSuchMethodError() : super("NoSuchMethodError"); |
| - bool matches(item, MatchState matchState) => item is NoSuchMethodError; |
| + bool matches(item, Map matchState) => item is NoSuchMethodError; |
| } |
| /** A matcher for UnimplementedErrors. */ |
| @@ -575,7 +589,7 @@ |
| class _UnimplementedError extends TypeMatcher { |
| const _UnimplementedError() : super("UnimplementedError"); |
| - bool matches(item, MatchState matchState) => item is UnimplementedError; |
| + bool matches(item, Map matchState) => item is UnimplementedError; |
| } |
| /** A matcher for UnsupportedError. */ |
| @@ -587,7 +601,7 @@ |
| class _UnsupportedError extends TypeMatcher { |
| const _UnsupportedError() : |
| super("UnsupportedError"); |
| - bool matches(item, MatchState matchState) => item is UnsupportedError; |
| + bool matches(item, Map matchState) => item is UnsupportedError; |
| } |
| /** A matcher for StateErrors. */ |
| @@ -599,7 +613,7 @@ |
| class _StateError extends TypeMatcher { |
| const _StateError() : super("StateError"); |
| - bool matches(item, MatchState matchState) => item is StateError; |
| + bool matches(item, Map matchState) => item is StateError; |
| } |
| @@ -608,7 +622,7 @@ |
| class _IsMap extends TypeMatcher { |
| const _IsMap() : super("Map"); |
| - bool matches(item, MatchState matchState) => item is Map; |
| + bool matches(item, Map matchState) => item is Map; |
| } |
| /** A matcher for List types. */ |
| @@ -616,7 +630,7 @@ |
| class _IsList extends TypeMatcher { |
| const _IsList() : super("List"); |
| - bool matches(item, MatchState matchState) => item is List; |
| + bool matches(item, Map matchState) => item is List; |
| } |
| /** |
| @@ -630,8 +644,16 @@ |
| final Matcher _matcher; |
| const _HasLength([Matcher matcher = null]) : this._matcher = matcher; |
| - bool matches(item, MatchState matchState) { |
| - return _matcher.matches(item.length, matchState); |
| + bool matches(item, Map matchState) { |
| + try { |
| + // This is harmless code that will throw if no length property |
| + // but subtle enough that an optimizer shouldn't strip it out. |
| + if (item.length * item.length >= 0) { |
| + return _matcher.matches(item.length, matchState); |
| + } |
| + } catch (e) { |
| + return false; |
| + } |
| } |
| Description describe(Description description) => |
| @@ -639,17 +661,16 @@ |
| addDescriptionOf(_matcher); |
| Description describeMismatch(item, Description mismatchDescription, |
| - MatchState matchState, bool verbose) { |
| + Map matchState, bool verbose) { |
| try { |
| // We want to generate a different description if there is no length |
| - // property. This is harmless code that will throw if no length property |
| - // but subtle enough that an optimizer shouldn't strip it out. |
| + // property; we use the same trick as in matches(). |
| if (item.length * item.length >= 0) { |
| - return mismatchDescription.add('had length of '). |
| + return mismatchDescription.add('has length of '). |
| addDescriptionOf(item.length); |
| } |
| } catch (e) { |
| - return mismatchDescription.add('had no length property'); |
| + return mismatchDescription.add('has no length property'); |
| } |
| } |
| } |
| @@ -670,7 +691,7 @@ |
| const _Contains(this._expected); |
| - bool matches(item, MatchState matchState) { |
| + bool matches(item, Map matchState) { |
| if (item is String) { |
| return item.indexOf(_expected) >= 0; |
| } else if (item is Iterable) { |
| @@ -687,6 +708,16 @@ |
| Description describe(Description description) => |
| description.add('contains ').addDescriptionOf(_expected); |
| + |
| + Description describeMismatch(item, Description mismatchDescription, |
| + Map matchState, bool verbose) { |
| + if (item is String || item is Iterable || item is Map) { |
| + return super.describeMismatch(item, mismatchDescription, matchState, |
| + verbose); |
| + } else { |
| + return mismatchDescription.add('is not a string, map or iterable'); |
| + } |
| + } |
| } |
| /** |
| @@ -701,7 +732,7 @@ |
| const _In(this._expected); |
| - bool matches(item, MatchState matchState) { |
| + bool matches(item, Map matchState) { |
| if (_expected is String) { |
| return _expected.indexOf(item) >= 0; |
| } else if (_expected is Iterable) { |
| @@ -732,7 +763,7 @@ |
| const _Predicate(this._matcher, this._description); |
| - bool matches(item, MatchState matchState) => _matcher(item); |
| + bool matches(item, Map matchState) => _matcher(item); |
| Description describe(Description description) => |
| description.add(_description); |
| @@ -770,10 +801,10 @@ |
| /** Override this to extract the interesting feature.*/ |
| featureValueOf(actual) => actual; |
| - bool matches(item, MatchState matchState) { |
| + bool matches(item, Map matchState) { |
| var f = featureValueOf(item); |
| if (_matcher.matches(f, matchState)) return true; |
| - matchState.state = { 'innerState': matchState.state, 'feature': f }; |
| + addStateInfo(matchState, {'feature': f}); |
| return false; |
| } |
| @@ -781,10 +812,16 @@ |
| description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); |
| Description describeMismatch(item, Description mismatchDescription, |
| - MatchState matchState, bool verbose) { |
| - mismatchDescription.add(_featureName).add(' '); |
| - _matcher.describeMismatch(matchState.state['feature'], mismatchDescription, |
| - matchState.state['innerState'], verbose); |
| + Map matchState, bool verbose) { |
| + mismatchDescription.add('has ').add(_featureName).add(' with value '). |
| + addDescriptionOf(matchState['feature']); |
| + var innerDescription = new StringDescription(); |
| + _matcher.describeMismatch(matchState['feature'], innerDescription, |
| + matchState['state'], verbose); |
| + if (innerDescription.length > 0) { |
| + mismatchDescription.add(' which ').add(innerDescription.toString()); |
| + } |
| return mismatchDescription; |
| } |
| } |
| + |