blob: 07c8ab2e979c33a57589a6e7f896fa7496876da1 [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 Peskineb4edff92021-03-30 19:09:05 +020031from typing import Iterable, List, Optional, Tuple
Gilles Peskine2adebc82020-12-11 00:30:53 +010032
33import scripts_path # pylint: disable=unused-import
34from mbedtls_dev import c_build_helper
Gilles Peskineb4edff92021-03-30 19:09:05 +020035from mbedtls_dev.macro_collector import InputsForTest, PSAMacroEnumerator
Gilles Peskine95649ed2021-03-29 20:37:40 +020036from mbedtls_dev import typing_util
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010037
Gilles Peskine95649ed2021-03-29 20:37:40 +020038def gather_inputs(headers: Iterable[str],
39 test_suites: Iterable[str],
40 inputs_class=InputsForTest) -> PSAMacroEnumerator:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020041 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +010042 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +020043 for header in headers:
44 inputs.parse_header(header)
45 for test_cases in test_suites:
46 inputs.parse_test_cases(test_cases)
Gilles Peskine3d404b82021-03-30 21:46:35 +020047 inputs.add_numerical_values()
Gilles Peskine24827022018-09-25 18:49:23 +020048 inputs.gather_arguments()
49 return inputs
50
Gilles Peskine95649ed2021-03-29 20:37:40 +020051def run_c(type_word: str,
52 expressions: Iterable[str],
53 include_path: Optional[str] = None,
54 keep_c: bool = False) -> List[str]:
Gilles Peskine2991b5f2021-01-19 21:19:02 +010055 """Generate and run a program to print out numerical values of C expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020056 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +010057 cast_to = 'long'
58 printf_format = '%ld'
59 else:
60 cast_to = 'unsigned long'
61 printf_format = '0x%08lx'
Gilles Peskine2adebc82020-12-11 00:30:53 +010062 return c_build_helper.get_c_expression_values(
Gilles Peskinefc622112020-12-11 00:27:14 +010063 cast_to, printf_format,
64 expressions,
65 caller='test_psa_constant_names.py for {} values'.format(type_word),
66 file_label=type_word,
67 header='#include <psa/crypto.h>',
68 include_path=include_path,
69 keep_c=keep_c
70 )
Gilles Peskine24827022018-09-25 18:49:23 +020071
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020072NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine95649ed2021-03-29 20:37:40 +020073def normalize(expr: str) -> str:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020074 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010075
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020076 Currently "trivial differences" means whitespace.
77 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +010078 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +020079
Gilles Peskine95649ed2021-03-29 20:37:40 +020080def collect_values(inputs: InputsForTest,
81 type_word: str,
82 include_path: Optional[str] = None,
83 keep_c: bool = False) -> Tuple[List[str], List[str]]:
Gilles Peskinec2317112019-11-21 17:17:39 +010084 """Generate expressions using known macro names and calculate their values.
85
86 Return a list of pairs of (expr, value) where expr is an expression and
87 value is a string representation of its integer value.
88 """
89 names = inputs.get_names(type_word)
90 expressions = sorted(inputs.generate_expressions(names))
Gilles Peskineb86b6d32019-11-21 17:26:10 +010091 values = run_c(type_word, expressions,
92 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +010093 return expressions, values
94
Gilles Peskine24609332019-11-21 17:44:21 +010095class Tests:
96 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +010097
Gilles Peskinea5000f12019-11-21 17:51:11 +010098 Error = namedtuple('Error',
99 ['type', 'expression', 'value', 'output'])
100
Gilles Peskine95649ed2021-03-29 20:37:40 +0200101 def __init__(self, options) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100102 self.options = options
103 self.count = 0
Gilles Peskine95649ed2021-03-29 20:37:40 +0200104 self.errors = [] #type: List[Tests.Error]
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100105
Gilles Peskine95649ed2021-03-29 20:37:40 +0200106 def run_one(self, inputs: InputsForTest, type_word: str) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100107 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200108
Gilles Peskine24609332019-11-21 17:44:21 +0100109 Run the program on the names for this type.
110 Use the inputs to figure out what arguments to pass to macros that
111 take arguments.
112 """
113 expressions, values = collect_values(inputs, type_word,
114 include_path=self.options.include,
115 keep_c=self.options.keep_c)
Gilles Peskine95649ed2021-03-29 20:37:40 +0200116 output_bytes = subprocess.check_output([self.options.program,
117 type_word] + values)
118 output = output_bytes.decode('ascii')
119 outputs = output.strip().split('\n')
Gilles Peskine24609332019-11-21 17:44:21 +0100120 self.count += len(expressions)
121 for expr, value, output in zip(expressions, values, outputs):
Gilles Peskine32558482019-12-03 19:03:35 +0100122 if self.options.show:
123 sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
Gilles Peskine24609332019-11-21 17:44:21 +0100124 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100125 self.errors.append(self.Error(type=type_word,
126 expression=expr,
127 value=value,
128 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200129
Gilles Peskine95649ed2021-03-29 20:37:40 +0200130 def run_all(self, inputs: InputsForTest) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100131 """Run psa_constant_names on all the gathered inputs."""
132 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
133 'key_type', 'key_usage']:
134 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100135
Gilles Peskine95649ed2021-03-29 20:37:40 +0200136 def report(self, out: typing_util.Writable) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100137 """Describe each case where the output is not as expected.
138
139 Write the errors to ``out``.
140 Also write a total.
141 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100142 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100143 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100144 .format(error.type, error.expression,
145 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100146 out.write('{} test cases'.format(self.count))
147 if self.errors:
148 out.write(', {} FAIL\n'.format(len(self.errors)))
149 else:
150 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200151
Gilles Peskine69f93b52019-11-21 16:49:50 +0100152HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
153TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
154
Gilles Peskine54f54452019-05-27 18:31:59 +0200155def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200156 parser = argparse.ArgumentParser(description=globals()['__doc__'])
157 parser.add_argument('--include', '-I',
158 action='append', default=['include'],
159 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200160 parser.add_argument('--keep-c',
161 action='store_true', dest='keep_c', default=False,
162 help='Keep the intermediate C file')
163 parser.add_argument('--no-keep-c',
164 action='store_false', dest='keep_c',
165 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100166 parser.add_argument('--program',
167 default='programs/psa/psa_constant_names',
168 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100169 parser.add_argument('--show',
170 action='store_true',
Gilles Peskinec893a572021-03-17 13:45:32 +0100171 help='Show tested values on stdout')
Gilles Peskine32558482019-12-03 19:03:35 +0100172 parser.add_argument('--no-show',
173 action='store_false', dest='show',
174 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200175 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100176 headers = [os.path.join(options.include[0], h) for h in HEADERS]
177 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100178 tests = Tests(options)
179 tests.run_all(inputs)
180 tests.report(sys.stdout)
181 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100182 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200183
184if __name__ == '__main__':
185 main()