Skip to content

Commit af02b9f

Browse files
committed
tests: make check_selftests check format of Makefiles and configs
Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 19c2685 commit af02b9f

File tree

5 files changed

+521
-31
lines changed

5 files changed

+521
-31
lines changed

tests/patch/check_selftest/check_selftest.sh

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"run": ["check_selftest.sh"]
2+
"pymod": "test",
3+
"pyfunc": "check_selftest"
34
}

tests/patch/check_selftest/test.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
3+
""" Test Makefile, .gitignore and config format """
4+
5+
import os
6+
import subprocess
7+
from typing import Tuple
8+
9+
10+
LOCAL_DIR = os.path.dirname(__file__)
11+
12+
13+
def ret_merge(ret, nret):
14+
""" merge results """
15+
if ret[0] == 0 or nret[0] == 0:
16+
val = 0
17+
else:
18+
val = min(ret[0], nret[0])
19+
20+
desc = ""
21+
if ret[1] and nret[1]:
22+
desc = ret[1] + "; " + nret[1]
23+
else:
24+
desc = ret[1] + nret[1]
25+
return (val, desc)
26+
27+
28+
def check_new_files_makefile(tree, new_files, log):
29+
""" Make sure new files are listed in a Makefile, somewhere """
30+
31+
ret = (0, "")
32+
cnt = 0
33+
34+
for path in new_files:
35+
if path.endswith(('.sh', '.py')):
36+
needle = path
37+
elif path.endswith(('.c')):
38+
needle = path.split('.')[0]
39+
else:
40+
log.append("makefile inclusion check ignoring " + path)
41+
continue
42+
43+
makefile = os.path.dirname(path) + "/Makefile"
44+
45+
cmd = ["git", "grep", "--exit-code", needle, "---", makefile]
46+
result = subprocess.run(cmd, cwd=tree.path, check=False)
47+
log.append(" ".join(cmd) + f":: {result.returncode}")
48+
if result.returncode:
49+
ret_merge(ret, (1, path + " not found in Makefile"))
50+
cnt += 1
51+
52+
if not ret[0] and cnt:
53+
ret = (0, f"New files in Makefile checked ({cnt})")
54+
55+
return ret
56+
57+
58+
def check_new_files_gitignore(tree, new_files, log):
59+
""" Make sure new binaries are listed in .gitignore """
60+
61+
ret = (0, "")
62+
cnt = 0
63+
64+
for path in new_files:
65+
if path.endswith(('.c')):
66+
needle = path.split('.')[0]
67+
else:
68+
log.append("gitignore check ignoring " + path)
69+
continue
70+
71+
target = os.path.dirname(path) + "/.gitignore"
72+
73+
cmd = ["git", "grep", "--exit-code", needle, "---", target]
74+
result = subprocess.run(cmd, cwd=tree.path, check=False)
75+
log.append(" ".join(cmd) + f":: {result.returncode}")
76+
if result.returncode:
77+
ret_merge(ret, (1, needle + " not found in .gitignore"))
78+
cnt += 1
79+
80+
if not ret[0] and cnt:
81+
ret = (0, f"New files in gitignore checked ({cnt})")
82+
83+
return ret
84+
85+
86+
def _check_file_fmt(tree, path, script, result_dir, ident):
87+
cmd = [script, os.path.join(tree.path, path)]
88+
89+
result = subprocess.run(cmd, cwd=LOCAL_DIR, capture_output=True,
90+
text=True, check=False)
91+
with open(os.path.join(result_dir, ident), "w", encoding="utf-8") as fp:
92+
fp.write(result.stdout)
93+
return result.returncode
94+
95+
96+
def check_file_formats(tree, file_list, log, result_dir):
97+
""" Validate sort order of all touched files """
98+
99+
ret = (0, "")
100+
i = 0
101+
for path in file_list:
102+
if path.endswith("/config"):
103+
script = "validate_config_format.py"
104+
fmt = f"fmt-config-{i}"
105+
elif path.endswith("/.gitignore"):
106+
script = "validate_config_format.py"
107+
fmt = f"fmt-gitignore-{i}"
108+
elif path.endswith("/Makefile"):
109+
script = "validate_Makefile_format.py"
110+
fmt = f"fmt-makefile-{i}"
111+
else:
112+
log.append("format check ignoring " + path)
113+
continue
114+
115+
if _check_file_fmt(tree, path, script, result_dir, fmt):
116+
ret = ret_merge(ret, (1, "Bad format: " + path))
117+
118+
if not ret[0] and i:
119+
ret = (0, f"Good format ({i})")
120+
121+
return ret
122+
123+
124+
def extract_files(patch):
125+
"""Extract paths of new files being added by the series."""
126+
127+
new_files = set()
128+
mod_files = set()
129+
lines = patch.raw_patch.split("\n")
130+
131+
# Walk lines, skip last since it doesn't have next
132+
for i, line in enumerate(lines[:-1]):
133+
next_line = lines[i + 1]
134+
135+
if not next_line.startswith("+++ b/"):
136+
continue
137+
if 'tools/testing/selftests/' not in next_line:
138+
continue
139+
140+
file_path = next_line[6:]
141+
142+
if line == "--- /dev/null":
143+
new_files.add(file_path)
144+
else:
145+
mod_files.add(file_path)
146+
147+
# We're testing a series, same file may appear multiple times
148+
mod_files -= new_files
149+
return list(new_files), list(mod_files)
150+
151+
152+
def check_selftest(tree, patch, result_dir) -> Tuple[int, str, str]:
153+
""" Main function / entry point """
154+
155+
# Check for new files in the series
156+
new_files, mod_files = extract_files(patch)
157+
158+
ret = (0, "")
159+
log = ["New files:"] + new_files + ["", "Modified files:"] + mod_files + [""]
160+
161+
if not new_files and not mod_files:
162+
ret = (0, "No changes to selftests")
163+
else:
164+
nret = check_file_formats(tree, new_files + mod_files, log, result_dir)
165+
ret = ret_merge(ret, nret)
166+
167+
if new_files:
168+
nret = check_new_files_makefile(tree, new_files, log)
169+
ret = ret_merge(ret, nret)
170+
171+
nret = check_new_files_gitignore(tree, new_files, log)
172+
ret = ret_merge(ret, nret)
173+
174+
if not ret[0] and not ret[1]:
175+
ret = (0, f"New files {len(new_files)}, modified {len(mod_files)}, no checks")
176+
177+
return ret[0], ret[1], "\n".join(log)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: GPL-2.0
3+
4+
import sys
5+
6+
7+
def extract_key(raw):
8+
k = raw.split("=")[0]
9+
k = k.strip()
10+
k = k.replace('_', '')
11+
return k
12+
13+
14+
def check_one(a, b, line):
15+
_a = extract_key(a)
16+
_b = extract_key(b)
17+
18+
if _a >= _b:
19+
return None
20+
21+
return f"Lines {line}-{line+1} invalid order, {a} should be after {b}"
22+
23+
24+
def validate_config(file_path):
25+
"""Validate a Makefile for proper variable assignment format."""
26+
27+
with open(file_path, "r", encoding="utf-8") as f:
28+
content = f.read()
29+
30+
lines = content.split("\n")
31+
32+
all_errors = []
33+
34+
prev = ""
35+
for i, line in enumerate(lines):
36+
# ignore comments
37+
if line.strip().startswith('#'):
38+
continue
39+
# ignore bad lines
40+
if "=" not in line:
41+
continue
42+
if not prev:
43+
prev = line
44+
continue
45+
46+
err = check_one(line, prev, i)
47+
if err:
48+
all_errors.append(err)
49+
50+
prev = line
51+
52+
if all_errors:
53+
print(f"Validation errors in {file_path}:")
54+
for error in all_errors:
55+
print(error)
56+
return False
57+
58+
print(f"✓ {file_path} is properly formatted")
59+
return True
60+
61+
62+
def fix(file_path):
63+
"""Fix the config file by sorting entries alphabetically."""
64+
65+
with open(file_path, "r", encoding="utf-8") as f:
66+
content = f.read()
67+
68+
lines = content.split("\n")
69+
70+
output = []
71+
72+
while lines:
73+
idx = 0
74+
first = lines[0]
75+
for i, line in enumerate(lines):
76+
# ignore comments
77+
if line.strip().startswith('#'):
78+
continue
79+
# ignore bad lines
80+
if "=" not in line:
81+
continue
82+
83+
err = check_one(line, first, i)
84+
if err:
85+
first = line
86+
idx = i
87+
output.append(first)
88+
lines.pop(idx)
89+
90+
# Write the fixed content back to the file
91+
with open(file_path, "w", encoding="utf-8") as f:
92+
f.write("\n".join(output))
93+
94+
print(f"✓ Fixed {file_path} - config entries sorted alphabetically")
95+
96+
97+
def main():
98+
"""Main entry point for the script."""
99+
if len(sys.argv) < 2:
100+
print("Usage: validate_config_format.py <config_path>")
101+
sys.exit(1)
102+
103+
file_path = sys.argv[1]
104+
if file_path == "--fix":
105+
file_path = sys.argv[2]
106+
107+
code = 0
108+
if not validate_config(file_path):
109+
code = 1
110+
if sys.argv[1] == "--fix":
111+
fix(file_path)
112+
113+
sys.exit(code)
114+
115+
116+
if __name__ == "__main__":
117+
main()

0 commit comments

Comments
 (0)