From 2d4ed107c288b6f40683d4714181db92031651ac Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Wed, 19 Aug 2026 11:42:06 +0700 Subject: [PATCH 1/6] show correct extra item in "contains one more item" diffs Co-authored-by: Claude --- changelog/13652.bugfix.rst | 1 + src/_pytest/assertion/_compare_sequence.py | 14 ++++++- testing/test_assertion.py | 24 +++++++++++ testing/test_error_diffs.py | 46 +++++++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 changelog/13652.bugfix.rst diff --git a/changelog/13652.bugfix.rst b/changelog/13652.bugfix.rst new file mode 100644 index 00000000000..97647c297c5 --- /dev/null +++ b/changelog/13652.bugfix.rst @@ -0,0 +1 @@ +``contains one more item`` now reports the actual extra item in list diffs. diff --git a/src/_pytest/assertion/_compare_sequence.py b/src/_pytest/assertion/_compare_sequence.py index cada856cc85..0bc5efa774d 100644 --- a/src/_pytest/assertion/_compare_sequence.py +++ b/src/_pytest/assertion/_compare_sequence.py @@ -52,6 +52,7 @@ def _compare_eq_sequence( comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) len_left = len(left) len_right = len(right) + found_diff = False for i in range(min(len_left, len_right)): if left[i] != right[i]: if comparing_bytes: @@ -73,6 +74,7 @@ def _compare_eq_sequence( f"At index {i} diff:" f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" ) + found_diff = True break if comparing_bytes: @@ -84,11 +86,19 @@ def _compare_eq_sequence( if len_diff: if len_diff > 0: dir_with_more = "Left" - extra = saferepr(left[len_right]) + # Only a single extra item can be an insertion at the diff point: + # tails of unequal length can never be equal. + if found_diff and len_diff == 1 and left[i + 1 :] == right[i:]: + extra = saferepr(left[i]) + else: + extra = saferepr(left[len_right]) else: len_diff = 0 - len_diff dir_with_more = "Right" - extra = saferepr(right[len_left]) + if found_diff and len_diff == 1 and right[i + 1 :] == left[i:]: + extra = saferepr(right[i]) + else: + extra = saferepr(right[len_left]) if len_diff == 1: yield f"{dir_with_more} contains one more item: {highlighter(extra)}" diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e473233b208..578e73238c6 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1105,6 +1105,30 @@ def test_sequence_different_items(self) -> None: " )", ] + @pytest.mark.parametrize( + ("left", "right", "expected"), + [ + ([], [1], "Right contains one more item: 1"), + ([1], [], "Left contains one more item: 1"), + ([1, 2, 3], [0, 1, 2, 3], "Right contains one more item: 0"), + ([1, 2, 3], [1, 2, 3, 4], "Right contains one more item: 4"), + ([1, 2, 3], [1, 2, 0, 3], "Right contains one more item: 0"), + ([1, 2, 0, 3], [1, 2, 3], "Left contains one more item: 0"), + ([1, 1], [1, 0, 1], "Right contains one more item: 0"), + ( + [1, 2, 3], + [0, 1, 2, 3, 4], + "Right contains 2 more items, first extra item: 3", + ), + ], + ) + def test_sequence_extra_item_message( + self, left: list[object], right: list[object], expected: str + ) -> None: + lines = callequal(left, right, verbose=1) + assert lines is not None + assert expected in lines + def test_set(self) -> None: expl = callequal({0, 1}, {0, 2}) assert expl is not None diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index 653559c8a99..78e9bba3437 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -66,7 +66,7 @@ def test_this(): > assert result == expected E assert [1, 3] == [1, 2, 3] E At index 1 diff: 3 != 2 - E Right contains one more item: 3 + E Right contains one more item: 2 E Full diff: (-: missing in left side, +: extra in left side) E [ E 1, @@ -76,6 +76,50 @@ def test_this(): """, id="Compare lists, one item missing", ), + pytest.param( + """ + def test_this(): + result = [1, 2, 3] + expected = [1, 2, 0, 3] + assert result == expected + """, + """ + > assert result == expected + E assert [1, 2, 3] == [1, 2, 0, 3] + E At index 2 diff: 3 != 0 + E Right contains one more item: 0 + E Full diff: (-: missing in left side, +: extra in left side) + E [ + E 1, + E 2, + E - 0, + E 3, + E ] + """, + id="Compare lists, one extra item inserted mid-list", + ), + pytest.param( + """ + def test_this(): + result = [1, 2, 0, 3] + expected = [1, 2, 3] + assert result == expected + """, + """ + > assert result == expected + E assert [1, 2, 0, 3] == [1, 2, 3] + E At index 2 diff: 0 != 3 + E Left contains one more item: 0 + E Full diff: (-: missing in left side, +: extra in left side) + E [ + E 1, + E 2, + E + 0, + E 3, + E ] + """, + id="Compare lists, one extra item inserted mid-list on left", + ), pytest.param( """ def test_this(): From 30c57ffa8289275f43f63981968e9bcb026a7d9d Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Sat, 22 Aug 2026 08:10:23 +0700 Subject: [PATCH 2/6] Add parametrized case for Left-side fallback --- testing/test_assertion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 0d570b5208a..309432f9876 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1115,6 +1115,7 @@ def test_sequence_different_items(self) -> None: ([1, 2, 3], [1, 2, 0, 3], "Right contains one more item: 0"), ([1, 2, 0, 3], [1, 2, 3], "Left contains one more item: 0"), ([1, 1], [1, 0, 1], "Right contains one more item: 0"), + ([3, 4, 5], [1, 2], "Left contains one more item: 5"), ( [1, 2, 3], [0, 1, 2, 3, 4], From af7fbb5f23bfc9962159f3b19228458b9743b09c Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Sat, 22 Aug 2026 14:31:34 +0700 Subject: [PATCH 3/6] Initialize i to fix mypy unbound --- src/_pytest/assertion/_compare_sequence.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_pytest/assertion/_compare_sequence.py b/src/_pytest/assertion/_compare_sequence.py index 0bc5efa774d..a772cde770f 100644 --- a/src/_pytest/assertion/_compare_sequence.py +++ b/src/_pytest/assertion/_compare_sequence.py @@ -53,6 +53,7 @@ def _compare_eq_sequence( len_left = len(left) len_right = len(right) found_diff = False + i = -1 for i in range(min(len_left, len_right)): if left[i] != right[i]: if comparing_bytes: From 223e8db77c589afd5b9f9f6d30fefbc05c65f1bd Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Tue, 25 Aug 2026 12:04:01 +0700 Subject: [PATCH 4/6] Update changelog --- changelog/13652.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/13652.bugfix.rst b/changelog/13652.bugfix.rst index 97647c297c5..12ff991a796 100644 --- a/changelog/13652.bugfix.rst +++ b/changelog/13652.bugfix.rst @@ -1 +1 @@ -``contains one more item`` now reports the actual extra item in list diffs. +``contains one more item`` now reports the actual extra item in sequence diffs. From 5ad60d52b3cf77e3cd60233a3862b304bacda576 Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Tue, 25 Aug 2026 12:05:47 +0700 Subject: [PATCH 5/6] Update comment in _compare_sequence.py --- src/_pytest/assertion/_compare_sequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_pytest/assertion/_compare_sequence.py b/src/_pytest/assertion/_compare_sequence.py index a772cde770f..c346c76b1dc 100644 --- a/src/_pytest/assertion/_compare_sequence.py +++ b/src/_pytest/assertion/_compare_sequence.py @@ -87,8 +87,9 @@ def _compare_eq_sequence( if len_diff: if len_diff > 0: dir_with_more = "Left" - # Only a single extra item can be an insertion at the diff point: - # tails of unequal length can never be equal. + # If the longer side has exactly one extra item and the tails after + # the first differing index align (offset by one), that item is the + # insertion. if found_diff and len_diff == 1 and left[i + 1 :] == right[i:]: extra = saferepr(left[i]) else: From 0784ec2f927d908c2aa197ffebe7b4ae8597a085 Mon Sep 17 00:00:00 2001 From: Dane Parin Date: Tue, 25 Aug 2026 12:17:06 +0700 Subject: [PATCH 6/6] Add fallback test Added fallback test cases for extra items in lists. --- testing/test_assertion.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 309432f9876..a95b3ffe6ed 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1116,6 +1116,9 @@ def test_sequence_different_items(self) -> None: ([1, 2, 0, 3], [1, 2, 3], "Left contains one more item: 0"), ([1, 1], [1, 0, 1], "Right contains one more item: 0"), ([3, 4, 5], [1, 2], "Left contains one more item: 5"), + # Fallback: single extra item but tails don't align + ([1, 2, 3], [1, 9, 8, 3], "Right contains one more item: 3"), + ([1, 9, 8, 3], [1, 2, 3], "Left contains one more item: 3"), ( [1, 2, 3], [0, 1, 2, 3, 4],