blob: 7382fb6ecb7c15e7c7b67705db33c7bf559e6895 [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.
109In case of on-target target_test.function tests are not dispatched
110on target. Target code is kept minimum and only test functions are
111dispatched. Test case dispatch is done on the host using tools like
112Greentea.
113
114Template file:
115---------
116Template file for example main_test.function is a template C file in
117which generated code and code from input files is substituted to
118generate a compilable C file. It also contains skeleton functions for
119dependency checks, expression evaluation and function dispatch. These
120functions are populated with checks and return codes by this script.
121
122Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100123strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100124
125This script:
126============
127Core function of this script is to fill the template file with
128code that is generated or read from helpers and platform files.
129
130This script replaces following fields in the template and generates
131the test source file:
132
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100133$test_common_helpers <-- All common code from helpers.function
Azim Khanaee05bb2018-07-02 16:01:04 +0100134 is substituted here.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100135$functions_code <-- Test functions are substituted here
Azim Khanaee05bb2018-07-02 16:01:04 +0100136 from the input test_suit_xyz.function
137 file. C preprocessor checks are generated
138 for the build dependencies specified
139 in the input file. This script also
140 generates wrappers for the test
141 functions with code to expand the
142 string parameters read from the data
143 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100144$expression_code <-- This script enumerates the
Azim Khanaee05bb2018-07-02 16:01:04 +0100145 expressions in the .data file and
146 generates code to handle enumerated
147 expression Ids and return the values.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100148$dep_check_code <-- This script enumerates all
Azim Khanaee05bb2018-07-02 16:01:04 +0100149 build dependencies and generate
150 code to handle enumerated build
151 dependency Id and return status: if
152 the dependency is defined or not.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100153$dispatch_code <-- This script enumerates the functions
Azim Khanaee05bb2018-07-02 16:01:04 +0100154 specified in the input test data file
155 and generates the initializer for the
156 function table in the template
157 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100158$platform_code <-- Platform specific setup and test
Azim Khanaee05bb2018-07-02 16:01:04 +0100159 dispatch code.
160
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100161"""
162
Azim Khanf0e42fb2017-08-02 14:47:13 +0100163
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100164import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100165import os
166import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100167import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100168import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100169import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100170
171
Azim Khanb31aa442018-07-03 11:57:54 +0100172BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
173END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100174
Azim Khanb31aa442018-07-03 11:57:54 +0100175BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
176END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000177
Azim Khanb31aa442018-07-03 11:57:54 +0100178BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
179END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100180
Azim Khan8d686bf2018-07-04 23:29:46 +0100181BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100182END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100183
Azim Khan8d686bf2018-07-04 23:29:46 +0100184DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldorb9b38132018-11-27 16:35:20 +0200185C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
186CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
187# forbid 0ddd which might be accidentally octal or accidentally decimal
188CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
189CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
190 CONDITION_OPERATOR_REGEX,
191 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100192TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100193INT_CHECK_REGEX = r'int\s+.*'
194CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
195DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100196FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
197EXIT_LABEL_REGEX = r'^exit:'
198
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100199
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100200class GeneratorInputError(Exception):
201 """
Azim Khane3b26af2018-06-29 02:36:57 +0100202 Exception to indicate error in the input files to this script.
203 This includes missing patterns, test function names and other
204 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100205 """
206 pass
207
208
Gilles Peskine184c0962020-03-24 18:25:17 +0100209class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100210 """
Azim Khane3b26af2018-06-29 02:36:57 +0100211 This class extends built-in io.FileIO class with attribute line_no,
212 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100213 """
214
215 def __init__(self, file_name):
216 """
Azim Khane3b26af2018-06-29 02:36:57 +0100217 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100218
Azim Khanf0e42fb2017-08-02 14:47:13 +0100219 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100220 """
221 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100222 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100223
Azim Khanb31aa442018-07-03 11:57:54 +0100224 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100225 """
Azim Khane3b26af2018-06-29 02:36:57 +0100226 Python 2 iterator method. This method overrides base class's
227 next method and extends the next method to count the line
228 numbers as each line is read.
229
230 It works for both Python 2 and Python 3 by checking iterator
231 method name in the base iterator object.
232
Azim Khanf0e42fb2017-08-02 14:47:13 +0100233 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100234 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200235 parent = super(FileWrapper, self)
236 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100237 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200238 else:
Gilles Peskinee915d532019-02-25 21:39:42 +0100239 line = parent.next() # Python 2 # pylint: disable=no-member
Azim Khanb31aa442018-07-03 11:57:54 +0100240 if line is not None:
241 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100242 # Convert byte array to string with correct encoding and
243 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100244 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100245 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100246
247 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100248 __next__ = next
249
250 def get_line_no(self):
251 """
252 Gives current line number.
253 """
254 return self._line_no
255
256 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100257
258
259def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100260 """
Azim Khanb31aa442018-07-03 11:57:54 +0100261 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262
263 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100264 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
265 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100266 """
Azim Khan4b543232017-06-30 09:35:21 +0100267 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
268
269
Azim Khanb31aa442018-07-03 11:57:54 +0100270def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100271 """
Azim Khane3b26af2018-06-29 02:36:57 +0100272 Test suite data and functions specifies compile time dependencies.
273 This function generates C preprocessor code from the input
274 dependency list. Caller uses the generated preprocessor code to
275 wrap dependent code.
276 A dependency in the input list can have a leading '!' character
277 to negate a condition. '!' is separated from the dependency using
278 function split_dep() and proper preprocessor check is generated
279 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100280
Azim Khanb31aa442018-07-03 11:57:54 +0100281 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100282 :return: if defined and endif code with macro annotations for
283 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100284 """
Azim Khanb31aa442018-07-03 11:57:54 +0100285 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
286 map(split_dep, dependencies)])
287 dep_end = ''.join(['#endif /* %s */\n' %
288 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100289
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100290 return dep_start, dep_end
291
292
Azim Khanb31aa442018-07-03 11:57:54 +0100293def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100294 """
Azim Khanb31aa442018-07-03 11:57:54 +0100295 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100296 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100297
Azim Khanb31aa442018-07-03 11:57:54 +0100298 :param dependencies: List of dependencies.
299 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100300 """
Azim Khanb31aa442018-07-03 11:57:54 +0100301 defines = '#if ' if dependencies else ''
302 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
303 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100304 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100305
306
Azim Khanb31aa442018-07-03 11:57:54 +0100307def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100308 """
Azim Khan040b6a22018-06-28 16:49:13 +0100309 Creates test function wrapper code. A wrapper has the code to
310 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100311
Azim Khanf0e42fb2017-08-02 14:47:13 +0100312 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100313 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100314 :param args_dispatch: List of dispatch arguments.
315 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100316 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100317 """
318 # Then create the wrapper
319 wrapper = '''
320void {name}_wrapper( void ** params )
321{{
Gilles Peskine77761412018-06-18 17:51:40 +0200322{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100323 {name}( {args} );
324}}
Gilles Peskine77761412018-06-18 17:51:40 +0200325'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100326 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100327 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100328 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100329 return wrapper
330
331
Azim Khanb31aa442018-07-03 11:57:54 +0100332def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100333 """
Azim Khane3b26af2018-06-29 02:36:57 +0100334 Test suite code template main_test.function defines a C function
335 array to contain test case functions. This function generates an
336 initializer entry for a function in that array. The entry is
337 composed of a compile time check for the test function
338 dependencies. At compile time the test function is assigned when
339 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100340
Azim Khanf0e42fb2017-08-02 14:47:13 +0100341 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100342 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100343 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100344 """
Azim Khanb31aa442018-07-03 11:57:54 +0100345 if dependencies:
346 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100347 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100348{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100349 {name}_wrapper,
350#else
351 NULL,
352#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100353'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100354 else:
355 dispatch_code = '''
356 {name}_wrapper,
357'''.format(name=name)
358
359 return dispatch_code
360
361
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000362def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100363 """
Azim Khane3b26af2018-06-29 02:36:57 +0100364 Matches pattern end_regex to the lines read from the file object.
365 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100366
Azim Khan8d686bf2018-07-04 23:29:46 +0100367 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000368 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100369 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100370 """
Azim Khan4b543232017-06-30 09:35:21 +0100371 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100372 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000373 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374 break
375 headers += line
376 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100377 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100378 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100379
Azim Khan4b543232017-06-30 09:35:21 +0100380 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100381
382
Azim Khan8d686bf2018-07-04 23:29:46 +0100383def validate_dependency(dependency):
384 """
385 Validates a C macro and raises GeneratorInputError on invalid input.
386 :param dependency: Input macro dependency
387 :return: input dependency stripped of leading & trailing white spaces.
388 """
389 dependency = dependency.strip()
Ron Eldorb9b38132018-11-27 16:35:20 +0200390 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100391 raise GeneratorInputError('Invalid dependency %s' % dependency)
392 return dependency
393
394
395def parse_dependencies(inp_str):
396 """
397 Parses dependencies out of inp_str, validates them and returns a
398 list of macros.
399
400 :param inp_str: Input string with macros delimited by ':'.
401 :return: list of dependencies
402 """
Gilles Peskine8b022352020-03-24 18:36:56 +0100403 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100404 return dependencies
405
406
Azim Khanb31aa442018-07-03 11:57:54 +0100407def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100408 """
Azim Khane3b26af2018-06-29 02:36:57 +0100409 Parses test suite dependencies specified at the top of a
410 .function file, that starts with pattern BEGIN_DEPENDENCIES
411 and end with END_DEPENDENCIES. Dependencies are specified
412 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100413
Azim Khan8d686bf2018-07-04 23:29:46 +0100414 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100415 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100416 """
Azim Khanb31aa442018-07-03 11:57:54 +0100417 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100418 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100419 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100420 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100421 try:
422 dependencies = parse_dependencies(match.group('dependencies'))
423 except GeneratorInputError as error:
424 raise GeneratorInputError(
425 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100426 if re.search(END_DEP_REGEX, line):
427 break
428 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100429 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100430 " not found!" % (funcs_f.name,
431 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100432
Azim Khanb31aa442018-07-03 11:57:54 +0100433 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100434
435
Azim Khanb31aa442018-07-03 11:57:54 +0100436def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100437 """
Azim Khane3b26af2018-06-29 02:36:57 +0100438 Parses function dependencies, that are in the same line as
439 comment BEGIN_CASE. Dependencies are specified after pattern
440 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100441
Azim Khan8d686bf2018-07-04 23:29:46 +0100442 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100443 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100444 """
Azim Khanb31aa442018-07-03 11:57:54 +0100445 dependencies = []
446 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100447 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100448 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100449 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100450 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100451 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100452
Azim Khan8d686bf2018-07-04 23:29:46 +0100453 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100454
Azim Khan4084ec72018-07-05 14:20:08 +0100455
Azim Khanfcdf6852018-07-05 17:31:46 +0100456def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100457 """
Azim Khane3b26af2018-06-29 02:36:57 +0100458 Parses test function signature for validation and generates
459 a dispatch wrapper function that translates input test vectors
460 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100461
Azim Khan8d686bf2018-07-04 23:29:46 +0100462 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100463 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100464 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100465 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100466 """
467 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100468 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100469 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100470 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100471 # Remove characters before arguments
472 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100473 # Process arguments, ex: <type> arg1, <type> arg2 )
474 # This script assumes that the argument list is terminated by ')'
475 # i.e. the test functions will not have a function pointer
476 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100477 for arg in line[:line.find(')')].split(','):
478 arg = arg.strip()
479 if arg == '':
480 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100481 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100482 args.append('int')
483 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100484 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100485 args.append('char*')
486 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100487 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100488 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100489 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100490 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
491 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100492 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100493""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100494
Azim Khan5fcca462018-06-29 11:05:32 +0100495 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100496 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100497 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100498 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100499 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100500 arg_idx += 1
501
Azim Khanfcdf6852018-07-05 17:31:46 +0100502 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100503
504
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100505def generate_function_code(name, code, local_vars, args_dispatch,
506 dependencies):
507 """
508 Generate function code with preprocessor checks and parameter dispatch
509 wrapper.
510
511 :param name: Function name
512 :param code: Function code
513 :param local_vars: Local variables for function wrapper
514 :param args_dispatch: Argument dispatch code
515 :param dependencies: Preprocessor dependencies list
516 :return: Final function code
517 """
518 # Add exit label if not present
519 if code.find('exit:') == -1:
520 split_code = code.rsplit('}', 1)
521 if len(split_code) == 2:
522 code = """exit:
523 ;
524}""".join(split_code)
525
526 code += gen_function_wrapper(name, local_vars, args_dispatch)
527 preprocessor_check_start, preprocessor_check_end = \
528 gen_dependencies(dependencies)
529 return preprocessor_check_start + code + preprocessor_check_end
530
531
Azim Khanb31aa442018-07-03 11:57:54 +0100532def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100533 """
Azim Khan040b6a22018-06-28 16:49:13 +0100534 Parses out a function from function file object and generates
535 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100536
Azim Khanf0e42fb2017-08-02 14:47:13 +0100537 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100538 :param dependencies: List of dependencies
539 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100540 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100541 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100542 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
543 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100544 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100545 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100546 # Check function signature. Function signature may be split
547 # across multiple lines. Here we try to find the start of
548 # arguments list, then remove '\n's and apply the regex to
549 # detect function start.
550 up_to_arg_list_start = code + line[:line.find('(') + 1]
551 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
552 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100553 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100554 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100555 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100556 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100557 for lin in funcs_f:
558 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100559 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100560 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100561 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100562 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100563 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100564 break
565 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100566 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100567 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100568 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100569
Azim Khanfcdf6852018-07-05 17:31:46 +0100570 # Prefix test function name with 'test_'
571 code = code.replace(name, 'test_' + name, 1)
572 name = 'test_' + name
573
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100574 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100575 if re.search(END_CASE_REGEX, line):
576 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100577 if not has_exit_label:
578 has_exit_label = \
579 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100580 code += line
581 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100582 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100583 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100584
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100585 code = line_directive + code
586 code = generate_function_code(name, code, local_vars, args_dispatch,
587 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100588 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100589 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100590
591
592def parse_functions(funcs_f):
593 """
Azim Khane3b26af2018-06-29 02:36:57 +0100594 Parses a test_suite_xxx.function file and returns information
595 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100596
Azim Khanf0e42fb2017-08-02 14:47:13 +0100597 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100598 :return: List of test suite dependencies, test function dispatch
599 code, function code and a dict with function identifiers
600 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100601 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000602 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100603 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100604 suite_functions = ''
605 func_info = {}
606 function_idx = 0
607 dispatch_code = ''
608 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100609 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100610 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000611 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100612 suite_helpers += parse_until_pattern(funcs_f,
613 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100614 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100615 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100616 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100617 try:
618 dependencies = parse_function_dependencies(line)
619 except GeneratorInputError as error:
620 raise GeneratorInputError(
621 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
622 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100623 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100624 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100625 suite_functions += func_code
626 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100627 if func_name in func_info:
628 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100629 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100630 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100631 func_info[func_name] = (function_idx, args)
632 dispatch_code += '/* Function Id: %d */\n' % function_idx
633 dispatch_code += func_dispatch
634 function_idx += 1
635
Azim Khanb31aa442018-07-03 11:57:54 +0100636 func_code = (suite_helpers +
637 suite_functions).join(gen_dependencies(suite_dependencies))
638 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100639
640
Azim Khanb31aa442018-07-03 11:57:54 +0100641def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100642 """
Azim Khanb31aa442018-07-03 11:57:54 +0100643 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100644 Since, return value is used to write back to the intermediate
645 data file, any escape characters in the input are retained in the
646 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100647
Azim Khanb31aa442018-07-03 11:57:54 +0100648 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100649 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100650 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100651 """
Azim Khanb31aa442018-07-03 11:57:54 +0100652 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100653 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100654 out = re.sub(r'(\\.)|' + split_char,
655 lambda m: m.group(1) or '\n', inp_str,
656 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100657 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100658 return out
659
660
Azim Khanb31aa442018-07-03 11:57:54 +0100661def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100662 """
Azim Khane3b26af2018-06-29 02:36:57 +0100663 Parses .data file for each test case name, test function name,
664 test dependencies and test arguments. This information is
665 correlated with the test functions file for generating an
666 intermediate data file replacing the strings for test function
667 names, dependencies and integer constant expressions with
668 identifiers. Mainly for optimising space for on-target
669 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100670
Azim Khanf0e42fb2017-08-02 14:47:13 +0100671 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100672 :return: Generator that yields test name, function name,
673 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100674 """
Azim Khanb31aa442018-07-03 11:57:54 +0100675 __state_read_name = 0
676 __state_read_args = 1
677 state = __state_read_name
678 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100679 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100680 for line in data_f:
681 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100682 # Skip comments
683 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100684 continue
685
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100686 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100687 if not line:
688 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100689 raise GeneratorInputError("[%s:%d] Newline before arguments. "
690 "Test function and arguments "
691 "missing for %s" %
692 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100693 continue
694
Azim Khanb31aa442018-07-03 11:57:54 +0100695 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100696 # Read test name
697 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100698 state = __state_read_args
699 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100700 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100701 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100702 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100703 try:
704 dependencies = parse_dependencies(
705 match.group('dependencies'))
706 except GeneratorInputError as error:
707 raise GeneratorInputError(
708 str(error) + " - %s:%d" %
709 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100710 else:
711 # Read test vectors
712 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100713 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100714 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100715 yield name, test_function, dependencies, args
716 dependencies = []
717 state = __state_read_name
718 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100719 raise GeneratorInputError("[%s:%d] Newline before arguments. "
720 "Test function and arguments missing for "
721 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100722
723
724def gen_dep_check(dep_id, dep):
725 """
Azim Khane3b26af2018-06-29 02:36:57 +0100726 Generate code for checking dependency with the associated
727 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100728
Azim Khanf0e42fb2017-08-02 14:47:13 +0100729 :param dep_id: Dependency identifier
730 :param dep: Dependency macro
731 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100732 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100733 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100734 raise GeneratorInputError("Dependency Id should be a positive "
735 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100736 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
737 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100738 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldorb9b38132018-11-27 16:35:20 +0200739
740 dependency = re.match(CONDITION_REGEX, dep, re.I)
741 if not dependency:
742 raise GeneratorInputError('Invalid dependency %s' % dep)
743
744 _defined = '' if dependency.group(2) else 'defined'
745 _cond = dependency.group(2) if dependency.group(2) else ''
746 _value = dependency.group(3) if dependency.group(3) else ''
747
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100748 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100749 case {id}:
750 {{
Ron Eldorb9b38132018-11-27 16:35:20 +0200751#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100752 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100753#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100754 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100755#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100756 }}
Ron Eldorb9b38132018-11-27 16:35:20 +0200757 break;'''.format(_not=_not, _defined=_defined,
758 macro=dependency.group(1), id=dep_id,
759 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100760 return dep_check
761
762
763def gen_expression_check(exp_id, exp):
764 """
Azim Khane3b26af2018-06-29 02:36:57 +0100765 Generates code for evaluating an integer expression using
766 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100767
Azim Khanf0e42fb2017-08-02 14:47:13 +0100768 :param exp_id: Expression Identifier
769 :param exp: Expression/Macro
770 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100771 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100772 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100773 raise GeneratorInputError("Expression Id should be a positive "
774 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100775 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100776 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100777 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100778 case {exp_id}:
779 {{
780 *out_value = {expression};
781 }}
782 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100783 return exp_code
784
785
Azim Khanb31aa442018-07-03 11:57:54 +0100786def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100787 """
Azim Khane3b26af2018-06-29 02:36:57 +0100788 Write dependencies to intermediate test data file, replacing
789 the string form with identifiers. Also, generates dependency
790 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100791
Azim Khanf0e42fb2017-08-02 14:47:13 +0100792 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100793 :param test_dependencies: Dependencies
794 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100795 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100796 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100797 """
Azim Khan599cd242017-07-06 17:34:27 +0100798 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100799 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100800 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100801 for dep in test_dependencies:
802 if dep not in unique_dependencies:
803 unique_dependencies.append(dep)
804 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100805 dep_check_code += gen_dep_check(dep_id, dep)
806 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100807 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100808 out_data_f.write(':' + str(dep_id))
809 out_data_f.write('\n')
810 return dep_check_code
811
812
813def write_parameters(out_data_f, test_args, func_args, unique_expressions):
814 """
Azim Khane3b26af2018-06-29 02:36:57 +0100815 Writes test parameters to the intermediate data file, replacing
816 the string form with identifiers. Also, generates expression
817 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100818
Azim Khanf0e42fb2017-08-02 14:47:13 +0100819 :param out_data_f: Output intermediate data file
820 :param test_args: Test parameters
821 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100822 :param unique_expressions: Mutable list to track unique
823 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100824 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100825 """
826 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100827 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100828 typ = func_args[i]
829 val = test_args[i]
830
Azim Khan040b6a22018-06-28 16:49:13 +0100831 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100832 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
833 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100834 typ = 'exp'
835 if val not in unique_expressions:
836 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100837 # exp_id can be derived from len(). But for
838 # readability and consistency with case of existing
839 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100840 exp_id = unique_expressions.index(val)
841 expression_code += gen_expression_check(exp_id, val)
842 val = exp_id
843 else:
844 val = unique_expressions.index(val)
845 out_data_f.write(':' + typ + ':' + str(val))
846 out_data_f.write('\n')
847 return expression_code
848
849
Azim Khanb31aa442018-07-03 11:57:54 +0100850def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100851 """
Azim Khane3b26af2018-06-29 02:36:57 +0100852 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100853
Azim Khanb31aa442018-07-03 11:57:54 +0100854 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100855 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100856 :param dep_check_code: Dependency check code
857 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100858 :return: Dependency and expression code guarded by test suite
859 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100860 """
Azim Khanb31aa442018-07-03 11:57:54 +0100861 if suite_dependencies:
862 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100863 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100864{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100865{code}
Azim Khan599cd242017-07-06 17:34:27 +0100866#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100867'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100868 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100869{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100870{code}
Azim Khan599cd242017-07-06 17:34:27 +0100871#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100872'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100873 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100874
875
Azim Khanb31aa442018-07-03 11:57:54 +0100876def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100877 """
Azim Khane3b26af2018-06-29 02:36:57 +0100878 This function reads test case name, dependencies and test vectors
879 from the .data file. This information is correlated with the test
880 functions file for generating an intermediate data file replacing
881 the strings for test function names, dependencies and integer
882 constant expressions with identifiers. Mainly for optimising
883 space for on-target execution.
884 It also generates test case dependency check code and expression
885 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100886
Azim Khanf0e42fb2017-08-02 14:47:13 +0100887 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100888 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100889 :param func_info: Dict keyed by function and with function id
890 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100891 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100892 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100893 """
Azim Khanb31aa442018-07-03 11:57:54 +0100894 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100895 unique_expressions = []
896 dep_check_code = ''
897 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100898 for test_name, function_name, test_dependencies, test_args in \
899 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100900 out_data_f.write(test_name + '\n')
901
Azim Khanb31aa442018-07-03 11:57:54 +0100902 # Write dependencies
903 dep_check_code += write_dependencies(out_data_f, test_dependencies,
904 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100905
Azim Khan599cd242017-07-06 17:34:27 +0100906 # Write test function name
907 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100908 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100909 raise GeneratorInputError("Function %s not found!" %
910 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100911 func_id, func_args = func_info[test_function_name]
912 out_data_f.write(str(func_id))
913
914 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100915 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100916 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100917 "%s. See function %s signature." %
918 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100919 expression_code += write_parameters(out_data_f, test_args, func_args,
920 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100921
Azim Khan599cd242017-07-06 17:34:27 +0100922 # Write a newline as test case separator
923 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100924
Azim Khanb31aa442018-07-03 11:57:54 +0100925 dep_check_code, expression_code = gen_suite_dep_checks(
926 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100927 return dep_check_code, expression_code
928
929
Azim Khanb31aa442018-07-03 11:57:54 +0100930def add_input_info(funcs_file, data_file, template_file,
931 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100932 """
Azim Khanb31aa442018-07-03 11:57:54 +0100933 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100934
Azim Khanf0e42fb2017-08-02 14:47:13 +0100935 :param funcs_file: Functions file object
936 :param data_file: Data file object
937 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100938 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100939 :param snippets: Dictionary to contain code pieces to be
940 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100941 :return:
942 """
Azim Khanb31aa442018-07-03 11:57:54 +0100943 snippets['test_file'] = c_file
944 snippets['test_main_file'] = template_file
945 snippets['test_case_file'] = funcs_file
946 snippets['test_case_data_file'] = data_file
947
948
949def read_code_from_input_files(platform_file, helpers_file,
950 out_data_file, snippets):
951 """
952 Read code from input files and create substitutions for replacement
953 strings in the template file.
954
955 :param platform_file: Platform file object
956 :param helpers_file: Helper functions file object
957 :param out_data_file: Output intermediate data file object
958 :param snippets: Dictionary to contain code pieces to be
959 substituted in the template.
960 :return:
961 """
962 # Read helpers
963 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
964 platform_f:
965 snippets['test_common_helper_file'] = helpers_file
966 snippets['test_common_helpers'] = help_f.read()
967 snippets['test_platform_file'] = platform_file
968 snippets['platform_code'] = platform_f.read().replace(
969 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
970
971
972def write_test_source_file(template_file, c_file, snippets):
973 """
974 Write output source file with generated source code.
975
976 :param template_file: Template file name
977 :param c_file: Output source file
978 :param snippets: Generated and code snippets
979 :return:
980 """
981 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +0100982 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +0100983 # Update line number. +1 as #line directive sets next line number
984 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100985 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +0100986 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +0100987
988
989def parse_function_file(funcs_file, snippets):
990 """
991 Parse function file and generate function dispatch code.
992
993 :param funcs_file: Functions file name
994 :param snippets: Dictionary to contain code pieces to be
995 substituted in the template.
996 :return:
997 """
998 with FileWrapper(funcs_file) as funcs_f:
999 suite_dependencies, dispatch_code, func_code, func_info = \
1000 parse_functions(funcs_f)
1001 snippets['functions_code'] = func_code
1002 snippets['dispatch_code'] = dispatch_code
1003 return suite_dependencies, func_info
1004
1005
1006def generate_intermediate_data_file(data_file, out_data_file,
1007 suite_dependencies, func_info, snippets):
1008 """
1009 Generates intermediate data file from input data file and
1010 information read from functions file.
1011
1012 :param data_file: Data file name
1013 :param out_data_file: Output/Intermediate data file
1014 :param suite_dependencies: List of suite dependencies.
1015 :param func_info: Function info parsed from functions file.
1016 :param snippets: Dictionary to contain code pieces to be
1017 substituted in the template.
1018 :return:
1019 """
1020 with FileWrapper(data_file) as data_f, \
1021 open(out_data_file, 'w') as out_data_f:
1022 dep_check_code, expression_code = gen_from_test_data(
1023 data_f, out_data_f, func_info, suite_dependencies)
1024 snippets['dep_check_code'] = dep_check_code
1025 snippets['expression_code'] = expression_code
1026
1027
1028def generate_code(**input_info):
1029 """
1030 Generates C source code from test suite file, data file, common
1031 helpers file and platform file.
1032
1033 input_info expands to following parameters:
1034 funcs_file: Functions file object
1035 data_file: Data file object
1036 template_file: Template file object
1037 platform_file: Platform file object
1038 helpers_file: Helper functions file object
1039 suites_dir: Test suites dir
1040 c_file: Output C file object
1041 out_data_file: Output intermediate data file object
1042 :return:
1043 """
1044 funcs_file = input_info['funcs_file']
1045 data_file = input_info['data_file']
1046 template_file = input_info['template_file']
1047 platform_file = input_info['platform_file']
1048 helpers_file = input_info['helpers_file']
1049 suites_dir = input_info['suites_dir']
1050 c_file = input_info['c_file']
1051 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001052 for name, path in [('Functions file', funcs_file),
1053 ('Data file', data_file),
1054 ('Template file', template_file),
1055 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001056 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001057 ('Suites dir', suites_dir)]:
1058 if not os.path.exists(path):
1059 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1060
Azim Khanb31aa442018-07-03 11:57:54 +01001061 snippets = {'generator_script': os.path.basename(__file__)}
1062 read_code_from_input_files(platform_file, helpers_file,
1063 out_data_file, snippets)
1064 add_input_info(funcs_file, data_file, template_file,
1065 c_file, snippets)
1066 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1067 generate_intermediate_data_file(data_file, out_data_file,
1068 suite_dependencies, func_info, snippets)
1069 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001070
1071
Azim Khan8d686bf2018-07-04 23:29:46 +01001072def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001073 """
1074 Command line parser.
1075
1076 :return:
1077 """
Azim Khan040b6a22018-06-28 16:49:13 +01001078 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001079 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001080
1081 parser.add_argument("-f", "--functions-file",
1082 dest="funcs_file",
1083 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001084 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001085 required=True)
1086
1087 parser.add_argument("-d", "--data-file",
1088 dest="data_file",
1089 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001090 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001091 required=True)
1092
1093 parser.add_argument("-t", "--template-file",
1094 dest="template_file",
1095 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001096 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001097 required=True)
1098
1099 parser.add_argument("-s", "--suites-dir",
1100 dest="suites_dir",
1101 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001102 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001103 required=True)
1104
Azim Khane3b26af2018-06-29 02:36:57 +01001105 parser.add_argument("--helpers-file",
1106 dest="helpers_file",
1107 help="Helpers file",
1108 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001109 required=True)
1110
1111 parser.add_argument("-p", "--platform-file",
1112 dest="platform_file",
1113 help="Platform code file",
1114 metavar="PLATFORM_FILE",
1115 required=True)
1116
1117 parser.add_argument("-o", "--out-dir",
1118 dest="out_dir",
1119 help="Dir where generated code and scripts are copied",
1120 metavar="OUT_DIR",
1121 required=True)
1122
1123 args = parser.parse_args()
1124
1125 data_file_name = os.path.basename(args.data_file)
1126 data_name = os.path.splitext(data_file_name)[0]
1127
1128 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001129 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001130
1131 out_c_file_dir = os.path.dirname(out_c_file)
1132 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001133 for directory in [out_c_file_dir, out_data_file_dir]:
1134 if not os.path.exists(directory):
1135 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001136
Azim Khanb31aa442018-07-03 11:57:54 +01001137 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1138 template_file=args.template_file,
1139 platform_file=args.platform_file,
1140 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1141 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001142
1143
1144if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001145 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001146 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001147 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001148 sys.exit("%s: input error: %s" %
1149 (os.path.basename(sys.argv[0]), str(err)))