Skip to content

Commit 9eb17cc

Browse files
authored
[Utils] Add support for split-file to diff_test_updater (#157765)
1 parent 4ae520b commit 9eb17cc

21 files changed

+260
-14
lines changed

llvm/utils/lit/lit/DiffUpdater.py

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,136 @@
11
import shutil
2+
import os
3+
import shlex
24

35
"""
46
This file provides the `diff_test_updater` function, which is invoked on failed RUN lines when lit is executed with --update-tests.
57
It checks whether the failed command is `diff` and, if so, uses heuristics to determine which file is the checked-in reference file and which file is output from the test case.
68
The heuristics are currently as follows:
9+
- if exactly one file originates from the `split-file` command, that file is the reference file and the other is the output file
710
- if exactly one file ends with ".expected" (common pattern in LLVM), that file is the reference file and the other is the output file
811
- if exactly one file path contains ".tmp" (e.g. because it contains the expansion of "%t"), that file is the reference file and the other is the output file
912
If the command matches one of these patterns the output file content is copied to the reference file to make the test pass.
13+
If the reference file originated in `split-file`, the output file content is instead copied to the corresponding slice of the test file.
1014
Otherwise the test is ignored.
1115
1216
Possible improvements:
1317
- Support stdin patterns like "my_binary %s | diff expected.txt"
14-
- Scan RUN lines to see if a file is the source of output from a previous command.
18+
- Scan RUN lines to see if a file is the source of output from a previous command (other than `split-file`).
1519
If it is then it is not a reference file that can be copied to, regardless of name, since the test will overwrite it anyways.
1620
- Only update the parts that need updating (based on the diff output). Could help avoid noisy updates when e.g. whitespace changes are ignored.
1721
"""
1822

1923

20-
def get_source_and_target(a, b):
24+
class NormalFileTarget:
25+
def __init__(self, target):
26+
self.target = target
27+
28+
def copyFrom(self, source):
29+
shutil.copy(source, self.target)
30+
31+
def __str__(self):
32+
return self.target
33+
34+
35+
class SplitFileTarget:
36+
def __init__(self, slice_start_idx, test_path, lines):
37+
self.slice_start_idx = slice_start_idx
38+
self.test_path = test_path
39+
self.lines = lines
40+
41+
def copyFrom(self, source):
42+
lines_before = self.lines[: self.slice_start_idx + 1]
43+
self.lines = self.lines[self.slice_start_idx + 1 :]
44+
slice_end_idx = None
45+
for i, l in enumerate(self.lines):
46+
if SplitFileTarget._get_split_line_path(l) != None:
47+
slice_end_idx = i
48+
break
49+
if slice_end_idx is not None:
50+
lines_after = self.lines[slice_end_idx:]
51+
else:
52+
lines_after = []
53+
with open(source, "r") as f:
54+
new_lines = lines_before + f.readlines() + lines_after
55+
with open(self.test_path, "w") as f:
56+
for l in new_lines:
57+
f.write(l)
58+
59+
def __str__(self):
60+
return f"slice in {self.test_path}"
61+
62+
@staticmethod
63+
def get_target_dir(commands, test_path):
64+
for cmd in commands:
65+
split = shlex.split(cmd)
66+
if "split-file" not in split:
67+
continue
68+
start_idx = split.index("split-file")
69+
split = split[start_idx:]
70+
if len(split) < 3:
71+
continue
72+
if split[1].strip() != test_path:
73+
continue
74+
return split[2].strip()
75+
return None
76+
77+
@staticmethod
78+
def create(path, commands, test_path, target_dir):
79+
filename = path.replace(target_dir, "")
80+
if filename.startswith(os.sep):
81+
filename = filename[len(os.sep) :]
82+
with open(test_path, "r") as f:
83+
lines = f.readlines()
84+
for i, l in enumerate(lines):
85+
p = SplitFileTarget._get_split_line_path(l)
86+
if p == filename:
87+
idx = i
88+
break
89+
else:
90+
return None
91+
return SplitFileTarget(idx, test_path, lines)
92+
93+
@staticmethod
94+
def _get_split_line_path(l):
95+
if len(l) < 6:
96+
return None
97+
if l.startswith("//"):
98+
l = l[2:]
99+
else:
100+
l = l[1:]
101+
if l.startswith("--- "):
102+
l = l[4:]
103+
else:
104+
return None
105+
return l.rstrip()
106+
107+
108+
def get_source_and_target(a, b, test_path, commands):
21109
"""
22110
Try to figure out which file is the test output and which is the reference.
23111
"""
112+
split_target_dir = SplitFileTarget.get_target_dir(commands, test_path)
113+
if split_target_dir:
114+
a_target = SplitFileTarget.create(a, commands, test_path, split_target_dir)
115+
b_target = SplitFileTarget.create(b, commands, test_path, split_target_dir)
116+
if a_target and b_target:
117+
return None
118+
if a_target:
119+
return b, a_target
120+
if b_target:
121+
return a, b_target
122+
24123
expected_suffix = ".expected"
25124
if a.endswith(expected_suffix) and not b.endswith(expected_suffix):
26-
return b, a
125+
return b, NormalFileTarget(a)
27126
if b.endswith(expected_suffix) and not a.endswith(expected_suffix):
28-
return a, b
127+
return a, NormalFileTarget(b)
29128

30129
tmp_substr = ".tmp"
31130
if tmp_substr in a and not tmp_substr in b:
32-
return a, b
131+
return a, NormalFileTarget(b)
33132
if tmp_substr in b and not tmp_substr in a:
34-
return b, a
133+
return b, NormalFileTarget(a)
35134

36135
return None
37136

@@ -40,16 +139,16 @@ def filter_flags(args):
40139
return [arg for arg in args if not arg.startswith("-")]
41140

42141

43-
def diff_test_updater(result, test):
142+
def diff_test_updater(result, test, commands):
44143
args = filter_flags(result.command.args)
45144
if len(args) != 3:
46145
return None
47146
[cmd, a, b] = args
48147
if cmd != "diff":
49148
return None
50-
res = get_source_and_target(a, b)
149+
res = get_source_and_target(a, b, test.getFilePath(), commands)
51150
if not res:
52151
return f"update-diff-test: could not deduce source and target from {a} and {b}"
53152
source, target = res
54-
shutil.copy(source, target)
153+
target.copyFrom(source)
55154
return f"update-diff-test: copied {source} to {target}"

llvm/utils/lit/lit/TestRunner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,7 @@ def executeScriptInternal(
12411241
):
12421242
for test_updater in litConfig.test_updaters:
12431243
try:
1244-
update_output = test_updater(result, test)
1244+
update_output = test_updater(result, test, commands)
12451245
except Exception as e:
12461246
output = out
12471247
output += err
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
; diff-tmp-dir.test clobbers this file
22
empty.txt
3+
; these test cases are clobbered when run, so they're recreated each time
4+
single-split-file.test
5+
single-split-file-populated.test
6+
multiple-split-file.test
7+
multiple-split-file-populated.test
8+
single-split-file-no-expected.test
9+
split-c-comments.test
10+
split whitespace.test
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test3.expected %t/out.txt
4+
5+
#--- test1.expected
6+
unrelated
7+
#--- test2.expected
8+
#--- test3.expected
9+
BAR
10+
11+
BAZ
12+
13+
#--- test4.expected
14+
filler
15+
#--- test5.expected
16+
17+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test3.expected %t/out.txt
4+
5+
#--- test1.expected
6+
unrelated
7+
#--- test2.expected
8+
#--- test3.expected
9+
#--- test4.expected
10+
filler
11+
#--- test5.expected
12+
13+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test3.expected %t/out.txt
4+
5+
#--- test1.expected
6+
unrelated
7+
#--- test2.expected
8+
#--- test3.expected
9+
FOO
10+
#--- test4.expected
11+
filler
12+
#--- test5.expected
13+
14+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test.txt %t/out.txt
4+
5+
#--- test.txt
6+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test.txt %t/out.txt
4+
5+
#--- test.txt
6+
FOO
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test.expected %t/out.txt
4+
5+
#--- test.expected
6+
BAR
7+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# RUN: split-file %s %t
2+
# RUN: cp %S/1.in %t/out.txt
3+
# RUN: diff %t/test.expected %t/out.txt
4+
5+
#--- test.expected

0 commit comments

Comments
 (0)