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

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,373 @@
+// 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 empty strings, maps or collections.
+ */
+final IMatcher isEmpty = const _Empty();
+
+class _Empty extends Matcher {
+ const _Empty();
+ bool matches(item) {
+ if (item is Map || item is Collection)
Bob Nystrom 2012/06/01 18:22:23 {} for multi-line ifs.
gram 2012/06/01 22:22:00 Done.
+ return item.isEmpty();
+ else if (item is String)
+ return item.length == 0;
+ else
+ return false;
+ }
+ IDescription describe(IDescription description) =>
+ description.add('empty');
+}
+
+/** A matcher that matches any null value. */
+final IMatcher isNull = const _IsNull();
+
+/** A matcher that matches any non-null value. */
+final IMatcher isNotNull = const _IsNotNull();
+
+class _IsNull extends Matcher {
+ const _IsNull();
+ bool matches(item) => item == null;
+ IDescription describe(IDescription description) =>
+ description.add('null');
+}
+
+class _IsNotNull extends Matcher {
+ const _IsNotNull();
+ bool matches(item) => item != null;
+ IDescription describe(IDescription description) =>
+ description.add('not null');
+}
+
+/** A matcher that matches the Boolean value true. */
+final IMatcher isTrue = const _IsTrue();
+
+/** A matcher that matches anything except the Boolean value true. */
+final IMatcher isFalse = const _IsFalse();
+
+class _IsTrue extends Matcher {
+ const _IsTrue();
+ bool matches(item) => item == true;
+ IDescription describe(IDescription description) =>
+ description.add('true');
+}
+
+class _IsFalse extends Matcher {
+ const _IsFalse();
+ bool matches(item) => item != true;
+ IDescription describe(IDescription description) =>
+ description.add('false');
+}
+
+/**
+ * Returns a matches that matches if the value is the same instance
+ * as [object] (`===`).
+ */
+IMatcher same(expected) => new _IsSameAs(expected);
+
+class _IsSameAs extends Matcher {
+ final _expected;
+ const _IsSameAs(this._expected);
+ bool matches(item) => item === _expected;
+ // If all types were hashable we could show a hash here.
+ IDescription describe(IDescription description) =>
+ description.add('same instance as ').addDescriptionOf(_expected);
+}
+
+/** Returns a matcher that matches if two objects are equal (==). */
+IMatcher equals(expected) => new _IsEqual(expected);
+
+class _IsEqual extends Matcher {
+ final _expected;
+ const _IsEqual(this._expected);
+ bool matches(item) => item == _expected;
+ IDescription describe(IDescription description) =>
+ description.addDescriptionOf(_expected);
+}
+
+/** A matcher that matches any value. */
+final IMatcher anything = const _IsAnything();
+
+class _IsAnything extends Matcher {
+ const _IsAnything();
+ bool matches(item) => true;
+ IDescription describe(IDescription description) =>
+ description.add('anything');
+}
+
+/**
+ * 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 upon success, or twice upon failure
+ * (the second time to get the failure description).
+ */
+final IMatcher throws = const _Throws();
+
+/**
+ * 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 throwsA(IMatcher matcher) => new _Throws(matcher);
Bob Nystrom 2012/06/01 18:22:23 Nice name. :)
gram 2012/06/01 22:22:00 Done.
+
+/**
+ * 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.
+ */
+final IMatcher returnsNormally = const _ReturnsNormally();
+
+class _Throws extends Matcher {
+ final IMatcher _matcher;
+
+ const _Throws([IMatcher this._matcher = null]);
+
+ 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.add("throws an exception");
+ } else {
+ return description.add('throws an exception which matches ').
+ addDescriptionOf(_matcher);
+ }
+ }
+
+ IDescription describeMismatch(item, IDescription mismatchDescription) {
+ try {
+ item();
+ return mismatchDescription.add(' no exception');
+ } catch (final e) {
+ return mismatchDescription.add(' exception does not match ').
+ addDescriptionOf(_matcher);
+ }
+ }
+}
+
+class _ReturnsNormally extends Matcher {
+
+ const _ReturnsNormally();
+
+ bool matches(f) {
+ try {
+ f();
+ return true;
+ } catch (final e) {
+ return false;
+ }
+ }
+
+ IDescription describe(IDescription description) =>
+ description.add("return normally");
+
+ IDescription describeMismatch(item, IDescription mismatchDescription) {
+ return mismatchDescription.add(' threw exception');
+ }
+}
+
+/**
+ * Returns a matcher that matches if an object is an instance
+ * of [type] (or a subtype).
+ *
+ * As types are not first class objects in Dart we can only
+ * approximate this test by using a generic wrapper class.
+ *
+ * For example, to test whether 'bar' is an instance of type
+ * 'Foo', we would write:
+ *
+ * expect(bar, new isInstanceOf<Foo>());
+ *
+ * To get better error message, supply a name when creating the
+ * Type wrapper; e.g.:
+ *
+ * expect(bar, new isInstanceOf<Foo>('Foo'));
+ */
+class isInstanceOf<T> extends Matcher {
+ final String _name;
+ const isInstanceOf([this._name = 'specified type']);
+ bool matches(obj) => obj is T;
+ // The description here is lame :-(
+ IDescription describe(IDescription description) =>
+ description.add('an instance of ${_name}');
+}
+
+/**
+ * Returns a matcher that matches if an object has a length property
+ * that matches [matcher].
+ */
+IMatcher hasLength(IMatcher matcher) =>
+ new _HasLength(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 {
+ final IMatcher _matcher;
+ const _HasLength([IMatcher this._matcher = null]);
+
+ bool matches(item) {
+ try {
+ // TODO(gram): this hack to throw if no length property is not
+ // working
+ if ((item.length * item.length) >= 0) {
+ return _matcher.matches(item.length);
+ }
+ } catch(var e) {
+ return false;
+ }
+ }
+
+ IDescription describe(IDescription description) =>
+ description.add('an object with length of ').
+ addDescriptionOf(_matcher);
+
+ 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.add(' with length of ').
+ addDescriptionOf(item.length);
+ }
+ } catch (var e) {
+ return mismatchDescription.add(' has no length property');
+ }
+ }
+}
+
+/**
+ * Returns a matcher that does a deep recursive match. This only works
+ * with scalars, Maps and Iterables. To handle cyclic structures an
+ * item [limit] can be provided; if after [limit] items have been
+ * compared and the process is not complete this will be treated as
+ * a mismatch. The default limit is 1000.
+ */
+IMatcher recursivelyMatches(expected) => new _DeepMatcher(expected);
+
+class _DeepMatcher extends Matcher {
+ final _expected;
+ final int _limit;
+ var count;
+
+ _DeepMatcher(this._expected, [this._limit = 1000]);
+
+ String _recursiveMatch(expected, actual, String location) {
+ String reason = null;
+ if (++count >= _limit) {
+ return 'item comparison limit exceeded ${location}';
+ } else if (expected is Iterable) {
+ if (actual is !Iterable) {
+ reason = 'expected an Iterable';
+ } else {
+ var expectedIterator = expected.iterator();
+ var actualIterator = actual.iterator();
+ var position = 0;
+ while (reason == null) {
+ if (expectedIterator.hasNext()) {
Bob Nystrom 2012/06/01 18:22:23 collection_matchers has a near identical chunk of
gram 2012/06/01 22:22:00 Done.
+ if (actualIterator.hasNext()) {
+ reason = _recursiveMatch(expectedIterator.next(),
+ actualIterator.next(),
+ 'at position ${position} ${location}');
+ if (reason != null) {
+ break;
+ }
+ ++position;
+ } else {
+ reason = 'shorter than expected ${location}';
+ }
+ } else if (actualIterator.hasNext()) {
+ reason = 'longer than expected ${location}';
+ } else {
+ break;
+ }
+ }
+ }
+ } else if (expected is Map) {
+ if (actual is !Map) {
+ 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}';
+ }
+ }
+
+ String _match(expected, actual) {
+ count = 0;
+ return _recursiveMatch(expected, actual, '');
+ }
+
+ bool matches(item) => _match(_expected, item) == null;
+
+ IDescription describe(IDescription description) =>
+ description.add('recursively matches ').addDescriptionOf(_expected);
+
+ IDescription describeMismatch(item, IDescription mismatchDescription) =>
+ mismatchDescription.add(_match(_expected, item));
+}
+
+/**
+ * Returns a matcher that matches if the match argument contains
+ * the expected value. For [String]s this means substring matching;
+ * for [Map]s is means the map has the key, and for [Collection]s it
+ * means the collection has a matching element.
+ */
+IMatcher contains(expected) => new _Contains(expected);
Bob Nystrom 2012/06/01 18:22:23 For the non-string case, can expected be a matcher
gram 2012/06/01 22:22:00 Good idea!
+
+class _Contains extends Matcher {
+
+ final _expected;
+
+ const _Contains(this._expected);
+
+ bool matches(item) {
+ if (item is String) {
+ return item.indexOf(_expected) >= 0;
+ } else if (item is Collection) {
+ return item.some((e) => e == _expected);
+ } else if (item is Map) {
+ return item.containsKey(_expected);
+ }
+ return false;
+ }
+
+ IDescription describe(IDescription description) =>
+ description.add('contains ').addDescriptionOf(_expected);
+}

Powered by Google App Engine
This is Rietveld 408576698