blob: 76806de95f950d2d42efd9eb67b1628b1d1f6262 [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 Horstmann360f8e42022-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 Khanfff49042017-03-28 01:48:31 +0100166import os
167import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100168import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100169import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100170import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100171
172
Gilles Peskine2986acc2023-04-26 19:57:46 +0200173# Types recognized as signed integer arguments in test functions.
Gilles Peskine6f5082b2022-12-04 15:57:49 +0100174SIGNED_INTEGER_TYPES = frozenset([
175 'char',
176 'short',
177 'short int',
178 'int',
179 'int8_t',
180 'int16_t',
181 'int32_t',
182 'int64_t',
183 'intmax_t',
184 'long',
185 'long int',
186 'long long int',
187 'mbedtls_mpi_sint',
188 'psa_status_t',
189])
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100190# Types recognized as string arguments in test functions.
191STRING_TYPES = frozenset(['char*', 'const char*', 'char const*'])
192# Types recognized as hex data arguments in test functions.
193DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*'])
194
Azim Khanb31aa442018-07-03 11:57:54 +0100195BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
196END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100197
Azim Khanb31aa442018-07-03 11:57:54 +0100198BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
199END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000200
Azim Khanb31aa442018-07-03 11:57:54 +0100201BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
202END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100203
Azim Khan8d686bf2018-07-04 23:29:46 +0100204BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100205END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100206
Azim Khan8d686bf2018-07-04 23:29:46 +0100207DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200208C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
209CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
210# forbid 0ddd which might be accidentally octal or accidentally decimal
211CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
212CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
213 CONDITION_OPERATOR_REGEX,
214 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100215TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100216FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
217EXIT_LABEL_REGEX = r'^exit:'
218
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100219
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100220class GeneratorInputError(Exception):
221 """
Azim Khane3b26af2018-06-29 02:36:57 +0100222 Exception to indicate error in the input files to this script.
223 This includes missing patterns, test function names and other
224 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100225 """
226 pass
227
228
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800229class FileWrapper:
Azim Khan4b543232017-06-30 09:35:21 +0100230 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800231 This class extends the file object with attribute line_no,
Azim Khane3b26af2018-06-29 02:36:57 +0100232 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100233 """
234
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800235 def __init__(self, file_name) -> None:
Azim Khan4b543232017-06-30 09:35:21 +0100236 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800237 Instantiate the file object and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100238
Azim Khanf0e42fb2017-08-02 14:47:13 +0100239 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100240 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800241 # private mix-in file object
242 self._f = open(file_name, 'rb')
Azim Khanb31aa442018-07-03 11:57:54 +0100243 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100244
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800245 def __iter__(self):
246 return self
247
Gilles Peskine0c3f5f12022-11-10 19:33:25 +0100248 def __next__(self):
Azim Khan4b543232017-06-30 09:35:21 +0100249 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800250 This method makes FileWrapper iterable.
251 It counts the line numbers as each line is read.
Azim Khane3b26af2018-06-29 02:36:57 +0100252
Azim Khanf0e42fb2017-08-02 14:47:13 +0100253 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100254 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800255 line = self._f.__next__()
256 self._line_no += 1
257 # Convert byte array to string with correct encoding and
258 # strip any whitespaces added in the decoding process.
259 return line.decode(sys.getdefaultencoding()).rstrip()+ '\n'
Azim Khane3b26af2018-06-29 02:36:57 +0100260
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800261 def __enter__(self):
262 return self
263
264 def __exit__(self, exc_type, exc_val, exc_tb):
265 self._f.__exit__(exc_type, exc_val, exc_tb)
266
267 @property
268 def line_no(self):
Azim Khanb31aa442018-07-03 11:57:54 +0100269 """
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800270 Property that indicates line number for the line that is read.
Azim Khanb31aa442018-07-03 11:57:54 +0100271 """
272 return self._line_no
273
Pengyu Lv7a344dd2023-04-19 15:03:20 +0800274 @property
275 def name(self):
276 """
277 Property that indicates name of the file that is read.
278 """
279 return self._f.name
Azim Khan4b543232017-06-30 09:35:21 +0100280
281
282def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100283 """
Azim Khanb31aa442018-07-03 11:57:54 +0100284 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100285
286 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100287 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
288 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100289 """
Azim Khan4b543232017-06-30 09:35:21 +0100290 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
291
292
Azim Khanb31aa442018-07-03 11:57:54 +0100293def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100294 """
Azim Khane3b26af2018-06-29 02:36:57 +0100295 Test suite data and functions specifies compile time dependencies.
296 This function generates C preprocessor code from the input
297 dependency list. Caller uses the generated preprocessor code to
298 wrap dependent code.
299 A dependency in the input list can have a leading '!' character
300 to negate a condition. '!' is separated from the dependency using
301 function split_dep() and proper preprocessor check is generated
302 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100303
Azim Khanb31aa442018-07-03 11:57:54 +0100304 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100305 :return: if defined and endif code with macro annotations for
306 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307 """
Azim Khanb31aa442018-07-03 11:57:54 +0100308 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
309 map(split_dep, dependencies)])
310 dep_end = ''.join(['#endif /* %s */\n' %
311 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100312
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100313 return dep_start, dep_end
314
315
Azim Khanb31aa442018-07-03 11:57:54 +0100316def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100317 """
Azim Khanb31aa442018-07-03 11:57:54 +0100318 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100319 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100320
Azim Khanb31aa442018-07-03 11:57:54 +0100321 :param dependencies: List of dependencies.
322 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100323 """
Azim Khanb31aa442018-07-03 11:57:54 +0100324 defines = '#if ' if dependencies else ''
325 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
326 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100327 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100328
329
Azim Khanb31aa442018-07-03 11:57:54 +0100330def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100331 """
Azim Khan040b6a22018-06-28 16:49:13 +0100332 Creates test function wrapper code. A wrapper has the code to
333 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100334
Azim Khanf0e42fb2017-08-02 14:47:13 +0100335 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100336 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100337 :param args_dispatch: List of dispatch arguments.
Gilles Peskineb70c4e02023-04-26 19:59:28 +0200338 Ex: ['(char *) params[0]', '*((int *) params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100339 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100340 """
341 # Then create the wrapper
342 wrapper = '''
343void {name}_wrapper( void ** params )
344{{
Gilles Peskine77761412018-06-18 17:51:40 +0200345{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100346 {name}( {args} );
347}}
Gilles Peskine77761412018-06-18 17:51:40 +0200348'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100349 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100350 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100351 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100352 return wrapper
353
354
Azim Khanb31aa442018-07-03 11:57:54 +0100355def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100356 """
Azim Khane3b26af2018-06-29 02:36:57 +0100357 Test suite code template main_test.function defines a C function
358 array to contain test case functions. This function generates an
359 initializer entry for a function in that array. The entry is
360 composed of a compile time check for the test function
361 dependencies. At compile time the test function is assigned when
362 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100363
Azim Khanf0e42fb2017-08-02 14:47:13 +0100364 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100365 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100366 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100367 """
Azim Khanb31aa442018-07-03 11:57:54 +0100368 if dependencies:
369 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100370 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100371{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100372 {name}_wrapper,
373#else
374 NULL,
375#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100376'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100377 else:
378 dispatch_code = '''
379 {name}_wrapper,
380'''.format(name=name)
381
382 return dispatch_code
383
384
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000385def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100386 """
Azim Khane3b26af2018-06-29 02:36:57 +0100387 Matches pattern end_regex to the lines read from the file object.
388 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100389
Azim Khan8d686bf2018-07-04 23:29:46 +0100390 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000391 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100392 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100393 """
Azim Khan4b543232017-06-30 09:35:21 +0100394 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100395 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000396 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100397 break
398 headers += line
399 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100400 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100401 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100402
Azim Khan4b543232017-06-30 09:35:21 +0100403 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100404
405
Azim Khan8d686bf2018-07-04 23:29:46 +0100406def validate_dependency(dependency):
407 """
408 Validates a C macro and raises GeneratorInputError on invalid input.
409 :param dependency: Input macro dependency
410 :return: input dependency stripped of leading & trailing white spaces.
411 """
412 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200413 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100414 raise GeneratorInputError('Invalid dependency %s' % dependency)
415 return dependency
416
417
418def parse_dependencies(inp_str):
419 """
420 Parses dependencies out of inp_str, validates them and returns a
421 list of macros.
422
423 :param inp_str: Input string with macros delimited by ':'.
424 :return: list of dependencies
425 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100426 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100427 return dependencies
428
429
Azim Khanb31aa442018-07-03 11:57:54 +0100430def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100431 """
Azim Khane3b26af2018-06-29 02:36:57 +0100432 Parses test suite dependencies specified at the top of a
433 .function file, that starts with pattern BEGIN_DEPENDENCIES
434 and end with END_DEPENDENCIES. Dependencies are specified
435 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100436
Azim Khan8d686bf2018-07-04 23:29:46 +0100437 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100438 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100439 """
Azim Khanb31aa442018-07-03 11:57:54 +0100440 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100441 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100442 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100443 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100444 try:
445 dependencies = parse_dependencies(match.group('dependencies'))
446 except GeneratorInputError as error:
447 raise GeneratorInputError(
448 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100449 if re.search(END_DEP_REGEX, line):
450 break
451 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100452 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100453 " not found!" % (funcs_f.name,
454 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100455
Azim Khanb31aa442018-07-03 11:57:54 +0100456 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100457
458
Azim Khanb31aa442018-07-03 11:57:54 +0100459def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100460 """
Azim Khane3b26af2018-06-29 02:36:57 +0100461 Parses function dependencies, that are in the same line as
462 comment BEGIN_CASE. Dependencies are specified after pattern
463 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100464
Azim Khan8d686bf2018-07-04 23:29:46 +0100465 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100466 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100467 """
Azim Khanb31aa442018-07-03 11:57:54 +0100468 dependencies = []
469 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100470 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100471 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100472 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100473 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100474 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100475
Azim Khan8d686bf2018-07-04 23:29:46 +0100476 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100477
Azim Khan4084ec72018-07-05 14:20:08 +0100478
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100479ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100480def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch):
481 """
482 Parses one test function's argument declaration.
483
484 :param arg: argument declaration.
485 :param arg_idx: current wrapper argument index.
486 :param args: accumulator of arguments' internal types.
487 :param local_vars: accumulator of internal variable declarations.
488 :param args_dispatch: accumulator of argument usage expressions.
489 :return: the number of new wrapper arguments,
490 or None if the argument declaration is invalid.
491 """
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100492 # Normalize whitespace
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100493 arg = arg.strip()
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100494 arg = re.sub(r'\s*\*\s*', r'*', arg)
495 arg = re.sub(r'\s+', r' ', arg)
496 # Extract name and type
497 m = ARGUMENT_DECLARATION_REGEX.search(arg)
498 if not m:
499 # E.g. "int x[42]"
500 return None
501 typ, _ = m.groups()
Gilles Peskine6f5082b2022-12-04 15:57:49 +0100502 if typ in SIGNED_INTEGER_TYPES:
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100503 args.append('int')
Gilles Peskineb70c4e02023-04-26 19:59:28 +0200504 args_dispatch.append('((mbedtls_test_argument_t *) params[%d])->sint' % arg_idx)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100505 return 1
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100506 if typ in STRING_TYPES:
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100507 args.append('char*')
508 args_dispatch.append('(char *) params[%d]' % arg_idx)
509 return 1
Gilles Peskine4ea4ad02022-12-04 15:11:00 +0100510 if typ in DATA_TYPES:
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100511 args.append('hex')
512 # create a structure
513 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
Gilles Peskineb70c4e02023-04-26 19:59:28 +0200514 len_initializer = '((mbedtls_test_argument_t *) params[%d])->len' % (arg_idx+1)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100515 local_vars.append(' data_t data%d = {%s, %s};\n' %
516 (arg_idx, pointer_initializer, len_initializer))
517 args_dispatch.append('&data%d' % arg_idx)
518 return 2
519 return None
520
Gilles Peskine47e2e882022-12-04 14:29:06 +0100521ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S)
Azim Khanfcdf6852018-07-05 17:31:46 +0100522def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100523 """
Azim Khane3b26af2018-06-29 02:36:57 +0100524 Parses test function signature for validation and generates
525 a dispatch wrapper function that translates input test vectors
526 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100527
Azim Khan8d686bf2018-07-04 23:29:46 +0100528 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100529 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100530 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100531 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100532 """
Azim Khan8d686bf2018-07-04 23:29:46 +0100533 # Process arguments, ex: <type> arg1, <type> arg2 )
534 # This script assumes that the argument list is terminated by ')'
535 # i.e. the test functions will not have a function pointer
536 # argument.
Gilles Peskine47e2e882022-12-04 14:29:06 +0100537 m = ARGUMENT_LIST_REGEX.search(line)
538 arg_list = m.group(1).strip()
539 if arg_list in ['', 'void']:
540 return [], '', []
541 args = []
542 local_vars = []
543 args_dispatch = []
544 arg_idx = 0
545 for arg in arg_list.split(','):
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100546 indexes = parse_function_argument(arg, arg_idx,
547 args, local_vars, args_dispatch)
548 if indexes is None:
Azim Khan040b6a22018-06-28 16:49:13 +0100549 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100550 "'char *' or 'data_t'\n%s" % line)
Gilles Peskine096f0ca2022-12-04 14:10:39 +0100551 arg_idx += indexes
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100552
Gilles Peskine400cde62022-12-04 14:00:32 +0100553 return args, ''.join(local_vars), args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100554
555
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100556def generate_function_code(name, code, local_vars, args_dispatch,
557 dependencies):
558 """
559 Generate function code with preprocessor checks and parameter dispatch
560 wrapper.
561
562 :param name: Function name
563 :param code: Function code
564 :param local_vars: Local variables for function wrapper
565 :param args_dispatch: Argument dispatch code
566 :param dependencies: Preprocessor dependencies list
567 :return: Final function code
568 """
569 # Add exit label if not present
570 if code.find('exit:') == -1:
571 split_code = code.rsplit('}', 1)
572 if len(split_code) == 2:
573 code = """exit:
574 ;
575}""".join(split_code)
576
577 code += gen_function_wrapper(name, local_vars, args_dispatch)
578 preprocessor_check_start, preprocessor_check_end = \
579 gen_dependencies(dependencies)
580 return preprocessor_check_start + code + preprocessor_check_end
581
Gilles Peskine07510f52022-11-11 16:37:16 +0100582COMMENT_START_REGEX = re.compile(r'/[*/]')
583
584def skip_comments(line, stream):
585 """Remove comments in line.
586
587 If the line contains an unfinished comment, read more lines from stream
588 until the line that contains the comment.
589
590 :return: The original line with inner comments replaced by spaces.
591 Trailing comments and whitespace may be removed completely.
592 """
593 pos = 0
594 while True:
595 opening = COMMENT_START_REGEX.search(line, pos)
596 if not opening:
597 break
598 if line[opening.start(0) + 1] == '/': # //...
599 continuation = line
Gilles Peskine18f70282022-11-30 16:38:49 +0100600 # Count the number of line breaks, to keep line numbers aligned
601 # in the output.
602 line_count = 1
Gilles Peskine07510f52022-11-11 16:37:16 +0100603 while continuation.endswith('\\\n'):
604 # This errors out if the file ends with an unfinished line
Gilles Peskine268ea5a2022-11-18 22:26:03 +0100605 # comment. That's acceptable to not complicate the code further.
Gilles Peskine07510f52022-11-11 16:37:16 +0100606 continuation = next(stream)
Gilles Peskine18f70282022-11-30 16:38:49 +0100607 line_count += 1
608 return line[:opening.start(0)].rstrip() + '\n' * line_count
Gilles Peskine07510f52022-11-11 16:37:16 +0100609 # Parsing /*...*/, looking for the end
610 closing = line.find('*/', opening.end(0))
611 while closing == -1:
612 # This errors out if the file ends with an unfinished block
Gilles Peskine268ea5a2022-11-18 22:26:03 +0100613 # comment. That's acceptable to not complicate the code further.
Gilles Peskine07510f52022-11-11 16:37:16 +0100614 line += next(stream)
615 closing = line.find('*/', opening.end(0))
616 pos = closing + 2
Gilles Peskine9ac62c32022-11-18 22:27:37 +0100617 # Replace inner comment by spaces. There needs to be at least one space
618 # for things like 'int/*ihatespaces*/foo'. Go further and preserve the
Gilles Peskined8c08032022-11-29 22:03:32 +0100619 # width of the comment and line breaks, this way positions in error
620 # messages remain correct.
Gilles Peskine07510f52022-11-11 16:37:16 +0100621 line = (line[:opening.start(0)] +
Gilles Peskined8c08032022-11-29 22:03:32 +0100622 re.sub(r'.', r' ', line[opening.start(0):pos]) +
Gilles Peskine07510f52022-11-11 16:37:16 +0100623 line[pos:])
Gilles Peskined8c08032022-11-29 22:03:32 +0100624 # Strip whitespace at the end of lines (it's irrelevant to error messages).
Gilles Peskine07510f52022-11-11 16:37:16 +0100625 return re.sub(r' +(\n|\Z)', r'\1', line)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100626
Azim Khanb31aa442018-07-03 11:57:54 +0100627def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 """
Azim Khan040b6a22018-06-28 16:49:13 +0100629 Parses out a function from function file object and generates
630 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100631
Azim Khanf0e42fb2017-08-02 14:47:13 +0100632 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100633 :param dependencies: List of dependencies
634 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100635 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100636 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100637 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
638 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100639 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100640 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100641 # Check function signature. Function signature may be split
642 # across multiple lines. Here we try to find the start of
643 # arguments list, then remove '\n's and apply the regex to
644 # detect function start.
Gilles Peskine07510f52022-11-11 16:37:16 +0100645 line = skip_comments(line, funcs_f)
Azim Khanfcdf6852018-07-05 17:31:46 +0100646 up_to_arg_list_start = code + line[:line.find('(') + 1]
647 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
648 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100649 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100650 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100651 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100652 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100653 for lin in funcs_f:
Gilles Peskine07510f52022-11-11 16:37:16 +0100654 line += skip_comments(lin, funcs_f)
Azim Khan8d686bf2018-07-04 23:29:46 +0100655 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100656 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100657 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100658 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100659 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100660 break
661 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100662 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100663 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100664 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100665
Azim Khanfcdf6852018-07-05 17:31:46 +0100666 # Prefix test function name with 'test_'
667 code = code.replace(name, 'test_' + name, 1)
668 name = 'test_' + name
669
Gowtham Suresh Kumar186731b2023-07-26 15:47:45 +0100670 # If a test function has no arguments then add 'void' argument to
Gowtham Suresh Kumar9da40b82023-07-31 16:38:10 +0100671 # avoid "-Wstrict-prototypes" warnings from clang
Gowtham Suresh Kumar186731b2023-07-26 15:47:45 +0100672 if len(args) == 0:
673 code = code.replace('()', '(void)', 1)
674
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100675 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100676 if re.search(END_CASE_REGEX, line):
677 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100678 if not has_exit_label:
679 has_exit_label = \
680 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100681 code += line
682 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100683 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100684 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100685
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100686 code = line_directive + code
687 code = generate_function_code(name, code, local_vars, args_dispatch,
688 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100689 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100690 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100691
692
693def parse_functions(funcs_f):
694 """
Azim Khane3b26af2018-06-29 02:36:57 +0100695 Parses a test_suite_xxx.function file and returns information
696 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100697
Azim Khanf0e42fb2017-08-02 14:47:13 +0100698 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100699 :return: List of test suite dependencies, test function dispatch
700 code, function code and a dict with function identifiers
701 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100702 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000703 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100704 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100705 suite_functions = ''
706 func_info = {}
707 function_idx = 0
708 dispatch_code = ''
709 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100710 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100711 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000712 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100713 suite_helpers += parse_until_pattern(funcs_f,
714 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100715 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100716 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100717 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100718 try:
719 dependencies = parse_function_dependencies(line)
720 except GeneratorInputError as error:
721 raise GeneratorInputError(
722 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
723 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100724 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100725 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100726 suite_functions += func_code
727 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100728 if func_name in func_info:
729 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100730 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100731 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100732 func_info[func_name] = (function_idx, args)
733 dispatch_code += '/* Function Id: %d */\n' % function_idx
734 dispatch_code += func_dispatch
735 function_idx += 1
736
Azim Khanb31aa442018-07-03 11:57:54 +0100737 func_code = (suite_helpers +
738 suite_functions).join(gen_dependencies(suite_dependencies))
739 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100740
741
Azim Khanb31aa442018-07-03 11:57:54 +0100742def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100743 """
Azim Khanb31aa442018-07-03 11:57:54 +0100744 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100745 Since, return value is used to write back to the intermediate
746 data file, any escape characters in the input are retained in the
747 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100748
Azim Khanb31aa442018-07-03 11:57:54 +0100749 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100750 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100751 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100752 """
Azim Khanb31aa442018-07-03 11:57:54 +0100753 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100754 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100755 out = re.sub(r'(\\.)|' + split_char,
756 lambda m: m.group(1) or '\n', inp_str,
757 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100758 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100759 return out
760
761
Azim Khanb31aa442018-07-03 11:57:54 +0100762def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100763 """
Azim Khane3b26af2018-06-29 02:36:57 +0100764 Parses .data file for each test case name, test function name,
765 test dependencies and test arguments. This information is
766 correlated with the test functions file for generating an
767 intermediate data file replacing the strings for test function
768 names, dependencies and integer constant expressions with
769 identifiers. Mainly for optimising space for on-target
770 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100771
Azim Khanf0e42fb2017-08-02 14:47:13 +0100772 :param data_f: file object of the data file.
Gilles Peskine8542f5c2022-12-03 22:58:52 +0100773 :return: Generator that yields line number, test name, function name,
Azim Khan040b6a22018-06-28 16:49:13 +0100774 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100775 """
Azim Khanb31aa442018-07-03 11:57:54 +0100776 __state_read_name = 0
777 __state_read_args = 1
778 state = __state_read_name
779 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100780 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100781 for line in data_f:
782 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100783 # Skip comments
784 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100785 continue
786
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100787 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100788 if not line:
789 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100790 raise GeneratorInputError("[%s:%d] Newline before arguments. "
791 "Test function and arguments "
792 "missing for %s" %
793 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100794 continue
795
Azim Khanb31aa442018-07-03 11:57:54 +0100796 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100797 # Read test name
798 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100799 state = __state_read_args
800 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100801 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100802 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100803 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100804 try:
805 dependencies = parse_dependencies(
806 match.group('dependencies'))
807 except GeneratorInputError as error:
808 raise GeneratorInputError(
809 str(error) + " - %s:%d" %
810 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100811 else:
812 # Read test vectors
813 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100814 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100815 args = parts[1:]
Gilles Peskine8542f5c2022-12-03 22:58:52 +0100816 yield data_f.line_no, name, test_function, dependencies, args
Azim Khanb31aa442018-07-03 11:57:54 +0100817 dependencies = []
818 state = __state_read_name
819 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100820 raise GeneratorInputError("[%s:%d] Newline before arguments. "
821 "Test function and arguments missing for "
822 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100823
824
825def gen_dep_check(dep_id, dep):
826 """
Azim Khane3b26af2018-06-29 02:36:57 +0100827 Generate code for checking dependency with the associated
828 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100829
Azim Khanf0e42fb2017-08-02 14:47:13 +0100830 :param dep_id: Dependency identifier
831 :param dep: Dependency macro
832 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100833 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100834 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100835 raise GeneratorInputError("Dependency Id should be a positive "
836 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100837 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
838 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100839 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200840
841 dependency = re.match(CONDITION_REGEX, dep, re.I)
842 if not dependency:
843 raise GeneratorInputError('Invalid dependency %s' % dep)
844
845 _defined = '' if dependency.group(2) else 'defined'
846 _cond = dependency.group(2) if dependency.group(2) else ''
847 _value = dependency.group(3) if dependency.group(3) else ''
848
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100849 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100850 case {id}:
851 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200852#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100853 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100854#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100855 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100856#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100857 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200858 break;'''.format(_not=_not, _defined=_defined,
859 macro=dependency.group(1), id=dep_id,
860 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100861 return dep_check
862
863
864def gen_expression_check(exp_id, exp):
865 """
Azim Khane3b26af2018-06-29 02:36:57 +0100866 Generates code for evaluating an integer expression using
867 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100868
Azim Khanf0e42fb2017-08-02 14:47:13 +0100869 :param exp_id: Expression Identifier
870 :param exp: Expression/Macro
871 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100872 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100873 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100874 raise GeneratorInputError("Expression Id should be a positive "
875 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100876 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100877 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100878 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100879 case {exp_id}:
880 {{
881 *out_value = {expression};
882 }}
883 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100884 return exp_code
885
886
Azim Khanb31aa442018-07-03 11:57:54 +0100887def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100888 """
Azim Khane3b26af2018-06-29 02:36:57 +0100889 Write dependencies to intermediate test data file, replacing
890 the string form with identifiers. Also, generates dependency
891 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100892
Azim Khanf0e42fb2017-08-02 14:47:13 +0100893 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100894 :param test_dependencies: Dependencies
895 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100896 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100897 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100898 """
Azim Khan599cd242017-07-06 17:34:27 +0100899 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100900 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100901 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100902 for dep in test_dependencies:
903 if dep not in unique_dependencies:
904 unique_dependencies.append(dep)
905 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100906 dep_check_code += gen_dep_check(dep_id, dep)
907 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100908 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100909 out_data_f.write(':' + str(dep_id))
910 out_data_f.write('\n')
911 return dep_check_code
912
913
Gilles Peskine5226eb52022-12-04 00:28:56 +0100914INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I)
915def val_is_int(val: str) -> bool:
916 """Whether val is suitable as an 'int' parameter in the .datax file."""
917 if not INT_VAL_REGEX.match(val):
918 return False
919 # Limit the range to what is guaranteed to get through strtol()
920 return abs(int(val, 0)) <= 0x7fffffff
921
Azim Khan599cd242017-07-06 17:34:27 +0100922def write_parameters(out_data_f, test_args, func_args, unique_expressions):
923 """
Azim Khane3b26af2018-06-29 02:36:57 +0100924 Writes test parameters to the intermediate data file, replacing
925 the string form with identifiers. Also, generates expression
926 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100927
Azim Khanf0e42fb2017-08-02 14:47:13 +0100928 :param out_data_f: Output intermediate data file
929 :param test_args: Test parameters
930 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100931 :param unique_expressions: Mutable list to track unique
932 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100933 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100934 """
935 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100936 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100937 typ = func_args[i]
938 val = test_args[i]
939
Gilles Peskine5226eb52022-12-04 00:28:56 +0100940 # Pass small integer constants literally. This reduces the size of
941 # the C code. Register anything else as an expression.
942 if typ == 'int' and not val_is_int(val):
Azim Khan599cd242017-07-06 17:34:27 +0100943 typ = 'exp'
944 if val not in unique_expressions:
945 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100946 # exp_id can be derived from len(). But for
947 # readability and consistency with case of existing
948 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100949 exp_id = unique_expressions.index(val)
950 expression_code += gen_expression_check(exp_id, val)
951 val = exp_id
952 else:
953 val = unique_expressions.index(val)
954 out_data_f.write(':' + typ + ':' + str(val))
955 out_data_f.write('\n')
956 return expression_code
957
958
Azim Khanb31aa442018-07-03 11:57:54 +0100959def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100960 """
Azim Khane3b26af2018-06-29 02:36:57 +0100961 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100962
Azim Khanb31aa442018-07-03 11:57:54 +0100963 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100964 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100965 :param dep_check_code: Dependency check code
966 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100967 :return: Dependency and expression code guarded by test suite
968 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100969 """
Azim Khanb31aa442018-07-03 11:57:54 +0100970 if suite_dependencies:
971 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100972 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100973{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100974{code}
Azim Khan599cd242017-07-06 17:34:27 +0100975#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100976'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100977 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100978{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100979{code}
Azim Khan599cd242017-07-06 17:34:27 +0100980#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100981'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100982 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100983
984
Gilles Peskineca25dee2022-12-04 17:27:25 +0100985def get_function_info(func_info, function_name, line_no):
986 """Look up information about a test function by name.
987
988 Raise an informative expression if function_name is not found.
989
990 :param func_info: dictionary mapping function names to their information.
991 :param function_name: the function name as written in the .function and
992 .data files.
993 :param line_no: line number for error messages.
994 :return Function information (id, args).
995 """
996 test_function_name = 'test_' + function_name
997 if test_function_name not in func_info:
998 raise GeneratorInputError("%d: Function %s not found!" %
999 (line_no, test_function_name))
1000 return func_info[test_function_name]
1001
1002
Azim Khanb31aa442018-07-03 11:57:54 +01001003def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001004 """
Azim Khane3b26af2018-06-29 02:36:57 +01001005 This function reads test case name, dependencies and test vectors
1006 from the .data file. This information is correlated with the test
1007 functions file for generating an intermediate data file replacing
1008 the strings for test function names, dependencies and integer
1009 constant expressions with identifiers. Mainly for optimising
1010 space for on-target execution.
1011 It also generates test case dependency check code and expression
1012 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +01001013
Azim Khanf0e42fb2017-08-02 14:47:13 +01001014 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +01001015 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +01001016 :param func_info: Dict keyed by function and with function id
1017 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +01001018 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +01001019 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001020 """
Azim Khanb31aa442018-07-03 11:57:54 +01001021 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001022 unique_expressions = []
1023 dep_check_code = ''
1024 expression_code = ''
Gilles Peskine8542f5c2022-12-03 22:58:52 +01001025 for line_no, test_name, function_name, test_dependencies, test_args in \
Azim Khanb31aa442018-07-03 11:57:54 +01001026 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001027 out_data_f.write(test_name + '\n')
1028
Azim Khanb31aa442018-07-03 11:57:54 +01001029 # Write dependencies
1030 dep_check_code += write_dependencies(out_data_f, test_dependencies,
1031 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001032
Azim Khan599cd242017-07-06 17:34:27 +01001033 # Write test function name
Gilles Peskineca25dee2022-12-04 17:27:25 +01001034 func_id, func_args = \
1035 get_function_info(func_info, function_name, line_no)
Azim Khan599cd242017-07-06 17:34:27 +01001036 out_data_f.write(str(func_id))
1037
1038 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001039 if len(test_args) != len(func_args):
Gilles Peskine8542f5c2022-12-03 22:58:52 +01001040 raise GeneratorInputError("%d: Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +01001041 "%s. See function %s signature." %
Gilles Peskine8542f5c2022-12-03 22:58:52 +01001042 (line_no, test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +01001043 expression_code += write_parameters(out_data_f, test_args, func_args,
1044 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001045
Azim Khan599cd242017-07-06 17:34:27 +01001046 # Write a newline as test case separator
1047 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001048
Azim Khanb31aa442018-07-03 11:57:54 +01001049 dep_check_code, expression_code = gen_suite_dep_checks(
1050 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001051 return dep_check_code, expression_code
1052
1053
Azim Khanb31aa442018-07-03 11:57:54 +01001054def add_input_info(funcs_file, data_file, template_file,
1055 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001056 """
Azim Khanb31aa442018-07-03 11:57:54 +01001057 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001058
Azim Khanf0e42fb2017-08-02 14:47:13 +01001059 :param funcs_file: Functions file object
1060 :param data_file: Data file object
1061 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +01001062 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +01001063 :param snippets: Dictionary to contain code pieces to be
1064 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001065 :return:
1066 """
Azim Khanb31aa442018-07-03 11:57:54 +01001067 snippets['test_file'] = c_file
1068 snippets['test_main_file'] = template_file
1069 snippets['test_case_file'] = funcs_file
1070 snippets['test_case_data_file'] = data_file
1071
1072
1073def read_code_from_input_files(platform_file, helpers_file,
1074 out_data_file, snippets):
1075 """
1076 Read code from input files and create substitutions for replacement
1077 strings in the template file.
1078
1079 :param platform_file: Platform file object
1080 :param helpers_file: Helper functions file object
1081 :param out_data_file: Output intermediate data file object
1082 :param snippets: Dictionary to contain code pieces to be
1083 substituted in the template.
1084 :return:
1085 """
1086 # Read helpers
1087 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
1088 platform_f:
1089 snippets['test_common_helper_file'] = helpers_file
1090 snippets['test_common_helpers'] = help_f.read()
1091 snippets['test_platform_file'] = platform_file
1092 snippets['platform_code'] = platform_f.read().replace(
1093 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
1094
1095
1096def write_test_source_file(template_file, c_file, snippets):
1097 """
1098 Write output source file with generated source code.
1099
1100 :param template_file: Template file name
1101 :param c_file: Output source file
1102 :param snippets: Generated and code snippets
1103 :return:
1104 """
David Horstmannb85838f2022-11-03 17:49:29 +00001105
1106 # Create a placeholder pattern with the correct named capture groups
1107 # to override the default provided with Template.
1108 # Match nothing (no way of escaping placeholders).
1109 escaped = "(?P<escaped>(?!))"
1110 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
1111 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
1112 # Match nothing (no braced placeholder syntax).
1113 braced = "(?P<braced>(?!))"
1114 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
1115 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
David Horstmann360f8e42022-11-09 17:27:33 +00001116 placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid]))
David Horstmannb85838f2022-11-03 17:49:29 +00001117
Azim Khanb31aa442018-07-03 11:57:54 +01001118 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001119 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001120 # Update line number. +1 as #line directive sets next line number
1121 snippets['line_no'] = line_no + 1
David Horstmannb85838f2022-11-03 17:49:29 +00001122 template = string.Template(line)
1123 template.pattern = placeholder_pattern
1124 snippets = {k.upper():v for (k, v) in snippets.items()}
1125 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001126 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001127
1128
1129def parse_function_file(funcs_file, snippets):
1130 """
1131 Parse function file and generate function dispatch code.
1132
1133 :param funcs_file: Functions file name
1134 :param snippets: Dictionary to contain code pieces to be
1135 substituted in the template.
1136 :return:
1137 """
1138 with FileWrapper(funcs_file) as funcs_f:
1139 suite_dependencies, dispatch_code, func_code, func_info = \
1140 parse_functions(funcs_f)
1141 snippets['functions_code'] = func_code
1142 snippets['dispatch_code'] = dispatch_code
1143 return suite_dependencies, func_info
1144
1145
1146def generate_intermediate_data_file(data_file, out_data_file,
1147 suite_dependencies, func_info, snippets):
1148 """
1149 Generates intermediate data file from input data file and
1150 information read from functions file.
1151
1152 :param data_file: Data file name
1153 :param out_data_file: Output/Intermediate data file
1154 :param suite_dependencies: List of suite dependencies.
1155 :param func_info: Function info parsed from functions file.
1156 :param snippets: Dictionary to contain code pieces to be
1157 substituted in the template.
1158 :return:
1159 """
1160 with FileWrapper(data_file) as data_f, \
1161 open(out_data_file, 'w') as out_data_f:
1162 dep_check_code, expression_code = gen_from_test_data(
1163 data_f, out_data_f, func_info, suite_dependencies)
1164 snippets['dep_check_code'] = dep_check_code
1165 snippets['expression_code'] = expression_code
1166
1167
1168def generate_code(**input_info):
1169 """
1170 Generates C source code from test suite file, data file, common
1171 helpers file and platform file.
1172
1173 input_info expands to following parameters:
1174 funcs_file: Functions file object
1175 data_file: Data file object
1176 template_file: Template file object
1177 platform_file: Platform file object
1178 helpers_file: Helper functions file object
1179 suites_dir: Test suites dir
1180 c_file: Output C file object
1181 out_data_file: Output intermediate data file object
1182 :return:
1183 """
1184 funcs_file = input_info['funcs_file']
1185 data_file = input_info['data_file']
1186 template_file = input_info['template_file']
1187 platform_file = input_info['platform_file']
1188 helpers_file = input_info['helpers_file']
1189 suites_dir = input_info['suites_dir']
1190 c_file = input_info['c_file']
1191 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001192 for name, path in [('Functions file', funcs_file),
1193 ('Data file', data_file),
1194 ('Template file', template_file),
1195 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001196 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001197 ('Suites dir', suites_dir)]:
1198 if not os.path.exists(path):
1199 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1200
Azim Khanb31aa442018-07-03 11:57:54 +01001201 snippets = {'generator_script': os.path.basename(__file__)}
1202 read_code_from_input_files(platform_file, helpers_file,
1203 out_data_file, snippets)
1204 add_input_info(funcs_file, data_file, template_file,
1205 c_file, snippets)
1206 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1207 generate_intermediate_data_file(data_file, out_data_file,
1208 suite_dependencies, func_info, snippets)
1209 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001210
1211
Azim Khan8d686bf2018-07-04 23:29:46 +01001212def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001213 """
1214 Command line parser.
1215
1216 :return:
1217 """
Azim Khan040b6a22018-06-28 16:49:13 +01001218 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001219 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001220
1221 parser.add_argument("-f", "--functions-file",
1222 dest="funcs_file",
1223 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001224 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001225 required=True)
1226
1227 parser.add_argument("-d", "--data-file",
1228 dest="data_file",
1229 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001230 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001231 required=True)
1232
1233 parser.add_argument("-t", "--template-file",
1234 dest="template_file",
1235 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001236 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001237 required=True)
1238
1239 parser.add_argument("-s", "--suites-dir",
1240 dest="suites_dir",
1241 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001242 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001243 required=True)
1244
Azim Khane3b26af2018-06-29 02:36:57 +01001245 parser.add_argument("--helpers-file",
1246 dest="helpers_file",
1247 help="Helpers file",
1248 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001249 required=True)
1250
1251 parser.add_argument("-p", "--platform-file",
1252 dest="platform_file",
1253 help="Platform code file",
1254 metavar="PLATFORM_FILE",
1255 required=True)
1256
1257 parser.add_argument("-o", "--out-dir",
1258 dest="out_dir",
1259 help="Dir where generated code and scripts are copied",
1260 metavar="OUT_DIR",
1261 required=True)
1262
1263 args = parser.parse_args()
1264
1265 data_file_name = os.path.basename(args.data_file)
1266 data_name = os.path.splitext(data_file_name)[0]
1267
1268 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001269 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001270
1271 out_c_file_dir = os.path.dirname(out_c_file)
1272 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001273 for directory in [out_c_file_dir, out_data_file_dir]:
1274 if not os.path.exists(directory):
1275 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001276
Azim Khanb31aa442018-07-03 11:57:54 +01001277 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1278 template_file=args.template_file,
1279 platform_file=args.platform_file,
1280 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1281 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001282
1283
1284if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001285 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001286 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001287 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001288 sys.exit("%s: input error: %s" %
1289 (os.path.basename(sys.argv[0]), str(err)))