blob: 938f24cf4fbffbe7b1824f47d430bbbb445dc2b7 [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
Azim Khanb31aa442018-07-03 11:57:54 +0100174BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
175END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100176
Azim Khanb31aa442018-07-03 11:57:54 +0100177BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
178END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000179
Azim Khanb31aa442018-07-03 11:57:54 +0100180BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
181END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100182
Azim Khan8d686bf2018-07-04 23:29:46 +0100183BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100184END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100185
Azim Khan8d686bf2018-07-04 23:29:46 +0100186DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200187C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
188CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
189# forbid 0ddd which might be accidentally octal or accidentally decimal
190CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
191CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
192 CONDITION_OPERATOR_REGEX,
193 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100194TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100195INT_CHECK_REGEX = r'int\s+.*'
196CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
197DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100198FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
199EXIT_LABEL_REGEX = r'^exit:'
200
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100201
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100202class GeneratorInputError(Exception):
203 """
Azim Khane3b26af2018-06-29 02:36:57 +0100204 Exception to indicate error in the input files to this script.
205 This includes missing patterns, test function names and other
206 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100207 """
208 pass
209
210
Gilles Peskine184c0962020-03-24 18:25:17 +0100211class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100212 """
Azim Khane3b26af2018-06-29 02:36:57 +0100213 This class extends built-in io.FileIO class with attribute line_no,
214 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100215 """
216
217 def __init__(self, file_name):
218 """
Azim Khane3b26af2018-06-29 02:36:57 +0100219 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100220
Azim Khanf0e42fb2017-08-02 14:47:13 +0100221 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100222 """
223 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100224 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100225
Azim Khanb31aa442018-07-03 11:57:54 +0100226 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100227 """
Azim Khane3b26af2018-06-29 02:36:57 +0100228 Python 2 iterator method. This method overrides base class's
229 next method and extends the next method to count the line
230 numbers as each line is read.
231
232 It works for both Python 2 and Python 3 by checking iterator
233 method name in the base iterator object.
234
Azim Khanf0e42fb2017-08-02 14:47:13 +0100235 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100236 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200237 parent = super(FileWrapper, self)
238 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100239 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200240 else:
Gilles Peskinee915d532019-02-25 21:39:42 +0100241 line = parent.next() # Python 2 # pylint: disable=no-member
Azim Khanb31aa442018-07-03 11:57:54 +0100242 if line is not None:
243 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100244 # Convert byte array to string with correct encoding and
245 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100246 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100247 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100248
249 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100250 __next__ = next
251
252 def get_line_no(self):
253 """
254 Gives current line number.
255 """
256 return self._line_no
257
258 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100259
260
261def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262 """
Azim Khanb31aa442018-07-03 11:57:54 +0100263 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100264
265 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100266 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
267 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100268 """
Azim Khan4b543232017-06-30 09:35:21 +0100269 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
270
271
Azim Khanb31aa442018-07-03 11:57:54 +0100272def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100273 """
Azim Khane3b26af2018-06-29 02:36:57 +0100274 Test suite data and functions specifies compile time dependencies.
275 This function generates C preprocessor code from the input
276 dependency list. Caller uses the generated preprocessor code to
277 wrap dependent code.
278 A dependency in the input list can have a leading '!' character
279 to negate a condition. '!' is separated from the dependency using
280 function split_dep() and proper preprocessor check is generated
281 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100282
Azim Khanb31aa442018-07-03 11:57:54 +0100283 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100284 :return: if defined and endif code with macro annotations for
285 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100286 """
Azim Khanb31aa442018-07-03 11:57:54 +0100287 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
288 map(split_dep, dependencies)])
289 dep_end = ''.join(['#endif /* %s */\n' %
290 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100291
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100292 return dep_start, dep_end
293
294
Azim Khanb31aa442018-07-03 11:57:54 +0100295def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100296 """
Azim Khanb31aa442018-07-03 11:57:54 +0100297 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100298 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100299
Azim Khanb31aa442018-07-03 11:57:54 +0100300 :param dependencies: List of dependencies.
301 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100302 """
Azim Khanb31aa442018-07-03 11:57:54 +0100303 defines = '#if ' if dependencies else ''
304 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
305 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100306 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
308
Azim Khanb31aa442018-07-03 11:57:54 +0100309def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100310 """
Azim Khan040b6a22018-06-28 16:49:13 +0100311 Creates test function wrapper code. A wrapper has the code to
312 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100313
Azim Khanf0e42fb2017-08-02 14:47:13 +0100314 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100315 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100316 :param args_dispatch: List of dispatch arguments.
317 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100318 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100319 """
320 # Then create the wrapper
321 wrapper = '''
322void {name}_wrapper( void ** params )
323{{
Gilles Peskine77761412018-06-18 17:51:40 +0200324{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100325 {name}( {args} );
326}}
Gilles Peskine77761412018-06-18 17:51:40 +0200327'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100328 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100329 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100330 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100331 return wrapper
332
333
Azim Khanb31aa442018-07-03 11:57:54 +0100334def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100335 """
Azim Khane3b26af2018-06-29 02:36:57 +0100336 Test suite code template main_test.function defines a C function
337 array to contain test case functions. This function generates an
338 initializer entry for a function in that array. The entry is
339 composed of a compile time check for the test function
340 dependencies. At compile time the test function is assigned when
341 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100342
Azim Khanf0e42fb2017-08-02 14:47:13 +0100343 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100344 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100345 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100346 """
Azim Khanb31aa442018-07-03 11:57:54 +0100347 if dependencies:
348 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100349 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100350{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100351 {name}_wrapper,
352#else
353 NULL,
354#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100355'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100356 else:
357 dispatch_code = '''
358 {name}_wrapper,
359'''.format(name=name)
360
361 return dispatch_code
362
363
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000364def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100365 """
Azim Khane3b26af2018-06-29 02:36:57 +0100366 Matches pattern end_regex to the lines read from the file object.
367 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100368
Azim Khan8d686bf2018-07-04 23:29:46 +0100369 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000370 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100371 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100372 """
Azim Khan4b543232017-06-30 09:35:21 +0100373 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000375 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100376 break
377 headers += line
378 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100379 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100380 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100381
Azim Khan4b543232017-06-30 09:35:21 +0100382 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100383
384
Azim Khan8d686bf2018-07-04 23:29:46 +0100385def validate_dependency(dependency):
386 """
387 Validates a C macro and raises GeneratorInputError on invalid input.
388 :param dependency: Input macro dependency
389 :return: input dependency stripped of leading & trailing white spaces.
390 """
391 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200392 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100393 raise GeneratorInputError('Invalid dependency %s' % dependency)
394 return dependency
395
396
397def parse_dependencies(inp_str):
398 """
399 Parses dependencies out of inp_str, validates them and returns a
400 list of macros.
401
402 :param inp_str: Input string with macros delimited by ':'.
403 :return: list of dependencies
404 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100405 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100406 return dependencies
407
408
Azim Khanb31aa442018-07-03 11:57:54 +0100409def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100410 """
Azim Khane3b26af2018-06-29 02:36:57 +0100411 Parses test suite dependencies specified at the top of a
412 .function file, that starts with pattern BEGIN_DEPENDENCIES
413 and end with END_DEPENDENCIES. Dependencies are specified
414 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100415
Azim Khan8d686bf2018-07-04 23:29:46 +0100416 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100417 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100418 """
Azim Khanb31aa442018-07-03 11:57:54 +0100419 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100420 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100421 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100422 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100423 try:
424 dependencies = parse_dependencies(match.group('dependencies'))
425 except GeneratorInputError as error:
426 raise GeneratorInputError(
427 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100428 if re.search(END_DEP_REGEX, line):
429 break
430 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100431 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100432 " not found!" % (funcs_f.name,
433 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100434
Azim Khanb31aa442018-07-03 11:57:54 +0100435 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100436
437
Azim Khanb31aa442018-07-03 11:57:54 +0100438def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100439 """
Azim Khane3b26af2018-06-29 02:36:57 +0100440 Parses function dependencies, that are in the same line as
441 comment BEGIN_CASE. Dependencies are specified after pattern
442 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100443
Azim Khan8d686bf2018-07-04 23:29:46 +0100444 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100445 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100446 """
Azim Khanb31aa442018-07-03 11:57:54 +0100447 dependencies = []
448 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100449 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100450 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100451 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100452 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100453 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100454
Azim Khan8d686bf2018-07-04 23:29:46 +0100455 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100456
Azim Khan4084ec72018-07-05 14:20:08 +0100457
Azim Khanfcdf6852018-07-05 17:31:46 +0100458def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100459 """
Azim Khane3b26af2018-06-29 02:36:57 +0100460 Parses test function signature for validation and generates
461 a dispatch wrapper function that translates input test vectors
462 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100463
Azim Khan8d686bf2018-07-04 23:29:46 +0100464 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100465 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100466 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100467 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100468 """
469 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100470 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100471 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100472 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100473 # Remove characters before arguments
474 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100475 # Process arguments, ex: <type> arg1, <type> arg2 )
476 # This script assumes that the argument list is terminated by ')'
477 # i.e. the test functions will not have a function pointer
478 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100479 for arg in line[:line.find(')')].split(','):
480 arg = arg.strip()
481 if arg == '':
482 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100483 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100484 args.append('int')
485 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100486 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100487 args.append('char*')
488 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100489 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100490 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100491 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100492 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
493 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100494 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100495""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100496
Azim Khan5fcca462018-06-29 11:05:32 +0100497 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100498 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100499 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100500 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100501 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100502 arg_idx += 1
503
Azim Khanfcdf6852018-07-05 17:31:46 +0100504 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100505
506
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100507def generate_function_code(name, code, local_vars, args_dispatch,
508 dependencies):
509 """
510 Generate function code with preprocessor checks and parameter dispatch
511 wrapper.
512
513 :param name: Function name
514 :param code: Function code
515 :param local_vars: Local variables for function wrapper
516 :param args_dispatch: Argument dispatch code
517 :param dependencies: Preprocessor dependencies list
518 :return: Final function code
519 """
520 # Add exit label if not present
521 if code.find('exit:') == -1:
522 split_code = code.rsplit('}', 1)
523 if len(split_code) == 2:
524 code = """exit:
525 ;
526}""".join(split_code)
527
528 code += gen_function_wrapper(name, local_vars, args_dispatch)
529 preprocessor_check_start, preprocessor_check_end = \
530 gen_dependencies(dependencies)
531 return preprocessor_check_start + code + preprocessor_check_end
532
533
Azim Khanb31aa442018-07-03 11:57:54 +0100534def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100535 """
Azim Khan040b6a22018-06-28 16:49:13 +0100536 Parses out a function from function file object and generates
537 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100538
Azim Khanf0e42fb2017-08-02 14:47:13 +0100539 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100540 :param dependencies: List of dependencies
541 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100542 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100543 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100544 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
545 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100546 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100547 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100548 # Check function signature. Function signature may be split
549 # across multiple lines. Here we try to find the start of
550 # arguments list, then remove '\n's and apply the regex to
551 # detect function start.
552 up_to_arg_list_start = code + line[:line.find('(') + 1]
553 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
554 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100555 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100556 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100557 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100558 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100559 for lin in funcs_f:
560 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100561 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100562 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100563 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100564 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100565 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100566 break
567 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100568 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100569 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100570 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100571
Azim Khanfcdf6852018-07-05 17:31:46 +0100572 # Prefix test function name with 'test_'
573 code = code.replace(name, 'test_' + name, 1)
574 name = 'test_' + name
575
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100576 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100577 if re.search(END_CASE_REGEX, line):
578 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100579 if not has_exit_label:
580 has_exit_label = \
581 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100582 code += line
583 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100584 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100585 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100586
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100587 code = line_directive + code
588 code = generate_function_code(name, code, local_vars, args_dispatch,
589 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100590 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100591 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100592
593
594def parse_functions(funcs_f):
595 """
Azim Khane3b26af2018-06-29 02:36:57 +0100596 Parses a test_suite_xxx.function file and returns information
597 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100598
Azim Khanf0e42fb2017-08-02 14:47:13 +0100599 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100600 :return: List of test suite dependencies, test function dispatch
601 code, function code and a dict with function identifiers
602 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100603 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000604 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100605 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100606 suite_functions = ''
607 func_info = {}
608 function_idx = 0
609 dispatch_code = ''
610 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100611 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100612 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000613 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100614 suite_helpers += parse_until_pattern(funcs_f,
615 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100616 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100617 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100618 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100619 try:
620 dependencies = parse_function_dependencies(line)
621 except GeneratorInputError as error:
622 raise GeneratorInputError(
623 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
624 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100625 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100626 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100627 suite_functions += func_code
628 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100629 if func_name in func_info:
630 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100631 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100632 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100633 func_info[func_name] = (function_idx, args)
634 dispatch_code += '/* Function Id: %d */\n' % function_idx
635 dispatch_code += func_dispatch
636 function_idx += 1
637
Azim Khanb31aa442018-07-03 11:57:54 +0100638 func_code = (suite_helpers +
639 suite_functions).join(gen_dependencies(suite_dependencies))
640 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100641
642
Azim Khanb31aa442018-07-03 11:57:54 +0100643def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100644 """
Azim Khanb31aa442018-07-03 11:57:54 +0100645 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100646 Since, return value is used to write back to the intermediate
647 data file, any escape characters in the input are retained in the
648 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100649
Azim Khanb31aa442018-07-03 11:57:54 +0100650 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100651 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100652 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100653 """
Azim Khanb31aa442018-07-03 11:57:54 +0100654 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100655 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100656 out = re.sub(r'(\\.)|' + split_char,
657 lambda m: m.group(1) or '\n', inp_str,
658 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100659 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100660 return out
661
662
Azim Khanb31aa442018-07-03 11:57:54 +0100663def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100664 """
Azim Khane3b26af2018-06-29 02:36:57 +0100665 Parses .data file for each test case name, test function name,
666 test dependencies and test arguments. This information is
667 correlated with the test functions file for generating an
668 intermediate data file replacing the strings for test function
669 names, dependencies and integer constant expressions with
670 identifiers. Mainly for optimising space for on-target
671 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100672
Azim Khanf0e42fb2017-08-02 14:47:13 +0100673 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100674 :return: Generator that yields test name, function name,
675 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100676 """
Azim Khanb31aa442018-07-03 11:57:54 +0100677 __state_read_name = 0
678 __state_read_args = 1
679 state = __state_read_name
680 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100681 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100682 for line in data_f:
683 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100684 # Skip comments
685 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100686 continue
687
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100688 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100689 if not line:
690 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100691 raise GeneratorInputError("[%s:%d] Newline before arguments. "
692 "Test function and arguments "
693 "missing for %s" %
694 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100695 continue
696
Azim Khanb31aa442018-07-03 11:57:54 +0100697 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100698 # Read test name
699 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100700 state = __state_read_args
701 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100702 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100703 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100704 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100705 try:
706 dependencies = parse_dependencies(
707 match.group('dependencies'))
708 except GeneratorInputError as error:
709 raise GeneratorInputError(
710 str(error) + " - %s:%d" %
711 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100712 else:
713 # Read test vectors
714 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100715 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100716 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100717 yield name, test_function, dependencies, args
718 dependencies = []
719 state = __state_read_name
720 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100721 raise GeneratorInputError("[%s:%d] Newline before arguments. "
722 "Test function and arguments missing for "
723 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100724
725
726def gen_dep_check(dep_id, dep):
727 """
Azim Khane3b26af2018-06-29 02:36:57 +0100728 Generate code for checking dependency with the associated
729 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100730
Azim Khanf0e42fb2017-08-02 14:47:13 +0100731 :param dep_id: Dependency identifier
732 :param dep: Dependency macro
733 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100734 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100735 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100736 raise GeneratorInputError("Dependency Id should be a positive "
737 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100738 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
739 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100740 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200741
742 dependency = re.match(CONDITION_REGEX, dep, re.I)
743 if not dependency:
744 raise GeneratorInputError('Invalid dependency %s' % dep)
745
746 _defined = '' if dependency.group(2) else 'defined'
747 _cond = dependency.group(2) if dependency.group(2) else ''
748 _value = dependency.group(3) if dependency.group(3) else ''
749
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100750 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100751 case {id}:
752 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200753#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100754 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100755#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100756 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100757#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100758 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200759 break;'''.format(_not=_not, _defined=_defined,
760 macro=dependency.group(1), id=dep_id,
761 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100762 return dep_check
763
764
765def gen_expression_check(exp_id, exp):
766 """
Azim Khane3b26af2018-06-29 02:36:57 +0100767 Generates code for evaluating an integer expression using
768 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100769
Azim Khanf0e42fb2017-08-02 14:47:13 +0100770 :param exp_id: Expression Identifier
771 :param exp: Expression/Macro
772 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100773 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100774 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100775 raise GeneratorInputError("Expression Id should be a positive "
776 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100777 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100778 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100779 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100780 case {exp_id}:
781 {{
782 *out_value = {expression};
783 }}
784 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100785 return exp_code
786
787
Azim Khanb31aa442018-07-03 11:57:54 +0100788def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100789 """
Azim Khane3b26af2018-06-29 02:36:57 +0100790 Write dependencies to intermediate test data file, replacing
791 the string form with identifiers. Also, generates dependency
792 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100793
Azim Khanf0e42fb2017-08-02 14:47:13 +0100794 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100795 :param test_dependencies: Dependencies
796 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100797 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100798 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100799 """
Azim Khan599cd242017-07-06 17:34:27 +0100800 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100801 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100802 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100803 for dep in test_dependencies:
804 if dep not in unique_dependencies:
805 unique_dependencies.append(dep)
806 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100807 dep_check_code += gen_dep_check(dep_id, dep)
808 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100809 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100810 out_data_f.write(':' + str(dep_id))
811 out_data_f.write('\n')
812 return dep_check_code
813
814
815def write_parameters(out_data_f, test_args, func_args, unique_expressions):
816 """
Azim Khane3b26af2018-06-29 02:36:57 +0100817 Writes test parameters to the intermediate data file, replacing
818 the string form with identifiers. Also, generates expression
819 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100820
Azim Khanf0e42fb2017-08-02 14:47:13 +0100821 :param out_data_f: Output intermediate data file
822 :param test_args: Test parameters
823 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100824 :param unique_expressions: Mutable list to track unique
825 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100826 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100827 """
828 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100829 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100830 typ = func_args[i]
831 val = test_args[i]
832
Azim Khan040b6a22018-06-28 16:49:13 +0100833 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100834 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
835 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100836 typ = 'exp'
837 if val not in unique_expressions:
838 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100839 # exp_id can be derived from len(). But for
840 # readability and consistency with case of existing
841 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100842 exp_id = unique_expressions.index(val)
843 expression_code += gen_expression_check(exp_id, val)
844 val = exp_id
845 else:
846 val = unique_expressions.index(val)
847 out_data_f.write(':' + typ + ':' + str(val))
848 out_data_f.write('\n')
849 return expression_code
850
851
Azim Khanb31aa442018-07-03 11:57:54 +0100852def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100853 """
Azim Khane3b26af2018-06-29 02:36:57 +0100854 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100855
Azim Khanb31aa442018-07-03 11:57:54 +0100856 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100857 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100858 :param dep_check_code: Dependency check code
859 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100860 :return: Dependency and expression code guarded by test suite
861 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100862 """
Azim Khanb31aa442018-07-03 11:57:54 +0100863 if suite_dependencies:
864 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100865 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100866{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100867{code}
Azim Khan599cd242017-07-06 17:34:27 +0100868#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100869'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100870 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100871{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100872{code}
Azim Khan599cd242017-07-06 17:34:27 +0100873#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100874'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100875 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100876
877
Azim Khanb31aa442018-07-03 11:57:54 +0100878def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100879 """
Azim Khane3b26af2018-06-29 02:36:57 +0100880 This function reads test case name, dependencies and test vectors
881 from the .data file. This information is correlated with the test
882 functions file for generating an intermediate data file replacing
883 the strings for test function names, dependencies and integer
884 constant expressions with identifiers. Mainly for optimising
885 space for on-target execution.
886 It also generates test case dependency check code and expression
887 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100888
Azim Khanf0e42fb2017-08-02 14:47:13 +0100889 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100890 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100891 :param func_info: Dict keyed by function and with function id
892 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100893 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100894 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100895 """
Azim Khanb31aa442018-07-03 11:57:54 +0100896 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100897 unique_expressions = []
898 dep_check_code = ''
899 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100900 for test_name, function_name, test_dependencies, test_args in \
901 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100902 out_data_f.write(test_name + '\n')
903
Azim Khanb31aa442018-07-03 11:57:54 +0100904 # Write dependencies
905 dep_check_code += write_dependencies(out_data_f, test_dependencies,
906 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100907
Azim Khan599cd242017-07-06 17:34:27 +0100908 # Write test function name
909 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100910 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100911 raise GeneratorInputError("Function %s not found!" %
912 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100913 func_id, func_args = func_info[test_function_name]
914 out_data_f.write(str(func_id))
915
916 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100917 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100918 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100919 "%s. See function %s signature." %
920 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100921 expression_code += write_parameters(out_data_f, test_args, func_args,
922 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100923
Azim Khan599cd242017-07-06 17:34:27 +0100924 # Write a newline as test case separator
925 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100926
Azim Khanb31aa442018-07-03 11:57:54 +0100927 dep_check_code, expression_code = gen_suite_dep_checks(
928 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100929 return dep_check_code, expression_code
930
931
Azim Khanb31aa442018-07-03 11:57:54 +0100932def add_input_info(funcs_file, data_file, template_file,
933 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100934 """
Azim Khanb31aa442018-07-03 11:57:54 +0100935 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100936
Azim Khanf0e42fb2017-08-02 14:47:13 +0100937 :param funcs_file: Functions file object
938 :param data_file: Data file object
939 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100940 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100941 :param snippets: Dictionary to contain code pieces to be
942 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100943 :return:
944 """
Azim Khanb31aa442018-07-03 11:57:54 +0100945 snippets['test_file'] = c_file
946 snippets['test_main_file'] = template_file
947 snippets['test_case_file'] = funcs_file
948 snippets['test_case_data_file'] = data_file
949
950
951def read_code_from_input_files(platform_file, helpers_file,
952 out_data_file, snippets):
953 """
954 Read code from input files and create substitutions for replacement
955 strings in the template file.
956
957 :param platform_file: Platform file object
958 :param helpers_file: Helper functions file object
959 :param out_data_file: Output intermediate data file object
960 :param snippets: Dictionary to contain code pieces to be
961 substituted in the template.
962 :return:
963 """
964 # Read helpers
965 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
966 platform_f:
967 snippets['test_common_helper_file'] = helpers_file
968 snippets['test_common_helpers'] = help_f.read()
969 snippets['test_platform_file'] = platform_file
970 snippets['platform_code'] = platform_f.read().replace(
971 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
972
973
974def write_test_source_file(template_file, c_file, snippets):
975 """
976 Write output source file with generated source code.
977
978 :param template_file: Template file name
979 :param c_file: Output source file
980 :param snippets: Generated and code snippets
981 :return:
982 """
David Horstmann14bae832022-11-03 17:49:29 +0000983
984 # Create a placeholder pattern with the correct named capture groups
985 # to override the default provided with Template.
986 # Match nothing (no way of escaping placeholders).
987 escaped = "(?P<escaped>(?!))"
988 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
989 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
990 # Match nothing (no braced placeholder syntax).
991 braced = "(?P<braced>(?!))"
992 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
993 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
David Horstmann8eff06f2022-11-09 17:27:33 +0000994 placeholder_pattern = re.compile("|".join([escaped, named, braced, invalid]))
David Horstmann14bae832022-11-03 17:49:29 +0000995
Azim Khanb31aa442018-07-03 11:57:54 +0100996 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +0100997 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +0100998 # Update line number. +1 as #line directive sets next line number
999 snippets['line_no'] = line_no + 1
David Horstmann14bae832022-11-03 17:49:29 +00001000 template = string.Template(line)
1001 template.pattern = placeholder_pattern
1002 snippets = {k.upper():v for (k, v) in snippets.items()}
1003 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001004 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001005
1006
1007def parse_function_file(funcs_file, snippets):
1008 """
1009 Parse function file and generate function dispatch code.
1010
1011 :param funcs_file: Functions file name
1012 :param snippets: Dictionary to contain code pieces to be
1013 substituted in the template.
1014 :return:
1015 """
1016 with FileWrapper(funcs_file) as funcs_f:
1017 suite_dependencies, dispatch_code, func_code, func_info = \
1018 parse_functions(funcs_f)
1019 snippets['functions_code'] = func_code
1020 snippets['dispatch_code'] = dispatch_code
1021 return suite_dependencies, func_info
1022
1023
1024def generate_intermediate_data_file(data_file, out_data_file,
1025 suite_dependencies, func_info, snippets):
1026 """
1027 Generates intermediate data file from input data file and
1028 information read from functions file.
1029
1030 :param data_file: Data file name
1031 :param out_data_file: Output/Intermediate data file
1032 :param suite_dependencies: List of suite dependencies.
1033 :param func_info: Function info parsed from functions file.
1034 :param snippets: Dictionary to contain code pieces to be
1035 substituted in the template.
1036 :return:
1037 """
1038 with FileWrapper(data_file) as data_f, \
1039 open(out_data_file, 'w') as out_data_f:
1040 dep_check_code, expression_code = gen_from_test_data(
1041 data_f, out_data_f, func_info, suite_dependencies)
1042 snippets['dep_check_code'] = dep_check_code
1043 snippets['expression_code'] = expression_code
1044
1045
1046def generate_code(**input_info):
1047 """
1048 Generates C source code from test suite file, data file, common
1049 helpers file and platform file.
1050
1051 input_info expands to following parameters:
1052 funcs_file: Functions file object
1053 data_file: Data file object
1054 template_file: Template file object
1055 platform_file: Platform file object
1056 helpers_file: Helper functions file object
1057 suites_dir: Test suites dir
1058 c_file: Output C file object
1059 out_data_file: Output intermediate data file object
1060 :return:
1061 """
1062 funcs_file = input_info['funcs_file']
1063 data_file = input_info['data_file']
1064 template_file = input_info['template_file']
1065 platform_file = input_info['platform_file']
1066 helpers_file = input_info['helpers_file']
1067 suites_dir = input_info['suites_dir']
1068 c_file = input_info['c_file']
1069 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001070 for name, path in [('Functions file', funcs_file),
1071 ('Data file', data_file),
1072 ('Template file', template_file),
1073 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001074 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001075 ('Suites dir', suites_dir)]:
1076 if not os.path.exists(path):
1077 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1078
Azim Khanb31aa442018-07-03 11:57:54 +01001079 snippets = {'generator_script': os.path.basename(__file__)}
1080 read_code_from_input_files(platform_file, helpers_file,
1081 out_data_file, snippets)
1082 add_input_info(funcs_file, data_file, template_file,
1083 c_file, snippets)
1084 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1085 generate_intermediate_data_file(data_file, out_data_file,
1086 suite_dependencies, func_info, snippets)
1087 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001088
1089
Azim Khan8d686bf2018-07-04 23:29:46 +01001090def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001091 """
1092 Command line parser.
1093
1094 :return:
1095 """
Azim Khan040b6a22018-06-28 16:49:13 +01001096 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001097 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001098
1099 parser.add_argument("-f", "--functions-file",
1100 dest="funcs_file",
1101 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001102 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001103 required=True)
1104
1105 parser.add_argument("-d", "--data-file",
1106 dest="data_file",
1107 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001108 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001109 required=True)
1110
1111 parser.add_argument("-t", "--template-file",
1112 dest="template_file",
1113 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001114 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001115 required=True)
1116
1117 parser.add_argument("-s", "--suites-dir",
1118 dest="suites_dir",
1119 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001120 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001121 required=True)
1122
Azim Khane3b26af2018-06-29 02:36:57 +01001123 parser.add_argument("--helpers-file",
1124 dest="helpers_file",
1125 help="Helpers file",
1126 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001127 required=True)
1128
1129 parser.add_argument("-p", "--platform-file",
1130 dest="platform_file",
1131 help="Platform code file",
1132 metavar="PLATFORM_FILE",
1133 required=True)
1134
1135 parser.add_argument("-o", "--out-dir",
1136 dest="out_dir",
1137 help="Dir where generated code and scripts are copied",
1138 metavar="OUT_DIR",
1139 required=True)
1140
1141 args = parser.parse_args()
1142
1143 data_file_name = os.path.basename(args.data_file)
1144 data_name = os.path.splitext(data_file_name)[0]
1145
1146 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001147 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001148
1149 out_c_file_dir = os.path.dirname(out_c_file)
1150 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001151 for directory in [out_c_file_dir, out_data_file_dir]:
1152 if not os.path.exists(directory):
1153 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001154
Azim Khanb31aa442018-07-03 11:57:54 +01001155 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1156 template_file=args.template_file,
1157 platform_file=args.platform_file,
1158 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1159 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001160
1161
1162if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001163 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001164 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001165 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001166 sys.exit("%s: input error: %s" %
1167 (os.path.basename(sys.argv[0]), str(err)))