check_test_order.py (4284B) - Raw
1 #!/usr/bin/env python3 2 """Check and optionally fix test order in parser_test.zig to match upstream.""" 3 4 import re 5 import sys 6 7 OURS = "parser_test.zig" 8 UPSTREAM = "../zig/lib/std/zig/parser_test.zig" 9 10 11 def extract_test_names(path): 12 with open(path) as f: 13 return re.findall(r'^test "(.+?)" \{', f.read(), re.M) 14 15 16 def extract_test_blocks(path): 17 """Split file into: header, list of (name, content) test blocks, footer.""" 18 with open(path) as f: 19 lines = f.readlines() 20 21 header = [] 22 footer = [] 23 blocks = [] 24 current_name = None 25 current_lines = [] 26 brace_depth = 0 27 in_test = False 28 found_first_test = False 29 30 for line in lines: 31 m = re.match(r'^test "(.+?)" \{', line) 32 if m and not in_test: 33 found_first_test = True 34 if current_name is not None: 35 blocks.append((current_name, "".join(current_lines))) 36 current_name = m.group(1) 37 current_lines = [line] 38 brace_depth = 1 39 in_test = True 40 continue 41 42 if in_test: 43 current_lines.append(line) 44 brace_depth += line.count("{") - line.count("}") 45 if brace_depth == 0: 46 in_test = False 47 elif not found_first_test: 48 header.append(line) 49 else: 50 # Non-test content after tests started — could be blank lines 51 # between tests or footer content 52 if current_name is not None: 53 # Append to previous test block as trailing content 54 current_lines.append(line) 55 else: 56 footer.append(line) 57 58 if current_name is not None: 59 blocks.append((current_name, "".join(current_lines))) 60 61 # Anything after the last test block is footer 62 # Split last block's trailing non-test content into footer 63 if blocks: 64 last_name, last_content = blocks[-1] 65 last_lines = last_content.split('\n') 66 # Find where the test block ends (} at column 0) 67 test_end = len(last_lines) 68 for i, line in enumerate(last_lines): 69 if line == '}' and i > 0: 70 test_end = i + 1 71 if test_end < len(last_lines): 72 blocks[-1] = (last_name, '\n'.join(last_lines[:test_end]) + '\n') 73 footer = ['\n'.join(last_lines[test_end:]) + '\n'] + footer 74 75 return "".join(header), blocks, "".join(footer) 76 77 78 def main(): 79 fix = "--fix" in sys.argv 80 81 upstream_order = extract_test_names(UPSTREAM) 82 our_names = extract_test_names(OURS) 83 84 # Build position map for upstream 85 upstream_pos = {name: i for i, name in enumerate(upstream_order)} 86 87 # Check order 88 our_in_upstream = [n for n in our_names if n in upstream_pos] 89 positions = [upstream_pos[n] for n in our_in_upstream] 90 is_sorted = positions == sorted(positions) 91 92 if is_sorted: 93 print(f"OK: {len(our_names)} tests in correct order") 94 return 0 95 96 # Find out-of-order tests 97 out_of_order = [] 98 prev_pos = -1 99 for name in our_in_upstream: 100 pos = upstream_pos[name] 101 if pos < prev_pos: 102 out_of_order.append(name) 103 prev_pos = max(prev_pos, pos) 104 105 print(f"WARN: {len(out_of_order)} tests out of order:") 106 for name in out_of_order[:10]: 107 print(f" - {name}") 108 if len(out_of_order) > 10: 109 print(f" ... and {len(out_of_order) - 10} more") 110 111 if not fix: 112 print("\nRun with --fix to reorder") 113 return 1 114 115 # Fix: reorder 116 header, blocks, footer = extract_test_blocks(OURS) 117 block_map = {name: content for name, content in blocks} 118 119 # Reorder: upstream-ordered first, then extras 120 ordered = [] 121 seen = set() 122 for name in upstream_order: 123 if name in block_map and name not in seen: 124 ordered.append((name, block_map[name])) 125 seen.add(name) 126 for name, content in blocks: 127 if name not in seen: 128 ordered.append((name, content)) 129 seen.add(name) 130 131 with open(OURS, "w") as f: 132 f.write(header) 133 for _, content in ordered: 134 f.write("\n") 135 f.write(content) 136 f.write(footer) 137 138 print(f"Fixed: {len(ordered)} tests reordered") 139 return 0 140 141 142 if __name__ == "__main__": 143 sys.exit(main())