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

Unified Diff: lib/unittest/core_matchers.dart

Issue 10441104: New expectation functions plus convert old tests to use these. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 7 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 side-by-side diff with in-line comments
Download patch
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, ''));
+}
+
+
+

Powered by Google App Engine
This is Rietveld 408576698