Skip to content

Commit f1b504b

Browse files
committed
[utils] integrate update-verify-tests with lit's --update-tests
This adds a lit plugin wrapping the core of update-verify-tests. When lit is invoked with --update-tests it will call the plugin, which checks if the failure is due to a -verify mismatch. If it is, it tries to repair the test. If the source file was originally created by split-file, the changes are propagated to the parent file. No tests are added, because I don't want to run nested llvm-lit tests in Swift's test suite, but the core functionality is tested through update-verify-tests.py.
1 parent bf46369 commit f1b504b

File tree

4 files changed

+156
-11
lines changed

4 files changed

+156
-11
lines changed

test/lit.cfg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3291,3 +3291,8 @@ lit_config.note(f"Target Triple: {config.target_triple}, Variant Triple: {config
32913291

32923292
lit_config.note("Available features: " + ", ".join(sorted(config.available_features)))
32933293
config.substitutions.append( ('%use_no_opaque_pointers', '-Xcc -Xclang -Xcc -no-opaque-pointers' ) )
3294+
3295+
if lit_config.update_tests:
3296+
sys.path.append(config.swift_utils)
3297+
from update_verify_tests.litplugin import uvt_lit_plugin
3298+
lit_config.test_updaters.append(uvt_lit_plugin)

utils/update-verify-tests.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ def main():
3232
parser = argparse.ArgumentParser(description=__doc__)
3333
parser.add_argument("--prefix", default="", help="The prefix passed to -verify")
3434
args = parser.parse_args()
35-
(ret_code, output) = check_expectations(sys.stdin.readlines(), args.prefix)
36-
print(output)
37-
sys.exit(ret_code)
35+
(err, updated_files) = check_expectations(sys.stdin.readlines(), args.prefix)
36+
if err:
37+
print(err)
38+
sys.exit(1)
39+
40+
if len(updated_files) > 1:
41+
print("\n\t".join(["updated files:"] + updated_files))
42+
print(f"updated file: {updated_files[0]}")
3843

3944

4045
if __name__ == "__main__":

utils/update_verify_tests/core.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -570,13 +570,13 @@ def update_test_files(errors, prefix):
570570
try:
571571
update_test_file(filename, diag_errors, prefix, updated_test_files)
572572
except KnownException as e:
573-
return f"Error in update-verify-tests while updating {filename}: {e}"
573+
return (
574+
f"Error in update-verify-tests while updating {filename}: {e}",
575+
None,
576+
)
574577
updated_files = list(updated_test_files)
575578
assert updated_files
576-
if len(updated_files) == 1:
577-
return f"updated file {updated_files[0]}"
578-
updated_files_s = "\n\t".join(updated_files)
579-
return "updated files:\n\t{updated_files_s}"
579+
return (None, updated_files)
580580

581581

582582
"""
@@ -786,8 +786,8 @@ def check_expectations(tool_output, prefix):
786786
top_level.extend(curr)
787787

788788
except KnownException as e:
789-
return (1, f"Error in update-verify-tests while parsing tool output: {e}")
789+
return (f"Error in update-verify-tests while parsing tool output: {e}", None)
790790
if top_level:
791-
return (0, update_test_files(top_level, prefix))
791+
return update_test_files(top_level, prefix)
792792
else:
793-
return (1, "no mismatching diagnostics found")
793+
return ("no mismatching diagnostics found", None)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import os
2+
import shlex
3+
import pathlib
4+
from update_verify_tests.core import check_expectations
5+
6+
"""
7+
This file provides the `uvt_lit_plugin` function, which is invoked on failed RUN lines when lit is executed with --update-tests.
8+
It checks whether the failed command is a swift compiler invocation with the `-verify` flag and analyses the output to try to
9+
repair the failed test. If the updated file was originally created by `split-file` it updates the corresponding slice in the source file.
10+
"""
11+
12+
13+
class SplitFileTarget:
14+
def __init__(self, slice_start_idx, test_path, lines, name):
15+
self.slice_start_idx = slice_start_idx
16+
self.test_path = test_path
17+
self.lines = lines
18+
self.name = name
19+
20+
def copyFrom(self, source):
21+
lines_before = self.lines[: self.slice_start_idx + 1]
22+
self.lines = self.lines[self.slice_start_idx + 1 :]
23+
slice_end_idx = None
24+
for i, l in enumerate(self.lines):
25+
if SplitFileTarget._get_split_line_path(l) != None:
26+
slice_end_idx = i
27+
break
28+
if slice_end_idx is not None:
29+
lines_after = self.lines[slice_end_idx:]
30+
else:
31+
lines_after = []
32+
with open(source, "r") as f:
33+
new_lines = lines_before + f.readlines() + lines_after
34+
with open(self.test_path, "w") as f:
35+
for l in new_lines:
36+
f.write(l)
37+
38+
def __str__(self):
39+
return f"slice {self.name} in {self.test_path}"
40+
41+
@staticmethod
42+
def get_target_dir(commands, test_path):
43+
# posix=True breaks Windows paths because \ is treated as an escaping character
44+
for cmd in commands:
45+
split = shlex.split(cmd, posix=False)
46+
if "split-file" not in split:
47+
continue
48+
start_idx = split.index("split-file")
49+
split = split[start_idx:]
50+
if len(split) < 3:
51+
continue
52+
p = unquote(split[1].strip())
53+
if not test_path.samefile(p):
54+
continue
55+
return unquote(split[2].strip())
56+
return None
57+
58+
@staticmethod
59+
def create(path, commands, test_path, target_dir):
60+
path = pathlib.Path(path)
61+
with open(test_path, "r") as f:
62+
lines = f.readlines()
63+
for i, l in enumerate(lines):
64+
p = SplitFileTarget._get_split_line_path(l)
65+
if p and path.samefile(os.path.join(target_dir, p)):
66+
idx = i
67+
break
68+
else:
69+
return None
70+
return SplitFileTarget(idx, test_path, lines, p)
71+
72+
@staticmethod
73+
def _get_split_line_path(l):
74+
if len(l) < 6:
75+
return None
76+
if l.startswith("//"):
77+
l = l[2:]
78+
else:
79+
l = l[1:]
80+
if l.startswith("--- "):
81+
l = l[4:]
82+
else:
83+
return None
84+
return l.rstrip()
85+
86+
87+
def unquote(s):
88+
if len(s) > 1 and s[0] == s[-1] and (s[0] == '"' or s[0] == "'"):
89+
return s[1:-1]
90+
return s
91+
92+
93+
def propagate_split_files(test_path, updated_files, commands):
94+
test_path = pathlib.Path(test_path)
95+
split_target_dir = SplitFileTarget.get_target_dir(commands, test_path)
96+
if not split_target_dir:
97+
return updated_files
98+
99+
new = []
100+
for file in updated_files:
101+
target = SplitFileTarget.create(file, commands, test_path, split_target_dir)
102+
if target:
103+
target.copyFrom(file)
104+
new.append(target)
105+
else:
106+
new.append(file)
107+
return new
108+
109+
110+
def uvt_lit_plugin(result, test, commands):
111+
if (
112+
not any(e.endswith("swift-frontend") for e in result.command.args)
113+
or not "-verify" in result.command.args
114+
):
115+
return None
116+
117+
prefix = ""
118+
for i, arg in enumerate(result.command.args):
119+
if arg == "-verify-additional-prefix":
120+
if i + 1 >= len(result.command.args):
121+
return None
122+
if prefix:
123+
# can only handle at most 1 additional prefix at the moment
124+
return None
125+
prefix = result.command.args[i + 1]
126+
127+
(err, updated_files) = check_expectations(result.stderr.split("\n"), prefix)
128+
if err:
129+
return err
130+
131+
updated_files = propagate_split_files(test.getFilePath(), updated_files, commands)
132+
133+
if len(updated_files) > 1:
134+
return "\n\t".join(["updated files:"] + updated_files)
135+
return f"updated file: {updated_files[0]}"

0 commit comments

Comments
 (0)