include a copy of old matcher

R=nweiz@google.com

Review URL: https://codereview.chromium.org//1135653004
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bd4967..20698e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,10 @@
 ##0.11.6
 
+* Merge in the last `0.11.x` release of `matcher` to allow projects to use both
+  `test` and `unittest` without conflicts.
+
 * Fix running individual tests with `HtmlIndividualConfiguration` when the test
-  name contains URI-escaped values and is provided with the `group` query 
+  name contains URI-escaped values and is provided with the `group` query
   parameter.
 
 ##0.11.5+4
diff --git a/README.md b/README.md
index f9ff2f9..faad9a8 100644
--- a/README.md
+++ b/README.md
@@ -1,141 +1,5 @@
-Support for writing unit tests in Dart.
+The `unittest` package has been renamed [`test`][test]. It will export `test`'s
+API through the `0.12.x` branch, but it is deprecated and `test` should be used
+instead.
 
-**See also:**
-[Unit Testing with Dart]
-(http://www.dartlang.org/articles/dart-unit-tests/)
-
-##Concepts
-
- * __Tests__: Tests are specified via the top-level function [test], they can be
-   organized together using [group].
-
- * __Checks__: Test expectations can be specified via [expect]
-
- * __Matchers__: [expect] assertions are written declaratively using the
-   [Matcher] class.
-
- * __Configuration__: The framework can be adapted by setting
-   [unittestConfiguration] with a [Configuration]. See the other libraries
-   in the `unittest` package for alternative implementations of
-   [Configuration] including `compact_vm_config.dart`, `html_config.dart`
-   and `html_enhanced_config.dart`.
-
-##Examples
-
-A trivial test:
-
-```dart
-import 'package:unittest/unittest.dart';
-
-void main() {
-  test('this is a test', () {
-    int x = 2 + 3;
-    expect(x, equals(5));
-  });
-}
-```
-
-Multiple tests:
-
-```dart
-import 'package:unittest/unittest.dart';
-
-void main() {
-  test('this is a test', () {
-    int x = 2 + 3;
-    expect(x, equals(5));
-  });
-  test('this is another test', () {
-    int x = 2 + 3;
-    expect(x, equals(5));
-  });
-}
-```
-
-Multiple tests, grouped by category:
-
-```dart
-import 'package:unittest/unittest.dart';
-
-void main() {
-  group('group A', () {
-    test('test A.1', () {
-      int x = 2 + 3;
-      expect(x, equals(5));
-    });
-    test('test A.2', () {
-      int x = 2 + 3;
-      expect(x, equals(5));
-    });
-  });
-  group('group B', () {
-    test('this B.1', () {
-      int x = 2 + 3;
-      expect(x, equals(5));
-    });
-  });
-}
-```
-
-Asynchronous tests: if callbacks expect between 0 and 6 positional
-arguments, [expectAsync] will wrap a function into a new callback and will
-not consider the test complete until that callback is run. A count argument
-can be provided to specify the number of times the callback should be called
-(the default is 1).
-
-```dart
-import 'dart:async';
-import 'package:unittest/unittest.dart';
-
-void main() {
-  test('callback is executed once', () {
-    // wrap the callback of an asynchronous call with [expectAsync] if
-    // the callback takes 0 arguments...
-    Timer.run(expectAsync(() {
-      int x = 2 + 3;
-      expect(x, equals(5));
-    }));
-  });
-
-  test('callback is executed twice', () {
-    var callback = expectAsync(() {
-      int x = 2 + 3;
-      expect(x, equals(5));
-    }, count: 2); // <-- we can indicate multiplicity to [expectAsync]
-    Timer.run(callback);
-    Timer.run(callback);
-  });
-}
-```
-
-There may be times when the number of times a callback should be called is
-non-deterministic. In this case a dummy callback can be created with
-expectAsync((){}) and this can be called from the real callback when it is
-finally complete.
-
-A variation on this is [expectAsyncUntil], which takes a callback as the
-first parameter and a predicate function as the second parameter. After each
-time the callback is called, the predicate function will be called. If it
-returns `false` the test will still be considered incomplete.
-
-Test functions can return [Future]s, which provide another way of doing
-asynchronous tests. The test framework will handle exceptions thrown by
-the Future, and will advance to the next test when the Future is complete.
-
-```dart
-import 'dart:async';
-import 'package:unittest/unittest.dart';
-
-void main() {
-  test('test that time has passed', () {
-    var duration = const Duration(milliseconds: 200);
-    var time = new DateTime.now();
-
-    return new Future.delayed(duration).then((_) {
-      var delta = new DateTime.now().difference(time);
-
-      expect(delta, greaterThanOrEqualTo(duration));
-    });
-  });
-}
-```
+[test]: https://pub.dartlang.org/packages/test
diff --git a/lib/src/matcher.dart b/lib/src/matcher.dart
new file mode 100644
index 0000000..cfd5857
--- /dev/null
+++ b/lib/src/matcher.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, 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.
+
+/// Support for specifying test expectations, such as for unit tests.
+library unittest.matcher;
+
+export 'matcher/core_matchers.dart';
+export 'matcher/description.dart';
+export 'matcher/error_matchers.dart';
+export 'matcher/expect.dart';
+export 'matcher/future_matchers.dart';
+export 'matcher/interfaces.dart';
+export 'matcher/iterable_matchers.dart';
+export 'matcher/map_matchers.dart';
+export 'matcher/numeric_matchers.dart';
+export 'matcher/operator_matchers.dart';
+export 'matcher/prints_matcher.dart';
+export 'matcher/string_matchers.dart';
+export 'matcher/throws_matcher.dart';
+export 'matcher/throws_matchers.dart';
+export 'matcher/util.dart';
diff --git a/lib/src/matcher/core_matchers.dart b/lib/src/matcher/core_matchers.dart
new file mode 100644
index 0000000..dbba367
--- /dev/null
+++ b/lib/src/matcher/core_matchers.dart
@@ -0,0 +1,645 @@
+// 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.
+
+library unittest.matcher.core_matchers;
+
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher that matches the isEmpty property.
+const Matcher isEmpty = const _Empty();
+
+class _Empty extends Matcher {
+  const _Empty();
+
+  bool matches(item, Map matchState) => item.isEmpty;
+
+  Description describe(Description description) => description.add('empty');
+}
+
+/// Returns a matcher that matches the isNotEmpty property.
+const Matcher isNotEmpty = const _NotEmpty();
+
+class _NotEmpty extends Matcher {
+  const _NotEmpty();
+
+  bool matches(item, Map matchState) => item.isNotEmpty;
+
+  Description describe(Description description) => description.add('non-empty');
+}
+
+/// A matcher that matches any null value.
+const Matcher isNull = const _IsNull();
+
+/// A matcher that matches any non-null value.
+const Matcher isNotNull = const _IsNotNull();
+
+class _IsNull extends Matcher {
+  const _IsNull();
+  bool matches(item, Map matchState) => item == null;
+  Description describe(Description description) => description.add('null');
+}
+
+class _IsNotNull extends Matcher {
+  const _IsNotNull();
+  bool matches(item, Map matchState) => item != null;
+  Description describe(Description description) => description.add('not null');
+}
+
+/// A matcher that matches the Boolean value true.
+const Matcher isTrue = const _IsTrue();
+
+/// A matcher that matches anything except the Boolean value true.
+const Matcher isFalse = const _IsFalse();
+
+class _IsTrue extends Matcher {
+  const _IsTrue();
+  bool matches(item, Map matchState) => item == true;
+  Description describe(Description description) => description.add('true');
+}
+
+class _IsFalse extends Matcher {
+  const _IsFalse();
+  bool matches(item, Map matchState) => item == false;
+  Description describe(Description description) => description.add('false');
+}
+
+/// A matcher that matches the numeric value NaN.
+const Matcher isNaN = const _IsNaN();
+
+/// A matcher that matches any non-NaN value.
+const Matcher isNotNaN = const _IsNotNaN();
+
+class _IsNaN extends Matcher {
+  const _IsNaN();
+  bool matches(item, Map matchState) => double.NAN.compareTo(item) == 0;
+  Description describe(Description description) => description.add('NaN');
+}
+
+class _IsNotNaN extends Matcher {
+  const _IsNotNaN();
+  bool matches(item, Map matchState) => double.NAN.compareTo(item) != 0;
+  Description describe(Description description) => description.add('not NaN');
+}
+
+/// Returns a matches that matches if the value is the same instance
+/// as [expected], using [identical].
+Matcher same(expected) => new _IsSameAs(expected);
+
+class _IsSameAs extends Matcher {
+  final _expected;
+  const _IsSameAs(this._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);
+}
+
+/// Returns a matcher that matches if the value is structurally equal to
+/// [expected].
+///
+/// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
+/// for equality using `==` on the expected value.
+///
+/// For [Iterable]s and [Map]s, this will recursively match the elements. To
+/// handle cyclic structures a recursion depth [limit] can be provided. The
+/// default limit is 100. [Set]s will be compared order-independently.
+Matcher equals(expected, [int limit = 100]) => expected is String
+    ? new _StringEqualsMatcher(expected)
+    : new _DeepMatcher(expected, limit);
+
+class _DeepMatcher extends Matcher {
+  final _expected;
+  final int _limit;
+  var count;
+
+  _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
+
+  // Returns a pair (reason, location)
+  List _compareIterables(expected, actual, matcher, depth, location) {
+    if (actual is! Iterable) return ['is not Iterable', location];
+
+    var expectedIterator = expected.iterator;
+    var actualIterator = actual.iterator;
+    for (var index = 0; ; index++) {
+      // Advance in lockstep.
+      var expectedNext = expectedIterator.moveNext();
+      var actualNext = actualIterator.moveNext();
+
+      // If we reached the end of both, we succeeded.
+      if (!expectedNext && !actualNext) return null;
+
+      // Fail if their lengths are different.
+      var newLocation = '${location}[${index}]';
+      if (!expectedNext) return ['longer than expected', newLocation];
+      if (!actualNext) return ['shorter than expected', newLocation];
+
+      // Match the elements.
+      var rp = matcher(
+          expectedIterator.current, actualIterator.current, newLocation, depth);
+      if (rp != null) return rp;
+    }
+  }
+
+  List _compareSets(Set expected, actual, matcher, depth, location) {
+    if (actual is! Iterable) return ['is not Iterable', location];
+    actual = actual.toSet();
+
+    for (var expectedElement in expected) {
+      if (actual.every((actualElement) =>
+          matcher(expectedElement, actualElement, location, depth) != null)) {
+        return ['does not contain $expectedElement', location];
+      }
+    }
+
+    if (actual.length > expected.length) {
+      return ['larger than expected', location];
+    } else if (actual.length < expected.length) {
+      return ['smaller than expected', location];
+    } else {
+      return null;
+    }
+  }
+
+  List _recursiveMatch(expected, actual, String location, int depth) {
+    // If the expected value is a matcher, try to match it.
+    if (expected is Matcher) {
+      var matchState = {};
+      if (expected.matches(actual, matchState)) return null;
+
+      var description = new StringDescription();
+      expected.describe(description);
+      return ['does not match $description', location];
+    } else {
+      // Otherwise, test for equality.
+      try {
+        if (expected == actual) return null;
+      } catch (e) {
+        // TODO(gram): Add a test for this case.
+        return ['== threw "$e"', location];
+      }
+    }
+
+    if (depth > _limit) return ['recursion depth limit exceeded', location];
+
+    // If _limit is 1 we can only recurse one level into object.
+    if (depth == 0 || _limit > 1) {
+      if (expected is Set) {
+        return _compareSets(
+            expected, actual, _recursiveMatch, depth + 1, location);
+      } else if (expected is Iterable) {
+        return _compareIterables(
+            expected, actual, _recursiveMatch, depth + 1, location);
+      } else if (expected is Map) {
+        if (actual is! Map) return ['expected a map', location];
+
+        var err = (expected.length == actual.length)
+            ? ''
+            : 'has different length and ';
+        for (var key in expected.keys) {
+          if (!actual.containsKey(key)) {
+            return ["${err}is missing map key '$key'", location];
+          }
+        }
+
+        for (var key in actual.keys) {
+          if (!expected.containsKey(key)) {
+            return ["${err}has extra map key '$key'", location];
+          }
+        }
+
+        for (var key in expected.keys) {
+          var rp = _recursiveMatch(
+              expected[key], actual[key], "${location}['${key}']", depth + 1);
+          if (rp != null) return rp;
+        }
+
+        return null;
+      }
+    }
+
+    var description = new StringDescription();
+
+    // If we have recursed, show the expected value too; if not, expect() will
+    // show it for us.
+    if (depth > 0) {
+      description
+          .add('was ')
+          .addDescriptionOf(actual)
+          .add(' instead of ')
+          .addDescriptionOf(expected);
+      return [description.toString(), location];
+    }
+
+    // We're not adding any value to the actual value.
+    return ["", 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;
+  }
+
+  bool matches(item, Map matchState) =>
+      _match(_expected, item, matchState) == null;
+
+  Description describe(Description description) =>
+      description.addDescriptionOf(_expected);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, 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.
+class _StringEqualsMatcher extends Matcher {
+  final String _value;
+
+  _StringEqualsMatcher(this._value);
+
+  bool get showActualValue => true;
+
+  bool matches(item, Map matchState) => _value == item;
+
+  Description describe(Description description) =>
+      description.addDescriptionOf(_value);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! String) {
+      return mismatchDescription.addDescriptionOf(item).add('is not a string');
+    } else {
+      var buff = new StringBuffer();
+      buff.write('is different.');
+      var escapedItem = escape(item);
+      var escapedValue = escape(_value);
+      int minLength = escapedItem.length < escapedValue.length
+          ? escapedItem.length
+          : escapedValue.length;
+      int start;
+      for (start = 0; start < minLength; start++) {
+        if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
+          break;
+        }
+      }
+      if (start == minLength) {
+        if (escapedValue.length < escapedItem.length) {
+          buff.write(' Both strings start the same, but the given value also'
+              ' has the following trailing characters: ');
+          _writeTrailing(buff, escapedItem, escapedValue.length);
+        } else {
+          buff.write(' Both strings start the same, but the given value is'
+              ' missing the following trailing characters: ');
+          _writeTrailing(buff, escapedValue, escapedItem.length);
+        }
+      } else {
+        buff.write('\nExpected: ');
+        _writeLeading(buff, escapedValue, start);
+        _writeTrailing(buff, escapedValue, start);
+        buff.write('\n  Actual: ');
+        _writeLeading(buff, escapedItem, start);
+        _writeTrailing(buff, escapedItem, start);
+        buff.write('\n          ');
+        for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
+        buff.write('^\n Differ at offset $start');
+      }
+
+      return mismatchDescription.replace(buff.toString());
+    }
+  }
+
+  static void _writeLeading(StringBuffer buff, String s, int start) {
+    if (start > 10) {
+      buff.write('... ');
+      buff.write(s.substring(start - 10, start));
+    } else {
+      buff.write(s.substring(0, start));
+    }
+  }
+
+  static void _writeTrailing(StringBuffer buff, String s, int start) {
+    if (start + 10 > s.length) {
+      buff.write(s.substring(start));
+    } else {
+      buff.write(s.substring(start, start + 10));
+      buff.write(' ...');
+    }
+  }
+}
+
+/// A matcher that matches any value.
+const Matcher anything = const _IsAnything();
+
+class _IsAnything extends Matcher {
+  const _IsAnything();
+  bool matches(item, Map matchState) => 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>());
+class isInstanceOf<T> extends Matcher {
+  /// The [name] parameter does nothing; it's deprecated and will be removed in
+  /// future version of [matcher].
+  const isInstanceOf([@deprecated String name]);
+
+  bool matches(obj, Map matchState) => obj is T;
+
+  Description describe(Description description) =>
+      description.add('an instance of $T');
+}
+
+/// 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.
+const Matcher returnsNormally = const _ReturnsNormally();
+
+class _ReturnsNormally extends Matcher {
+  const _ReturnsNormally();
+
+  bool matches(f, Map matchState) {
+    try {
+      f();
+      return true;
+    } catch (e, s) {
+      addStateInfo(matchState, {'exception': e, 'stack': s});
+      return false;
+    }
+  }
+
+  Description describe(Description description) =>
+      description.add("return normally");
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
+    if (verbose) {
+      mismatchDescription.add(' at ').add(matchState['stack'].toString());
+    }
+    return mismatchDescription;
+  }
+}
+
+/*
+ * Matchers for different exception types. Ideally we should just be able to
+ * use something like:
+ *
+ * final Matcher throwsException =
+ *     const _Throws(const isInstanceOf<Exception>());
+ *
+ * Unfortunately instanceOf is not working with dart2js.
+ *
+ * Alternatively, if static functions could be used in const expressions,
+ * we could use:
+ *
+ * bool _isException(x) => x is Exception;
+ * final Matcher isException = const _Predicate(_isException, "Exception");
+ * final Matcher throwsException = const _Throws(isException);
+ *
+ * But currently using static functions in const expressions is not supported.
+ * For now the only solution for all platforms seems to be separate classes
+ * for each exception type.
+ */
+
+abstract class TypeMatcher extends Matcher {
+  final String _name;
+  const TypeMatcher(this._name);
+  Description describe(Description description) => description.add(_name);
+}
+
+/// A matcher for Map types.
+const Matcher isMap = const _IsMap();
+
+class _IsMap extends TypeMatcher {
+  const _IsMap() : super("Map");
+  bool matches(item, Map matchState) => item is Map;
+}
+
+/// A matcher for List types.
+const Matcher isList = const _IsList();
+
+class _IsList extends TypeMatcher {
+  const _IsList() : super("List");
+  bool matches(item, Map matchState) => item is List;
+}
+
+/// 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 Matcher {
+  final Matcher _matcher;
+  const _HasLength([Matcher matcher = null]) : this._matcher = matcher;
+
+  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) =>
+      description.add('an object with length of ').addDescriptionOf(_matcher);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    try {
+      // We want to generate a different description if there is no length
+      // property; we use the same trick as in matches().
+      if (item.length * item.length >= 0) {
+        return mismatchDescription
+            .add('has length of ')
+            .addDescriptionOf(item.length);
+      }
+    } catch (e) {}
+    return mismatchDescription.add('has no length property');
+  }
+}
+
+/// Returns a matcher that matches if the match argument contains the expected
+/// value.
+///
+/// For [String]s this means substring matching;
+/// for [Map]s it means the map has the key, and for [Iterable]s
+/// it means the iterable has a matching element. In the case of iterables,
+/// [expected] can itself be a matcher.
+Matcher contains(expected) => new _Contains(expected);
+
+class _Contains extends Matcher {
+  final _expected;
+
+  const _Contains(this._expected);
+
+  bool matches(item, Map matchState) {
+    if (item is String) {
+      return item.indexOf(_expected) >= 0;
+    } else if (item is Iterable) {
+      if (_expected is Matcher) {
+        return item.any((e) => _expected.matches(e, matchState));
+      } else {
+        return item.contains(_expected);
+      }
+    } else if (item is Map) {
+      return item.containsKey(_expected);
+    }
+    return false;
+  }
+
+  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');
+    }
+  }
+}
+
+/// Returns a matcher that matches if the match argument is in
+/// the expected value. This is the converse of [contains].
+Matcher isIn(expected) => new _In(expected);
+
+class _In extends Matcher {
+  final _expected;
+
+  const _In(this._expected);
+
+  bool matches(item, Map matchState) {
+    if (_expected is String) {
+      return _expected.indexOf(item) >= 0;
+    } else if (_expected is Iterable) {
+      return _expected.any((e) => e == item);
+    } else if (_expected is Map) {
+      return _expected.containsKey(item);
+    }
+    return false;
+  }
+
+  Description describe(Description description) =>
+      description.add('is in ').addDescriptionOf(_expected);
+}
+
+/// Returns a matcher that uses an arbitrary function that returns
+/// true or false for the actual value.
+///
+/// For example:
+///
+///     expect(v, predicate((x) => ((x % 2) == 0), "is even"))
+Matcher predicate(bool f(value), [String description = 'satisfies function']) =>
+    new _Predicate(f, description);
+
+typedef bool _PredicateFunction(value);
+
+class _Predicate extends Matcher {
+  final _PredicateFunction _matcher;
+  final String _description;
+
+  const _Predicate(this._matcher, this._description);
+
+  bool matches(item, Map matchState) => _matcher(item);
+
+  Description describe(Description description) =>
+      description.add(_description);
+}
+
+/// A useful utility class for implementing other matchers through inheritance.
+/// Derived classes should call the base constructor with a feature name and
+/// description, and an instance matcher, and should implement the
+/// [featureValueOf] abstract method.
+///
+/// The feature description will typically describe the item and the feature,
+/// while the feature name will just name the feature. For example, we may
+/// have a Widget class where each Widget has a price; we could make a
+/// [CustomMatcher] that can make assertions about prices with:
+///
+///     class HasPrice extends CustomMatcher {
+///       const HasPrice(matcher) :
+///           super("Widget with price that is", "price", matcher);
+///       featureValueOf(actual) => actual.price;
+///     }
+///
+/// and then use this for example like:
+///
+///      expect(inventoryItem, new HasPrice(greaterThan(0)));
+class CustomMatcher extends Matcher {
+  final String _featureDescription;
+  final String _featureName;
+  final Matcher _matcher;
+
+  CustomMatcher(this._featureDescription, this._featureName, matcher)
+      : this._matcher = wrapMatcher(matcher);
+
+  /// Override this to extract the interesting feature.
+  featureValueOf(actual) => actual;
+
+  bool matches(item, Map matchState) {
+    var f = featureValueOf(item);
+    if (_matcher.matches(f, matchState)) return true;
+    addStateInfo(matchState, {'feature': f});
+    return false;
+  }
+
+  Description describe(Description description) =>
+      description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, 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;
+  }
+}
diff --git a/lib/src/matcher/description.dart b/lib/src/matcher/description.dart
new file mode 100644
index 0000000..3f2862f
--- /dev/null
+++ b/lib/src/matcher/description.dart
@@ -0,0 +1,68 @@
+// 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.
+
+library unittest.matcher.description;
+
+import 'interfaces.dart';
+import 'pretty_print.dart';
+
+/// The default implementation of [Description]. This should rarely need
+/// substitution, although conceivably it is a place where other languages
+/// could be supported.
+class StringDescription implements Description {
+  final StringBuffer _out = new StringBuffer();
+
+  /// Initialize the description with initial contents [init].
+  StringDescription([String init = '']) {
+    _out.write(init);
+  }
+
+  int get length => _out.length;
+
+  /// Get the description as a string.
+  String toString() => _out.toString();
+
+  /// Append [text] to the description.
+  Description add(String text) {
+    _out.write(text);
+    return this;
+  }
+
+  /// Change the value of the description.
+  Description replace(String text) {
+    _out.clear();
+    return add(text);
+  }
+
+  /// Appends a description of [value]. If it is an IMatcher use its
+  /// describe method; if it is a string use its literal value after
+  /// escaping any embedded control characters; otherwise use its
+  /// toString() value and wrap it in angular "quotes".
+  Description addDescriptionOf(value) {
+    if (value is Matcher) {
+      value.describe(this);
+    } else {
+      add(prettyPrint(value, maxLineLength: 80, maxItems: 25));
+    }
+    return this;
+  }
+
+  /// Append an [Iterable] [list] of objects to the description, using the
+  /// specified [separator] and framing the list with [start]
+  /// and [end].
+  Description addAll(
+      String start, String separator, String end, Iterable list) {
+    var separate = false;
+    add(start);
+    for (var item in list) {
+      if (separate) {
+        add(separator);
+      }
+      addDescriptionOf(item);
+      separate = true;
+    }
+    add(end);
+    return this;
+  }
+}
diff --git a/lib/src/matcher/error_matchers.dart b/lib/src/matcher/error_matchers.dart
new file mode 100644
index 0000000..503f32d
--- /dev/null
+++ b/lib/src/matcher/error_matchers.dart
@@ -0,0 +1,97 @@
+// 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.
+
+library unittest.matcher.error_matchers;
+
+import 'core_matchers.dart';
+import 'interfaces.dart';
+
+/// A matcher for ArgumentErrors.
+const Matcher isArgumentError = const _ArgumentError();
+
+class _ArgumentError extends TypeMatcher {
+  const _ArgumentError() : super("ArgumentError");
+  bool matches(item, Map matchState) => item is ArgumentError;
+}
+
+/// A matcher for ConcurrentModificationError.
+const Matcher isConcurrentModificationError =
+    const _ConcurrentModificationError();
+
+class _ConcurrentModificationError extends TypeMatcher {
+  const _ConcurrentModificationError() : super("ConcurrentModificationError");
+  bool matches(item, Map matchState) => item is ConcurrentModificationError;
+}
+
+/// A matcher for CyclicInitializationError.
+const Matcher isCyclicInitializationError = const _CyclicInitializationError();
+
+class _CyclicInitializationError extends TypeMatcher {
+  const _CyclicInitializationError() : super("CyclicInitializationError");
+  bool matches(item, Map matchState) => item is CyclicInitializationError;
+}
+
+/// A matcher for Exceptions.
+const Matcher isException = const _Exception();
+
+class _Exception extends TypeMatcher {
+  const _Exception() : super("Exception");
+  bool matches(item, Map matchState) => item is Exception;
+}
+
+/// A matcher for FormatExceptions.
+const Matcher isFormatException = const _FormatException();
+
+class _FormatException extends TypeMatcher {
+  const _FormatException() : super("FormatException");
+  bool matches(item, Map matchState) => item is FormatException;
+}
+
+/// A matcher for NoSuchMethodErrors.
+const Matcher isNoSuchMethodError = const _NoSuchMethodError();
+
+class _NoSuchMethodError extends TypeMatcher {
+  const _NoSuchMethodError() : super("NoSuchMethodError");
+  bool matches(item, Map matchState) => item is NoSuchMethodError;
+}
+
+/// A matcher for NullThrownError.
+const Matcher isNullThrownError = const _NullThrownError();
+
+class _NullThrownError extends TypeMatcher {
+  const _NullThrownError() : super("NullThrownError");
+  bool matches(item, Map matchState) => item is NullThrownError;
+}
+
+/// A matcher for RangeErrors.
+const Matcher isRangeError = const _RangeError();
+
+class _RangeError extends TypeMatcher {
+  const _RangeError() : super("RangeError");
+  bool matches(item, Map matchState) => item is RangeError;
+}
+
+/// A matcher for StateErrors.
+const Matcher isStateError = const _StateError();
+
+class _StateError extends TypeMatcher {
+  const _StateError() : super("StateError");
+  bool matches(item, Map matchState) => item is StateError;
+}
+
+/// A matcher for UnimplementedErrors.
+const Matcher isUnimplementedError = const _UnimplementedError();
+
+class _UnimplementedError extends TypeMatcher {
+  const _UnimplementedError() : super("UnimplementedError");
+  bool matches(item, Map matchState) => item is UnimplementedError;
+}
+
+/// A matcher for UnsupportedError.
+const Matcher isUnsupportedError = const _UnsupportedError();
+
+class _UnsupportedError extends TypeMatcher {
+  const _UnsupportedError() : super("UnsupportedError");
+  bool matches(item, Map matchState) => item is UnsupportedError;
+}
diff --git a/lib/src/matcher/expect.dart b/lib/src/matcher/expect.dart
new file mode 100644
index 0000000..eeeeeff
--- /dev/null
+++ b/lib/src/matcher/expect.dart
@@ -0,0 +1,177 @@
+// 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.
+
+library unittest.matcher.expect;
+
+import 'core_matchers.dart';
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// The objects thrown by the default failure handler.
+class TestFailure extends Error {
+  final String message;
+
+  TestFailure(this.message);
+
+  String toString() => message;
+}
+
+/// Failed matches are reported using a default IFailureHandler.
+/// The default implementation simply throws [TestFailure]s;
+/// this can be replaced by some other implementation of
+/// IFailureHandler by calling configureExpectHandler.
+abstract class FailureHandler {
+  /// This handles failures given a textual decription
+  void fail(String reason);
+
+  /// This handles failures given the actual [value], the [matcher]
+  /// the [reason] (argument from [expect]), some additonal [matchState]
+  /// generated by the [matcher], and a verbose flag which controls in
+  /// some cases how much [matchState] information is used. It will use
+  /// these to create a detailed error message (typically by calling
+  /// an [ErrorFormatter]) and then call [fail] with this message.
+  void failMatch(
+      actual, Matcher matcher, String reason, Map matchState, bool verbose);
+}
+
+/// The ErrorFormatter type is used for functions that
+/// can be used to build up error reports upon [expect] failures.
+/// There is one built-in implementation ([defaultErrorFormatter])
+/// which is used by the default failure handler. If the failure handler
+/// is replaced it may be desirable to replace the [stringDescription]
+/// error formatter with another.
+typedef String ErrorFormatter(
+    actual, Matcher matcher, String reason, Map matchState, bool verbose);
+
+/// This Function is used by certain Matchers to catch certain exceptions.
+///
+/// Some matchers, like those for Futures and exception testing,
+/// can fail in asynchronous sections, and throw exceptions.
+/// A user of this library will typically want to catch and handle
+/// such exceptions. The [wrapAsync] property is a function that
+/// can wrap callbacks used by these Matchers so that they can be
+/// used safely. For example, the unittest library will set this
+/// to be `expectAsync`. By default this is an identity function.
+Function wrapAsync = (Function f, [id]) => f;
+
+/// Assert that [actual] matches [matcher].
+///
+/// This is the main assertion function. [reason] is optional and is typically
+/// not supplied, as a reason is generated from the matcher; if [reason]
+/// is included it is appended to the reason generated by the matcher.
+///
+/// [matcher] can be a value in which case it will be wrapped in an
+/// [equals] matcher.
+///
+/// If the assertion fails, then the default behavior is to throw a
+/// [TestFailure], but this behavior can be changed by calling
+/// [configureExpectFailureHandler] and providing an alternative handler that
+/// implements the [IFailureHandler] interface. It is also possible to
+/// pass a [failureHandler] to [expect] as a final parameter for fine-
+/// grained control.
+///
+/// In some cases extra diagnostic info can be produced on failure (for
+/// example, stack traces on mismatched exceptions). To enable these,
+/// [verbose] should be specified as true;
+void expect(actual, matcher,
+    {String reason, FailureHandler failureHandler, bool verbose: false}) {
+  matcher = wrapMatcher(matcher);
+  bool doesMatch;
+  var matchState = {};
+  try {
+    doesMatch = matcher.matches(actual, matchState);
+  } catch (e, trace) {
+    doesMatch = false;
+    if (reason == null) {
+      reason = '${(e is String) ? e : e.toString()} at $trace';
+    }
+  }
+  if (!doesMatch) {
+    if (failureHandler == null) {
+      failureHandler = getOrCreateExpectFailureHandler();
+    }
+    failureHandler.failMatch(actual, matcher, reason, matchState, verbose);
+  }
+}
+
+void fail(String message, {FailureHandler failureHandler}) {
+  if (failureHandler == null) {
+    failureHandler = getOrCreateExpectFailureHandler();
+  }
+  failureHandler.fail(message);
+}
+
+// The handler for failed asserts.
+FailureHandler _assertFailureHandler = null;
+
+// The default failure handler that throws [TestFailure]s.
+class DefaultFailureHandler implements FailureHandler {
+  DefaultFailureHandler() {
+    if (_assertErrorFormatter == null) {
+      _assertErrorFormatter = _defaultErrorFormatter;
+    }
+  }
+  void fail(String reason) {
+    throw new TestFailure(reason);
+  }
+  void failMatch(
+      actual, Matcher matcher, String reason, Map matchState, bool verbose) {
+    fail(_assertErrorFormatter(actual, matcher, reason, matchState, verbose));
+  }
+}
+
+/// Changes the default failure handler for [expect].
+///
+/// [handler] is a reference to the new handler; if this is omitted
+/// or null then the failure handler is reset to the default, which
+/// throws [TestFailure]s on [expect] assertion failures.
+void configureExpectFailureHandler([FailureHandler handler = null]) {
+  if (handler == null) {
+    handler = new DefaultFailureHandler();
+  }
+  _assertFailureHandler = handler;
+}
+
+FailureHandler getOrCreateExpectFailureHandler() {
+  if (_assertFailureHandler == null) {
+    configureExpectFailureHandler();
+  }
+  return _assertFailureHandler;
+}
+
+// The error message formatter for failed asserts.
+ErrorFormatter _assertErrorFormatter = null;
+
+// The default error formatter implementation.
+String _defaultErrorFormatter(
+    actual, Matcher matcher, String reason, Map matchState, bool verbose) {
+  var description = new StringDescription();
+  description.add('Expected: ').addDescriptionOf(matcher).add('\n');
+  description.add('  Actual: ').addDescriptionOf(actual).add('\n');
+
+  var mismatchDescription = new StringDescription();
+  matcher.describeMismatch(actual, mismatchDescription, matchState, verbose);
+
+  if (mismatchDescription.length > 0) {
+    description.add('   Which: ${mismatchDescription}\n');
+  }
+  if (reason != null) {
+    description.add(reason).add('\n');
+  }
+  return description.toString();
+}
+
+/// Changes the failure message formatter for expect().
+///
+/// [formatter] is a reference to the new formatter; if this is omitted or
+/// null then the failure formatter is reset to the default. The new
+/// formatter is returned; this allows custom expect handlers to easily
+/// get a reference to the default formatter.
+ErrorFormatter configureExpectFormatter([ErrorFormatter formatter = null]) {
+  if (formatter == null) {
+    formatter = _defaultErrorFormatter;
+  }
+  return _assertErrorFormatter = formatter;
+}
diff --git a/lib/src/matcher/future_matchers.dart b/lib/src/matcher/future_matchers.dart
new file mode 100644
index 0000000..9b6319b
--- /dev/null
+++ b/lib/src/matcher/future_matchers.dart
@@ -0,0 +1,75 @@
+// 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.
+
+library unittest.matcher.future_matchers;
+
+import 'dart:async';
+
+import 'expect.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Matches a [Future] that completes successfully with a value.
+///
+/// Note that this creates an asynchronous expectation. The call to `expect()`
+/// that includes this will return immediately and execution will continue.
+/// Later, when the future completes, the actual expectation will run.
+///
+/// To test that a Future completes with an exception, you can use [throws] and
+/// [throwsA].
+final Matcher completes = const _Completes(null, '');
+
+/// Matches a [Future] that completes succesfully with a value that matches
+/// [matcher].
+///
+/// Note that this creates an asynchronous expectation. The call to
+/// `expect()` that includes this will return immediately and execution will
+/// continue. Later, when the future completes, the actual expectation will run.
+///
+/// To test that a Future completes with an exception, you can use [throws] and
+/// [throwsA].
+///
+/// [id] is an optional tag that can be used to identify the completion matcher
+/// in error messages.
+Matcher completion(matcher, [String id = '']) =>
+    new _Completes(wrapMatcher(matcher), id);
+
+class _Completes extends Matcher {
+  final Matcher _matcher;
+  final String _id;
+
+  const _Completes(this._matcher, this._id);
+
+  bool matches(item, Map matchState) {
+    if (item is! Future) return false;
+    var done = wrapAsync((fn) => fn(), _id);
+
+    item.then((value) {
+      done(() {
+        if (_matcher != null) expect(value, _matcher);
+      });
+    }, onError: (error, trace) {
+      var id = _id == '' ? '' : '${_id} ';
+      var reason = 'Expected future ${id}to complete successfully, '
+          'but it failed with ${error}';
+      if (trace != null) {
+        var stackTrace = trace.toString();
+        stackTrace = '  ${stackTrace.replaceAll('\n', '\n  ')}';
+        reason = '$reason\nStack trace:\n$stackTrace';
+      }
+      done(() => fail(reason));
+    });
+
+    return true;
+  }
+
+  Description describe(Description description) {
+    if (_matcher == null) {
+      description.add('completes successfully');
+    } else {
+      description.add('completes to a value that ').addDescriptionOf(_matcher);
+    }
+    return description;
+  }
+}
diff --git a/lib/src/matcher/interfaces.dart b/lib/src/matcher/interfaces.dart
new file mode 100644
index 0000000..3e68d2d
--- /dev/null
+++ b/lib/src/matcher/interfaces.dart
@@ -0,0 +1,60 @@
+// 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.
+
+library unittest.matcher.interfaces;
+
+// To decouple the reporting of errors, and allow for extensibility of
+// matchers, we make use of some interfaces.
+
+/// Matchers build up their error messages by appending to
+/// Description objects. This interface is implemented by
+/// StringDescription. This interface is unlikely to need
+/// other implementations, but could be useful to replace in
+/// some cases - e.g. language conversion.
+abstract class Description {
+  int get length;
+
+  /// Change the value of the description.
+  Description replace(String text);
+
+  /// This is used to add arbitrary text to the description.
+  Description add(String text);
+
+  /// This is used to add a meaningful description of a value.
+  Description addDescriptionOf(value);
+
+  /// This is used to add a description of an [Iterable] [list],
+  /// with appropriate [start] and [end] markers and inter-element [separator].
+  Description addAll(String start, String separator, String end, Iterable list);
+}
+
+/// [expect] Matchers must implement/extend the Matcher class.
+/// The base Matcher class has a generic implementation of [describeMismatch]
+/// so this does not need to be provided unless a more clear description is
+/// required. The other two methods ([matches] and [describe])
+/// must always be provided as they are highly matcher-specific.
+abstract class Matcher {
+  const Matcher();
+
+  /// This does the matching of the actual vs expected values.
+  /// [item] is the actual value. [matchState] can be supplied
+  /// and may be used to add details about the mismatch that are too
+  /// costly to determine in [describeMismatch].
+  bool matches(item, Map matchState);
+
+  /// This builds a textual description of the matcher.
+  Description describe(Description description);
+
+  /// This builds a textual description of a specific mismatch. [item]
+  /// is the value that was tested by [matches]; [matchState] is
+  /// the [Map] that was passed to and supplemented by [matches]
+  /// with additional information about the mismatch, and [mismatchDescription]
+  /// is the [Description] that is being built to decribe the mismatch.
+  /// A few matchers make use of the [verbose] flag to provide detailed
+  /// information that is not typically included but can be of help in
+  /// diagnosing failures, such as stack traces.
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) =>
+      mismatchDescription;
+}
diff --git a/lib/src/matcher/iterable_matchers.dart b/lib/src/matcher/iterable_matchers.dart
new file mode 100644
index 0000000..6a759ec
--- /dev/null
+++ b/lib/src/matcher/iterable_matchers.dart
@@ -0,0 +1,267 @@
+// 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.
+
+library unittest.matcher.iterable_matchers;
+
+import 'core_matchers.dart';
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher which matches [Iterable]s in which all elements
+/// match the given [matcher].
+Matcher everyElement(matcher) => new _EveryElement(wrapMatcher(matcher));
+
+class _EveryElement extends _IterableMatcher {
+  final Matcher _matcher;
+
+  _EveryElement(Matcher this._matcher);
+
+  bool matches(item, Map matchState) {
+    if (item is! Iterable) {
+      return false;
+    }
+    var i = 0;
+    for (var element in item) {
+      if (!_matcher.matches(element, matchState)) {
+        addStateInfo(matchState, {'index': i, 'element': element});
+        return false;
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  Description describe(Description description) =>
+      description.add('every element(').addDescriptionOf(_matcher).add(')');
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (matchState['index'] != null) {
+      var index = matchState['index'];
+      var element = matchState['element'];
+      mismatchDescription
+          .add('has value ')
+          .addDescriptionOf(element)
+          .add(' which ');
+      var subDescription = new StringDescription();
+      _matcher.describeMismatch(
+          element, subDescription, matchState['state'], verbose);
+      if (subDescription.length > 0) {
+        mismatchDescription.add(subDescription.toString());
+      } else {
+        mismatchDescription.add("doesn't match ");
+        _matcher.describe(mismatchDescription);
+      }
+      mismatchDescription.add(' at index $index');
+      return mismatchDescription;
+    }
+    return super.describeMismatch(
+        item, mismatchDescription, matchState, verbose);
+  }
+}
+
+/// Returns a matcher which matches [Iterable]s in which at least one
+/// element matches the given [matcher].
+Matcher anyElement(matcher) => new _AnyElement(wrapMatcher(matcher));
+
+class _AnyElement extends _IterableMatcher {
+  final Matcher _matcher;
+
+  _AnyElement(this._matcher);
+
+  bool matches(item, Map matchState) {
+    return item.any((e) => _matcher.matches(e, matchState));
+  }
+
+  Description describe(Description description) =>
+      description.add('some element ').addDescriptionOf(_matcher);
+}
+
+/// Returns a matcher which matches [Iterable]s that have the same
+/// length and the same elements as [expected], in the same order.
+///
+/// This is equivalent to [equals] but does not recurse.
+Matcher orderedEquals(Iterable expected) => new _OrderedEquals(expected);
+
+class _OrderedEquals extends Matcher {
+  final Iterable _expected;
+  Matcher _matcher;
+
+  _OrderedEquals(this._expected) {
+    _matcher = equals(_expected, 1);
+  }
+
+  bool matches(item, Map matchState) =>
+      (item is Iterable) && _matcher.matches(item, matchState);
+
+  Description describe(Description description) =>
+      description.add('equals ').addDescriptionOf(_expected).add(' ordered');
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! Iterable) {
+      return mismatchDescription.add('is not an Iterable');
+    } else {
+      return _matcher.describeMismatch(
+          item, mismatchDescription, matchState, verbose);
+    }
+  }
+}
+
+/// Returns a matcher which matches [Iterable]s that have the same length and
+/// the same elements as [expected], but not necessarily in the same order.
+///
+/// Note that this is O(n^2) so should only be used on small objects.
+Matcher unorderedEquals(Iterable expected) => new _UnorderedEquals(expected);
+
+class _UnorderedEquals extends _UnorderedMatches {
+  final List _expectedValues;
+
+  _UnorderedEquals(Iterable expected)
+      : super(expected.map(equals)),
+        _expectedValues = expected.toList();
+
+  Description describe(Description description) => description
+      .add('equals ')
+      .addDescriptionOf(_expectedValues)
+      .add(' unordered');
+}
+
+/// Iterable matchers match against [Iterable]s. We add this intermediate
+/// class to give better mismatch error messages than the base Matcher class.
+abstract class _IterableMatcher extends Matcher {
+  const _IterableMatcher();
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! Iterable) {
+      return mismatchDescription.addDescriptionOf(item).add(' not an Iterable');
+    } else {
+      return super.describeMismatch(
+          item, mismatchDescription, matchState, verbose);
+    }
+  }
+}
+
+/// Returns a matcher which matches [Iterable]s whose elements match the
+/// matchers in [expected], but not necessarily in the same order.
+///
+///  Note that this is `O(n^2)` and so should only be used on small objects.
+Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected);
+
+class _UnorderedMatches extends Matcher {
+  final List<Matcher> _expected;
+
+  _UnorderedMatches(Iterable expected)
+      : _expected = expected.map(wrapMatcher).toList();
+
+  String _test(item) {
+    if (item is! Iterable) return 'not iterable';
+    item = item.toList();
+
+    // Check the lengths are the same.
+    if (_expected.length > item.length) {
+      return 'has too few elements (${item.length} < ${_expected.length})';
+    } else if (_expected.length < item.length) {
+      return 'has too many elements (${item.length} > ${_expected.length})';
+    }
+
+    var matched = new List<bool>.filled(item.length, false);
+    var expectedPosition = 0;
+    for (var expectedMatcher in _expected) {
+      var actualPosition = 0;
+      var gotMatch = false;
+      for (var actualElement in item) {
+        if (!matched[actualPosition]) {
+          if (expectedMatcher.matches(actualElement, {})) {
+            matched[actualPosition] = gotMatch = true;
+            break;
+          }
+        }
+        ++actualPosition;
+      }
+
+      if (!gotMatch) {
+        return new StringDescription()
+            .add('has no match for ')
+            .addDescriptionOf(expectedMatcher)
+            .add(' at index ${expectedPosition}')
+            .toString();
+      }
+
+      ++expectedPosition;
+    }
+    return null;
+  }
+
+  bool matches(item, Map mismatchState) => _test(item) == null;
+
+  Description describe(Description description) => description
+      .add('matches ')
+      .addAll('[', ', ', ']', _expected)
+      .add(' unordered');
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) =>
+      mismatchDescription.add(_test(item));
+}
+
+/// A pairwise matcher for [Iterable]s.
+///
+/// The [comparator] function, taking an expected and an actual argument, and
+/// returning whether they match, will be applied to each pair in order.
+/// [description] should be a meaningful name for the comparator.
+Matcher pairwiseCompare(
+    Iterable expected, bool comparator(a, b), String description) =>
+        new _PairwiseCompare(expected, comparator, description);
+
+typedef bool _Comparator(a, b);
+
+class _PairwiseCompare extends _IterableMatcher {
+  final Iterable _expected;
+  final _Comparator _comparator;
+  final String _description;
+
+  _PairwiseCompare(this._expected, this._comparator, this._description);
+
+  bool matches(item, Map matchState) {
+    if (item is! Iterable) return false;
+    if (item.length != _expected.length) return false;
+    var iterator = item.iterator;
+    var i = 0;
+    for (var e in _expected) {
+      iterator.moveNext();
+      if (!_comparator(e, iterator.current)) {
+        addStateInfo(matchState, {
+          'index': i,
+          'expected': e,
+          'actual': iterator.current
+        });
+        return false;
+      }
+      i++;
+    }
+    return true;
+  }
+
+  Description describe(Description description) =>
+      description.add('pairwise $_description ').addDescriptionOf(_expected);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! Iterable) {
+      return mismatchDescription.add('is not an Iterable');
+    } else if (item.length != _expected.length) {
+      return mismatchDescription
+          .add('has length ${item.length} instead of ${_expected.length}');
+    } else {
+      return mismatchDescription
+          .add('has ')
+          .addDescriptionOf(matchState["actual"])
+          .add(' which is not $_description ')
+          .addDescriptionOf(matchState["expected"])
+          .add(' at index ${matchState["index"]}');
+    }
+  }
+}
diff --git a/lib/src/matcher/map_matchers.dart b/lib/src/matcher/map_matchers.dart
new file mode 100644
index 0000000..98e89af
--- /dev/null
+++ b/lib/src/matcher/map_matchers.dart
@@ -0,0 +1,61 @@
+// 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.
+
+library unittest.matcher.map_matchers;
+
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher which matches maps containing the given [value].
+Matcher containsValue(value) => new _ContainsValue(value);
+
+class _ContainsValue extends Matcher {
+  final _value;
+
+  const _ContainsValue(this._value);
+
+  bool matches(item, Map matchState) => item.containsValue(_value);
+  Description describe(Description description) =>
+      description.add('contains value ').addDescriptionOf(_value);
+}
+
+/// Returns a matcher which matches maps containing the key-value pair
+/// with [key] => [value].
+Matcher containsPair(key, value) =>
+    new _ContainsMapping(key, wrapMatcher(value));
+
+class _ContainsMapping extends Matcher {
+  final _key;
+  final Matcher _valueMatcher;
+
+  const _ContainsMapping(this._key, Matcher this._valueMatcher);
+
+  bool matches(item, Map matchState) =>
+      item.containsKey(_key) && _valueMatcher.matches(item[_key], matchState);
+
+  Description describe(Description description) {
+    return description
+        .add('contains pair ')
+        .addDescriptionOf(_key)
+        .add(' => ')
+        .addDescriptionOf(_valueMatcher);
+  }
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (!item.containsKey(_key)) {
+      return mismatchDescription
+          .add(" doesn't contain key ")
+          .addDescriptionOf(_key);
+    } else {
+      mismatchDescription
+          .add(' contains key ')
+          .addDescriptionOf(_key)
+          .add(' but with value ');
+      _valueMatcher.describeMismatch(
+          item[_key], mismatchDescription, matchState, verbose);
+      return mismatchDescription;
+    }
+  }
+}
diff --git a/lib/src/matcher/numeric_matchers.dart b/lib/src/matcher/numeric_matchers.dart
new file mode 100644
index 0000000..18c498e
--- /dev/null
+++ b/lib/src/matcher/numeric_matchers.dart
@@ -0,0 +1,202 @@
+// 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.
+
+library unittest.matcher.numeric_matchers;
+
+import 'interfaces.dart';
+
+/// Returns a matcher which matches if the match argument is greater
+/// than the given [value].
+Matcher greaterThan(value) =>
+    new _OrderingComparison(value, false, false, true, 'a value greater than');
+
+/// Returns a matcher which matches if the match argument is greater
+/// than or equal to the given [value].
+Matcher greaterThanOrEqualTo(value) => new _OrderingComparison(
+    value, true, false, true, 'a value greater than or equal to');
+
+/// Returns a matcher which matches if the match argument is less
+/// than the given [value].
+Matcher lessThan(value) =>
+    new _OrderingComparison(value, false, true, false, 'a value less than');
+
+/// Returns a matcher which matches if the match argument is less
+/// than or equal to the given [value].
+Matcher lessThanOrEqualTo(value) => new _OrderingComparison(
+    value, true, true, false, 'a value less than or equal to');
+
+/// A matcher which matches if the match argument is zero.
+const Matcher isZero =
+    const _OrderingComparison(0, true, false, false, 'a value equal to');
+
+/// A matcher which matches if the match argument is non-zero.
+const Matcher isNonZero =
+    const _OrderingComparison(0, false, true, true, 'a value not equal to');
+
+/// A matcher which matches if the match argument is positive.
+const Matcher isPositive =
+    const _OrderingComparison(0, false, false, true, 'a positive value', false);
+
+/// A matcher which matches if the match argument is zero or negative.
+const Matcher isNonPositive = const _OrderingComparison(
+    0, true, true, false, 'a non-positive value', false);
+
+/// A matcher which matches if the match argument is negative.
+const Matcher isNegative =
+    const _OrderingComparison(0, false, true, false, 'a negative value', false);
+
+/// A matcher which matches if the match argument is zero or positive.
+const Matcher isNonNegative = const _OrderingComparison(
+    0, true, false, true, 'a non-negative value', false);
+
+bool _isNumeric(value) {
+  return value is num;
+}
+
+// TODO(kevmoo) Note that matchers that use _OrderingComparison only use
+// `==` and `<` operators to evaluate the match. Or change the matcher.
+class _OrderingComparison extends Matcher {
+  /// Expected value.
+  final _value;
+  /// What to return if actual == expected
+  final bool _equalValue;
+  /// What to return if actual < expected
+  final bool _lessThanValue;
+  /// What to return if actual > expected
+  final bool _greaterThanValue;
+  /// Textual name of the inequality
+  final String _comparisonDescription;
+  /// Whether to include the expected value in the description
+  final bool _valueInDescription;
+
+  const _OrderingComparison(this._value, this._equalValue, this._lessThanValue,
+      this._greaterThanValue, this._comparisonDescription,
+      [bool valueInDescription = true])
+      : this._valueInDescription = valueInDescription;
+
+  bool matches(item, Map matchState) {
+    if (item == _value) {
+      return _equalValue;
+    } else if (item < _value) {
+      return _lessThanValue;
+    } else {
+      return _greaterThanValue;
+    }
+  }
+
+  Description describe(Description description) {
+    if (_valueInDescription) {
+      return description
+          .add(_comparisonDescription)
+          .add(' ')
+          .addDescriptionOf(_value);
+    } else {
+      return description.add(_comparisonDescription);
+    }
+  }
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    mismatchDescription.add('is not ');
+    return describe(mismatchDescription);
+  }
+}
+
+/// Returns a matcher which matches if the match argument is within [delta]
+/// of some [value].
+///
+/// In other words, this matches if the match argument is greater than
+/// than or equal [value]-[delta] and less than or equal to [value]+[delta].
+Matcher closeTo(num value, num delta) => new _IsCloseTo(value, delta);
+
+class _IsCloseTo extends Matcher {
+  final num _value, _delta;
+
+  const _IsCloseTo(this._value, this._delta);
+
+  bool matches(item, Map matchState) {
+    if (!_isNumeric(item)) {
+      return false;
+    }
+    var diff = item - _value;
+    if (diff < 0) diff = -diff;
+    return (diff <= _delta);
+  }
+
+  Description describe(Description description) => description
+      .add('a numeric value within ')
+      .addDescriptionOf(_delta)
+      .add(' of ')
+      .addDescriptionOf(_value);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! num) {
+      return mismatchDescription.add(' not numeric');
+    } else {
+      var diff = item - _value;
+      if (diff < 0) diff = -diff;
+      return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
+    }
+  }
+}
+
+/// Returns a matcher which matches if the match argument is greater
+/// than or equal to [low] and less than or equal to [high].
+Matcher inInclusiveRange(num low, num high) =>
+    new _InRange(low, high, true, true);
+
+/// Returns a matcher which matches if the match argument is greater
+/// than [low] and less than [high].
+Matcher inExclusiveRange(num low, num high) =>
+    new _InRange(low, high, false, false);
+
+/// Returns a matcher which matches if the match argument is greater
+/// than [low] and less than or equal to [high].
+Matcher inOpenClosedRange(num low, num high) =>
+    new _InRange(low, high, false, true);
+
+/// Returns a matcher which matches if the match argument is greater
+/// than or equal to a [low] and less than [high].
+Matcher inClosedOpenRange(num low, num high) =>
+    new _InRange(low, high, true, false);
+
+class _InRange extends Matcher {
+  final num _low, _high;
+  final bool _lowMatchValue, _highMatchValue;
+
+  const _InRange(
+      this._low, this._high, this._lowMatchValue, this._highMatchValue);
+
+  bool matches(value, Map matchState) {
+    if (value is! num) {
+      return false;
+    }
+    if (value < _low || value > _high) {
+      return false;
+    }
+    if (value == _low) {
+      return _lowMatchValue;
+    }
+    if (value == _high) {
+      return _highMatchValue;
+    }
+    return true;
+  }
+
+  Description describe(Description description) => description.add(
+      "be in range from "
+      "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to "
+      "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})");
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! num) {
+      return mismatchDescription.addDescriptionOf(item).add(' not numeric');
+    } else {
+      return super.describeMismatch(
+          item, mismatchDescription, matchState, verbose);
+    }
+  }
+}
diff --git a/lib/src/matcher/operator_matchers.dart b/lib/src/matcher/operator_matchers.dart
new file mode 100644
index 0000000..432f905
--- /dev/null
+++ b/lib/src/matcher/operator_matchers.dart
@@ -0,0 +1,113 @@
+// 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.
+
+library unittest.matcher.operator_matchers;
+
+import 'interfaces.dart';
+import 'util.dart';
+
+/// This returns a matcher that inverts [matcher] to its logical negation.
+Matcher isNot(matcher) => new _IsNot(wrapMatcher(matcher));
+
+class _IsNot extends Matcher {
+  final Matcher _matcher;
+
+  const _IsNot(this._matcher);
+
+  bool matches(item, Map matchState) => !_matcher.matches(item, matchState);
+
+  Description describe(Description description) =>
+      description.add('not ').addDescriptionOf(_matcher);
+}
+
+/// This returns a matcher that matches if all of the matchers passed as
+/// arguments (up to 7) match.
+///
+/// Instead of passing the matchers separately they can be passed as a single
+/// List argument. Any argument that is not a matcher is implicitly wrapped in a
+/// Matcher to check for equality.
+Matcher allOf(arg0, [arg1, arg2, arg3, arg4, arg5, arg6]) {
+  return new _AllOf(_wrapArgs(arg0, arg1, arg2, arg3, arg4, arg5, arg6));
+}
+
+class _AllOf extends Matcher {
+  final List<Matcher> _matchers;
+
+  const _AllOf(this._matchers);
+
+  bool matches(item, Map matchState) {
+    for (var matcher in _matchers) {
+      if (!matcher.matches(item, matchState)) {
+        addStateInfo(matchState, {'matcher': matcher});
+        return false;
+      }
+    }
+    return true;
+  }
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    var matcher = matchState['matcher'];
+    matcher.describeMismatch(
+        item, mismatchDescription, matchState['state'], verbose);
+    return mismatchDescription;
+  }
+
+  Description describe(Description description) =>
+      description.addAll('(', ' and ', ')', _matchers);
+}
+
+/// Matches if any of the given matchers evaluate to true.
+///
+/// The arguments can be a set of matchers as separate parameters
+/// (up to 7), or a List of matchers.
+///
+/// The matchers are evaluated from left to right using short-circuit
+/// evaluation, so evaluation stops as soon as a matcher returns true.
+///
+/// Any argument that is not a matcher is implicitly wrapped in a
+/// Matcher to check for equality.
+Matcher anyOf(arg0, [arg1, arg2, arg3, arg4, arg5, arg6]) {
+  return new _AnyOf(_wrapArgs(arg0, arg1, arg2, arg3, arg4, arg5, arg6));
+}
+
+class _AnyOf extends Matcher {
+  final List<Matcher> _matchers;
+
+  const _AnyOf(this._matchers);
+
+  bool matches(item, Map matchState) {
+    for (var matcher in _matchers) {
+      if (matcher.matches(item, matchState)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Description describe(Description description) =>
+      description.addAll('(', ' or ', ')', _matchers);
+}
+
+List<Matcher> _wrapArgs(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
+  Iterable<Matcher> matchers;
+  if (arg0 is List) {
+    if (arg1 != null ||
+        arg2 != null ||
+        arg3 != null ||
+        arg4 != null ||
+        arg5 != null ||
+        arg6 != null) {
+      throw new ArgumentError('If arg0 is a List, all other arguments must be'
+          ' null.');
+    }
+
+    matchers = arg0;
+  } else {
+    matchers =
+        [arg0, arg1, arg2, arg3, arg4, arg5, arg6].where((e) => e != null);
+  }
+
+  return matchers.map((e) => wrapMatcher(e)).toList();
+}
diff --git a/lib/src/matcher/pretty_print.dart b/lib/src/matcher/pretty_print.dart
new file mode 100644
index 0000000..8e96c04
--- /dev/null
+++ b/lib/src/matcher/pretty_print.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2013, 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.
+
+library unittest.matcher.pretty_print;
+
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a pretty-printed representation of [object].
+///
+/// If [maxLineLength] is passed, this will attempt to ensure that each line is
+/// no longer than [maxLineLength] characters long. This isn't guaranteed, since
+/// individual objects may have string representations that are too long, but
+/// most lines will be less than [maxLineLength] long.
+///
+/// If [maxItems] is passed, [Iterable]s and [Map]s will only print their first
+/// [maxItems] members or key/value pairs, respectively.
+String prettyPrint(object, {int maxLineLength, int maxItems}) {
+  String _prettyPrint(object, int indent, Set seen, bool top) {
+    // If the object is a matcher, use its description.
+    if (object is Matcher) {
+      var description = new StringDescription();
+      object.describe(description);
+      return "<$description>";
+    }
+
+    // Avoid looping infinitely on recursively-nested data structures.
+    if (seen.contains(object)) return "(recursive)";
+    seen = seen.union(new Set.from([object]));
+    String pp(child) => _prettyPrint(child, indent + 2, seen, false);
+
+    if (object is Iterable) {
+      // Print the type name for non-List iterables.
+      var type = object is List ? "" : _typeName(object) + ":";
+
+      // Truncate the list of strings if it's longer than [maxItems].
+      var strings = object.map(pp).toList();
+      if (maxItems != null && strings.length > maxItems) {
+        strings.replaceRange(maxItems - 1, strings.length, ['...']);
+      }
+
+      // If the printed string is short and doesn't contain a newline, print it
+      // as a single line.
+      var singleLine = "$type[${strings.join(', ')}]";
+      if ((maxLineLength == null ||
+              singleLine.length + indent <= maxLineLength) &&
+          !singleLine.contains("\n")) {
+        return singleLine;
+      }
+
+      // Otherwise, print each member on its own line.
+      return "$type[\n" + strings.map((string) {
+        return _indent(indent + 2) + string;
+      }).join(",\n") + "\n" + _indent(indent) + "]";
+    } else if (object is Map) {
+      // Convert the contents of the map to string representations.
+      var strings = object.keys.map((key) {
+        return '${pp(key)}: ${pp(object[key])}';
+      }).toList();
+
+      // Truncate the list of strings if it's longer than [maxItems].
+      if (maxItems != null && strings.length > maxItems) {
+        strings.replaceRange(maxItems - 1, strings.length, ['...']);
+      }
+
+      // If the printed string is short and doesn't contain a newline, print it
+      // as a single line.
+      var singleLine = "{${strings.join(", ")}}";
+      if ((maxLineLength == null ||
+              singleLine.length + indent <= maxLineLength) &&
+          !singleLine.contains("\n")) {
+        return singleLine;
+      }
+
+      // Otherwise, print each key/value pair on its own line.
+      return "{\n" + strings.map((string) {
+        return _indent(indent + 2) + string;
+      }).join(",\n") + "\n" + _indent(indent) + "}";
+    } else if (object is String) {
+      // Escape strings and print each line on its own line.
+      var lines = object.split("\n");
+      return "'" +
+          lines.map(_escapeString).join("\\n'\n${_indent(indent + 2)}'") +
+          "'";
+    } else {
+      var value = object.toString().replaceAll("\n", _indent(indent) + "\n");
+      var defaultToString = value.startsWith("Instance of ");
+
+      // If this is the top-level call to [prettyPrint], wrap the value on angle
+      // brackets to set it apart visually.
+      if (top) value = "<$value>";
+
+      // Print the type of objects with custom [toString] methods. Primitive
+      // objects and objects that don't implement a custom [toString] don't need
+      // to have their types printed.
+      if (object is num ||
+          object is bool ||
+          object is Function ||
+          object == null ||
+          defaultToString) {
+        return value;
+      } else {
+        return "${_typeName(object)}:$value";
+      }
+    }
+  }
+
+  return _prettyPrint(object, 0, new Set(), true);
+}
+
+String _indent(int length) => new List.filled(length, ' ').join('');
+
+/// Returns the name of the type of [x], or "Unknown" if the type name can't be
+/// determined.
+String _typeName(x) {
+  // dart2js blows up on some objects (e.g. window.navigator).
+  // So we play safe here.
+  try {
+    if (x == null) return "null";
+    var type = x.runtimeType.toString();
+    // TODO(nweiz): if the object's type is private, find a public superclass to
+    // display once there's a portable API to do that.
+    return type.startsWith("_") ? "?" : type;
+  } catch (e) {
+    return "?";
+  }
+}
+
+/// Returns [source] with any control characters replaced by their escape
+/// sequences.
+///
+/// This doesn't add quotes to the string, but it does escape single quote
+/// characters so that single quotes can be applied externally.
+String _escapeString(String source) => escape(source).replaceAll("'", r"\'");
diff --git a/lib/src/matcher/prints_matcher.dart b/lib/src/matcher/prints_matcher.dart
new file mode 100644
index 0000000..03c0eaf
--- /dev/null
+++ b/lib/src/matcher/prints_matcher.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2014, 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.
+
+library unittest.matcher.prints_matcher;
+
+import 'dart:async';
+
+import 'description.dart';
+import 'expect.dart';
+import 'interfaces.dart';
+import 'future_matchers.dart';
+import 'util.dart';
+
+/// Matches a [Function] that prints text that matches [matcher].
+///
+/// [matcher] may be a String or a [Matcher].
+///
+/// If the function this runs against returns a [Future], all text printed by
+/// the function (using [Zone] scoping) until that Future completes is matched.
+///
+/// This only tracks text printed using the [print] function.
+Matcher prints(matcher) => new _Prints(wrapMatcher(matcher));
+
+class _Prints extends Matcher {
+  final Matcher _matcher;
+
+  _Prints(this._matcher);
+
+  bool matches(item, Map matchState) {
+    if (item is! Function) return false;
+
+    var buffer = new StringBuffer();
+    var result = runZoned(item,
+        zoneSpecification: new ZoneSpecification(print: (_, __, ____, line) {
+      buffer.writeln(line);
+    }));
+
+    if (result is! Future) {
+      var actual = buffer.toString();
+      matchState['prints.actual'] = actual;
+      return _matcher.matches(actual, matchState);
+    }
+
+    return completes.matches(result.then(wrapAsync((_) {
+      expect(buffer.toString(), _matcher);
+    }, 'prints')), matchState);
+  }
+
+  Description describe(Description description) =>
+      description.add('prints ').addDescriptionOf(_matcher);
+
+  Description describeMismatch(
+      item, Description description, Map matchState, bool verbose) {
+    var actual = matchState.remove('prints.actual');
+    if (actual == null) return description;
+    if (actual.isEmpty) return description.add("printed nothing.");
+
+    description.add('printed ').addDescriptionOf(actual);
+
+    // Create a new description for the matcher because at least
+    // [_StringEqualsMatcher] replaces the previous contents of the description.
+    var innerMismatch = _matcher
+        .describeMismatch(actual, new StringDescription(), matchState, verbose)
+        .toString();
+
+    if (innerMismatch.isNotEmpty) {
+      description.add('\n   Which: ').add(innerMismatch.toString());
+    }
+
+    return description;
+  }
+}
diff --git a/lib/src/matcher/string_matchers.dart b/lib/src/matcher/string_matchers.dart
new file mode 100644
index 0000000..bf26e5b
--- /dev/null
+++ b/lib/src/matcher/string_matchers.dart
@@ -0,0 +1,201 @@
+// 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.
+
+library unittest.matcher.string_matchers;
+
+import 'interfaces.dart';
+
+/// Returns a matcher which matches if the match argument is a string and
+/// is equal to [value] when compared case-insensitively.
+Matcher equalsIgnoringCase(String value) => new _IsEqualIgnoringCase(value);
+
+class _IsEqualIgnoringCase extends _StringMatcher {
+  final String _value;
+  final String _matchValue;
+
+  _IsEqualIgnoringCase(String value)
+      : _value = value,
+        _matchValue = value.toLowerCase();
+
+  bool matches(item, Map matchState) =>
+      item is String && _matchValue == item.toLowerCase();
+
+  Description describe(Description description) =>
+      description.addDescriptionOf(_value).add(' ignoring case');
+}
+
+/// Returns a matcher which matches if the match argument is a string and
+/// is equal to [value], ignoring whitespace.
+///
+/// In this matcher, "ignoring whitespace" means comparing with all runs of
+/// whitespace collapsed to single space characters and leading and trailing
+/// whitespace removed.
+///
+/// For example, the following will all match successfully:
+///
+///     expect("hello   world", equalsIgnoringWhitespace("hello world"));
+///     expect("  hello world", equalsIgnoringWhitespace("hello world"));
+///     expect("hello world  ", equalsIgnoringWhitespace("hello world"));
+///
+/// The following will not match:
+///
+///     expect("helloworld", equalsIgnoringWhitespace("hello world"));
+///     expect("he llo world", equalsIgnoringWhitespace("hello world"));
+Matcher equalsIgnoringWhitespace(String value) =>
+    new _IsEqualIgnoringWhitespace(value);
+
+class _IsEqualIgnoringWhitespace extends _StringMatcher {
+  final String _value;
+  final String _matchValue;
+
+  _IsEqualIgnoringWhitespace(String value)
+      : _value = value,
+        _matchValue = collapseWhitespace(value);
+
+  bool matches(item, Map matchState) =>
+      item is String && _matchValue == collapseWhitespace(item);
+
+  Description describe(Description description) =>
+      description.addDescriptionOf(_matchValue).add(' ignoring whitespace');
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is String) {
+      return mismatchDescription
+          .add('is ')
+          .addDescriptionOf(collapseWhitespace(item))
+          .add(' with whitespace compressed');
+    } else {
+      return super.describeMismatch(
+          item, mismatchDescription, matchState, verbose);
+    }
+  }
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// starts with [prefixString].
+Matcher startsWith(String prefixString) => new _StringStartsWith(prefixString);
+
+class _StringStartsWith extends _StringMatcher {
+  final String _prefix;
+
+  const _StringStartsWith(this._prefix);
+
+  bool matches(item, Map matchState) =>
+      item is String && item.startsWith(_prefix);
+
+  Description describe(Description description) =>
+      description.add('a string starting with ').addDescriptionOf(_prefix);
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// ends with [suffixString].
+Matcher endsWith(String suffixString) => new _StringEndsWith(suffixString);
+
+class _StringEndsWith extends _StringMatcher {
+  final String _suffix;
+
+  const _StringEndsWith(this._suffix);
+
+  bool matches(item, Map matchState) =>
+      item is String && item.endsWith(_suffix);
+
+  Description describe(Description description) =>
+      description.add('a string ending with ').addDescriptionOf(_suffix);
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// contains a given list of [substrings] in relative order.
+///
+/// For example, `stringContainsInOrder(["a", "e", "i", "o", "u"])` will match
+/// "abcdefghijklmnopqrstuvwxyz".
+
+Matcher stringContainsInOrder(List<String> substrings) =>
+    new _StringContainsInOrder(substrings);
+
+class _StringContainsInOrder extends _StringMatcher {
+  final List<String> _substrings;
+
+  const _StringContainsInOrder(this._substrings);
+
+  bool matches(item, Map matchState) {
+    if (!(item is String)) {
+      return false;
+    }
+    var from_index = 0;
+    for (var s in _substrings) {
+      from_index = item.indexOf(s, from_index);
+      if (from_index < 0) return false;
+    }
+    return true;
+  }
+
+  Description describe(Description description) => description.addAll(
+      'a string containing ', ', ', ' in order', _substrings);
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// matches the regular expression given by [re].
+///
+/// [re] can be a [RegExp] instance or a [String]; in the latter case it will be
+/// used to create a RegExp instance.
+Matcher matches(re) => new _MatchesRegExp(re);
+
+class _MatchesRegExp extends _StringMatcher {
+  RegExp _regexp;
+
+  _MatchesRegExp(re) {
+    if (re is String) {
+      _regexp = new RegExp(re);
+    } else if (re is RegExp) {
+      _regexp = re;
+    } else {
+      throw new ArgumentError('matches requires a regexp or string');
+    }
+  }
+
+  bool matches(item, Map matchState) =>
+      item is String ? _regexp.hasMatch(item) : false;
+
+  Description describe(Description description) =>
+      description.add("match '${_regexp.pattern}'");
+}
+
+// String matchers match against a string. We add this intermediate
+// class to give better mismatch error messages than the base Matcher class.
+abstract class _StringMatcher extends Matcher {
+  const _StringMatcher();
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (!(item is String)) {
+      return mismatchDescription.addDescriptionOf(item).add(' not a string');
+    } else {
+      return super.describeMismatch(
+          item, mismatchDescription, matchState, verbose);
+    }
+  }
+}
+
+/// Utility function to collapse whitespace runs to single spaces
+/// and strip leading/trailing whitespace.
+String collapseWhitespace(String string) {
+  var result = new StringBuffer();
+  var skipSpace = true;
+  for (var i = 0; i < string.length; i++) {
+    var character = string[i];
+    if (_isWhitespace(character)) {
+      if (!skipSpace) {
+        result.write(' ');
+        skipSpace = true;
+      }
+    } else {
+      result.write(character);
+      skipSpace = false;
+    }
+  }
+  return result.toString().trim();
+}
+
+bool _isWhitespace(String ch) =>
+    ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
diff --git a/lib/src/matcher/throws_matcher.dart b/lib/src/matcher/throws_matcher.dart
new file mode 100644
index 0000000..72f726d
--- /dev/null
+++ b/lib/src/matcher/throws_matcher.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2014, 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.
+
+library unittest.matcher.throws_matcher;
+
+import 'dart:async';
+
+import 'expect.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// This can be used to match two kinds of objects:
+///
+///   * A [Function] that throws an exception when called. The function cannot
+///     take any arguments. If you want to test that a function expecting
+///     arguments throws, wrap it in another zero-argument function that calls
+///     the one you want to test.
+///
+///   * A [Future] that completes with an exception. Note that this creates an
+///     asynchronous expectation. The call to `expect()` that includes this will
+///     return immediately and execution will continue. Later, when the future
+///     completes, the actual expectation will run.
+const Matcher throws = const Throws();
+
+/// This can be used to match two kinds of objects:
+///
+///   * A [Function] that throws an exception when called. The function cannot
+///     take any arguments. If you want to test that a function expecting
+///     arguments throws, wrap it in another zero-argument function that calls
+///     the one you want to test.
+///
+///   * A [Future] that completes with an exception. Note that this creates an
+///     asynchronous expectation. The call to `expect()` that includes this will
+///     return immediately and execution will continue. Later, when the future
+///     completes, the actual expectation will run.
+///
+/// In both cases, when an exception is thrown, this will test that the exception
+/// object matches [matcher]. If [matcher] is not an instance of [Matcher], it
+/// will implicitly be treated as `equals(matcher)`.
+Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher));
+
+class Throws extends Matcher {
+  final Matcher _matcher;
+
+  const Throws([Matcher matcher]) : this._matcher = matcher;
+
+  bool matches(item, Map matchState) {
+    if (item is! Function && item is! Future) return false;
+    if (item is Future) {
+      var done = wrapAsync((fn) => fn());
+
+      // Queue up an asynchronous expectation that validates when the future
+      // completes.
+      item.then((value) {
+        done(() {
+          fail("Expected future to fail, but succeeded with '$value'.");
+        });
+      }, onError: (error, trace) {
+        done(() {
+          if (_matcher == null) return;
+          var reason;
+          if (trace != null) {
+            var stackTrace = trace.toString();
+            stackTrace = "  ${stackTrace.replaceAll("\n", "\n  ")}";
+            reason = "Actual exception trace:\n$stackTrace";
+          }
+          expect(error, _matcher, reason: reason);
+        });
+      });
+      // It hasn't failed yet.
+      return true;
+    }
+
+    try {
+      item();
+      return false;
+    } catch (e, s) {
+      if (_matcher == null || _matcher.matches(e, matchState)) {
+        return true;
+      } else {
+        addStateInfo(matchState, {'exception': e, 'stack': s});
+        return false;
+      }
+    }
+  }
+
+  Description describe(Description description) {
+    if (_matcher == null) {
+      return description.add("throws");
+    } else {
+      return description.add('throws ').addDescriptionOf(_matcher);
+    }
+  }
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is! Function && item is! Future) {
+      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('threw ')
+          .addDescriptionOf(matchState['exception']);
+      if (verbose) {
+        mismatchDescription.add(' at ').add(matchState['stack'].toString());
+      }
+      return mismatchDescription;
+    }
+  }
+}
diff --git a/lib/src/matcher/throws_matchers.dart b/lib/src/matcher/throws_matchers.dart
new file mode 100644
index 0000000..92accfd
--- /dev/null
+++ b/lib/src/matcher/throws_matchers.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2014, 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.
+
+library unittest.matcher.throws_matchers;
+
+import 'error_matchers.dart';
+import 'interfaces.dart';
+import 'throws_matcher.dart';
+
+/// A matcher for functions that throw ArgumentError.
+const Matcher throwsArgumentError = const Throws(isArgumentError);
+
+/// A matcher for functions that throw ConcurrentModificationError.
+const Matcher throwsConcurrentModificationError =
+    const Throws(isConcurrentModificationError);
+
+/// A matcher for functions that throw CyclicInitializationError.
+const Matcher throwsCyclicInitializationError =
+    const Throws(isCyclicInitializationError);
+
+/// A matcher for functions that throw Exception.
+const Matcher throwsException = const Throws(isException);
+
+/// A matcher for functions that throw FormatException.
+const Matcher throwsFormatException = const Throws(isFormatException);
+
+/// A matcher for functions that throw NoSuchMethodError.
+const Matcher throwsNoSuchMethodError = const Throws(isNoSuchMethodError);
+
+/// A matcher for functions that throw NullThrownError.
+const Matcher throwsNullThrownError = const Throws(isNullThrownError);
+
+/// A matcher for functions that throw RangeError.
+const Matcher throwsRangeError = const Throws(isRangeError);
+
+/// A matcher for functions that throw StateError.
+const Matcher throwsStateError = const Throws(isStateError);
+
+/// A matcher for functions that throw Exception.
+const Matcher throwsUnimplementedError = const Throws(isUnimplementedError);
+
+/// A matcher for functions that throw UnsupportedError.
+const Matcher throwsUnsupportedError = const Throws(isUnsupportedError);
diff --git a/lib/src/matcher/util.dart b/lib/src/matcher/util.dart
new file mode 100644
index 0000000..b98c1a8
--- /dev/null
+++ b/lib/src/matcher/util.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2014, 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.
+
+library unittest.matcher.util;
+
+import 'core_matchers.dart';
+import 'interfaces.dart';
+
+/// A [Map] between whitespace characters and their escape sequences.
+const _escapeMap = const {
+  '\n': r'\n',
+  '\r': r'\r',
+  '\f': r'\f',
+  '\b': r'\b',
+  '\t': r'\t',
+  '\v': r'\v',
+  '\x7F': r'\x7F', // delete
+};
+
+/// A [RegExp] that matches whitespace characters that should be escaped.
+final _escapeRegExp = new RegExp(
+    "[\\x00-\\x07\\x0E-\\x1F${_escapeMap.keys.map(_getHexLiteral).join()}]");
+
+/// Useful utility for nesting match states.
+void addStateInfo(Map matchState, Map values) {
+  var innerState = new Map.from(matchState);
+  matchState.clear();
+  matchState['state'] = innerState;
+  matchState.addAll(values);
+}
+
+/// Takes an argument and returns an equivalent [Matcher].
+///
+/// If the argument is already a matcher this does nothing,
+/// else if the argument is a function, it generates a predicate
+/// function matcher, else it generates an equals matcher.
+Matcher wrapMatcher(x) {
+  if (x is Matcher) {
+    return x;
+  } else if (x is Function) {
+    return predicate(x);
+  } else {
+    return equals(x);
+  }
+}
+
+/// Returns [str] with all whitespace characters represented as their escape
+/// sequences.
+///
+/// Backslash characters are escaped as `\\`
+String escape(String str) {
+  str = str.replaceAll('\\', r'\\');
+  return str.replaceAllMapped(_escapeRegExp, (match) {
+    var mapped = _escapeMap[match[0]];
+    if (mapped != null) return mapped;
+    return _getHexLiteral(match[0]);
+  });
+}
+
+/// Given single-character string, return the hex-escaped equivalent.
+String _getHexLiteral(String input) {
+  int rune = input.runes.single;
+  return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0');
+}
diff --git a/lib/src/simple_configuration.dart b/lib/src/simple_configuration.dart
index 6157663..8eac371 100644
--- a/lib/src/simple_configuration.dart
+++ b/lib/src/simple_configuration.dart
@@ -6,7 +6,7 @@
 
 import 'dart:isolate';
 
-import 'package:matcher/matcher.dart'
+import 'matcher.dart'
     show DefaultFailureHandler, configureExpectFailureHandler, TestFailure;
 
 import '../unittest.dart';
diff --git a/lib/unittest.dart b/lib/unittest.dart
index a22b271..51ccff4 100644
--- a/lib/unittest.dart
+++ b/lib/unittest.dart
@@ -7,18 +7,16 @@
 import 'dart:async';
 import 'dart:collection';
 
-import 'package:matcher/matcher.dart' show TestFailure, wrapAsync;
-
 import 'src/configuration.dart';
 import 'src/expected_function.dart';
 import 'src/group_context.dart';
 import 'src/internal_test_case.dart';
+import 'src/matcher.dart' show TestFailure, wrapAsync;
 import 'src/test_case.dart';
 import 'src/test_environment.dart';
 
-export 'package:matcher/matcher.dart';
-
 export 'src/configuration.dart';
+export 'src/matcher.dart';
 export 'src/simple_configuration.dart';
 export 'src/test_case.dart';
 
diff --git a/pubspec.yaml b/pubspec.yaml
index 5d284c4..16118e9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: unittest
-version: 0.11.6-dev
+version: 0.11.6
 author: Dart Team <misc@dartlang.org>
 description: A library for writing dart unit tests.
 homepage: https://github.com/dart-lang/old_unittest
@@ -7,10 +7,5 @@
   sdk: '>=1.0.0 <2.0.0'
 dependencies:
   stack_trace: '>=0.9.0 <2.0.0'
-
-  # Because unittest exports matcher, it needs to keep its version constraint
-  # tight to ensure that a constraint on unittest properly constraints all
-  # features it provides.
-  matcher: '>=0.11.4 <0.11.5'
 dev_dependencies:
   metatest: '>=0.1.0 <0.2.0'
diff --git a/test/core_matchers_test.dart b/test/core_matchers_test.dart
new file mode 100644
index 0000000..3e16882
--- /dev/null
+++ b/test/core_matchers_test.dart
@@ -0,0 +1,191 @@
+// 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.
+
+library unittest.matcher.core_matchers_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('isTrue', () {
+    shouldPass(true, isTrue);
+    shouldFail(false, isTrue, "Expected: true Actual: <false>");
+  });
+
+  test('isFalse', () {
+    shouldPass(false, isFalse);
+    shouldFail(10, isFalse, "Expected: false Actual: <10>");
+    shouldFail(true, isFalse, "Expected: false Actual: <true>");
+  });
+
+  test('isNull', () {
+    shouldPass(null, isNull);
+    shouldFail(false, isNull, "Expected: null Actual: <false>");
+  });
+
+  test('isNotNull', () {
+    shouldPass(false, isNotNull);
+    shouldFail(null, isNotNull, "Expected: not null Actual: <null>");
+  });
+
+  test('isNaN', () {
+    shouldPass(double.NAN, isNaN);
+    shouldFail(3.1, isNaN, "Expected: NaN Actual: <3.1>");
+  });
+
+  test('isNotNaN', () {
+    shouldPass(3.1, isNotNaN);
+    shouldFail(double.NAN, isNotNaN, "Expected: not NaN Actual: <NaN>");
+  });
+
+  test('same', () {
+    var a = new Map();
+    var b = new Map();
+    shouldPass(a, same(a));
+    shouldFail(b, same(a), "Expected: same instance as {} Actual: {}");
+  });
+
+  test('equals', () {
+    var a = new Map();
+    var b = new Map();
+    shouldPass(a, equals(a));
+    shouldPass(a, equals(b));
+  });
+
+  test('equals with a set', () {
+    var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    var set1 = numbers.toSet();
+    numbers.shuffle();
+    var set2 = numbers.toSet();
+
+    shouldPass(set2, equals(set1));
+    shouldPass(numbers, equals(set1));
+    shouldFail([
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9
+    ], equals(set1), matches(r"Expected: .*:\[1, 2, 3, 4, 5, 6, 7, 8, 9, 10\]"
+        r"  Actual: \[1, 2, 3, 4, 5, 6, 7, 8, 9\]"
+        r"   Which: does not contain 10"));
+    shouldFail([
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      10,
+      11
+    ], equals(set1), matches(r"Expected: .*:\[1, 2, 3, 4, 5, 6, 7, 8, 9, 10\]"
+        r"  Actual: \[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11\]"
+        r"   Which: larger than expected"));
+  });
+
+  test('anything', () {
+    var a = new Map();
+    shouldPass(0, anything);
+    shouldPass(null, anything);
+    shouldPass(a, anything);
+    shouldFail(a, isNot(anything), "Expected: not anything Actual: {}");
+  });
+
+  test('returnsNormally', () {
+    shouldPass(doesNotThrow, returnsNormally);
+    shouldFail(doesThrow, returnsNormally, matches(r"Expected: return normally"
+        r"  Actual: <Closure(: \(\) => dynamic "
+        r"from Function 'doesThrow': static\.)?>"
+        r"   Which: threw 'X'"));
+  });
+
+  test('hasLength', () {
+    var a = new Map();
+    var b = new List();
+    shouldPass(a, hasLength(0));
+    shouldPass(b, hasLength(0));
+    shouldPass('a', hasLength(1));
+    shouldFail(0, hasLength(0), new PrefixMatcher(
+        "Expected: an object with length of <0> "
+        "Actual: <0> "
+        "Which: has no length property"));
+
+    b.add(0);
+    shouldPass(b, hasLength(1));
+    shouldFail(b, hasLength(2), "Expected: an object with length of <2> "
+        "Actual: [0] "
+        "Which: has length of <1>");
+
+    b.add(0);
+    shouldFail(b, hasLength(1), "Expected: an object with length of <1> "
+        "Actual: [0, 0] "
+        "Which: has length of <2>");
+    shouldPass(b, hasLength(2));
+  });
+
+  test('scalar type mismatch', () {
+    shouldFail('error', equals(5.1), "Expected: <5.1> "
+        "Actual: 'error'");
+  });
+
+  test('nested type mismatch', () {
+    shouldFail(['error'], equals([5.1]), "Expected: [5.1] "
+        "Actual: ['error'] "
+        "Which: was 'error' instead of <5.1> at location [0]");
+  });
+
+  test('doubly-nested type mismatch', () {
+    shouldFail([['error']], equals([[5.1]]), "Expected: [[5.1]] "
+        "Actual: [['error']] "
+        "Which: was 'error' instead of <5.1> at location [0][0]");
+  });
+
+  test('doubly nested inequality', () {
+    var actual1 = [['foo', 'bar'], ['foo'], 3, []];
+    var expected1 = [['foo', 'bar'], ['foo'], 4, []];
+    var reason1 = "Expected: [['foo', 'bar'], ['foo'], 4, []] "
+        "Actual: [['foo', 'bar'], ['foo'], 3, []] "
+        "Which: was <3> instead of <4> at location [2]";
+
+    var actual2 = [['foo', 'barry'], ['foo'], 4, []];
+    var expected2 = [['foo', 'bar'], ['foo'], 4, []];
+    var reason2 = "Expected: [['foo', 'bar'], ['foo'], 4, []] "
+        "Actual: [['foo', 'barry'], ['foo'], 4, []] "
+        "Which: was 'barry' instead of 'bar' at location [0][1]";
+
+    var actual3 = [['foo', 'bar'], ['foo'], 4, {'foo': 'bar'}];
+    var expected3 = [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}];
+    var reason3 = "Expected: [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}] "
+        "Actual: [['foo', 'bar'], ['foo'], 4, {'foo': 'bar'}] "
+        "Which: was 'bar' instead of 'barry' at location [3]['foo']";
+
+    shouldFail(actual1, equals(expected1), reason1);
+    shouldFail(actual2, equals(expected2), reason2);
+    shouldFail(actual3, equals(expected3), reason3);
+  });
+
+  test('isInstanceOf', () {
+    shouldFail(0, new isInstanceOf<String>(),
+        "Expected: an instance of String Actual: <0>");
+    shouldPass('cow', new isInstanceOf<String>());
+  });
+
+  group('Predicate Matchers', () {
+    test('isInstanceOf', () {
+      shouldFail(0, predicate((x) => x is String, "an instance of String"),
+          "Expected: an instance of String Actual: <0>");
+      shouldPass('cow', predicate((x) => x is String, "an instance of String"));
+    });
+  });
+}
diff --git a/test/escape_test.dart b/test/escape_test.dart
new file mode 100644
index 0000000..f26177f
--- /dev/null
+++ b/test/escape_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2015, 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.
+
+library unittest.matcher.escape_test;
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/src/matcher/util.dart';
+
+void main() {
+  group('escaping should work with', () {
+    _testEscaping('no escaped chars', 'Hello, world!', 'Hello, world!');
+    _testEscaping('newline', '\n', r'\n');
+    _testEscaping('carriage return', '\r', r'\r');
+    _testEscaping('form feed', '\f', r'\f');
+    _testEscaping('backspace', '\b', r'\b');
+    _testEscaping('tab', '\t', r'\t');
+    _testEscaping('vertical tab', '\v', r'\v');
+    _testEscaping('null byte', '\x00', r'\x00');
+    _testEscaping('ASCII control character', '\x11', r'\x11');
+    _testEscaping('delete', '\x7F', r'\x7F');
+    _testEscaping('escape combos', r'\n', r'\\n');
+    _testEscaping('All characters',
+        'A new line\nA charriage return\rA form feed\fA backspace\b'
+        'A tab\tA vertical tab\vA slash\\A null byte\x00A control char\x1D'
+        'A delete\x7F',
+        r'A new line\nA charriage return\rA form feed\fA backspace\b'
+        r'A tab\tA vertical tab\vA slash\\A null byte\x00A control char\x1D'
+        r'A delete\x7F');
+  });
+
+  group('unequal strings remain unequal when escaped', () {
+    _testUnequalStrings('with a newline', '\n', r'\n');
+    _testUnequalStrings('with slash literals', '\\', r'\\');
+  });
+}
+
+/// Creates a [test] with name [name] that verifies [source] escapes to value
+/// [target].
+void _testEscaping(String name, String source, String target) {
+  test(name, () {
+    var escaped = escape(source);
+    expect(escaped == target, isTrue,
+        reason: "Expected escaped value: $target\n"
+        "  Actual escaped value: $escaped");
+  });
+}
+
+/// Creates a [test] with name [name] that ensures two different [String] values
+/// [s1] and [s2] remain unequal when escaped.
+void _testUnequalStrings(String name, String s1, String s2) {
+  test(name, () {
+    // Explicitly not using the equals matcher
+    expect(s1 != s2, isTrue, reason: 'The source values should be unequal');
+
+    var escapedS1 = escape(s1);
+    var escapedS2 = escape(s2);
+
+    // Explicitly not using the equals matcher
+    expect(escapedS1 != escapedS2, isTrue,
+        reason: 'Unequal strings, when escaped, should remain unequal.');
+  });
+}
diff --git a/test/future_matchers_test.dart b/test/future_matchers_test.dart
new file mode 100644
index 0000000..5a9a605
--- /dev/null
+++ b/test/future_matchers_test.dart
@@ -0,0 +1,75 @@
+// 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.
+
+library unittest.matcher.future_matchers_test;
+
+import 'dart:async';
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('completes - unexpected error', () {
+    var completer = new Completer();
+    completer.completeError('X');
+    shouldFail(completer.future, completes, contains(
+        'Expected future to complete successfully, '
+        'but it failed with X'), isAsync: true);
+  });
+
+  test('completes - successfully', () {
+    var completer = new Completer();
+    completer.complete('1');
+    shouldPass(completer.future, completes, isAsync: true);
+  });
+
+  test('throws - unexpected to see normal completion', () {
+    var completer = new Completer();
+    completer.complete('1');
+    shouldFail(completer.future, throws,
+        contains("Expected future to fail, but succeeded with '1'"),
+        isAsync: true);
+  });
+
+  test('throws - expected to see exception', () {
+    var completer = new Completer();
+    completer.completeError('X');
+    shouldPass(completer.future, throws, isAsync: true);
+  });
+
+  test('throws - expected to see exception thrown later on', () {
+    var completer = new Completer();
+    var chained = completer.future.then((_) {
+      throw 'X';
+    });
+    shouldPass(chained, throws, isAsync: true);
+    completer.complete('1');
+  });
+
+  test('throwsA - unexpected normal completion', () {
+    var completer = new Completer();
+    completer.complete('1');
+    shouldFail(completer.future, throwsA(equals('X')),
+        contains("Expected future to fail, but succeeded with '1'"),
+        isAsync: true);
+  });
+
+  test('throwsA - correct error', () {
+    var completer = new Completer();
+    completer.completeError('X');
+    shouldPass(completer.future, throwsA(equals('X')), isAsync: true);
+  });
+
+  test('throwsA - wrong error', () {
+    var completer = new Completer();
+    completer.completeError('X');
+    shouldFail(completer.future, throwsA(equals('Y')),
+        "Expected: 'Y' Actual: 'X' "
+        "Which: is different. "
+        "Expected: Y Actual: X ^ Differ at offset 0", isAsync: true);
+  });
+}
diff --git a/test/iterable_matchers_test.dart b/test/iterable_matchers_test.dart
new file mode 100644
index 0000000..d38f836
--- /dev/null
+++ b/test/iterable_matchers_test.dart
@@ -0,0 +1,165 @@
+// 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.
+
+library unittest.matcher.iterable_matchers_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('isEmpty', () {
+    shouldPass([], isEmpty);
+    shouldFail([1], isEmpty, "Expected: empty Actual: [1]");
+  });
+
+  test('isNotEmpty', () {
+    shouldFail([], isNotEmpty, "Expected: non-empty Actual: []");
+    shouldPass([1], isNotEmpty);
+  });
+
+  test('contains', () {
+    var d = [1, 2];
+    shouldPass(d, contains(1));
+    shouldFail(d, contains(0), "Expected: contains <0> "
+        "Actual: [1, 2]");
+  });
+
+  test('equals with matcher element', () {
+    var d = ['foo', 'bar'];
+    shouldPass(d, equals(['foo', startsWith('ba')]));
+    shouldFail(d, equals(['foo', endsWith('ba')]),
+        "Expected: ['foo', <a string ending with 'ba'>] "
+        "Actual: ['foo', 'bar'] "
+        "Which: does not match a string ending with 'ba' at location [1]");
+  });
+
+  test('isIn', () {
+    var d = [1, 2];
+    shouldPass(1, isIn(d));
+    shouldFail(0, isIn(d), "Expected: is in [1, 2] Actual: <0>");
+  });
+
+  test('everyElement', () {
+    var d = [1, 2];
+    var e = [1, 1, 1];
+    shouldFail(d, everyElement(1), "Expected: every element(<1>) "
+        "Actual: [1, 2] "
+        "Which: has value <2> which doesn't match <1> at index 1");
+    shouldPass(e, everyElement(1));
+  });
+
+  test('nested everyElement', () {
+    var d = [['foo', 'bar'], ['foo'], []];
+    var e = [['foo', 'bar'], ['foo'], 3, []];
+    shouldPass(d, everyElement(anyOf(isEmpty, contains('foo'))));
+    shouldFail(d, everyElement(everyElement(equals('foo'))),
+        "Expected: every element(every element('foo')) "
+        "Actual: [['foo', 'bar'], ['foo'], []] "
+        "Which: has value ['foo', 'bar'] which has value 'bar' "
+        "which is different. Expected: foo Actual: bar ^ "
+        "Differ at offset 0 at index 1 at index 0");
+    shouldFail(d,
+        everyElement(allOf(hasLength(greaterThan(0)), contains('foo'))),
+        "Expected: every element((an object with length of a value "
+        "greater than <0> and contains 'foo')) "
+        "Actual: [['foo', 'bar'], ['foo'], []] "
+        "Which: has value [] which has length of <0> at index 2");
+    shouldFail(d,
+        everyElement(allOf(contains('foo'), hasLength(greaterThan(0)))),
+        "Expected: every element((contains 'foo' and "
+        "an object with length of a value greater than <0>)) "
+        "Actual: [['foo', 'bar'], ['foo'], []] "
+        "Which: has value [] which doesn't match (contains 'foo' and "
+        "an object with length of a value greater than <0>) at index 2");
+    shouldFail(e,
+        everyElement(allOf(contains('foo'), hasLength(greaterThan(0)))),
+        "Expected: every element((contains 'foo' and an object with "
+        "length of a value greater than <0>)) "
+        "Actual: [['foo', 'bar'], ['foo'], 3, []] "
+        "Which: has value <3> which is not a string, map or iterable "
+        "at index 2");
+  });
+
+  test('anyElement', () {
+    var d = [1, 2];
+    var e = [1, 1, 1];
+    shouldPass(d, anyElement(2));
+    shouldFail(
+        e, anyElement(2), "Expected: some element <2> Actual: [1, 1, 1]");
+  });
+
+  test('orderedEquals', () {
+    shouldPass([null], orderedEquals([null]));
+    var d = [1, 2];
+    shouldPass(d, orderedEquals([1, 2]));
+    shouldFail(d, orderedEquals([2, 1]), "Expected: equals [2, 1] ordered "
+        "Actual: [1, 2] "
+        "Which: was <1> instead of <2> at location [0]");
+  });
+
+  test('unorderedEquals', () {
+    var d = [1, 2];
+    shouldPass(d, unorderedEquals([2, 1]));
+    shouldFail(d, unorderedEquals([1]), "Expected: equals [1] unordered "
+        "Actual: [1, 2] "
+        "Which: has too many elements (2 > 1)");
+    shouldFail(d, unorderedEquals([3, 2, 1]),
+        "Expected: equals [3, 2, 1] unordered "
+        "Actual: [1, 2] "
+        "Which: has too few elements (2 < 3)");
+    shouldFail(d, unorderedEquals([3, 1]), "Expected: equals [3, 1] unordered "
+        "Actual: [1, 2] "
+        "Which: has no match for <3> at index 0");
+  });
+
+  test('unorderedMatchess', () {
+    var d = [1, 2];
+    shouldPass(d, unorderedMatches([2, 1]));
+    shouldPass(d, unorderedMatches([greaterThan(1), greaterThan(0)]));
+    shouldFail(d, unorderedMatches([greaterThan(0)]),
+        "Expected: matches [a value greater than <0>] unordered "
+        "Actual: [1, 2] "
+        "Which: has too many elements (2 > 1)");
+    shouldFail(d, unorderedMatches([3, 2, 1]),
+        "Expected: matches [<3>, <2>, <1>] unordered "
+        "Actual: [1, 2] "
+        "Which: has too few elements (2 < 3)");
+    shouldFail(d, unorderedMatches([3, 1]),
+        "Expected: matches [<3>, <1>] unordered "
+        "Actual: [1, 2] "
+        "Which: has no match for <3> at index 0");
+    shouldFail(d, unorderedMatches([greaterThan(3), greaterThan(0)]),
+        "Expected: matches [a value greater than <3>, a value greater than "
+        "<0>] unordered "
+        "Actual: [1, 2] "
+        "Which: has no match for a value greater than <3> at index 0");
+  });
+
+  test('pairwise compare', () {
+    var c = [1, 2];
+    var d = [1, 2, 3];
+    var e = [1, 4, 9];
+    shouldFail('x', pairwiseCompare(e, (e, a) => a <= e, "less than or equal"),
+        "Expected: pairwise less than or equal [1, 4, 9] "
+        "Actual: 'x' "
+        "Which: is not an Iterable");
+    shouldFail(c, pairwiseCompare(e, (e, a) => a <= e, "less than or equal"),
+        "Expected: pairwise less than or equal [1, 4, 9] "
+        "Actual: [1, 2] "
+        "Which: has length 2 instead of 3");
+    shouldPass(d, pairwiseCompare(e, (e, a) => a <= e, "less than or equal"));
+    shouldFail(d, pairwiseCompare(e, (e, a) => a < e, "less than"),
+        "Expected: pairwise less than [1, 4, 9] "
+        "Actual: [1, 2, 3] "
+        "Which: has <1> which is not less than <1> at index 0");
+    shouldPass(d, pairwiseCompare(e, (e, a) => a * a == e, "square root of"));
+    shouldFail(d, pairwiseCompare(e, (e, a) => a + a == e, "double"),
+        "Expected: pairwise double [1, 4, 9] "
+        "Actual: [1, 2, 3] "
+        "Which: has <1> which is not double <1> at index 0");
+  });
+}
diff --git a/test/matchers_minified_test.dart b/test/matchers_minified_test.dart
new file mode 100644
index 0000000..af12906
--- /dev/null
+++ b/test/matchers_minified_test.dart
@@ -0,0 +1,142 @@
+// 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.
+
+// This file is for matcher tests that rely on the names of various Dart types.
+// These tests normally fail when run in minified dart2js, since the names will
+// be mangled. This version of the file is modified to expect minified names.
+
+library unittest.matcher.minified_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_common.dart';
+import 'test_utils.dart';
+
+// A regexp fragment matching a minified name.
+const _MINIFIED_NAME = r"[A-Za-z0-9]{1,3}";
+
+void main() {
+  initUtils();
+
+  group('Core matchers', () {
+    test('throwsFormatException', () {
+      shouldPass(() {
+        throw new FormatException('');
+      }, throwsFormatException);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsFormatException, matches(r"Expected: throws FormatException +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+
+    test('throwsArgumentError', () {
+      shouldPass(() {
+        throw new ArgumentError('');
+      }, throwsArgumentError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsArgumentError, matches(r"Expected: throws ArgumentError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+
+    test('throwsRangeError', () {
+      shouldPass(() {
+        throw new RangeError(0);
+      }, throwsRangeError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsRangeError, matches(r"Expected: throws RangeError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+
+    test('throwsNoSuchMethodError', () {
+      shouldPass(() {
+        throw new NoSuchMethodError(null, const Symbol(''), null, null);
+      }, throwsNoSuchMethodError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsNoSuchMethodError, matches(
+          r"Expected: throws NoSuchMethodError +"
+              r"Actual: <Closure(: \(\) => dynamic)?> +"
+              r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+
+    test('throwsUnimplementedError', () {
+      shouldPass(() {
+        throw new UnimplementedError('');
+      }, throwsUnimplementedError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsUnimplementedError, matches(
+          r"Expected: throws UnimplementedError +"
+              r"Actual: <Closure(: \(\) => dynamic)?> +"
+              r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+
+    test('throwsUnsupportedError', () {
+      shouldPass(() {
+        throw new UnsupportedError('');
+      }, throwsUnsupportedError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsUnsupportedError, matches(r"Expected: throws UnsupportedError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+
+    test('throwsStateError', () {
+      shouldPass(() {
+        throw new StateError('');
+      }, throwsStateError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsStateError, matches(r"Expected: throws StateError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw " + _MINIFIED_NAME + r":<Exception>"));
+    });
+  });
+
+  group('Iterable Matchers', () {
+    test('isEmpty', () {
+      var d = new SimpleIterable(0);
+      var e = new SimpleIterable(1);
+      shouldPass(d, isEmpty);
+      shouldFail(e, isEmpty,
+          matches(r"Expected: empty +Actual: " + _MINIFIED_NAME + r":\[1\]"));
+    });
+
+    test('isNotEmpty', () {
+      var d = new SimpleIterable(0);
+      var e = new SimpleIterable(1);
+      shouldPass(e, isNotEmpty);
+      shouldFail(d, isNotEmpty, matches(
+          r"Expected: non-empty +Actual: " + _MINIFIED_NAME + r":\[\]"));
+    });
+
+    test('contains', () {
+      var d = new SimpleIterable(3);
+      shouldPass(d, contains(2));
+      shouldFail(d, contains(5), matches(r"Expected: contains <5> +"
+          r"Actual: " + _MINIFIED_NAME + r":\[3, 2, 1\]"));
+    });
+  });
+
+  group('Feature Matchers', () {
+    test("Feature Matcher", () {
+      var w = new Widget();
+      w.price = 10;
+      shouldPass(w, new HasPrice(10));
+      shouldPass(w, new HasPrice(greaterThan(0)));
+      shouldFail(w, new HasPrice(greaterThan(10)), matches(
+          r"Expected: Widget with a price that is a value greater than "
+              r"<10> +"
+              r"Actual: <Instance of '" + _MINIFIED_NAME + r"'> +"
+              r"Which: has price with value <10> which is not "
+              r"a value greater than <10>"));
+    });
+  });
+}
diff --git a/test/matchers_unminified_test.dart b/test/matchers_unminified_test.dart
new file mode 100644
index 0000000..dc4b2b4
--- /dev/null
+++ b/test/matchers_unminified_test.dart
@@ -0,0 +1,139 @@
+// 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.
+
+// This file is for matcher tests that rely on the names of various Dart types.
+// These tests will fail when run in minified dart2js, since the names will be
+// mangled. A version of this file that works in minified dart2js is in
+// matchers_minified_test.dart.
+
+library unittest.matcher.unminified_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_common.dart';
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  group('Core matchers', () {
+    test('throwsFormatException', () {
+      shouldPass(() {
+        throw new FormatException('');
+      }, throwsFormatException);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsFormatException, matches(r"Expected: throws FormatException +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+
+    test('throwsArgumentError', () {
+      shouldPass(() {
+        throw new ArgumentError('');
+      }, throwsArgumentError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsArgumentError, matches(r"Expected: throws ArgumentError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+
+    test('throwsRangeError', () {
+      shouldPass(() {
+        throw new RangeError(0);
+      }, throwsRangeError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsRangeError, matches(r"Expected: throws RangeError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+
+    test('throwsNoSuchMethodError', () {
+      shouldPass(() {
+        throw new NoSuchMethodError(null, const Symbol(''), null, null);
+      }, throwsNoSuchMethodError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsNoSuchMethodError, matches(
+          r"Expected: throws NoSuchMethodError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+
+    test('throwsUnimplementedError', () {
+      shouldPass(() {
+        throw new UnimplementedError('');
+      }, throwsUnimplementedError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsUnimplementedError, matches(
+          r"Expected: throws UnimplementedError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+
+    test('throwsUnsupportedError', () {
+      shouldPass(() {
+        throw new UnsupportedError('');
+      }, throwsUnsupportedError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsUnsupportedError, matches(r"Expected: throws UnsupportedError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+
+    test('throwsStateError', () {
+      shouldPass(() {
+        throw new StateError('');
+      }, throwsStateError);
+      shouldFail(() {
+        throw new Exception();
+      }, throwsStateError, matches(r"Expected: throws StateError +"
+          r"Actual: <Closure(: \(\) => dynamic)?> +"
+          r"Which: threw \?:<Exception>"));
+    });
+  });
+
+  group('Iterable Matchers', () {
+    test('isEmpty', () {
+      var d = new SimpleIterable(0);
+      var e = new SimpleIterable(1);
+      shouldPass(d, isEmpty);
+      shouldFail(e, isEmpty, "Expected: empty "
+          "Actual: SimpleIterable:[1]");
+    });
+
+    test('isNotEmpty', () {
+      var d = new SimpleIterable(0);
+      var e = new SimpleIterable(1);
+      shouldPass(e, isNotEmpty);
+      shouldFail(d, isNotEmpty, "Expected: non-empty "
+          "Actual: SimpleIterable:[]");
+    });
+
+    test('contains', () {
+      var d = new SimpleIterable(3);
+      shouldPass(d, contains(2));
+      shouldFail(d, contains(5), "Expected: contains <5> "
+          "Actual: SimpleIterable:[3, 2, 1]");
+    });
+  });
+
+  group('Feature Matchers', () {
+    test("Feature Matcher", () {
+      var w = new Widget();
+      w.price = 10;
+      shouldPass(w, new HasPrice(10));
+      shouldPass(w, new HasPrice(greaterThan(0)));
+      shouldFail(w, new HasPrice(greaterThan(10)),
+          "Expected: Widget with a price that is a value greater than <10> "
+          "Actual: <Instance of 'Widget'> "
+          "Which: has price with value <10> which is not "
+          "a value greater than <10>");
+    });
+  });
+}
diff --git a/test/numeric_matchers_test.dart b/test/numeric_matchers_test.dart
new file mode 100644
index 0000000..3d1bbe7
--- /dev/null
+++ b/test/numeric_matchers_test.dart
@@ -0,0 +1,142 @@
+// 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.
+
+library unittest.matcher.numeric_matchers_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('greaterThan', () {
+    shouldPass(10, greaterThan(9));
+    shouldFail(9, greaterThan(10), "Expected: a value greater than <10> "
+        "Actual: <9> "
+        "Which: is not a value greater than <10>");
+  });
+
+  test('greaterThanOrEqualTo', () {
+    shouldPass(10, greaterThanOrEqualTo(10));
+    shouldFail(9, greaterThanOrEqualTo(10),
+        "Expected: a value greater than or equal to <10> "
+        "Actual: <9> "
+        "Which: is not a value greater than or equal to <10>");
+  });
+
+  test('lessThan', () {
+    shouldFail(10, lessThan(9), "Expected: a value less than <9> "
+        "Actual: <10> "
+        "Which: is not a value less than <9>");
+    shouldPass(9, lessThan(10));
+  });
+
+  test('lessThanOrEqualTo', () {
+    shouldPass(10, lessThanOrEqualTo(10));
+    shouldFail(11, lessThanOrEqualTo(10),
+        "Expected: a value less than or equal to <10> "
+        "Actual: <11> "
+        "Which: is not a value less than or equal to <10>");
+  });
+
+  test('isZero', () {
+    shouldPass(0, isZero);
+    shouldFail(1, isZero, "Expected: a value equal to <0> "
+        "Actual: <1> "
+        "Which: is not a value equal to <0>");
+  });
+
+  test('isNonZero', () {
+    shouldFail(0, isNonZero, "Expected: a value not equal to <0> "
+        "Actual: <0> "
+        "Which: is not a value not equal to <0>");
+    shouldPass(1, isNonZero);
+  });
+
+  test('isPositive', () {
+    shouldFail(-1, isPositive, "Expected: a positive value "
+        "Actual: <-1> "
+        "Which: is not a positive value");
+    shouldFail(0, isPositive, "Expected: a positive value "
+        "Actual: <0> "
+        "Which: is not a positive value");
+    shouldPass(1, isPositive);
+  });
+
+  test('isNegative', () {
+    shouldPass(-1, isNegative);
+    shouldFail(0, isNegative, "Expected: a negative value "
+        "Actual: <0> "
+        "Which: is not a negative value");
+  });
+
+  test('isNonPositive', () {
+    shouldPass(-1, isNonPositive);
+    shouldPass(0, isNonPositive);
+    shouldFail(1, isNonPositive, "Expected: a non-positive value "
+        "Actual: <1> "
+        "Which: is not a non-positive value");
+  });
+
+  test('isNonNegative', () {
+    shouldPass(1, isNonNegative);
+    shouldPass(0, isNonNegative);
+    shouldFail(-1, isNonNegative, "Expected: a non-negative value "
+        "Actual: <-1> "
+        "Which: is not a non-negative value");
+  });
+
+  test('closeTo', () {
+    shouldPass(0, closeTo(0, 1));
+    shouldPass(-1, closeTo(0, 1));
+    shouldPass(1, closeTo(0, 1));
+    shouldFail(1.001, closeTo(0, 1),
+        "Expected: a numeric value within <1> of <0> "
+        "Actual: <1.001> "
+        "Which: differs by <1.001>");
+    shouldFail(-1.001, closeTo(0, 1),
+        "Expected: a numeric value within <1> of <0> "
+        "Actual: <-1.001> "
+        "Which: differs by <1.001>");
+  });
+
+  test('inInclusiveRange', () {
+    shouldFail(-1, inInclusiveRange(0, 2),
+        "Expected: be in range from 0 (inclusive) to 2 (inclusive) "
+        "Actual: <-1>");
+    shouldPass(0, inInclusiveRange(0, 2));
+    shouldPass(1, inInclusiveRange(0, 2));
+    shouldPass(2, inInclusiveRange(0, 2));
+    shouldFail(3, inInclusiveRange(0, 2),
+        "Expected: be in range from 0 (inclusive) to 2 (inclusive) "
+        "Actual: <3>");
+  });
+
+  test('inExclusiveRange', () {
+    shouldFail(0, inExclusiveRange(0, 2),
+        "Expected: be in range from 0 (exclusive) to 2 (exclusive) "
+        "Actual: <0>");
+    shouldPass(1, inExclusiveRange(0, 2));
+    shouldFail(2, inExclusiveRange(0, 2),
+        "Expected: be in range from 0 (exclusive) to 2 (exclusive) "
+        "Actual: <2>");
+  });
+
+  test('inOpenClosedRange', () {
+    shouldFail(0, inOpenClosedRange(0, 2),
+        "Expected: be in range from 0 (exclusive) to 2 (inclusive) "
+        "Actual: <0>");
+    shouldPass(1, inOpenClosedRange(0, 2));
+    shouldPass(2, inOpenClosedRange(0, 2));
+  });
+
+  test('inClosedOpenRange', () {
+    shouldPass(0, inClosedOpenRange(0, 2));
+    shouldPass(1, inClosedOpenRange(0, 2));
+    shouldFail(2, inClosedOpenRange(0, 2),
+        "Expected: be in range from 0 (inclusive) to 2 (exclusive) "
+        "Actual: <2>");
+  });
+}
diff --git a/test/operator_matchers_test.dart b/test/operator_matchers_test.dart
new file mode 100644
index 0000000..4938917
--- /dev/null
+++ b/test/operator_matchers_test.dart
@@ -0,0 +1,58 @@
+// 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.
+
+library unittest.matcher.operator_matchers_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('anyOf', () {
+    // with a list
+    shouldFail(
+        0, anyOf([equals(1), equals(2)]), "Expected: (<1> or <2>) Actual: <0>");
+    shouldPass(1, anyOf([equals(1), equals(2)]));
+
+    // with individual items
+    shouldFail(
+        0, anyOf(equals(1), equals(2)), "Expected: (<1> or <2>) Actual: <0>");
+    shouldPass(1, anyOf(equals(1), equals(2)));
+  });
+
+  test('allOf', () {
+    // with a list
+    shouldPass(1, allOf([lessThan(10), greaterThan(0)]));
+    shouldFail(-1, allOf([lessThan(10), greaterThan(0)]),
+        "Expected: (a value less than <10> and a value greater than <0>) "
+        "Actual: <-1> "
+        "Which: is not a value greater than <0>");
+
+    // with individual items
+    shouldPass(1, allOf(lessThan(10), greaterThan(0)));
+    shouldFail(-1, allOf(lessThan(10), greaterThan(0)),
+        "Expected: (a value less than <10> and a value greater than <0>) "
+        "Actual: <-1> "
+        "Which: is not a value greater than <0>");
+
+    // with maximum items
+    shouldPass(1, allOf(lessThan(10), lessThan(9), lessThan(8),
+        lessThan(7), lessThan(6), lessThan(5), lessThan(4)));
+    shouldFail(4, allOf(lessThan(10), lessThan(9), lessThan(8), lessThan(7),
+            lessThan(6), lessThan(5), lessThan(4)),
+        "Expected: (a value less than <10> and a value less than <9> and a "
+        "value less than <8> and a value less than <7> and a value less than "
+        "<6> and a value less than <5> and a value less than <4>) "
+        "Actual: <4> "
+        "Which: is not a value less than <4>");
+  });
+
+  test('If the first argument is a List, the rest must be null', () {
+    expect(() => allOf([], 5), throwsArgumentError);
+    expect(
+        () => anyOf([], null, null, null, null, null, 42), throwsArgumentError);
+  });
+}
diff --git a/test/pretty_print_test.dart b/test/pretty_print_test.dart
new file mode 100644
index 0000000..6717a56
--- /dev/null
+++ b/test/pretty_print_test.dart
@@ -0,0 +1,193 @@
+// Copyright (c) 2013, 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.
+
+library unittest.matcher.pretty_print_test;
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/src/matcher/pretty_print.dart';
+
+void main() {
+  test('with primitive objects', () {
+    expect(prettyPrint(12), equals('<12>'));
+    expect(prettyPrint(12.13), equals('<12.13>'));
+    expect(prettyPrint(true), equals('<true>'));
+    expect(prettyPrint(null), equals('<null>'));
+    expect(prettyPrint(() => 12), matches(r'<Closure(: \(\) => dynamic)?>'));
+  });
+
+  group('with a string', () {
+    test('containing simple characters', () {
+      expect(prettyPrint('foo'), equals("'foo'"));
+    });
+
+    test('containing newlines', () {
+      expect(prettyPrint('foo\nbar\nbaz'), equals("'foo\\n'\n"
+          "  'bar\\n'\n"
+          "  'baz'"));
+    });
+
+    test('containing escapable characters', () {
+      expect(
+          prettyPrint("foo\rbar\tbaz'qux\v"), equals(r"'foo\rbar\tbaz\'qux\v'"));
+    });
+  });
+
+  group('with an iterable', () {
+    test('containing primitive objects', () {
+      expect(prettyPrint([1, true, 'foo']), equals("[1, true, 'foo']"));
+    });
+
+    test('containing a multiline string', () {
+      expect(prettyPrint(['foo', 'bar\nbaz\nbip', 'qux']), equals("[\n"
+          "  'foo',\n"
+          "  'bar\\n'\n"
+          "    'baz\\n'\n"
+          "    'bip',\n"
+          "  'qux'\n"
+          "]"));
+    });
+
+    test('containing a matcher', () {
+      expect(prettyPrint(['foo', endsWith('qux')]),
+          equals("['foo', <a string ending with 'qux'>]"));
+    });
+
+    test("that's under maxLineLength", () {
+      expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxLineLength: 30),
+          equals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"));
+    });
+
+    test("that's over maxLineLength", () {
+      expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxLineLength: 29),
+          equals("[\n"
+              "  0,\n"
+              "  1,\n"
+              "  2,\n"
+              "  3,\n"
+              "  4,\n"
+              "  5,\n"
+              "  6,\n"
+              "  7,\n"
+              "  8,\n"
+              "  9\n"
+              "]"));
+    });
+
+    test("factors indentation into maxLineLength", () {
+      expect(prettyPrint(["foo\nbar", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],],
+          maxLineLength: 30), equals("[\n"
+          "  'foo\\n'\n"
+          "    'bar',\n"
+          "  [\n"
+          "    0,\n"
+          "    1,\n"
+          "    2,\n"
+          "    3,\n"
+          "    4,\n"
+          "    5,\n"
+          "    6,\n"
+          "    7,\n"
+          "    8,\n"
+          "    9\n"
+          "  ]\n"
+          "]"));
+    });
+
+    test("that's under maxItems", () {
+      expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxItems: 10),
+          equals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"));
+    });
+
+    test("that's over maxItems", () {
+      expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxItems: 9),
+          equals("[0, 1, 2, 3, 4, 5, 6, 7, ...]"));
+    });
+
+    test("that's recursive", () {
+      var list = [1, 2, 3];
+      list.add(list);
+      expect(prettyPrint(list), equals("[1, 2, 3, (recursive)]"));
+    });
+  });
+
+  group("with a map", () {
+    test('containing primitive objects', () {
+      expect(prettyPrint({'foo': 1, 'bar': true}),
+          equals("{'foo': 1, 'bar': true}"));
+    });
+
+    test('containing a multiline string key', () {
+      expect(prettyPrint({'foo\nbar': 1, 'bar': true}), equals("{\n"
+          "  'foo\\n'\n"
+          "    'bar': 1,\n"
+          "  'bar': true\n"
+          "}"));
+    });
+
+    test('containing a multiline string value', () {
+      expect(prettyPrint({'foo': 'bar\nbaz', 'qux': true}), equals("{\n"
+          "  'foo': 'bar\\n'\n"
+          "    'baz',\n"
+          "  'qux': true\n"
+          "}"));
+    });
+
+    test('containing a multiline string key/value pair', () {
+      expect(prettyPrint({'foo\nbar': 'baz\nqux'}), equals("{\n"
+          "  'foo\\n'\n"
+          "    'bar': 'baz\\n'\n"
+          "    'qux'\n"
+          "}"));
+    });
+
+    test('containing a matcher key', () {
+      expect(prettyPrint({endsWith('bar'): 'qux'}),
+          equals("{<a string ending with 'bar'>: 'qux'}"));
+    });
+
+    test('containing a matcher value', () {
+      expect(prettyPrint({'foo': endsWith('qux')}),
+          equals("{'foo': <a string ending with 'qux'>}"));
+    });
+
+    test("that's under maxLineLength", () {
+      expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxLineLength: 32),
+          equals("{'0': 1, '2': 3, '4': 5, '6': 7}"));
+    });
+
+    test("that's over maxLineLength", () {
+      expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxLineLength: 31),
+          equals("{\n"
+              "  '0': 1,\n"
+              "  '2': 3,\n"
+              "  '4': 5,\n"
+              "  '6': 7\n"
+              "}"));
+    });
+
+    test("factors indentation into maxLineLength", () {
+      expect(prettyPrint(["foo\nbar", {'0': 1, '2': 3, '4': 5, '6': 7}],
+          maxLineLength: 32), equals("[\n"
+          "  'foo\\n'\n"
+          "    'bar',\n"
+          "  {\n"
+          "    '0': 1,\n"
+          "    '2': 3,\n"
+          "    '4': 5,\n"
+          "    '6': 7\n"
+          "  }\n"
+          "]"));
+    });
+
+    test("that's under maxItems", () {
+      expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxItems: 4),
+          equals("{'0': 1, '2': 3, '4': 5, '6': 7}"));
+    });
+
+    test("that's over maxItems", () {
+      expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxItems: 3),
+          equals("{'0': 1, '2': 3, ...}"));
+    });
+  });
+}
diff --git a/test/prints_matcher_test.dart b/test/prints_matcher_test.dart
new file mode 100644
index 0000000..f35acf2
--- /dev/null
+++ b/test/prints_matcher_test.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2014, 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.
+
+library unittest.matcher.prints_matchers_test;
+
+import 'dart:async';
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+/// The VM and dart2js have different toStrings for closures.
+final closureToString = (() {}).toString();
+
+void main() {
+  initUtils();
+
+  group('synchronous', () {
+    test("passes with an expected print", () {
+      shouldPass(() => print("Hello, world!"), prints("Hello, world!\n"));
+    });
+
+    test("combines multiple prints", () {
+      shouldPass(() {
+        print("Hello");
+        print("World!");
+      }, prints("Hello\nWorld!\n"));
+    });
+
+    test("works with a Matcher", () {
+      shouldPass(() => print("Hello, world!"), prints(contains("Hello")));
+    });
+
+    test("describes a failure nicely", () {
+      shouldFail(() => print("Hello, world!"), prints("Goodbye, world!\n"),
+          "Expected: prints 'Goodbye, world!\\n' ''"
+          "  Actual: <$closureToString> "
+          "   Which: printed 'Hello, world!\\n' ''"
+          "   Which: is different. "
+          "Expected: Goodbye, w ... "
+          "  Actual: Hello, wor ... "
+          "          ^ Differ at offset 0");
+    });
+
+    test("describes a failure with a non-descriptive Matcher nicely", () {
+      shouldFail(() => print("Hello, world!"), prints(contains("Goodbye")),
+          "Expected: prints contains 'Goodbye'"
+          "  Actual: <$closureToString> "
+          "   Which: printed 'Hello, world!\\n' ''");
+    });
+
+    test("describes a failure with no text nicely", () {
+      shouldFail(() {}, prints(contains("Goodbye")),
+          "Expected: prints contains 'Goodbye'"
+          "  Actual: <$closureToString> "
+          "   Which: printed nothing.");
+    });
+  });
+
+  group('asynchronous', () {
+    test("passes with an expected print", () {
+      shouldPass(() => new Future(() => print("Hello, world!")),
+          prints("Hello, world!\n"));
+    });
+
+    test("combines multiple prints", () {
+      shouldPass(() => new Future(() {
+        print("Hello");
+        print("World!");
+      }), prints("Hello\nWorld!\n"));
+    });
+
+    test("works with a Matcher", () {
+      shouldPass(() => new Future(() => print("Hello, world!")),
+          prints(contains("Hello")));
+    });
+
+    test("describes a failure nicely", () {
+      shouldFail(() => new Future(() => print("Hello, world!")),
+          prints("Goodbye, world!\n"), "Expected: 'Goodbye, world!\\n' ''"
+          "  Actual: 'Hello, world!\\n' ''"
+          "   Which: is different. "
+          "Expected: Goodbye, w ... "
+          "  Actual: Hello, wor ... "
+          "          ^ Differ at offset 0", isAsync: true);
+    });
+
+    test("describes a failure with a non-descriptive Matcher nicely", () {
+      shouldFail(() => new Future(() => print("Hello, world!")),
+          prints(contains("Goodbye")), "Expected: contains 'Goodbye'"
+          "  Actual: 'Hello, world!\\n' ''", isAsync: true);
+    });
+
+    test("describes a failure with no text nicely", () {
+      shouldFail(() => new Future.value(), prints(contains("Goodbye")),
+          "Expected: contains 'Goodbye'"
+          "  Actual: ''", isAsync: true);
+    });
+  });
+}
diff --git a/test/string_matchers_test.dart b/test/string_matchers_test.dart
new file mode 100644
index 0000000..511187d
--- /dev/null
+++ b/test/string_matchers_test.dart
@@ -0,0 +1,114 @@
+// 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.
+
+library unittest.matcher.string_matchers_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('Reports mismatches in whitespace and escape sequences', () {
+    shouldFail('before\nafter', equals('before\\nafter'),
+        contains('Differ at offset 7'));
+  });
+
+  test('collapseWhitespace', () {
+    var source = '\t\r\n hello\t\r\n world\r\t \n';
+    expect(collapseWhitespace(source), 'hello world');
+  });
+
+  test('isEmpty', () {
+    shouldPass('', isEmpty);
+    shouldFail(null, isEmpty, startsWith("Expected: empty  Actual: <null>"));
+    shouldFail(0, isEmpty, startsWith("Expected: empty  Actual: <0>"));
+    shouldFail('a', isEmpty, startsWith("Expected: empty  Actual: 'a'"));
+  });
+
+  // Regression test for: https://code.google.com/p/dart/issues/detail?id=21562
+  test('isNot(isEmpty)', () {
+    shouldPass('a', isNot(isEmpty));
+    shouldFail('', isNot(isEmpty), 'Expected: not empty Actual: \'\'');
+    shouldFail(null, isNot(isEmpty),
+        startsWith('Expected: not empty  Actual: <null>'));
+  });
+
+  test('isNotEmpty', () {
+    shouldFail('', isNotEmpty, startsWith("Expected: non-empty  Actual: ''"));
+    shouldFail(
+        null, isNotEmpty, startsWith("Expected: non-empty  Actual: <null>"));
+    shouldFail(0, isNotEmpty, startsWith("Expected: non-empty  Actual: <0>"));
+    shouldPass('a', isNotEmpty);
+  });
+
+  test('equalsIgnoringCase', () {
+    shouldPass('hello', equalsIgnoringCase('HELLO'));
+    shouldFail('hi', equalsIgnoringCase('HELLO'),
+        "Expected: 'HELLO' ignoring case Actual: 'hi'");
+  });
+
+  test('equalsIgnoringWhitespace', () {
+    shouldPass(' hello   world  ', equalsIgnoringWhitespace('hello world'));
+    shouldFail(' helloworld  ', equalsIgnoringWhitespace('hello world'),
+        "Expected: 'hello world' ignoring whitespace "
+        "Actual: ' helloworld ' "
+        "Which: is 'helloworld' with whitespace compressed");
+  });
+
+  test('startsWith', () {
+    shouldPass('hello', startsWith(''));
+    shouldPass('hello', startsWith('hell'));
+    shouldPass('hello', startsWith('hello'));
+    shouldFail('hello', startsWith('hello '),
+        "Expected: a string starting with 'hello ' "
+        "Actual: 'hello'");
+  });
+
+  test('endsWith', () {
+    shouldPass('hello', endsWith(''));
+    shouldPass('hello', endsWith('lo'));
+    shouldPass('hello', endsWith('hello'));
+    shouldFail('hello', endsWith(' hello'),
+        "Expected: a string ending with ' hello' "
+        "Actual: 'hello'");
+  });
+
+  test('contains', () {
+    shouldPass('hello', contains(''));
+    shouldPass('hello', contains('h'));
+    shouldPass('hello', contains('o'));
+    shouldPass('hello', contains('hell'));
+    shouldPass('hello', contains('hello'));
+    shouldFail(
+        'hello', contains(' '), "Expected: contains ' ' Actual: 'hello'");
+  });
+
+  test('stringContainsInOrder', () {
+    shouldPass('goodbye cruel world', stringContainsInOrder(['']));
+    shouldPass('goodbye cruel world', stringContainsInOrder(['goodbye']));
+    shouldPass('goodbye cruel world', stringContainsInOrder(['cruel']));
+    shouldPass('goodbye cruel world', stringContainsInOrder(['world']));
+    shouldPass(
+        'goodbye cruel world', stringContainsInOrder(['good', 'bye', 'world']));
+    shouldPass(
+        'goodbye cruel world', stringContainsInOrder(['goodbye', 'cruel']));
+    shouldPass(
+        'goodbye cruel world', stringContainsInOrder(['cruel', 'world']));
+    shouldPass('goodbye cruel world',
+        stringContainsInOrder(['goodbye', 'cruel', 'world']));
+    shouldFail('goodbye cruel world',
+        stringContainsInOrder(['goo', 'cruel', 'bye']),
+        "Expected: a string containing 'goo', 'cruel', 'bye' in order "
+        "Actual: 'goodbye cruel world'");
+  });
+
+  test('matches', () {
+    shouldPass('c0d', matches('[a-z][0-9][a-z]'));
+    shouldPass('c0d', matches(new RegExp('[a-z][0-9][a-z]')));
+    shouldFail('cOd', matches('[a-z][0-9][a-z]'),
+        "Expected: match '[a-z][0-9][a-z]' Actual: 'cOd'");
+  });
+}
diff --git a/test/test_common.dart b/test/test_common.dart
new file mode 100644
index 0000000..7d7d70b
--- /dev/null
+++ b/test/test_common.dart
@@ -0,0 +1,58 @@
+// 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.
+
+library unittest.matcher.test_common;
+
+import 'dart:collection';
+
+import 'package:unittest/unittest.dart';
+
+class Widget {
+  int price;
+}
+
+class HasPrice extends CustomMatcher {
+  HasPrice(matcher) : super("Widget with a price that is", "price", matcher);
+  featureValueOf(actual) => actual.price;
+}
+
+class SimpleIterable extends IterableBase<int> {
+  final int count;
+
+  SimpleIterable(this.count);
+
+  bool contains(int val) => count < val ? false : true;
+
+  bool any(bool f(element)) {
+    for (var i = 0; i <= count; i++) {
+      if (f(i)) return true;
+    }
+    return false;
+  }
+
+  String toString() => "<[$count]>";
+
+  Iterator get iterator {
+    return new _SimpleIterator(count);
+  }
+}
+
+class _SimpleIterator implements Iterator<int> {
+  int _count;
+  int _current;
+
+  _SimpleIterator(this._count);
+
+  bool moveNext() {
+    if (_count > 0) {
+      _current = _count;
+      _count--;
+      return true;
+    }
+    _current = null;
+    return false;
+  }
+
+  int get current => _current;
+}
diff --git a/test/test_utils.dart b/test/test_utils.dart
new file mode 100644
index 0000000..d0adda2
--- /dev/null
+++ b/test/test_utils.dart
@@ -0,0 +1,83 @@
+// 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.
+
+library unittest.matcher.test_utils;
+
+import 'dart:async';
+
+import 'package:unittest/unittest.dart';
+
+int _errorCount;
+String _errorString;
+FailureHandler _testHandler = null;
+
+class MyFailureHandler extends DefaultFailureHandler {
+  void fail(String reason) {
+    ++_errorCount;
+    _errorString = reason;
+  }
+}
+
+void initUtils() {
+  if (_testHandler == null) {
+    _testHandler = new MyFailureHandler();
+  }
+}
+
+void shouldFail(value, Matcher matcher, expected, {bool isAsync: false}) {
+  configureExpectFailureHandler(_testHandler);
+  _errorCount = 0;
+  _errorString = '';
+  expect(value, matcher);
+  afterTest() {
+    configureExpectFailureHandler(null);
+    expect(_errorCount, equals(1));
+    if (expected is String) {
+      expect(_errorString, equalsIgnoringWhitespace(expected));
+    } else {
+      expect(_errorString.replaceAll('\n', ''), expected);
+    }
+  }
+
+  if (isAsync) {
+    Timer.run(expectAsync(afterTest));
+  } else {
+    afterTest();
+  }
+}
+
+void shouldPass(value, Matcher matcher, {bool isAsync: false}) {
+  configureExpectFailureHandler(_testHandler);
+  _errorCount = 0;
+  _errorString = '';
+  expect(value, matcher);
+  afterTest() {
+    configureExpectFailureHandler(null);
+    expect(_errorCount, equals(0));
+  }
+  if (isAsync) {
+    Timer.run(expectAsync(afterTest));
+  } else {
+    afterTest();
+  }
+}
+
+doesNotThrow() {}
+doesThrow() {
+  throw 'X';
+}
+
+class PrefixMatcher extends Matcher {
+  final String _prefix;
+  const PrefixMatcher(this._prefix);
+  bool matches(item, Map matchState) {
+    return item is String &&
+        (collapseWhitespace(item)).startsWith(collapseWhitespace(_prefix));
+  }
+
+  Description describe(Description description) => description
+      .add('a string starting with ')
+      .addDescriptionOf(collapseWhitespace(_prefix))
+      .add(' ignoring whitespace');
+}
diff --git a/test/throws_matchers_test.dart b/test/throws_matchers_test.dart
new file mode 100644
index 0000000..7833f9a
--- /dev/null
+++ b/test/throws_matchers_test.dart
@@ -0,0 +1,66 @@
+// 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.
+
+library unittest.matcher.core_matchers_test;
+
+import 'package:unittest/unittest.dart';
+
+import 'test_utils.dart';
+
+void main() {
+  initUtils();
+
+  test('throws', () {
+    shouldFail(doesNotThrow, throws, matches(r"Expected: throws"
+        r"  Actual: <Closure(: \(\) => dynamic "
+        r"from Function 'doesNotThrow': static\.)?>"
+        r"   Which: did not throw"));
+    shouldPass(doesThrow, throws);
+    shouldFail(true, throws, "Expected: throws"
+        "  Actual: <true>"
+        "   Which: is not a Function or Future");
+  });
+
+  test('throwsA', () {
+    shouldPass(doesThrow, throwsA(equals('X')));
+    shouldFail(doesThrow, throwsA(equals('Y')), matches(r"Expected: throws 'Y'"
+        r"  Actual: <Closure(: \(\) => dynamic "
+        r"from Function 'doesThrow': static\.)?>"
+        r"   Which: threw 'X'"));
+  });
+
+  test('throwsA', () {
+    shouldPass(doesThrow, throwsA(equals('X')));
+    shouldFail(doesThrow, throwsA(equals('Y')), matches("Expected: throws 'Y'.*"
+        "Actual: <Closure.*"
+        "Which: threw 'X'"));
+  });
+
+  group('exception/error matchers', () {
+    test('throwsCyclicInitializationError', () {
+      expect(() => _Bicycle.foo, throwsCyclicInitializationError);
+    });
+
+    test('throwsConcurrentModificationError', () {
+      expect(() {
+        var a = {'foo': 'bar'};
+        for (var k in a.keys) {
+          a.remove(k);
+        }
+      }, throwsConcurrentModificationError);
+    });
+
+    test('throwsNullThrownError', () {
+      expect(() => throw null, throwsNullThrownError);
+    });
+  });
+}
+
+class _Bicycle {
+  static final foo = bar();
+
+  static bar() {
+    return foo + 1;
+  }
+}