Skip to content

Commit 6f836a3

Browse files
authored
Merge pull request #12 from gilles-peskine-arm/mbedtls-run-tests
New script to run a subset of unit tests based on their description
2 parents 8e48184 + c92f594 commit 6f836a3

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

tools/bin/mbedtls-run-tests

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env python3
2+
3+
"""Run Mbed TLS unit tests.
4+
5+
By default, run all test cases. If at least one of --prefix, --regex or
6+
--substring is specified, only run the test cases that match any of these
7+
options.
8+
"""
9+
10+
import argparse
11+
import os
12+
import re
13+
import subprocess
14+
import tempfile
15+
16+
17+
class TestCaseFilter:
18+
"""Test case filter."""
19+
20+
def __init__(self, options):
21+
"""Set up a test case filter.
22+
23+
See `main` for what options are valid.
24+
"""
25+
include = ''
26+
for string in options.substring:
27+
include += '|.*' + re.escape(string)
28+
for string in options.prefix:
29+
include += '|' + re.escape(string)
30+
for regex in options.regex:
31+
include += '|.*' + regex
32+
if include:
33+
include = include[1:] # remove the leading '|'
34+
self.include = re.compile(include.encode())
35+
if options.exclude:
36+
exclude = '|'.join(options.exclude)
37+
else:
38+
exclude = r'.\A' # matches nothing
39+
self.exclude = re.compile(exclude.encode())
40+
41+
def match(self, description):
42+
"""Whether the given test case description matches the filter."""
43+
return (re.match(self.include, description) and
44+
not re.match(self.exclude, description))
45+
46+
47+
def extract_description(stanza):
48+
"""Extract the description from a .data stanza."""
49+
m = re.match(r'(?:\n|#[^\n]*\n)*([^#\n][^\n]*)\n', stanza + '\n')
50+
if m:
51+
return m.group(1)
52+
else:
53+
return None
54+
55+
def filter_test_cases(all_datax, tcf, temp_datax):
56+
"""Filter test cases.
57+
58+
Filter test cases from datax_file based on their description according
59+
to the filter tcf. Write the selected test cases to temp_datax.
60+
"""
61+
in_stanza = False
62+
in_match = False
63+
for line in open(all_datax, 'rb'):
64+
was_in_stanza = in_stanza
65+
in_stanza = not not line.strip()
66+
if in_stanza and not was_in_stanza:
67+
in_match = tcf.match(line)
68+
elif not in_stanza:
69+
in_match = False
70+
if in_match:
71+
temp_datax.write(line)
72+
temp_datax.flush()
73+
74+
def run_exe(keep_temp, precommand, exe, extra_options, all_datax, tcf):
75+
"""Run one Mbed TLS test suites based on the specified options.
76+
77+
Return the subprocess's exit status (should be 0 for success, 1 for
78+
a test failure, 2 on operational error), or a negative code if it was
79+
killed by a signal.
80+
81+
See `main` for what options are valid.
82+
"""
83+
directory = os.path.dirname(exe)
84+
with tempfile.NamedTemporaryFile(
85+
dir=directory, suffix='.datax', delete=not keep_temp
86+
) as temp_datax:
87+
filter_test_cases(all_datax, tcf, temp_datax)
88+
print(directory,
89+
os.path.basename(exe),
90+
os.path.basename(temp_datax.name))
91+
cmd = (precommand +
92+
[os.path.join(os.path.curdir, os.path.basename(exe))] +
93+
extra_options +
94+
[os.path.basename(temp_datax.name)])
95+
outcome = subprocess.run(cmd, cwd=directory)
96+
return outcome.returncode
97+
98+
def find_data_file(exe):
99+
"""Return the .datax file for the specified test suite executable."""
100+
directory = os.path.dirname(exe)
101+
basename = os.path.basename(exe)
102+
basebasename = os.path.splitext(basename)[0]
103+
for datax_file in [
104+
os.path.join(directory, basename + '.datax'),
105+
os.path.join(directory, basebasename + '.datax'),
106+
]:
107+
if os.path.exists(datax_file):
108+
return datax_file
109+
else:
110+
raise Exception('.datax file not found for ' + exe)
111+
112+
def run(options):
113+
"""Run Mbed TLS test suites based on the specified options.
114+
115+
See `main` for what options are valid.
116+
"""
117+
extra_options = []
118+
if options.verbose:
119+
extra_options.append('-v')
120+
tcf = TestCaseFilter(options)
121+
# Make sure we can find all the data files before we start running tests.
122+
data_files = [find_data_file(exe) for exe in options.exes]
123+
global_status = 0
124+
for exe, data_file in zip(options.exes, data_files):
125+
status = run_exe(options.keep,
126+
options.command, exe, extra_options,
127+
data_file, tcf)
128+
if status > 0: # test failure or operational error
129+
global_status = max(global_status, status)
130+
elif status < 0: # killed by a signal
131+
global_status = max(global_status, 120)
132+
return global_status
133+
134+
def main():
135+
"""Process the command line and run Mbed TLS tests."""
136+
parser = argparse.ArgumentParser(description=__doc__)
137+
parser.add_argument('--command', '-c',
138+
action='append', default=[],
139+
help='Precommand modifier (run COMMAND EXE instead of just EXE)')
140+
parser.add_argument('--exclude', '-e',
141+
action='append', default=[],
142+
help='Exclude tests whose description contains a match for the specified Python regex')
143+
parser.add_argument('--keep', '-k',
144+
action='store_true', default=False,
145+
help='Keep the temporary .datax files')
146+
parser.add_argument('--prefix', '-p',
147+
action='append', default=[],
148+
help='Only run tests whose description starts with the specified string')
149+
parser.add_argument('--regex', '-r',
150+
action='append', default=[],
151+
help='Only run tests whose description contains a match for the specified Python regex')
152+
parser.add_argument('--substring', '-s',
153+
action='append', default=[],
154+
help='Only run tests whose description contains the specified substring')
155+
parser.add_argument('--verbose', '-v',
156+
action='store_true',
157+
help='Run test suites in verbose mode')
158+
parser.add_argument(metavar='EXE', nargs='+',
159+
dest='exes',
160+
help='Test suite executable')
161+
options = parser.parse_args()
162+
exit(run(options))
163+
164+
if __name__ == '__main__':
165+
main()

0 commit comments

Comments
 (0)