Index: Lib/unittest/case.py =================================================================== --- Lib/unittest/case.py (revision 78801) +++ Lib/unittest/case.py (working copy) @@ -8,7 +8,8 @@ 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): @@ -785,7 +786,7 @@ self.fail(self._formatMessage(msg, standardMsg)) def assertSameElements(self, expected_seq, actual_seq, msg=None): - """An unordered sequence specific comparison. + """An unordered sequence/set specific comparison. Raises with an error message listing which elements of expected_seq are missing from actual_seq and vice versa if any. @@ -810,9 +811,17 @@ 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) + try: + expected = sorted(expected_seq) + actual = sorted(actual_seq) + except TypeError: + # Unsortable items (example: set(), complex(), ...) + expected = list(expected_seq) + actual = list(actual_seq) + missing, unexpected = unorderable_list_difference( + expected, actual, ignore_duplicate=True) + else: + missing, unexpected = sorted_list_difference(expected, actual) errors = [] if missing: errors.append('Expected, but missing:\n %s' % Index: Lib/unittest/util.py =================================================================== --- Lib/unittest/util.py (revision 78801) +++ Lib/unittest/util.py (working copy) @@ -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 Index: Lib/test/test_unittest.py =================================================================== --- Lib/test/test_unittest.py (revision 78801) +++ Lib/test/test_unittest.py (working copy) @@ -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,46 @@ self.assertRaises(self.failureException, self.assertDictEqual, [], d) self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) + def testSameElements(self): + a = object() + + # Compare sets of elements 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.assertSameElements([a, a, 2, 2, 3], (a, 2, 3, a, 2)) + self.assertSameElements([1, "2", "a", "a"], ["a", "2", True, 1]) self.assertRaises(self.failureException, self.assertSameElements, [10], [10, 11]) self.assertRaises(self.failureException, self.assertSameElements, [10, 11], [10]) # Test that sequences of unhashable objects can be tested for sameness: - self.assertSameElements([[1, 2], [3, 4]], [[3, 4], [1, 2]]) + self.assertSameElements([0, [1, 2], [3, 4]], [[3, 4], False, [1, 2]]) - self.assertSameElements([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) + with test_support.check_warnings(quiet=True) as w: + # hashable types, but not orderable + self.assertRaises(self.failureException, self.assertSameElements, + [], [divmod, 'x', 1, 5j, 2j, frozenset()]) + # comparing dicts raises a py3k warning + self.assertSameElements([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) + # comparing heterogenous non-hashable sequences raises a py3k warning + self.assertSameElements([1, 'x', divmod, []], [divmod, [], 'x', 1]) + self.assertRaises(self.failureException, self.assertSameElements, + [], [divmod, [], 'x', 1, 5j, 2j, set()]) + # fail the test if warnings are not silenced + if w.warnings: + raise w.warnings[0].message + self.fail('assertSameElements raised a warning: ' + + str(w.warnings[0])) self.assertRaises(self.failureException, self.assertSameElements, [[1]], [[2]]) + # Same elements, but not same sequence length + self.assertSameElements([1, 1, 2], [2, 1]) + self.assertSameElements([1, 1, "2", "a", "a"], ["2", "2", True, "a"]) + self.assertSameElements([1, {'b': 2}, None, True], [{'b': 2}, True, None]) + def testAssertSetEqual(self): set1 = set() set2 = set() @@ -3009,13 +3034,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