Skip to content

Commit 4deecc9

Browse files
committed
[test] Add test that verify correct usage of REQUIRES for Swift features
The test will look for other tests using `RUN:` lines that use experimental or upcoming features and will check that the tests also are checking with the right `REQUIRES:` lines for the used features. This should avoid new tests being introduced without the right `REQUIRES` and should avoid breaking the toolchain in less tested configurations.
1 parent 70496ed commit 4deecc9

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#! /usr/bin/env python3
2+
# -*- python -*-
3+
# RUN: %{python} %s '%swift_src_root'
4+
5+
import pathlib
6+
import re
7+
import subprocess
8+
import sys
9+
10+
11+
# Tests that check for the behaviour of experimental/upcoming features, so
12+
# they cannot automatically be checked.
13+
EXCEPTIONAL_FILES = [
14+
pathlib.Path("test/Frontend/experimental-features-no-asserts.swift"),
15+
pathlib.Path("test/Frontend/upcoming_feature.swift"),
16+
]
17+
18+
FEATURE_USAGE_RE = re.compile(r"-enable-(?:experimental|upcoming)-feature (?:-Xfrontend )?([A-Za-z0-9]*)")
19+
20+
21+
def find_test_files_with_features_usage(swift_src_root):
22+
# Look for every test file in the test directories with `RUN` lines that
23+
# mention `-enable-experimental-feature` or `-enable-upcoming-feature`.
24+
# Be careful of not using REQUIRES or RUN with a colon after them or Lit will
25+
# pick them up.
26+
output = subprocess.check_output([
27+
"grep",
28+
"--extended-regexp",
29+
"--recursive",
30+
"-e",
31+
"RUN[:].*-enable-(experimental|upcoming)-feature",
32+
"--files-with-matches",
33+
str(swift_src_root / "test"),
34+
str(swift_src_root / "validation-test"),
35+
], text=True)
36+
return output.splitlines()
37+
38+
39+
def find_run_lines(test_file):
40+
output = subprocess.check_output([
41+
"grep",
42+
"--extended-regexp",
43+
"--no-filename",
44+
"-e",
45+
"RUN[:]",
46+
str(test_file),
47+
], text=True)
48+
return output.splitlines()
49+
50+
51+
def check_existing_requires(test_file, feature):
52+
returncode = subprocess.call([
53+
"grep",
54+
"--extended-regexp",
55+
"--quiet",
56+
"-e",
57+
"REQUIRES[:].*swift_feature_" + feature,
58+
str(test_file),
59+
])
60+
return returncode != 0
61+
62+
63+
def check_existing_error_message_checks(test_file, feature):
64+
returncode = subprocess.call([
65+
"grep",
66+
"--extended-regexp",
67+
"--quiet",
68+
"-e",
69+
"requires '-enable-(experimental|upcoming)-feature " + feature + "'",
70+
str(test_file),
71+
])
72+
return returncode != 0
73+
74+
75+
def check_test_file_feature_usage(test_file):
76+
run_lines = find_run_lines(test_file)
77+
features = set(
78+
feature
79+
for line in run_lines
80+
for feature in FEATURE_USAGE_RE.findall(line)
81+
)
82+
num_failures = 0
83+
for feature in features:
84+
# No warning if the necessary `REQUIRES` is already there
85+
if not check_existing_requires(test_file, feature):
86+
continue
87+
88+
# Some tests check for the errors themselves, so we can skip them as well
89+
if not check_existing_error_message_checks(test_file, feature):
90+
continue
91+
92+
# For everything else, print a warning and for an invalid exit code
93+
print("error: {}: Missing '{}: swift_feature_{}'".format(str(test_file), "REQUIRES", feature))
94+
num_failures += 1
95+
return num_failures == 0
96+
97+
98+
def main():
99+
if len(sys.argv) < 2:
100+
print('Invalid number of arguments.')
101+
sys.exit(1)
102+
103+
swift_src_root = pathlib.Path(sys.argv[1])
104+
105+
num_failures = 0
106+
test_files_with_features_usage = find_test_files_with_features_usage(swift_src_root)
107+
for test_file in test_files_with_features_usage:
108+
test_file = pathlib.Path(test_file)
109+
# First lets check this is not one of the exceptional files
110+
if test_file.relative_to(swift_src_root) in EXCEPTIONAL_FILES:
111+
continue
112+
113+
if not check_test_file_feature_usage(test_file):
114+
num_failures += 1
115+
116+
if num_failures > 0:
117+
sys.exit(1)
118+
119+
if __name__ == '__main__':
120+
main()

0 commit comments

Comments
 (0)