From 67f8e6bac6221b903d343f001d1e8ad062315f45 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 14 Sep 2023 01:51:11 -0700 Subject: [PATCH 1/5] Better diffs in tests 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. --- mypy/test/helpers.py | 128 ++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index 7447391593d5..27a5dfb2ea71 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +import difflib import os import pathlib import re @@ -43,64 +44,79 @@ 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. - - 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) - - sys.stderr.write("Expected:\n") +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() + + 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)) + + i = block.a + block.size + j = block.b + block.size + + # matched range + left_ranges.append((block.a, i)) + right_ranges.append((block.b, j)) + return left_ranges, right_ranges + + +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. if they are at the beginning or end, also + # elide the beginning or end. + and (j >= 3 or line_range[0] == 0) + and (j < len(lines) - 3 or line_range[1] == len(content)) + ): + if j == 3: + sys.stderr.write(" ...\n") + continue - # If omit some lines at the beginning, indicate it by displaying a line - # with '...'. - if num_skip_start > 0: - sys.stderr.write(" ...\n") + if not is_matching and colour: + sys.stderr.write(colour) - # Keep track of the first different line. - first_diff = -1 + sys.stderr.write(" " + line) - # Display only this many first characters of identical lines. - width = 75 + if not is_matching: + if colour: + sys.stderr.write("\033[0m") + sys.stderr.write(" (diff)") - 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") - sys.stderr.write("Actual:\n") - - if num_skip_start > 0: - sys.stderr.write(" ...\n") - 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") +def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) -> None: + """Assert that two string arrays are equal. - sys.stderr.write("\n") + 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) + 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 @@ -226,20 +242,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 From f33a3fa53b9f9a4ac069101e334319eb6f49c168 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:01:41 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/test/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index 27a5dfb2ea71..a09293978607 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -69,7 +69,9 @@ def diff_ranges( return left_ranges, right_ranges -def render_diff_range(ranges: list[tuple[int, int]], content: list[str], colour: str | None = None) -> None: +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]] From fea284e7768afb85b089deeb3772fdf358c87df7 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 14 Sep 2023 11:26:45 -0700 Subject: [PATCH 3/5] ellipsis --- mypy/test/helpers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index a09293978607..fecdd2cfc415 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -78,10 +78,9 @@ def render_diff_range( for j, line in enumerate(lines): if ( is_matching - # elide the middle of matching blocks. if they are at the beginning or end, also - # elide the beginning or end. - and (j >= 3 or line_range[0] == 0) - and (j < len(lines) - 3 or line_range[1] == len(content)) + # elide the middle of matching blocks + and j >= 3 + and j < len(lines) - 3 ): if j == 3: sys.stderr.write(" ...\n") From 8fc4e219ea2128599505f82af90adadbf103153b Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 14 Sep 2023 11:30:15 -0700 Subject: [PATCH 4/5] update data --- mypy/test/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index fecdd2cfc415..582deb737aea 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -114,6 +114,7 @@ def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) 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)), @@ -126,6 +127,7 @@ 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") pytest.fail(msg, pytrace=False) From bf57bf645a0ba18669b4db13ed59b75cb9787b00 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 14 Sep 2023 11:41:28 -0700 Subject: [PATCH 5/5] message --- mypy/test/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index 582deb737aea..a53e16e27dfa 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -127,7 +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") + 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)