blob: 237a344eb1a841396e3e627583794e50044f2939 [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
Bence Szépkúti1e148272020-08-07 13:07:28 +020010# Copyright The Mbed TLS Contributors
Bence Szépkútic7da1fe2020-05-26 01:54:15 +020011# SPDX-License-Identifier: Apache-2.0
12#
13# Licensed under the Apache License, Version 2.0 (the "License"); you may
14# not use this file except in compliance with the License.
15# You may obtain a copy of the License at
16#
17# http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22# See the License for the specific language governing permissions and
23# limitations under the License.
Bence Szépkúti700ee442020-05-26 00:33:31 +020024
Gilles Peskine24827022018-09-25 18:49:23 +020025import argparse
Gilles Peskinea5000f12019-11-21 17:51:11 +010026from collections import namedtuple
Gilles Peskine24827022018-09-25 18:49:23 +020027import os
Gilles Peskine24827022018-09-25 18:49:23 +020028import re
29import subprocess
30import sys
Gilles Peskine95649ed2021-03-29 20:37:40 +020031from typing import Dict, Iterable, Iterator, List, Optional, Pattern, Set, Tuple, Union
Gilles Peskine2adebc82020-12-11 00:30:53 +010032
33import scripts_path # pylint: disable=unused-import
34from mbedtls_dev import c_build_helper
Gilles Peskine95649ed2021-03-29 20:37:40 +020035from mbedtls_dev.macro_collector import PSAMacroEnumerator
36from mbedtls_dev import typing_util
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010037
Gilles Peskinea0a315c2018-10-19 11:27:10 +020038class ReadFileLineException(Exception):
Gilles Peskine95649ed2021-03-29 20:37:40 +020039 def __init__(self, filename: str, line_number: Union[int, str]) -> None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020040 message = 'in {} at {}'.format(filename, line_number)
41 super(ReadFileLineException, self).__init__(message)
42 self.filename = filename
43 self.line_number = line_number
44
45class read_file_lines:
Gilles Peskine54f54452019-05-27 18:31:59 +020046 # Dear Pylint, conventionally, a context manager class name is lowercase.
47 # pylint: disable=invalid-name,too-few-public-methods
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020048 """Context manager to read a text file line by line.
49
50 ```
51 with read_file_lines(filename) as lines:
52 for line in lines:
53 process(line)
54 ```
55 is equivalent to
56 ```
57 with open(filename, 'r') as input_file:
58 for line in input_file:
59 process(line)
60 ```
61 except that if process(line) raises an exception, then the read_file_lines
62 snippet annotates the exception with the file name and line number.
63 """
Gilles Peskine95649ed2021-03-29 20:37:40 +020064 def __init__(self, filename: str, binary: bool = False) -> None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020065 self.filename = filename
Gilles Peskine95649ed2021-03-29 20:37:40 +020066 self.line_number = 'entry' #type: Union[int, str]
67 self.generator = None #type: Optional[Iterable[Tuple[int, str]]]
Gilles Peskine49af2d32019-12-06 19:20:13 +010068 self.binary = binary
Gilles Peskine95649ed2021-03-29 20:37:40 +020069 def __enter__(self) -> 'read_file_lines':
Gilles Peskine49af2d32019-12-06 19:20:13 +010070 self.generator = enumerate(open(self.filename,
71 'rb' if self.binary else 'r'))
Gilles Peskinea0a315c2018-10-19 11:27:10 +020072 return self
Gilles Peskine95649ed2021-03-29 20:37:40 +020073 def __iter__(self) -> Iterator[str]:
74 assert self.generator is not None
Gilles Peskinea0a315c2018-10-19 11:27:10 +020075 for line_number, content in self.generator:
76 self.line_number = line_number
77 yield content
78 self.line_number = 'exit'
Gilles Peskine95649ed2021-03-29 20:37:40 +020079 def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020080 if exc_type is not None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020081 raise ReadFileLineException(self.filename, self.line_number) \
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020082 from exc_value
Gilles Peskinea0a315c2018-10-19 11:27:10 +020083
Gilles Peskine95649ed2021-03-29 20:37:40 +020084class InputsForTest(PSAMacroEnumerator):
Gilles Peskine8c8694c2019-11-21 19:22:45 +010085 # pylint: disable=too-many-instance-attributes
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020086 """Accumulate information about macros to test.
Gilles Peskine95649ed2021-03-29 20:37:40 +020087enumerate
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020088 This includes macro names as well as information about their arguments
89 when applicable.
90 """
91
Gilles Peskine95649ed2021-03-29 20:37:40 +020092 def __init__(self) -> None:
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010093 super().__init__()
Gilles Peskine95649ed2021-03-29 20:37:40 +020094 self.all_declared = set() #type: Set[str]
Gilles Peskine24827022018-09-25 18:49:23 +020095 # Sets of names per type
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010096 self.statuses.add('PSA_SUCCESS')
97 self.algorithms.add('0xffffffff')
98 self.ecc_curves.add('0xff')
99 self.dh_groups.add('0xff')
100 self.key_types.add('0xffff')
101 self.key_usage_flags.add('0x80000000')
102
Bence Szépkúti4af65602020-12-08 11:10:21 +0100103 # Hard-coded values for unknown algorithms
104 #
105 # These have to have values that are correct for their respective
106 # PSA_ALG_IS_xxx macros, but are also not currently assigned and are
107 # not likely to be assigned in the near future.
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100108 self.hash_algorithms.add('0x020000fe') # 0x020000ff is PSA_ALG_ANY_HASH
109 self.mac_algorithms.add('0x03007fff')
110 self.ka_algorithms.add('0x09fc0000')
111 self.kdf_algorithms.add('0x080000ff')
Gilles Peskine434899f2018-10-19 11:30:26 +0200112 # For AEAD algorithms, the only variability is over the tag length,
113 # and this only applies to known algorithms, so don't test an
114 # unknown algorithm.
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100115
Gilles Peskine24827022018-09-25 18:49:23 +0200116 # Identifier prefixes
117 self.table_by_prefix = {
118 'ERROR': self.statuses,
119 'ALG': self.algorithms,
Gilles Peskine98a710c2019-11-21 18:58:36 +0100120 'ECC_CURVE': self.ecc_curves,
121 'DH_GROUP': self.dh_groups,
Gilles Peskine24827022018-09-25 18:49:23 +0200122 'KEY_TYPE': self.key_types,
123 'KEY_USAGE': self.key_usage_flags,
Gilles Peskine95649ed2021-03-29 20:37:40 +0200124 } #type: Dict[str, Set[str]]
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100125 # Test functions
126 self.table_by_test_function = {
Gilles Peskine8fa13482019-11-25 17:10:12 +0100127 # Any function ending in _algorithm also gets added to
128 # self.algorithms.
129 'key_type': [self.key_types],
Gilles Peskinef8210f22019-12-02 17:26:44 +0100130 'block_cipher_key_type': [self.key_types],
131 'stream_cipher_key_type': [self.key_types],
Gilles Peskine228abc52019-12-03 17:24:19 +0100132 'ecc_key_family': [self.ecc_curves],
Gilles Peskine8fa13482019-11-25 17:10:12 +0100133 'ecc_key_types': [self.ecc_curves],
Gilles Peskine228abc52019-12-03 17:24:19 +0100134 'dh_key_family': [self.dh_groups],
Gilles Peskine8fa13482019-11-25 17:10:12 +0100135 'dh_key_types': [self.dh_groups],
136 'hash_algorithm': [self.hash_algorithms],
137 'mac_algorithm': [self.mac_algorithms],
138 'cipher_algorithm': [],
139 'hmac_algorithm': [self.mac_algorithms],
140 'aead_algorithm': [self.aead_algorithms],
141 'key_derivation_algorithm': [self.kdf_algorithms],
142 'key_agreement_algorithm': [self.ka_algorithms],
143 'asymmetric_signature_algorithm': [],
144 'asymmetric_signature_wildcard': [self.algorithms],
145 'asymmetric_encryption_algorithm': [],
146 'other_algorithm': [],
Gilles Peskine95649ed2021-03-29 20:37:40 +0200147 } #type: Dict[str, List[Set[str]]]
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100148 self.arguments_for['mac_length'] += ['1', '63']
149 self.arguments_for['min_mac_length'] += ['1', '63']
150 self.arguments_for['tag_length'] += ['1', '63']
151 self.arguments_for['min_tag_length'] += ['1', '63']
Gilles Peskine24827022018-09-25 18:49:23 +0200152
Gilles Peskine95649ed2021-03-29 20:37:40 +0200153 def get_names(self, type_word: str) -> Set[str]:
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100154 """Return the set of known names of values of the given type."""
155 return {
156 'status': self.statuses,
157 'algorithm': self.algorithms,
158 'ecc_curve': self.ecc_curves,
159 'dh_group': self.dh_groups,
160 'key_type': self.key_types,
161 'key_usage': self.key_usage_flags,
162 }[type_word]
163
Gilles Peskine24827022018-09-25 18:49:23 +0200164 # Regex for interesting header lines.
165 # Groups: 1=macro name, 2=type, 3=argument list (optional).
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200166 _header_line_re = \
Gilles Peskine24827022018-09-25 18:49:23 +0200167 re.compile(r'#define +' +
Gilles Peskine98a710c2019-11-21 18:58:36 +0100168 r'(PSA_((?:(?:DH|ECC|KEY)_)?[A-Z]+)_\w+)' +
Gilles Peskine24827022018-09-25 18:49:23 +0200169 r'(?:\(([^\n()]*)\))?')
170 # Regex of macro names to exclude.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200171 _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
Gilles Peskinec68ce962018-10-19 11:31:52 +0200172 # Additional excluded macros.
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200173 _excluded_names = set([
174 # Macros that provide an alternative way to build the same
175 # algorithm as another macro.
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100176 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200177 'PSA_ALG_FULL_LENGTH_MAC',
178 # Auxiliary macro whose name doesn't fit the usual patterns for
179 # auxiliary macros.
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100180 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200181 ])
Gilles Peskine95649ed2021-03-29 20:37:40 +0200182 def parse_header_line(self, line: str) -> None:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200183 """Parse a C header line, looking for "#define PSA_xxx"."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200184 m = re.match(self._header_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200185 if not m:
186 return
187 name = m.group(1)
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100188 self.all_declared.add(name)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200189 if re.search(self._excluded_name_re, name) or \
190 name in self._excluded_names:
Gilles Peskine24827022018-09-25 18:49:23 +0200191 return
192 dest = self.table_by_prefix.get(m.group(2))
193 if dest is None:
194 return
195 dest.add(name)
196 if m.group(3):
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200197 self.argspecs[name] = self._argument_split(m.group(3))
Gilles Peskine24827022018-09-25 18:49:23 +0200198
Gilles Peskine95649ed2021-03-29 20:37:40 +0200199 _nonascii_re = re.compile(rb'[^\x00-\x7f]+') #type: Pattern
200 def parse_header(self, filename: str) -> None:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200201 """Parse a C header file, looking for "#define PSA_xxx"."""
Gilles Peskine49af2d32019-12-06 19:20:13 +0100202 with read_file_lines(filename, binary=True) as lines:
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200203 for line in lines:
Gilles Peskine49af2d32019-12-06 19:20:13 +0100204 line = re.sub(self._nonascii_re, rb'', line).decode('ascii')
Gilles Peskine24827022018-09-25 18:49:23 +0200205 self.parse_header_line(line)
206
Gilles Peskine49af2d32019-12-06 19:20:13 +0100207 _macro_identifier_re = re.compile(r'[A-Z]\w+')
Gilles Peskine95649ed2021-03-29 20:37:40 +0200208 def generate_undeclared_names(self, expr: str) -> Iterable[str]:
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100209 for name in re.findall(self._macro_identifier_re, expr):
210 if name not in self.all_declared:
211 yield name
212
Gilles Peskine95649ed2021-03-29 20:37:40 +0200213 def accept_test_case_line(self, function: str, argument: str) -> bool:
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100214 #pylint: disable=unused-argument
215 undeclared = list(self.generate_undeclared_names(argument))
216 if undeclared:
217 raise Exception('Undeclared names in test case', undeclared)
218 return True
219
Gilles Peskine95649ed2021-03-29 20:37:40 +0200220 def add_test_case_line(self, function: str, argument: str) -> None:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200221 """Parse a test case data line, looking for algorithm metadata tests."""
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100222 sets = []
Gilles Peskine24827022018-09-25 18:49:23 +0200223 if function.endswith('_algorithm'):
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100224 sets.append(self.algorithms)
Gilles Peskine79616682019-11-21 20:08:10 +0100225 if function == 'key_agreement_algorithm' and \
226 argument.startswith('PSA_ALG_KEY_AGREEMENT('):
227 # We only want *raw* key agreement algorithms as such, so
228 # exclude ones that are already chained with a KDF.
229 # Keep the expression as one to test as an algorithm.
230 function = 'other_algorithm'
Gilles Peskine8fa13482019-11-25 17:10:12 +0100231 sets += self.table_by_test_function[function]
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100232 if self.accept_test_case_line(function, argument):
233 for s in sets:
234 s.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200235
236 # Regex matching a *.data line containing a test function call and
237 # its arguments. The actual definition is partly positional, but this
238 # regex is good enough in practice.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200239 _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
Gilles Peskine95649ed2021-03-29 20:37:40 +0200240 def parse_test_cases(self, filename: str) -> None:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200241 """Parse a test case file (*.data), looking for algorithm metadata tests."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200242 with read_file_lines(filename) as lines:
243 for line in lines:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200244 m = re.match(self._test_case_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200245 if m:
246 self.add_test_case_line(m.group(1), m.group(2))
247
Gilles Peskine95649ed2021-03-29 20:37:40 +0200248def gather_inputs(headers: Iterable[str],
249 test_suites: Iterable[str],
250 inputs_class=InputsForTest) -> PSAMacroEnumerator:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200251 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +0100252 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +0200253 for header in headers:
254 inputs.parse_header(header)
255 for test_cases in test_suites:
256 inputs.parse_test_cases(test_cases)
257 inputs.gather_arguments()
258 return inputs
259
Gilles Peskine95649ed2021-03-29 20:37:40 +0200260def run_c(type_word: str,
261 expressions: Iterable[str],
262 include_path: Optional[str] = None,
263 keep_c: bool = False) -> List[str]:
Gilles Peskine2991b5f2021-01-19 21:19:02 +0100264 """Generate and run a program to print out numerical values of C expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200265 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100266 cast_to = 'long'
267 printf_format = '%ld'
268 else:
269 cast_to = 'unsigned long'
270 printf_format = '0x%08lx'
Gilles Peskine2adebc82020-12-11 00:30:53 +0100271 return c_build_helper.get_c_expression_values(
Gilles Peskinefc622112020-12-11 00:27:14 +0100272 cast_to, printf_format,
273 expressions,
274 caller='test_psa_constant_names.py for {} values'.format(type_word),
275 file_label=type_word,
276 header='#include <psa/crypto.h>',
277 include_path=include_path,
278 keep_c=keep_c
279 )
Gilles Peskine24827022018-09-25 18:49:23 +0200280
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200281NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine95649ed2021-03-29 20:37:40 +0200282def normalize(expr: str) -> str:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200283 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100284
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200285 Currently "trivial differences" means whitespace.
286 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +0100287 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +0200288
Gilles Peskine95649ed2021-03-29 20:37:40 +0200289def collect_values(inputs: InputsForTest,
290 type_word: str,
291 include_path: Optional[str] = None,
292 keep_c: bool = False) -> Tuple[List[str], List[str]]:
Gilles Peskinec2317112019-11-21 17:17:39 +0100293 """Generate expressions using known macro names and calculate their values.
294
295 Return a list of pairs of (expr, value) where expr is an expression and
296 value is a string representation of its integer value.
297 """
298 names = inputs.get_names(type_word)
299 expressions = sorted(inputs.generate_expressions(names))
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100300 values = run_c(type_word, expressions,
301 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +0100302 return expressions, values
303
Gilles Peskine24609332019-11-21 17:44:21 +0100304class Tests:
305 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100306
Gilles Peskinea5000f12019-11-21 17:51:11 +0100307 Error = namedtuple('Error',
308 ['type', 'expression', 'value', 'output'])
309
Gilles Peskine95649ed2021-03-29 20:37:40 +0200310 def __init__(self, options) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100311 self.options = options
312 self.count = 0
Gilles Peskine95649ed2021-03-29 20:37:40 +0200313 self.errors = [] #type: List[Tests.Error]
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100314
Gilles Peskine95649ed2021-03-29 20:37:40 +0200315 def run_one(self, inputs: InputsForTest, type_word: str) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100316 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200317
Gilles Peskine24609332019-11-21 17:44:21 +0100318 Run the program on the names for this type.
319 Use the inputs to figure out what arguments to pass to macros that
320 take arguments.
321 """
322 expressions, values = collect_values(inputs, type_word,
323 include_path=self.options.include,
324 keep_c=self.options.keep_c)
Gilles Peskine95649ed2021-03-29 20:37:40 +0200325 output_bytes = subprocess.check_output([self.options.program,
326 type_word] + values)
327 output = output_bytes.decode('ascii')
328 outputs = output.strip().split('\n')
Gilles Peskine24609332019-11-21 17:44:21 +0100329 self.count += len(expressions)
330 for expr, value, output in zip(expressions, values, outputs):
Gilles Peskine32558482019-12-03 19:03:35 +0100331 if self.options.show:
332 sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
Gilles Peskine24609332019-11-21 17:44:21 +0100333 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100334 self.errors.append(self.Error(type=type_word,
335 expression=expr,
336 value=value,
337 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200338
Gilles Peskine95649ed2021-03-29 20:37:40 +0200339 def run_all(self, inputs: InputsForTest) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100340 """Run psa_constant_names on all the gathered inputs."""
341 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
342 'key_type', 'key_usage']:
343 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100344
Gilles Peskine95649ed2021-03-29 20:37:40 +0200345 def report(self, out: typing_util.Writable) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100346 """Describe each case where the output is not as expected.
347
348 Write the errors to ``out``.
349 Also write a total.
350 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100351 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100352 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100353 .format(error.type, error.expression,
354 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100355 out.write('{} test cases'.format(self.count))
356 if self.errors:
357 out.write(', {} FAIL\n'.format(len(self.errors)))
358 else:
359 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200360
Gilles Peskine69f93b52019-11-21 16:49:50 +0100361HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
362TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
363
Gilles Peskine54f54452019-05-27 18:31:59 +0200364def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200365 parser = argparse.ArgumentParser(description=globals()['__doc__'])
366 parser.add_argument('--include', '-I',
367 action='append', default=['include'],
368 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200369 parser.add_argument('--keep-c',
370 action='store_true', dest='keep_c', default=False,
371 help='Keep the intermediate C file')
372 parser.add_argument('--no-keep-c',
373 action='store_false', dest='keep_c',
374 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100375 parser.add_argument('--program',
376 default='programs/psa/psa_constant_names',
377 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100378 parser.add_argument('--show',
379 action='store_true',
Gilles Peskinec893a572021-03-17 13:45:32 +0100380 help='Show tested values on stdout')
Gilles Peskine32558482019-12-03 19:03:35 +0100381 parser.add_argument('--no-show',
382 action='store_false', dest='show',
383 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200384 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100385 headers = [os.path.join(options.include[0], h) for h in HEADERS]
386 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100387 tests = Tests(options)
388 tests.run_all(inputs)
389 tests.report(sys.stdout)
390 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100391 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200392
393if __name__ == '__main__':
394 main()