Index: lib/unittest/core_matchers.dart |
=================================================================== |
--- lib/unittest/core_matchers.dart (revision 0) |
+++ lib/unittest/core_matchers.dart (revision 0) |
@@ -0,0 +1,410 @@ |
+// 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 Matcher isEmpty = const _Empty(); |
+ |
+class _Empty extends BaseMatcher { |
+ const _Empty(); |
+ bool matches(item) { |
+ if (item is Map || item is Collection) { |
+ return item.isEmpty(); |
+ } else if (item is String) { |
+ return item.length == 0; |
+ } else { |
+ return false; |
+ } |
+ } |
+ Description describe(Description description) => |
+ description.add('empty'); |
+} |
+ |
+/** A matcher that matches any null value. */ |
+final Matcher isNull = const _IsNull(); |
+ |
+/** A matcher that matches any non-null value. */ |
+final Matcher isNotNull = const _IsNotNull(); |
+ |
+class _IsNull extends BaseMatcher { |
+ const _IsNull(); |
+ bool matches(item) => item == null; |
+ Description describe(Description description) => |
+ description.add('null'); |
+} |
+ |
+class _IsNotNull extends BaseMatcher { |
+ const _IsNotNull(); |
+ bool matches(item) => item != null; |
+ Description describe(Description description) => |
+ description.add('not null'); |
+} |
+ |
+/** A matcher that matches the Boolean value true. */ |
+final Matcher isTrue = const _IsTrue(); |
+ |
+/** A matcher that matches anything except the Boolean value true. */ |
+final Matcher isFalse = const _IsFalse(); |
+ |
+class _IsTrue extends BaseMatcher { |
+ const _IsTrue(); |
+ bool matches(item) => item == true; |
+ Description describe(Description description) => |
+ description.add('true'); |
+} |
+ |
+class _IsFalse extends BaseMatcher { |
+ const _IsFalse(); |
+ bool matches(item) => item != true; |
+ Description describe(Description description) => |
+ description.add('false'); |
+} |
+ |
+/** |
+ * Returns a matches that matches if the value is the same instance |
+ * as [object] (`===`). |
+ */ |
+Matcher same(expected) => new _IsSameAs(expected); |
+ |
+class _IsSameAs extends BaseMatcher { |
+ final _expected; |
+ const _IsSameAs(this._expected); |
+ bool matches(item) => item === _expected; |
+ // If all types were hashable we could show a hash here. |
+ Description describe(Description description) => |
+ description.add('same instance as ').addDescriptionOf(_expected); |
+} |
+ |
+/** Returns a matcher that matches if two objects are equal (==). */ |
+Matcher equals(expected) => new _IsEqual(expected); |
+ |
+class _IsEqual extends BaseMatcher { |
+ final _expected; |
+ const _IsEqual(this._expected); |
+ bool matches(item) => item == _expected; |
+ Description describe(Description description) => |
+ description.addDescriptionOf(_expected); |
+} |
+ |
+/** A matcher that matches any value. */ |
+final Matcher anything = const _IsAnything(); |
+ |
+class _IsAnything extends BaseMatcher { |
+ const _IsAnything(); |
+ bool matches(item) => true; |
+ Description describe(Description description) => |
+ description.add('anything'); |
+} |
+ |
+/** |
+ * 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 BaseMatcher { |
+ final String _name; |
+ const isInstanceOf([this._name = 'specified type']); |
+ bool matches(obj) => obj is T; |
+ // The description here is lame :-( |
+ Description describe(Description description) => |
+ description.add('an instance of ${_name}'); |
+} |
+ |
+/** |
+ * 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 Matcher 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). |
+ */ |
+Matcher throwsA(Matcher matcher) => new _Throws(matcher); |
+ |
+/** |
+ * 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 Matcher returnsNormally = const _ReturnsNormally(); |
+ |
+class _Throws extends BaseMatcher { |
+ final Matcher _matcher; |
+ |
+ const _Throws([Matcher this._matcher = null]); |
+ |
+ bool matches(item) { |
+ try { |
+ item(); |
+ return false; |
+ } catch (final e) { |
+ return _matcher == null || _matcher.matches(e); |
+ } |
+ } |
+ |
+ Description describe(Description description) { |
+ if (_matcher == null) { |
+ return description.add("throws an exception"); |
+ } else { |
+ return description.add('throws an exception which matches '). |
+ addDescriptionOf(_matcher); |
+ } |
+ } |
+ |
+ Description describeMismatch(item, Description mismatchDescription) { |
+ try { |
+ item(); |
+ return mismatchDescription.add(' no exception'); |
+ } catch (final e) { |
+ return mismatchDescription.add(' exception does not match '). |
+ addDescriptionOf(_matcher); |
+ } |
+ } |
+} |
+ |
+class _ReturnsNormally extends BaseMatcher { |
+ |
+ const _ReturnsNormally(); |
+ |
+ bool matches(f) { |
+ try { |
+ f(); |
+ return true; |
+ } catch (final e) { |
+ return false; |
+ } |
+ } |
+ |
+ Description describe(Description description) => |
+ description.add("return normally"); |
+ |
+ Description describeMismatch(item, Description mismatchDescription) { |
+ return mismatchDescription.add(' threw exception'); |
+ } |
+} |
+ |
+/** A matcher for functions that throw BadNumberFormatException */ |
+final Matcher throwsBadNumberFormatException = |
+ new _Throws(new isInstanceOf<BadNumberFormatException>()); |
+ |
+/** A matcher for functions that throw an Exception */ |
+final Matcher throwsException = |
+ new _Throws(new isInstanceOf<Exception>()); |
+ |
+/** A matcher for functions that throw an IllegalArgumentException */ |
+final Matcher throwsIllegalArgumentException = |
+ new _Throws(new isInstanceOf<IllegalArgumentException>()); |
+ |
+/** A matcher for functions that throw an IllegalJSRegExpException */ |
+final Matcher throwsIllegalJSRegExpException = |
+ new _Throws(new isInstanceOf<IllegalJSRegExpException>()); |
+ |
+/** A matcher for functions that throw an IndexOutOfRangeException */ |
+final Matcher throwsIndexOutOfRangeException = |
+ new _Throws(new isInstanceOf<IndexOutOfRangeException>()); |
+ |
+/** A matcher for functions that throw a NoSuchMethodException */ |
+final Matcher throwsNoSuchMethodException = |
+ new _Throws(new isInstanceOf<NoSuchMethodException>()); |
+ |
+/** A matcher for functions that throw a NotImplementedException */ |
+final Matcher throwsNotImplementedException = |
+ new _Throws(new isInstanceOf<NotImplementedException>()); |
+ |
+/** A matcher for functions that throw a NullPointerException */ |
+final Matcher throwsNullPointerException = |
+ new _Throws(new isInstanceOf<NullPointerException>()); |
+ |
+/** A matcher for functions that throw an UnsupportedOperationException */ |
+final Matcher throwsUnsupportedOperationException = |
+ new _Throws(new isInstanceOf<UnsupportedOperationException>()); |
+ |
+/** |
+ * Returns a matcher that matches if an object has a length property |
+ * that matches [matcher]. |
+ */ |
+Matcher hasLength(matcher) => |
+ new _HasLength(wrapMatcher(matcher)); |
+ |
+class _HasLength extends BaseMatcher { |
+ final Matcher _matcher; |
+ const _HasLength([Matcher this._matcher = null]); |
+ |
+ bool matches(item) { |
+ return _matcher.matches(item.length); |
+ } |
+ |
+ Description describe(Description description) => |
+ description.add('an object with length of '). |
+ addDescriptionOf(_matcher); |
+ |
+ Description describeMismatch(item, Description mismatchDescription) { |
+ super.describeMismatch(item, mismatchDescription); |
+ 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. |
+ 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. |
+ */ |
+Matcher recursivelyMatches(expected, [limit=1000]) => |
+ new _DeepMatcher(expected, limit); |
+ |
+// A utility function for comparing iterators |
+ |
+String _compareIterables(expected, actual, matcher) { |
+ if (actual is !Iterable) { |
+ return 'is not Iterable'; |
+ } |
+ var expectedIterator = expected.iterator(); |
+ var actualIterator = actual.iterator(); |
+ var position = 0; |
+ String reason = null; |
+ while (reason == null) { |
+ if (expectedIterator.hasNext()) { |
+ if (actualIterator.hasNext()) { |
+ reason = matcher(expectedIterator.next(), |
+ actualIterator.next(), |
+ 'mismatch at position ${position}'); |
+ ++position; |
+ } else { |
+ reason = 'shorter than expected'; |
+ } |
+ } else if (actualIterator.hasNext()) { |
+ reason = 'longer than expected'; |
+ } else { |
+ return null; |
+ } |
+ } |
+ return reason; |
+} |
+ |
+class _DeepMatcher extends BaseMatcher { |
+ 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) { |
+ reason = 'item comparison limit exceeded'; |
+ } else if (expected is Iterable) { |
+ reason = _compareIterables(expected, actual, _recursiveMatch); |
+ } 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; |
+ |
+ Description describe(Description description) => |
+ description.add('recursively matches ').addDescriptionOf(_expected); |
+ |
+ Description describeMismatch(item, Description 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. In the case of collections, |
+ * [expected] can itself be a matcher. |
+ */ |
+Matcher contains(expected) => new _Contains(expected); |
+ |
+class _Contains extends BaseMatcher { |
+ |
+ final _expected; |
+ |
+ const _Contains(this._expected); |
+ |
+ bool matches(item) { |
+ if (item is String) { |
+ return item.indexOf(_expected) >= 0; |
+ } else if (item is Collection) { |
+ if (_expected is Matcher) { |
+ return item.some((e) => _expected.matches(e)); |
+ } else { |
+ return item.some((e) => e == _expected); |
+ } |
+ } else if (item is Map) { |
+ return item.containsKey(_expected); |
+ } |
+ return false; |
+ } |
+ |
+ Description describe(Description description) => |
+ description.add('contains ').addDescriptionOf(_expected); |
+} |