blob: b74ed5501fc6a1789812de8030dfec67bc053ff9 [file] [log] [blame]
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +01001#!/usr/bin/env python3
Gilles Peskine3c546f72021-05-06 15:07:29 +02002
3# This script should still be compatible with Python 2 in Mbed TLS 2.16.x.
4
Azim Khanf0e42fb2017-08-02 14:47:13 +01005# Test suites code generator.
6#
Bence Szépkútia2947ac2020-08-19 16:37:36 +02007# Copyright The Mbed TLS Contributors
Bence Szépkútif744bd72020-06-05 13:02:18 +02008# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
9#
10# This file is provided under the Apache License 2.0, or the
11# GNU General Public License v2.0 or later.
12#
13# **********
14# Apache License 2.0:
Azim Khanf0e42fb2017-08-02 14:47:13 +010015#
16# Licensed under the Apache License, Version 2.0 (the "License"); you may
17# not use this file except in compliance with the License.
18# You may obtain a copy of the License at
19#
20# http://www.apache.org/licenses/LICENSE-2.0
21#
22# Unless required by applicable law or agreed to in writing, software
23# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25# See the License for the specific language governing permissions and
26# limitations under the License.
27#
Bence Szépkútif744bd72020-06-05 13:02:18 +020028# **********
29#
30# **********
31# GNU General Public License v2.0 or later:
32#
33# This program is free software; you can redistribute it and/or modify
34# it under the terms of the GNU General Public License as published by
35# the Free Software Foundation; either version 2 of the License, or
36# (at your option) any later version.
37#
38# This program is distributed in the hope that it will be useful,
39# but WITHOUT ANY WARRANTY; without even the implied warranty of
40# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41# GNU General Public License for more details.
42#
43# You should have received a copy of the GNU General Public License along
44# with this program; if not, write to the Free Software Foundation, Inc.,
45# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
46#
47# **********
Azim Khanf0e42fb2017-08-02 14:47:13 +010048
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010049"""
Azim Khanaee05bb2018-07-02 16:01:04 +010050This script is a key part of Mbed TLS test suites framework. For
51understanding the script it is important to understand the
52framework. This doc string contains a summary of the framework
53and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010054
Azim Khanaee05bb2018-07-02 16:01:04 +010055Mbed TLS test suites:
56=====================
57Scope:
58------
59The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010060include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010061module. However, the framework is not capable of testing SSL
62protocol, since that requires full stack execution and that is best
63tested as part of the system test.
64
65Test case definition:
66---------------------
67Tests are defined in a test_suite_<module>[.<optional sub module>].data
68file. A test definition contains:
69 test name
70 optional build macro dependencies
71 test function
72 test parameters
73
74Test dependencies are build macros that can be specified to indicate
75the build config in which the test is valid. For example if a test
76depends on a feature that is only enabled by defining a macro. Then
77that macro should be specified as a dependency of the test.
78
79Test function is the function that implements the test steps. This
80function is specified for different tests that perform same steps
81with different parameters.
82
83Test parameters are specified in string form separated by ':'.
84Parameters can be of type string, binary data specified as hex
85string and integer constants specified as integer, macro or
86as an expression. Following is an example test definition:
87
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010088 AES 128 GCM Encrypt and decrypt 8 bytes
89 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
90 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010091
92Test functions:
93---------------
94Test functions are coded in C in test_suite_<module>.function files.
95Functions file is itself not compilable and contains special
96format patterns to specify test suite dependencies, start and end
97of functions and function dependencies. Check any existing functions
98file for example.
99
100Execution:
101----------
102Tests are executed in 3 steps:
103- Generating test_suite_<module>[.<optional sub module>].c file
104 for each corresponding .data file.
105- Building each source file into executables.
106- Running each executable and printing report.
107
108Generating C test source requires more than just the test functions.
109Following extras are required:
110- Process main()
111- Reading .data file and dispatching test cases.
112- Platform specific test case execution
113- Dependency checking
114- Integer expression evaluation
115- Test function dispatch
116
117Build dependencies and integer expressions (in the test parameters)
118are specified as strings in the .data file. Their run time value is
119not known at the generation stage. Hence, they need to be translated
120into run time evaluations. This script generates the run time checks
121for dependencies and integer expressions.
122
123Similarly, function names have to be translated into function calls.
124This script also generates code for function dispatch.
125
126The extra code mentioned here is either generated by this script
127or it comes from the input files: helpers file, platform file and
128the template file.
129
130Helper file:
131------------
132Helpers file contains common helper/utility functions and data.
133
134Platform file:
135--------------
136Platform file contains platform specific setup code and test case
137dispatch code. For example, host_test.function reads test data
138file from host's file system and dispatches tests.
139In case of on-target target_test.function tests are not dispatched
140on target. Target code is kept minimum and only test functions are
141dispatched. Test case dispatch is done on the host using tools like
142Greentea.
143
144Template file:
145---------
146Template file for example main_test.function is a template C file in
147which generated code and code from input files is substituted to
148generate a compilable C file. It also contains skeleton functions for
149dependency checks, expression evaluation and function dispatch. These
150functions are populated with checks and return codes by this script.
151
152Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100153strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100154
155This script:
156============
157Core function of this script is to fill the template file with
158code that is generated or read from helpers and platform files.
159
160This script replaces following fields in the template and generates
161the test source file:
162
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100163$test_common_helpers <-- All common code from helpers.function
Azim Khanaee05bb2018-07-02 16:01:04 +0100164 is substituted here.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100165$functions_code <-- Test functions are substituted here
Azim Khanaee05bb2018-07-02 16:01:04 +0100166 from the input test_suit_xyz.function
167 file. C preprocessor checks are generated
168 for the build dependencies specified
169 in the input file. This script also
170 generates wrappers for the test
171 functions with code to expand the
172 string parameters read from the data
173 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100174$expression_code <-- This script enumerates the
Azim Khanaee05bb2018-07-02 16:01:04 +0100175 expressions in the .data file and
176 generates code to handle enumerated
177 expression Ids and return the values.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100178$dep_check_code <-- This script enumerates all
Azim Khanaee05bb2018-07-02 16:01:04 +0100179 build dependencies and generate
180 code to handle enumerated build
181 dependency Id and return status: if
182 the dependency is defined or not.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100183$dispatch_code <-- This script enumerates the functions
Azim Khanaee05bb2018-07-02 16:01:04 +0100184 specified in the input test data file
185 and generates the initializer for the
186 function table in the template
187 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100188$platform_code <-- Platform specific setup and test
Azim Khanaee05bb2018-07-02 16:01:04 +0100189 dispatch code.
190
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100191"""
192
Azim Khanf0e42fb2017-08-02 14:47:13 +0100193
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100194import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100195import os
196import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100197import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100198import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100199import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100200
201
Azim Khanb31aa442018-07-03 11:57:54 +0100202BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
203END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100204
Azim Khanb31aa442018-07-03 11:57:54 +0100205BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
206END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000207
Azim Khanb31aa442018-07-03 11:57:54 +0100208BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
209END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100210
Azim Khan8d686bf2018-07-04 23:29:46 +0100211BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100212END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100213
Azim Khan8d686bf2018-07-04 23:29:46 +0100214DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldor016f9252018-11-27 16:35:20 +0200215C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
216CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
217# forbid 0ddd which might be accidentally octal or accidentally decimal
218CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
219CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
220 CONDITION_OPERATOR_REGEX,
221 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100222TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100223INT_CHECK_REGEX = r'int\s+.*'
224CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
225DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100226FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
227EXIT_LABEL_REGEX = r'^exit:'
228
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100229
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100230class GeneratorInputError(Exception):
231 """
Azim Khane3b26af2018-06-29 02:36:57 +0100232 Exception to indicate error in the input files to this script.
233 This includes missing patterns, test function names and other
234 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100235 """
236 pass
237
238
Gilles Peskine5d1dfd42020-03-24 18:25:17 +0100239class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100240 """
Azim Khane3b26af2018-06-29 02:36:57 +0100241 This class extends built-in io.FileIO class with attribute line_no,
242 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100243 """
244
245 def __init__(self, file_name):
246 """
Azim Khane3b26af2018-06-29 02:36:57 +0100247 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100248
Azim Khanf0e42fb2017-08-02 14:47:13 +0100249 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100250 """
251 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100252 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100253
Azim Khanb31aa442018-07-03 11:57:54 +0100254 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100255 """
Azim Khane3b26af2018-06-29 02:36:57 +0100256 Python 2 iterator method. This method overrides base class's
257 next method and extends the next method to count the line
258 numbers as each line is read.
259
260 It works for both Python 2 and Python 3 by checking iterator
261 method name in the base iterator object.
262
Azim Khanf0e42fb2017-08-02 14:47:13 +0100263 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100264 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200265 parent = super(FileWrapper, self)
266 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100267 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200268 else:
Gilles Peskineafd19dd2019-02-25 21:39:42 +0100269 line = parent.next() # Python 2 # pylint: disable=no-member
Azim Khanb31aa442018-07-03 11:57:54 +0100270 if line is not None:
271 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100272 # Convert byte array to string with correct encoding and
273 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100274 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100275 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100276
277 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100278 __next__ = next
279
280 def get_line_no(self):
281 """
282 Gives current line number.
283 """
284 return self._line_no
285
286 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100287
288
289def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100290 """
Azim Khanb31aa442018-07-03 11:57:54 +0100291 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100292
293 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100294 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
295 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100296 """
Azim Khan4b543232017-06-30 09:35:21 +0100297 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
298
299
Azim Khanb31aa442018-07-03 11:57:54 +0100300def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100301 """
Azim Khane3b26af2018-06-29 02:36:57 +0100302 Test suite data and functions specifies compile time dependencies.
303 This function generates C preprocessor code from the input
304 dependency list. Caller uses the generated preprocessor code to
305 wrap dependent code.
306 A dependency in the input list can have a leading '!' character
307 to negate a condition. '!' is separated from the dependency using
308 function split_dep() and proper preprocessor check is generated
309 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100310
Azim Khanb31aa442018-07-03 11:57:54 +0100311 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100312 :return: if defined and endif code with macro annotations for
313 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100314 """
Azim Khanb31aa442018-07-03 11:57:54 +0100315 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
316 map(split_dep, dependencies)])
317 dep_end = ''.join(['#endif /* %s */\n' %
318 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100319
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100320 return dep_start, dep_end
321
322
Azim Khanb31aa442018-07-03 11:57:54 +0100323def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100324 """
Azim Khanb31aa442018-07-03 11:57:54 +0100325 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100326 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100327
Azim Khanb31aa442018-07-03 11:57:54 +0100328 :param dependencies: List of dependencies.
329 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100330 """
Azim Khanb31aa442018-07-03 11:57:54 +0100331 defines = '#if ' if dependencies else ''
332 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
333 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100334 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100335
336
Azim Khanb31aa442018-07-03 11:57:54 +0100337def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100338 """
Azim Khan040b6a22018-06-28 16:49:13 +0100339 Creates test function wrapper code. A wrapper has the code to
340 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100341
Azim Khanf0e42fb2017-08-02 14:47:13 +0100342 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100343 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100344 :param args_dispatch: List of dispatch arguments.
345 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100346 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100347 """
348 # Then create the wrapper
349 wrapper = '''
350void {name}_wrapper( void ** params )
351{{
Gilles Peskine77761412018-06-18 17:51:40 +0200352{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100353 {name}( {args} );
354}}
Gilles Peskine77761412018-06-18 17:51:40 +0200355'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100356 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100357 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100358 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100359 return wrapper
360
361
Azim Khanb31aa442018-07-03 11:57:54 +0100362def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100363 """
Azim Khane3b26af2018-06-29 02:36:57 +0100364 Test suite code template main_test.function defines a C function
365 array to contain test case functions. This function generates an
366 initializer entry for a function in that array. The entry is
367 composed of a compile time check for the test function
368 dependencies. At compile time the test function is assigned when
369 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100370
Azim Khanf0e42fb2017-08-02 14:47:13 +0100371 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100372 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100373 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374 """
Azim Khanb31aa442018-07-03 11:57:54 +0100375 if dependencies:
376 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100377 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100378{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100379 {name}_wrapper,
380#else
381 NULL,
382#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100383'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100384 else:
385 dispatch_code = '''
386 {name}_wrapper,
387'''.format(name=name)
388
389 return dispatch_code
390
391
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000392def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100393 """
Azim Khane3b26af2018-06-29 02:36:57 +0100394 Matches pattern end_regex to the lines read from the file object.
395 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100396
Azim Khan8d686bf2018-07-04 23:29:46 +0100397 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000398 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100399 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100400 """
Azim Khan4b543232017-06-30 09:35:21 +0100401 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100402 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000403 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100404 break
405 headers += line
406 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100407 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100408 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100409
Azim Khan4b543232017-06-30 09:35:21 +0100410 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100411
412
Azim Khan8d686bf2018-07-04 23:29:46 +0100413def validate_dependency(dependency):
414 """
415 Validates a C macro and raises GeneratorInputError on invalid input.
416 :param dependency: Input macro dependency
417 :return: input dependency stripped of leading & trailing white spaces.
418 """
419 dependency = dependency.strip()
Ron Eldor016f9252018-11-27 16:35:20 +0200420 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100421 raise GeneratorInputError('Invalid dependency %s' % dependency)
422 return dependency
423
424
425def parse_dependencies(inp_str):
426 """
427 Parses dependencies out of inp_str, validates them and returns a
428 list of macros.
429
430 :param inp_str: Input string with macros delimited by ':'.
431 :return: list of dependencies
432 """
Gilles Peskine399b82f2020-03-24 18:36:56 +0100433 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100434 return dependencies
435
436
Azim Khanb31aa442018-07-03 11:57:54 +0100437def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100438 """
Azim Khane3b26af2018-06-29 02:36:57 +0100439 Parses test suite dependencies specified at the top of a
440 .function file, that starts with pattern BEGIN_DEPENDENCIES
441 and end with END_DEPENDENCIES. Dependencies are specified
442 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100443
Azim Khan8d686bf2018-07-04 23:29:46 +0100444 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100445 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100446 """
Azim Khanb31aa442018-07-03 11:57:54 +0100447 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100448 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100449 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100450 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100451 try:
452 dependencies = parse_dependencies(match.group('dependencies'))
453 except GeneratorInputError as error:
454 raise GeneratorInputError(
455 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100456 if re.search(END_DEP_REGEX, line):
457 break
458 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100459 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100460 " not found!" % (funcs_f.name,
461 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100462
Azim Khanb31aa442018-07-03 11:57:54 +0100463 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100464
465
Azim Khanb31aa442018-07-03 11:57:54 +0100466def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100467 """
Azim Khane3b26af2018-06-29 02:36:57 +0100468 Parses function dependencies, that are in the same line as
469 comment BEGIN_CASE. Dependencies are specified after pattern
470 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100471
Azim Khan8d686bf2018-07-04 23:29:46 +0100472 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100473 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100474 """
Azim Khanb31aa442018-07-03 11:57:54 +0100475 dependencies = []
476 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100477 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100478 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100479 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100480 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100481 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100482
Azim Khan8d686bf2018-07-04 23:29:46 +0100483 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100484
Azim Khan4084ec72018-07-05 14:20:08 +0100485
Azim Khanfcdf6852018-07-05 17:31:46 +0100486def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100487 """
Azim Khane3b26af2018-06-29 02:36:57 +0100488 Parses test function signature for validation and generates
489 a dispatch wrapper function that translates input test vectors
490 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100491
Azim Khan8d686bf2018-07-04 23:29:46 +0100492 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100493 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100494 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100495 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100496 """
497 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100498 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100499 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100500 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100501 # Remove characters before arguments
502 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100503 # Process arguments, ex: <type> arg1, <type> arg2 )
504 # This script assumes that the argument list is terminated by ')'
505 # i.e. the test functions will not have a function pointer
506 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100507 for arg in line[:line.find(')')].split(','):
508 arg = arg.strip()
509 if arg == '':
510 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100511 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100512 args.append('int')
513 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100514 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100515 args.append('char*')
516 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100517 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100518 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100519 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100520 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
521 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100522 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100523""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100524
Azim Khan5fcca462018-06-29 11:05:32 +0100525 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100526 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100527 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100528 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100529 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100530 arg_idx += 1
531
Azim Khanfcdf6852018-07-05 17:31:46 +0100532 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100533
534
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100535def generate_function_code(name, code, local_vars, args_dispatch,
536 dependencies):
537 """
538 Generate function code with preprocessor checks and parameter dispatch
539 wrapper.
540
541 :param name: Function name
542 :param code: Function code
543 :param local_vars: Local variables for function wrapper
544 :param args_dispatch: Argument dispatch code
545 :param dependencies: Preprocessor dependencies list
546 :return: Final function code
547 """
548 # Add exit label if not present
549 if code.find('exit:') == -1:
550 split_code = code.rsplit('}', 1)
551 if len(split_code) == 2:
552 code = """exit:
553 ;
554}""".join(split_code)
555
556 code += gen_function_wrapper(name, local_vars, args_dispatch)
557 preprocessor_check_start, preprocessor_check_end = \
558 gen_dependencies(dependencies)
559 return preprocessor_check_start + code + preprocessor_check_end
560
561
Azim Khanb31aa442018-07-03 11:57:54 +0100562def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100563 """
Azim Khan040b6a22018-06-28 16:49:13 +0100564 Parses out a function from function file object and generates
565 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100566
Azim Khanf0e42fb2017-08-02 14:47:13 +0100567 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100568 :param dependencies: List of dependencies
569 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100570 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100571 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100572 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
573 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100574 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100575 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100576 # Check function signature. Function signature may be split
577 # across multiple lines. Here we try to find the start of
578 # arguments list, then remove '\n's and apply the regex to
579 # detect function start.
580 up_to_arg_list_start = code + line[:line.find('(') + 1]
581 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
582 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100583 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100584 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100585 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100586 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100587 for lin in funcs_f:
588 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100589 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100590 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100591 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100592 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100593 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100594 break
595 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100596 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100597 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100598 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100599
Azim Khanfcdf6852018-07-05 17:31:46 +0100600 # Prefix test function name with 'test_'
601 code = code.replace(name, 'test_' + name, 1)
602 name = 'test_' + name
603
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100604 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100605 if re.search(END_CASE_REGEX, line):
606 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100607 if not has_exit_label:
608 has_exit_label = \
609 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100610 code += line
611 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100612 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100613 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100614
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100615 code = line_directive + code
616 code = generate_function_code(name, code, local_vars, args_dispatch,
617 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100618 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100619 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100620
621
622def parse_functions(funcs_f):
623 """
Azim Khane3b26af2018-06-29 02:36:57 +0100624 Parses a test_suite_xxx.function file and returns information
625 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100626
Azim Khanf0e42fb2017-08-02 14:47:13 +0100627 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100628 :return: List of test suite dependencies, test function dispatch
629 code, function code and a dict with function identifiers
630 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100631 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000632 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100633 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100634 suite_functions = ''
635 func_info = {}
636 function_idx = 0
637 dispatch_code = ''
638 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100639 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100640 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000641 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100642 suite_helpers += parse_until_pattern(funcs_f,
643 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100644 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100645 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100646 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100647 try:
648 dependencies = parse_function_dependencies(line)
649 except GeneratorInputError as error:
650 raise GeneratorInputError(
651 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
652 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100653 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100654 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100655 suite_functions += func_code
656 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100657 if func_name in func_info:
658 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100659 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100660 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100661 func_info[func_name] = (function_idx, args)
662 dispatch_code += '/* Function Id: %d */\n' % function_idx
663 dispatch_code += func_dispatch
664 function_idx += 1
665
Azim Khanb31aa442018-07-03 11:57:54 +0100666 func_code = (suite_helpers +
667 suite_functions).join(gen_dependencies(suite_dependencies))
668 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100669
670
Azim Khanb31aa442018-07-03 11:57:54 +0100671def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100672 """
Azim Khanb31aa442018-07-03 11:57:54 +0100673 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100674 Since, return value is used to write back to the intermediate
675 data file, any escape characters in the input are retained in the
676 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100677
Azim Khanb31aa442018-07-03 11:57:54 +0100678 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100679 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100680 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100681 """
Azim Khanb31aa442018-07-03 11:57:54 +0100682 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100683 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100684 out = re.sub(r'(\\.)|' + split_char,
685 lambda m: m.group(1) or '\n', inp_str,
686 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100687 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100688 return out
689
690
Azim Khanb31aa442018-07-03 11:57:54 +0100691def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100692 """
Azim Khane3b26af2018-06-29 02:36:57 +0100693 Parses .data file for each test case name, test function name,
694 test dependencies and test arguments. This information is
695 correlated with the test functions file for generating an
696 intermediate data file replacing the strings for test function
697 names, dependencies and integer constant expressions with
698 identifiers. Mainly for optimising space for on-target
699 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100700
Azim Khanf0e42fb2017-08-02 14:47:13 +0100701 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100702 :return: Generator that yields test name, function name,
703 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100704 """
Azim Khanb31aa442018-07-03 11:57:54 +0100705 __state_read_name = 0
706 __state_read_args = 1
707 state = __state_read_name
708 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100709 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100710 for line in data_f:
711 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100712 # Skip comments
713 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100714 continue
715
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100716 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100717 if not line:
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 "
721 "missing for %s" %
722 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100723 continue
724
Azim Khanb31aa442018-07-03 11:57:54 +0100725 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100726 # Read test name
727 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100728 state = __state_read_args
729 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100730 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100731 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100732 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100733 try:
734 dependencies = parse_dependencies(
735 match.group('dependencies'))
736 except GeneratorInputError as error:
737 raise GeneratorInputError(
738 str(error) + " - %s:%d" %
739 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100740 else:
741 # Read test vectors
742 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100743 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100744 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100745 yield name, test_function, dependencies, args
746 dependencies = []
747 state = __state_read_name
748 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100749 raise GeneratorInputError("[%s:%d] Newline before arguments. "
750 "Test function and arguments missing for "
751 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100752
753
754def gen_dep_check(dep_id, dep):
755 """
Azim Khane3b26af2018-06-29 02:36:57 +0100756 Generate code for checking dependency with the associated
757 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100758
Azim Khanf0e42fb2017-08-02 14:47:13 +0100759 :param dep_id: Dependency identifier
760 :param dep: Dependency macro
761 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100762 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100763 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100764 raise GeneratorInputError("Dependency Id should be a positive "
765 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100766 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
767 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100768 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldor016f9252018-11-27 16:35:20 +0200769
770 dependency = re.match(CONDITION_REGEX, dep, re.I)
771 if not dependency:
772 raise GeneratorInputError('Invalid dependency %s' % dep)
773
774 _defined = '' if dependency.group(2) else 'defined'
775 _cond = dependency.group(2) if dependency.group(2) else ''
776 _value = dependency.group(3) if dependency.group(3) else ''
777
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100778 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100779 case {id}:
780 {{
Ron Eldor016f9252018-11-27 16:35:20 +0200781#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100782 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100783#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100784 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100785#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100786 }}
Ron Eldor016f9252018-11-27 16:35:20 +0200787 break;'''.format(_not=_not, _defined=_defined,
788 macro=dependency.group(1), id=dep_id,
789 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100790 return dep_check
791
792
793def gen_expression_check(exp_id, exp):
794 """
Azim Khane3b26af2018-06-29 02:36:57 +0100795 Generates code for evaluating an integer expression using
796 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100797
Azim Khanf0e42fb2017-08-02 14:47:13 +0100798 :param exp_id: Expression Identifier
799 :param exp: Expression/Macro
800 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100801 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100802 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100803 raise GeneratorInputError("Expression Id should be a positive "
804 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100805 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100806 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100807 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100808 case {exp_id}:
809 {{
810 *out_value = {expression};
811 }}
812 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100813 return exp_code
814
815
Azim Khanb31aa442018-07-03 11:57:54 +0100816def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100817 """
Azim Khane3b26af2018-06-29 02:36:57 +0100818 Write dependencies to intermediate test data file, replacing
819 the string form with identifiers. Also, generates dependency
820 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100821
Azim Khanf0e42fb2017-08-02 14:47:13 +0100822 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100823 :param test_dependencies: Dependencies
824 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100825 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100826 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100827 """
Azim Khan599cd242017-07-06 17:34:27 +0100828 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100829 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100830 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100831 for dep in test_dependencies:
832 if dep not in unique_dependencies:
833 unique_dependencies.append(dep)
834 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100835 dep_check_code += gen_dep_check(dep_id, dep)
836 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100837 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100838 out_data_f.write(':' + str(dep_id))
839 out_data_f.write('\n')
840 return dep_check_code
841
842
843def write_parameters(out_data_f, test_args, func_args, unique_expressions):
844 """
Azim Khane3b26af2018-06-29 02:36:57 +0100845 Writes test parameters to the intermediate data file, replacing
846 the string form with identifiers. Also, generates expression
847 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100848
Azim Khanf0e42fb2017-08-02 14:47:13 +0100849 :param out_data_f: Output intermediate data file
850 :param test_args: Test parameters
851 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100852 :param unique_expressions: Mutable list to track unique
853 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100854 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100855 """
856 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100857 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100858 typ = func_args[i]
859 val = test_args[i]
860
Azim Khan040b6a22018-06-28 16:49:13 +0100861 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100862 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
863 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100864 typ = 'exp'
865 if val not in unique_expressions:
866 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100867 # exp_id can be derived from len(). But for
868 # readability and consistency with case of existing
869 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100870 exp_id = unique_expressions.index(val)
871 expression_code += gen_expression_check(exp_id, val)
872 val = exp_id
873 else:
874 val = unique_expressions.index(val)
875 out_data_f.write(':' + typ + ':' + str(val))
876 out_data_f.write('\n')
877 return expression_code
878
879
Azim Khanb31aa442018-07-03 11:57:54 +0100880def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100881 """
Azim Khane3b26af2018-06-29 02:36:57 +0100882 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100883
Azim Khanb31aa442018-07-03 11:57:54 +0100884 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100885 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100886 :param dep_check_code: Dependency check code
887 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100888 :return: Dependency and expression code guarded by test suite
889 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100890 """
Azim Khanb31aa442018-07-03 11:57:54 +0100891 if suite_dependencies:
892 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100893 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100894{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100895{code}
Azim Khan599cd242017-07-06 17:34:27 +0100896#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100897'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100898 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100899{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100900{code}
Azim Khan599cd242017-07-06 17:34:27 +0100901#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100902'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100903 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100904
905
Azim Khanb31aa442018-07-03 11:57:54 +0100906def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100907 """
Azim Khane3b26af2018-06-29 02:36:57 +0100908 This function reads test case name, dependencies and test vectors
909 from the .data file. This information is correlated with the test
910 functions file for generating an intermediate data file replacing
911 the strings for test function names, dependencies and integer
912 constant expressions with identifiers. Mainly for optimising
913 space for on-target execution.
914 It also generates test case dependency check code and expression
915 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100916
Azim Khanf0e42fb2017-08-02 14:47:13 +0100917 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100918 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100919 :param func_info: Dict keyed by function and with function id
920 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100921 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100922 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100923 """
Azim Khanb31aa442018-07-03 11:57:54 +0100924 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100925 unique_expressions = []
926 dep_check_code = ''
927 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100928 for test_name, function_name, test_dependencies, test_args in \
929 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100930 out_data_f.write(test_name + '\n')
931
Azim Khanb31aa442018-07-03 11:57:54 +0100932 # Write dependencies
933 dep_check_code += write_dependencies(out_data_f, test_dependencies,
934 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100935
Azim Khan599cd242017-07-06 17:34:27 +0100936 # Write test function name
937 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100938 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100939 raise GeneratorInputError("Function %s not found!" %
940 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100941 func_id, func_args = func_info[test_function_name]
942 out_data_f.write(str(func_id))
943
944 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100945 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100946 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100947 "%s. See function %s signature." %
948 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100949 expression_code += write_parameters(out_data_f, test_args, func_args,
950 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100951
Azim Khan599cd242017-07-06 17:34:27 +0100952 # Write a newline as test case separator
953 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100954
Azim Khanb31aa442018-07-03 11:57:54 +0100955 dep_check_code, expression_code = gen_suite_dep_checks(
956 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100957 return dep_check_code, expression_code
958
959
Azim Khanb31aa442018-07-03 11:57:54 +0100960def add_input_info(funcs_file, data_file, template_file,
961 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100962 """
Azim Khanb31aa442018-07-03 11:57:54 +0100963 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100964
Azim Khanf0e42fb2017-08-02 14:47:13 +0100965 :param funcs_file: Functions file object
966 :param data_file: Data file object
967 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100968 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100969 :param snippets: Dictionary to contain code pieces to be
970 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100971 :return:
972 """
Azim Khanb31aa442018-07-03 11:57:54 +0100973 snippets['test_file'] = c_file
974 snippets['test_main_file'] = template_file
975 snippets['test_case_file'] = funcs_file
976 snippets['test_case_data_file'] = data_file
977
978
979def read_code_from_input_files(platform_file, helpers_file,
980 out_data_file, snippets):
981 """
982 Read code from input files and create substitutions for replacement
983 strings in the template file.
984
985 :param platform_file: Platform file object
986 :param helpers_file: Helper functions file object
987 :param out_data_file: Output intermediate data file object
988 :param snippets: Dictionary to contain code pieces to be
989 substituted in the template.
990 :return:
991 """
992 # Read helpers
993 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
994 platform_f:
995 snippets['test_common_helper_file'] = helpers_file
996 snippets['test_common_helpers'] = help_f.read()
997 snippets['test_platform_file'] = platform_file
998 snippets['platform_code'] = platform_f.read().replace(
999 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
1000
1001
1002def write_test_source_file(template_file, c_file, snippets):
1003 """
1004 Write output source file with generated source code.
1005
1006 :param template_file: Template file name
1007 :param c_file: Output source file
1008 :param snippets: Generated and code snippets
1009 :return:
1010 """
1011 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001012 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001013 # Update line number. +1 as #line directive sets next line number
1014 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +01001015 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001016 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001017
1018
1019def parse_function_file(funcs_file, snippets):
1020 """
1021 Parse function file and generate function dispatch code.
1022
1023 :param funcs_file: Functions file name
1024 :param snippets: Dictionary to contain code pieces to be
1025 substituted in the template.
1026 :return:
1027 """
1028 with FileWrapper(funcs_file) as funcs_f:
1029 suite_dependencies, dispatch_code, func_code, func_info = \
1030 parse_functions(funcs_f)
1031 snippets['functions_code'] = func_code
1032 snippets['dispatch_code'] = dispatch_code
1033 return suite_dependencies, func_info
1034
1035
1036def generate_intermediate_data_file(data_file, out_data_file,
1037 suite_dependencies, func_info, snippets):
1038 """
1039 Generates intermediate data file from input data file and
1040 information read from functions file.
1041
1042 :param data_file: Data file name
1043 :param out_data_file: Output/Intermediate data file
1044 :param suite_dependencies: List of suite dependencies.
1045 :param func_info: Function info parsed from functions file.
1046 :param snippets: Dictionary to contain code pieces to be
1047 substituted in the template.
1048 :return:
1049 """
1050 with FileWrapper(data_file) as data_f, \
1051 open(out_data_file, 'w') as out_data_f:
1052 dep_check_code, expression_code = gen_from_test_data(
1053 data_f, out_data_f, func_info, suite_dependencies)
1054 snippets['dep_check_code'] = dep_check_code
1055 snippets['expression_code'] = expression_code
1056
1057
1058def generate_code(**input_info):
1059 """
1060 Generates C source code from test suite file, data file, common
1061 helpers file and platform file.
1062
1063 input_info expands to following parameters:
1064 funcs_file: Functions file object
1065 data_file: Data file object
1066 template_file: Template file object
1067 platform_file: Platform file object
1068 helpers_file: Helper functions file object
1069 suites_dir: Test suites dir
1070 c_file: Output C file object
1071 out_data_file: Output intermediate data file object
1072 :return:
1073 """
1074 funcs_file = input_info['funcs_file']
1075 data_file = input_info['data_file']
1076 template_file = input_info['template_file']
1077 platform_file = input_info['platform_file']
1078 helpers_file = input_info['helpers_file']
1079 suites_dir = input_info['suites_dir']
1080 c_file = input_info['c_file']
1081 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001082 for name, path in [('Functions file', funcs_file),
1083 ('Data file', data_file),
1084 ('Template file', template_file),
1085 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001086 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001087 ('Suites dir', suites_dir)]:
1088 if not os.path.exists(path):
1089 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1090
Azim Khanb31aa442018-07-03 11:57:54 +01001091 snippets = {'generator_script': os.path.basename(__file__)}
1092 read_code_from_input_files(platform_file, helpers_file,
1093 out_data_file, snippets)
1094 add_input_info(funcs_file, data_file, template_file,
1095 c_file, snippets)
1096 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1097 generate_intermediate_data_file(data_file, out_data_file,
1098 suite_dependencies, func_info, snippets)
1099 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001100
1101
Azim Khan8d686bf2018-07-04 23:29:46 +01001102def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001103 """
1104 Command line parser.
1105
1106 :return:
1107 """
Azim Khan040b6a22018-06-28 16:49:13 +01001108 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001109 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001110
1111 parser.add_argument("-f", "--functions-file",
1112 dest="funcs_file",
1113 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001114 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001115 required=True)
1116
1117 parser.add_argument("-d", "--data-file",
1118 dest="data_file",
1119 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001120 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001121 required=True)
1122
1123 parser.add_argument("-t", "--template-file",
1124 dest="template_file",
1125 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001126 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001127 required=True)
1128
1129 parser.add_argument("-s", "--suites-dir",
1130 dest="suites_dir",
1131 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001132 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001133 required=True)
1134
Azim Khane3b26af2018-06-29 02:36:57 +01001135 parser.add_argument("--helpers-file",
1136 dest="helpers_file",
1137 help="Helpers file",
1138 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001139 required=True)
1140
1141 parser.add_argument("-p", "--platform-file",
1142 dest="platform_file",
1143 help="Platform code file",
1144 metavar="PLATFORM_FILE",
1145 required=True)
1146
1147 parser.add_argument("-o", "--out-dir",
1148 dest="out_dir",
1149 help="Dir where generated code and scripts are copied",
1150 metavar="OUT_DIR",
1151 required=True)
1152
1153 args = parser.parse_args()
1154
1155 data_file_name = os.path.basename(args.data_file)
1156 data_name = os.path.splitext(data_file_name)[0]
1157
1158 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001159 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001160
1161 out_c_file_dir = os.path.dirname(out_c_file)
1162 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001163 for directory in [out_c_file_dir, out_data_file_dir]:
1164 if not os.path.exists(directory):
1165 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001166
Azim Khanb31aa442018-07-03 11:57:54 +01001167 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1168 template_file=args.template_file,
1169 platform_file=args.platform_file,
1170 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1171 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001172
1173
1174if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001175 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001176 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001177 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001178 sys.exit("%s: input error: %s" %
1179 (os.path.basename(sys.argv[0]), str(err)))