blob: 53af0a5240b11450b58ecfc315714a43009a3e1b [file] [log] [blame]
Gilles Peskine24827022018-09-25 18:49:23 +02001#!/usr/bin/env python3
Gilles Peskinea3b93ff2019-06-03 11:23:56 +02002"""Test the program psa_constant_names.
Gilles Peskine24827022018-09-25 18:49:23 +02003Gather constant names from header files and test cases. Compile a C program
4to print out their numerical values, feed these numerical values to
5psa_constant_names, and check that the output is the original name.
6Return 0 if all test cases pass, 1 if the output was not always as expected,
Gilles Peskinea3b93ff2019-06-03 11:23:56 +02007or 1 (with a Python backtrace) if there was an operational error.
8"""
Gilles Peskine24827022018-09-25 18:49:23 +02009
10import argparse
11import itertools
12import os
13import platform
14import re
15import subprocess
16import sys
17import tempfile
18
Gilles Peskinea0a315c2018-10-19 11:27:10 +020019class ReadFileLineException(Exception):
20 def __init__(self, filename, line_number):
21 message = 'in {} at {}'.format(filename, line_number)
22 super(ReadFileLineException, self).__init__(message)
23 self.filename = filename
24 self.line_number = line_number
25
26class read_file_lines:
Gilles Peskine54f54452019-05-27 18:31:59 +020027 # Dear Pylint, conventionally, a context manager class name is lowercase.
28 # pylint: disable=invalid-name,too-few-public-methods
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020029 """Context manager to read a text file line by line.
30
31 ```
32 with read_file_lines(filename) as lines:
33 for line in lines:
34 process(line)
35 ```
36 is equivalent to
37 ```
38 with open(filename, 'r') as input_file:
39 for line in input_file:
40 process(line)
41 ```
42 except that if process(line) raises an exception, then the read_file_lines
43 snippet annotates the exception with the file name and line number.
44 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +020045 def __init__(self, filename):
46 self.filename = filename
47 self.line_number = 'entry'
Gilles Peskine54f54452019-05-27 18:31:59 +020048 self.generator = None
Gilles Peskinea0a315c2018-10-19 11:27:10 +020049 def __enter__(self):
50 self.generator = enumerate(open(self.filename, 'r'))
51 return self
52 def __iter__(self):
53 for line_number, content in self.generator:
54 self.line_number = line_number
55 yield content
56 self.line_number = 'exit'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020057 def __exit__(self, exc_type, exc_value, exc_traceback):
58 if exc_type is not None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020059 raise ReadFileLineException(self.filename, self.line_number) \
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020060 from exc_value
Gilles Peskinea0a315c2018-10-19 11:27:10 +020061
Gilles Peskine24827022018-09-25 18:49:23 +020062class Inputs:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020063 """Accumulate information about macros to test.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010064
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020065 This includes macro names as well as information about their arguments
66 when applicable.
67 """
68
Gilles Peskine24827022018-09-25 18:49:23 +020069 def __init__(self):
70 # Sets of names per type
71 self.statuses = set(['PSA_SUCCESS'])
72 self.algorithms = set(['0xffffffff'])
73 self.ecc_curves = set(['0xffff'])
Gilles Peskinedcaefae2019-05-16 12:55:35 +020074 self.dh_groups = set(['0xffff'])
Gilles Peskine24827022018-09-25 18:49:23 +020075 self.key_types = set(['0xffffffff'])
76 self.key_usage_flags = set(['0x80000000'])
Gilles Peskine434899f2018-10-19 11:30:26 +020077 # Hard-coded value for unknown algorithms
Darryl Green61b7f612019-02-04 16:00:21 +000078 self.hash_algorithms = set(['0x010000fe'])
Gilles Peskine434899f2018-10-19 11:30:26 +020079 self.mac_algorithms = set(['0x02ff00ff'])
Gilles Peskine882e57e2019-04-12 00:12:07 +020080 self.ka_algorithms = set(['0x30fc0000'])
81 self.kdf_algorithms = set(['0x200000ff'])
Gilles Peskine434899f2018-10-19 11:30:26 +020082 # For AEAD algorithms, the only variability is over the tag length,
83 # and this only applies to known algorithms, so don't test an
84 # unknown algorithm.
85 self.aead_algorithms = set()
Gilles Peskine24827022018-09-25 18:49:23 +020086 # Identifier prefixes
87 self.table_by_prefix = {
88 'ERROR': self.statuses,
89 'ALG': self.algorithms,
90 'CURVE': self.ecc_curves,
Gilles Peskinedcaefae2019-05-16 12:55:35 +020091 'GROUP': self.dh_groups,
Gilles Peskine24827022018-09-25 18:49:23 +020092 'KEY_TYPE': self.key_types,
93 'KEY_USAGE': self.key_usage_flags,
94 }
95 # macro name -> list of argument names
96 self.argspecs = {}
97 # argument name -> list of values
Gilles Peskine434899f2018-10-19 11:30:26 +020098 self.arguments_for = {
99 'mac_length': ['1', '63'],
100 'tag_length': ['1', '63'],
101 }
Gilles Peskine24827022018-09-25 18:49:23 +0200102
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100103 def get_names(self, type_word):
104 """Return the set of known names of values of the given type."""
105 return {
106 'status': self.statuses,
107 'algorithm': self.algorithms,
108 'ecc_curve': self.ecc_curves,
109 'dh_group': self.dh_groups,
110 'key_type': self.key_types,
111 'key_usage': self.key_usage_flags,
112 }[type_word]
113
Gilles Peskine24827022018-09-25 18:49:23 +0200114 def gather_arguments(self):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200115 """Populate the list of values for macro arguments.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100116
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200117 Call this after parsing all the inputs.
118 """
Gilles Peskine24827022018-09-25 18:49:23 +0200119 self.arguments_for['hash_alg'] = sorted(self.hash_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200120 self.arguments_for['mac_alg'] = sorted(self.mac_algorithms)
Gilles Peskine882e57e2019-04-12 00:12:07 +0200121 self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
Gilles Peskine17542082019-01-04 19:46:31 +0100122 self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200123 self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
Gilles Peskine24827022018-09-25 18:49:23 +0200124 self.arguments_for['curve'] = sorted(self.ecc_curves)
Gilles Peskinedcaefae2019-05-16 12:55:35 +0200125 self.arguments_for['group'] = sorted(self.dh_groups)
Gilles Peskine24827022018-09-25 18:49:23 +0200126
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200127 @staticmethod
128 def _format_arguments(name, arguments):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200129 """Format a macro call with arguments.."""
Gilles Peskine24827022018-09-25 18:49:23 +0200130 return name + '(' + ', '.join(arguments) + ')'
131
132 def distribute_arguments(self, name):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200133 """Generate macro calls with each tested argument set.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100134
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200135 If name is a macro without arguments, just yield "name".
136 If name is a macro with arguments, yield a series of
137 "name(arg1,...,argN)" where each argument takes each possible
138 value at least once.
139 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200140 try:
141 if name not in self.argspecs:
142 yield name
143 return
144 argspec = self.argspecs[name]
145 if argspec == []:
146 yield name + '()'
147 return
148 argument_lists = [self.arguments_for[arg] for arg in argspec]
149 arguments = [values[0] for values in argument_lists]
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200150 yield self._format_arguments(name, arguments)
Gilles Peskine54f54452019-05-27 18:31:59 +0200151 # Dear Pylint, enumerate won't work here since we're modifying
152 # the array.
153 # pylint: disable=consider-using-enumerate
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200154 for i in range(len(arguments)):
155 for value in argument_lists[i][1:]:
156 arguments[i] = value
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200157 yield self._format_arguments(name, arguments)
Gilles Peskinef96ed662018-10-19 11:29:56 +0200158 arguments[i] = argument_lists[0][0]
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200159 except BaseException as e:
160 raise Exception('distribute_arguments({})'.format(name)) from e
Gilles Peskine24827022018-09-25 18:49:23 +0200161
Gilles Peskine5a994c12019-11-21 16:46:51 +0100162 def generate_expressions(self, names):
163 return itertools.chain(*map(self.distribute_arguments, names))
164
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200165 _argument_split_re = re.compile(r' *, *')
166 @classmethod
167 def _argument_split(cls, arguments):
168 return re.split(cls._argument_split_re, arguments)
169
Gilles Peskine24827022018-09-25 18:49:23 +0200170 # Regex for interesting header lines.
171 # Groups: 1=macro name, 2=type, 3=argument list (optional).
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200172 _header_line_re = \
Gilles Peskine24827022018-09-25 18:49:23 +0200173 re.compile(r'#define +' +
174 r'(PSA_((?:KEY_)?[A-Z]+)_\w+)' +
175 r'(?:\(([^\n()]*)\))?')
176 # Regex of macro names to exclude.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200177 _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
Gilles Peskinec68ce962018-10-19 11:31:52 +0200178 # Additional excluded macros.
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200179 _excluded_names = set([
180 # Macros that provide an alternative way to build the same
181 # algorithm as another macro.
182 'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH',
183 'PSA_ALG_FULL_LENGTH_MAC',
184 # Auxiliary macro whose name doesn't fit the usual patterns for
185 # auxiliary macros.
186 'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH_CASE',
187 # PSA_ALG_ECDH and PSA_ALG_FFDH are excluded for now as the script
188 # currently doesn't support them.
189 'PSA_ALG_ECDH',
190 'PSA_ALG_FFDH',
191 # Deprecated aliases.
192 'PSA_ERROR_UNKNOWN_ERROR',
193 'PSA_ERROR_OCCUPIED_SLOT',
194 'PSA_ERROR_EMPTY_SLOT',
195 'PSA_ERROR_INSUFFICIENT_CAPACITY',
Gilles Peskine19835122019-05-17 12:06:55 +0200196 'PSA_ERROR_TAMPERING_DETECTED',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200197 ])
Gilles Peskine24827022018-09-25 18:49:23 +0200198 def parse_header_line(self, line):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200199 """Parse a C header line, looking for "#define PSA_xxx"."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200200 m = re.match(self._header_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200201 if not m:
202 return
203 name = m.group(1)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200204 if re.search(self._excluded_name_re, name) or \
205 name in self._excluded_names:
Gilles Peskine24827022018-09-25 18:49:23 +0200206 return
207 dest = self.table_by_prefix.get(m.group(2))
208 if dest is None:
209 return
210 dest.add(name)
211 if m.group(3):
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200212 self.argspecs[name] = self._argument_split(m.group(3))
Gilles Peskine24827022018-09-25 18:49:23 +0200213
214 def parse_header(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200215 """Parse a C header file, looking for "#define PSA_xxx"."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200216 with read_file_lines(filename) as lines:
217 for line in lines:
Gilles Peskine24827022018-09-25 18:49:23 +0200218 self.parse_header_line(line)
219
220 def add_test_case_line(self, function, argument):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200221 """Parse a test case data line, looking for algorithm metadata tests."""
Gilles Peskine24827022018-09-25 18:49:23 +0200222 if function.endswith('_algorithm'):
Darryl Greenb8fe0682019-02-06 13:21:31 +0000223 # As above, ECDH and FFDH algorithms are excluded for now.
224 # Support for them will be added in the future.
Darryl Greenec079502019-01-29 15:48:00 +0000225 if 'ECDH' in argument or 'FFDH' in argument:
226 return
Gilles Peskine24827022018-09-25 18:49:23 +0200227 self.algorithms.add(argument)
228 if function == 'hash_algorithm':
229 self.hash_algorithms.add(argument)
Gilles Peskine434899f2018-10-19 11:30:26 +0200230 elif function in ['mac_algorithm', 'hmac_algorithm']:
231 self.mac_algorithms.add(argument)
232 elif function == 'aead_algorithm':
233 self.aead_algorithms.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200234 elif function == 'key_type':
235 self.key_types.add(argument)
236 elif function == 'ecc_key_types':
237 self.ecc_curves.add(argument)
Gilles Peskinedcaefae2019-05-16 12:55:35 +0200238 elif function == 'dh_key_types':
239 self.dh_groups.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200240
241 # Regex matching a *.data line containing a test function call and
242 # its arguments. The actual definition is partly positional, but this
243 # regex is good enough in practice.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200244 _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
Gilles Peskine24827022018-09-25 18:49:23 +0200245 def parse_test_cases(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200246 """Parse a test case file (*.data), looking for algorithm metadata tests."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200247 with read_file_lines(filename) as lines:
248 for line in lines:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200249 m = re.match(self._test_case_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200250 if m:
251 self.add_test_case_line(m.group(1), m.group(2))
252
253def gather_inputs(headers, test_suites):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200254 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine24827022018-09-25 18:49:23 +0200255 inputs = Inputs()
256 for header in headers:
257 inputs.parse_header(header)
258 for test_cases in test_suites:
259 inputs.parse_test_cases(test_cases)
260 inputs.gather_arguments()
261 return inputs
262
263def remove_file_if_exists(filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200264 """Remove the specified file, ignoring errors."""
Gilles Peskine24827022018-09-25 18:49:23 +0200265 if not filename:
266 return
267 try:
268 os.remove(filename)
Gilles Peskine54f54452019-05-27 18:31:59 +0200269 except OSError:
Gilles Peskine24827022018-09-25 18:49:23 +0200270 pass
271
Gilles Peskine5a994c12019-11-21 16:46:51 +0100272def run_c(options, type_word, expressions):
273 """Generate and run a program to print out numerical values for expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200274 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100275 cast_to = 'long'
276 printf_format = '%ld'
277 else:
278 cast_to = 'unsigned long'
279 printf_format = '0x%08lx'
Gilles Peskine24827022018-09-25 18:49:23 +0200280 c_name = None
281 exe_name = None
282 try:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200283 c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(type_word),
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100284 suffix='.c',
Gilles Peskine24827022018-09-25 18:49:23 +0200285 dir='programs/psa')
286 exe_suffix = '.exe' if platform.system() == 'Windows' else ''
287 exe_name = c_name[:-2] + exe_suffix
288 remove_file_if_exists(exe_name)
289 c_file = os.fdopen(c_fd, 'w', encoding='ascii')
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100290 c_file.write('/* Generated by test_psa_constant_names.py for {} values */'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200291 .format(type_word))
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100292 c_file.write('''
Gilles Peskine24827022018-09-25 18:49:23 +0200293#include <stdio.h>
294#include <psa/crypto.h>
295int main(void)
296{
297''')
Gilles Peskine5a994c12019-11-21 16:46:51 +0100298 for expr in expressions:
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100299 c_file.write(' printf("{}\\n", ({}) {});\n'
Gilles Peskine5a994c12019-11-21 16:46:51 +0100300 .format(printf_format, cast_to, expr))
Gilles Peskine24827022018-09-25 18:49:23 +0200301 c_file.write(''' return 0;
302}
303''')
304 c_file.close()
305 cc = os.getenv('CC', 'cc')
306 subprocess.check_call([cc] +
307 ['-I' + dir for dir in options.include] +
308 ['-o', exe_name, c_name])
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200309 if options.keep_c:
310 sys.stderr.write('List of {} tests kept at {}\n'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200311 .format(type_word, c_name))
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200312 else:
313 os.remove(c_name)
Gilles Peskine24827022018-09-25 18:49:23 +0200314 output = subprocess.check_output([exe_name])
315 return output.decode('ascii').strip().split('\n')
316 finally:
317 remove_file_if_exists(exe_name)
318
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200319NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine24827022018-09-25 18:49:23 +0200320def normalize(expr):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200321 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100322
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200323 Currently "trivial differences" means whitespace.
324 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +0100325 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +0200326
Gilles Peskinec2317112019-11-21 17:17:39 +0100327def collect_values(options, inputs, type_word):
328 """Generate expressions using known macro names and calculate their values.
329
330 Return a list of pairs of (expr, value) where expr is an expression and
331 value is a string representation of its integer value.
332 """
333 names = inputs.get_names(type_word)
334 expressions = sorted(inputs.generate_expressions(names))
335 values = run_c(options, type_word, expressions)
336 return expressions, values
337
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100338def do_test(options, inputs, type_word):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200339 """Test psa_constant_names for the specified type.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100340
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200341 Run program on names.
342 Use inputs to figure out what arguments to pass to macros that
343 take arguments.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100344
345 Return ``(count, errors)`` where ``count`` is the number of expressions
346 that have been tested and ``errors`` is the list of errors that were
347 encountered.
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200348 """
Gilles Peskinec2317112019-11-21 17:17:39 +0100349 expressions, values = collect_values(options, inputs, type_word)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200350 output = subprocess.check_output([options.program, type_word] + values)
Gilles Peskine24827022018-09-25 18:49:23 +0200351 outputs = output.decode('ascii').strip().split('\n')
Gilles Peskine5a994c12019-11-21 16:46:51 +0100352 errors = [(type_word, expr, value, output)
353 for (expr, value, output) in zip(expressions, values, outputs)
354 if normalize(expr) != normalize(output)]
355 return len(expressions), errors
Gilles Peskine24827022018-09-25 18:49:23 +0200356
357def report_errors(errors):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200358 """Describe each case where the output is not as expected."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200359 for type_word, name, value, output in errors:
Gilles Peskine24827022018-09-25 18:49:23 +0200360 print('For {} "{}", got "{}" (value: {})'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200361 .format(type_word, name, output, value))
Gilles Peskine24827022018-09-25 18:49:23 +0200362
363def run_tests(options, inputs):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200364 """Run psa_constant_names on all the gathered inputs.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100365
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200366 Return a tuple (count, errors) where count is the total number of inputs
367 that were tested and errors is the list of cases where the output was
368 not as expected.
369 """
Gilles Peskine24827022018-09-25 18:49:23 +0200370 count = 0
371 errors = []
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100372 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
373 'key_type', 'key_usage']:
374 c, e = do_test(options, inputs, type_word)
Gilles Peskine24827022018-09-25 18:49:23 +0200375 count += c
376 errors += e
377 return count, errors
378
Gilles Peskine69f93b52019-11-21 16:49:50 +0100379HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
380TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
381
Gilles Peskine54f54452019-05-27 18:31:59 +0200382def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200383 parser = argparse.ArgumentParser(description=globals()['__doc__'])
384 parser.add_argument('--include', '-I',
385 action='append', default=['include'],
386 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200387 parser.add_argument('--keep-c',
388 action='store_true', dest='keep_c', default=False,
389 help='Keep the intermediate C file')
390 parser.add_argument('--no-keep-c',
391 action='store_false', dest='keep_c',
392 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100393 parser.add_argument('--program',
394 default='programs/psa/psa_constant_names',
395 help='Program to test')
Gilles Peskine24827022018-09-25 18:49:23 +0200396 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100397 headers = [os.path.join(options.include[0], h) for h in HEADERS]
398 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24827022018-09-25 18:49:23 +0200399 count, errors = run_tests(options, inputs)
400 report_errors(errors)
401 if errors == []:
402 print('{} test cases PASS'.format(count))
403 else:
404 print('{} test cases, {} FAIL'.format(count, len(errors)))
405 exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200406
407if __name__ == '__main__':
408 main()