blob: a3f6937c70fe42545ffc95bdab05b772397c5bfa [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 Peskine615be632022-12-04 15:11:00 +0100174# Types regognized as integer arguments in test functions.
175INTEGER_TYPES = frozenset(['int'])
176# Types recognized as string arguments in test functions.
177STRING_TYPES = frozenset(['char*', 'const char*', 'char const*'])
178# Types recognized as hex data arguments in test functions.
179DATA_TYPES = frozenset(['data_t*', 'const data_t*', 'data_t const*'])
180
Azim Khanb31aa442018-07-03 11:57:54 +0100181BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
182END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100183
Azim Khanb31aa442018-07-03 11:57:54 +0100184BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
185END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000186
Azim Khanb31aa442018-07-03 11:57:54 +0100187BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
188END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100189
Azim Khan8d686bf2018-07-04 23:29:46 +0100190BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100191END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100192
Azim Khan8d686bf2018-07-04 23:29:46 +0100193DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200194C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
195CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
196# forbid 0ddd which might be accidentally octal or accidentally decimal
197CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
198CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
199 CONDITION_OPERATOR_REGEX,
200 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100201TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100202FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
203EXIT_LABEL_REGEX = r'^exit:'
204
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100205
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100206class GeneratorInputError(Exception):
207 """
Azim Khane3b26af2018-06-29 02:36:57 +0100208 Exception to indicate error in the input files to this script.
209 This includes missing patterns, test function names and other
210 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100211 """
212 pass
213
214
Gilles Peskine184c0962020-03-24 18:25:17 +0100215class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100216 """
Azim Khane3b26af2018-06-29 02:36:57 +0100217 This class extends built-in io.FileIO class with attribute line_no,
218 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100219 """
220
221 def __init__(self, file_name):
222 """
Azim Khane3b26af2018-06-29 02:36:57 +0100223 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100224
Azim Khanf0e42fb2017-08-02 14:47:13 +0100225 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100226 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100227 super().__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100228 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100229
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100230 def __next__(self):
Azim Khan4b543232017-06-30 09:35:21 +0100231 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100232 This method overrides base class's __next__ method and extends it
233 method to count the line numbers as each line is read.
Azim Khane3b26af2018-06-29 02:36:57 +0100234
Azim Khanf0e42fb2017-08-02 14:47:13 +0100235 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100236 """
Gilles Peskine5f0057d2022-11-10 19:33:25 +0100237 line = super().__next__()
Azim Khanb31aa442018-07-03 11:57:54 +0100238 if line is not None:
239 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100240 # Convert byte array to string with correct encoding and
241 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100242 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100243 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100244
Azim Khanb31aa442018-07-03 11:57:54 +0100245 def get_line_no(self):
246 """
247 Gives current line number.
248 """
249 return self._line_no
250
251 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100252
253
254def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100255 """
Azim Khanb31aa442018-07-03 11:57:54 +0100256 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100257
258 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100259 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
260 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100261 """
Azim Khan4b543232017-06-30 09:35:21 +0100262 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
263
264
Azim Khanb31aa442018-07-03 11:57:54 +0100265def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100266 """
Azim Khane3b26af2018-06-29 02:36:57 +0100267 Test suite data and functions specifies compile time dependencies.
268 This function generates C preprocessor code from the input
269 dependency list. Caller uses the generated preprocessor code to
270 wrap dependent code.
271 A dependency in the input list can have a leading '!' character
272 to negate a condition. '!' is separated from the dependency using
273 function split_dep() and proper preprocessor check is generated
274 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100275
Azim Khanb31aa442018-07-03 11:57:54 +0100276 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100277 :return: if defined and endif code with macro annotations for
278 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100279 """
Azim Khanb31aa442018-07-03 11:57:54 +0100280 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
281 map(split_dep, dependencies)])
282 dep_end = ''.join(['#endif /* %s */\n' %
283 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100284
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100285 return dep_start, dep_end
286
287
Azim Khanb31aa442018-07-03 11:57:54 +0100288def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100289 """
Azim Khanb31aa442018-07-03 11:57:54 +0100290 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100291 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100292
Azim Khanb31aa442018-07-03 11:57:54 +0100293 :param dependencies: List of dependencies.
294 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100295 """
Azim Khanb31aa442018-07-03 11:57:54 +0100296 defines = '#if ' if dependencies else ''
297 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
298 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100299 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100300
301
Azim Khanb31aa442018-07-03 11:57:54 +0100302def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100303 """
Azim Khan040b6a22018-06-28 16:49:13 +0100304 Creates test function wrapper code. A wrapper has the code to
305 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100306
Azim Khanf0e42fb2017-08-02 14:47:13 +0100307 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100308 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100309 :param args_dispatch: List of dispatch arguments.
310 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100311 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100312 """
313 # Then create the wrapper
314 wrapper = '''
315void {name}_wrapper( void ** params )
316{{
Gilles Peskine77761412018-06-18 17:51:40 +0200317{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100318 {name}( {args} );
319}}
Gilles Peskine77761412018-06-18 17:51:40 +0200320'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100321 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100322 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100323 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100324 return wrapper
325
326
Azim Khanb31aa442018-07-03 11:57:54 +0100327def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100328 """
Azim Khane3b26af2018-06-29 02:36:57 +0100329 Test suite code template main_test.function defines a C function
330 array to contain test case functions. This function generates an
331 initializer entry for a function in that array. The entry is
332 composed of a compile time check for the test function
333 dependencies. At compile time the test function is assigned when
334 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100335
Azim Khanf0e42fb2017-08-02 14:47:13 +0100336 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100337 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100338 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100339 """
Azim Khanb31aa442018-07-03 11:57:54 +0100340 if dependencies:
341 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100342 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100343{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100344 {name}_wrapper,
345#else
346 NULL,
347#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100348'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100349 else:
350 dispatch_code = '''
351 {name}_wrapper,
352'''.format(name=name)
353
354 return dispatch_code
355
356
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000357def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100358 """
Azim Khane3b26af2018-06-29 02:36:57 +0100359 Matches pattern end_regex to the lines read from the file object.
360 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100361
Azim Khan8d686bf2018-07-04 23:29:46 +0100362 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000363 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100364 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100365 """
Azim Khan4b543232017-06-30 09:35:21 +0100366 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100367 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000368 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100369 break
370 headers += line
371 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100372 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100373 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374
Azim Khan4b543232017-06-30 09:35:21 +0100375 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100376
377
Azim Khan8d686bf2018-07-04 23:29:46 +0100378def validate_dependency(dependency):
379 """
380 Validates a C macro and raises GeneratorInputError on invalid input.
381 :param dependency: Input macro dependency
382 :return: input dependency stripped of leading & trailing white spaces.
383 """
384 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200385 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100386 raise GeneratorInputError('Invalid dependency %s' % dependency)
387 return dependency
388
389
390def parse_dependencies(inp_str):
391 """
392 Parses dependencies out of inp_str, validates them and returns a
393 list of macros.
394
395 :param inp_str: Input string with macros delimited by ':'.
396 :return: list of dependencies
397 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100398 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100399 return dependencies
400
401
Azim Khanb31aa442018-07-03 11:57:54 +0100402def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100403 """
Azim Khane3b26af2018-06-29 02:36:57 +0100404 Parses test suite dependencies specified at the top of a
405 .function file, that starts with pattern BEGIN_DEPENDENCIES
406 and end with END_DEPENDENCIES. Dependencies are specified
407 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100408
Azim Khan8d686bf2018-07-04 23:29:46 +0100409 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100410 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100411 """
Azim Khanb31aa442018-07-03 11:57:54 +0100412 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100413 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100414 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100415 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100416 try:
417 dependencies = parse_dependencies(match.group('dependencies'))
418 except GeneratorInputError as error:
419 raise GeneratorInputError(
420 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100421 if re.search(END_DEP_REGEX, line):
422 break
423 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100424 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100425 " not found!" % (funcs_f.name,
426 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100427
Azim Khanb31aa442018-07-03 11:57:54 +0100428 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100429
430
Azim Khanb31aa442018-07-03 11:57:54 +0100431def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100432 """
Azim Khane3b26af2018-06-29 02:36:57 +0100433 Parses function dependencies, that are in the same line as
434 comment BEGIN_CASE. Dependencies are specified after pattern
435 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100436
Azim Khan8d686bf2018-07-04 23:29:46 +0100437 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100438 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100439 """
Azim Khanb31aa442018-07-03 11:57:54 +0100440 dependencies = []
441 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100442 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100443 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100444 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100445 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100446 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100447
Azim Khan8d686bf2018-07-04 23:29:46 +0100448 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100449
Azim Khan4084ec72018-07-05 14:20:08 +0100450
Gilles Peskine615be632022-12-04 15:11:00 +0100451ARGUMENT_DECLARATION_REGEX = re.compile(r'(.+?) ?(?:\bconst\b)? ?(\w+)\Z', re.S)
Gilles Peskinef153c562022-12-04 14:10:39 +0100452def parse_function_argument(arg, arg_idx, args, local_vars, args_dispatch):
453 """
454 Parses one test function's argument declaration.
455
456 :param arg: argument declaration.
457 :param arg_idx: current wrapper argument index.
458 :param args: accumulator of arguments' internal types.
459 :param local_vars: accumulator of internal variable declarations.
460 :param args_dispatch: accumulator of argument usage expressions.
461 :return: the number of new wrapper arguments,
462 or None if the argument declaration is invalid.
463 """
Gilles Peskine615be632022-12-04 15:11:00 +0100464 # Normalize whitespace
Gilles Peskinef153c562022-12-04 14:10:39 +0100465 arg = arg.strip()
Gilles Peskine615be632022-12-04 15:11:00 +0100466 arg = re.sub(r'\s*\*\s*', r'*', arg)
467 arg = re.sub(r'\s+', r' ', arg)
468 # Extract name and type
469 m = ARGUMENT_DECLARATION_REGEX.search(arg)
470 if not m:
471 # E.g. "int x[42]"
472 return None
473 typ, _ = m.groups()
474 if typ in INTEGER_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100475 args.append('int')
Gilles Peskine6494d922022-12-04 15:32:54 +0100476 args_dispatch.append('((mbedtls_test_argument_t*)params[%d])->sint' % arg_idx)
Gilles Peskinef153c562022-12-04 14:10:39 +0100477 return 1
Gilles Peskine615be632022-12-04 15:11:00 +0100478 if typ in STRING_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100479 args.append('char*')
480 args_dispatch.append('(char *) params[%d]' % arg_idx)
481 return 1
Gilles Peskine615be632022-12-04 15:11:00 +0100482 if typ in DATA_TYPES:
Gilles Peskinef153c562022-12-04 14:10:39 +0100483 args.append('hex')
484 # create a structure
485 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
486 len_initializer = '((mbedtls_test_argument_t*)params[%d])->len' % (arg_idx+1)
487 local_vars.append(' data_t data%d = {%s, %s};\n' %
488 (arg_idx, pointer_initializer, len_initializer))
489 args_dispatch.append('&data%d' % arg_idx)
490 return 2
491 return None
492
Gilles Peskine46476e02022-12-04 14:29:06 +0100493ARGUMENT_LIST_REGEX = re.compile(r'\((.*?)\)', re.S)
Azim Khanfcdf6852018-07-05 17:31:46 +0100494def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100495 """
Azim Khane3b26af2018-06-29 02:36:57 +0100496 Parses test function signature for validation and generates
497 a dispatch wrapper function that translates input test vectors
498 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100499
Azim Khan8d686bf2018-07-04 23:29:46 +0100500 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100501 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100502 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100503 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100504 """
Azim Khan8d686bf2018-07-04 23:29:46 +0100505 # Process arguments, ex: <type> arg1, <type> arg2 )
506 # This script assumes that the argument list is terminated by ')'
507 # i.e. the test functions will not have a function pointer
508 # argument.
Gilles Peskine46476e02022-12-04 14:29:06 +0100509 m = ARGUMENT_LIST_REGEX.search(line)
510 arg_list = m.group(1).strip()
511 if arg_list in ['', 'void']:
512 return [], '', []
513 args = []
514 local_vars = []
515 args_dispatch = []
516 arg_idx = 0
517 for arg in arg_list.split(','):
Gilles Peskinef153c562022-12-04 14:10:39 +0100518 indexes = parse_function_argument(arg, arg_idx,
519 args, local_vars, args_dispatch)
520 if indexes is None:
Azim Khan040b6a22018-06-28 16:49:13 +0100521 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100522 "'char *' or 'data_t'\n%s" % line)
Gilles Peskinef153c562022-12-04 14:10:39 +0100523 arg_idx += indexes
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100524
Gilles Peskine3a37f192022-12-04 14:00:32 +0100525 return args, ''.join(local_vars), args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100526
527
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100528def generate_function_code(name, code, local_vars, args_dispatch,
529 dependencies):
530 """
531 Generate function code with preprocessor checks and parameter dispatch
532 wrapper.
533
534 :param name: Function name
535 :param code: Function code
536 :param local_vars: Local variables for function wrapper
537 :param args_dispatch: Argument dispatch code
538 :param dependencies: Preprocessor dependencies list
539 :return: Final function code
540 """
541 # Add exit label if not present
542 if code.find('exit:') == -1:
543 split_code = code.rsplit('}', 1)
544 if len(split_code) == 2:
545 code = """exit:
546 ;
547}""".join(split_code)
548
549 code += gen_function_wrapper(name, local_vars, args_dispatch)
550 preprocessor_check_start, preprocessor_check_end = \
551 gen_dependencies(dependencies)
552 return preprocessor_check_start + code + preprocessor_check_end
553
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100554COMMENT_START_REGEX = re.compile(r'/[*/]')
555
556def skip_comments(line, stream):
557 """Remove comments in line.
558
559 If the line contains an unfinished comment, read more lines from stream
560 until the line that contains the comment.
561
562 :return: The original line with inner comments replaced by spaces.
563 Trailing comments and whitespace may be removed completely.
564 """
565 pos = 0
566 while True:
567 opening = COMMENT_START_REGEX.search(line, pos)
568 if not opening:
569 break
570 if line[opening.start(0) + 1] == '/': # //...
571 continuation = line
Gilles Peskineaec4bec2022-11-30 16:38:49 +0100572 # Count the number of line breaks, to keep line numbers aligned
573 # in the output.
574 line_count = 1
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100575 while continuation.endswith('\\\n'):
576 # This errors out if the file ends with an unfinished line
Gilles Peskine43febf22022-11-18 22:26:03 +0100577 # comment. That's acceptable to not complicate the code further.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100578 continuation = next(stream)
Gilles Peskineaec4bec2022-11-30 16:38:49 +0100579 line_count += 1
580 return line[:opening.start(0)].rstrip() + '\n' * line_count
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100581 # Parsing /*...*/, looking for the end
582 closing = line.find('*/', opening.end(0))
583 while closing == -1:
584 # This errors out if the file ends with an unfinished block
Gilles Peskine43febf22022-11-18 22:26:03 +0100585 # comment. That's acceptable to not complicate the code further.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100586 line += next(stream)
587 closing = line.find('*/', opening.end(0))
588 pos = closing + 2
Gilles Peskine7e8d4b62022-11-18 22:27:37 +0100589 # Replace inner comment by spaces. There needs to be at least one space
590 # for things like 'int/*ihatespaces*/foo'. Go further and preserve the
Gilles Peskine07995fd2022-11-29 22:03:32 +0100591 # width of the comment and line breaks, this way positions in error
592 # messages remain correct.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100593 line = (line[:opening.start(0)] +
Gilles Peskine07995fd2022-11-29 22:03:32 +0100594 re.sub(r'.', r' ', line[opening.start(0):pos]) +
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100595 line[pos:])
Gilles Peskine07995fd2022-11-29 22:03:32 +0100596 # Strip whitespace at the end of lines (it's irrelevant to error messages).
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100597 return re.sub(r' +(\n|\Z)', r'\1', line)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100598
Azim Khanb31aa442018-07-03 11:57:54 +0100599def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100600 """
Azim Khan040b6a22018-06-28 16:49:13 +0100601 Parses out a function from function file object and generates
602 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100603
Azim Khanf0e42fb2017-08-02 14:47:13 +0100604 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100605 :param dependencies: List of dependencies
606 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100607 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100608 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100609 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
610 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100611 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100612 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100613 # Check function signature. Function signature may be split
614 # across multiple lines. Here we try to find the start of
615 # arguments list, then remove '\n's and apply the regex to
616 # detect function start.
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100617 line = skip_comments(line, funcs_f)
Azim Khanfcdf6852018-07-05 17:31:46 +0100618 up_to_arg_list_start = code + line[:line.find('(') + 1]
619 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
620 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100621 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100622 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100623 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100624 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100625 for lin in funcs_f:
Gilles Peskined3ad55e2022-11-11 16:37:16 +0100626 line += skip_comments(lin, funcs_f)
Azim Khan8d686bf2018-07-04 23:29:46 +0100627 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100629 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100630 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100631 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100632 break
633 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100634 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100635 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100636 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100637
Azim Khanfcdf6852018-07-05 17:31:46 +0100638 # Prefix test function name with 'test_'
639 code = code.replace(name, 'test_' + name, 1)
640 name = 'test_' + name
641
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100642 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100643 if re.search(END_CASE_REGEX, line):
644 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100645 if not has_exit_label:
646 has_exit_label = \
647 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100648 code += line
649 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100650 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100651 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100652
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100653 code = line_directive + code
654 code = generate_function_code(name, code, local_vars, args_dispatch,
655 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100656 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100657 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100658
659
660def parse_functions(funcs_f):
661 """
Azim Khane3b26af2018-06-29 02:36:57 +0100662 Parses a test_suite_xxx.function file and returns information
663 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100664
Azim Khanf0e42fb2017-08-02 14:47:13 +0100665 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100666 :return: List of test suite dependencies, test function dispatch
667 code, function code and a dict with function identifiers
668 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100669 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000670 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100671 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100672 suite_functions = ''
673 func_info = {}
674 function_idx = 0
675 dispatch_code = ''
676 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100677 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100678 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000679 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100680 suite_helpers += parse_until_pattern(funcs_f,
681 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100682 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100683 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100684 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100685 try:
686 dependencies = parse_function_dependencies(line)
687 except GeneratorInputError as error:
688 raise GeneratorInputError(
689 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
690 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100691 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100692 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100693 suite_functions += func_code
694 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100695 if func_name in func_info:
696 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100697 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100698 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100699 func_info[func_name] = (function_idx, args)
700 dispatch_code += '/* Function Id: %d */\n' % function_idx
701 dispatch_code += func_dispatch
702 function_idx += 1
703
Azim Khanb31aa442018-07-03 11:57:54 +0100704 func_code = (suite_helpers +
705 suite_functions).join(gen_dependencies(suite_dependencies))
706 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100707
708
Azim Khanb31aa442018-07-03 11:57:54 +0100709def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100710 """
Azim Khanb31aa442018-07-03 11:57:54 +0100711 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100712 Since, return value is used to write back to the intermediate
713 data file, any escape characters in the input are retained in the
714 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100715
Azim Khanb31aa442018-07-03 11:57:54 +0100716 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100717 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100718 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100719 """
Azim Khanb31aa442018-07-03 11:57:54 +0100720 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100721 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100722 out = re.sub(r'(\\.)|' + split_char,
723 lambda m: m.group(1) or '\n', inp_str,
724 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100725 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100726 return out
727
728
Azim Khanb31aa442018-07-03 11:57:54 +0100729def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100730 """
Azim Khane3b26af2018-06-29 02:36:57 +0100731 Parses .data file for each test case name, test function name,
732 test dependencies and test arguments. This information is
733 correlated with the test functions file for generating an
734 intermediate data file replacing the strings for test function
735 names, dependencies and integer constant expressions with
736 identifiers. Mainly for optimising space for on-target
737 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100738
Azim Khanf0e42fb2017-08-02 14:47:13 +0100739 :param data_f: file object of the data file.
Gilles Peskinef122aed2022-12-03 22:58:52 +0100740 :return: Generator that yields line number, test name, function name,
Azim Khan040b6a22018-06-28 16:49:13 +0100741 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100742 """
Azim Khanb31aa442018-07-03 11:57:54 +0100743 __state_read_name = 0
744 __state_read_args = 1
745 state = __state_read_name
746 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100747 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100748 for line in data_f:
749 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100750 # Skip comments
751 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100752 continue
753
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100754 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100755 if not line:
756 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100757 raise GeneratorInputError("[%s:%d] Newline before arguments. "
758 "Test function and arguments "
759 "missing for %s" %
760 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100761 continue
762
Azim Khanb31aa442018-07-03 11:57:54 +0100763 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100764 # Read test name
765 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100766 state = __state_read_args
767 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100768 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100769 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100770 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100771 try:
772 dependencies = parse_dependencies(
773 match.group('dependencies'))
774 except GeneratorInputError as error:
775 raise GeneratorInputError(
776 str(error) + " - %s:%d" %
777 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100778 else:
779 # Read test vectors
780 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100781 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100782 args = parts[1:]
Gilles Peskinef122aed2022-12-03 22:58:52 +0100783 yield data_f.line_no, name, test_function, dependencies, args
Azim Khanb31aa442018-07-03 11:57:54 +0100784 dependencies = []
785 state = __state_read_name
786 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100787 raise GeneratorInputError("[%s:%d] Newline before arguments. "
788 "Test function and arguments missing for "
789 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100790
791
792def gen_dep_check(dep_id, dep):
793 """
Azim Khane3b26af2018-06-29 02:36:57 +0100794 Generate code for checking dependency with the associated
795 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100796
Azim Khanf0e42fb2017-08-02 14:47:13 +0100797 :param dep_id: Dependency identifier
798 :param dep: Dependency macro
799 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100800 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100801 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100802 raise GeneratorInputError("Dependency Id should be a positive "
803 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100804 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
805 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100806 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200807
808 dependency = re.match(CONDITION_REGEX, dep, re.I)
809 if not dependency:
810 raise GeneratorInputError('Invalid dependency %s' % dep)
811
812 _defined = '' if dependency.group(2) else 'defined'
813 _cond = dependency.group(2) if dependency.group(2) else ''
814 _value = dependency.group(3) if dependency.group(3) else ''
815
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100816 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100817 case {id}:
818 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200819#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100820 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100821#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100822 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100823#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100824 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200825 break;'''.format(_not=_not, _defined=_defined,
826 macro=dependency.group(1), id=dep_id,
827 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100828 return dep_check
829
830
831def gen_expression_check(exp_id, exp):
832 """
Azim Khane3b26af2018-06-29 02:36:57 +0100833 Generates code for evaluating an integer expression using
834 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100835
Azim Khanf0e42fb2017-08-02 14:47:13 +0100836 :param exp_id: Expression Identifier
837 :param exp: Expression/Macro
838 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100839 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100840 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100841 raise GeneratorInputError("Expression Id should be a positive "
842 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100843 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100844 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100845 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100846 case {exp_id}:
847 {{
848 *out_value = {expression};
849 }}
850 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100851 return exp_code
852
853
Azim Khanb31aa442018-07-03 11:57:54 +0100854def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100855 """
Azim Khane3b26af2018-06-29 02:36:57 +0100856 Write dependencies to intermediate test data file, replacing
857 the string form with identifiers. Also, generates dependency
858 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100859
Azim Khanf0e42fb2017-08-02 14:47:13 +0100860 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100861 :param test_dependencies: Dependencies
862 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100863 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100864 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100865 """
Azim Khan599cd242017-07-06 17:34:27 +0100866 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100867 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100868 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100869 for dep in test_dependencies:
870 if dep not in unique_dependencies:
871 unique_dependencies.append(dep)
872 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100873 dep_check_code += gen_dep_check(dep_id, dep)
874 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100875 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100876 out_data_f.write(':' + str(dep_id))
877 out_data_f.write('\n')
878 return dep_check_code
879
880
Gilles Peskinea2990432022-12-04 00:28:56 +0100881INT_VAL_REGEX = re.compile(r'-?(\d+|0x[0-9a-f]+)$', re.I)
882def val_is_int(val: str) -> bool:
883 """Whether val is suitable as an 'int' parameter in the .datax file."""
884 if not INT_VAL_REGEX.match(val):
885 return False
886 # Limit the range to what is guaranteed to get through strtol()
887 return abs(int(val, 0)) <= 0x7fffffff
888
Azim Khan599cd242017-07-06 17:34:27 +0100889def write_parameters(out_data_f, test_args, func_args, unique_expressions):
890 """
Azim Khane3b26af2018-06-29 02:36:57 +0100891 Writes test parameters to the intermediate data file, replacing
892 the string form with identifiers. Also, generates expression
893 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100894
Azim Khanf0e42fb2017-08-02 14:47:13 +0100895 :param out_data_f: Output intermediate data file
896 :param test_args: Test parameters
897 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100898 :param unique_expressions: Mutable list to track unique
899 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100900 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100901 """
902 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100903 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100904 typ = func_args[i]
905 val = test_args[i]
906
Gilles Peskinea2990432022-12-04 00:28:56 +0100907 # Pass small integer constants literally. This reduces the size of
908 # the C code. Register anything else as an expression.
909 if typ == 'int' and not val_is_int(val):
Azim Khan599cd242017-07-06 17:34:27 +0100910 typ = 'exp'
911 if val not in unique_expressions:
912 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100913 # exp_id can be derived from len(). But for
914 # readability and consistency with case of existing
915 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100916 exp_id = unique_expressions.index(val)
917 expression_code += gen_expression_check(exp_id, val)
918 val = exp_id
919 else:
920 val = unique_expressions.index(val)
921 out_data_f.write(':' + typ + ':' + str(val))
922 out_data_f.write('\n')
923 return expression_code
924
925
Azim Khanb31aa442018-07-03 11:57:54 +0100926def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100927 """
Azim Khane3b26af2018-06-29 02:36:57 +0100928 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100929
Azim Khanb31aa442018-07-03 11:57:54 +0100930 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100931 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100932 :param dep_check_code: Dependency check code
933 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100934 :return: Dependency and expression code guarded by test suite
935 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100936 """
Azim Khanb31aa442018-07-03 11:57:54 +0100937 if suite_dependencies:
938 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100939 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100940{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100941{code}
Azim Khan599cd242017-07-06 17:34:27 +0100942#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100943'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100944 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100945{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100946{code}
Azim Khan599cd242017-07-06 17:34:27 +0100947#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100948'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100949 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100950
951
Gilles Peskineab56a692022-12-04 17:27:25 +0100952def get_function_info(func_info, function_name, line_no):
953 """Look up information about a test function by name.
954
955 Raise an informative expression if function_name is not found.
956
957 :param func_info: dictionary mapping function names to their information.
958 :param function_name: the function name as written in the .function and
959 .data files.
960 :param line_no: line number for error messages.
961 :return Function information (id, args).
962 """
963 test_function_name = 'test_' + function_name
964 if test_function_name not in func_info:
965 raise GeneratorInputError("%d: Function %s not found!" %
966 (line_no, test_function_name))
967 return func_info[test_function_name]
968
969
Azim Khanb31aa442018-07-03 11:57:54 +0100970def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100971 """
Azim Khane3b26af2018-06-29 02:36:57 +0100972 This function reads test case name, dependencies and test vectors
973 from the .data file. This information is correlated with the test
974 functions file for generating an intermediate data file replacing
975 the strings for test function names, dependencies and integer
976 constant expressions with identifiers. Mainly for optimising
977 space for on-target execution.
978 It also generates test case dependency check code and expression
979 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100980
Azim Khanf0e42fb2017-08-02 14:47:13 +0100981 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100982 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100983 :param func_info: Dict keyed by function and with function id
984 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100985 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100986 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100987 """
Azim Khanb31aa442018-07-03 11:57:54 +0100988 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100989 unique_expressions = []
990 dep_check_code = ''
991 expression_code = ''
Gilles Peskinef122aed2022-12-03 22:58:52 +0100992 for line_no, test_name, function_name, test_dependencies, test_args in \
Azim Khanb31aa442018-07-03 11:57:54 +0100993 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100994 out_data_f.write(test_name + '\n')
995
Azim Khanb31aa442018-07-03 11:57:54 +0100996 # Write dependencies
997 dep_check_code += write_dependencies(out_data_f, test_dependencies,
998 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100999
Azim Khan599cd242017-07-06 17:34:27 +01001000 # Write test function name
Gilles Peskineab56a692022-12-04 17:27:25 +01001001 func_id, func_args = \
1002 get_function_info(func_info, function_name, line_no)
Azim Khan599cd242017-07-06 17:34:27 +01001003 out_data_f.write(str(func_id))
1004
1005 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001006 if len(test_args) != len(func_args):
Gilles Peskinef122aed2022-12-03 22:58:52 +01001007 raise GeneratorInputError("%d: Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +01001008 "%s. See function %s signature." %
Gilles Peskinef122aed2022-12-03 22:58:52 +01001009 (line_no, test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +01001010 expression_code += write_parameters(out_data_f, test_args, func_args,
1011 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001012
Azim Khan599cd242017-07-06 17:34:27 +01001013 # Write a newline as test case separator
1014 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001015
Azim Khanb31aa442018-07-03 11:57:54 +01001016 dep_check_code, expression_code = gen_suite_dep_checks(
1017 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001018 return dep_check_code, expression_code
1019
1020
Azim Khanb31aa442018-07-03 11:57:54 +01001021def add_input_info(funcs_file, data_file, template_file,
1022 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001023 """
Azim Khanb31aa442018-07-03 11:57:54 +01001024 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001025
Azim Khanf0e42fb2017-08-02 14:47:13 +01001026 :param funcs_file: Functions file object
1027 :param data_file: Data file object
1028 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +01001029 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +01001030 :param snippets: Dictionary to contain code pieces to be
1031 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001032 :return:
1033 """
Azim Khanb31aa442018-07-03 11:57:54 +01001034 snippets['test_file'] = c_file
1035 snippets['test_main_file'] = template_file
1036 snippets['test_case_file'] = funcs_file
1037 snippets['test_case_data_file'] = data_file
1038
1039
1040def read_code_from_input_files(platform_file, helpers_file,
1041 out_data_file, snippets):
1042 """
1043 Read code from input files and create substitutions for replacement
1044 strings in the template file.
1045
1046 :param platform_file: Platform file object
1047 :param helpers_file: Helper functions file object
1048 :param out_data_file: Output intermediate data file object
1049 :param snippets: Dictionary to contain code pieces to be
1050 substituted in the template.
1051 :return:
1052 """
1053 # Read helpers
1054 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
1055 platform_f:
1056 snippets['test_common_helper_file'] = helpers_file
1057 snippets['test_common_helpers'] = help_f.read()
1058 snippets['test_platform_file'] = platform_file
1059 snippets['platform_code'] = platform_f.read().replace(
1060 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
1061
1062
1063def write_test_source_file(template_file, c_file, snippets):
1064 """
1065 Write output source file with generated source code.
1066
1067 :param template_file: Template file name
1068 :param c_file: Output source file
1069 :param snippets: Generated and code snippets
1070 :return:
1071 """
David Horstmann14bae832022-11-03 17:49:29 +00001072
1073 # Create a placeholder pattern with the correct named capture groups
1074 # to override the default provided with Template.
1075 # Match nothing (no way of escaping placeholders).
1076 escaped = "(?P<escaped>(?!))"
1077 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
1078 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
1079 # Match nothing (no braced placeholder syntax).
1080 braced = "(?P<braced>(?!))"
1081 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
1082 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
David Horstmann8eff06f2022-11-09 17:27:33 +00001083 placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid]))
David Horstmann14bae832022-11-03 17:49:29 +00001084
Azim Khanb31aa442018-07-03 11:57:54 +01001085 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001086 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001087 # Update line number. +1 as #line directive sets next line number
1088 snippets['line_no'] = line_no + 1
David Horstmann14bae832022-11-03 17:49:29 +00001089 template = string.Template(line)
1090 template.pattern = placeholder_pattern
1091 snippets = {k.upper():v for (k, v) in snippets.items()}
1092 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001093 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001094
1095
1096def parse_function_file(funcs_file, snippets):
1097 """
1098 Parse function file and generate function dispatch code.
1099
1100 :param funcs_file: Functions file name
1101 :param snippets: Dictionary to contain code pieces to be
1102 substituted in the template.
1103 :return:
1104 """
1105 with FileWrapper(funcs_file) as funcs_f:
1106 suite_dependencies, dispatch_code, func_code, func_info = \
1107 parse_functions(funcs_f)
1108 snippets['functions_code'] = func_code
1109 snippets['dispatch_code'] = dispatch_code
1110 return suite_dependencies, func_info
1111
1112
1113def generate_intermediate_data_file(data_file, out_data_file,
1114 suite_dependencies, func_info, snippets):
1115 """
1116 Generates intermediate data file from input data file and
1117 information read from functions file.
1118
1119 :param data_file: Data file name
1120 :param out_data_file: Output/Intermediate data file
1121 :param suite_dependencies: List of suite dependencies.
1122 :param func_info: Function info parsed from functions file.
1123 :param snippets: Dictionary to contain code pieces to be
1124 substituted in the template.
1125 :return:
1126 """
1127 with FileWrapper(data_file) as data_f, \
1128 open(out_data_file, 'w') as out_data_f:
1129 dep_check_code, expression_code = gen_from_test_data(
1130 data_f, out_data_f, func_info, suite_dependencies)
1131 snippets['dep_check_code'] = dep_check_code
1132 snippets['expression_code'] = expression_code
1133
1134
1135def generate_code(**input_info):
1136 """
1137 Generates C source code from test suite file, data file, common
1138 helpers file and platform file.
1139
1140 input_info expands to following parameters:
1141 funcs_file: Functions file object
1142 data_file: Data file object
1143 template_file: Template file object
1144 platform_file: Platform file object
1145 helpers_file: Helper functions file object
1146 suites_dir: Test suites dir
1147 c_file: Output C file object
1148 out_data_file: Output intermediate data file object
1149 :return:
1150 """
1151 funcs_file = input_info['funcs_file']
1152 data_file = input_info['data_file']
1153 template_file = input_info['template_file']
1154 platform_file = input_info['platform_file']
1155 helpers_file = input_info['helpers_file']
1156 suites_dir = input_info['suites_dir']
1157 c_file = input_info['c_file']
1158 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001159 for name, path in [('Functions file', funcs_file),
1160 ('Data file', data_file),
1161 ('Template file', template_file),
1162 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001163 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001164 ('Suites dir', suites_dir)]:
1165 if not os.path.exists(path):
1166 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1167
Azim Khanb31aa442018-07-03 11:57:54 +01001168 snippets = {'generator_script': os.path.basename(__file__)}
1169 read_code_from_input_files(platform_file, helpers_file,
1170 out_data_file, snippets)
1171 add_input_info(funcs_file, data_file, template_file,
1172 c_file, snippets)
1173 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1174 generate_intermediate_data_file(data_file, out_data_file,
1175 suite_dependencies, func_info, snippets)
1176 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001177
1178
Azim Khan8d686bf2018-07-04 23:29:46 +01001179def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001180 """
1181 Command line parser.
1182
1183 :return:
1184 """
Azim Khan040b6a22018-06-28 16:49:13 +01001185 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001186 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001187
1188 parser.add_argument("-f", "--functions-file",
1189 dest="funcs_file",
1190 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001191 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001192 required=True)
1193
1194 parser.add_argument("-d", "--data-file",
1195 dest="data_file",
1196 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001197 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001198 required=True)
1199
1200 parser.add_argument("-t", "--template-file",
1201 dest="template_file",
1202 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001203 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001204 required=True)
1205
1206 parser.add_argument("-s", "--suites-dir",
1207 dest="suites_dir",
1208 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001209 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001210 required=True)
1211
Azim Khane3b26af2018-06-29 02:36:57 +01001212 parser.add_argument("--helpers-file",
1213 dest="helpers_file",
1214 help="Helpers file",
1215 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001216 required=True)
1217
1218 parser.add_argument("-p", "--platform-file",
1219 dest="platform_file",
1220 help="Platform code file",
1221 metavar="PLATFORM_FILE",
1222 required=True)
1223
1224 parser.add_argument("-o", "--out-dir",
1225 dest="out_dir",
1226 help="Dir where generated code and scripts are copied",
1227 metavar="OUT_DIR",
1228 required=True)
1229
1230 args = parser.parse_args()
1231
1232 data_file_name = os.path.basename(args.data_file)
1233 data_name = os.path.splitext(data_file_name)[0]
1234
1235 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001236 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001237
1238 out_c_file_dir = os.path.dirname(out_c_file)
1239 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001240 for directory in [out_c_file_dir, out_data_file_dir]:
1241 if not os.path.exists(directory):
1242 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001243
Azim Khanb31aa442018-07-03 11:57:54 +01001244 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1245 template_file=args.template_file,
1246 platform_file=args.platform_file,
1247 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1248 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001249
1250
1251if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001252 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001253 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001254 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001255 sys.exit("%s: input error: %s" %
1256 (os.path.basename(sys.argv[0]), str(err)))