Commit 6e165b30 authored by Raymond Hettinger's avatar Raymond Hettinger

Issue 10242: unittest.assertItemsEqual makes too many assumptions.

parent db213a29
...@@ -1068,8 +1068,8 @@ Test cases ...@@ -1068,8 +1068,8 @@ Test cases
| :meth:`assertDictContainsSubset(a, b) | all the key/value pairs | 3.1 | | :meth:`assertDictContainsSubset(a, b) | all the key/value pairs | 3.1 |
| <TestCase.assertDictContainsSubset>` | in `a` exist in `b` | | | <TestCase.assertDictContainsSubset>` | in `a` exist in `b` | |
+---------------------------------------+--------------------------------+--------------+ +---------------------------------------+--------------------------------+--------------+
| :meth:`assertItemsEqual(a, b) | `a` and `b` have the same | 3.2 | | :meth:`assertCountEqual(a, b) | `a` and `b` have the same | 3.2 |
| <TestCase.assertItemsEqual>` | elements in the same number, | | | <TestCase.assertCountEqual>` | elements in the same number, | |
| | regardless of their order | | | | regardless of their order | |
+---------------------------------------+--------------------------------+--------------+ +---------------------------------------+--------------------------------+--------------+
...@@ -1130,7 +1130,7 @@ Test cases ...@@ -1130,7 +1130,7 @@ Test cases
.. versionadded:: 3.1 .. 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*, Test that sequence *expected* contains the same elements as *actual*,
regardless of their order. When they don't, an error message listing the regardless of their order. When they don't, an error message listing the
...@@ -1138,12 +1138,18 @@ Test cases ...@@ -1138,12 +1138,18 @@ Test cases
Duplicate elements are *not* ignored when comparing *actual* and Duplicate elements are *not* ignored when comparing *actual* and
*expected*. It verifies if each element has the same count in both *expected*. It verifies if each element has the same count in both
sequences. It is the equivalent of ``assertEqual(sorted(expected), sequences. Equivalent to:
sorted(actual))`` but it works with sequences of unhashable objects as ``assertEqual(Counter(iter(expected)), Counter(iter(actual)))``
well. but works with sequences of unhashable objects as well.
.. versionadded:: 3.2 .. 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) .. method:: assertSameElements(actual, expected, msg=None)
...@@ -1155,7 +1161,7 @@ Test cases ...@@ -1155,7 +1161,7 @@ Test cases
It is the equivalent of ``assertEqual(set(expected), set(actual))`` It is the equivalent of ``assertEqual(set(expected), set(actual))``
but it works with sequences of unhashable objects as well. Because but it works with sequences of unhashable objects as well. Because
duplicates are ignored, this method has been deprecated in favour of duplicates are ignored, this method has been deprecated in favour of
:meth:`assertItemsEqual`. :meth:`assertCountEqual`.
.. versionadded:: 3.1 .. versionadded:: 3.1
.. deprecated:: 3.2 .. deprecated:: 3.2
......
...@@ -6,6 +6,7 @@ import difflib ...@@ -6,6 +6,7 @@ import difflib
import pprint import pprint
import re import re
import warnings import warnings
import collections
from . import result from . import result
from .util import (strclass, safe_repr, sorted_list_difference, from .util import (strclass, safe_repr, sorted_list_difference,
...@@ -990,15 +991,13 @@ class TestCase(object): ...@@ -990,15 +991,13 @@ class TestCase(object):
self.fail(self._formatMessage(msg, standardMsg)) self.fail(self._formatMessage(msg, standardMsg))
def assertItemsEqual(self, expected_seq, actual_seq, msg=None): def assertCountEqual(self, expected_seq, actual_seq, msg=None):
"""An unordered sequence / set specific comparison. It asserts that """An unordered sequence specific comparison. It asserts that
expected_seq and actual_seq contain the same elements. It is expected_seq and actual_seq have the same element counts.
the equivalent of:: Equivalent to::
self.assertEqual(sorted(expected_seq), sorted(actual_seq)) self.assertEqual(Counter(iter(expected_seq)),
Counter(iter(actual_seq)))
Raises with an error message listing which elements of expected_seq
are missing from actual_seq and vice versa if any.
Asserts that each element has the same count in both sequences. Asserts that each element has the same count in both sequences.
Example: Example:
...@@ -1006,15 +1005,18 @@ class TestCase(object): ...@@ -1006,15 +1005,18 @@ class TestCase(object):
- [0, 0, 1] and [0, 1] compare unequal. - [0, 0, 1] and [0, 1] compare unequal.
""" """
try: try:
expected = sorted(expected_seq) expected = collections.Counter(iter(expected_seq))
actual = sorted(actual_seq) actual = collections.Counter(iter(actual_seq))
except TypeError: except TypeError:
# Unsortable items (example: set(), complex(), ...) # Unsortable items (example: set(), complex(), ...)
expected = list(expected_seq) expected = list(expected_seq)
actual = list(actual_seq) actual = list(actual_seq)
missing, unexpected = unorderable_list_difference(expected, actual) missing, unexpected = unorderable_list_difference(expected, actual)
else: else:
return self.assertSequenceEqual(expected, actual, msg=msg) if expected == actual:
return
missing = list(expected - actual)
unexpected = list(actual - expected)
errors = [] errors = []
if missing: if missing:
...@@ -1027,6 +1029,9 @@ class TestCase(object): ...@@ -1027,6 +1029,9 @@ class TestCase(object):
standardMsg = '\n'.join(errors) standardMsg = '\n'.join(errors)
self.fail(self._formatMessage(msg, standardMsg)) self.fail(self._formatMessage(msg, standardMsg))
# Old name for assertCountEqual()
assertItemsEqual = assertCountEqual
def assertMultiLineEqual(self, first, second, msg=None): def assertMultiLineEqual(self, first, second, msg=None):
"""Assert that two multi-line strings are equal.""" """Assert that two multi-line strings are equal."""
self.assertIsInstance(first, str, 'First argument is not a string') self.assertIsInstance(first, str, 'First argument is not a string')
......
...@@ -229,8 +229,8 @@ class TestLongMessage(unittest.TestCase): ...@@ -229,8 +229,8 @@ class TestLongMessage(unittest.TestCase):
"^Missing: 'key'$", "^Missing: 'key'$",
"^Missing: 'key' : oops$"]) "^Missing: 'key' : oops$"])
def testAssertItemsEqual(self): def testassertCountEqual(self):
self.assertMessages('assertItemsEqual', ([], [None]), self.assertMessages('assertCountEqual', ([], [None]),
[r"\[None\]$", "^oops$", [r"\[None\]$", "^oops$",
r"\[None\]$", r"\[None\]$",
r"\[None\] : oops$"]) r"\[None\] : oops$"])
......
...@@ -672,46 +672,53 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): ...@@ -672,46 +672,53 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
else: else:
self.fail('assertMultiLineEqual did not fail') self.fail('assertMultiLineEqual did not fail')
def testAssertItemsEqual(self): def testassertCountEqual(self):
a = object() a = object()
self.assertItemsEqual([1, 2, 3], [3, 2, 1]) self.assertCountEqual([1, 2, 3], [3, 2, 1])
self.assertItemsEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) self.assertCountEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo'])
self.assertItemsEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2)) self.assertCountEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2))
self.assertItemsEqual([1, "2", "a", "a"], ["a", "2", True, "a"]) self.assertCountEqual([1, "2", "a", "a"], ["a", "2", True, "a"])
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[1, 2] + [3] * 100, [1] * 100 + [2, 3]) [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]) [1, "2", "a", "a"], ["a", "2", True, 1])
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[10], [10, 11]) [10], [10, 11])
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[10, 11], [10]) [10, 11], [10])
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[10, 11, 10], [10, 11]) [10, 11, 10], [10, 11])
# Test that sequences of unhashable objects can be tested for sameness: # 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 # hashable types, but not orderable
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[], [divmod, 'x', 1, 5j, 2j, frozenset()]) [], [divmod, 'x', 1, 5j, 2j, frozenset()])
# comparing dicts # 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 # comparing heterogenous non-hashable sequences
self.assertItemsEqual([1, 'x', divmod, []], [divmod, [], 'x', 1]) self.assertCountEqual([1, 'x', divmod, []], [divmod, [], 'x', 1])
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[], [divmod, [], 'x', 1, 5j, 2j, set()]) [], [divmod, [], 'x', 1, 5j, 2j, set()])
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[[1]], [[2]]) [[1]], [[2]])
# Same elements, but not same sequence length # Same elements, but not same sequence length
self.assertRaises(self.failureException, self.assertItemsEqual, self.assertRaises(self.failureException, self.assertCountEqual,
[1, 1, 2], [2, 1]) [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"]) [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]) [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): def testAssertSetEqual(self):
set1 = set() set1 = set()
set2 = set() set2 = set()
......
...@@ -41,6 +41,9 @@ Core and Builtins ...@@ -41,6 +41,9 @@ Core and Builtins
Library 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 - Issue #2986: difflib.SequenceMatcher gets a new parameter, autojunk, which
can be set to False to turn off the previously undocumented 'popularity' can be set to False to turn off the previously undocumented 'popularity'
heuristic. Patch by Terry Reedy and Eli Bendersky. heuristic. Patch by Terry Reedy and Eli Bendersky.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment