blob: 6db121c29794435ecf9a336bbf576f3e0c061095 [file] [log] [blame]
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +01001#!/usr/bin/env python3
Azim Khanf0e42fb2017-08-02 14:47:13 +01002# Test suites code generator.
3#
Bence Szépkúti1e148272020-08-07 13:07:28 +02004# Copyright The Mbed TLS Contributors
Dave Rodgman7ff79652023-11-03 12:04:52 +00005# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Azim Khanf0e42fb2017-08-02 14:47:13 +01006
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01007"""
Azim Khanaee05bb2018-07-02 16:01:04 +01008This script is a key part of Mbed TLS test suites framework. For
9understanding the script it is important to understand the
10framework. This doc string contains a summary of the framework
11and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010012
Azim Khanaee05bb2018-07-02 16:01:04 +010013Mbed TLS test suites:
14=====================
15Scope:
16------
17The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010018include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010019module. However, the framework is not capable of testing SSL
20protocol, since that requires full stack execution and that is best
21tested as part of the system test.
22
23Test case definition:
24---------------------
25Tests are defined in a test_suite_<module>[.<optional sub module>].data
26file. A test definition contains:
27 test name
28 optional build macro dependencies
29 test function
30 test parameters
31
32Test dependencies are build macros that can be specified to indicate
33the build config in which the test is valid. For example if a test
34depends on a feature that is only enabled by defining a macro. Then
35that macro should be specified as a dependency of the test.
36
37Test function is the function that implements the test steps. This
38function is specified for different tests that perform same steps
39with different parameters.
40
41Test parameters are specified in string form separated by ':'.
42Parameters can be of type string, binary data specified as hex
43string and integer constants specified as integer, macro or
44as an expression. Following is an example test definition:
45
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010046 AES 128 GCM Encrypt and decrypt 8 bytes
47 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
48 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010049
50Test functions:
51---------------
52Test functions are coded in C in test_suite_<module>.function files.
53Functions file is itself not compilable and contains special
54format patterns to specify test suite dependencies, start and end
55of functions and function dependencies. Check any existing functions
56file for example.
57
58Execution:
59----------
60Tests are executed in 3 steps:
61- Generating test_suite_<module>[.<optional sub module>].c file
62 for each corresponding .data file.
63- Building each source file into executables.
64- Running each executable and printing report.
65
66Generating C test source requires more than just the test functions.
67Following extras are required:
68- Process main()
69- Reading .data file and dispatching test cases.
70- Platform specific test case execution
71- Dependency checking
72- Integer expression evaluation
73- Test function dispatch
74
75Build dependencies and integer expressions (in the test parameters)
76are specified as strings in the .data file. Their run time value is
77not known at the generation stage. Hence, they need to be translated
78into run time evaluations. This script generates the run time checks
79for dependencies and integer expressions.
80
81Similarly, function names have to be translated into function calls.
82This script also generates code for function dispatch.
83
84The extra code mentioned here is either generated by this script
85or it comes from the input files: helpers file, platform file and
86the template file.
87
88Helper file:
89------------
90Helpers file contains common helper/utility functions and data.
91
92Platform file:
93--------------
94Platform file contains platform specific setup code and test case
95dispatch code. For example, host_test.function reads test data
96file from host's file system and dispatches tests.
Azim Khanaee05bb2018-07-02 16:01:04 +010097
98Template file:
99---------
100Template file for example main_test.function is a template C file in
101which generated code and code from input files is substituted to
102generate a compilable C file. It also contains skeleton functions for
103dependency checks, expression evaluation and function dispatch. These
104functions are populated with checks and return codes by this script.
105
106Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100107strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100108
109This script:
110============
111Core function of this script is to fill the template file with
112code that is generated or read from helpers and platform files.
113
114This script replaces following fields in the template and generates
115the test source file:
116
David Horstmann8eff06f2022-11-09 17:27:33 +0000117__MBEDTLS_TEST_TEMPLATE__TEST_COMMON_HELPERS
118 All common code from helpers.function
119 is substituted here.
120__MBEDTLS_TEST_TEMPLATE__FUNCTIONS_CODE
121 Test functions are substituted here
122 from the input test_suit_xyz.function
123 file. C preprocessor checks are generated
124 for the build dependencies specified
125 in the input file. This script also
126 generates wrappers for the test
127 functions with code to expand the
128 string parameters read from the data
129 file.
130__MBEDTLS_TEST_TEMPLATE__EXPRESSION_CODE
131 This script enumerates the
132 expressions in the .data file and
133 generates code to handle enumerated
134 expression Ids and return the values.
135__MBEDTLS_TEST_TEMPLATE__DEP_CHECK_CODE
136 This script enumerates all
137 build dependencies and generate
138 code to handle enumerated build
139 dependency Id and return status: if
140 the dependency is defined or not.
141__MBEDTLS_TEST_TEMPLATE__DISPATCH_CODE
142 This script enumerates the functions
143 specified in the input test data file
144 and generates the initializer for the
145 function table in the template
146 file.
147__MBEDTLS_TEST_TEMPLATE__PLATFORM_CODE
148 Platform specific setup and test
149 dispatch code.
Azim Khanaee05bb2018-07-02 16:01:04 +0100150
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100151"""
152
Azim Khanf0e42fb2017-08-02 14:47:13 +0100153
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100154import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100155import os
156import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100157import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100158import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100159import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100160
161
Gilles Peskine47903b12023-04-26 19:57:46 +0200162# Types recognized as signed integer arguments in test functions.
Gilles Peskineba726622022-12-04 15:57:49 +0100163SIGNED_INTEGER_TYPES = frozenset([
164 'char',
165 'short',
166 'short int',
167 'int',
168 'int8_t',
169 'int16_t',
170 'int32_t',
171 'int64_t',
172 'intmax_t',
173 'long',
174 'long int',
175 'long long int',
176 'mbedtls_mpi_sint',
177 'psa_status_t',
178])
Gilles Peskine615be632022-12-04 15:11:00 +0100179# Types recognized as string arguments in test functions.
180STRING_TYPES = frozenset(['char*', 'const char*', 'char const*'])
181# Types recognized as hex data arguments in test functions.
182DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*'])
183
Azim Khanb31aa442018-07-03 11:57:54 +0100184BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
185END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100186
Azim Khanb31aa442018-07-03 11:57:54 +0100187BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
188END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000189
Azim Khanb31aa442018-07-03 11:57:54 +0100190BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
191END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100192
Azim Khan8d686bf2018-07-04 23:29:46 +0100193BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100194END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100195
Azim Khan8d686bf2018-07-04 23:29:46 +0100196DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200197C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
198CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
199# forbid 0ddd which might be accidentally octal or accidentally decimal
200CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
201CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
202 CONDITION_OPERATOR_REGEX,
203 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100204TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100205FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
206EXIT_LABEL_REGEX = r'^exit:'
207
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100208
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100209class GeneratorInputError(Exception):
210 """
Azim Khane3b26af2018-06-29 02:36:57 +0100211 Exception to indicate error in the input files to this script.
212 This includes missing patterns, test function names and other
213 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100214 """
215 pass
216
217
Gilles Peskine184c0962020-03-24 18:25:17 +0100218class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100219 """
Azim Khane3b26af2018-06-29 02:36:57 +0100220 This class extends built-in io.FileIO class with attribute line_no,
221 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100222 """
223
224 def __init__(self, file_name):
225 """
Azim Khane3b26af2018-06-29 02:36:57 +0100226 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100227
Azim Khanf0e42fb2017-08-02 14:47:13 +0100228 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100229 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100230 super().__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100231 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100232
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100233 def __next__(self):
Azim Khan4b543232017-06-30 09:35:21 +0100234 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100235 This method overrides base class's __next__ method and extends it
236 method to count the line numbers as each line is read.
Azim Khane3b26af2018-06-29 02:36:57 +0100237
Azim Khanf0e42fb2017-08-02 14:47:13 +0100238 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100239 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100240 line = super().__next__()
Azim Khanb31aa442018-07-03 11:57:54 +0100241 if line is not None:
242 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100243 # Convert byte array to string with correct encoding and
244 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100245 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100246 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100247
Azim Khanb31aa442018-07-03 11:57:54 +0100248 def get_line_no(self):
249 """
250 Gives current line number.
251 """
252 return self._line_no
253
254 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100255
256
257def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100258 """
Azim Khanb31aa442018-07-03 11:57:54 +0100259 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100260
261 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100262 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
263 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100264 """
Azim Khan4b543232017-06-30 09:35:21 +0100265 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
266
267
Azim Khanb31aa442018-07-03 11:57:54 +0100268def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100269 """
Azim Khane3b26af2018-06-29 02:36:57 +0100270 Test suite data and functions specifies compile time dependencies.
271 This function generates C preprocessor code from the input
272 dependency list. Caller uses the generated preprocessor code to
273 wrap dependent code.
274 A dependency in the input list can have a leading '!' character
275 to negate a condition. '!' is separated from the dependency using
276 function split_dep() and proper preprocessor check is generated
277 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100278
Azim Khanb31aa442018-07-03 11:57:54 +0100279 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100280 :return: if defined and endif code with macro annotations for
281 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100282 """
Azim Khanb31aa442018-07-03 11:57:54 +0100283 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
284 map(split_dep, dependencies)])
285 dep_end = ''.join(['#endif /* %s */\n' %
286 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100287
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100288 return dep_start, dep_end
289
290
Azim Khanb31aa442018-07-03 11:57:54 +0100291def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100292 """
Azim Khanb31aa442018-07-03 11:57:54 +0100293 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100294 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100295
Azim Khanb31aa442018-07-03 11:57:54 +0100296 :param dependencies: List of dependencies.
297 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100298 """
Azim Khanb31aa442018-07-03 11:57:54 +0100299 defines = '#if ' if dependencies else ''
300 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
301 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100302 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100303
304
Azim Khanb31aa442018-07-03 11:57:54 +0100305def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100306 """
Azim Khan040b6a22018-06-28 16:49:13 +0100307 Creates test function wrapper code. A wrapper has the code to
308 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100309
Azim Khanf0e42fb2017-08-02 14:47:13 +0100310 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100311 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100312 :param args_dispatch: List of dispatch arguments.
Gilles Peskinedca05012023-04-26 19:59:28 +0200313 Ex: ['(char *) params[0]', '*((int *) params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100314 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100315 """
316 # Then create the wrapper
317 wrapper = '''
318void {name}_wrapper( void ** params )
319{{
Gilles Peskine77761412018-06-18 17:51:40 +0200320{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100321 {name}( {args} );
322}}
Gilles Peskine77761412018-06-18 17:51:40 +0200323'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100324 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100325 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100326 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100327 return wrapper
328
329
Azim Khanb31aa442018-07-03 11:57:54 +0100330def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100331 """
Azim Khane3b26af2018-06-29 02:36:57 +0100332 Test suite code template main_test.function defines a C function
333 array to contain test case functions. This function generates an
334 initializer entry for a function in that array. The entry is
335 composed of a compile time check for the test function
336 dependencies. At compile time the test function is assigned when
337 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100338
Azim Khanf0e42fb2017-08-02 14:47:13 +0100339 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100340 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100341 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100342 """
Azim Khanb31aa442018-07-03 11:57:54 +0100343 if dependencies:
344 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100345 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100346{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100347 {name}_wrapper,
348#else
349 NULL,
350#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100351'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100352 else:
353 dispatch_code = '''
354 {name}_wrapper,
355'''.format(name=name)
356
357 return dispatch_code
358
359
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000360def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100361 """
Azim Khane3b26af2018-06-29 02:36:57 +0100362 Matches pattern end_regex to the lines read from the file object.
363 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100364
Azim Khan8d686bf2018-07-04 23:29:46 +0100365 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000366 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100367 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100368 """
Azim Khan4b543232017-06-30 09:35:21 +0100369 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100370 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000371 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100372 break
373 headers += line
374 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100375 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100376 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100377
Azim Khan4b543232017-06-30 09:35:21 +0100378 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100379
380
Azim Khan8d686bf2018-07-04 23:29:46 +0100381def validate_dependency(dependency):
382 """
383 Validates a C macro and raises GeneratorInputError on invalid input.
384 :param dependency: Input macro dependency
385 :return: input dependency stripped of leading & trailing white spaces.
386 """
387 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200388 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100389 raise GeneratorInputError('Invalid dependency %s' % dependency)
390 return dependency
391
392
393def parse_dependencies(inp_str):
394 """
395 Parses dependencies out of inp_str, validates them and returns a
396 list of macros.
397
398 :param inp_str: Input string with macros delimited by ':'.
399 :return: list of dependencies
400 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100401 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100402 return dependencies
403
404
Azim Khanb31aa442018-07-03 11:57:54 +0100405def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100406 """
Azim Khane3b26af2018-06-29 02:36:57 +0100407 Parses test suite dependencies specified at the top of a
408 .function file, that starts with pattern BEGIN_DEPENDENCIES
409 and end with END_DEPENDENCIES. Dependencies are specified
410 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100411
Azim Khan8d686bf2018-07-04 23:29:46 +0100412 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100413 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100414 """
Azim Khanb31aa442018-07-03 11:57:54 +0100415 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100416 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100417 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100418 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100419 try:
420 dependencies = parse_dependencies(match.group('dependencies'))
421 except GeneratorInputError as error:
422 raise GeneratorInputError(
423 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100424 if re.search(END_DEP_REGEX, line):
425 break
426 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100427 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100428 " not found!" % (funcs_f.name,
429 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100430
Azim Khanb31aa442018-07-03 11:57:54 +0100431 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100432
433
Azim Khanb31aa442018-07-03 11:57:54 +0100434def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100435 """
Azim Khane3b26af2018-06-29 02:36:57 +0100436 Parses function dependencies, that are in the same line as
437 comment BEGIN_CASE. Dependencies are specified after pattern
438 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100439
Azim Khan8d686bf2018-07-04 23:29:46 +0100440 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100441 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100442 """
Azim Khanb31aa442018-07-03 11:57:54 +0100443 dependencies = []
444 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100445 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100446 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100447 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100448 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100449 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100450
Azim Khan8d686bf2018-07-04 23:29:46 +0100451 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100452
Azim Khan4084ec72018-07-05 14:20:08 +0100453
Gilles Peskine615be632022-12-04 15:11:00 +0100454ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S)
Gilles Peskinef153c562022-12-04 14:10:39 +0100455def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch):
456 """
457 Parses one test function's argument declaration.
458
459 :param arg: argument declaration.
460 :param arg_idx: current wrapper argument index.
461 :param args: accumulator of arguments' internal types.
462 :param local_vars: accumulator of internal variable declarations.
463 :param args_dispatch: accumulator of argument usage expressions.
464 :return: the number of new wrapper arguments,
465 or None if the argument declaration is invalid.
466 """
Gilles Peskine615be632022-12-04 15:11:00 +0100467 # Normalize whitespace
Gilles Peskinef153c562022-12-04 14:10:39 +0100468 arg = arg.strip()
Gilles Peskine615be632022-12-04 15:11:00 +0100469 arg = re.sub(r'\s*\*\s*', r'*', arg)
470 arg = re.sub(r'\s+', r' ', arg)
471 # Extract name and type
472 m = ARGUMENT_DECLARATION_REGEX.search(arg)
473 if not m:
474 # E.g. "int x[42]"
475 return None
476 typ, _ = m.groups()
Gilles Peskineba726622022-12-04 15:57:49 +0100477 if typ in SIGNED_INTEGER_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100478 args.append('int')
Gilles Peskinedca05012023-04-26 19:59:28 +0200479 args_dispatch.append('((mbedtls_test_argument_t *) params[%d])->sint' % arg_idx)
Gilles Peskinef153c562022-12-04 14:10:39 +0100480 return 1
Gilles Peskine615be632022-12-04 15:11:00 +0100481 if typ in STRING_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100482 args.append('char*')
483 args_dispatch.append('(char *) params[%d]' % arg_idx)
484 return 1
Gilles Peskine615be632022-12-04 15:11:00 +0100485 if typ in DATA_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100486 args.append('hex')
487 # create a structure
488 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
Gilles Peskinedca05012023-04-26 19:59:28 +0200489 len_initializer = '((mbedtls_test_argument_t *) params[%d])->len' % (arg_idx+1)
Gilles Peskinef153c562022-12-04 14:10:39 +0100490 local_vars.append(' data_t data%d = {%s, %s};\n' %
491 (arg_idx, pointer_initializer, len_initializer))
492 args_dispatch.append('&data%d' % arg_idx)
493 return 2
494 return None
495
Gilles Peskine46476e02022-12-04 14:29:06 +0100496ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S)
Azim Khanfcdf6852018-07-05 17:31:46 +0100497def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100498 """
Azim Khane3b26af2018-06-29 02:36:57 +0100499 Parses test function signature for validation and generates
500 a dispatch wrapper function that translates input test vectors
501 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100502
Azim Khan8d686bf2018-07-04 23:29:46 +0100503 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100504 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100505 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100506 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100507 """
Azim Khan8d686bf2018-07-04 23:29:46 +0100508 # Process arguments, ex: <type> arg1, <type> arg2 )
509 # This script assumes that the argument list is terminated by ')'
510 # i.e. the test functions will not have a function pointer
511 # argument.
Gilles Peskine46476e02022-12-04 14:29:06 +0100512 m = ARGUMENT_LIST_REGEX.search(line)
513 arg_list = m.group(1).strip()
514 if arg_list in ['', 'void']:
515 return [], '', []
516 args = []
517 local_vars = []
518 args_dispatch = []
519 arg_idx = 0
520 for arg in arg_list.split(','):
Gilles Peskinef153c562022-12-04 14:10:39 +0100521 indexes = parse_function_argument(arg, arg_idx,
522 args, local_vars, args_dispatch)
523 if indexes is None:
Azim Khan040b6a22018-06-28 16:49:13 +0100524 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100525 "'char *' or 'data_t'\n%s" % line)
Gilles Peskinef153c562022-12-04 14:10:39 +0100526 arg_idx += indexes
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100527
Gilles Peskine3a37f192022-12-04 14:00:32 +0100528 return args, ''.join(local_vars), args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100529
530
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100531def generate_function_code(name, code, local_vars, args_dispatch,
532 dependencies):
533 """
534 Generate function code with preprocessor checks and parameter dispatch
535 wrapper.
536
537 :param name: Function name
538 :param code: Function code
539 :param local_vars: Local variables for function wrapper
540 :param args_dispatch: Argument dispatch code
541 :param dependencies: Preprocessor dependencies list
542 :return: Final function code
543 """
544 # Add exit label if not present
545 if code.find('exit:') == -1:
546 split_code = code.rsplit('}', 1)
547 if len(split_code) == 2:
548 code = """exit:
549 ;
550}""".join(split_code)
551
552 code += gen_function_wrapper(name, local_vars, args_dispatch)
553 preprocessor_check_start, preprocessor_check_end = \
554 gen_dependencies(dependencies)
555 return preprocessor_check_start + code + preprocessor_check_end
556
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100557COMMENT_START_REGEX = re.compile(r'/[*/]')
558
559def skip_comments(line, stream):
560 """Remove comments in line.
561
562 If the line contains an unfinished comment, read more lines from stream
563 until the line that contains the comment.
564
565 :return: The original line with inner comments replaced by spaces.
566 Trailing comments and whitespace may be removed completely.
567 """
568 pos = 0
569 while True:
570 opening = COMMENT_START_REGEX.search(line, pos)
571 if not opening:
572 break
573 if line[opening.start(0) + 1] == '/': # //...
574 continuation = line
Gilles Peskineaec4bec2022-11-30 16:38:49 +0100575 # Count the number of line breaks, to keep line numbers aligned
576 # in the output.
577 line_count = 1
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100578 while continuation.endswith('\\\n'):
579 # This errors out if the file ends with an unfinished line
Gilles Peskine43febf22022-11-18 22:26:03 +0100580 # comment. That's acceptable to not complicate the code further.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100581 continuation = next(stream)
Gilles Peskineaec4bec2022-11-30 16:38:49 +0100582 line_count += 1
583 return line[:opening.start(0)].rstrip() + '\n' * line_count
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100584 # Parsing /*...*/, looking for the end
585 closing = line.find('*/', opening.end(0))
586 while closing == -1:
587 # This errors out if the file ends with an unfinished block
Gilles Peskine43febf22022-11-18 22:26:03 +0100588 # comment. That's acceptable to not complicate the code further.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100589 line += next(stream)
590 closing = line.find('*/', opening.end(0))
591 pos = closing + 2
Gilles Peskine7e8d4b62022-11-18 22:27:37 +0100592 # Replace inner comment by spaces. There needs to be at least one space
593 # for things like 'int/*ihatespaces*/foo'. Go further and preserve the
Gilles Peskine07995fd2022-11-29 22:03:32 +0100594 # width of the comment and line breaks, this way positions in error
595 # messages remain correct.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100596 line = (line[:opening.start(0)] +
Gilles Peskine07995fd2022-11-29 22:03:32 +0100597 re.sub(r'.', r' ', line[opening.start(0):pos]) +
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100598 line[pos:])
Gilles Peskine07995fd2022-11-29 22:03:32 +0100599 # Strip whitespace at the end of lines (it's irrelevant to error messages).
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100600 return re.sub(r' +(\n|\Z)', r'\1', line)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100601
Azim Khanb31aa442018-07-03 11:57:54 +0100602def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100603 """
Azim Khan040b6a22018-06-28 16:49:13 +0100604 Parses out a function from function file object and generates
605 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100606
Azim Khanf0e42fb2017-08-02 14:47:13 +0100607 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100608 :param dependencies: List of dependencies
609 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100610 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100611 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100612 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
613 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100614 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100615 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100616 # Check function signature. Function signature may be split
617 # across multiple lines. Here we try to find the start of
618 # arguments list, then remove '\n's and apply the regex to
619 # detect function start.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100620 line = skip_comments(line, funcs_f)
Azim Khanfcdf6852018-07-05 17:31:46 +0100621 up_to_arg_list_start = code + line[:line.find('(') + 1]
622 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
623 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100624 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100625 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100626 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100627 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 for lin in funcs_f:
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100629 line += skip_comments(lin, funcs_f)
Azim Khan8d686bf2018-07-04 23:29:46 +0100630 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100631 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100632 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100633 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100634 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100635 break
636 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100637 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100638 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100639 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100640
Azim Khanfcdf6852018-07-05 17:31:46 +0100641 # Prefix test function name with 'test_'
642 code = code.replace(name, 'test_' + name, 1)
643 name = 'test_' + name
644
Gowtham Suresh Kumar34d8bd32023-07-26 17:18:55 +0100645 # If a test function has no arguments then add 'void' argument to
Gowtham Suresh Kumarcc029af2023-08-01 09:48:32 +0100646 # avoid "-Wstrict-prototypes" warnings from clang
Gowtham Suresh Kumar34d8bd32023-07-26 17:18:55 +0100647 if len(args) == 0:
648 code = code.replace('()', '(void)', 1)
649
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100650 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100651 if re.search(END_CASE_REGEX, line):
652 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100653 if not has_exit_label:
654 has_exit_label = \
655 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100656 code += line
657 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100658 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100659 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100660
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100661 code = line_directive + code
662 code = generate_function_code(name, code, local_vars, args_dispatch,
663 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100664 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100665 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100666
667
668def parse_functions(funcs_f):
669 """
Azim Khane3b26af2018-06-29 02:36:57 +0100670 Parses a test_suite_xxx.function file and returns information
671 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100672
Azim Khanf0e42fb2017-08-02 14:47:13 +0100673 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100674 :return: List of test suite dependencies, test function dispatch
675 code, function code and a dict with function identifiers
676 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100677 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000678 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100679 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100680 suite_functions = ''
681 func_info = {}
682 function_idx = 0
683 dispatch_code = ''
684 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100685 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100686 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000687 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100688 suite_helpers += parse_until_pattern(funcs_f,
689 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100690 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100691 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100692 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100693 try:
694 dependencies = parse_function_dependencies(line)
695 except GeneratorInputError as error:
696 raise GeneratorInputError(
697 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
698 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100699 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100700 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100701 suite_functions += func_code
702 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100703 if func_name in func_info:
704 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100705 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100706 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100707 func_info[func_name] = (function_idx, args)
708 dispatch_code += '/* Function Id: %d */\n' % function_idx
709 dispatch_code += func_dispatch
710 function_idx += 1
711
Azim Khanb31aa442018-07-03 11:57:54 +0100712 func_code = (suite_helpers +
713 suite_functions).join(gen_dependencies(suite_dependencies))
714 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100715
716
Azim Khanb31aa442018-07-03 11:57:54 +0100717def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100718 """
Azim Khanb31aa442018-07-03 11:57:54 +0100719 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100720 Since, return value is used to write back to the intermediate
721 data file, any escape characters in the input are retained in the
722 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100723
Azim Khanb31aa442018-07-03 11:57:54 +0100724 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100725 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100726 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100727 """
Azim Khanb31aa442018-07-03 11:57:54 +0100728 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100729 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100730 out = re.sub(r'(\\.)|' + split_char,
731 lambda m: m.group(1) or '\n', inp_str,
732 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100733 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100734 return out
735
736
Azim Khanb31aa442018-07-03 11:57:54 +0100737def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100738 """
Azim Khane3b26af2018-06-29 02:36:57 +0100739 Parses .data file for each test case name, test function name,
740 test dependencies and test arguments. This information is
741 correlated with the test functions file for generating an
742 intermediate data file replacing the strings for test function
743 names, dependencies and integer constant expressions with
744 identifiers. Mainly for optimising space for on-target
745 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100746
Azim Khanf0e42fb2017-08-02 14:47:13 +0100747 :param data_f: file object of the data file.
Gilles Peskinef122aed2022-12-03 22:58:52 +0100748 :return: Generator that yields line number, test name, function name,
Azim Khan040b6a22018-06-28 16:49:13 +0100749 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100750 """
Azim Khanb31aa442018-07-03 11:57:54 +0100751 __state_read_name = 0
752 __state_read_args = 1
753 state = __state_read_name
754 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100755 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100756 for line in data_f:
757 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100758 # Skip comments
759 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100760 continue
761
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100762 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100763 if not line:
764 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100765 raise GeneratorInputError("[%s:%d] Newline before arguments. "
766 "Test function and arguments "
767 "missing for %s" %
768 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100769 continue
770
Azim Khanb31aa442018-07-03 11:57:54 +0100771 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100772 # Read test name
773 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100774 state = __state_read_args
775 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100776 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100777 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100778 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100779 try:
780 dependencies = parse_dependencies(
781 match.group('dependencies'))
782 except GeneratorInputError as error:
783 raise GeneratorInputError(
784 str(error) + " - %s:%d" %
785 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100786 else:
787 # Read test vectors
788 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100789 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100790 args = parts[1:]
Gilles Peskinef122aed2022-12-03 22:58:52 +0100791 yield data_f.line_no, name, test_function, dependencies, args
Azim Khanb31aa442018-07-03 11:57:54 +0100792 dependencies = []
793 state = __state_read_name
794 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100795 raise GeneratorInputError("[%s:%d] Newline before arguments. "
796 "Test function and arguments missing for "
797 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100798
799
800def gen_dep_check(dep_id, dep):
801 """
Azim Khane3b26af2018-06-29 02:36:57 +0100802 Generate code for checking dependency with the associated
803 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100804
Azim Khanf0e42fb2017-08-02 14:47:13 +0100805 :param dep_id: Dependency identifier
806 :param dep: Dependency macro
807 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100808 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100809 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100810 raise GeneratorInputError("Dependency Id should be a positive "
811 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100812 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
813 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100814 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200815
816 dependency = re.match(CONDITION_REGEX, dep, re.I)
817 if not dependency:
818 raise GeneratorInputError('Invalid dependency %s' % dep)
819
820 _defined = '' if dependency.group(2) else 'defined'
821 _cond = dependency.group(2) if dependency.group(2) else ''
822 _value = dependency.group(3) if dependency.group(3) else ''
823
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100824 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100825 case {id}:
826 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200827#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100828 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100829#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100830 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100831#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100832 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200833 break;'''.format(_not=_not, _defined=_defined,
834 macro=dependency.group(1), id=dep_id,
835 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100836 return dep_check
837
838
839def gen_expression_check(exp_id, exp):
840 """
Azim Khane3b26af2018-06-29 02:36:57 +0100841 Generates code for evaluating an integer expression using
842 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100843
Azim Khanf0e42fb2017-08-02 14:47:13 +0100844 :param exp_id: Expression Identifier
845 :param exp: Expression/Macro
846 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100847 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100848 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100849 raise GeneratorInputError("Expression Id should be a positive "
850 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100851 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100852 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100853 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100854 case {exp_id}:
855 {{
856 *out_value = {expression};
857 }}
858 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100859 return exp_code
860
861
Azim Khanb31aa442018-07-03 11:57:54 +0100862def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100863 """
Azim Khane3b26af2018-06-29 02:36:57 +0100864 Write dependencies to intermediate test data file, replacing
865 the string form with identifiers. Also, generates dependency
866 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100867
Azim Khanf0e42fb2017-08-02 14:47:13 +0100868 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100869 :param test_dependencies: Dependencies
870 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100871 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100872 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100873 """
Azim Khan599cd242017-07-06 17:34:27 +0100874 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100875 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100876 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100877 for dep in test_dependencies:
878 if dep not in unique_dependencies:
879 unique_dependencies.append(dep)
880 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100881 dep_check_code += gen_dep_check(dep_id, dep)
882 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100883 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100884 out_data_f.write(':' + str(dep_id))
885 out_data_f.write('\n')
886 return dep_check_code
887
888
Gilles Peskinea2990432022-12-04 00:28:56 +0100889INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I)
890def val_is_int(val: str) -> bool:
891 """Whether val is suitable as an 'int' parameter in the .datax file."""
892 if not INT_VAL_REGEX.match(val):
893 return False
894 # Limit the range to what is guaranteed to get through strtol()
895 return abs(int(val, 0)) <= 0x7fffffff
896
Azim Khan599cd242017-07-06 17:34:27 +0100897def write_parameters(out_data_f, test_args, func_args, unique_expressions):
898 """
Azim Khane3b26af2018-06-29 02:36:57 +0100899 Writes test parameters to the intermediate data file, replacing
900 the string form with identifiers. Also, generates expression
901 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100902
Azim Khanf0e42fb2017-08-02 14:47:13 +0100903 :param out_data_f: Output intermediate data file
904 :param test_args: Test parameters
905 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100906 :param unique_expressions: Mutable list to track unique
907 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100908 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100909 """
910 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100911 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100912 typ = func_args[i]
913 val = test_args[i]
914
Gilles Peskinea2990432022-12-04 00:28:56 +0100915 # Pass small integer constants literally. This reduces the size of
916 # the C code. Register anything else as an expression.
917 if typ == 'int' and not val_is_int(val):
Azim Khan599cd242017-07-06 17:34:27 +0100918 typ = 'exp'
919 if val not in unique_expressions:
920 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100921 # exp_id can be derived from len(). But for
922 # readability and consistency with case of existing
923 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100924 exp_id = unique_expressions.index(val)
925 expression_code += gen_expression_check(exp_id, val)
926 val = exp_id
927 else:
928 val = unique_expressions.index(val)
929 out_data_f.write(':' + typ + ':' + str(val))
930 out_data_f.write('\n')
931 return expression_code
932
933
Azim Khanb31aa442018-07-03 11:57:54 +0100934def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100935 """
Azim Khane3b26af2018-06-29 02:36:57 +0100936 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100937
Azim Khanb31aa442018-07-03 11:57:54 +0100938 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100939 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100940 :param dep_check_code: Dependency check code
941 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100942 :return: Dependency and expression code guarded by test suite
943 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100944 """
Azim Khanb31aa442018-07-03 11:57:54 +0100945 if suite_dependencies:
946 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100947 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100948{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100949{code}
Azim Khan599cd242017-07-06 17:34:27 +0100950#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100951'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100952 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100953{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100954{code}
Azim Khan599cd242017-07-06 17:34:27 +0100955#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100956'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100957 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100958
959
Gilles Peskineab56a692022-12-04 17:27:25 +0100960def get_function_info(func_info, function_name, line_no):
961 """Look up information about a test function by name.
962
963 Raise an informative expression if function_name is not found.
964
965 :param func_info: dictionary mapping function names to their information.
966 :param function_name: the function name as written in the .function and
967 .data files.
968 :param line_no: line number for error messages.
969 :return Function information (id, args).
970 """
971 test_function_name = 'test_' + function_name
972 if test_function_name not in func_info:
973 raise GeneratorInputError("%d: Function %s not found!" %
974 (line_no, test_function_name))
975 return func_info[test_function_name]
976
977
Azim Khanb31aa442018-07-03 11:57:54 +0100978def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100979 """
Azim Khane3b26af2018-06-29 02:36:57 +0100980 This function reads test case name, dependencies and test vectors
981 from the .data file. This information is correlated with the test
982 functions file for generating an intermediate data file replacing
983 the strings for test function names, dependencies and integer
984 constant expressions with identifiers. Mainly for optimising
985 space for on-target execution.
986 It also generates test case dependency check code and expression
987 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100988
Azim Khanf0e42fb2017-08-02 14:47:13 +0100989 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100990 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100991 :param func_info: Dict keyed by function and with function id
992 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100993 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100994 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100995 """
Azim Khanb31aa442018-07-03 11:57:54 +0100996 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100997 unique_expressions = []
998 dep_check_code = ''
999 expression_code = ''
Gilles Peskinef122aed2022-12-03 22:58:52 +01001000 for line_no, test_name, function_name, test_dependencies, test_args in \
Azim Khanb31aa442018-07-03 11:57:54 +01001001 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001002 out_data_f.write(test_name + '\n')
1003
Azim Khanb31aa442018-07-03 11:57:54 +01001004 # Write dependencies
1005 dep_check_code += write_dependencies(out_data_f, test_dependencies,
1006 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001007
Azim Khan599cd242017-07-06 17:34:27 +01001008 # Write test function name
Gilles Peskineab56a692022-12-04 17:27:25 +01001009 func_id, func_args = \
1010 get_function_info(func_info, function_name, line_no)
Azim Khan599cd242017-07-06 17:34:27 +01001011 out_data_f.write(str(func_id))
1012
1013 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001014 if len(test_args) != len(func_args):
Gilles Peskinef122aed2022-12-03 22:58:52 +01001015 raise GeneratorInputError("%d: Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +01001016 "%s. See function %s signature." %
Gilles Peskinef122aed2022-12-03 22:58:52 +01001017 (line_no, test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +01001018 expression_code += write_parameters(out_data_f, test_args, func_args,
1019 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001020
Azim Khan599cd242017-07-06 17:34:27 +01001021 # Write a newline as test case separator
1022 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001023
Azim Khanb31aa442018-07-03 11:57:54 +01001024 dep_check_code, expression_code = gen_suite_dep_checks(
1025 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001026 return dep_check_code, expression_code
1027
1028
Azim Khanb31aa442018-07-03 11:57:54 +01001029def add_input_info(funcs_file, data_file, template_file,
1030 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001031 """
Azim Khanb31aa442018-07-03 11:57:54 +01001032 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001033
Azim Khanf0e42fb2017-08-02 14:47:13 +01001034 :param funcs_file: Functions file object
1035 :param data_file: Data file object
1036 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +01001037 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +01001038 :param snippets: Dictionary to contain code pieces to be
1039 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001040 :return:
1041 """
Azim Khanb31aa442018-07-03 11:57:54 +01001042 snippets['test_file'] = c_file
1043 snippets['test_main_file'] = template_file
1044 snippets['test_case_file'] = funcs_file
1045 snippets['test_case_data_file'] = data_file
1046
1047
1048def read_code_from_input_files(platform_file, helpers_file,
1049 out_data_file, snippets):
1050 """
1051 Read code from input files and create substitutions for replacement
1052 strings in the template file.
1053
1054 :param platform_file: Platform file object
1055 :param helpers_file: Helper functions file object
1056 :param out_data_file: Output intermediate data file object
1057 :param snippets: Dictionary to contain code pieces to be
1058 substituted in the template.
1059 :return:
1060 """
1061 # Read helpers
1062 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
1063 platform_f:
1064 snippets['test_common_helper_file'] = helpers_file
1065 snippets['test_common_helpers'] = help_f.read()
1066 snippets['test_platform_file'] = platform_file
1067 snippets['platform_code'] = platform_f.read().replace(
1068 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
1069
1070
1071def write_test_source_file(template_file, c_file, snippets):
1072 """
1073 Write output source file with generated source code.
1074
1075 :param template_file: Template file name
1076 :param c_file: Output source file
1077 :param snippets: Generated and code snippets
1078 :return:
1079 """
David Horstmann14bae832022-11-03 17:49:29 +00001080
1081 # Create a placeholder pattern with the correct named capture groups
1082 # to override the default provided with Template.
1083 # Match nothing (no way of escaping placeholders).
1084 escaped = "(?P<escaped>(?!))"
1085 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
1086 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
1087 # Match nothing (no braced placeholder syntax).
1088 braced = "(?P<braced>(?!))"
1089 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
1090 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
David Horstmann8eff06f2022-11-09 17:27:33 +00001091 placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid]))
David Horstmann14bae832022-11-03 17:49:29 +00001092
Azim Khanb31aa442018-07-03 11:57:54 +01001093 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001094 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001095 # Update line number. +1 as #line directive sets next line number
1096 snippets['line_no'] = line_no + 1
David Horstmann14bae832022-11-03 17:49:29 +00001097 template = string.Template(line)
1098 template.pattern = placeholder_pattern
1099 snippets = {k.upper():v for (k, v) in snippets.items()}
1100 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001101 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001102
1103
1104def parse_function_file(funcs_file, snippets):
1105 """
1106 Parse function file and generate function dispatch code.
1107
1108 :param funcs_file: Functions file name
1109 :param snippets: Dictionary to contain code pieces to be
1110 substituted in the template.
1111 :return:
1112 """
1113 with FileWrapper(funcs_file) as funcs_f:
1114 suite_dependencies, dispatch_code, func_code, func_info = \
1115 parse_functions(funcs_f)
1116 snippets['functions_code'] = func_code
1117 snippets['dispatch_code'] = dispatch_code
1118 return suite_dependencies, func_info
1119
1120
1121def generate_intermediate_data_file(data_file, out_data_file,
1122 suite_dependencies, func_info, snippets):
1123 """
1124 Generates intermediate data file from input data file and
1125 information read from functions file.
1126
1127 :param data_file: Data file name
1128 :param out_data_file: Output/Intermediate data file
1129 :param suite_dependencies: List of suite dependencies.
1130 :param func_info: Function info parsed from functions file.
1131 :param snippets: Dictionary to contain code pieces to be
1132 substituted in the template.
1133 :return:
1134 """
1135 with FileWrapper(data_file) as data_f, \
1136 open(out_data_file, 'w') as out_data_f:
1137 dep_check_code, expression_code = gen_from_test_data(
1138 data_f, out_data_f, func_info, suite_dependencies)
1139 snippets['dep_check_code'] = dep_check_code
1140 snippets['expression_code'] = expression_code
1141
1142
1143def generate_code(**input_info):
1144 """
1145 Generates C source code from test suite file, data file, common
1146 helpers file and platform file.
1147
1148 input_info expands to following parameters:
1149 funcs_file: Functions file object
1150 data_file: Data file object
1151 template_file: Template file object
1152 platform_file: Platform file object
1153 helpers_file: Helper functions file object
1154 suites_dir: Test suites dir
1155 c_file: Output C file object
1156 out_data_file: Output intermediate data file object
1157 :return:
1158 """
1159 funcs_file = input_info['funcs_file']
1160 data_file = input_info['data_file']
1161 template_file = input_info['template_file']
1162 platform_file = input_info['platform_file']
1163 helpers_file = input_info['helpers_file']
1164 suites_dir = input_info['suites_dir']
1165 c_file = input_info['c_file']
1166 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001167 for name, path in [('Functions file', funcs_file),
1168 ('Data file', data_file),
1169 ('Template file', template_file),
1170 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001171 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001172 ('Suites dir', suites_dir)]:
1173 if not os.path.exists(path):
1174 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1175
Azim Khanb31aa442018-07-03 11:57:54 +01001176 snippets = {'generator_script': os.path.basename(__file__)}
1177 read_code_from_input_files(platform_file, helpers_file,
1178 out_data_file, snippets)
1179 add_input_info(funcs_file, data_file, template_file,
1180 c_file, snippets)
1181 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1182 generate_intermediate_data_file(data_file, out_data_file,
1183 suite_dependencies, func_info, snippets)
1184 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001185
1186
Azim Khan8d686bf2018-07-04 23:29:46 +01001187def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001188 """
1189 Command line parser.
1190
1191 :return:
1192 """
Azim Khan040b6a22018-06-28 16:49:13 +01001193 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001194 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001195
1196 parser.add_argument("-f", "--functions-file",
1197 dest="funcs_file",
1198 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001199 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001200 required=True)
1201
1202 parser.add_argument("-d", "--data-file",
1203 dest="data_file",
1204 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001205 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001206 required=True)
1207
1208 parser.add_argument("-t", "--template-file",
1209 dest="template_file",
1210 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001211 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001212 required=True)
1213
1214 parser.add_argument("-s", "--suites-dir",
1215 dest="suites_dir",
1216 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001217 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001218 required=True)
1219
Azim Khane3b26af2018-06-29 02:36:57 +01001220 parser.add_argument("--helpers-file",
1221 dest="helpers_file",
1222 help="Helpers file",
1223 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001224 required=True)
1225
1226 parser.add_argument("-p", "--platform-file",
1227 dest="platform_file",
1228 help="Platform code file",
1229 metavar="PLATFORM_FILE",
1230 required=True)
1231
1232 parser.add_argument("-o", "--out-dir",
1233 dest="out_dir",
1234 help="Dir where generated code and scripts are copied",
1235 metavar="OUT_DIR",
1236 required=True)
1237
1238 args = parser.parse_args()
1239
1240 data_file_name = os.path.basename(args.data_file)
1241 data_name = os.path.splitext(data_file_name)[0]
1242
1243 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001244 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001245
1246 out_c_file_dir = os.path.dirname(out_c_file)
1247 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001248 for directory in [out_c_file_dir, out_data_file_dir]:
1249 if not os.path.exists(directory):
1250 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001251
Azim Khanb31aa442018-07-03 11:57:54 +01001252 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1253 template_file=args.template_file,
1254 platform_file=args.platform_file,
1255 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1256 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001257
1258
1259if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001260 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001261 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001262 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001263 sys.exit("%s: input error: %s" %
1264 (os.path.basename(sys.argv[0]), str(err)))