Chromium Code Reviews| Index: lib/unittest/core_matchers.dart |
| =================================================================== |
| --- lib/unittest/core_matchers.dart (revision 0) |
| +++ lib/unittest/core_matchers.dart (revision 0) |
| @@ -0,0 +1,337 @@ |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +/** Returns a matcher that matches any null value. */ |
| +IMatcher isNull() => new _IsNull(); |
|
Bob Nystrom
2012/05/30 23:23:51
Make it a var:
final isNull = const _IsNull();
P
gram
2012/06/01 17:33:15
Done.
|
| + |
| +/** Returns a matcher that matches any non-null value. */ |
| +IMatcher isNotNull() => isNot(isNull()); |
| + |
| +class _IsNull extends Matcher { |
| + bool matches(item) => (item == null); |
|
Bob Nystrom
2012/05/30 23:23:51
Remove parens:
bool matches(item) => item == nul
gram
2012/06/01 17:33:15
Done.
|
| + IDescription describe(IDescription description) => description.append('null'); |
| +} |
| + |
| +/** Returns a matcher that matches the Boolean value true. */ |
| +IMatcher isTrue() => new _IsTrue(); |
| + |
| +/** Returns a matcher that matches anything except the Boolean value true. */ |
| +IMatcher isFalse() => isNot(isTrue()); |
| + |
| +class _IsTrue extends Matcher { |
| + |
|
Bob Nystrom
2012/05/30 23:23:51
Delete this blank line.
gram
2012/06/01 17:33:15
Done.
|
| + bool matches(item) => (item == true); |
|
Bob Nystrom
2012/05/30 23:23:51
Remove unneeded parens.
gram
2012/06/01 17:33:15
Done.
|
| + |
| + IDescription describe(IDescription description) => description.append('true'); |
| +} |
| + |
| +/** Returns a matches that matches if the value is the same instance |
|
Bob Nystrom
2012/05/30 23:23:51
Can you format this with the text on the next line
gram
2012/06/01 17:33:15
Done.
|
| + * as [object] (===). |
|
Bob Nystrom
2012/05/30 23:23:51
Put === in backticks to code format it: as [object
gram
2012/06/01 17:33:15
Done.
|
| + */ |
| +IMatcher same(object) => new _IsSame(object); |
|
Bob Nystrom
2012/05/30 23:23:51
"isSameAs"?
gram
2012/06/01 17:33:15
Done.
|
| + |
| +class _IsSame extends Matcher { |
| + |
| + var _object; |
|
Bob Nystrom
2012/05/30 23:23:51
Other matcher classes use "_val" for this. Should
gram
2012/06/01 17:33:15
Done.
|
| + |
| + _IsSame(this._object); |
| + |
| + bool matches(item) => (item === _object); |
|
Bob Nystrom
2012/05/30 23:23:51
Unnecessary ().
gram
2012/06/01 17:33:15
Done.
|
| + |
| + // If all types were hashable we could show a hash here |
|
Bob Nystrom
2012/05/30 23:23:51
True. :(
Add a "." at the end of the sentence.
gram
2012/06/01 17:33:15
Done.
|
| + IDescription describe(IDescription description) => |
| + description.append('same instance as ').appendDescriptionOf(_object); |
| +} |
| + |
| +/** Returns a matcher that matches if two objects are equal (==). */ |
| +IMatcher equals(obj) => new _IsEqual(obj); |
| + |
| +class _IsEqual extends Matcher { |
| + var _object; |
| + |
| + _IsEqual(this._object); |
| + |
| + bool matches(item) => (item == _object); |
| + |
| + IDescription describe(IDescription description) { |
| + //if (_object is IMatcher) { |
|
Bob Nystrom
2012/05/30 23:23:51
Remove commented out code.
gram
2012/06/01 17:33:15
Done.
|
| + // return description.append('<'). |
| + // appendDescriptionOf(_object). |
| + // append('>'); |
| + //} else { |
| + return description.appendDescriptionOf(_object); |
| + //} |
| + } |
| +} |
| + |
| +/** Returns a matcher that matches any value. */ |
| +IMatcher anything([description = 'anything']) => new _IsAnything(description); |
|
Bob Nystrom
2012/05/30 23:23:51
Can we get rid of description and make this a cons
gram
2012/06/01 17:33:15
Done.
|
| + |
| +class _IsAnything extends Matcher { |
| + var _description; |
| + |
| + _IsAnything([this._description = 'anything']); |
| + |
| + bool matches(item) => true; |
| + |
| + IDescription describe(IDescription description) => |
| + description.append(_description); |
| +} |
| + |
| +/** |
| + * Returns a matcher that matches functions that throw exceptions when called. |
| + * The value passed to expect() should be a reference to the function. |
| + * Note that the function cannot take arguments; to handle this |
| + * a wrapper will have to be created. |
| + * The function will be called once. |
| + */ |
| +IMatcher throwsException() => new _Throws(); |
|
Bob Nystrom
2012/05/30 23:23:51
"throwsException" -> "throws"?
gram
2012/06/01 17:33:15
Done.
|
| + |
| +/** |
| + * Returns a matcher that matches a function call against an exception, |
| + * which is in turn constrained by a [matcher]. |
| + * The value passed to expect() should be a reference to the function. |
| + * Note that the function cannot take arguments; to handle this |
| + * a wrapper will have to be created. |
| + * The function will be called once upon success, or twice upon failure |
| + * (the second time to get the failure description). |
| + */ |
| +IMatcher throwsExceptionWhich(IMatcher matcher) => new _Throws(matcher); |
| +/** |
|
Bob Nystrom
2012/05/30 23:23:51
Add blank line above comment.
gram
2012/06/01 17:33:15
Done.
|
| + * Returns a matcher that matches a function call against no exception. |
| + * The function will be called once. Any exceptions will be silently swallowed. |
| + * The value passed to expect() should be a reference to the function. |
| + * Note that the function cannot take arguments; to handle this |
| + * a wrapper will have to be created. |
| + */ |
| +IMatcher returnsNormally() => new _ReturnsNormally(); |
|
Bob Nystrom
2012/05/30 23:23:51
Maybe I don't the intent here, but it seems to me
gram
2012/06/01 17:33:15
Yes - this will catch an exception and return fals
Bob Nystrom
2012/06/01 18:22:22
What happens when exceptions are thrown outside of
|
| + |
| +class _Throws extends Matcher { |
| + IMatcher _matcher; |
| + |
| + _Throws([IMatcher this._matcher = null]) { |
| + if (_matcher != null && !(_matcher is Matcher)) { |
|
Bob Nystrom
2012/05/30 23:23:51
You can delete this. Dart will validate this for y
gram
2012/06/01 17:33:15
Done.
gram
2012/06/01 17:33:15
Done.
|
| + throw new IllegalArgumentException('Throws parameter must be matcher'); |
| + } |
| + } |
| + |
| + bool matches(item) { |
| + try { |
| + item(); |
| + return false; |
| + } catch (final e) { |
| + return _matcher == null || _matcher.matches(e); |
| + } |
| + } |
| + |
| + IDescription describe(IDescription description) { |
| + if (_matcher == null) { |
| + return description.append("throws an exception"); |
| + } else { |
| + return description.append('throws an exception which needs to match '). |
|
Bob Nystrom
2012/05/30 23:23:51
"which needs to match" -> "which matches"?
gram
2012/06/01 17:33:15
Done.
|
| + appendDescriptionOf(_matcher); |
| + } |
| + } |
| + |
| + IDescription describeMismatch(item, IDescription mismatchDescription) { |
| + try { |
| + item(); |
| + return mismatchDescription.append(' no exception'); |
| + } catch (final e) { |
| + return mismatchDescription.append(' exception does not match '). |
| + appendDescriptionOf(_matcher); |
| + } |
| + } |
| +} |
| + |
| +class _ReturnsNormally extends Matcher { |
| + bool matches(f) { |
| + try { |
| + f(); |
| + return true; |
| + } catch (final e) { |
| + return false; |
| + } |
| + } |
| + IDescription describe(IDescription description) => |
| + description.append("return normally"); |
| + IDescription describeMismatch(item, IDescription mismatchDescription) { |
| + return mismatchDescription.append(' threw exception'); |
| + } |
| +} |
| + |
| +/** |
| + * Returns a matcher that matches if an object is an instance |
| + * of a (super)[type]. |
|
Bob Nystrom
2012/05/30 23:23:51
This is worded a bit confusingly. Maybe:
"if an o
gram
2012/06/01 17:33:15
Done.
|
| + * |
| + * As types are not first class objects in Dart we can only |
| + * approximate this test, by using a generic wrapper named Type. |
|
Bob Nystrom
2012/05/30 23:23:51
Delete ","
gram
2012/06/01 17:33:15
Done.
|
| + * |
| + * For example, to test whether 'bar' is an instance of type |
| + * 'Foo', we would write: |
| + * |
| + * expect(bar, instanceOf(new Type<Foo>())); |
| + * |
| + * To get better error message, supply a name when creating the |
| + * Type wrapper; e.g.: |
| + * |
| + * expect(bar, instanceOf(new Type<Foo>('Foo'))); |
| + */ |
| +IMatcher isInstanceOf(Type type) => new _IsInstanceOf(type); |
| + |
| +class Type<T> { |
|
Bob Nystrom
2012/05/30 23:23:51
How about making this implement IMatcher directly
gram
2012/06/01 17:33:15
Done.
|
| + String _name; |
| + Type([this._name = 'specified type']); |
| + bool isInstance(obj) => obj is T; |
| + String toString() => _name; |
| +} |
| + |
| +class _IsInstanceOf extends Matcher { |
| + |
| + Type _expected_type; |
|
Bob Nystrom
2012/05/30 23:23:51
"_expectedType"
gram
2012/06/01 17:33:15
No longer applicable.
|
| + |
| + _IsInstanceOf(Type this._expected_type) { |
| + if (!(_expected_type is Type)) { |
| + throw new IllegalArgumentException('IsInstanceOf requires type'); |
| + } |
| + } |
| + |
| + bool matches(item) => _expected_type.isInstance(item); |
| + |
| + // The description here is lame :-( |
|
Bob Nystrom
2012/05/30 23:23:51
Why?
gram
2012/06/01 17:33:15
Because we don't have the type name unless it is e
|
| + IDescription describe(IDescription description) => |
| + description.append('an instance of ${_expected_type.toString()}'); |
|
Bob Nystrom
2012/05/30 23:23:51
You don't need .toString() here. Interpolation doe
gram
2012/06/01 17:33:15
Done.
|
| +} |
| + |
| +/** |
| + * Returns a matcher that matches if an object has a length property |
| + * that matches [matcher]. |
|
Bob Nystrom
2012/05/30 23:23:51
This doesn't seem to add a lot of value. Why not j
gram
2012/06/01 17:33:15
You can make this a bit shorter, with:
expect(foo
Bob Nystrom
2012/06/01 18:22:22
I like terseness, but I also like simplicity and m
|
| + */ |
| +IMatcher hasLength([matcher = null]) => |
| + new _HasLength(matcher == null ? anything() : wrapMatcher(matcher)); |
| + |
| +// TODO(gram) - this is intended to fail if the value |
| +// does not have a length property but it doesn't. See if this |
| +// can be fixed. |
| +class _HasLength extends Matcher { |
| + |
| + var _len_matcher; |
|
Bob Nystrom
2012/05/30 23:23:51
"_lenMatcher"
gram
2012/06/01 17:33:15
Done.
|
| + |
| + _HasLength(Matcher this._len_matcher) { } |
| + bool matches(item) { |
| + try { |
| + // TODO(gram): this hack to throw if no length property is not |
| + // working |
| + if ((item.length * item.length) >= 0) { |
| + return _len_matcher.matches(item.length); |
| + } |
| + } catch(var e) { |
| + return false; |
| + } |
| + } |
| + |
| + IDescription describeMismatch(item, IDescription mismatchDescription) { |
| + super.describeMismatch(item, mismatchDescription); |
| + try { |
| + // TODO(gram): this hack to throw if no length property is not |
| + // working |
| + if ((item.length * item.length) >= 0) { |
| + return mismatchDescription.append(' with length of '). |
| + appendDescriptionOf(item.length); |
| + } |
| + } catch (var e) { |
| + return mismatchDescription.append(' has no length property'); |
| + } |
| + } |
| + |
| + IDescription describe(IDescription description) => |
| + description.append('an object with length of '). |
| + appendDescriptionOf(_len_matcher); |
| +} |
| + |
| +/** Returns a matcher that matches if value.toString() matches [matcher] */ |
| +IMatcher hasString(matcher) => new _HasString(wrapMatcher(matcher)); |
|
Bob Nystrom
2012/05/30 23:23:51
This name is confusing to me, and it doesn't seem
gram
2012/06/01 17:33:15
Done.
|
| + |
| +class _HasString extends Matcher { |
| + var _str_matcher; |
| + |
| + _HasString(this._str_matcher); |
| + |
| + bool matches(item) => _str_matcher.matches(item.toString()); |
| + |
| + IDescription describe(IDescription description) => |
| + description.append('with toString() value '). |
| + appendDescriptionOf(_str_matcher); |
| +} |
| + |
| +/** |
| + * Returns a matcher that does a deep recursive match. This only works |
| + * with built-in types. Note that cyclic structures will never terminate! |
|
Bob Nystrom
2012/05/30 23:23:51
Wouldn't this also work with user-defined types th
gram
2012/06/01 17:33:15
I made it a bit more general, and added an item co
|
| + */ |
| +IMatcher recursivelyMatches(obj) => new _DeepMatcher(obj); |
| + |
| +class _DeepMatcher extends Matcher { |
| + var _obj; |
| + |
| + _DeepMatcher(this._obj) {} |
| + |
| + String _recursiveMatch(expected, actual, String location) { |
| + String reason = null; |
| + if (expected is Collection) { |
| + if (!(actual is Collection)) { |
| + reason = 'expected a collection'; |
| + } else if (expected.length != actual.length) { |
| + reason = 'different collection lengths'; |
| + } else { |
| + for (var i = 0; i < expected.length; i++) { |
| + reason = _recursiveMatch(expected[i], actual[i], |
| + 'at position ${i} ${location}'); |
|
Bob Nystrom
2012/05/30 23:23:51
Indent another 2 spaces. Continued lines are inden
gram
2012/06/01 17:33:15
Done.
|
| + if (reason != null) { |
| + break; |
| + } |
| + } |
| + } |
| + } else if (expected is Map) { |
| + if (!(actual is Map)) { |
|
Bob Nystrom
2012/05/30 23:23:51
actual is !Map
gram
2012/06/01 17:33:15
Done.
|
| + reason = 'expected a map'; |
| + } else if (expected.length != actual.length) { |
| + reason = 'different map lengths'; |
| + } else { |
| + for (var key in expected.getKeys()) { |
| + if (!actual.containsKey(key)) { |
| + reason = 'missing map key ${key}'; |
| + break; |
| + } |
| + reason = _recursiveMatch(expected[key], actual[key], |
| + 'with key ${key} ${location}'); |
| + if (reason != null) { |
| + break; |
| + } |
| + } |
| + } |
| + } else { |
| + if (expected != actual) { |
| + reason = 'expected ${expected} but got ${actual}'; |
| + } |
| + } |
| + if (reason == null) { |
| + return null; |
| + } else { |
| + return '${reason} ${location}'; |
| + } |
| + } |
| + |
| + bool matches(item) => (_recursiveMatch(_obj, item, '') == null); |
| + |
| + IDescription describe(IDescription description) => |
| + description.append('recursively matches '). |
| + appendDescriptionOf(_obj); |
| + |
| + |
| + IDescription describeMismatch(item, IDescription mismatchDescription) => |
| + mismatchDescription.append(_recursiveMatch(_obj, item, '')); |
| +} |
| + |
| + |
| + |