diff --git a/Lib/test/test_unittest/test_util.py b/Lib/test/test_unittest/test_util.py index d590a333930278..09ce09b91b7ac2 100644 --- a/Lib/test/test_unittest/test_util.py +++ b/Lib/test/test_unittest/test_util.py @@ -26,6 +26,39 @@ def test_sorted_list_difference(self): self.assertEqual(sorted_list_difference([2], [1, 1]), ([2], [1])) self.assertEqual(sorted_list_difference([1, 2], [1, 1]), ([2], [])) + def test_sorted_list_difference_tail_deduplication(self): + # Tail deduplication when one list is exhausted before the other. + # These exercise the except-IndexError path in sorted_list_difference. + self.assertEqual(sorted_list_difference([], [0, 0]), ([], [0])) + self.assertEqual(sorted_list_difference([0, 0], []), ([0], [])) + self.assertEqual(sorted_list_difference([], [1, 1, 2, 2]), ([], [1, 2])) + self.assertEqual(sorted_list_difference([1, 1, 2, 2], []), ([1, 2], [])) + # One list exhausts mid-way, leaving duplicated tail in the other. + self.assertEqual(sorted_list_difference([1], [1, 2, 2, 3, 3]), ([], [2, 3])) + self.assertEqual(sorted_list_difference([1, 2, 2, 3, 3], [1]), ([2, 3], [])) + + def test_sorted_list_difference_strings(self): + self.assertEqual( + sorted_list_difference(['a', 'b'], ['b', 'c']), + (['a'], ['c'])) + self.assertEqual( + sorted_list_difference([], ['a', 'a', 'b']), + ([], ['a', 'b'])) + self.assertEqual( + sorted_list_difference(['a', 'a', 'b'], []), + (['a', 'b'], [])) + + def test_sorted_list_difference_unhashable(self): + self.assertEqual( + sorted_list_difference([[1], [2]], [[2], [3]]), + ([[1]], [[3]])) + self.assertEqual( + sorted_list_difference([], [[0], [0]]), + ([], [[0]])) + self.assertEqual( + sorted_list_difference([[0], [0]], []), + ([[0]], [])) + def test_unorderable_list_difference(self): self.assertEqual(unorderable_list_difference([], []), ([], [])) self.assertEqual(unorderable_list_difference([1, 2], []), ([2, 1], [])) diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py index c7e6b941978cd5..1c6dafcc0db7e4 100644 --- a/Lib/unittest/util.py +++ b/Lib/unittest/util.py @@ -63,6 +63,17 @@ def safe_repr(obj, short=False): def strclass(cls): return "%s.%s" % (cls.__module__, cls.__qualname__) +def _dedupe_sorted(lst): + """Remove consecutive duplicate elements from a sorted list. + + Only requires that elements support equality comparison, + not hashing.""" + result = [] + for item in lst: + if not result or result[-1] != item: + result.append(item) + return result + def sorted_list_difference(expected, actual): """Finds elements in only one or the other of two, sorted input lists. @@ -98,8 +109,8 @@ def sorted_list_difference(expected, actual): while actual[j] == a: j += 1 except IndexError: - missing.extend(expected[i:]) - unexpected.extend(actual[j:]) + missing.extend(_dedupe_sorted(expected[i:])) + unexpected.extend(_dedupe_sorted(actual[j:])) break return missing, unexpected diff --git a/Misc/NEWS.d/next/Library/2026-03-05-14-13-10.gh-issue-145546.3tnlxx.rst b/Misc/NEWS.d/next/Library/2026-03-05-14-13-10.gh-issue-145546.3tnlxx.rst new file mode 100644 index 00000000000000..e9401bb08c6774 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-05-14-13-10.gh-issue-145546.3tnlxx.rst @@ -0,0 +1,2 @@ +Fix ``unittest.util.sorted_list_difference()`` to deduplicate remaining +elements when one input list is exhausted before the other.