blob: 5b86b247da63afbaad11c3471676bf9997e1cd57 [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
Gilles Peskinea5000f12019-11-21 17:51:11 +010011from collections import namedtuple
Gilles Peskine24827022018-09-25 18:49:23 +020012import itertools
13import os
14import platform
15import re
16import subprocess
17import sys
18import tempfile
19
Gilles Peskinea0a315c2018-10-19 11:27:10 +020020class ReadFileLineException(Exception):
21 def __init__(self, filename, line_number):
22 message = 'in {} at {}'.format(filename, line_number)
23 super(ReadFileLineException, self).__init__(message)
24 self.filename = filename
25 self.line_number = line_number
26
27class read_file_lines:
Gilles Peskine54f54452019-05-27 18:31:59 +020028 # Dear Pylint, conventionally, a context manager class name is lowercase.
29 # pylint: disable=invalid-name,too-few-public-methods
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020030 """Context manager to read a text file line by line.
31
32 ```
33 with read_file_lines(filename) as lines:
34 for line in lines:
35 process(line)
36 ```
37 is equivalent to
38 ```
39 with open(filename, 'r') as input_file:
40 for line in input_file:
41 process(line)
42 ```
43 except that if process(line) raises an exception, then the read_file_lines
44 snippet annotates the exception with the file name and line number.
45 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +020046 def __init__(self, filename):
47 self.filename = filename
48 self.line_number = 'entry'
Gilles Peskine54f54452019-05-27 18:31:59 +020049 self.generator = None
Gilles Peskinea0a315c2018-10-19 11:27:10 +020050 def __enter__(self):
51 self.generator = enumerate(open(self.filename, 'r'))
52 return self
53 def __iter__(self):
54 for line_number, content in self.generator:
55 self.line_number = line_number
56 yield content
57 self.line_number = 'exit'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020058 def __exit__(self, exc_type, exc_value, exc_traceback):
59 if exc_type is not None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020060 raise ReadFileLineException(self.filename, self.line_number) \
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020061 from exc_value
Gilles Peskinea0a315c2018-10-19 11:27:10 +020062
Gilles Peskine24827022018-09-25 18:49:23 +020063class Inputs:
Gilles Peskine8c8694c2019-11-21 19:22:45 +010064 # pylint: disable=too-many-instance-attributes
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020065 """Accumulate information about macros to test.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010066
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020067 This includes macro names as well as information about their arguments
68 when applicable.
69 """
70
Gilles Peskine24827022018-09-25 18:49:23 +020071 def __init__(self):
72 # Sets of names per type
73 self.statuses = set(['PSA_SUCCESS'])
74 self.algorithms = set(['0xffffffff'])
75 self.ecc_curves = set(['0xffff'])
Gilles Peskinedcaefae2019-05-16 12:55:35 +020076 self.dh_groups = set(['0xffff'])
Gilles Peskine24827022018-09-25 18:49:23 +020077 self.key_types = set(['0xffffffff'])
78 self.key_usage_flags = set(['0x80000000'])
Gilles Peskine434899f2018-10-19 11:30:26 +020079 # Hard-coded value for unknown algorithms
Darryl Green61b7f612019-02-04 16:00:21 +000080 self.hash_algorithms = set(['0x010000fe'])
Gilles Peskine434899f2018-10-19 11:30:26 +020081 self.mac_algorithms = set(['0x02ff00ff'])
Gilles Peskine882e57e2019-04-12 00:12:07 +020082 self.ka_algorithms = set(['0x30fc0000'])
83 self.kdf_algorithms = set(['0x200000ff'])
Gilles Peskine434899f2018-10-19 11:30:26 +020084 # For AEAD algorithms, the only variability is over the tag length,
85 # and this only applies to known algorithms, so don't test an
86 # unknown algorithm.
87 self.aead_algorithms = set()
Gilles Peskine24827022018-09-25 18:49:23 +020088 # Identifier prefixes
89 self.table_by_prefix = {
90 'ERROR': self.statuses,
91 'ALG': self.algorithms,
92 'CURVE': self.ecc_curves,
Gilles Peskinedcaefae2019-05-16 12:55:35 +020093 'GROUP': self.dh_groups,
Gilles Peskine24827022018-09-25 18:49:23 +020094 'KEY_TYPE': self.key_types,
95 'KEY_USAGE': self.key_usage_flags,
96 }
Gilles Peskine8c8694c2019-11-21 19:22:45 +010097 # Test functions
98 self.table_by_test_function = {
99 'key_type': self.key_types,
100 'ecc_key_types': self.ecc_curves,
101 'dh_key_types': self.dh_groups,
102 'hash_algorithm': self.hash_algorithms,
103 'mac_algorithm': self.mac_algorithms,
104 'hmac_algorithm': self.mac_algorithms,
105 'aead_algorithm': self.aead_algorithms,
106 }
Gilles Peskine24827022018-09-25 18:49:23 +0200107 # macro name -> list of argument names
108 self.argspecs = {}
109 # argument name -> list of values
Gilles Peskine434899f2018-10-19 11:30:26 +0200110 self.arguments_for = {
111 'mac_length': ['1', '63'],
112 'tag_length': ['1', '63'],
113 }
Gilles Peskine24827022018-09-25 18:49:23 +0200114
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100115 def get_names(self, type_word):
116 """Return the set of known names of values of the given type."""
117 return {
118 'status': self.statuses,
119 'algorithm': self.algorithms,
120 'ecc_curve': self.ecc_curves,
121 'dh_group': self.dh_groups,
122 'key_type': self.key_types,
123 'key_usage': self.key_usage_flags,
124 }[type_word]
125
Gilles Peskine24827022018-09-25 18:49:23 +0200126 def gather_arguments(self):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200127 """Populate the list of values for macro arguments.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100128
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200129 Call this after parsing all the inputs.
130 """
Gilles Peskine24827022018-09-25 18:49:23 +0200131 self.arguments_for['hash_alg'] = sorted(self.hash_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200132 self.arguments_for['mac_alg'] = sorted(self.mac_algorithms)
Gilles Peskine882e57e2019-04-12 00:12:07 +0200133 self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
Gilles Peskine17542082019-01-04 19:46:31 +0100134 self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200135 self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
Gilles Peskine24827022018-09-25 18:49:23 +0200136 self.arguments_for['curve'] = sorted(self.ecc_curves)
Gilles Peskinedcaefae2019-05-16 12:55:35 +0200137 self.arguments_for['group'] = sorted(self.dh_groups)
Gilles Peskine24827022018-09-25 18:49:23 +0200138
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200139 @staticmethod
140 def _format_arguments(name, arguments):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200141 """Format a macro call with arguments.."""
Gilles Peskine24827022018-09-25 18:49:23 +0200142 return name + '(' + ', '.join(arguments) + ')'
143
144 def distribute_arguments(self, name):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200145 """Generate macro calls with each tested argument set.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100146
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200147 If name is a macro without arguments, just yield "name".
148 If name is a macro with arguments, yield a series of
149 "name(arg1,...,argN)" where each argument takes each possible
150 value at least once.
151 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200152 try:
153 if name not in self.argspecs:
154 yield name
155 return
156 argspec = self.argspecs[name]
157 if argspec == []:
158 yield name + '()'
159 return
160 argument_lists = [self.arguments_for[arg] for arg in argspec]
161 arguments = [values[0] for values in argument_lists]
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200162 yield self._format_arguments(name, arguments)
Gilles Peskine54f54452019-05-27 18:31:59 +0200163 # Dear Pylint, enumerate won't work here since we're modifying
164 # the array.
165 # pylint: disable=consider-using-enumerate
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200166 for i in range(len(arguments)):
167 for value in argument_lists[i][1:]:
168 arguments[i] = value
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200169 yield self._format_arguments(name, arguments)
Gilles Peskinef96ed662018-10-19 11:29:56 +0200170 arguments[i] = argument_lists[0][0]
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200171 except BaseException as e:
172 raise Exception('distribute_arguments({})'.format(name)) from e
Gilles Peskine24827022018-09-25 18:49:23 +0200173
Gilles Peskine5a994c12019-11-21 16:46:51 +0100174 def generate_expressions(self, names):
175 return itertools.chain(*map(self.distribute_arguments, names))
176
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200177 _argument_split_re = re.compile(r' *, *')
178 @classmethod
179 def _argument_split(cls, arguments):
180 return re.split(cls._argument_split_re, arguments)
181
Gilles Peskine24827022018-09-25 18:49:23 +0200182 # Regex for interesting header lines.
183 # Groups: 1=macro name, 2=type, 3=argument list (optional).
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200184 _header_line_re = \
Gilles Peskine24827022018-09-25 18:49:23 +0200185 re.compile(r'#define +' +
186 r'(PSA_((?:KEY_)?[A-Z]+)_\w+)' +
187 r'(?:\(([^\n()]*)\))?')
188 # Regex of macro names to exclude.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200189 _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
Gilles Peskinec68ce962018-10-19 11:31:52 +0200190 # Additional excluded macros.
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200191 _excluded_names = set([
192 # Macros that provide an alternative way to build the same
193 # algorithm as another macro.
194 'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH',
195 'PSA_ALG_FULL_LENGTH_MAC',
196 # Auxiliary macro whose name doesn't fit the usual patterns for
197 # auxiliary macros.
198 'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH_CASE',
199 # PSA_ALG_ECDH and PSA_ALG_FFDH are excluded for now as the script
200 # currently doesn't support them.
201 'PSA_ALG_ECDH',
202 'PSA_ALG_FFDH',
203 # Deprecated aliases.
204 'PSA_ERROR_UNKNOWN_ERROR',
205 'PSA_ERROR_OCCUPIED_SLOT',
206 'PSA_ERROR_EMPTY_SLOT',
207 'PSA_ERROR_INSUFFICIENT_CAPACITY',
Gilles Peskine19835122019-05-17 12:06:55 +0200208 'PSA_ERROR_TAMPERING_DETECTED',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200209 ])
Gilles Peskine24827022018-09-25 18:49:23 +0200210 def parse_header_line(self, line):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200211 """Parse a C header line, looking for "#define PSA_xxx"."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200212 m = re.match(self._header_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200213 if not m:
214 return
215 name = m.group(1)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200216 if re.search(self._excluded_name_re, name) or \
217 name in self._excluded_names:
Gilles Peskine24827022018-09-25 18:49:23 +0200218 return
219 dest = self.table_by_prefix.get(m.group(2))
220 if dest is None:
221 return
222 dest.add(name)
223 if m.group(3):
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200224 self.argspecs[name] = self._argument_split(m.group(3))
Gilles Peskine24827022018-09-25 18:49:23 +0200225
226 def parse_header(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200227 """Parse a C header file, looking for "#define PSA_xxx"."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200228 with read_file_lines(filename) as lines:
229 for line in lines:
Gilles Peskine24827022018-09-25 18:49:23 +0200230 self.parse_header_line(line)
231
232 def add_test_case_line(self, function, argument):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200233 """Parse a test case data line, looking for algorithm metadata tests."""
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100234 sets = []
Gilles Peskine24827022018-09-25 18:49:23 +0200235 if function.endswith('_algorithm'):
Darryl Greenb8fe0682019-02-06 13:21:31 +0000236 # As above, ECDH and FFDH algorithms are excluded for now.
237 # Support for them will be added in the future.
Darryl Greenec079502019-01-29 15:48:00 +0000238 if 'ECDH' in argument or 'FFDH' in argument:
239 return
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100240 sets.append(self.algorithms)
241 if function in self.table_by_test_function:
242 sets.append(self.table_by_test_function[function])
243 for s in sets:
244 s.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200245
246 # Regex matching a *.data line containing a test function call and
247 # its arguments. The actual definition is partly positional, but this
248 # regex is good enough in practice.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200249 _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
Gilles Peskine24827022018-09-25 18:49:23 +0200250 def parse_test_cases(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200251 """Parse a test case file (*.data), looking for algorithm metadata tests."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200252 with read_file_lines(filename) as lines:
253 for line in lines:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200254 m = re.match(self._test_case_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200255 if m:
256 self.add_test_case_line(m.group(1), m.group(2))
257
Gilles Peskine84a45812019-11-21 19:50:33 +0100258def gather_inputs(headers, test_suites, inputs_class=Inputs):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200259 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +0100260 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +0200261 for header in headers:
262 inputs.parse_header(header)
263 for test_cases in test_suites:
264 inputs.parse_test_cases(test_cases)
265 inputs.gather_arguments()
266 return inputs
267
268def remove_file_if_exists(filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200269 """Remove the specified file, ignoring errors."""
Gilles Peskine24827022018-09-25 18:49:23 +0200270 if not filename:
271 return
272 try:
273 os.remove(filename)
Gilles Peskine54f54452019-05-27 18:31:59 +0200274 except OSError:
Gilles Peskine24827022018-09-25 18:49:23 +0200275 pass
276
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100277def run_c(type_word, expressions, include_path=None, keep_c=False):
Gilles Peskine5a994c12019-11-21 16:46:51 +0100278 """Generate and run a program to print out numerical values for expressions."""
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100279 if include_path is None:
280 include_path = []
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200281 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100282 cast_to = 'long'
283 printf_format = '%ld'
284 else:
285 cast_to = 'unsigned long'
286 printf_format = '0x%08lx'
Gilles Peskine24827022018-09-25 18:49:23 +0200287 c_name = None
288 exe_name = None
289 try:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200290 c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(type_word),
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100291 suffix='.c',
Gilles Peskine24827022018-09-25 18:49:23 +0200292 dir='programs/psa')
293 exe_suffix = '.exe' if platform.system() == 'Windows' else ''
294 exe_name = c_name[:-2] + exe_suffix
295 remove_file_if_exists(exe_name)
296 c_file = os.fdopen(c_fd, 'w', encoding='ascii')
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100297 c_file.write('/* Generated by test_psa_constant_names.py for {} values */'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200298 .format(type_word))
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100299 c_file.write('''
Gilles Peskine24827022018-09-25 18:49:23 +0200300#include <stdio.h>
301#include <psa/crypto.h>
302int main(void)
303{
304''')
Gilles Peskine5a994c12019-11-21 16:46:51 +0100305 for expr in expressions:
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100306 c_file.write(' printf("{}\\n", ({}) {});\n'
Gilles Peskine5a994c12019-11-21 16:46:51 +0100307 .format(printf_format, cast_to, expr))
Gilles Peskine24827022018-09-25 18:49:23 +0200308 c_file.write(''' return 0;
309}
310''')
311 c_file.close()
312 cc = os.getenv('CC', 'cc')
313 subprocess.check_call([cc] +
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100314 ['-I' + dir for dir in include_path] +
Gilles Peskine24827022018-09-25 18:49:23 +0200315 ['-o', exe_name, c_name])
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100316 if keep_c:
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200317 sys.stderr.write('List of {} tests kept at {}\n'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200318 .format(type_word, c_name))
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200319 else:
320 os.remove(c_name)
Gilles Peskine24827022018-09-25 18:49:23 +0200321 output = subprocess.check_output([exe_name])
322 return output.decode('ascii').strip().split('\n')
323 finally:
324 remove_file_if_exists(exe_name)
325
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200326NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine24827022018-09-25 18:49:23 +0200327def normalize(expr):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200328 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100329
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200330 Currently "trivial differences" means whitespace.
331 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +0100332 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +0200333
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100334def collect_values(inputs, type_word, include_path=None, keep_c=False):
Gilles Peskinec2317112019-11-21 17:17:39 +0100335 """Generate expressions using known macro names and calculate their values.
336
337 Return a list of pairs of (expr, value) where expr is an expression and
338 value is a string representation of its integer value.
339 """
340 names = inputs.get_names(type_word)
341 expressions = sorted(inputs.generate_expressions(names))
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100342 values = run_c(type_word, expressions,
343 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +0100344 return expressions, values
345
Gilles Peskine24609332019-11-21 17:44:21 +0100346class Tests:
347 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100348
Gilles Peskinea5000f12019-11-21 17:51:11 +0100349 Error = namedtuple('Error',
350 ['type', 'expression', 'value', 'output'])
351
Gilles Peskine24609332019-11-21 17:44:21 +0100352 def __init__(self, options):
353 self.options = options
354 self.count = 0
355 self.errors = []
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100356
Gilles Peskine24609332019-11-21 17:44:21 +0100357 def run_one(self, inputs, type_word):
358 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200359
Gilles Peskine24609332019-11-21 17:44:21 +0100360 Run the program on the names for this type.
361 Use the inputs to figure out what arguments to pass to macros that
362 take arguments.
363 """
364 expressions, values = collect_values(inputs, type_word,
365 include_path=self.options.include,
366 keep_c=self.options.keep_c)
367 output = subprocess.check_output([self.options.program, type_word] +
368 values)
369 outputs = output.decode('ascii').strip().split('\n')
370 self.count += len(expressions)
371 for expr, value, output in zip(expressions, values, outputs):
372 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100373 self.errors.append(self.Error(type=type_word,
374 expression=expr,
375 value=value,
376 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200377
Gilles Peskine24609332019-11-21 17:44:21 +0100378 def run_all(self, inputs):
379 """Run psa_constant_names on all the gathered inputs."""
380 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
381 'key_type', 'key_usage']:
382 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100383
Gilles Peskine24609332019-11-21 17:44:21 +0100384 def report(self, out):
385 """Describe each case where the output is not as expected.
386
387 Write the errors to ``out``.
388 Also write a total.
389 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100390 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100391 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100392 .format(error.type, error.expression,
393 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100394 out.write('{} test cases'.format(self.count))
395 if self.errors:
396 out.write(', {} FAIL\n'.format(len(self.errors)))
397 else:
398 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200399
Gilles Peskine69f93b52019-11-21 16:49:50 +0100400HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
401TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
402
Gilles Peskine54f54452019-05-27 18:31:59 +0200403def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200404 parser = argparse.ArgumentParser(description=globals()['__doc__'])
405 parser.add_argument('--include', '-I',
406 action='append', default=['include'],
407 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200408 parser.add_argument('--keep-c',
409 action='store_true', dest='keep_c', default=False,
410 help='Keep the intermediate C file')
411 parser.add_argument('--no-keep-c',
412 action='store_false', dest='keep_c',
413 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100414 parser.add_argument('--program',
415 default='programs/psa/psa_constant_names',
416 help='Program to test')
Gilles Peskine24827022018-09-25 18:49:23 +0200417 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100418 headers = [os.path.join(options.include[0], h) for h in HEADERS]
419 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100420 tests = Tests(options)
421 tests.run_all(inputs)
422 tests.report(sys.stdout)
423 if tests.errors:
Gilles Peskine24827022018-09-25 18:49:23 +0200424 exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200425
426if __name__ == '__main__':
427 main()