Skip to content

Commit b41df3b

Browse files
committed
[Utils] Add support for split-file to diff_test_updater
This adds awareness of the `split-file` tool to `diff_test_updater`. Now tests that diff an output file with a file created using `split-file` will have the corresponding slice in the original test updated, rather than the temporary file created by `split-file`.
1 parent e320d9b commit b41df3b

19 files changed

+244
-14
lines changed

llvm/utils/lit/lit/DiffUpdater.py

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,135 @@
11
import shutil
2+
import os
23

34
"""
45
This file provides the `diff_test_updater` function, which is invoked on failed RUN lines when lit is executed with --update-tests.
56
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.
67
The heuristics are currently as follows:
8+
- if exactly one file originates from the `split-file` command, that file is the reference file and the other is the output file
79
- if exactly one file ends with ".expected" (common pattern in LLVM), that file is the reference file and the other is the output file
810
- 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
911
If the command matches one of these patterns the output file content is copied to the reference file to make the test pass.
12+
If the reference file originated in `split-file`, the output file content is instead copied to the corresponding slice of the test file.
1013
Otherwise the test is ignored.
1114
1215
Possible improvements:
1316
- 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.
17+
- Scan RUN lines to see if a file is the source of output from a previous command (other than `split-file`).
1518
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.
1619
- Only update the parts that need updating (based on the diff output). Could help avoid noisy updates when e.g. whitespace changes are ignored.
1720
"""
1821

1922

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

30128
tmp_substr = ".tmp"
31129
if tmp_substr in a and not tmp_substr in b:
32-
return a, b
130+
return a, NormalFileTarget(b)
33131
if tmp_substr in b and not tmp_substr in a:
34-
return b, a
132+
return b, NormalFileTarget(a)
35133

36134
return None
37135

@@ -40,16 +138,16 @@ def filter_flags(args):
40138
return [arg for arg in args if not arg.startswith("-")]
41139

42140

43-
def diff_test_updater(result, test):
141+
def diff_test_updater(result, test, commands):
44142
args = filter_flags(result.command.args)
45143
if len(args) != 3:
46144
return None
47145
[cmd, a, b] = args
48146
if cmd != "diff":
49147
return None
50-
res = get_source_and_target(a, b)
148+
res = get_source_and_target(a, b, test.getFilePath(), commands)
51149
if not res:
52150
return f"update-diff-test: could not deduce source and target from {a} and {b}"
53151
source, target = res
54-
shutil.copy(source, target)
152+
target.copyFrom(source)
55153
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
@@ -1247,7 +1247,7 @@ def executeScriptInternal(
12471247
):
12481248
for test_updater in litConfig.test_updaters:
12491249
try:
1250-
update_output = test_updater(result, test)
1250+
update_output = test_updater(result, test, commands)
12511251
except Exception as e:
12521252
output = out
12531253
output += err
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
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
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)