diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index f2c6deee307..2236f72db55 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1068,8 +1068,8 @@ Test cases | :meth:`assertDictContainsSubset(a, b) | all the key/value pairs | 3.1 | | ` | in `a` exist in `b` | | +---------------------------------------+--------------------------------+--------------+ - | :meth:`assertItemsEqual(a, b) | `a` and `b` have the same | 3.2 | - | ` | elements in the same number, | | + | :meth:`assertCountEqual(a, b) | `a` and `b` have the same | 3.2 | + | ` | elements in the same number, | | | | regardless of their order | | +---------------------------------------+--------------------------------+--------------+ @@ -1130,7 +1130,7 @@ Test cases .. versionadded:: 3.1 - .. method:: assertItemsEqual(actual, expected, msg=None) + .. method:: assertCountEqual(expected, actual, msg=None) Test that sequence *expected* contains the same elements as *actual*, regardless of their order. When they don't, an error message listing the @@ -1138,12 +1138,18 @@ Test cases Duplicate elements are *not* ignored when comparing *actual* and *expected*. It verifies if each element has the same count in both - sequences. It is the equivalent of ``assertEqual(sorted(expected), - sorted(actual))`` but it works with sequences of unhashable objects as - well. + sequences. Equivalent to: + ``assertEqual(Counter(iter(expected)), Counter(iter(actual)))`` + but works with sequences of unhashable objects as well. .. versionadded:: 3.2 + .. method:: assertItemsEqual(actual, expected, msg=None) + + Outdated name for :meth:`assertCountEqual`, kept for compatibility + with Python 2.7. + + .. versionadded:: 3.2 .. method:: assertSameElements(actual, expected, msg=None) @@ -1155,7 +1161,7 @@ Test cases It is the equivalent of ``assertEqual(set(expected), set(actual))`` but it works with sequences of unhashable objects as well. Because duplicates are ignored, this method has been deprecated in favour of - :meth:`assertItemsEqual`. + :meth:`assertCountEqual`. .. versionadded:: 3.1 .. deprecated:: 3.2 diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index dbf7aa2e40e..f59c0684ce3 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -6,6 +6,7 @@ import difflib import pprint import re import warnings +import collections from . import result from .util import (strclass, safe_repr, sorted_list_difference, @@ -990,15 +991,13 @@ class TestCase(object): self.fail(self._formatMessage(msg, standardMsg)) - def assertItemsEqual(self, expected_seq, actual_seq, msg=None): - """An unordered sequence / set specific comparison. It asserts that - expected_seq and actual_seq contain the same elements. It is - the equivalent of:: + def assertCountEqual(self, expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. It asserts that + expected_seq and actual_seq have the same element counts. + Equivalent to:: - self.assertEqual(sorted(expected_seq), sorted(actual_seq)) - - Raises with an error message listing which elements of expected_seq - are missing from actual_seq and vice versa if any. + self.assertEqual(Counter(iter(expected_seq)), + Counter(iter(actual_seq))) Asserts that each element has the same count in both sequences. Example: @@ -1006,15 +1005,18 @@ class TestCase(object): - [0, 0, 1] and [0, 1] compare unequal. """ try: - expected = sorted(expected_seq) - actual = sorted(actual_seq) + expected = collections.Counter(iter(expected_seq)) + actual = collections.Counter(iter(actual_seq)) except TypeError: # Unsortable items (example: set(), complex(), ...) expected = list(expected_seq) actual = list(actual_seq) missing, unexpected = unorderable_list_difference(expected, actual) else: - return self.assertSequenceEqual(expected, actual, msg=msg) + if expected == actual: + return + missing = list(expected - actual) + unexpected = list(actual - expected) errors = [] if missing: @@ -1027,6 +1029,9 @@ class TestCase(object): standardMsg = '\n'.join(errors) self.fail(self._formatMessage(msg, standardMsg)) + # Old name for assertCountEqual() + assertItemsEqual = assertCountEqual + def assertMultiLineEqual(self, first, second, msg=None): """Assert that two multi-line strings are equal.""" self.assertIsInstance(first, str, 'First argument is not a string') diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py index 91eb8bf0707..6a30db6ffe1 100644 --- a/Lib/unittest/test/test_assertions.py +++ b/Lib/unittest/test/test_assertions.py @@ -229,8 +229,8 @@ class TestLongMessage(unittest.TestCase): "^Missing: 'key'$", "^Missing: 'key' : oops$"]) - def testAssertItemsEqual(self): - self.assertMessages('assertItemsEqual', ([], [None]), + def testassertCountEqual(self): + self.assertMessages('assertCountEqual', ([], [None]), [r"\[None\]$", "^oops$", r"\[None\]$", r"\[None\] : oops$"]) diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index 07904d431ed..aa8cc370dc8 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -672,46 +672,53 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): else: self.fail('assertMultiLineEqual did not fail') - def testAssertItemsEqual(self): + def testassertCountEqual(self): a = object() - self.assertItemsEqual([1, 2, 3], [3, 2, 1]) - self.assertItemsEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) - self.assertItemsEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2)) - self.assertItemsEqual([1, "2", "a", "a"], ["a", "2", True, "a"]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertCountEqual([1, 2, 3], [3, 2, 1]) + self.assertCountEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) + self.assertCountEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2)) + self.assertCountEqual([1, "2", "a", "a"], ["a", "2", True, "a"]) + self.assertRaises(self.failureException, self.assertCountEqual, [1, 2] + [3] * 100, [1] * 100 + [2, 3]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [1, "2", "a", "a"], ["a", "2", True, 1]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [10], [10, 11]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [10, 11], [10]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [10, 11, 10], [10, 11]) # Test that sequences of unhashable objects can be tested for sameness: - self.assertItemsEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]]) + self.assertCountEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]]) # hashable types, but not orderable - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [], [divmod, 'x', 1, 5j, 2j, frozenset()]) # comparing dicts - self.assertItemsEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) + self.assertCountEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) # comparing heterogenous non-hashable sequences - self.assertItemsEqual([1, 'x', divmod, []], [divmod, [], 'x', 1]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertCountEqual([1, 'x', divmod, []], [divmod, [], 'x', 1]) + self.assertRaises(self.failureException, self.assertCountEqual, [], [divmod, [], 'x', 1, 5j, 2j, set()]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [[1]], [[2]]) # Same elements, but not same sequence length - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [1, 1, 2], [2, 1]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [1, 1, "2", "a", "a"], ["2", "2", True, "a"]) - self.assertRaises(self.failureException, self.assertItemsEqual, + self.assertRaises(self.failureException, self.assertCountEqual, [1, {'b': 2}, None, True], [{'b': 2}, True, None]) + # Same elements which don't reliably compare, in + # different order, see issue 10242 + a = [{2,4}, {1,2}] + b = a[::-1] + self.assertCountEqual(a, b) + + def testAssertSetEqual(self): set1 = set() set2 = set() diff --git a/Misc/NEWS b/Misc/NEWS index f90deb989c8..07f0570e9e8 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -41,6 +41,9 @@ Core and Builtins Library ------- +- Issue #10242: Fixed implementation of unittest.ItemsEqual and gave it + a new more informative name, unittest.CountEqual. + - Issue #2986: difflib.SequenceMatcher gets a new parameter, autojunk, which can be set to False to turn off the previously undocumented 'popularity' heuristic. Patch by Terry Reedy and Eli Bendersky.