blob: 5f711bfb19b4d91de532509a1ba6b971d380e476 [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 Rodgman16799db2023-11-02 19:47:20 +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 Horstmann360f8e42022-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 Khanfff49042017-03-28 01:48:31 +0100154import os
155import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100156import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100157import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100158import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100159
160
Gilles Peskine2986acc2023-04-26 19:57:46 +0200161# Types recognized as signed integer arguments in test functions.
Gilles Peskine6f5082b2022-12-04 15:57:49 +0100162SIGNED_INTEGER_TYPES = frozenset([
163 'char',
164 'short',
165 'short int',
166 'int',
167 'int8_t',
168 'int16_t',
169 'int32_t',
170 'int64_t',
171 'intmax_t',
172 'long',
173 'long int',
174 'long long int',
175 'mbedtls_mpi_sint',
176 'psa_status_t',
177])
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100178# Types recognized as string arguments in test functions.
179STRING_TYPES = frozenset(['char*', 'const char*', 'char const*'])
180# Types recognized as hex data arguments in test functions.
181DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*'])
182
Azim Khanb31aa442018-07-03 11:57:54 +0100183BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
184END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100185
Azim Khanb31aa442018-07-03 11:57:54 +0100186BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
187END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000188
Azim Khanb31aa442018-07-03 11:57:54 +0100189BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
190END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100191
Azim Khan8d686bf2018-07-04 23:29:46 +0100192BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100193END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100194
Azim Khan8d686bf2018-07-04 23:29:46 +0100195DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200196C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
197CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
198# forbid 0ddd which might be accidentally octal or accidentally decimal
199CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
200CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
201 CONDITION_OPERATOR_REGEX,
202 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100203TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100204FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
205EXIT_LABEL_REGEX = r'^exit:'
206
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100207
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100208class GeneratorInputError(Exception):
209 """
Azim Khane3b26af2018-06-29 02:36:57 +0100210 Exception to indicate error in the input files to this script.
211 This includes missing patterns, test function names and other
212 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100213 """
214 pass
215
216
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800217class FileWrapper:
Azim Khan4b543232017-06-30 09:35:21 +0100218 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800219 This class extends the file object with attribute line_no,
Azim Khane3b26af2018-06-29 02:36:57 +0100220 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100221 """
222
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800223 def __init__(self, file_name) -> None:
Azim Khan4b543232017-06-30 09:35:21 +0100224 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800225 Instantiate the file object and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100226
Azim Khanf0e42fb2017-08-02 14:47:13 +0100227 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100228 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800229 # private mix-in file object
230 self._f = open(file_name, 'rb')
Azim Khanb31aa442018-07-03 11:57:54 +0100231 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100232
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800233 def __iter__(self):
234 return self
235
Gilles Peskine0c3f5f12022-11-10 19:33:25 +0100236 def __next__(self):
Azim Khan4b543232017-06-30 09:35:21 +0100237 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800238 This method makes FileWrapper iterable.
239 It counts the line numbers as each line is read.
Azim Khane3b26af2018-06-29 02:36:57 +0100240
Azim Khanf0e42fb2017-08-02 14:47:13 +0100241 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100242 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800243 line = self._f.__next__()
244 self._line_no += 1
245 # Convert byte array to string with correct encoding and
246 # strip any whitespaces added in the decoding process.
247 return line.decode(sys.getdefaultencoding()).rstrip()+ '\n'
Azim Khane3b26af2018-06-29 02:36:57 +0100248
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800249 def __enter__(self):
250 return self
251
252 def __exit__(self, exc_type, exc_val, exc_tb):
253 self._f.__exit__(exc_type, exc_val, exc_tb)
254
255 @property
256 def line_no(self):
Azim Khanb31aa442018-07-03 11:57:54 +0100257 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800258 Property that indicates line number for the line that is read.
Azim Khanb31aa442018-07-03 11:57:54 +0100259 """
260 return self._line_no
261
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800262 @property
263 def name(self):
264 """
265 Property that indicates name of the file that is read.
266 """
267 return self._f.name
Azim Khan4b543232017-06-30 09:35:21 +0100268
269
270def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100271 """
Azim Khanb31aa442018-07-03 11:57:54 +0100272 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100273
274 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100275 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
276 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100277 """
Azim Khan4b543232017-06-30 09:35:21 +0100278 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
279
280
Azim Khanb31aa442018-07-03 11:57:54 +0100281def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100282 """
Azim Khane3b26af2018-06-29 02:36:57 +0100283 Test suite data and functions specifies compile time dependencies.
284 This function generates C preprocessor code from the input
285 dependency list. Caller uses the generated preprocessor code to
286 wrap dependent code.
287 A dependency in the input list can have a leading '!' character
288 to negate a condition. '!' is separated from the dependency using
289 function split_dep() and proper preprocessor check is generated
290 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100291
Azim Khanb31aa442018-07-03 11:57:54 +0100292 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100293 :return: if defined and endif code with macro annotations for
294 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100295 """
Azim Khanb31aa442018-07-03 11:57:54 +0100296 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
297 map(split_dep, dependencies)])
298 dep_end = ''.join(['#endif /* %s */\n' %
299 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100300
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100301 return dep_start, dep_end
302
303
Azim Khanb31aa442018-07-03 11:57:54 +0100304def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100305 """
Azim Khanb31aa442018-07-03 11:57:54 +0100306 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100307 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100308
Azim Khanb31aa442018-07-03 11:57:54 +0100309 :param dependencies: List of dependencies.
310 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100311 """
Azim Khanb31aa442018-07-03 11:57:54 +0100312 defines = '#if ' if dependencies else ''
313 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
314 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100315 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100316
317
Azim Khanb31aa442018-07-03 11:57:54 +0100318def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100319 """
Azim Khan040b6a22018-06-28 16:49:13 +0100320 Creates test function wrapper code. A wrapper has the code to
321 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100322
Azim Khanf0e42fb2017-08-02 14:47:13 +0100323 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100324 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100325 :param args_dispatch: List of dispatch arguments.
Gilles Peskineb70c4e02023-04-26 19:59:28 +0200326 Ex: ['(char *) params[0]', '*((int *) params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100327 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100328 """
329 # Then create the wrapper
330 wrapper = '''
331void {name}_wrapper( void ** params )
332{{
Gilles Peskine77761412018-06-18 17:51:40 +0200333{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100334 {name}( {args} );
335}}
Gilles Peskine77761412018-06-18 17:51:40 +0200336'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100337 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100338 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100339 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100340 return wrapper
341
342
Azim Khanb31aa442018-07-03 11:57:54 +0100343def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100344 """
Azim Khane3b26af2018-06-29 02:36:57 +0100345 Test suite code template main_test.function defines a C function
346 array to contain test case functions. This function generates an
347 initializer entry for a function in that array. The entry is
348 composed of a compile time check for the test function
349 dependencies. At compile time the test function is assigned when
350 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100351
Azim Khanf0e42fb2017-08-02 14:47:13 +0100352 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100353 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100354 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100355 """
Azim Khanb31aa442018-07-03 11:57:54 +0100356 if dependencies:
357 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100358 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100359{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100360 {name}_wrapper,
361#else
362 NULL,
363#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100364'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100365 else:
366 dispatch_code = '''
367 {name}_wrapper,
368'''.format(name=name)
369
370 return dispatch_code
371
372
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000373def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374 """
Azim Khane3b26af2018-06-29 02:36:57 +0100375 Matches pattern end_regex to the lines read from the file object.
376 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100377
Azim Khan8d686bf2018-07-04 23:29:46 +0100378 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000379 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100380 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100381 """
Azim Khan4b543232017-06-30 09:35:21 +0100382 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100383 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000384 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100385 break
386 headers += line
387 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100388 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100389 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100390
Azim Khan4b543232017-06-30 09:35:21 +0100391 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100392
393
Azim Khan8d686bf2018-07-04 23:29:46 +0100394def validate_dependency(dependency):
395 """
396 Validates a C macro and raises GeneratorInputError on invalid input.
397 :param dependency: Input macro dependency
398 :return: input dependency stripped of leading & trailing white spaces.
399 """
400 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200401 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100402 raise GeneratorInputError('Invalid dependency %s' % dependency)
403 return dependency
404
405
406def parse_dependencies(inp_str):
407 """
408 Parses dependencies out of inp_str, validates them and returns a
409 list of macros.
410
411 :param inp_str: Input string with macros delimited by ':'.
412 :return: list of dependencies
413 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100414 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100415 return dependencies
416
417
Azim Khanb31aa442018-07-03 11:57:54 +0100418def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100419 """
Azim Khane3b26af2018-06-29 02:36:57 +0100420 Parses test suite dependencies specified at the top of a
421 .function file, that starts with pattern BEGIN_DEPENDENCIES
422 and end with END_DEPENDENCIES. Dependencies are specified
423 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100424
Azim Khan8d686bf2018-07-04 23:29:46 +0100425 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100426 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100427 """
Azim Khanb31aa442018-07-03 11:57:54 +0100428 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100429 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100430 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100431 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100432 try:
433 dependencies = parse_dependencies(match.group('dependencies'))
434 except GeneratorInputError as error:
435 raise GeneratorInputError(
436 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100437 if re.search(END_DEP_REGEX, line):
438 break
439 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100440 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100441 " not found!" % (funcs_f.name,
442 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100443
Azim Khanb31aa442018-07-03 11:57:54 +0100444 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100445
446
Azim Khanb31aa442018-07-03 11:57:54 +0100447def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100448 """
Azim Khane3b26af2018-06-29 02:36:57 +0100449 Parses function dependencies, that are in the same line as
450 comment BEGIN_CASE. Dependencies are specified after pattern
451 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100452
Azim Khan8d686bf2018-07-04 23:29:46 +0100453 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100454 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100455 """
Azim Khanb31aa442018-07-03 11:57:54 +0100456 dependencies = []
457 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100458 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100459 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100460 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100461 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100462 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100463
Azim Khan8d686bf2018-07-04 23:29:46 +0100464 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100465
Azim Khan4084ec72018-07-05 14:20:08 +0100466
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100467ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100468def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch):
469 """
470 Parses one test function's argument declaration.
471
472 :param arg: argument declaration.
473 :param arg_idx: current wrapper argument index.
474 :param args: accumulator of arguments' internal types.
475 :param local_vars: accumulator of internal variable declarations.
476 :param args_dispatch: accumulator of argument usage expressions.
477 :return: the number of new wrapper arguments,
478 or None if the argument declaration is invalid.
479 """
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100480 # Normalize whitespace
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100481 arg = arg.strip()
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100482 arg = re.sub(r'\s*\*\s*', r'*', arg)
483 arg = re.sub(r'\s+', r' ', arg)
484 # Extract name and type
485 m = ARGUMENT_DECLARATION_REGEX.search(arg)
486 if not m:
487 # E.g. "int x[42]"
488 return None
489 typ, _ = m.groups()
Gilles Peskine6f5082b2022-12-04 15:57:49 +0100490 if typ in SIGNED_INTEGER_TYPES:
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100491 args.append('int')
Gilles Peskineb70c4e02023-04-26 19:59:28 +0200492 args_dispatch.append('((mbedtls_test_argument_t *) params[%d])->sint' % arg_idx)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100493 return 1
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100494 if typ in STRING_TYPES:
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100495 args.append('char*')
496 args_dispatch.append('(char *) params[%d]' % arg_idx)
497 return 1
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100498 if typ in DATA_TYPES:
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100499 args.append('hex')
500 # create a structure
501 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
Gilles Peskineb70c4e02023-04-26 19:59:28 +0200502 len_initializer = '((mbedtls_test_argument_t *) params[%d])->len' % (arg_idx+1)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100503 local_vars.append(' data_t data%d = {%s, %s};\n' %
504 (arg_idx, pointer_initializer, len_initializer))
505 args_dispatch.append('&data%d' % arg_idx)
506 return 2
507 return None
508
Gilles Peskine47e2e882022-12-04 14:29:06 +0100509ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S)
Azim Khanfcdf6852018-07-05 17:31:46 +0100510def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100511 """
Azim Khane3b26af2018-06-29 02:36:57 +0100512 Parses test function signature for validation and generates
513 a dispatch wrapper function that translates input test vectors
514 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100515
Azim Khan8d686bf2018-07-04 23:29:46 +0100516 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100517 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100518 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100519 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100520 """
Azim Khan8d686bf2018-07-04 23:29:46 +0100521 # Process arguments, ex: <type> arg1, <type> arg2 )
522 # This script assumes that the argument list is terminated by ')'
523 # i.e. the test functions will not have a function pointer
524 # argument.
Gilles Peskine47e2e882022-12-04 14:29:06 +0100525 m = ARGUMENT_LIST_REGEX.search(line)
526 arg_list = m.group(1).strip()
527 if arg_list in ['', 'void']:
528 return [], '', []
529 args = []
530 local_vars = []
531 args_dispatch = []
532 arg_idx = 0
533 for arg in arg_list.split(','):
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100534 indexes = parse_function_argument(arg, arg_idx,
535 args, local_vars, args_dispatch)
536 if indexes is None:
Azim Khan040b6a22018-06-28 16:49:13 +0100537 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100538 "'char *' or 'data_t'\n%s" % line)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100539 arg_idx += indexes
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100540
Gilles Peskine400cde62022-12-04 14:00:32 +0100541 return args, ''.join(local_vars), args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100542
543
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100544def generate_function_code(name, code, local_vars, args_dispatch,
545 dependencies):
546 """
547 Generate function code with preprocessor checks and parameter dispatch
548 wrapper.
549
550 :param name: Function name
551 :param code: Function code
552 :param local_vars: Local variables for function wrapper
553 :param args_dispatch: Argument dispatch code
554 :param dependencies: Preprocessor dependencies list
555 :return: Final function code
556 """
557 # Add exit label if not present
558 if code.find('exit:') == -1:
559 split_code = code.rsplit('}', 1)
560 if len(split_code) == 2:
561 code = """exit:
562 ;
563}""".join(split_code)
564
565 code += gen_function_wrapper(name, local_vars, args_dispatch)
566 preprocessor_check_start, preprocessor_check_end = \
567 gen_dependencies(dependencies)
568 return preprocessor_check_start + code + preprocessor_check_end
569
Gilles Peskine07510f52022-11-11 16:37:16 +0100570COMMENT_START_REGEX = re.compile(r'/[*/]')
571
572def skip_comments(line, stream):
573 """Remove comments in line.
574
575 If the line contains an unfinished comment, read more lines from stream
576 until the line that contains the comment.
577
578 :return: The original line with inner comments replaced by spaces.
579 Trailing comments and whitespace may be removed completely.
580 """
581 pos = 0
582 while True:
583 opening = COMMENT_START_REGEX.search(line, pos)
584 if not opening:
585 break
586 if line[opening.start(0) + 1] == '/': # //...
587 continuation = line
Gilles Peskine18f70282022-11-30 16:38:49 +0100588 # Count the number of line breaks, to keep line numbers aligned
589 # in the output.
590 line_count = 1
Gilles Peskine07510f52022-11-11 16:37:16 +0100591 while continuation.endswith('\\\n'):
592 # This errors out if the file ends with an unfinished line
Gilles Peskine268ea5a2022-11-18 22:26:03 +0100593 # comment. That's acceptable to not complicate the code further.
Gilles Peskine07510f52022-11-11 16:37:16 +0100594 continuation = next(stream)
Gilles Peskine18f70282022-11-30 16:38:49 +0100595 line_count += 1
596 return line[:opening.start(0)].rstrip() + '\n' * line_count
Gilles Peskine07510f52022-11-11 16:37:16 +0100597 # Parsing /*...*/, looking for the end
598 closing = line.find('*/', opening.end(0))
599 while closing == -1:
600 # This errors out if the file ends with an unfinished block
Gilles Peskine268ea5a2022-11-18 22:26:03 +0100601 # comment. That's acceptable to not complicate the code further.
Gilles Peskine07510f52022-11-11 16:37:16 +0100602 line += next(stream)
603 closing = line.find('*/', opening.end(0))
604 pos = closing + 2
Gilles Peskine9ac62c32022-11-18 22:27:37 +0100605 # Replace inner comment by spaces. There needs to be at least one space
606 # for things like 'int/*ihatespaces*/foo'. Go further and preserve the
Gilles Peskined8c08032022-11-29 22:03:32 +0100607 # width of the comment and line breaks, this way positions in error
608 # messages remain correct.
Gilles Peskine07510f52022-11-11 16:37:16 +0100609 line = (line[:opening.start(0)] +
Gilles Peskined8c08032022-11-29 22:03:32 +0100610 re.sub(r'.', r' ', line[opening.start(0):pos]) +
Gilles Peskine07510f52022-11-11 16:37:16 +0100611 line[pos:])
Gilles Peskined8c08032022-11-29 22:03:32 +0100612 # Strip whitespace at the end of lines (it's irrelevant to error messages).
Gilles Peskine07510f52022-11-11 16:37:16 +0100613 return re.sub(r' +(\n|\Z)', r'\1', line)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100614
Azim Khanb31aa442018-07-03 11:57:54 +0100615def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100616 """
Azim Khan040b6a22018-06-28 16:49:13 +0100617 Parses out a function from function file object and generates
618 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100619
Azim Khanf0e42fb2017-08-02 14:47:13 +0100620 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100621 :param dependencies: List of dependencies
622 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100623 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100624 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100625 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
626 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100627 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100629 # Check function signature. Function signature may be split
630 # across multiple lines. Here we try to find the start of
631 # arguments list, then remove '\n's and apply the regex to
632 # detect function start.
Gilles Peskine07510f52022-11-11 16:37:16 +0100633 line = skip_comments(line, funcs_f)
Azim Khanfcdf6852018-07-05 17:31:46 +0100634 up_to_arg_list_start = code + line[:line.find('(') + 1]
635 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
636 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100637 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100638 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100639 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100640 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100641 for lin in funcs_f:
Gilles Peskine07510f52022-11-11 16:37:16 +0100642 line += skip_comments(lin, funcs_f)
Azim Khan8d686bf2018-07-04 23:29:46 +0100643 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100644 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100645 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100646 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100647 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100648 break
649 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100650 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100651 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100652 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100653
Azim Khanfcdf6852018-07-05 17:31:46 +0100654 # Prefix test function name with 'test_'
655 code = code.replace(name, 'test_' + name, 1)
656 name = 'test_' + name
657
Gowtham Suresh Kumar186731b2023-07-26 15:47:45 +0100658 # If a test function has no arguments then add 'void' argument to
Gowtham Suresh Kumar9da40b82023-07-31 16:38:10 +0100659 # avoid "-Wstrict-prototypes" warnings from clang
Gowtham Suresh Kumar186731b2023-07-26 15:47:45 +0100660 if len(args) == 0:
661 code = code.replace('()', '(void)', 1)
662
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100663 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100664 if re.search(END_CASE_REGEX, line):
665 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100666 if not has_exit_label:
667 has_exit_label = \
668 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100669 code += line
670 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100671 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100672 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100673
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100674 code = line_directive + code
675 code = generate_function_code(name, code, local_vars, args_dispatch,
676 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100677 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100678 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100679
680
681def parse_functions(funcs_f):
682 """
Azim Khane3b26af2018-06-29 02:36:57 +0100683 Parses a test_suite_xxx.function file and returns information
684 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100685
Azim Khanf0e42fb2017-08-02 14:47:13 +0100686 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100687 :return: List of test suite dependencies, test function dispatch
688 code, function code and a dict with function identifiers
689 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100690 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000691 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100692 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100693 suite_functions = ''
694 func_info = {}
695 function_idx = 0
696 dispatch_code = ''
697 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100698 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100699 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000700 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100701 suite_helpers += parse_until_pattern(funcs_f,
702 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100703 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100704 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100705 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100706 try:
707 dependencies = parse_function_dependencies(line)
708 except GeneratorInputError as error:
709 raise GeneratorInputError(
710 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
711 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100712 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100713 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100714 suite_functions += func_code
715 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100716 if func_name in func_info:
717 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100718 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100719 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100720 func_info[func_name] = (function_idx, args)
721 dispatch_code += '/* Function Id: %d */\n' % function_idx
722 dispatch_code += func_dispatch
723 function_idx += 1
724
Azim Khanb31aa442018-07-03 11:57:54 +0100725 func_code = (suite_helpers +
726 suite_functions).join(gen_dependencies(suite_dependencies))
727 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100728
729
Azim Khanb31aa442018-07-03 11:57:54 +0100730def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100731 """
Azim Khanb31aa442018-07-03 11:57:54 +0100732 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100733 Since, return value is used to write back to the intermediate
734 data file, any escape characters in the input are retained in the
735 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100736
Azim Khanb31aa442018-07-03 11:57:54 +0100737 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100738 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100739 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100740 """
Azim Khanb31aa442018-07-03 11:57:54 +0100741 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100742 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100743 out = re.sub(r'(\\.)|' + split_char,
744 lambda m: m.group(1) or '\n', inp_str,
745 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100746 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100747 return out
748
749
Azim Khanb31aa442018-07-03 11:57:54 +0100750def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100751 """
Azim Khane3b26af2018-06-29 02:36:57 +0100752 Parses .data file for each test case name, test function name,
753 test dependencies and test arguments. This information is
754 correlated with the test functions file for generating an
755 intermediate data file replacing the strings for test function
756 names, dependencies and integer constant expressions with
757 identifiers. Mainly for optimising space for on-target
758 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100759
Azim Khanf0e42fb2017-08-02 14:47:13 +0100760 :param data_f: file object of the data file.
Gilles Peskine8542f5c2022-12-03 22:58:52 +0100761 :return: Generator that yields line number, test name, function name,
Azim Khan040b6a22018-06-28 16:49:13 +0100762 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100763 """
Azim Khanb31aa442018-07-03 11:57:54 +0100764 __state_read_name = 0
765 __state_read_args = 1
766 state = __state_read_name
767 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100768 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100769 for line in data_f:
770 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100771 # Skip comments
772 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100773 continue
774
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100775 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100776 if not line:
777 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100778 raise GeneratorInputError("[%s:%d] Newline before arguments. "
779 "Test function and arguments "
780 "missing for %s" %
781 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100782 continue
783
Azim Khanb31aa442018-07-03 11:57:54 +0100784 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100785 # Read test name
786 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100787 state = __state_read_args
788 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100789 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100790 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100791 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100792 try:
793 dependencies = parse_dependencies(
794 match.group('dependencies'))
795 except GeneratorInputError as error:
796 raise GeneratorInputError(
797 str(error) + " - %s:%d" %
798 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100799 else:
800 # Read test vectors
801 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100802 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100803 args = parts[1:]
Gilles Peskine8542f5c2022-12-03 22:58:52 +0100804 yield data_f.line_no, name, test_function, dependencies, args
Azim Khanb31aa442018-07-03 11:57:54 +0100805 dependencies = []
806 state = __state_read_name
807 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100808 raise GeneratorInputError("[%s:%d] Newline before arguments. "
809 "Test function and arguments missing for "
810 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100811
812
813def gen_dep_check(dep_id, dep):
814 """
Azim Khane3b26af2018-06-29 02:36:57 +0100815 Generate code for checking dependency with the associated
816 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100817
Azim Khanf0e42fb2017-08-02 14:47:13 +0100818 :param dep_id: Dependency identifier
819 :param dep: Dependency macro
820 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100821 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100822 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100823 raise GeneratorInputError("Dependency Id should be a positive "
824 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100825 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
826 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100827 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200828
829 dependency = re.match(CONDITION_REGEX, dep, re.I)
830 if not dependency:
831 raise GeneratorInputError('Invalid dependency %s' % dep)
832
833 _defined = '' if dependency.group(2) else 'defined'
834 _cond = dependency.group(2) if dependency.group(2) else ''
835 _value = dependency.group(3) if dependency.group(3) else ''
836
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100837 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100838 case {id}:
839 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200840#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100841 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100842#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100843 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100844#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100845 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200846 break;'''.format(_not=_not, _defined=_defined,
847 macro=dependency.group(1), id=dep_id,
848 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100849 return dep_check
850
851
852def gen_expression_check(exp_id, exp):
853 """
Azim Khane3b26af2018-06-29 02:36:57 +0100854 Generates code for evaluating an integer expression using
855 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100856
Azim Khanf0e42fb2017-08-02 14:47:13 +0100857 :param exp_id: Expression Identifier
858 :param exp: Expression/Macro
859 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100860 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100861 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100862 raise GeneratorInputError("Expression Id should be a positive "
863 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100864 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100865 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100866 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100867 case {exp_id}:
868 {{
869 *out_value = {expression};
870 }}
871 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100872 return exp_code
873
874
Azim Khanb31aa442018-07-03 11:57:54 +0100875def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100876 """
Azim Khane3b26af2018-06-29 02:36:57 +0100877 Write dependencies to intermediate test data file, replacing
878 the string form with identifiers. Also, generates dependency
879 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100880
Azim Khanf0e42fb2017-08-02 14:47:13 +0100881 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100882 :param test_dependencies: Dependencies
883 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100884 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100885 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100886 """
Azim Khan599cd242017-07-06 17:34:27 +0100887 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100888 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100889 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100890 for dep in test_dependencies:
891 if dep not in unique_dependencies:
892 unique_dependencies.append(dep)
893 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100894 dep_check_code += gen_dep_check(dep_id, dep)
895 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100896 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100897 out_data_f.write(':' + str(dep_id))
898 out_data_f.write('\n')
899 return dep_check_code
900
901
Gilles Peskine5226eb52022-12-04 00:28:56 +0100902INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I)
903def val_is_int(val: str) -> bool:
904 """Whether val is suitable as an 'int' parameter in the .datax file."""
905 if not INT_VAL_REGEX.match(val):
906 return False
907 # Limit the range to what is guaranteed to get through strtol()
908 return abs(int(val, 0)) <= 0x7fffffff
909
Azim Khan599cd242017-07-06 17:34:27 +0100910def write_parameters(out_data_f, test_args, func_args, unique_expressions):
911 """
Azim Khane3b26af2018-06-29 02:36:57 +0100912 Writes test parameters to the intermediate data file, replacing
913 the string form with identifiers. Also, generates expression
914 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100915
Azim Khanf0e42fb2017-08-02 14:47:13 +0100916 :param out_data_f: Output intermediate data file
917 :param test_args: Test parameters
918 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100919 :param unique_expressions: Mutable list to track unique
920 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100921 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100922 """
923 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100924 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100925 typ = func_args[i]
926 val = test_args[i]
927
Gilles Peskine5226eb52022-12-04 00:28:56 +0100928 # Pass small integer constants literally. This reduces the size of
929 # the C code. Register anything else as an expression.
930 if typ == 'int' and not val_is_int(val):
Azim Khan599cd242017-07-06 17:34:27 +0100931 typ = 'exp'
932 if val not in unique_expressions:
933 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100934 # exp_id can be derived from len(). But for
935 # readability and consistency with case of existing
936 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100937 exp_id = unique_expressions.index(val)
938 expression_code += gen_expression_check(exp_id, val)
939 val = exp_id
940 else:
941 val = unique_expressions.index(val)
942 out_data_f.write(':' + typ + ':' + str(val))
943 out_data_f.write('\n')
944 return expression_code
945
946
Azim Khanb31aa442018-07-03 11:57:54 +0100947def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100948 """
Azim Khane3b26af2018-06-29 02:36:57 +0100949 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100950
Azim Khanb31aa442018-07-03 11:57:54 +0100951 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100952 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100953 :param dep_check_code: Dependency check code
954 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100955 :return: Dependency and expression code guarded by test suite
956 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100957 """
Azim Khanb31aa442018-07-03 11:57:54 +0100958 if suite_dependencies:
959 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100960 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100961{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100962{code}
Azim Khan599cd242017-07-06 17:34:27 +0100963#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100964'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100965 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100966{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100967{code}
Azim Khan599cd242017-07-06 17:34:27 +0100968#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100969'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100970 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100971
972
Gilles Peskineca25dee2022-12-04 17:27:25 +0100973def get_function_info(func_info, function_name, line_no):
974 """Look up information about a test function by name.
975
976 Raise an informative expression if function_name is not found.
977
978 :param func_info: dictionary mapping function names to their information.
979 :param function_name: the function name as written in the .function and
980 .data files.
981 :param line_no: line number for error messages.
982 :return Function information (id, args).
983 """
984 test_function_name = 'test_' + function_name
985 if test_function_name not in func_info:
986 raise GeneratorInputError("%d: Function %s not found!" %
987 (line_no, test_function_name))
988 return func_info[test_function_name]
989
990
Azim Khanb31aa442018-07-03 11:57:54 +0100991def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100992 """
Azim Khane3b26af2018-06-29 02:36:57 +0100993 This function reads test case name, dependencies and test vectors
994 from the .data file. This information is correlated with the test
995 functions file for generating an intermediate data file replacing
996 the strings for test function names, dependencies and integer
997 constant expressions with identifiers. Mainly for optimising
998 space for on-target execution.
999 It also generates test case dependency check code and expression
1000 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +01001001
Azim Khanf0e42fb2017-08-02 14:47:13 +01001002 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +01001003 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +01001004 :param func_info: Dict keyed by function and with function id
1005 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +01001006 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +01001007 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001008 """
Azim Khanb31aa442018-07-03 11:57:54 +01001009 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001010 unique_expressions = []
1011 dep_check_code = ''
1012 expression_code = ''
Gilles Peskine8542f5c2022-12-03 22:58:52 +01001013 for line_no, test_name, function_name, test_dependencies, test_args in \
Azim Khanb31aa442018-07-03 11:57:54 +01001014 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001015 out_data_f.write(test_name + '\n')
1016
Azim Khanb31aa442018-07-03 11:57:54 +01001017 # Write dependencies
1018 dep_check_code += write_dependencies(out_data_f, test_dependencies,
1019 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001020
Azim Khan599cd242017-07-06 17:34:27 +01001021 # Write test function name
Gilles Peskineca25dee2022-12-04 17:27:25 +01001022 func_id, func_args = \
1023 get_function_info(func_info, function_name, line_no)
Azim Khan599cd242017-07-06 17:34:27 +01001024 out_data_f.write(str(func_id))
1025
1026 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001027 if len(test_args) != len(func_args):
Gilles Peskine8542f5c2022-12-03 22:58:52 +01001028 raise GeneratorInputError("%d: Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +01001029 "%s. See function %s signature." %
Gilles Peskine8542f5c2022-12-03 22:58:52 +01001030 (line_no, test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +01001031 expression_code += write_parameters(out_data_f, test_args, func_args,
1032 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001033
Azim Khan599cd242017-07-06 17:34:27 +01001034 # Write a newline as test case separator
1035 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001036
Azim Khanb31aa442018-07-03 11:57:54 +01001037 dep_check_code, expression_code = gen_suite_dep_checks(
1038 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001039 return dep_check_code, expression_code
1040
1041
Azim Khanb31aa442018-07-03 11:57:54 +01001042def add_input_info(funcs_file, data_file, template_file,
1043 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001044 """
Azim Khanb31aa442018-07-03 11:57:54 +01001045 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001046
Azim Khanf0e42fb2017-08-02 14:47:13 +01001047 :param funcs_file: Functions file object
1048 :param data_file: Data file object
1049 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +01001050 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +01001051 :param snippets: Dictionary to contain code pieces to be
1052 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001053 :return:
1054 """
Azim Khanb31aa442018-07-03 11:57:54 +01001055 snippets['test_file'] = c_file
1056 snippets['test_main_file'] = template_file
1057 snippets['test_case_file'] = funcs_file
1058 snippets['test_case_data_file'] = data_file
1059
1060
1061def read_code_from_input_files(platform_file, helpers_file,
1062 out_data_file, snippets):
1063 """
1064 Read code from input files and create substitutions for replacement
1065 strings in the template file.
1066
1067 :param platform_file: Platform file object
1068 :param helpers_file: Helper functions file object
1069 :param out_data_file: Output intermediate data file object
1070 :param snippets: Dictionary to contain code pieces to be
1071 substituted in the template.
1072 :return:
1073 """
1074 # Read helpers
1075 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
1076 platform_f:
1077 snippets['test_common_helper_file'] = helpers_file
1078 snippets['test_common_helpers'] = help_f.read()
1079 snippets['test_platform_file'] = platform_file
1080 snippets['platform_code'] = platform_f.read().replace(
1081 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
1082
1083
1084def write_test_source_file(template_file, c_file, snippets):
1085 """
1086 Write output source file with generated source code.
1087
1088 :param template_file: Template file name
1089 :param c_file: Output source file
1090 :param snippets: Generated and code snippets
1091 :return:
1092 """
David Horstmannb85838f2022-11-03 17:49:29 +00001093
1094 # Create a placeholder pattern with the correct named capture groups
1095 # to override the default provided with Template.
1096 # Match nothing (no way of escaping placeholders).
1097 escaped = "(?P<escaped>(?!))"
1098 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
1099 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
1100 # Match nothing (no braced placeholder syntax).
1101 braced = "(?P<braced>(?!))"
1102 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
1103 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
David Horstmann360f8e42022-11-09 17:27:33 +00001104 placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid]))
David Horstmannb85838f2022-11-03 17:49:29 +00001105
Azim Khanb31aa442018-07-03 11:57:54 +01001106 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001107 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001108 # Update line number. +1 as #line directive sets next line number
1109 snippets['line_no'] = line_no + 1
David Horstmannb85838f2022-11-03 17:49:29 +00001110 template = string.Template(line)
1111 template.pattern = placeholder_pattern
1112 snippets = {k.upper():v for (k, v) in snippets.items()}
1113 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001114 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001115
1116
1117def parse_function_file(funcs_file, snippets):
1118 """
1119 Parse function file and generate function dispatch code.
1120
1121 :param funcs_file: Functions file name
1122 :param snippets: Dictionary to contain code pieces to be
1123 substituted in the template.
1124 :return:
1125 """
1126 with FileWrapper(funcs_file) as funcs_f:
1127 suite_dependencies, dispatch_code, func_code, func_info = \
1128 parse_functions(funcs_f)
1129 snippets['functions_code'] = func_code
1130 snippets['dispatch_code'] = dispatch_code
1131 return suite_dependencies, func_info
1132
1133
1134def generate_intermediate_data_file(data_file, out_data_file,
1135 suite_dependencies, func_info, snippets):
1136 """
1137 Generates intermediate data file from input data file and
1138 information read from functions file.
1139
1140 :param data_file: Data file name
1141 :param out_data_file: Output/Intermediate data file
1142 :param suite_dependencies: List of suite dependencies.
1143 :param func_info: Function info parsed from functions file.
1144 :param snippets: Dictionary to contain code pieces to be
1145 substituted in the template.
1146 :return:
1147 """
1148 with FileWrapper(data_file) as data_f, \
1149 open(out_data_file, 'w') as out_data_f:
1150 dep_check_code, expression_code = gen_from_test_data(
1151 data_f, out_data_f, func_info, suite_dependencies)
1152 snippets['dep_check_code'] = dep_check_code
1153 snippets['expression_code'] = expression_code
1154
1155
1156def generate_code(**input_info):
1157 """
1158 Generates C source code from test suite file, data file, common
1159 helpers file and platform file.
1160
1161 input_info expands to following parameters:
1162 funcs_file: Functions file object
1163 data_file: Data file object
1164 template_file: Template file object
1165 platform_file: Platform file object
1166 helpers_file: Helper functions file object
1167 suites_dir: Test suites dir
1168 c_file: Output C file object
1169 out_data_file: Output intermediate data file object
1170 :return:
1171 """
1172 funcs_file = input_info['funcs_file']
1173 data_file = input_info['data_file']
1174 template_file = input_info['template_file']
1175 platform_file = input_info['platform_file']
1176 helpers_file = input_info['helpers_file']
1177 suites_dir = input_info['suites_dir']
1178 c_file = input_info['c_file']
1179 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001180 for name, path in [('Functions file', funcs_file),
1181 ('Data file', data_file),
1182 ('Template file', template_file),
1183 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001184 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001185 ('Suites dir', suites_dir)]:
1186 if not os.path.exists(path):
1187 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1188
Azim Khanb31aa442018-07-03 11:57:54 +01001189 snippets = {'generator_script': os.path.basename(__file__)}
1190 read_code_from_input_files(platform_file, helpers_file,
1191 out_data_file, snippets)
1192 add_input_info(funcs_file, data_file, template_file,
1193 c_file, snippets)
1194 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1195 generate_intermediate_data_file(data_file, out_data_file,
1196 suite_dependencies, func_info, snippets)
1197 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001198
1199
Azim Khan8d686bf2018-07-04 23:29:46 +01001200def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001201 """
1202 Command line parser.
1203
1204 :return:
1205 """
Azim Khan040b6a22018-06-28 16:49:13 +01001206 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001207 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001208
1209 parser.add_argument("-f", "--functions-file",
1210 dest="funcs_file",
1211 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001212 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001213 required=True)
1214
1215 parser.add_argument("-d", "--data-file",
1216 dest="data_file",
1217 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001218 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001219 required=True)
1220
1221 parser.add_argument("-t", "--template-file",
1222 dest="template_file",
1223 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001224 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001225 required=True)
1226
1227 parser.add_argument("-s", "--suites-dir",
1228 dest="suites_dir",
1229 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001230 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001231 required=True)
1232
Azim Khane3b26af2018-06-29 02:36:57 +01001233 parser.add_argument("--helpers-file",
1234 dest="helpers_file",
1235 help="Helpers file",
1236 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001237 required=True)
1238
1239 parser.add_argument("-p", "--platform-file",
1240 dest="platform_file",
1241 help="Platform code file",
1242 metavar="PLATFORM_FILE",
1243 required=True)
1244
1245 parser.add_argument("-o", "--out-dir",
1246 dest="out_dir",
1247 help="Dir where generated code and scripts are copied",
1248 metavar="OUT_DIR",
1249 required=True)
1250
1251 args = parser.parse_args()
1252
1253 data_file_name = os.path.basename(args.data_file)
1254 data_name = os.path.splitext(data_file_name)[0]
1255
1256 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001257 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001258
1259 out_c_file_dir = os.path.dirname(out_c_file)
1260 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001261 for directory in [out_c_file_dir, out_data_file_dir]:
1262 if not os.path.exists(directory):
1263 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001264
Azim Khanb31aa442018-07-03 11:57:54 +01001265 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1266 template_file=args.template_file,
1267 platform_file=args.platform_file,
1268 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1269 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001270
1271
1272if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001273 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001274 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001275 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001276 sys.exit("%s: input error: %s" %
1277 (os.path.basename(sys.argv[0]), str(err)))