1
1
from __future__ import annotations
2
2
3
3
import contextlib
4
+ import difflib
4
5
import os
5
6
import pathlib
6
7
import re
@@ -43,64 +44,81 @@ def run_mypy(args: list[str]) -> None:
43
44
pytest .fail (msg = "Sample check failed" , pytrace = False )
44
45
45
46
46
- def assert_string_arrays_equal (expected : list [str ], actual : list [str ], msg : str ) -> None :
47
- """Assert that two string arrays are equal.
47
+ def diff_ranges (
48
+ left : list [str ], right : list [str ]
49
+ ) -> tuple [list [tuple [int , int ]], list [tuple [int , int ]]]:
50
+ seq = difflib .SequenceMatcher (None , left , right )
51
+ # note last triple is a dummy, so don't need to worry
52
+ blocks = seq .get_matching_blocks ()
48
53
49
- Display any differences in a human-readable form.
50
- """
51
- actual = clean_up (actual )
52
- if actual != expected :
53
- num_skip_start = num_skipped_prefix_lines (expected , actual )
54
- num_skip_end = num_skipped_suffix_lines (expected , actual )
54
+ i = 0
55
+ j = 0
56
+ left_ranges = []
57
+ right_ranges = []
58
+ for block in blocks :
59
+ # mismatched range
60
+ left_ranges .append ((i , block .a ))
61
+ right_ranges .append ((j , block .b ))
55
62
56
- sys .stderr .write ("Expected:\n " )
63
+ i = block .a + block .size
64
+ j = block .b + block .size
57
65
58
- # If omit some lines at the beginning, indicate it by displaying a line
59
- # with '...'.
60
- if num_skip_start > 0 :
61
- sys . stderr . write ( " ... \n " )
66
+ # matched range
67
+ left_ranges . append (( block . a , i ))
68
+ right_ranges . append (( block . b , j ))
69
+ return left_ranges , right_ranges
62
70
63
- # Keep track of the first different line.
64
- first_diff = - 1
65
71
66
- # Display only this many first characters of identical lines.
67
- width = 75
72
+ def render_diff_range (
73
+ ranges : list [tuple [int , int ]], content : list [str ], colour : str | None = None
74
+ ) -> None :
75
+ for i , line_range in enumerate (ranges ):
76
+ is_matching = i % 2 == 1
77
+ lines = content [line_range [0 ] : line_range [1 ]]
78
+ for j , line in enumerate (lines ):
79
+ if (
80
+ is_matching
81
+ # elide the middle of matching blocks
82
+ and j >= 3
83
+ and j < len (lines ) - 3
84
+ ):
85
+ if j == 3 :
86
+ sys .stderr .write (" ...\n " )
87
+ continue
68
88
69
- for i in range (num_skip_start , len (expected ) - num_skip_end ):
70
- if i >= len (actual ) or expected [i ] != actual [i ]:
71
- if first_diff < 0 :
72
- first_diff = i
73
- sys .stderr .write (f" { expected [i ]:<45} (diff)" )
74
- else :
75
- e = expected [i ]
76
- sys .stderr .write (" " + e [:width ])
77
- if len (e ) > width :
78
- sys .stderr .write ("..." )
79
- sys .stderr .write ("\n " )
80
- if num_skip_end > 0 :
81
- sys .stderr .write (" ...\n " )
89
+ if not is_matching and colour :
90
+ sys .stderr .write (colour )
82
91
83
- sys .stderr .write ("Actual: \n " )
92
+ sys .stderr .write (" " + line )
84
93
85
- if num_skip_start > 0 :
86
- sys .stderr .write (" ...\n " )
94
+ if not is_matching :
95
+ if colour :
96
+ sys .stderr .write ("\033 [0m" )
97
+ sys .stderr .write (" (diff)" )
87
98
88
- for j in range (num_skip_start , len (actual ) - num_skip_end ):
89
- if j >= len (expected ) or expected [j ] != actual [j ]:
90
- sys .stderr .write (f" { actual [j ]:<45} (diff)" )
91
- else :
92
- a = actual [j ]
93
- sys .stderr .write (" " + a [:width ])
94
- if len (a ) > width :
95
- sys .stderr .write ("..." )
96
99
sys .stderr .write ("\n " )
97
- if not actual :
98
- sys .stderr .write (" (empty)\n " )
99
- if num_skip_end > 0 :
100
- sys .stderr .write (" ...\n " )
101
100
102
- sys .stderr .write ("\n " )
103
101
102
+ def assert_string_arrays_equal (expected : list [str ], actual : list [str ], msg : str ) -> None :
103
+ """Assert that two string arrays are equal.
104
+
105
+ Display any differences in a human-readable form.
106
+ """
107
+ actual = clean_up (actual )
108
+ if expected != actual :
109
+ expected_ranges , actual_ranges = diff_ranges (expected , actual )
110
+ sys .stderr .write ("Expected:\n " )
111
+ red = "\033 [31m" if sys .platform != "win32" else None
112
+ render_diff_range (expected_ranges , expected , colour = red )
113
+ sys .stderr .write ("Actual:\n " )
114
+ green = "\033 [32m" if sys .platform != "win32" else None
115
+ render_diff_range (actual_ranges , actual , colour = green )
116
+
117
+ sys .stderr .write ("\n " )
118
+ first_diff = next (
119
+ (i for i , (a , b ) in enumerate (zip (expected , actual )) if a != b ),
120
+ max (len (expected ), len (actual )),
121
+ )
104
122
if 0 <= first_diff < len (actual ) and (
105
123
len (expected [first_diff ]) >= MIN_LINE_LENGTH_FOR_ALIGNMENT
106
124
or len (actual [first_diff ]) >= MIN_LINE_LENGTH_FOR_ALIGNMENT
@@ -109,6 +127,10 @@ def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str)
109
127
# long lines.
110
128
show_align_message (expected [first_diff ], actual [first_diff ])
111
129
130
+ sys .stderr .write (
131
+ "Update the test output using --update-data -n0 "
132
+ "(you can additionally use the -k selector to update only specific tests)"
133
+ )
112
134
pytest .fail (msg , pytrace = False )
113
135
114
136
@@ -226,20 +248,6 @@ def local_sys_path_set() -> Iterator[None]:
226
248
sys .path = old_sys_path
227
249
228
250
229
- def num_skipped_prefix_lines (a1 : list [str ], a2 : list [str ]) -> int :
230
- num_eq = 0
231
- while num_eq < min (len (a1 ), len (a2 )) and a1 [num_eq ] == a2 [num_eq ]:
232
- num_eq += 1
233
- return max (0 , num_eq - 4 )
234
-
235
-
236
- def num_skipped_suffix_lines (a1 : list [str ], a2 : list [str ]) -> int :
237
- num_eq = 0
238
- while num_eq < min (len (a1 ), len (a2 )) and a1 [- num_eq - 1 ] == a2 [- num_eq - 1 ]:
239
- num_eq += 1
240
- return max (0 , num_eq - 4 )
241
-
242
-
243
251
def testfile_pyversion (path : str ) -> tuple [int , int ]:
244
252
if path .endswith ("python312.test" ):
245
253
return 3 , 12
0 commit comments