diff --git a/changelog/13652.bugfix.rst b/changelog/13652.bugfix.rst new file mode 100644 index 00000000000..12ff991a796 --- /dev/null +++ b/changelog/13652.bugfix.rst @@ -0,0 +1 @@ +``contains one more item`` now reports the actual extra item in sequence diffs. diff --git a/src/_pytest/assertion/_compare_sequence.py b/src/_pytest/assertion/_compare_sequence.py index cada856cc85..c346c76b1dc 100644 --- a/src/_pytest/assertion/_compare_sequence.py +++ b/src/_pytest/assertion/_compare_sequence.py @@ -52,6 +52,8 @@ def _compare_eq_sequence( comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) 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: @@ -73,6 +75,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 +87,20 @@ def _compare_eq_sequence( if len_diff: if len_diff > 0: dir_with_more = "Left" - extra = saferepr(left[len_right]) + # 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: + 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 cc2ea761e81..a95b3ffe6ed 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1105,6 +1105,34 @@ 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"), + ([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], + "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():