Skip to content

Commit

Permalink
Better diffs in tests (#16112)
Browse files Browse the repository at this point in the history
It's annoying that one line change causes everything else to show up as
a diff. Just use difflib instead. I also highlight the changed lines. We
can't use FancyFormatter because it doesn't work well with pytest.
  • Loading branch information
hauntsaninja authored Sep 15, 2023
1 parent 2c2d126 commit d77310a
Showing 1 changed file with 68 additions and 60 deletions.
128 changes: 68 additions & 60 deletions mypy/test/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import contextlib
import difflib
import os
import pathlib
import re
Expand Down Expand Up @@ -43,64 +44,81 @@ def run_mypy(args: list[str]) -> None:
pytest.fail(msg="Sample check failed", pytrace=False)


def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) -> None:
"""Assert that two string arrays are equal.
def diff_ranges(
left: list[str], right: list[str]
) -> tuple[list[tuple[int, int]], list[tuple[int, int]]]:
seq = difflib.SequenceMatcher(None, left, right)
# note last triple is a dummy, so don't need to worry
blocks = seq.get_matching_blocks()

Display any differences in a human-readable form.
"""
actual = clean_up(actual)
if actual != expected:
num_skip_start = num_skipped_prefix_lines(expected, actual)
num_skip_end = num_skipped_suffix_lines(expected, actual)
i = 0
j = 0
left_ranges = []
right_ranges = []
for block in blocks:
# mismatched range
left_ranges.append((i, block.a))
right_ranges.append((j, block.b))

sys.stderr.write("Expected:\n")
i = block.a + block.size
j = block.b + block.size

# If omit some lines at the beginning, indicate it by displaying a line
# with '...'.
if num_skip_start > 0:
sys.stderr.write(" ...\n")
# matched range
left_ranges.append((block.a, i))
right_ranges.append((block.b, j))
return left_ranges, right_ranges

# Keep track of the first different line.
first_diff = -1

# Display only this many first characters of identical lines.
width = 75
def render_diff_range(
ranges: list[tuple[int, int]], content: list[str], colour: str | None = None
) -> None:
for i, line_range in enumerate(ranges):
is_matching = i % 2 == 1
lines = content[line_range[0] : line_range[1]]
for j, line in enumerate(lines):
if (
is_matching
# elide the middle of matching blocks
and j >= 3
and j < len(lines) - 3
):
if j == 3:
sys.stderr.write(" ...\n")
continue

for i in range(num_skip_start, len(expected) - num_skip_end):
if i >= len(actual) or expected[i] != actual[i]:
if first_diff < 0:
first_diff = i
sys.stderr.write(f" {expected[i]:<45} (diff)")
else:
e = expected[i]
sys.stderr.write(" " + e[:width])
if len(e) > width:
sys.stderr.write("...")
sys.stderr.write("\n")
if num_skip_end > 0:
sys.stderr.write(" ...\n")
if not is_matching and colour:
sys.stderr.write(colour)

sys.stderr.write("Actual:\n")
sys.stderr.write(" " + line)

if num_skip_start > 0:
sys.stderr.write(" ...\n")
if not is_matching:
if colour:
sys.stderr.write("\033[0m")
sys.stderr.write(" (diff)")

for j in range(num_skip_start, len(actual) - num_skip_end):
if j >= len(expected) or expected[j] != actual[j]:
sys.stderr.write(f" {actual[j]:<45} (diff)")
else:
a = actual[j]
sys.stderr.write(" " + a[:width])
if len(a) > width:
sys.stderr.write("...")
sys.stderr.write("\n")
if not actual:
sys.stderr.write(" (empty)\n")
if num_skip_end > 0:
sys.stderr.write(" ...\n")

sys.stderr.write("\n")

def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) -> None:
"""Assert that two string arrays are equal.
Display any differences in a human-readable form.
"""
actual = clean_up(actual)
if expected != actual:
expected_ranges, actual_ranges = diff_ranges(expected, actual)
sys.stderr.write("Expected:\n")
red = "\033[31m" if sys.platform != "win32" else None
render_diff_range(expected_ranges, expected, colour=red)
sys.stderr.write("Actual:\n")
green = "\033[32m" if sys.platform != "win32" else None
render_diff_range(actual_ranges, actual, colour=green)

sys.stderr.write("\n")
first_diff = next(
(i for i, (a, b) in enumerate(zip(expected, actual)) if a != b),
max(len(expected), len(actual)),
)
if 0 <= first_diff < len(actual) and (
len(expected[first_diff]) >= MIN_LINE_LENGTH_FOR_ALIGNMENT
or len(actual[first_diff]) >= MIN_LINE_LENGTH_FOR_ALIGNMENT
Expand All @@ -109,6 +127,10 @@ def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str)
# long lines.
show_align_message(expected[first_diff], actual[first_diff])

sys.stderr.write(
"Update the test output using --update-data -n0 "
"(you can additionally use the -k selector to update only specific tests)"
)
pytest.fail(msg, pytrace=False)


Expand Down Expand Up @@ -226,20 +248,6 @@ def local_sys_path_set() -> Iterator[None]:
sys.path = old_sys_path


def num_skipped_prefix_lines(a1: list[str], a2: list[str]) -> int:
num_eq = 0
while num_eq < min(len(a1), len(a2)) and a1[num_eq] == a2[num_eq]:
num_eq += 1
return max(0, num_eq - 4)


def num_skipped_suffix_lines(a1: list[str], a2: list[str]) -> int:
num_eq = 0
while num_eq < min(len(a1), len(a2)) and a1[-num_eq - 1] == a2[-num_eq - 1]:
num_eq += 1
return max(0, num_eq - 4)


def testfile_pyversion(path: str) -> tuple[int, int]:
if path.endswith("python312.test"):
return 3, 12
Expand Down

0 comments on commit d77310a

Please sign in to comment.