Skip to content

assertion diff: trailing character skip fails when first characters differ (off-by-one in left[-i] when i=0) #14637

Description

@EternalRights

Was looking at assertion diff output and noticed the trailing skip optimization wasn't kicking in when the compared strings differed at the start. Traced it to _diff_text in compare_text.py line 62:

if len(left) == len(right):
    for i in range(len(left)):
        if left[-i] != right[-i]:
            break
    if i > 42:
        i -= 10  # Provide some context
        yield (
            f"Skipping {i} identical trailing "
            "characters in diff, use -v to show"
        )
        left = left[:-i]
        right = right[:-i]

-0 == 0 in Python, so left[-0] is actually left[0] — the first character, not the last. When the first chars differ, the loop breaks at i=0, i > 42 is False, and the trailing identical characters never get skipped. Dozens of them, just sitting there in the diff output.

Reproducer:

from _pytest.assertion.compare_text import _diff_text
from _pytest.assertion.highlight import dummy_highlighter

# Case 1: first char same — skip works
result = list(_diff_text("a" + "x" + "z" * 50, "a" + "y" + "z" * 50, dummy_highlighter, 0))
# → contains "Skipping 41 identical trailing characters in diff, use -v to show"

# Case 2: first char differs — skip silently fails
result = list(_diff_text("x" + "z" * 50, "y" + "z" * 50, dummy_highlighter, 0))
# → no "Skipping" message, full 50-char diff output

Fix: loop from i=1 instead of i=0:

for i in range(1, len(left) + 1):
    if left[-i] != right[-i]:
        break

The existing test test_text_skipping_trailing doesn't catch this because both test strings start with 'a', so the accidental left[-0] comparison happens to pass and the loop continues to the actual trailing characters.

This code has been around since 2013 (originally in assertion/util.py, moved to compare_text.py in #14520).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions