Skip to content

Commit 7079296

Browse files
committed
tests/selftests: add selftest script
Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
1 parent 47f7ed5 commit 7079296

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

tests/selftests/selftest

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Selftest script for patchtest2
4+
5+
This script runs patchtest against all files in tests/selftests/files/
6+
and compares the results against expected outcomes based on file extensions:
7+
- .pass files should PASS (reported as XPASS)
8+
- .fail files should FAIL (reported as XFAIL)
9+
- .skip files should SKIP (reported as XSKIP)
10+
"""
11+
12+
import os
13+
import sys
14+
import subprocess
15+
import glob
16+
from pathlib import Path
17+
from collections import defaultdict
18+
19+
def get_expected_result(filename):
20+
"""Determine expected result based on file extension"""
21+
if filename.endswith('.pass'):
22+
return 'PASS'
23+
elif filename.endswith('.fail'):
24+
return 'FAIL'
25+
elif filename.endswith('.skip'):
26+
return 'SKIP'
27+
else:
28+
return 'UNKNOWN'
29+
30+
def extract_test_name(filename):
31+
"""Extract test name from filename, removing extension and numbering"""
32+
basename = os.path.basename(filename)
33+
# Remove extension (.pass, .fail, .skip)
34+
name_parts = basename.split('.')
35+
if len(name_parts) > 1 and name_parts[-1] in ['pass', 'fail', 'skip']:
36+
# Remove the result extension
37+
name_parts = name_parts[:-1]
38+
# If there's a number before the extension, remove it too
39+
if len(name_parts) > 1 and name_parts[-1].isdigit():
40+
name_parts = name_parts[:-1]
41+
42+
# Return the test function name directly from the filename
43+
return '.'.join(name_parts)
44+
45+
def run_patchtest(mbox_file):
46+
"""Run patchtest2 on a single mbox file and return results"""
47+
try:
48+
# Run patchtest with --patch flag and capture output
49+
result = subprocess.run(
50+
['patchtest', '--patch', mbox_file],
51+
capture_output=True,
52+
text=True,
53+
timeout=30
54+
)
55+
56+
# Parse the output to extract test results
57+
test_results = {}
58+
59+
if result.returncode == 0 or result.stdout:
60+
# Parse output lines
61+
lines = result.stdout.strip().split('\n')
62+
for line in lines:
63+
line = line.strip()
64+
if not line:
65+
continue
66+
67+
# Parse format: "RESULT: test_name on [PATCH] subject (optional reason)"
68+
if line.startswith(('PASS:', 'FAIL:', 'SKIP:')):
69+
parts = line.split(':', 1)
70+
if len(parts) >= 2:
71+
test_result = parts[0].strip() # PASS, FAIL, or SKIP
72+
rest = parts[1].strip()
73+
74+
# Extract test name (everything before " on [PATCH]")
75+
if ' on [PATCH]' in rest:
76+
test_name = rest.split(' on [PATCH]')[0].strip()
77+
test_results[test_name] = test_result
78+
else:
79+
# Fallback: use first word as test name
80+
test_name = rest.split()[0] if rest.split() else 'UNKNOWN'
81+
test_results[test_name] = test_result
82+
83+
# Handle error cases
84+
if not test_results:
85+
if result.stderr:
86+
test_results['ERROR'] = 'ERROR'
87+
elif result.returncode != 0:
88+
test_results['UNKNOWN'] = 'FAIL'
89+
else:
90+
test_results['NO_OUTPUT'] = 'ERROR'
91+
92+
return test_results
93+
94+
except subprocess.TimeoutExpired:
95+
return {'TIMEOUT': 'ERROR'}
96+
except Exception as e:
97+
return {'EXCEPTION': 'ERROR'}
98+
99+
def map_result_to_x_result(actual, expected):
100+
"""Map actual result to X-result based on expectation"""
101+
if expected == 'PASS' and actual == 'PASS':
102+
return 'XPASS'
103+
elif expected == 'FAIL' and actual == 'FAIL':
104+
return 'XFAIL'
105+
elif expected == 'SKIP' and actual == 'SKIP':
106+
return 'XSKIP'
107+
elif actual == 'PASS':
108+
return 'PASS'
109+
elif actual == 'FAIL':
110+
return 'FAIL'
111+
elif actual == 'SKIP':
112+
return 'SKIP'
113+
else:
114+
return 'ERROR'
115+
116+
def main():
117+
# Find the tests directory
118+
script_dir = Path(__file__).parent
119+
test_files_dir = script_dir / 'files'
120+
121+
if not test_files_dir.exists():
122+
print(f"Error: Test files directory not found: {test_files_dir}")
123+
sys.exit(1)
124+
125+
# Find all test files
126+
test_files = []
127+
for ext in ['*.pass', '*.fail', '*.skip']:
128+
test_files.extend(glob.glob(str(test_files_dir / ext)))
129+
130+
if not test_files:
131+
print(f"Error: No test files found in {test_files_dir}")
132+
sys.exit(1)
133+
134+
# Sort files for consistent output
135+
test_files.sort()
136+
137+
# Track results
138+
results = []
139+
counters = defaultdict(int)
140+
141+
print("Running selftests...")
142+
143+
for test_file in test_files:
144+
expected = get_expected_result(test_file)
145+
test_name = extract_test_name(test_file)
146+
filename = os.path.basename(test_file)
147+
148+
# Run patchtest on this file
149+
test_results = run_patchtest(test_file)
150+
151+
# Find the specific test result for this file's test
152+
if test_results and test_name in test_results:
153+
actual_result = test_results[test_name]
154+
x_result = map_result_to_x_result(actual_result, expected)
155+
156+
# Format output line
157+
output_line = f"{x_result}: {test_name} (file: {filename})"
158+
results.append(output_line)
159+
counters[x_result] += 1
160+
else:
161+
# Handle case where the specific test was not found in results
162+
x_result = 'ERROR'
163+
output_line = f"{x_result}: {test_name} (file: {filename}) - Test not found in patchtest output"
164+
results.append(output_line)
165+
counters[x_result] += 1
166+
167+
# Print all results
168+
for result in results:
169+
print(result)
170+
171+
# Print summary
172+
total = sum(counters.values())
173+
print("\n" + "=" * 76)
174+
print(f"{'Testsuite summary for patchtest':^76}")
175+
print("=" * 76)
176+
print(f"# TOTAL: {total}")
177+
print(f"# XPASS: {counters['XPASS']}")
178+
print(f"# XFAIL: {counters['XFAIL']}")
179+
print(f"# XSKIP: {counters['XSKIP']}")
180+
print(f"# PASS: {counters['PASS']}")
181+
print(f"# FAIL: {counters['FAIL']}")
182+
print(f"# SKIP: {counters['SKIP']}")
183+
print(f"# ERROR: {counters['ERROR']}")
184+
print("=" * 76)
185+
186+
# Return appropriate exit code
187+
if counters['FAIL'] > 0 or counters['ERROR'] > 0:
188+
sys.exit(1)
189+
else:
190+
sys.exit(0)
191+
192+
if __name__ == '__main__':
193+
main()

0 commit comments

Comments
 (0)