Add containsAll (#76)

Closes #72

- Add `allowUnmatchedValues` to the implementation for unordered matches.
- Implement containsAll as an unorderedMatches which does allows extra
  values in the checked iterable.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50e0e32..a5ebcfd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
   element and order of the elements doesn't line up with the order of the
   matchers.
 
+* Add containsAll matcher for Iterables. This Matcher checks that all
+  values/matchers in an expected iterable are satisfied by an element in the
+  value without allowing the same value to satisfy multiple matchers.
+
 ## 0.12.1+4
 
 * Fixed SDK constraint to allow edge builds.
diff --git a/lib/src/iterable_matchers.dart b/lib/src/iterable_matchers.dart
index 697df0c..a4a122c 100644
--- a/lib/src/iterable_matchers.dart
+++ b/lib/src/iterable_matchers.dart
@@ -152,9 +152,11 @@
 
 class _UnorderedMatches extends Matcher {
   final List<Matcher> _expected;
+  final bool _allowUnmatchedValues;
 
-  _UnorderedMatches(Iterable expected)
-      : _expected = expected.map(wrapMatcher).toList();
+  _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues})
+      : _expected = expected.map(wrapMatcher).toList(),
+        _allowUnmatchedValues = allowUnmatchedValues ?? false;
 
   String _test(item) {
     if (item is! Iterable) return 'not iterable';
@@ -164,7 +166,7 @@
     // Check the lengths are the same.
     if (_expected.length > values.length) {
       return 'has too few elements (${values.length} < ${_expected.length})';
-    } else if (_expected.length < values.length) {
+    } else if (!_allowUnmatchedValues && _expected.length < values.length) {
       return 'has too many elements (${values.length} > ${_expected.length})';
     }
 
@@ -300,11 +302,43 @@
 }
 
 /// Matches [Iterable]s which contain an element matching every value in
+/// [expected] in any order, and may contain additional values.
+///
+/// For example: `[0, 1, 0, 2, 0]` matches `containsAll([1, 2])` and
+/// `containsAll([2, 1])` but not `containsAll([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
+///
+/// Each element in the value will only be considered a match for a single
+/// matcher in [expected] even if it could satisfy more than one. For instance
+/// `containsAll([greaterThan(1), greaterThan(2)])` will not be satisfied by
+/// `[3]`. To check that all matchers are satisfied within an iterable and allow
+/// the same element to satisfy multiple matchers use
+/// `allOf(matchers.map(contains))`.
+///
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
+Matcher containsAll(Iterable expected) => new _ContainsAll(expected);
+
+class _ContainsAll extends _UnorderedMatches {
+  final Iterable _unwrappedExpected;
+
+  _ContainsAll(Iterable expected)
+      : _unwrappedExpected = expected,
+        super(expected.map(wrapMatcher), allowUnmatchedValues: true);
+  @override
+  Description describe(Description description) =>
+      description.add('contains all of ').addDescriptionOf(_unwrappedExpected);
+}
+
+/// Matches [Iterable]s which contain an element matching every value in
 /// [expected] in the same order, but may contain additional values interleaved
 /// throughout.
 ///
 /// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not
 /// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
 Matcher containsAllInOrder(Iterable expected) =>
     new _ContainsAllInOrder(expected);
 
diff --git a/pubspec.yaml b/pubspec.yaml
index 015c64f..c898554 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: matcher
-version: 0.12.2-dev
+version: 0.12.2
 author: Dart Team <misc@dartlang.org>
 description: Support for specifying test expectations
 homepage: https://github.com/dart-lang/matcher
diff --git a/test/iterable_matchers_test.dart b/test/iterable_matchers_test.dart
index ffd2e60..e8a8df8 100644
--- a/test/iterable_matchers_test.dart
+++ b/test/iterable_matchers_test.dart
@@ -199,6 +199,33 @@
         "Which: has no match for a value greater than <3> at index 0");
   });
 
+  test('containsAll', () {
+    var d = [0, 1, 2];
+    shouldPass(d, containsAll([1, 2]));
+    shouldPass(d, containsAll([2, 1]));
+    shouldPass(d, containsAll([greaterThan(0), greaterThan(1)]));
+    shouldPass([2, 1], containsAll([greaterThan(0), greaterThan(1)]));
+    shouldFail(
+        d,
+        containsAll([1, 2, 3]),
+        "Expected: contains all of [1, 2, 3] "
+        "Actual: [0, 1, 2] "
+        "Which: has no match for <3> at index 2");
+    shouldFail(
+        1,
+        containsAll([1]),
+        "Expected: contains all of [1] "
+        "Actual: <1> "
+        "Which: not iterable");
+    shouldFail(
+        [-1, 2],
+        containsAll([greaterThan(0), greaterThan(1)]),
+        "Expected: contains all of [<a value greater than <0>>, "
+        "<a value greater than <1>>] "
+        "Actual: [-1, 2] "
+        "Which: has no match for a value greater than <1> at index 1");
+  });
+
   test('containsAllInOrder', () {
     var d = [0, 1, 0, 2];
     shouldPass(d, containsAllInOrder([1, 2]));