blob: 6d65986c8852ffec0dee5e33355f8c546099f7ac [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 Horstmann14bae832022-11-03 17:49:29 +0000129__MBEDTLS_TEST_TEMPLATE__TEST_COMMON_HELPERS <-- All common code from helpers.function
130 is substituted here.
131__MBEDTLS_TEST_TEMPLATE__FUNCTIONS_CODE <-- Test functions are substituted here
132 from the input test_suit_xyz.function
133 file. C preprocessor checks are generated
134 for the build dependencies specified
135 in the input file. This script also
136 generates wrappers for the test
137 functions with code to expand the
138 string parameters read from the data
139 file.
140__MBEDTLS_TEST_TEMPLATE__EXPRESSION_CODE <-- This script enumerates the
141 expressions in the .data file and
142 generates code to handle enumerated
143 expression Ids and return the values.
144__MBEDTLS_TEST_TEMPLATE__DEP_CHECK_CODE <-- This script enumerates all
145 build dependencies and generate
146 code to handle enumerated build
147 dependency Id and return status: if
148 the dependency is defined or not.
149__MBEDTLS_TEST_TEMPLATE__DISPATCH_CODE <-- This script enumerates the functions
150 specified in the input test data file
151 and generates the initializer for the
152 function table in the template
153 file.
154__MBEDTLS_TEST_TEMPLATE__PLATFORM_CODE <-- Platform specific setup and test
155 dispatch code.
Azim Khanaee05bb2018-07-02 16:01:04 +0100156
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100157"""
158
Azim Khanf0e42fb2017-08-02 14:47:13 +0100159
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100160import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100161import os
162import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100163import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100164import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100165import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100166
167
Azim Khanb31aa442018-07-03 11:57:54 +0100168BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
169END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100170
Azim Khanb31aa442018-07-03 11:57:54 +0100171BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
172END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000173
Azim Khanb31aa442018-07-03 11:57:54 +0100174BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
175END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100176
Azim Khan8d686bf2018-07-04 23:29:46 +0100177BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100178END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100179
Azim Khan8d686bf2018-07-04 23:29:46 +0100180DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200181C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
182CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
183# forbid 0ddd which might be accidentally octal or accidentally decimal
184CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
185CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
186 CONDITION_OPERATOR_REGEX,
187 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100188TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100189INT_CHECK_REGEX = r'int\s+.*'
190CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
191DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100192FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
193EXIT_LABEL_REGEX = r'^exit:'
194
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100195
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100196class GeneratorInputError(Exception):
197 """
Azim Khane3b26af2018-06-29 02:36:57 +0100198 Exception to indicate error in the input files to this script.
199 This includes missing patterns, test function names and other
200 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100201 """
202 pass
203
204
Gilles Peskine184c0962020-03-24 18:25:17 +0100205class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100206 """
Azim Khane3b26af2018-06-29 02:36:57 +0100207 This class extends built-in io.FileIO class with attribute line_no,
208 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100209 """
210
211 def __init__(self, file_name):
212 """
Azim Khane3b26af2018-06-29 02:36:57 +0100213 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100214
Azim Khanf0e42fb2017-08-02 14:47:13 +0100215 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100216 """
217 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100218 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100219
Azim Khanb31aa442018-07-03 11:57:54 +0100220 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100221 """
Azim Khane3b26af2018-06-29 02:36:57 +0100222 Python 2 iterator method. This method overrides base class's
223 next method and extends the next method to count the line
224 numbers as each line is read.
225
226 It works for both Python 2 and Python 3 by checking iterator
227 method name in the base iterator object.
228
Azim Khanf0e42fb2017-08-02 14:47:13 +0100229 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100230 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200231 parent = super(FileWrapper, self)
232 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100233 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200234 else:
Gilles Peskinee915d532019-02-25 21:39:42 +0100235 line = parent.next() # Python 2 # pylint: disable=no-member
Azim Khanb31aa442018-07-03 11:57:54 +0100236 if line is not None:
237 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100238 # Convert byte array to string with correct encoding and
239 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100240 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100241 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100242
243 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100244 __next__ = next
245
246 def get_line_no(self):
247 """
248 Gives current line number.
249 """
250 return self._line_no
251
252 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100253
254
255def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100256 """
Azim Khanb31aa442018-07-03 11:57:54 +0100257 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100258
259 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100260 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
261 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262 """
Azim Khan4b543232017-06-30 09:35:21 +0100263 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
264
265
Azim Khanb31aa442018-07-03 11:57:54 +0100266def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100267 """
Azim Khane3b26af2018-06-29 02:36:57 +0100268 Test suite data and functions specifies compile time dependencies.
269 This function generates C preprocessor code from the input
270 dependency list. Caller uses the generated preprocessor code to
271 wrap dependent code.
272 A dependency in the input list can have a leading '!' character
273 to negate a condition. '!' is separated from the dependency using
274 function split_dep() and proper preprocessor check is generated
275 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100276
Azim Khanb31aa442018-07-03 11:57:54 +0100277 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100278 :return: if defined and endif code with macro annotations for
279 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100280 """
Azim Khanb31aa442018-07-03 11:57:54 +0100281 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
282 map(split_dep, dependencies)])
283 dep_end = ''.join(['#endif /* %s */\n' %
284 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100285
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100286 return dep_start, dep_end
287
288
Azim Khanb31aa442018-07-03 11:57:54 +0100289def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100290 """
Azim Khanb31aa442018-07-03 11:57:54 +0100291 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100292 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100293
Azim Khanb31aa442018-07-03 11:57:54 +0100294 :param dependencies: List of dependencies.
295 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100296 """
Azim Khanb31aa442018-07-03 11:57:54 +0100297 defines = '#if ' if dependencies else ''
298 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
299 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100300 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100301
302
Azim Khanb31aa442018-07-03 11:57:54 +0100303def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100304 """
Azim Khan040b6a22018-06-28 16:49:13 +0100305 Creates test function wrapper code. A wrapper has the code to
306 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
Azim Khanf0e42fb2017-08-02 14:47:13 +0100308 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100309 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100310 :param args_dispatch: List of dispatch arguments.
311 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100312 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100313 """
314 # Then create the wrapper
315 wrapper = '''
316void {name}_wrapper( void ** params )
317{{
Gilles Peskine77761412018-06-18 17:51:40 +0200318{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100319 {name}( {args} );
320}}
Gilles Peskine77761412018-06-18 17:51:40 +0200321'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100322 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100323 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100324 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100325 return wrapper
326
327
Azim Khanb31aa442018-07-03 11:57:54 +0100328def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100329 """
Azim Khane3b26af2018-06-29 02:36:57 +0100330 Test suite code template main_test.function defines a C function
331 array to contain test case functions. This function generates an
332 initializer entry for a function in that array. The entry is
333 composed of a compile time check for the test function
334 dependencies. At compile time the test function is assigned when
335 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100336
Azim Khanf0e42fb2017-08-02 14:47:13 +0100337 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100338 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100339 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100340 """
Azim Khanb31aa442018-07-03 11:57:54 +0100341 if dependencies:
342 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100343 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100344{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100345 {name}_wrapper,
346#else
347 NULL,
348#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100349'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100350 else:
351 dispatch_code = '''
352 {name}_wrapper,
353'''.format(name=name)
354
355 return dispatch_code
356
357
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000358def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100359 """
Azim Khane3b26af2018-06-29 02:36:57 +0100360 Matches pattern end_regex to the lines read from the file object.
361 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100362
Azim Khan8d686bf2018-07-04 23:29:46 +0100363 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000364 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100365 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100366 """
Azim Khan4b543232017-06-30 09:35:21 +0100367 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100368 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000369 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100370 break
371 headers += line
372 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100373 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100374 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100375
Azim Khan4b543232017-06-30 09:35:21 +0100376 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100377
378
Azim Khan8d686bf2018-07-04 23:29:46 +0100379def validate_dependency(dependency):
380 """
381 Validates a C macro and raises GeneratorInputError on invalid input.
382 :param dependency: Input macro dependency
383 :return: input dependency stripped of leading & trailing white spaces.
384 """
385 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200386 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100387 raise GeneratorInputError('Invalid dependency %s' % dependency)
388 return dependency
389
390
391def parse_dependencies(inp_str):
392 """
393 Parses dependencies out of inp_str, validates them and returns a
394 list of macros.
395
396 :param inp_str: Input string with macros delimited by ':'.
397 :return: list of dependencies
398 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100399 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100400 return dependencies
401
402
Azim Khanb31aa442018-07-03 11:57:54 +0100403def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100404 """
Azim Khane3b26af2018-06-29 02:36:57 +0100405 Parses test suite dependencies specified at the top of a
406 .function file, that starts with pattern BEGIN_DEPENDENCIES
407 and end with END_DEPENDENCIES. Dependencies are specified
408 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100409
Azim Khan8d686bf2018-07-04 23:29:46 +0100410 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100411 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100412 """
Azim Khanb31aa442018-07-03 11:57:54 +0100413 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100414 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100415 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100416 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100417 try:
418 dependencies = parse_dependencies(match.group('dependencies'))
419 except GeneratorInputError as error:
420 raise GeneratorInputError(
421 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100422 if re.search(END_DEP_REGEX, line):
423 break
424 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100425 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100426 " not found!" % (funcs_f.name,
427 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100428
Azim Khanb31aa442018-07-03 11:57:54 +0100429 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100430
431
Azim Khanb31aa442018-07-03 11:57:54 +0100432def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100433 """
Azim Khane3b26af2018-06-29 02:36:57 +0100434 Parses function dependencies, that are in the same line as
435 comment BEGIN_CASE. Dependencies are specified after pattern
436 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100437
Azim Khan8d686bf2018-07-04 23:29:46 +0100438 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100439 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100440 """
Azim Khanb31aa442018-07-03 11:57:54 +0100441 dependencies = []
442 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100443 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100444 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100445 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100446 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100447 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100448
Azim Khan8d686bf2018-07-04 23:29:46 +0100449 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100450
Azim Khan4084ec72018-07-05 14:20:08 +0100451
Azim Khanfcdf6852018-07-05 17:31:46 +0100452def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100453 """
Azim Khane3b26af2018-06-29 02:36:57 +0100454 Parses test function signature for validation and generates
455 a dispatch wrapper function that translates input test vectors
456 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100457
Azim Khan8d686bf2018-07-04 23:29:46 +0100458 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100459 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100460 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100461 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100462 """
463 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100464 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100465 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100466 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100467 # Remove characters before arguments
468 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100469 # Process arguments, ex: <type> arg1, <type> arg2 )
470 # This script assumes that the argument list is terminated by ')'
471 # i.e. the test functions will not have a function pointer
472 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100473 for arg in line[:line.find(')')].split(','):
474 arg = arg.strip()
475 if arg == '':
476 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100477 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100478 args.append('int')
479 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100480 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100481 args.append('char*')
482 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100483 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100484 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100485 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100486 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
487 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100488 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100489""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100490
Azim Khan5fcca462018-06-29 11:05:32 +0100491 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100492 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100493 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100494 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100495 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100496 arg_idx += 1
497
Azim Khanfcdf6852018-07-05 17:31:46 +0100498 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100499
500
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100501def generate_function_code(name, code, local_vars, args_dispatch,
502 dependencies):
503 """
504 Generate function code with preprocessor checks and parameter dispatch
505 wrapper.
506
507 :param name: Function name
508 :param code: Function code
509 :param local_vars: Local variables for function wrapper
510 :param args_dispatch: Argument dispatch code
511 :param dependencies: Preprocessor dependencies list
512 :return: Final function code
513 """
514 # Add exit label if not present
515 if code.find('exit:') == -1:
516 split_code = code.rsplit('}', 1)
517 if len(split_code) == 2:
518 code = """exit:
519 ;
520}""".join(split_code)
521
522 code += gen_function_wrapper(name, local_vars, args_dispatch)
523 preprocessor_check_start, preprocessor_check_end = \
524 gen_dependencies(dependencies)
525 return preprocessor_check_start + code + preprocessor_check_end
526
527
Azim Khanb31aa442018-07-03 11:57:54 +0100528def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100529 """
Azim Khan040b6a22018-06-28 16:49:13 +0100530 Parses out a function from function file object and generates
531 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100532
Azim Khanf0e42fb2017-08-02 14:47:13 +0100533 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100534 :param dependencies: List of dependencies
535 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100536 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100537 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100538 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
539 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100540 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100541 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100542 # Check function signature. Function signature may be split
543 # across multiple lines. Here we try to find the start of
544 # arguments list, then remove '\n's and apply the regex to
545 # detect function start.
546 up_to_arg_list_start = code + line[:line.find('(') + 1]
547 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
548 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100549 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100550 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100551 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100552 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100553 for lin in funcs_f:
554 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100555 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100556 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100557 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100558 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100559 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100560 break
561 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100562 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100563 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100564 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100565
Azim Khanfcdf6852018-07-05 17:31:46 +0100566 # Prefix test function name with 'test_'
567 code = code.replace(name, 'test_' + name, 1)
568 name = 'test_' + name
569
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100570 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100571 if re.search(END_CASE_REGEX, line):
572 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100573 if not has_exit_label:
574 has_exit_label = \
575 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100576 code += line
577 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100578 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100579 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100580
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100581 code = line_directive + code
582 code = generate_function_code(name, code, local_vars, args_dispatch,
583 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100584 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100585 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100586
587
588def parse_functions(funcs_f):
589 """
Azim Khane3b26af2018-06-29 02:36:57 +0100590 Parses a test_suite_xxx.function file and returns information
591 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100592
Azim Khanf0e42fb2017-08-02 14:47:13 +0100593 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100594 :return: List of test suite dependencies, test function dispatch
595 code, function code and a dict with function identifiers
596 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100597 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000598 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100599 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100600 suite_functions = ''
601 func_info = {}
602 function_idx = 0
603 dispatch_code = ''
604 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100605 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100606 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000607 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100608 suite_helpers += parse_until_pattern(funcs_f,
609 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100610 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100611 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100612 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100613 try:
614 dependencies = parse_function_dependencies(line)
615 except GeneratorInputError as error:
616 raise GeneratorInputError(
617 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
618 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100619 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100620 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100621 suite_functions += func_code
622 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100623 if func_name in func_info:
624 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100625 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100626 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100627 func_info[func_name] = (function_idx, args)
628 dispatch_code += '/* Function Id: %d */\n' % function_idx
629 dispatch_code += func_dispatch
630 function_idx += 1
631
Azim Khanb31aa442018-07-03 11:57:54 +0100632 func_code = (suite_helpers +
633 suite_functions).join(gen_dependencies(suite_dependencies))
634 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100635
636
Azim Khanb31aa442018-07-03 11:57:54 +0100637def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100638 """
Azim Khanb31aa442018-07-03 11:57:54 +0100639 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100640 Since, return value is used to write back to the intermediate
641 data file, any escape characters in the input are retained in the
642 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100643
Azim Khanb31aa442018-07-03 11:57:54 +0100644 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100645 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100646 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100647 """
Azim Khanb31aa442018-07-03 11:57:54 +0100648 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100649 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100650 out = re.sub(r'(\\.)|' + split_char,
651 lambda m: m.group(1) or '\n', inp_str,
652 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100653 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100654 return out
655
656
Azim Khanb31aa442018-07-03 11:57:54 +0100657def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100658 """
Azim Khane3b26af2018-06-29 02:36:57 +0100659 Parses .data file for each test case name, test function name,
660 test dependencies and test arguments. This information is
661 correlated with the test functions file for generating an
662 intermediate data file replacing the strings for test function
663 names, dependencies and integer constant expressions with
664 identifiers. Mainly for optimising space for on-target
665 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100666
Azim Khanf0e42fb2017-08-02 14:47:13 +0100667 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100668 :return: Generator that yields test name, function name,
669 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100670 """
Azim Khanb31aa442018-07-03 11:57:54 +0100671 __state_read_name = 0
672 __state_read_args = 1
673 state = __state_read_name
674 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100675 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100676 for line in data_f:
677 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100678 # Skip comments
679 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100680 continue
681
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100682 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100683 if not line:
684 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100685 raise GeneratorInputError("[%s:%d] Newline before arguments. "
686 "Test function and arguments "
687 "missing for %s" %
688 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100689 continue
690
Azim Khanb31aa442018-07-03 11:57:54 +0100691 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100692 # Read test name
693 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100694 state = __state_read_args
695 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100696 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100697 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100698 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100699 try:
700 dependencies = parse_dependencies(
701 match.group('dependencies'))
702 except GeneratorInputError as error:
703 raise GeneratorInputError(
704 str(error) + " - %s:%d" %
705 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100706 else:
707 # Read test vectors
708 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100709 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100710 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100711 yield name, test_function, dependencies, args
712 dependencies = []
713 state = __state_read_name
714 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100715 raise GeneratorInputError("[%s:%d] Newline before arguments. "
716 "Test function and arguments missing for "
717 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100718
719
720def gen_dep_check(dep_id, dep):
721 """
Azim Khane3b26af2018-06-29 02:36:57 +0100722 Generate code for checking dependency with the associated
723 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100724
Azim Khanf0e42fb2017-08-02 14:47:13 +0100725 :param dep_id: Dependency identifier
726 :param dep: Dependency macro
727 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100728 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100729 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100730 raise GeneratorInputError("Dependency Id should be a positive "
731 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100732 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
733 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100734 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200735
736 dependency = re.match(CONDITION_REGEX, dep, re.I)
737 if not dependency:
738 raise GeneratorInputError('Invalid dependency %s' % dep)
739
740 _defined = '' if dependency.group(2) else 'defined'
741 _cond = dependency.group(2) if dependency.group(2) else ''
742 _value = dependency.group(3) if dependency.group(3) else ''
743
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100744 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100745 case {id}:
746 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200747#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100748 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100749#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100750 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100751#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100752 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200753 break;'''.format(_not=_not, _defined=_defined,
754 macro=dependency.group(1), id=dep_id,
755 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100756 return dep_check
757
758
759def gen_expression_check(exp_id, exp):
760 """
Azim Khane3b26af2018-06-29 02:36:57 +0100761 Generates code for evaluating an integer expression using
762 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100763
Azim Khanf0e42fb2017-08-02 14:47:13 +0100764 :param exp_id: Expression Identifier
765 :param exp: Expression/Macro
766 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100767 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100768 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100769 raise GeneratorInputError("Expression Id should be a positive "
770 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100771 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100772 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100773 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100774 case {exp_id}:
775 {{
776 *out_value = {expression};
777 }}
778 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100779 return exp_code
780
781
Azim Khanb31aa442018-07-03 11:57:54 +0100782def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100783 """
Azim Khane3b26af2018-06-29 02:36:57 +0100784 Write dependencies to intermediate test data file, replacing
785 the string form with identifiers. Also, generates dependency
786 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100787
Azim Khanf0e42fb2017-08-02 14:47:13 +0100788 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100789 :param test_dependencies: Dependencies
790 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100791 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100792 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100793 """
Azim Khan599cd242017-07-06 17:34:27 +0100794 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100795 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100796 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100797 for dep in test_dependencies:
798 if dep not in unique_dependencies:
799 unique_dependencies.append(dep)
800 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100801 dep_check_code += gen_dep_check(dep_id, dep)
802 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100803 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100804 out_data_f.write(':' + str(dep_id))
805 out_data_f.write('\n')
806 return dep_check_code
807
808
809def write_parameters(out_data_f, test_args, func_args, unique_expressions):
810 """
Azim Khane3b26af2018-06-29 02:36:57 +0100811 Writes test parameters to the intermediate data file, replacing
812 the string form with identifiers. Also, generates expression
813 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100814
Azim Khanf0e42fb2017-08-02 14:47:13 +0100815 :param out_data_f: Output intermediate data file
816 :param test_args: Test parameters
817 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100818 :param unique_expressions: Mutable list to track unique
819 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100820 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100821 """
822 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100823 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100824 typ = func_args[i]
825 val = test_args[i]
826
Azim Khan040b6a22018-06-28 16:49:13 +0100827 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100828 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
829 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100830 typ = 'exp'
831 if val not in unique_expressions:
832 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100833 # exp_id can be derived from len(). But for
834 # readability and consistency with case of existing
835 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100836 exp_id = unique_expressions.index(val)
837 expression_code += gen_expression_check(exp_id, val)
838 val = exp_id
839 else:
840 val = unique_expressions.index(val)
841 out_data_f.write(':' + typ + ':' + str(val))
842 out_data_f.write('\n')
843 return expression_code
844
845
Azim Khanb31aa442018-07-03 11:57:54 +0100846def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100847 """
Azim Khane3b26af2018-06-29 02:36:57 +0100848 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100849
Azim Khanb31aa442018-07-03 11:57:54 +0100850 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100851 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100852 :param dep_check_code: Dependency check code
853 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100854 :return: Dependency and expression code guarded by test suite
855 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100856 """
Azim Khanb31aa442018-07-03 11:57:54 +0100857 if suite_dependencies:
858 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100859 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100860{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100861{code}
Azim Khan599cd242017-07-06 17:34:27 +0100862#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100863'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100864 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100865{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100866{code}
Azim Khan599cd242017-07-06 17:34:27 +0100867#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100868'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100869 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100870
871
Azim Khanb31aa442018-07-03 11:57:54 +0100872def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100873 """
Azim Khane3b26af2018-06-29 02:36:57 +0100874 This function reads test case name, dependencies and test vectors
875 from the .data file. This information is correlated with the test
876 functions file for generating an intermediate data file replacing
877 the strings for test function names, dependencies and integer
878 constant expressions with identifiers. Mainly for optimising
879 space for on-target execution.
880 It also generates test case dependency check code and expression
881 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100882
Azim Khanf0e42fb2017-08-02 14:47:13 +0100883 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100884 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100885 :param func_info: Dict keyed by function and with function id
886 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100887 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100888 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100889 """
Azim Khanb31aa442018-07-03 11:57:54 +0100890 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100891 unique_expressions = []
892 dep_check_code = ''
893 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100894 for test_name, function_name, test_dependencies, test_args in \
895 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100896 out_data_f.write(test_name + '\n')
897
Azim Khanb31aa442018-07-03 11:57:54 +0100898 # Write dependencies
899 dep_check_code += write_dependencies(out_data_f, test_dependencies,
900 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100901
Azim Khan599cd242017-07-06 17:34:27 +0100902 # Write test function name
903 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100904 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100905 raise GeneratorInputError("Function %s not found!" %
906 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100907 func_id, func_args = func_info[test_function_name]
908 out_data_f.write(str(func_id))
909
910 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100911 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100912 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100913 "%s. See function %s signature." %
914 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100915 expression_code += write_parameters(out_data_f, test_args, func_args,
916 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100917
Azim Khan599cd242017-07-06 17:34:27 +0100918 # Write a newline as test case separator
919 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100920
Azim Khanb31aa442018-07-03 11:57:54 +0100921 dep_check_code, expression_code = gen_suite_dep_checks(
922 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100923 return dep_check_code, expression_code
924
925
Azim Khanb31aa442018-07-03 11:57:54 +0100926def add_input_info(funcs_file, data_file, template_file,
927 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100928 """
Azim Khanb31aa442018-07-03 11:57:54 +0100929 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100930
Azim Khanf0e42fb2017-08-02 14:47:13 +0100931 :param funcs_file: Functions file object
932 :param data_file: Data file object
933 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100934 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100935 :param snippets: Dictionary to contain code pieces to be
936 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100937 :return:
938 """
Azim Khanb31aa442018-07-03 11:57:54 +0100939 snippets['test_file'] = c_file
940 snippets['test_main_file'] = template_file
941 snippets['test_case_file'] = funcs_file
942 snippets['test_case_data_file'] = data_file
943
944
945def read_code_from_input_files(platform_file, helpers_file,
946 out_data_file, snippets):
947 """
948 Read code from input files and create substitutions for replacement
949 strings in the template file.
950
951 :param platform_file: Platform file object
952 :param helpers_file: Helper functions file object
953 :param out_data_file: Output intermediate data file object
954 :param snippets: Dictionary to contain code pieces to be
955 substituted in the template.
956 :return:
957 """
958 # Read helpers
959 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
960 platform_f:
961 snippets['test_common_helper_file'] = helpers_file
962 snippets['test_common_helpers'] = help_f.read()
963 snippets['test_platform_file'] = platform_file
964 snippets['platform_code'] = platform_f.read().replace(
965 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
966
967
968def write_test_source_file(template_file, c_file, snippets):
969 """
970 Write output source file with generated source code.
971
972 :param template_file: Template file name
973 :param c_file: Output source file
974 :param snippets: Generated and code snippets
975 :return:
976 """
David Horstmann14bae832022-11-03 17:49:29 +0000977
978 # Create a placeholder pattern with the correct named capture groups
979 # to override the default provided with Template.
980 # Match nothing (no way of escaping placeholders).
981 escaped = "(?P<escaped>(?!))"
982 # Match the "__MBEDTLS_TEST_TEMPLATE__PLACEHOLDER_NAME" pattern.
983 named = "__MBEDTLS_TEST_TEMPLATE__(?P<named>[A-Z][_A-Z0-9]*)"
984 # Match nothing (no braced placeholder syntax).
985 braced = "(?P<braced>(?!))"
986 # If not already matched, a "__MBEDTLS_TEST_TEMPLATE__" prefix is invalid.
987 invalid = "(?P<invalid>__MBEDTLS_TEST_TEMPLATE__)"
988 placeholder_pattern = re.compile(escaped \
989 + "|" + named \
990 + "|" + braced \
991 + "|" + invalid)
992
Azim Khanb31aa442018-07-03 11:57:54 +0100993 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +0100994 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +0100995 # Update line number. +1 as #line directive sets next line number
996 snippets['line_no'] = line_no + 1
David Horstmann14bae832022-11-03 17:49:29 +0000997 template = string.Template(line)
998 template.pattern = placeholder_pattern
999 snippets = {k.upper():v for (k, v) in snippets.items()}
1000 code = template.substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001001 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001002
1003
1004def parse_function_file(funcs_file, snippets):
1005 """
1006 Parse function file and generate function dispatch code.
1007
1008 :param funcs_file: Functions file name
1009 :param snippets: Dictionary to contain code pieces to be
1010 substituted in the template.
1011 :return:
1012 """
1013 with FileWrapper(funcs_file) as funcs_f:
1014 suite_dependencies, dispatch_code, func_code, func_info = \
1015 parse_functions(funcs_f)
1016 snippets['functions_code'] = func_code
1017 snippets['dispatch_code'] = dispatch_code
1018 return suite_dependencies, func_info
1019
1020
1021def generate_intermediate_data_file(data_file, out_data_file,
1022 suite_dependencies, func_info, snippets):
1023 """
1024 Generates intermediate data file from input data file and
1025 information read from functions file.
1026
1027 :param data_file: Data file name
1028 :param out_data_file: Output/Intermediate data file
1029 :param suite_dependencies: List of suite dependencies.
1030 :param func_info: Function info parsed from functions file.
1031 :param snippets: Dictionary to contain code pieces to be
1032 substituted in the template.
1033 :return:
1034 """
1035 with FileWrapper(data_file) as data_f, \
1036 open(out_data_file, 'w') as out_data_f:
1037 dep_check_code, expression_code = gen_from_test_data(
1038 data_f, out_data_f, func_info, suite_dependencies)
1039 snippets['dep_check_code'] = dep_check_code
1040 snippets['expression_code'] = expression_code
1041
1042
1043def generate_code(**input_info):
1044 """
1045 Generates C source code from test suite file, data file, common
1046 helpers file and platform file.
1047
1048 input_info expands to following parameters:
1049 funcs_file: Functions file object
1050 data_file: Data file object
1051 template_file: Template file object
1052 platform_file: Platform file object
1053 helpers_file: Helper functions file object
1054 suites_dir: Test suites dir
1055 c_file: Output C file object
1056 out_data_file: Output intermediate data file object
1057 :return:
1058 """
1059 funcs_file = input_info['funcs_file']
1060 data_file = input_info['data_file']
1061 template_file = input_info['template_file']
1062 platform_file = input_info['platform_file']
1063 helpers_file = input_info['helpers_file']
1064 suites_dir = input_info['suites_dir']
1065 c_file = input_info['c_file']
1066 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001067 for name, path in [('Functions file', funcs_file),
1068 ('Data file', data_file),
1069 ('Template file', template_file),
1070 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001071 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001072 ('Suites dir', suites_dir)]:
1073 if not os.path.exists(path):
1074 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1075
Azim Khanb31aa442018-07-03 11:57:54 +01001076 snippets = {'generator_script': os.path.basename(__file__)}
1077 read_code_from_input_files(platform_file, helpers_file,
1078 out_data_file, snippets)
1079 add_input_info(funcs_file, data_file, template_file,
1080 c_file, snippets)
1081 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1082 generate_intermediate_data_file(data_file, out_data_file,
1083 suite_dependencies, func_info, snippets)
1084 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001085
1086
Azim Khan8d686bf2018-07-04 23:29:46 +01001087def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001088 """
1089 Command line parser.
1090
1091 :return:
1092 """
Azim Khan040b6a22018-06-28 16:49:13 +01001093 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001094 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001095
1096 parser.add_argument("-f", "--functions-file",
1097 dest="funcs_file",
1098 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001099 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001100 required=True)
1101
1102 parser.add_argument("-d", "--data-file",
1103 dest="data_file",
1104 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001105 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001106 required=True)
1107
1108 parser.add_argument("-t", "--template-file",
1109 dest="template_file",
1110 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001111 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001112 required=True)
1113
1114 parser.add_argument("-s", "--suites-dir",
1115 dest="suites_dir",
1116 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001117 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001118 required=True)
1119
Azim Khane3b26af2018-06-29 02:36:57 +01001120 parser.add_argument("--helpers-file",
1121 dest="helpers_file",
1122 help="Helpers file",
1123 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001124 required=True)
1125
1126 parser.add_argument("-p", "--platform-file",
1127 dest="platform_file",
1128 help="Platform code file",
1129 metavar="PLATFORM_FILE",
1130 required=True)
1131
1132 parser.add_argument("-o", "--out-dir",
1133 dest="out_dir",
1134 help="Dir where generated code and scripts are copied",
1135 metavar="OUT_DIR",
1136 required=True)
1137
1138 args = parser.parse_args()
1139
1140 data_file_name = os.path.basename(args.data_file)
1141 data_name = os.path.splitext(data_file_name)[0]
1142
1143 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001144 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001145
1146 out_c_file_dir = os.path.dirname(out_c_file)
1147 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001148 for directory in [out_c_file_dir, out_data_file_dir]:
1149 if not os.path.exists(directory):
1150 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001151
Azim Khanb31aa442018-07-03 11:57:54 +01001152 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1153 template_file=args.template_file,
1154 platform_file=args.platform_file,
1155 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1156 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001157
1158
1159if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001160 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001161 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001162 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001163 sys.exit("%s: input error: %s" %
1164 (os.path.basename(sys.argv[0]), str(err)))