[Python-checkins] r79132 - in python/trunk: Doc/library/unittest.rst Doc/whatsnew/2.7.rst Lib/test/test_cgi.py Lib/test/test_unittest.py Lib/unittest/case.py Lib/unittest/util.py

michael.foord python-checkins at python.org
Sat Mar 20 04:00:34 CET 2010


Author: michael.foord
Date: Sat Mar 20 04:00:34 2010
New Revision: 79132

Log:
Issue 7832: renaming unittest.TestCase.assertSameElements to assertItemsEqual and changing behaviour

Modified:
   python/trunk/Doc/library/unittest.rst
   python/trunk/Doc/whatsnew/2.7.rst
   python/trunk/Lib/test/test_cgi.py
   python/trunk/Lib/test/test_unittest.py
   python/trunk/Lib/unittest/case.py
   python/trunk/Lib/unittest/util.py

Modified: python/trunk/Doc/library/unittest.rst
==============================================================================
--- python/trunk/Doc/library/unittest.rst	(original)
+++ python/trunk/Doc/library/unittest.rst	Sat Mar 20 04:00:34 2010
@@ -786,7 +786,7 @@
       will be included in the error message. This method is used by default
       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
 
@@ -807,22 +807,24 @@
       Tests that *first* is or is not in *second* with an explanatory error
       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
 
 
-   .. method:: assertSameElements(actual, expected, msg=None)
+   .. method:: assertItemsEqual(actual, expected, 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 differences between the sequences will be generated.
+      regardless of their order. When they don't, an error message listing the
+      differences between the sequences will be generated.
 
-      Duplicate elements are ignored when comparing *actual* and *expected*.
-      It is the equivalent of ``assertEqual(set(expected), set(actual))``
-      but it works with sequences of unhashable objects as well.
+      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.
 
-      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
 
@@ -836,7 +838,7 @@
       Fails if either of *set1* or *set2* does not have a :meth:`set.difference`
       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
 
@@ -848,7 +850,7 @@
       method will be used by default to compare dictionaries in
       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
 
@@ -859,7 +861,7 @@
       superset of those in *expected*.  If not, an error message listing
       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
 
@@ -873,7 +875,7 @@
       These methods are used by default when comparing lists or tuples 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
 
@@ -885,7 +887,7 @@
       be raised.  If the sequences are different an error message is
       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
       :meth:`assertTupleEqual`.

Modified: python/trunk/Doc/whatsnew/2.7.rst
==============================================================================
--- python/trunk/Doc/whatsnew/2.7.rst	(original)
+++ python/trunk/Doc/whatsnew/2.7.rst	Sat Mar 20 04:00:34 2010
@@ -1086,7 +1086,7 @@
 * :meth:`assertIn` and :meth:`assertNotIn` tests whether
   *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.
 
 * :meth:`assertSetEqual` compares whether two sets are equal, and

Modified: python/trunk/Lib/test/test_cgi.py
==============================================================================
--- python/trunk/Lib/test/test_cgi.py	(original)
+++ python/trunk/Lib/test/test_cgi.py	Sat Mar 20 04:00:34 2010
@@ -135,18 +135,18 @@
             if isinstance(expect, dict):
                 # test dict interface
                 self.assertEqual(len(expect), len(fcd))
-                self.assertSameElements(expect.keys(), fcd.keys())
-                self.assertSameElements(expect.values(), fcd.values())
-                self.assertSameElements(expect.items(), fcd.items())
+                self.assertItemsEqual(expect.keys(), fcd.keys())
+                self.assertItemsEqual(expect.values(), fcd.values())
+                self.assertItemsEqual(expect.items(), fcd.items())
                 self.assertEqual(fcd.get("nonexistent field", "default"), "default")
                 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")
                 # test individual fields
                 for key in expect.keys():
                     expect_val = expect[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.assertTrue(fs.has_key(key))
                     if len(expect_val) > 1:
@@ -162,11 +162,11 @@
                         self.assertTrue(single_value)
                         self.assertEqual(val, 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:
-                        self.assertSameElements(sd.values(),
+                        self.assertItemsEqual(sd.values(),
                                                 first_elts(expect.values()))
-                        self.assertSameElements(sd.items(),
+                        self.assertItemsEqual(sd.items(),
                                                 first_second_elts(expect.items()))
 
     def test_weird_formcontentdict(self):
@@ -178,7 +178,7 @@
             self.assertEqual(d[k], v)
         for k, v in d.items():
             self.assertEqual(expect[k], v)
-        self.assertSameElements(expect.values(), d.values())
+        self.assertItemsEqual(expect.values(), d.values())
 
     def test_log(self):
         cgi.log("Testing")

Modified: python/trunk/Lib/test/test_unittest.py
==============================================================================
--- python/trunk/Lib/test/test_unittest.py	(original)
+++ python/trunk/Lib/test/test_unittest.py	Sat Mar 20 04:00:34 2010
@@ -2575,9 +2575,9 @@
         class SadSnake(object):
             """Dummy class for test_addTypeEqualityFunc."""
         s1, s2 = SadSnake(), SadSnake()
-        self.assertFalse(s1 == s2)
+        self.assertNotEqual(s1, s2)
         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.assertEqual(s1, s2)
         # No this doesn't clean up and remove the SadSnake equality func
@@ -2745,21 +2745,51 @@
         self.assertRaises(self.failureException, self.assertDictEqual, [], d)
         self.assertRaises(self.failureException, self.assertDictEqual, 1, 1)
 
-        self.assertSameElements([1, 2, 3], [3, 2, 1])
-        self.assertSameElements([1, 2] + [3] * 100, [1] * 100 + [2, 3])
-        self.assertSameElements(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo'])
-        self.assertRaises(self.failureException, self.assertSameElements,
+    def testAssertItemsEqual(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,
+                          [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])
-        self.assertRaises(self.failureException, self.assertSameElements,
+        self.assertRaises(self.failureException, self.assertItemsEqual,
                           [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:
-        self.assertSameElements([[1, 2], [3, 4]], [[3, 4], [1, 2]])
-
-        self.assertSameElements([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}])
-        self.assertRaises(self.failureException, self.assertSameElements,
+        self.assertItemsEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]])
+        with test_support.check_warnings(quiet=True) as w:
+            # hashable types, but not orderable
+            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]])
 
+        # 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):
         set1 = set()
         set2 = set()
@@ -3009,13 +3039,14 @@
 
         Do not use these methods.  They will go away in 3.3.
         """
-        self.failIfEqual(3, 5)
-        self.failUnlessEqual(3, 3)
-        self.failUnlessAlmostEqual(2.0, 2.0)
-        self.failIfAlmostEqual(3.0, 5.0)
-        self.failUnless(True)
-        self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam')
-        self.failIf(False)
+        with test_support.check_warnings():
+            self.failIfEqual(3, 5)
+            self.failUnlessEqual(3, 3)
+            self.failUnlessAlmostEqual(2.0, 2.0)
+            self.failIfAlmostEqual(3.0, 5.0)
+            self.failUnless(True)
+            self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam')
+            self.failIf(False)
 
     def testDeepcopy(self):
         # Issue: 5660
@@ -3355,8 +3386,8 @@
                              "^Missing: 'key'$",
                              "^Missing: 'key' : oops$"])
 
-    def testAssertSameElements(self):
-        self.assertMessages('assertSameElements', ([], [None]),
+    def testAssertItemsEqual(self):
+        self.assertMessages('assertItemsEqual', ([], [None]),
                             [r"\[None\]$", "^oops$",
                              r"\[None\]$",
                              r"\[None\] : oops$"])

Modified: python/trunk/Lib/unittest/case.py
==============================================================================
--- python/trunk/Lib/unittest/case.py	(original)
+++ python/trunk/Lib/unittest/case.py	Sat Mar 20 04:00:34 2010
@@ -8,8 +8,9 @@
 import warnings
 
 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):
     """
@@ -686,10 +687,9 @@
             msg: Optional message to use on failure instead of a list of
                     differences.
 
-        For more general containership equality, assertSameElements will work
-        with things other than sets.    This uses ducktyping to support
-        different types of sets, and is optimized for sets specifically
-        (parameters must support a difference method).
+        assertSetEqual uses ducktyping to support different types of sets, and
+        is optimized for sets specifically (parameters must support a
+        difference method).
         """
         try:
             difference1 = set1.difference(set2)
@@ -784,42 +784,48 @@
 
         self.fail(self._formatMessage(msg, standardMsg))
 
-    def assertSameElements(self, expected_seq, actual_seq, msg=None):
-        """An unordered sequence specific comparison.
+    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::
+
+            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.
 
-        Duplicate elements are ignored when comparing *expected_seq* and
-        *actual_seq*. It is the equivalent of ``assertEqual(set(expected),
-        set(actual))`` but it works with sequences of unhashable objects as
-        well.
+        Asserts that each element has the same count in both sequences.
+        Example:
+            - [0, 1, 1] and [1, 0, 1] compare equal.
+            - [0, 0, 1] and [0, 1] compare unequal.
         """
         with warnings.catch_warnings():
             if sys.py3kwarning:
                 # Silence Py3k warning raised during the sorting
                 for _msg in ["dict inequality comparisons",
-                            "builtin_function_or_method order comparisons",
-                            "comparing unequal types"]:
+                             "builtin_function_or_method order comparisons",
+                             "comparing unequal types"]:
                     warnings.filterwarnings("ignore", _msg, DeprecationWarning)
             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)
                 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 = []
         if missing:
             errors.append('Expected, but missing:\n    %s' %
-                          safe_repr(missing))
+                           safe_repr(missing))
         if unexpected:
             errors.append('Unexpected, but present:\n    %s' %
-                          safe_repr(unexpected))
+                           safe_repr(unexpected))
         if errors:
             standardMsg = '\n'.join(errors)
             self.fail(self._formatMessage(msg, standardMsg))

Modified: python/trunk/Lib/unittest/util.py
==============================================================================
--- python/trunk/Lib/unittest/util.py	(original)
+++ python/trunk/Lib/unittest/util.py	Sat Mar 20 04:00:34 2010
@@ -48,3 +48,40 @@
             unexpected.extend(actual[j:])
             break
     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


More information about the Python-checkins mailing list