Index: Lib/unittest/case.py =================================================================== --- Lib/unittest/case.py (revision 77910) +++ Lib/unittest/case.py (working copy) @@ -39,6 +39,33 @@ def _id(obj): return obj +if sys.py3kwarning: + def _deprecated_comparison(func): + def wrapper(*args, **kwargs): + with warnings.catch_warnings(): + # Silence Py3k warning raised during the sorting + for msg in ("comparing unequal types", + "dict inequality comparisons", + "builtin_function_or_method order comparisons"): + warnings.filterwarnings("ignore", msg, DeprecationWarning) + return func(*args, **kwargs) + return wrapper +else: + _deprecated_comparison = _id + +@_deprecated_comparison +def _sorted(iterable, ignore_duplicate=False): + if not ignore_duplicate: + return sorted(iterable) + res = [] + last = object() + for el in sorted(iterable): + if el == last: + continue + res.append(el) + last = el + return res + def skip(reason): """ Unconditionally skip a test. @@ -729,32 +756,34 @@ 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. + Example: + - [1, 0] and [0, 0, 1] and [True, False] compare equal. """ try: expected = set(expected_seq) actual = set(actual_seq) - missing = list(expected.difference(actual)) - unexpected = list(actual.difference(expected)) - missing.sort() - unexpected.sort() + 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 = list(expected_seq) - actual = list(actual_seq) - with warnings.catch_warnings(): - if sys.py3kwarning: - # Silence Py3k warning - warnings.filterwarnings("ignore", - "dict inequality comparisons " - "not supported", DeprecationWarning) - expected.sort() - actual.sort() - missing, unexpected = util.sorted_list_difference(expected, actual) + try: + expected = _sorted(expected_seq, ignore_duplicate=True) + actual = _sorted(actual_seq, ignore_duplicate=True) + except TypeError: + # Unsortable items (example: set(), complex(), ...) + expected = list(expected_seq) + actual = list(actual_seq) + missing, unexpected = util.unorderable_list_difference( + expected, actual, ignore_duplicate=True) + else: + self.assertSequenceEqual(expected, actual, msg=msg) + return + errors = [] if missing: errors.append('Expected, but missing:\n %r' % missing) Index: Lib/unittest/util.py =================================================================== --- Lib/unittest/util.py (revision 77910) +++ Lib/unittest/util.py (working copy) @@ -42,3 +42,39 @@ 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 77910) +++ Lib/test/test_unittest.py (working copy) @@ -2646,21 +2646,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() 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()