blob: 18a29d1e69ccc8c0acbd19fc14b306993e39f6c9 [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
Azim Khanf0e42fb2017-08-02 14:47:13 +01005# SPDX-License-Identifier: Apache-2.0
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
Azim Khanf0e42fb2017-08-02 14:47:13 +010018
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010019"""
Azim Khanaee05bb2018-07-02 16:01:04 +010020This script is a key part of Mbed TLS test suites framework. For
21understanding the script it is important to understand the
22framework. This doc string contains a summary of the framework
23and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010024
Azim Khanaee05bb2018-07-02 16:01:04 +010025Mbed TLS test suites:
26=====================
27Scope:
28------
29The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010030include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010031module. However, the framework is not capable of testing SSL
32protocol, since that requires full stack execution and that is best
33tested as part of the system test.
34
35Test case definition:
36---------------------
37Tests are defined in a test_suite_<module>[.<optional sub module>].data
38file. A test definition contains:
39 test name
40 optional build macro dependencies
41 test function
42 test parameters
43
44Test dependencies are build macros that can be specified to indicate
45the build config in which the test is valid. For example if a test
46depends on a feature that is only enabled by defining a macro. Then
47that macro should be specified as a dependency of the test.
48
49Test function is the function that implements the test steps. This
50function is specified for different tests that perform same steps
51with different parameters.
52
53Test parameters are specified in string form separated by ':'.
54Parameters can be of type string, binary data specified as hex
55string and integer constants specified as integer, macro or
56as an expression. Following is an example test definition:
57
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010058 AES 128 GCM Encrypt and decrypt 8 bytes
59 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
60 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010061
62Test functions:
63---------------
64Test functions are coded in C in test_suite_<module>.function files.
65Functions file is itself not compilable and contains special
66format patterns to specify test suite dependencies, start and end
67of functions and function dependencies. Check any existing functions
68file for example.
69
70Execution:
71----------
72Tests are executed in 3 steps:
73- Generating test_suite_<module>[.<optional sub module>].c file
74 for each corresponding .data file.
75- Building each source file into executables.
76- Running each executable and printing report.
77
78Generating C test source requires more than just the test functions.
79Following extras are required:
80- Process main()
81- Reading .data file and dispatching test cases.
82- Platform specific test case execution
83- Dependency checking
84- Integer expression evaluation
85- Test function dispatch
86
87Build dependencies and integer expressions (in the test parameters)
88are specified as strings in the .data file. Their run time value is
89not known at the generation stage. Hence, they need to be translated
90into run time evaluations. This script generates the run time checks
91for dependencies and integer expressions.
92
93Similarly, function names have to be translated into function calls.
94This script also generates code for function dispatch.
95
96The extra code mentioned here is either generated by this script
97or it comes from the input files: helpers file, platform file and
98the template file.
99
100Helper file:
101------------
102Helpers file contains common helper/utility functions and data.
103
104Platform file:
105--------------
106Platform file contains platform specific setup code and test case
107dispatch code. For example, host_test.function reads test data
108file from host's file system and dispatches tests.
Azim Khanaee05bb2018-07-02 16:01:04 +0100109
110Template file:
111---------
112Template file for example main_test.function is a template C file in
113which generated code and code from input files is substituted to
114generate a compilable C file. It also contains skeleton functions for
115dependency checks, expression evaluation and function dispatch. These
116functions are populated with checks and return codes by this script.
117
118Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100119strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100120
121This script:
122============
123Core function of this script is to fill the template file with
124code that is generated or read from helpers and platform files.
125
126This script replaces following fields in the template and generates
127the test source file:
128
David Horstmann8eff06f2022-11-09 17:27:33 +0000129__MBEDTLS_TEST_TEMPLATE__TEST_COMMON_HELPERS
130 All common code from helpers.function
131 is substituted here.
132__MBEDTLS_TEST_TEMPLATE__FUNCTIONS_CODE
133 Test functions are substituted here
134 from the input test_suit_xyz.function
135 file. C preprocessor checks are generated
136 for the build dependencies specified
137 in the input file. This script also
138 generates wrappers for the test
139 functions with code to expand the
140 string parameters read from the data
141 file.
142__MBEDTLS_TEST_TEMPLATE__EXPRESSION_CODE
143 This script enumerates the
144 expressions in the .data file and
145 generates code to handle enumerated
146 expression Ids and return the values.
147__MBEDTLS_TEST_TEMPLATE__DEP_CHECK_CODE
148 This script enumerates all
149 build dependencies and generate
150 code to handle enumerated build
151 dependency Id and return status: if
152 the dependency is defined or not.
153__MBEDTLS_TEST_TEMPLATE__DISPATCH_CODE
154 This script enumerates the functions
155 specified in the input test data file
156 and generates the initializer for the
157 function table in the template
158 file.
159__MBEDTLS_TEST_TEMPLATE__PLATFORM_CODE
160 Platform specific setup and test
161 dispatch code.
Azim Khanaee05bb2018-07-02 16:01:04 +0100162
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100163"""
164
Azim Khanf0e42fb2017-08-02 14:47:13 +0100165
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100166import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100167import os
168import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100169import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100170import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100171import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100172
173
Gilles Peskine47903b12023-04-26 19:57:46 +0200174# Types recognized as signed integer arguments in test functions.
Gilles Peskineba726622022-12-04 15:57:49 +0100175SIGNED_INTEGER_TYPES = frozenset([
176 'char',
177 'short',
178 'short int',
179 'int',
180 'int8_t',
181 'int16_t',
182 'int32_t',
183 'int64_t',
184 'intmax_t',
185 'long',
186 'long int',
187 'long long int',
188 'mbedtls_mpi_sint',
189 'psa_status_t',
190])
Gilles Peskine615be632022-12-04 15:11:00 +0100191# Types recognized as string arguments in test functions.
192STRING_TYPES = frozenset(['char*', 'const char*', 'char const*'])
193# Types recognized as hex data arguments in test functions.
194DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*'])
195
Azim Khanb31aa442018-07-03 11:57:54 +0100196BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
197END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100198
Azim Khanb31aa442018-07-03 11:57:54 +0100199BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
200END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000201
Azim Khanb31aa442018-07-03 11:57:54 +0100202BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
203END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100204
Azim Khan8d686bf2018-07-04 23:29:46 +0100205BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100206END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100207
Azim Khan8d686bf2018-07-04 23:29:46 +0100208DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200209C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
210CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
211# forbid 0ddd which might be accidentally octal or accidentally decimal
212CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
213CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
214 CONDITION_OPERATOR_REGEX,
215 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100216TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100217FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
218EXIT_LABEL_REGEX = r'^exit:'
219
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100220
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100221class GeneratorInputError(Exception):
222 """
Azim Khane3b26af2018-06-29 02:36:57 +0100223 Exception to indicate error in the input files to this script.
224 This includes missing patterns, test function names and other
225 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100226 """
227 pass
228
229
Gilles Peskine184c0962020-03-24 18:25:17 +0100230class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100231 """
Azim Khane3b26af2018-06-29 02:36:57 +0100232 This class extends built-in io.FileIO class with attribute line_no,
233 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100234 """
235
236 def __init__(self, file_name):
237 """
Azim Khane3b26af2018-06-29 02:36:57 +0100238 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100239
Azim Khanf0e42fb2017-08-02 14:47:13 +0100240 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100241 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100242 super().__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100243 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100244
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100245 def __next__(self):
Azim Khan4b543232017-06-30 09:35:21 +0100246 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100247 This method overrides base class's __next__ method and extends it
248 method to count the line numbers as each line is read.
Azim Khane3b26af2018-06-29 02:36:57 +0100249
Azim Khanf0e42fb2017-08-02 14:47:13 +0100250 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100251 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100252 line = super().__next__()
Azim Khanb31aa442018-07-03 11:57:54 +0100253 if line is not None:
254 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100255 # Convert byte array to string with correct encoding and
256 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100257 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100258 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100259
Azim Khanb31aa442018-07-03 11:57:54 +0100260 def get_line_no(self):
261 """
262 Gives current line number.
263 """
264 return self._line_no
265
266 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100267
268
269def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100270 """
Azim Khanb31aa442018-07-03 11:57:54 +0100271 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100272
273 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100274 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
275 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100276 """
Azim Khan4b543232017-06-30 09:35:21 +0100277 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
278
279
Azim Khanb31aa442018-07-03 11:57:54 +0100280def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100281 """
Azim Khane3b26af2018-06-29 02:36:57 +0100282 Test suite data and functions specifies compile time dependencies.
283 This function generates C preprocessor code from the input
284 dependency list. Caller uses the generated preprocessor code to
285 wrap dependent code.
286 A dependency in the input list can have a leading '!' character
287 to negate a condition. '!' is separated from the dependency using
288 function split_dep() and proper preprocessor check is generated
289 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100290
Azim Khanb31aa442018-07-03 11:57:54 +0100291 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100292 :return: if defined and endif code with macro annotations for
293 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100294 """
Azim Khanb31aa442018-07-03 11:57:54 +0100295 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
296 map(split_dep, dependencies)])
297 dep_end = ''.join(['#endif /* %s */\n' %
298 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100299
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100300 return dep_start, dep_end
301
302
Azim Khanb31aa442018-07-03 11:57:54 +0100303def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100304 """
Azim Khanb31aa442018-07-03 11:57:54 +0100305 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100306 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
Azim Khanb31aa442018-07-03 11:57:54 +0100308 :param dependencies: List of dependencies.
309 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100310 """
Azim Khanb31aa442018-07-03 11:57:54 +0100311 defines = '#if ' if dependencies else ''
312 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
313 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100314 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100315
316
Azim Khanb31aa442018-07-03 11:57:54 +0100317def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100318 """
Azim Khan040b6a22018-06-28 16:49:13 +0100319 Creates test function wrapper code. A wrapper has the code to
320 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100321
Azim Khanf0e42fb2017-08-02 14:47:13 +0100322 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100323 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100324 :param args_dispatch: List of dispatch arguments.
325 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100326 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100327 """
328 # Then create the wrapper
329 wrapper = '''
330void {name}_wrapper( void ** params )
331{{
Gilles Peskine77761412018-06-18 17:51:40 +0200332{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100333 {name}( {args} );
334}}
Gilles Peskine77761412018-06-18 17:51:40 +0200335'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100336 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100337 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100338 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100339 return wrapper
340
341
Azim Khanb31aa442018-07-03 11:57:54 +0100342def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100343 """
Azim Khane3b26af2018-06-29 02:36:57 +0100344 Test suite code template main_test.function defines a C function
345 array to contain test case functions. This function generates an
346 initializer entry for a function in that array. The entry is
347 composed of a compile time check for the test function
348 dependencies. At compile time the test function is assigned when
349 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100350
Azim Khanf0e42fb2017-08-02 14:47:13 +0100351 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100352 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100353 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100354 """
Azim Khanb31aa442018-07-03 11:57:54 +0100355 if dependencies:
356 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100357 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100358{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100359 {name}_wrapper,
360#else
361 NULL,
362#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100363'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100364 else:
365 dispatch_code = '''
366 {name}_wrapper,
367'''.format(name=name)
368
369 return dispatch_code
370
371
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000372def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100373 """
Azim Khane3b26af2018-06-29 02:36:57 +0100374 Matches pattern end_regex to the lines read from the file object.
375 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100376
Azim Khan8d686bf2018-07-04 23:29:46 +0100377 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000378 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100379 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100380 """
Azim Khan4b543232017-06-30 09:35:21 +0100381 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100382 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000383 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100384 break
385 headers += line
386 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100387 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100388 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100389
Azim Khan4b543232017-06-30 09:35:21 +0100390 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100391
392
Azim Khan8d686bf2018-07-04 23:29:46 +0100393def validate_dependency(dependency):
394 """
395 Validates a C macro and raises GeneratorInputError on invalid input.
396 :param dependency: Input macro dependency
397 :return: input dependency stripped of leading & trailing white spaces.
398 """
399 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200400 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100401 raise GeneratorInputError('Invalid dependency %s' % dependency)
402 return dependency
403
404
405def parse_dependencies(inp_str):
406 """
407 Parses dependencies out of inp_str, validates them and returns a
408 list of macros.
409
410 :param inp_str: Input string with macros delimited by ':'.
411 :return: list of dependencies
412 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100413 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100414 return dependencies
415
416
Azim Khanb31aa442018-07-03 11:57:54 +0100417def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100418 """
Azim Khane3b26af2018-06-29 02:36:57 +0100419 Parses test suite dependencies specified at the top of a
420 .function file, that starts with pattern BEGIN_DEPENDENCIES
421 and end with END_DEPENDENCIES. Dependencies are specified
422 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100423
Azim Khan8d686bf2018-07-04 23:29:46 +0100424 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100425 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100426 """
Azim Khanb31aa442018-07-03 11:57:54 +0100427 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100428 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100429 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100430 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100431 try:
432 dependencies = parse_dependencies(match.group('dependencies'))
433 except GeneratorInputError as error:
434 raise GeneratorInputError(
435 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100436 if re.search(END_DEP_REGEX, line):
437 break
438 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100439 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100440 " not found!" % (funcs_f.name,
441 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100442
Azim Khanb31aa442018-07-03 11:57:54 +0100443 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100444
445
Azim Khanb31aa442018-07-03 11:57:54 +0100446def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100447 """
Azim Khane3b26af2018-06-29 02:36:57 +0100448 Parses function dependencies, that are in the same line as
449 comment BEGIN_CASE. Dependencies are specified after pattern
450 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100451
Azim Khan8d686bf2018-07-04 23:29:46 +0100452 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100453 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100454 """
Azim Khanb31aa442018-07-03 11:57:54 +0100455 dependencies = []
456 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100457 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100458 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100459 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100460 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100461 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100462
Azim Khan8d686bf2018-07-04 23:29:46 +0100463 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100464
Azim Khan4084ec72018-07-05 14:20:08 +0100465
Gilles Peskine615be632022-12-04 15:11:00 +0100466ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S)
Gilles Peskinef153c562022-12-04 14:10:39 +0100467def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch):
468 """
469 Parses one test function's argument declaration.
470
471 :param arg: argument declaration.
472 :param arg_idx: current wrapper argument index.
473 :param args: accumulator of arguments' internal types.
474 :param local_vars: accumulator of internal variable declarations.
475 :param args_dispatch: accumulator of argument usage expressions.
476 :return: the number of new wrapper arguments,
477 or None if the argument declaration is invalid.
478 """
Gilles Peskine615be632022-12-04 15:11:00 +0100479 # Normalize whitespace
Gilles Peskinef153c562022-12-04 14:10:39 +0100480 arg = arg.strip()
Gilles Peskine615be632022-12-04 15:11:00 +0100481 arg = re.sub(r'\s*\*\s*', r'*', arg)
482 arg = re.sub(r'\s+', r' ', arg)
483 # Extract name and type
484 m = ARGUMENT_DECLARATION_REGEX.search(arg)
485 if not m:
486 # E.g. "int x[42]"
487 return None
488 typ, _ = m.groups()
Gilles Peskineba726622022-12-04 15:57:49 +0100489 if typ in SIGNED_INTEGER_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100490 args.append('int')
Gilles Peskine6494d922022-12-04 15:32:54 +0100491 args_dispatch.append('((mbedtls_test_argument_t*)params[%d])->sint' % arg_idx)
Gilles Peskinef153c562022-12-04 14:10:39 +0100492 return 1
Gilles Peskine615be632022-12-04 15:11:00 +0100493 if typ in STRING_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100494 args.append('char*')
495 args_dispatch.append('(char *) params[%d]' % arg_idx)
496 return 1
Gilles Peskine615be632022-12-04 15:11:00 +0100497 if typ in DATA_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100498 args.append('hex')
499 # create a structure
500 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
501 len_initializer = '((mbedtls_test_argument_t*)params[%d])->len' % (arg_idx+1)
502 local_vars.append(' data_t data%d = {%s, %s};\n' %
503 (arg_idx, pointer_initializer, len_initializer))
504 args_dispatch.append('&data%d' % arg_idx)
505 return 2
506 return None
507
Gilles Peskine46476e02022-12-04 14:29:06 +0100508ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S)
Azim Khanfcdf6852018-07-05 17:31:46 +0100509def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100510 """
Azim Khane3b26af2018-06-29 02:36:57 +0100511 Parses test function signature for validation and generates
512 a dispatch wrapper function that translates input test vectors
513 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100514
Azim Khan8d686bf2018-07-04 23:29:46 +0100515 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100516 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100517 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100518 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100519 """
Azim Khan8d686bf2018-07-04 23:29:46 +0100520 # Process arguments, ex: <type> arg1, <type> arg2 )
521 # This script assumes that the argument list is terminated by ')'
522 # i.e. the test functions will not have a function pointer
523 # argument.
Gilles Peskine46476e02022-12-04 14:29:06 +0100524 m = ARGUMENT_LIST_REGEX.search(line)
525 arg_list = m.group(1).strip()
526 if arg_list in ['', 'void']:
527 return [], '', []
528 args = []
529 local_vars = []
530 args_dispatch = []
531 arg_idx = 0
532 for arg in arg_list.split(','):
Gilles Peskinef153c562022-12-04 14:10:39 +0100533 indexes = parse_function_argument(arg, arg_idx,
534 args, local_vars, args_dispatch)
535 if indexes is None:
Azim Khan040b6a22018-06-28 16:49:13 +0100536 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100537 "'char *' or 'data_t'\n%s" % line)
Gilles Peskinef153c562022-12-04 14:10:39 +0100538 arg_idx += indexes
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100539
Gilles Peskine3a37f192022-12-04 14:00:32 +0100540 return args, ''.join(local_vars), args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100541
542
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100543def generate_function_code(name, code, local_vars, args_dispatch,
544 dependencies):
545 """
546 Generate function code with preprocessor checks and parameter dispatch
547 wrapper.
548
549 :param name: Function name
550 :param code: Function code
551 :param local_vars: Local variables for function wrapper
552 :param args_dispatch: Argument dispatch code
553 :param dependencies: Preprocessor dependencies list
554 :return: Final function code
555 """
556 # Add exit label if not present
557 if code.find('exit:') == -1:
558 split_code = code.rsplit('}', 1)
559 if len(split_code) == 2:
560 code = """exit:
561 ;
562}""".join(split_code)
563
564 code += gen_function_wrapper(name, local_vars, args_dispatch)
565 preprocessor_check_start, preprocessor_check_end = \
566 gen_dependencies(dependencies)
567 return preprocessor_check_start + code + preprocessor_check_end
568
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100569COMMENT_START_REGEX = re.compile(r'/[*/]')
570
571def skip_comments(line, stream):
572 """Remove comments in line.
573
574 If the line contains an unfinished comment, read more lines from stream
575 until the line that contains the comment.
576
577 :return: The original line with inner comments replaced by spaces.
578 Trailing comments and whitespace may be removed completely.
579 """
580 pos = 0
581 while True:
582 opening = COMMENT_START_REGEX.search(line, pos)
583 if not opening:
584 break
585 if line[opening.start(0) + 1] == '/': # //...
586 continuation = line
Gilles Peskineaec4bec2022-11-30 16:38:49 +0100587 # Count the number of line breaks, to keep line numbers aligned
588 # in the output.
589 line_count = 1
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100590 while continuation.endswith('\\\n'):
591 # This errors out if the file ends with an unfinished line
Gilles Peskine43febf22022-11-18 22:26:03 +0100592 # comment. That's acceptable to not complicate the code further.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100593 continuation = next(stream)
Gilles Peskineaec4bec2022-11-30 16:38:49 +0100594 line_count += 1
595 return line[:opening.start(0)].rstrip() + '\n' * line_count
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100596 # Parsing /*...*/, looking for the end
597 closing = line.find('*/', opening.end(0))
598 while closing == -1:
599 # This errors out if the file ends with an unfinished block
Gilles Peskine43febf22022-11-18 22:26:03 +0100600 # comment. That's acceptable to not complicate the code further.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100601 line += next(stream)
602 closing = line.find('*/', opening.end(0))
603 pos = closing + 2
Gilles Peskine7e8d4b62022-11-18 22:27:37 +0100604 # Replace inner comment by spaces. There needs to be at least one space
605 # for things like 'int/*ihatespaces*/foo'. Go further and preserve the
Gilles Peskine07995fd2022-11-29 22:03:32 +0100606 # width of the comment and line breaks, this way positions in error
607 # messages remain correct.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100608 line = (line[:opening.start(0)] +
Gilles Peskine07995fd2022-11-29 22:03:32 +0100609 re.sub(r'.', r' ', line[opening.start(0):pos]) +
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100610 line[pos:])
Gilles Peskine07995fd2022-11-29 22:03:32 +0100611 # Strip whitespace at the end of lines (it's irrelevant to error messages).
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100612 return re.sub(r' +(\n|\Z)', r'\1', line)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100613
Azim Khanb31aa442018-07-03 11:57:54 +0100614def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100615 """
Azim Khan040b6a22018-06-28 16:49:13 +0100616 Parses out a function from function file object and generates
617 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100618
Azim Khanf0e42fb2017-08-02 14:47:13 +0100619 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100620 :param dependencies: List of dependencies
621 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100622 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100623 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100624 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
625 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100626 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100627 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100628 # Check function signature. Function signature may be split
629 # across multiple lines. Here we try to find the start of
630 # arguments list, then remove '\n's and apply the regex to
631 # detect function start.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100632 line = skip_comments(line, funcs_f)
Azim Khanfcdf6852018-07-05 17:31:46 +0100633 up_to_arg_list_start = code + line[:line.find('(') + 1]
634 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
635 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100636 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100637 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100638 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100639 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100640 for lin in funcs_f:
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100641 line += skip_comments(lin, funcs_f)
Azim Khan8d686bf2018-07-04 23:29:46 +0100642 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100643 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100644 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100645 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100646 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100647 break
648 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100649 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100650 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100651 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100652
Azim Khanfcdf6852018-07-05 17:31:46 +0100653 # Prefix test function name with 'test_'
654 code = code.replace(name, 'test_' + name, 1)
655 name = 'test_' + name
656
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100657 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100658 if re.search(END_CASE_REGEX, line):
659 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100660 if not has_exit_label:
661 has_exit_label = \
662 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100663 code += line
664 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100665 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100666 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100667
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100668 code = line_directive + code
669 code = generate_function_code(name, code, local_vars, args_dispatch,
670 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100671 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100672 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100673
674
675def parse_functions(funcs_f):
676 """
Azim Khane3b26af2018-06-29 02:36:57 +0100677 Parses a test_suite_xxx.function file and returns information
678 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100679
Azim Khanf0e42fb2017-08-02 14:47:13 +0100680 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100681 :return: List of test suite dependencies, test function dispatch
682 code, function code and a dict with function identifiers
683 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100684 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000685 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100686 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100687 suite_functions = ''
688 func_info = {}
689 function_idx = 0
690 dispatch_code = ''
691 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100692 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100693 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000694 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100695 suite_helpers += parse_until_pattern(funcs_f,
696 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100697 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100698 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100699 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100700 try:
701 dependencies = parse_function_dependencies(line)
702 except GeneratorInputError as error:
703 raise GeneratorInputError(
704 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
705 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100706 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100707 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100708 suite_functions += func_code
709 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100710 if func_name in func_info:
711 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100712 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100713 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100714 func_info[func_name] = (function_idx, args)
715 dispatch_code += '/* Function Id: %d */\n' % function_idx
716 dispatch_code += func_dispatch
717 function_idx += 1
718
Azim Khanb31aa442018-07-03 11:57:54 +0100719 func_code = (suite_helpers +
720 suite_functions).join(gen_dependencies(suite_dependencies))
721 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100722
723
Azim Khanb31aa442018-07-03 11:57:54 +0100724def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100725 """
Azim Khanb31aa442018-07-03 11:57:54 +0100726 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100727 Since, return value is used to write back to the intermediate
728 data file, any escape characters in the input are retained in the
729 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100730
Azim Khanb31aa442018-07-03 11:57:54 +0100731 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100732 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100733 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100734 """
Azim Khanb31aa442018-07-03 11:57:54 +0100735 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100736 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100737 out = re.sub(r'(\\.)|' + split_char,
738 lambda m: m.group(1) or '\n', inp_str,
739 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100740 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100741 return out
742
743
Azim Khanb31aa442018-07-03 11:57:54 +0100744def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100745 """
Azim Khane3b26af2018-06-29 02:36:57 +0100746 Parses .data file for each test case name, test function name,
747 test dependencies and test arguments. This information is
748 correlated with the test functions file for generating an
749 intermediate data file replacing the strings for test function
750 names, dependencies and integer constant expressions with
751 identifiers. Mainly for optimising space for on-target
752 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100753
Azim Khanf0e42fb2017-08-02 14:47:13 +0100754 :param data_f: file object of the data file.
Gilles Peskinef122aed2022-12-03 22:58:52 +0100755 :return: Generator that yields line number, test name, function name,
Azim Khan040b6a22018-06-28 16:49:13 +0100756 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100757 """
Azim Khanb31aa442018-07-03 11:57:54 +0100758 __state_read_name = 0
759 __state_read_args = 1
760 state = __state_read_name
761 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100762 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100763 for line in data_f:
764 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100765 # Skip comments
766 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100767 continue
768
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100769 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100770 if not line:
771 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100772 raise GeneratorInputError("[%s:%d] Newline before arguments. "
773 "Test function and arguments "
774 "missing for %s" %
775 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100776 continue
777
Azim Khanb31aa442018-07-03 11:57:54 +0100778 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100779 # Read test name
780 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100781 state = __state_read_args
782 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100783 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100784 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100785 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100786 try:
787 dependencies = parse_dependencies(
788 match.group('dependencies'))
789 except GeneratorInputError as error:
790 raise GeneratorInputError(
791 str(error) + " - %s:%d" %
792 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100793 else:
794 # Read test vectors
795 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100796 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100797 args = parts[1:]
Gilles Peskinef122aed2022-12-03 22:58:52 +0100798 yield data_f.line_no, name, test_function, dependencies, args
Azim Khanb31aa442018-07-03 11:57:54 +0100799 dependencies = []
800 state = __state_read_name
801 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100802 raise GeneratorInputError("[%s:%d] Newline before arguments. "
803 "Test function and arguments missing for "
804 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100805
806
807def gen_dep_check(dep_id, dep):
808 """
Azim Khane3b26af2018-06-29 02:36:57 +0100809 Generate code for checking dependency with the associated
810 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100811
Azim Khanf0e42fb2017-08-02 14:47:13 +0100812 :param dep_id: Dependency identifier
813 :param dep: Dependency macro
814 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100815 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100816 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100817 raise GeneratorInputError("Dependency Id should be a positive "
818 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100819 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
820 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100821 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200822
823 dependency = re.match(CONDITION_REGEX, dep, re.I)
824 if not dependency:
825 raise GeneratorInputError('Invalid dependency %s' % dep)
826
827 _defined = '' if dependency.group(2) else 'defined'
828 _cond = dependency.group(2) if dependency.group(2) else ''
829 _value = dependency.group(3) if dependency.group(3) else ''
830
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100831 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100832 case {id}:
833 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200834#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100835 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100836#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100837 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100838#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100839 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200840 break;'''.format(_not=_not, _defined=_defined,
841 macro=dependency.group(1), id=dep_id,
842 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100843 return dep_check
844
845
846def gen_expression_check(exp_id, exp):
847 """
Azim Khane3b26af2018-06-29 02:36:57 +0100848 Generates code for evaluating an integer expression using
849 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100850
Azim Khanf0e42fb2017-08-02 14:47:13 +0100851 :param exp_id: Expression Identifier
852 :param exp: Expression/Macro
853 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100854 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100855 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100856 raise GeneratorInputError("Expression Id should be a positive "
857 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100858 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100859 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100860 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100861 case {exp_id}:
862 {{
863 *out_value = {expression};
864 }}
865 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100866 return exp_code
867
868
Azim Khanb31aa442018-07-03 11:57:54 +0100869def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100870 """
Azim Khane3b26af2018-06-29 02:36:57 +0100871 Write dependencies to intermediate test data file, replacing
872 the string form with identifiers. Also, generates dependency
873 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100874
Azim Khanf0e42fb2017-08-02 14:47:13 +0100875 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100876 :param test_dependencies: Dependencies
877 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100878 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100879 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100880 """
Azim Khan599cd242017-07-06 17:34:27 +0100881 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100882 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100883 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100884 for dep in test_dependencies:
885 if dep not in unique_dependencies:
886 unique_dependencies.append(dep)
887 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100888 dep_check_code += gen_dep_check(dep_id, dep)
889 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100890 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100891 out_data_f.write(':' + str(dep_id))
892 out_data_f.write('\n')
893 return dep_check_code
894
895
Gilles Peskinea2990432022-12-04 00:28:56 +0100896INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I)
897def val_is_int(val: str) -> bool:
898 """Whether val is suitable as an 'int' parameter in the .datax file."""
899 if not INT_VAL_REGEX.match(val):
900 return False
901 # Limit the range to what is guaranteed to get through strtol()
902 return abs(int(val, 0)) <= 0x7fffffff
903
Azim Khan599cd242017-07-06 17:34:27 +0100904def write_parameters(out_data_f, test_args, func_args, unique_expressions):
905 """
Azim Khane3b26af2018-06-29 02:36:57 +0100906 Writes test parameters to the intermediate data file, replacing
907 the string form with identifiers. Also, generates expression
908 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100909
Azim Khanf0e42fb2017-08-02 14:47:13 +0100910 :param out_data_f: Output intermediate data file
911 :param test_args: Test parameters
912 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100913 :param unique_expressions: Mutable list to track unique
914 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100915 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100916 """
917 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100918 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100919 typ = func_args[i]
920 val = test_args[i]
921
Gilles Peskinea2990432022-12-04 00:28:56 +0100922 # Pass small integer constants literally. This reduces the size of
923 # the C code. Register anything else as an expression.
924 if typ == 'int' and not val_is_int(val):
Azim Khan599cd242017-07-06 17:34:27 +0100925 typ = 'exp'
926 if val not in unique_expressions:
927 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100928 # exp_id can be derived from len(). But for
929 # readability and consistency with case of existing
930 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100931 exp_id = unique_expressions.index(val)
932 expression_code += gen_expression_check(exp_id, val)
933 val = exp_id
934 else:
935 val = unique_expressions.index(val)
936 out_data_f.write(':' + typ + ':' + str(val))
937 out_data_f.write('\n')
938 return expression_code
939
940
Azim Khanb31aa442018-07-03 11:57:54 +0100941def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100942 """
Azim Khane3b26af2018-06-29 02:36:57 +0100943 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100944
Azim Khanb31aa442018-07-03 11:57:54 +0100945 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100946 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100947 :param dep_check_code: Dependency check code
948 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100949 :return: Dependency and expression code guarded by test suite
950 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100951 """
Azim Khanb31aa442018-07-03 11:57:54 +0100952 if suite_dependencies:
953 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100954 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100955{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100956{code}
Azim Khan599cd242017-07-06 17:34:27 +0100957#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100958'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100959 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100960{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100961{code}
Azim Khan599cd242017-07-06 17:34:27 +0100962#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100963'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100964 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100965
966
Gilles Peskineab56a692022-12-04 17:27:25 +0100967def get_function_info(func_info, function_name, line_no):
968 """Look up information about a test function by name.
969
970 Raise an informative expression if function_name is not found.
971
972 :param func_info: dictionary mapping function names to their information.
973 :param function_name: the function name as written in the .function and
974 .data files.
975 :param line_no: line number for error messages.
976 :return Function information (id, args).
977 """
978 test_function_name = 'test_' + function_name
979 if test_function_name not in func_info:
980 raise GeneratorInputError("%d: Function %s not found!" %
981 (line_no, test_function_name))
982 return func_info[test_function_name]
983
984
Azim Khanb31aa442018-07-03 11:57:54 +0100985def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100986 """
Azim Khane3b26af2018-06-29 02:36:57 +0100987 This function reads test case name, dependencies and test vectors
988 from the .data file. This information is correlated with the test
989 functions file for generating an intermediate data file replacing
990 the strings for test function names, dependencies and integer
991 constant expressions with identifiers. Mainly for optimising
992 space for on-target execution.
993 It also generates test case dependency check code and expression
994 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100995
Azim Khanf0e42fb2017-08-02 14:47:13 +0100996 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100997 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100998 :param func_info: Dict keyed by function and with function id
999 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +01001000 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +01001001 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001002 """
Azim Khanb31aa442018-07-03 11:57:54 +01001003 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001004 unique_expressions = []
1005 dep_check_code = ''
1006 expression_code = ''
Gilles Peskinef122aed2022-12-03 22:58:52 +01001007 for line_no, test_name, function_name, test_dependencies, test_args in \
Azim Khanb31aa442018-07-03 11:57:54 +01001008 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001009 out_data_f.write(test_name + '\n')
1010
Azim Khanb31aa442018-07-03 11:57:54 +01001011 # Write dependencies
1012 dep_check_code += write_dependencies(out_data_f, test_dependencies,
1013 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001014
Azim Khan599cd242017-07-06 17:34:27 +01001015 # Write test function name
Gilles Peskineab56a692022-12-04 17:27:25 +01001016 func_id, func_args = \
1017 get_function_info(func_info, function_name, line_no)
Azim Khan599cd242017-07-06 17:34:27 +01001018 out_data_f.write(str(func_id))
1019
1020 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001021 if len(test_args) != len(func_args):
Gilles Peskinef122aed2022-12-03 22:58:52 +01001022 raise GeneratorInputError("%d: Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +01001023 "%s. See function %s signature." %
Gilles Peskinef122aed2022-12-03 22:58:52 +01001024 (line_no, test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +01001025 expression_code += write_parameters(out_data_f, test_args, func_args,
1026 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001027
Azim Khan599cd242017-07-06 17:34:27 +01001028 # Write a newline as test case separator
1029 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001030
Azim Khanb31aa442018-07-03 11:57:54 +01001031 dep_check_code, expression_code = gen_suite_dep_checks(
1032 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001033 return dep_check_code, expression_code
1034
1035
Azim Khanb31aa442018-07-03 11:57:54 +01001036def add_input_info(funcs_file, data_file, template_file,
1037 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001038 """
Azim Khanb31aa442018-07-03 11:57:54 +01001039 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001040
Azim Khanf0e42fb2017-08-02 14:47:13 +01001041 :param funcs_file: Functions file object
1042 :param data_file: Data file object
1043 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +01001044 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +01001045 :param snippets: Dictionary to contain code pieces to be
1046 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001047 :return:
1048 """
Azim Khanb31aa442018-07-03 11:57:54 +01001049 snippets['test_file'] = c_file
1050 snippets['test_main_file'] = template_file
1051 snippets['test_case_file'] = funcs_file
1052 snippets['test_case_data_file'] = data_file
1053
1054
1055def read_code_from_input_files(platform_file, helpers_file,
1056 out_data_file, snippets):
1057 """
1058 Read code from input files and create substitutions for replacement
1059 strings in the template file.
1060
1061 :param platform_file: Platform file object
1062 :param helpers_file: Helper functions file object
1063 :param out_data_file: Output intermediate data file object
1064 :param snippets: Dictionary to contain code pieces to be
1065 substituted in the template.
1066 :return:
1067 """
1068 # Read helpers
1069 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
1070 platform_f:
1071 snippets['test_common_helper_file'] = helpers_file
1072 snippets['test_common_helpers'] = help_f.read()
1073 snippets['test_platform_file'] = platform_file
1074 snippets['platform_code'] = platform_f.read().replace(
1075 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
1076
1077
1078def write_test_source_file(template_file, c_file, snippets):
1079 """
1080 Write output source file with generated source code.
1081
1082 :param template_file: Template file name
1083 :param c_file: Output source file
1084 :param snippets: Generated and code snippets
1085 :return:
1086 """
David Horstmann14bae832022-11-03 17:49:29 +00001087
1088 # Create a placeholder pattern with the correct named capture groups
1089 # to override the default provided with Template.
1090 # Match nothing (no way of escaping placeholders).
1091 escaped = "(?P<escaped>(?!))"
1092 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
1093 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
1094 # Match nothing (no braced placeholder syntax).
1095 braced = "(?P<braced>(?!))"
1096 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
1097 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
David Horstmann8eff06f2022-11-09 17:27:33 +00001098 placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid]))
David Horstmann14bae832022-11-03 17:49:29 +00001099
Azim Khanb31aa442018-07-03 11:57:54 +01001100 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001101 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001102 # Update line number. +1 as #line directive sets next line number
1103 snippets['line_no'] = line_no + 1
David Horstmann14bae832022-11-03 17:49:29 +00001104 template = string.Template(line)
1105 template.pattern = placeholder_pattern
1106 snippets = {k.upper():v for (k, v) in snippets.items()}
1107 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001108 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001109
1110
1111def parse_function_file(funcs_file, snippets):
1112 """
1113 Parse function file and generate function dispatch code.
1114
1115 :param funcs_file: Functions file name
1116 :param snippets: Dictionary to contain code pieces to be
1117 substituted in the template.
1118 :return:
1119 """
1120 with FileWrapper(funcs_file) as funcs_f:
1121 suite_dependencies, dispatch_code, func_code, func_info = \
1122 parse_functions(funcs_f)
1123 snippets['functions_code'] = func_code
1124 snippets['dispatch_code'] = dispatch_code
1125 return suite_dependencies, func_info
1126
1127
1128def generate_intermediate_data_file(data_file, out_data_file,
1129 suite_dependencies, func_info, snippets):
1130 """
1131 Generates intermediate data file from input data file and
1132 information read from functions file.
1133
1134 :param data_file: Data file name
1135 :param out_data_file: Output/Intermediate data file
1136 :param suite_dependencies: List of suite dependencies.
1137 :param func_info: Function info parsed from functions file.
1138 :param snippets: Dictionary to contain code pieces to be
1139 substituted in the template.
1140 :return:
1141 """
1142 with FileWrapper(data_file) as data_f, \
1143 open(out_data_file, 'w') as out_data_f:
1144 dep_check_code, expression_code = gen_from_test_data(
1145 data_f, out_data_f, func_info, suite_dependencies)
1146 snippets['dep_check_code'] = dep_check_code
1147 snippets['expression_code'] = expression_code
1148
1149
1150def generate_code(**input_info):
1151 """
1152 Generates C source code from test suite file, data file, common
1153 helpers file and platform file.
1154
1155 input_info expands to following parameters:
1156 funcs_file: Functions file object
1157 data_file: Data file object
1158 template_file: Template file object
1159 platform_file: Platform file object
1160 helpers_file: Helper functions file object
1161 suites_dir: Test suites dir
1162 c_file: Output C file object
1163 out_data_file: Output intermediate data file object
1164 :return:
1165 """
1166 funcs_file = input_info['funcs_file']
1167 data_file = input_info['data_file']
1168 template_file = input_info['template_file']
1169 platform_file = input_info['platform_file']
1170 helpers_file = input_info['helpers_file']
1171 suites_dir = input_info['suites_dir']
1172 c_file = input_info['c_file']
1173 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001174 for name, path in [('Functions file', funcs_file),
1175 ('Data file', data_file),
1176 ('Template file', template_file),
1177 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001178 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001179 ('Suites dir', suites_dir)]:
1180 if not os.path.exists(path):
1181 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1182
Azim Khanb31aa442018-07-03 11:57:54 +01001183 snippets = {'generator_script': os.path.basename(__file__)}
1184 read_code_from_input_files(platform_file, helpers_file,
1185 out_data_file, snippets)
1186 add_input_info(funcs_file, data_file, template_file,
1187 c_file, snippets)
1188 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1189 generate_intermediate_data_file(data_file, out_data_file,
1190 suite_dependencies, func_info, snippets)
1191 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001192
1193
Azim Khan8d686bf2018-07-04 23:29:46 +01001194def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001195 """
1196 Command line parser.
1197
1198 :return:
1199 """
Azim Khan040b6a22018-06-28 16:49:13 +01001200 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001201 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001202
1203 parser.add_argument("-f", "--functions-file",
1204 dest="funcs_file",
1205 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001206 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001207 required=True)
1208
1209 parser.add_argument("-d", "--data-file",
1210 dest="data_file",
1211 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001212 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001213 required=True)
1214
1215 parser.add_argument("-t", "--template-file",
1216 dest="template_file",
1217 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001218 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001219 required=True)
1220
1221 parser.add_argument("-s", "--suites-dir",
1222 dest="suites_dir",
1223 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001224 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001225 required=True)
1226
Azim Khane3b26af2018-06-29 02:36:57 +01001227 parser.add_argument("--helpers-file",
1228 dest="helpers_file",
1229 help="Helpers file",
1230 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001231 required=True)
1232
1233 parser.add_argument("-p", "--platform-file",
1234 dest="platform_file",
1235 help="Platform code file",
1236 metavar="PLATFORM_FILE",
1237 required=True)
1238
1239 parser.add_argument("-o", "--out-dir",
1240 dest="out_dir",
1241 help="Dir where generated code and scripts are copied",
1242 metavar="OUT_DIR",
1243 required=True)
1244
1245 args = parser.parse_args()
1246
1247 data_file_name = os.path.basename(args.data_file)
1248 data_name = os.path.splitext(data_file_name)[0]
1249
1250 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001251 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001252
1253 out_c_file_dir = os.path.dirname(out_c_file)
1254 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001255 for directory in [out_c_file_dir, out_data_file_dir]:
1256 if not os.path.exists(directory):
1257 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001258
Azim Khanb31aa442018-07-03 11:57:54 +01001259 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1260 template_file=args.template_file,
1261 platform_file=args.platform_file,
1262 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1263 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001264
1265
1266if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001267 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001268 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001269 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001270 sys.exit("%s: input error: %s" %
1271 (os.path.basename(sys.argv[0]), str(err)))