Kaydet (Commit) 98e7b764 authored tarafından Michael Foord's avatar Michael Foord

Issue 7832: renaming unittest.TestCase.assertSameElements to assertItemsEqual…

Issue 7832: renaming unittest.TestCase.assertSameElements to assertItemsEqual and changing behaviour
üst 2e6d2622
...@@ -786,7 +786,7 @@ Test cases ...@@ -786,7 +786,7 @@ Test cases
will be included in the error message. This method is used by default will be included in the error message. This method is used by default
when comparing Unicode strings with :meth:`assertEqual`. when comparing Unicode strings with :meth:`assertEqual`.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
...@@ -807,22 +807,24 @@ Test cases ...@@ -807,22 +807,24 @@ Test cases
Tests that *first* is or is not in *second* with an explanatory error Tests that *first* is or is not in *second* with an explanatory error
message as appropriate. message as appropriate.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
.. method:: assertSameElements(actual, expected, msg=None) .. method:: assertItemsEqual(actual, expected, 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 regardless of their order. When they don't, an error message listing the
the differences between the sequences will be generated. differences between the sequences will be generated.
Duplicate elements are ignored when comparing *actual* and *expected*. Duplicate elements are *not* ignored when comparing *actual* and
It is the equivalent of ``assertEqual(set(expected), set(actual))`` *expected*. It verifies if each element has the same count in both
but it works with sequences of unhashable objects as well. sequences. It is the equivalent of ``assertEqual(sorted(expected),
sorted(actual))`` but it works with sequences of unhashable objects as
well.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
...@@ -836,7 +838,7 @@ Test cases ...@@ -836,7 +838,7 @@ Test cases
Fails if either of *set1* or *set2* does not have a :meth:`set.difference` Fails if either of *set1* or *set2* does not have a :meth:`set.difference`
method. method.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
...@@ -848,7 +850,7 @@ Test cases ...@@ -848,7 +850,7 @@ Test cases
method will be used by default to compare dictionaries in method will be used by default to compare dictionaries in
calls to :meth:`assertEqual`. calls to :meth:`assertEqual`.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
...@@ -859,7 +861,7 @@ Test cases ...@@ -859,7 +861,7 @@ Test cases
superset of those in *expected*. If not, an error message listing superset of those in *expected*. If not, an error message listing
the missing keys and mismatched values is generated. the missing keys and mismatched values is generated.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
...@@ -873,7 +875,7 @@ Test cases ...@@ -873,7 +875,7 @@ Test cases
These methods are used by default when comparing lists or tuples with These methods are used by default when comparing lists or tuples with
:meth:`assertEqual`. :meth:`assertEqual`.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7 .. versionadded:: 2.7
...@@ -885,7 +887,7 @@ Test cases ...@@ -885,7 +887,7 @@ Test cases
be raised. If the sequences are different an error message is be raised. If the sequences are different an error message is
constructed that shows the difference between the two. constructed that shows the difference between the two.
If specified *msg* will be used as the error message on failure. If specified, *msg* will be used as the error message on failure.
This method is used to implement :meth:`assertListEqual` and This method is used to implement :meth:`assertListEqual` and
:meth:`assertTupleEqual`. :meth:`assertTupleEqual`.
......
...@@ -1086,7 +1086,7 @@ GvR worked on merging them into Python's version of :mod:`unittest`. ...@@ -1086,7 +1086,7 @@ GvR worked on merging them into Python's version of :mod:`unittest`.
* :meth:`assertIn` and :meth:`assertNotIn` tests whether * :meth:`assertIn` and :meth:`assertNotIn` tests whether
*first* is or is not in *second*. *first* is or is not in *second*.
* :meth:`assertSameElements` tests whether two provided sequences * :meth:`assertItemsEqual` tests whether two provided sequences
contain the same elements. contain the same elements.
* :meth:`assertSetEqual` compares whether two sets are equal, and * :meth:`assertSetEqual` compares whether two sets are equal, and
......
...@@ -135,18 +135,18 @@ class CgiTests(unittest.TestCase): ...@@ -135,18 +135,18 @@ class CgiTests(unittest.TestCase):
if isinstance(expect, dict): if isinstance(expect, dict):
# test dict interface # test dict interface
self.assertEqual(len(expect), len(fcd)) self.assertEqual(len(expect), len(fcd))
self.assertSameElements(expect.keys(), fcd.keys()) self.assertItemsEqual(expect.keys(), fcd.keys())
self.assertSameElements(expect.values(), fcd.values()) self.assertItemsEqual(expect.values(), fcd.values())
self.assertSameElements(expect.items(), fcd.items()) self.assertItemsEqual(expect.items(), fcd.items())
self.assertEqual(fcd.get("nonexistent field", "default"), "default") self.assertEqual(fcd.get("nonexistent field", "default"), "default")
self.assertEqual(len(sd), len(fs)) self.assertEqual(len(sd), len(fs))
self.assertSameElements(sd.keys(), fs.keys()) self.assertItemsEqual(sd.keys(), fs.keys())
self.assertEqual(fs.getvalue("nonexistent field", "default"), "default") self.assertEqual(fs.getvalue("nonexistent field", "default"), "default")
# test individual fields # test individual fields
for key in expect.keys(): for key in expect.keys():
expect_val = expect[key] expect_val = expect[key]
self.assertTrue(fcd.has_key(key)) self.assertTrue(fcd.has_key(key))
self.assertSameElements(fcd[key], expect[key]) self.assertItemsEqual(fcd[key], expect[key])
self.assertEqual(fcd.get(key, "default"), fcd[key]) self.assertEqual(fcd.get(key, "default"), fcd[key])
self.assertTrue(fs.has_key(key)) self.assertTrue(fs.has_key(key))
if len(expect_val) > 1: if len(expect_val) > 1:
...@@ -162,11 +162,11 @@ class CgiTests(unittest.TestCase): ...@@ -162,11 +162,11 @@ class CgiTests(unittest.TestCase):
self.assertTrue(single_value) self.assertTrue(single_value)
self.assertEqual(val, expect_val[0]) self.assertEqual(val, expect_val[0])
self.assertEqual(fs.getvalue(key), expect_val[0]) self.assertEqual(fs.getvalue(key), expect_val[0])
self.assertSameElements(sd.getlist(key), expect_val) self.assertItemsEqual(sd.getlist(key), expect_val)
if single_value: if single_value:
self.assertSameElements(sd.values(), self.assertItemsEqual(sd.values(),
first_elts(expect.values())) first_elts(expect.values()))
self.assertSameElements(sd.items(), self.assertItemsEqual(sd.items(),
first_second_elts(expect.items())) first_second_elts(expect.items()))
def test_weird_formcontentdict(self): def test_weird_formcontentdict(self):
...@@ -178,7 +178,7 @@ class CgiTests(unittest.TestCase): ...@@ -178,7 +178,7 @@ class CgiTests(unittest.TestCase):
self.assertEqual(d[k], v) self.assertEqual(d[k], v)
for k, v in d.items(): for k, v in d.items():
self.assertEqual(expect[k], v) self.assertEqual(expect[k], v)
self.assertSameElements(expect.values(), d.values()) self.assertItemsEqual(expect.values(), d.values())
def test_log(self): def test_log(self):
cgi.log("Testing") cgi.log("Testing")
......
...@@ -2575,9 +2575,9 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): ...@@ -2575,9 +2575,9 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
class SadSnake(object): class SadSnake(object):
"""Dummy class for test_addTypeEqualityFunc.""" """Dummy class for test_addTypeEqualityFunc."""
s1, s2 = SadSnake(), SadSnake() s1, s2 = SadSnake(), SadSnake()
self.assertFalse(s1 == s2) self.assertNotEqual(s1, s2)
def AllSnakesCreatedEqual(a, b, msg=None): def AllSnakesCreatedEqual(a, b, msg=None):
return type(a) == type(b) == SadSnake return type(a) is type(b) is SadSnake
self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual) self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual)
self.assertEqual(s1, s2) self.assertEqual(s1, s2)
# No this doesn't clean up and remove the SadSnake equality func # No this doesn't clean up and remove the SadSnake equality func
...@@ -2745,21 +2745,51 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): ...@@ -2745,21 +2745,51 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
self.assertRaises(self.failureException, self.assertDictEqual, [], d) self.assertRaises(self.failureException, self.assertDictEqual, [], d)
self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) self.assertRaises(self.failureException, self.assertDictEqual, 1, 1)
self.assertSameElements([1, 2, 3], [3, 2, 1]) def testAssertItemsEqual(self):
self.assertSameElements([1, 2] + [3] * 100, [1] * 100 + [2, 3]) a = object()
self.assertSameElements(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) self.assertItemsEqual([1, 2, 3], [3, 2, 1])
self.assertRaises(self.failureException, self.assertSameElements, 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,
[1, 2] + [3] * 100, [1] * 100 + [2, 3])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, "2", "a", "a"], ["a", "2", True, 1])
self.assertRaises(self.failureException, self.assertItemsEqual,
[10], [10, 11]) [10], [10, 11])
self.assertRaises(self.failureException, self.assertSameElements, self.assertRaises(self.failureException, self.assertItemsEqual,
[10, 11], [10]) [10, 11], [10])
self.assertRaises(self.failureException, self.assertItemsEqual,
[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.assertSameElements([[1, 2], [3, 4]], [[3, 4], [1, 2]]) self.assertItemsEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]])
with test_support.check_warnings(quiet=True) as w:
self.assertSameElements([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) # hashable types, but not orderable
self.assertRaises(self.failureException, self.assertSameElements, self.assertRaises(self.failureException, self.assertItemsEqual,
[], [divmod, 'x', 1, 5j, 2j, frozenset()])
# comparing dicts raises a py3k warning
self.assertItemsEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}])
# comparing heterogenous non-hashable sequences raises a py3k warning
self.assertItemsEqual([1, 'x', divmod, []], [divmod, [], 'x', 1])
self.assertRaises(self.failureException, self.assertItemsEqual,
[], [divmod, [], 'x', 1, 5j, 2j, set()])
# fail the test if warnings are not silenced
if w.warnings:
self.fail('assertItemsEqual raised a warning: ' +
str(w.warnings[0]))
self.assertRaises(self.failureException, self.assertItemsEqual,
[[1]], [[2]]) [[1]], [[2]])
# Same elements, but not same sequence length
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, 1, 2], [2, 1])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, 1, "2", "a", "a"], ["2", "2", True, "a"])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, {'b': 2}, None, True], [{'b': 2}, True, None])
def testAssertSetEqual(self): def testAssertSetEqual(self):
set1 = set() set1 = set()
set2 = set() set2 = set()
...@@ -3009,13 +3039,14 @@ test case ...@@ -3009,13 +3039,14 @@ test case
Do not use these methods. They will go away in 3.3. Do not use these methods. They will go away in 3.3.
""" """
self.failIfEqual(3, 5) with test_support.check_warnings():
self.failUnlessEqual(3, 3) self.failIfEqual(3, 5)
self.failUnlessAlmostEqual(2.0, 2.0) self.failUnlessEqual(3, 3)
self.failIfAlmostEqual(3.0, 5.0) self.failUnlessAlmostEqual(2.0, 2.0)
self.failUnless(True) self.failIfAlmostEqual(3.0, 5.0)
self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam') self.failUnless(True)
self.failIf(False) self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam')
self.failIf(False)
def testDeepcopy(self): def testDeepcopy(self):
# Issue: 5660 # Issue: 5660
...@@ -3355,8 +3386,8 @@ class TestLongMessage(TestCase): ...@@ -3355,8 +3386,8 @@ class TestLongMessage(TestCase):
"^Missing: 'key'$", "^Missing: 'key'$",
"^Missing: 'key' : oops$"]) "^Missing: 'key' : oops$"])
def testAssertSameElements(self): def testAssertItemsEqual(self):
self.assertMessages('assertSameElements', ([], [None]), self.assertMessages('assertItemsEqual', ([], [None]),
[r"\[None\]$", "^oops$", [r"\[None\]$", "^oops$",
r"\[None\]$", r"\[None\]$",
r"\[None\] : oops$"]) r"\[None\] : oops$"])
......
...@@ -8,8 +8,9 @@ import re ...@@ -8,8 +8,9 @@ import re
import warnings import warnings
from . import result from . import result
from .util import strclass, safe_repr, sorted_list_difference from .util import (
strclass, safe_repr, sorted_list_difference, unorderable_list_difference
)
class SkipTest(Exception): class SkipTest(Exception):
""" """
...@@ -686,10 +687,9 @@ class TestCase(object): ...@@ -686,10 +687,9 @@ class TestCase(object):
msg: Optional message to use on failure instead of a list of msg: Optional message to use on failure instead of a list of
differences. differences.
For more general containership equality, assertSameElements will work assertSetEqual uses ducktyping to support different types of sets, and
with things other than sets. This uses ducktyping to support is optimized for sets specifically (parameters must support a
different types of sets, and is optimized for sets specifically difference method).
(parameters must support a difference method).
""" """
try: try:
difference1 = set1.difference(set2) difference1 = set1.difference(set2)
...@@ -784,42 +784,48 @@ class TestCase(object): ...@@ -784,42 +784,48 @@ class TestCase(object):
self.fail(self._formatMessage(msg, standardMsg)) self.fail(self._formatMessage(msg, standardMsg))
def assertSameElements(self, expected_seq, actual_seq, msg=None): def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
"""An unordered sequence specific comparison. """An unordered sequence / set specific comparison. It asserts that
expected_seq and actual_seq contain the same elements. It is
the equivalent of::
self.assertEqual(sorted(expected_seq), sorted(actual_seq))
Raises with an error message listing which elements of expected_seq Raises with an error message listing which elements of expected_seq
are missing from actual_seq and vice versa if any. are missing from actual_seq and vice versa if any.
Duplicate elements are ignored when comparing *expected_seq* and Asserts that each element has the same count in both sequences.
*actual_seq*. It is the equivalent of ``assertEqual(set(expected), Example:
set(actual))`` but it works with sequences of unhashable objects as - [0, 1, 1] and [1, 0, 1] compare equal.
well. - [0, 0, 1] and [0, 1] compare unequal.
""" """
with warnings.catch_warnings(): with warnings.catch_warnings():
if sys.py3kwarning: if sys.py3kwarning:
# Silence Py3k warning raised during the sorting # Silence Py3k warning raised during the sorting
for _msg in ["dict inequality comparisons", for _msg in ["dict inequality comparisons",
"builtin_function_or_method order comparisons", "builtin_function_or_method order comparisons",
"comparing unequal types"]: "comparing unequal types"]:
warnings.filterwarnings("ignore", _msg, DeprecationWarning) warnings.filterwarnings("ignore", _msg, DeprecationWarning)
try: try:
expected = set(expected_seq)
actual = set(actual_seq)
missing = sorted(expected.difference(actual))
unexpected = sorted(actual.difference(expected))
except TypeError:
# Fall back to slower list-compare if any of the objects are
# not hashable.
expected = sorted(expected_seq) expected = sorted(expected_seq)
actual = sorted(actual_seq) actual = sorted(actual_seq)
missing, unexpected = sorted_list_difference(expected, actual) except TypeError:
# Unsortable items (example: set(), complex(), ...)
expected = list(expected_seq)
actual = list(actual_seq)
missing, unexpected = unorderable_list_difference(
expected, actual, ignore_duplicate=False
)
else:
return self.assertSequenceEqual(expected, actual, msg=msg)
errors = [] errors = []
if missing: if missing:
errors.append('Expected, but missing:\n %s' % errors.append('Expected, but missing:\n %s' %
safe_repr(missing)) safe_repr(missing))
if unexpected: if unexpected:
errors.append('Unexpected, but present:\n %s' % errors.append('Unexpected, but present:\n %s' %
safe_repr(unexpected)) safe_repr(unexpected))
if errors: if errors:
standardMsg = '\n'.join(errors) standardMsg = '\n'.join(errors)
self.fail(self._formatMessage(msg, standardMsg)) self.fail(self._formatMessage(msg, standardMsg))
......
...@@ -48,3 +48,40 @@ def sorted_list_difference(expected, actual): ...@@ -48,3 +48,40 @@ def sorted_list_difference(expected, actual):
unexpected.extend(actual[j:]) unexpected.extend(actual[j:])
break break
return missing, unexpected return missing, unexpected
def unorderable_list_difference(expected, actual, ignore_duplicate=False):
"""Same behavior as sorted_list_difference but
for lists of unorderable items (like dicts).
As it does a linear search per item (remove) it
has O(n*n) performance.
"""
missing = []
unexpected = []
while expected:
item = expected.pop()
try:
actual.remove(item)
except ValueError:
missing.append(item)
if ignore_duplicate:
for lst in expected, actual:
try:
while True:
lst.remove(item)
except ValueError:
pass
if ignore_duplicate:
while actual:
item = actual.pop()
unexpected.append(item)
try:
while True:
actual.remove(item)
except ValueError:
pass
return missing, unexpected
# anything left in actual is unexpected
return missing, actual
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