blob: e9ce70c45cfee42d1e66a788e089024b18f4655b [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#
Azim Khan8d686bf2018-07-04 23:29:46 +01004# Copyright (C) 2018, Arm Limited, All Rights Reserved
Bence Szépkútif744bd72020-06-05 13:02:18 +02005# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
6#
7# This file is provided under the Apache License 2.0, or the
8# GNU General Public License v2.0 or later.
9#
10# **********
11# Apache License 2.0:
Azim Khanf0e42fb2017-08-02 14:47:13 +010012#
13# Licensed under the Apache License, Version 2.0 (the "License"); you may
14# not use this file except in compliance with the License.
15# You may obtain a copy of the License at
16#
17# http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22# See the License for the specific language governing permissions and
23# limitations under the License.
24#
Bence Szépkútif744bd72020-06-05 13:02:18 +020025# **********
26#
27# **********
28# GNU General Public License v2.0 or later:
29#
30# This program is free software; you can redistribute it and/or modify
31# it under the terms of the GNU General Public License as published by
32# the Free Software Foundation; either version 2 of the License, or
33# (at your option) any later version.
34#
35# This program is distributed in the hope that it will be useful,
36# but WITHOUT ANY WARRANTY; without even the implied warranty of
37# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38# GNU General Public License for more details.
39#
40# You should have received a copy of the GNU General Public License along
41# with this program; if not, write to the Free Software Foundation, Inc.,
42# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
43#
44# **********
45#
Azim Khanb31aa442018-07-03 11:57:54 +010046# This file is part of Mbed TLS (https://tls.mbed.org)
Azim Khanf0e42fb2017-08-02 14:47:13 +010047
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010048"""
Azim Khanaee05bb2018-07-02 16:01:04 +010049This script is a key part of Mbed TLS test suites framework. For
50understanding the script it is important to understand the
51framework. This doc string contains a summary of the framework
52and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010053
Azim Khanaee05bb2018-07-02 16:01:04 +010054Mbed TLS test suites:
55=====================
56Scope:
57------
58The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010059include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010060module. However, the framework is not capable of testing SSL
61protocol, since that requires full stack execution and that is best
62tested as part of the system test.
63
64Test case definition:
65---------------------
66Tests are defined in a test_suite_<module>[.<optional sub module>].data
67file. A test definition contains:
68 test name
69 optional build macro dependencies
70 test function
71 test parameters
72
73Test dependencies are build macros that can be specified to indicate
74the build config in which the test is valid. For example if a test
75depends on a feature that is only enabled by defining a macro. Then
76that macro should be specified as a dependency of the test.
77
78Test function is the function that implements the test steps. This
79function is specified for different tests that perform same steps
80with different parameters.
81
82Test parameters are specified in string form separated by ':'.
83Parameters can be of type string, binary data specified as hex
84string and integer constants specified as integer, macro or
85as an expression. Following is an example test definition:
86
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010087 AES 128 GCM Encrypt and decrypt 8 bytes
88 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
89 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010090
91Test functions:
92---------------
93Test functions are coded in C in test_suite_<module>.function files.
94Functions file is itself not compilable and contains special
95format patterns to specify test suite dependencies, start and end
96of functions and function dependencies. Check any existing functions
97file for example.
98
99Execution:
100----------
101Tests are executed in 3 steps:
102- Generating test_suite_<module>[.<optional sub module>].c file
103 for each corresponding .data file.
104- Building each source file into executables.
105- Running each executable and printing report.
106
107Generating C test source requires more than just the test functions.
108Following extras are required:
109- Process main()
110- Reading .data file and dispatching test cases.
111- Platform specific test case execution
112- Dependency checking
113- Integer expression evaluation
114- Test function dispatch
115
116Build dependencies and integer expressions (in the test parameters)
117are specified as strings in the .data file. Their run time value is
118not known at the generation stage. Hence, they need to be translated
119into run time evaluations. This script generates the run time checks
120for dependencies and integer expressions.
121
122Similarly, function names have to be translated into function calls.
123This script also generates code for function dispatch.
124
125The extra code mentioned here is either generated by this script
126or it comes from the input files: helpers file, platform file and
127the template file.
128
129Helper file:
130------------
131Helpers file contains common helper/utility functions and data.
132
133Platform file:
134--------------
135Platform file contains platform specific setup code and test case
136dispatch code. For example, host_test.function reads test data
137file from host's file system and dispatches tests.
138In case of on-target target_test.function tests are not dispatched
139on target. Target code is kept minimum and only test functions are
140dispatched. Test case dispatch is done on the host using tools like
141Greentea.
142
143Template file:
144---------
145Template file for example main_test.function is a template C file in
146which generated code and code from input files is substituted to
147generate a compilable C file. It also contains skeleton functions for
148dependency checks, expression evaluation and function dispatch. These
149functions are populated with checks and return codes by this script.
150
151Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100152strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100153
154This script:
155============
156Core function of this script is to fill the template file with
157code that is generated or read from helpers and platform files.
158
159This script replaces following fields in the template and generates
160the test source file:
161
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100162$test_common_helpers <-- All common code from helpers.function
Azim Khanaee05bb2018-07-02 16:01:04 +0100163 is substituted here.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100164$functions_code <-- Test functions are substituted here
Azim Khanaee05bb2018-07-02 16:01:04 +0100165 from the input test_suit_xyz.function
166 file. C preprocessor checks are generated
167 for the build dependencies specified
168 in the input file. This script also
169 generates wrappers for the test
170 functions with code to expand the
171 string parameters read from the data
172 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100173$expression_code <-- This script enumerates the
Azim Khanaee05bb2018-07-02 16:01:04 +0100174 expressions in the .data file and
175 generates code to handle enumerated
176 expression Ids and return the values.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100177$dep_check_code <-- This script enumerates all
Azim Khanaee05bb2018-07-02 16:01:04 +0100178 build dependencies and generate
179 code to handle enumerated build
180 dependency Id and return status: if
181 the dependency is defined or not.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100182$dispatch_code <-- This script enumerates the functions
Azim Khanaee05bb2018-07-02 16:01:04 +0100183 specified in the input test data file
184 and generates the initializer for the
185 function table in the template
186 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100187$platform_code <-- Platform specific setup and test
Azim Khanaee05bb2018-07-02 16:01:04 +0100188 dispatch code.
189
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100190"""
191
Azim Khanf0e42fb2017-08-02 14:47:13 +0100192
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100193import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100194import os
195import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100196import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100197import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100198import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100199
200
Azim Khanb31aa442018-07-03 11:57:54 +0100201BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
202END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100203
Azim Khanb31aa442018-07-03 11:57:54 +0100204BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
205END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000206
Azim Khanb31aa442018-07-03 11:57:54 +0100207BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
208END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100209
Azim Khan8d686bf2018-07-04 23:29:46 +0100210BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100211END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100212
Azim Khan8d686bf2018-07-04 23:29:46 +0100213DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldor016f9252018-11-27 16:35:20 +0200214C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
215CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
216# forbid 0ddd which might be accidentally octal or accidentally decimal
217CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
218CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
219 CONDITION_OPERATOR_REGEX,
220 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100221TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100222INT_CHECK_REGEX = r'int\s+.*'
223CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
224DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100225FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
226EXIT_LABEL_REGEX = r'^exit:'
227
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100228
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100229class GeneratorInputError(Exception):
230 """
Azim Khane3b26af2018-06-29 02:36:57 +0100231 Exception to indicate error in the input files to this script.
232 This includes missing patterns, test function names and other
233 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100234 """
235 pass
236
237
Gilles Peskine5d1dfd42020-03-24 18:25:17 +0100238class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100239 """
Azim Khane3b26af2018-06-29 02:36:57 +0100240 This class extends built-in io.FileIO class with attribute line_no,
241 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100242 """
243
244 def __init__(self, file_name):
245 """
Azim Khane3b26af2018-06-29 02:36:57 +0100246 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100247
Azim Khanf0e42fb2017-08-02 14:47:13 +0100248 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100249 """
250 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100251 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100252
Azim Khanb31aa442018-07-03 11:57:54 +0100253 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100254 """
Azim Khane3b26af2018-06-29 02:36:57 +0100255 Python 2 iterator method. This method overrides base class's
256 next method and extends the next method to count the line
257 numbers as each line is read.
258
259 It works for both Python 2 and Python 3 by checking iterator
260 method name in the base iterator object.
261
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100263 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200264 parent = super(FileWrapper, self)
265 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100266 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200267 else:
Gilles Peskineafd19dd2019-02-25 21:39:42 +0100268 line = parent.next() # Python 2 # pylint: disable=no-member
Azim Khanb31aa442018-07-03 11:57:54 +0100269 if line is not None:
270 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100271 # Convert byte array to string with correct encoding and
272 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100273 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100274 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100275
276 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100277 __next__ = next
278
279 def get_line_no(self):
280 """
281 Gives current line number.
282 """
283 return self._line_no
284
285 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100286
287
288def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100289 """
Azim Khanb31aa442018-07-03 11:57:54 +0100290 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100291
292 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100293 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
294 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100295 """
Azim Khan4b543232017-06-30 09:35:21 +0100296 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
297
298
Azim Khanb31aa442018-07-03 11:57:54 +0100299def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100300 """
Azim Khane3b26af2018-06-29 02:36:57 +0100301 Test suite data and functions specifies compile time dependencies.
302 This function generates C preprocessor code from the input
303 dependency list. Caller uses the generated preprocessor code to
304 wrap dependent code.
305 A dependency in the input list can have a leading '!' character
306 to negate a condition. '!' is separated from the dependency using
307 function split_dep() and proper preprocessor check is generated
308 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100309
Azim Khanb31aa442018-07-03 11:57:54 +0100310 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100311 :return: if defined and endif code with macro annotations for
312 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100313 """
Azim Khanb31aa442018-07-03 11:57:54 +0100314 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
315 map(split_dep, dependencies)])
316 dep_end = ''.join(['#endif /* %s */\n' %
317 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100318
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100319 return dep_start, dep_end
320
321
Azim Khanb31aa442018-07-03 11:57:54 +0100322def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100323 """
Azim Khanb31aa442018-07-03 11:57:54 +0100324 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100325 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100326
Azim Khanb31aa442018-07-03 11:57:54 +0100327 :param dependencies: List of dependencies.
328 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100329 """
Azim Khanb31aa442018-07-03 11:57:54 +0100330 defines = '#if ' if dependencies else ''
331 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
332 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100333 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100334
335
Azim Khanb31aa442018-07-03 11:57:54 +0100336def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100337 """
Azim Khan040b6a22018-06-28 16:49:13 +0100338 Creates test function wrapper code. A wrapper has the code to
339 unpack parameters from parameters[] array.
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 local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100343 :param args_dispatch: List of dispatch arguments.
344 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100345 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100346 """
347 # Then create the wrapper
348 wrapper = '''
349void {name}_wrapper( void ** params )
350{{
Gilles Peskine77761412018-06-18 17:51:40 +0200351{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100352 {name}( {args} );
353}}
Gilles Peskine77761412018-06-18 17:51:40 +0200354'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100355 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100356 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100357 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100358 return wrapper
359
360
Azim Khanb31aa442018-07-03 11:57:54 +0100361def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100362 """
Azim Khane3b26af2018-06-29 02:36:57 +0100363 Test suite code template main_test.function defines a C function
364 array to contain test case functions. This function generates an
365 initializer entry for a function in that array. The entry is
366 composed of a compile time check for the test function
367 dependencies. At compile time the test function is assigned when
368 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100369
Azim Khanf0e42fb2017-08-02 14:47:13 +0100370 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100371 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100372 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100373 """
Azim Khanb31aa442018-07-03 11:57:54 +0100374 if dependencies:
375 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100376 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100377{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100378 {name}_wrapper,
379#else
380 NULL,
381#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100382'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100383 else:
384 dispatch_code = '''
385 {name}_wrapper,
386'''.format(name=name)
387
388 return dispatch_code
389
390
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000391def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100392 """
Azim Khane3b26af2018-06-29 02:36:57 +0100393 Matches pattern end_regex to the lines read from the file object.
394 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100395
Azim Khan8d686bf2018-07-04 23:29:46 +0100396 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000397 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100398 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100399 """
Azim Khan4b543232017-06-30 09:35:21 +0100400 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100401 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000402 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100403 break
404 headers += line
405 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100406 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100407 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100408
Azim Khan4b543232017-06-30 09:35:21 +0100409 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100410
411
Azim Khan8d686bf2018-07-04 23:29:46 +0100412def validate_dependency(dependency):
413 """
414 Validates a C macro and raises GeneratorInputError on invalid input.
415 :param dependency: Input macro dependency
416 :return: input dependency stripped of leading & trailing white spaces.
417 """
418 dependency = dependency.strip()
Ron Eldor016f9252018-11-27 16:35:20 +0200419 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100420 raise GeneratorInputError('Invalid dependency %s' % dependency)
421 return dependency
422
423
424def parse_dependencies(inp_str):
425 """
426 Parses dependencies out of inp_str, validates them and returns a
427 list of macros.
428
429 :param inp_str: Input string with macros delimited by ':'.
430 :return: list of dependencies
431 """
Gilles Peskine399b82f2020-03-24 18:36:56 +0100432 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100433 return dependencies
434
435
Azim Khanb31aa442018-07-03 11:57:54 +0100436def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100437 """
Azim Khane3b26af2018-06-29 02:36:57 +0100438 Parses test suite dependencies specified at the top of a
439 .function file, that starts with pattern BEGIN_DEPENDENCIES
440 and end with END_DEPENDENCIES. Dependencies are specified
441 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100442
Azim Khan8d686bf2018-07-04 23:29:46 +0100443 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100444 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100445 """
Azim Khanb31aa442018-07-03 11:57:54 +0100446 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100447 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100448 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100449 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100450 try:
451 dependencies = parse_dependencies(match.group('dependencies'))
452 except GeneratorInputError as error:
453 raise GeneratorInputError(
454 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100455 if re.search(END_DEP_REGEX, line):
456 break
457 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100458 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100459 " not found!" % (funcs_f.name,
460 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100461
Azim Khanb31aa442018-07-03 11:57:54 +0100462 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100463
464
Azim Khanb31aa442018-07-03 11:57:54 +0100465def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100466 """
Azim Khane3b26af2018-06-29 02:36:57 +0100467 Parses function dependencies, that are in the same line as
468 comment BEGIN_CASE. Dependencies are specified after pattern
469 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100470
Azim Khan8d686bf2018-07-04 23:29:46 +0100471 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100472 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100473 """
Azim Khanb31aa442018-07-03 11:57:54 +0100474 dependencies = []
475 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100476 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100477 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100478 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100479 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100480 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100481
Azim Khan8d686bf2018-07-04 23:29:46 +0100482 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100483
Azim Khan4084ec72018-07-05 14:20:08 +0100484
Azim Khanfcdf6852018-07-05 17:31:46 +0100485def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100486 """
Azim Khane3b26af2018-06-29 02:36:57 +0100487 Parses test function signature for validation and generates
488 a dispatch wrapper function that translates input test vectors
489 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100490
Azim Khan8d686bf2018-07-04 23:29:46 +0100491 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100492 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100493 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100494 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100495 """
496 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100497 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100498 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100499 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100500 # Remove characters before arguments
501 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100502 # Process arguments, ex: <type> arg1, <type> arg2 )
503 # This script assumes that the argument list is terminated by ')'
504 # i.e. the test functions will not have a function pointer
505 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100506 for arg in line[:line.find(')')].split(','):
507 arg = arg.strip()
508 if arg == '':
509 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100510 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100511 args.append('int')
512 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100513 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100514 args.append('char*')
515 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100516 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100517 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100518 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100519 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
520 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100521 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100522""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100523
Azim Khan5fcca462018-06-29 11:05:32 +0100524 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100525 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100526 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100527 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100528 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100529 arg_idx += 1
530
Azim Khanfcdf6852018-07-05 17:31:46 +0100531 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100532
533
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100534def generate_function_code(name, code, local_vars, args_dispatch,
535 dependencies):
536 """
537 Generate function code with preprocessor checks and parameter dispatch
538 wrapper.
539
540 :param name: Function name
541 :param code: Function code
542 :param local_vars: Local variables for function wrapper
543 :param args_dispatch: Argument dispatch code
544 :param dependencies: Preprocessor dependencies list
545 :return: Final function code
546 """
547 # Add exit label if not present
548 if code.find('exit:') == -1:
549 split_code = code.rsplit('}', 1)
550 if len(split_code) == 2:
551 code = """exit:
552 ;
553}""".join(split_code)
554
555 code += gen_function_wrapper(name, local_vars, args_dispatch)
556 preprocessor_check_start, preprocessor_check_end = \
557 gen_dependencies(dependencies)
558 return preprocessor_check_start + code + preprocessor_check_end
559
560
Azim Khanb31aa442018-07-03 11:57:54 +0100561def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100562 """
Azim Khan040b6a22018-06-28 16:49:13 +0100563 Parses out a function from function file object and generates
564 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100565
Azim Khanf0e42fb2017-08-02 14:47:13 +0100566 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100567 :param dependencies: List of dependencies
568 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100569 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100570 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100571 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
572 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100573 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100574 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100575 # Check function signature. Function signature may be split
576 # across multiple lines. Here we try to find the start of
577 # arguments list, then remove '\n's and apply the regex to
578 # detect function start.
579 up_to_arg_list_start = code + line[:line.find('(') + 1]
580 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
581 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100582 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100583 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100584 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100585 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100586 for lin in funcs_f:
587 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100588 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100589 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100590 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100591 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100592 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100593 break
594 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100595 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100596 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100597 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100598
Azim Khanfcdf6852018-07-05 17:31:46 +0100599 # Prefix test function name with 'test_'
600 code = code.replace(name, 'test_' + name, 1)
601 name = 'test_' + name
602
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100603 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100604 if re.search(END_CASE_REGEX, line):
605 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100606 if not has_exit_label:
607 has_exit_label = \
608 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100609 code += line
610 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100611 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100612 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100613
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100614 code = line_directive + code
615 code = generate_function_code(name, code, local_vars, args_dispatch,
616 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100617 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100618 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100619
620
621def parse_functions(funcs_f):
622 """
Azim Khane3b26af2018-06-29 02:36:57 +0100623 Parses a test_suite_xxx.function file and returns information
624 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100625
Azim Khanf0e42fb2017-08-02 14:47:13 +0100626 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100627 :return: List of test suite dependencies, test function dispatch
628 code, function code and a dict with function identifiers
629 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100630 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000631 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100632 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100633 suite_functions = ''
634 func_info = {}
635 function_idx = 0
636 dispatch_code = ''
637 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100638 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100639 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000640 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100641 suite_helpers += parse_until_pattern(funcs_f,
642 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100643 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100644 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100645 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100646 try:
647 dependencies = parse_function_dependencies(line)
648 except GeneratorInputError as error:
649 raise GeneratorInputError(
650 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
651 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100652 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100653 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100654 suite_functions += func_code
655 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100656 if func_name in func_info:
657 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100658 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100659 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100660 func_info[func_name] = (function_idx, args)
661 dispatch_code += '/* Function Id: %d */\n' % function_idx
662 dispatch_code += func_dispatch
663 function_idx += 1
664
Azim Khanb31aa442018-07-03 11:57:54 +0100665 func_code = (suite_helpers +
666 suite_functions).join(gen_dependencies(suite_dependencies))
667 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100668
669
Azim Khanb31aa442018-07-03 11:57:54 +0100670def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100671 """
Azim Khanb31aa442018-07-03 11:57:54 +0100672 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100673 Since, return value is used to write back to the intermediate
674 data file, any escape characters in the input are retained in the
675 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100676
Azim Khanb31aa442018-07-03 11:57:54 +0100677 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100678 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100679 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100680 """
Azim Khanb31aa442018-07-03 11:57:54 +0100681 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100682 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100683 out = re.sub(r'(\\.)|' + split_char,
684 lambda m: m.group(1) or '\n', inp_str,
685 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100686 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100687 return out
688
689
Azim Khanb31aa442018-07-03 11:57:54 +0100690def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100691 """
Azim Khane3b26af2018-06-29 02:36:57 +0100692 Parses .data file for each test case name, test function name,
693 test dependencies and test arguments. This information is
694 correlated with the test functions file for generating an
695 intermediate data file replacing the strings for test function
696 names, dependencies and integer constant expressions with
697 identifiers. Mainly for optimising space for on-target
698 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100699
Azim Khanf0e42fb2017-08-02 14:47:13 +0100700 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100701 :return: Generator that yields test name, function name,
702 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100703 """
Azim Khanb31aa442018-07-03 11:57:54 +0100704 __state_read_name = 0
705 __state_read_args = 1
706 state = __state_read_name
707 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100708 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100709 for line in data_f:
710 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100711 # Skip comments
712 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100713 continue
714
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100715 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100716 if not line:
717 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100718 raise GeneratorInputError("[%s:%d] Newline before arguments. "
719 "Test function and arguments "
720 "missing for %s" %
721 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100722 continue
723
Azim Khanb31aa442018-07-03 11:57:54 +0100724 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100725 # Read test name
726 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100727 state = __state_read_args
728 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100729 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100730 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100731 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100732 try:
733 dependencies = parse_dependencies(
734 match.group('dependencies'))
735 except GeneratorInputError as error:
736 raise GeneratorInputError(
737 str(error) + " - %s:%d" %
738 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100739 else:
740 # Read test vectors
741 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100742 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100743 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100744 yield name, test_function, dependencies, args
745 dependencies = []
746 state = __state_read_name
747 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100748 raise GeneratorInputError("[%s:%d] Newline before arguments. "
749 "Test function and arguments missing for "
750 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100751
752
753def gen_dep_check(dep_id, dep):
754 """
Azim Khane3b26af2018-06-29 02:36:57 +0100755 Generate code for checking dependency with the associated
756 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100757
Azim Khanf0e42fb2017-08-02 14:47:13 +0100758 :param dep_id: Dependency identifier
759 :param dep: Dependency macro
760 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100761 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100762 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100763 raise GeneratorInputError("Dependency Id should be a positive "
764 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100765 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
766 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100767 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldor016f9252018-11-27 16:35:20 +0200768
769 dependency = re.match(CONDITION_REGEX, dep, re.I)
770 if not dependency:
771 raise GeneratorInputError('Invalid dependency %s' % dep)
772
773 _defined = '' if dependency.group(2) else 'defined'
774 _cond = dependency.group(2) if dependency.group(2) else ''
775 _value = dependency.group(3) if dependency.group(3) else ''
776
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100777 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100778 case {id}:
779 {{
Ron Eldor016f9252018-11-27 16:35:20 +0200780#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100781 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100782#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100783 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100784#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100785 }}
Ron Eldor016f9252018-11-27 16:35:20 +0200786 break;'''.format(_not=_not, _defined=_defined,
787 macro=dependency.group(1), id=dep_id,
788 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100789 return dep_check
790
791
792def gen_expression_check(exp_id, exp):
793 """
Azim Khane3b26af2018-06-29 02:36:57 +0100794 Generates code for evaluating an integer expression using
795 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100796
Azim Khanf0e42fb2017-08-02 14:47:13 +0100797 :param exp_id: Expression Identifier
798 :param exp: Expression/Macro
799 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100800 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100801 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100802 raise GeneratorInputError("Expression Id should be a positive "
803 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100804 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100805 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100806 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100807 case {exp_id}:
808 {{
809 *out_value = {expression};
810 }}
811 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100812 return exp_code
813
814
Azim Khanb31aa442018-07-03 11:57:54 +0100815def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100816 """
Azim Khane3b26af2018-06-29 02:36:57 +0100817 Write dependencies to intermediate test data file, replacing
818 the string form with identifiers. Also, generates dependency
819 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100820
Azim Khanf0e42fb2017-08-02 14:47:13 +0100821 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100822 :param test_dependencies: Dependencies
823 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100824 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100825 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100826 """
Azim Khan599cd242017-07-06 17:34:27 +0100827 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100828 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100829 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100830 for dep in test_dependencies:
831 if dep not in unique_dependencies:
832 unique_dependencies.append(dep)
833 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100834 dep_check_code += gen_dep_check(dep_id, dep)
835 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100836 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100837 out_data_f.write(':' + str(dep_id))
838 out_data_f.write('\n')
839 return dep_check_code
840
841
842def write_parameters(out_data_f, test_args, func_args, unique_expressions):
843 """
Azim Khane3b26af2018-06-29 02:36:57 +0100844 Writes test parameters to the intermediate data file, replacing
845 the string form with identifiers. Also, generates expression
846 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100847
Azim Khanf0e42fb2017-08-02 14:47:13 +0100848 :param out_data_f: Output intermediate data file
849 :param test_args: Test parameters
850 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100851 :param unique_expressions: Mutable list to track unique
852 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100853 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100854 """
855 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100856 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100857 typ = func_args[i]
858 val = test_args[i]
859
Azim Khan040b6a22018-06-28 16:49:13 +0100860 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100861 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
862 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100863 typ = 'exp'
864 if val not in unique_expressions:
865 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100866 # exp_id can be derived from len(). But for
867 # readability and consistency with case of existing
868 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100869 exp_id = unique_expressions.index(val)
870 expression_code += gen_expression_check(exp_id, val)
871 val = exp_id
872 else:
873 val = unique_expressions.index(val)
874 out_data_f.write(':' + typ + ':' + str(val))
875 out_data_f.write('\n')
876 return expression_code
877
878
Azim Khanb31aa442018-07-03 11:57:54 +0100879def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100880 """
Azim Khane3b26af2018-06-29 02:36:57 +0100881 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100882
Azim Khanb31aa442018-07-03 11:57:54 +0100883 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100884 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100885 :param dep_check_code: Dependency check code
886 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100887 :return: Dependency and expression code guarded by test suite
888 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100889 """
Azim Khanb31aa442018-07-03 11:57:54 +0100890 if suite_dependencies:
891 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100892 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100893{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100894{code}
Azim Khan599cd242017-07-06 17:34:27 +0100895#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100896'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100897 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100898{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100899{code}
Azim Khan599cd242017-07-06 17:34:27 +0100900#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100901'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100902 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100903
904
Azim Khanb31aa442018-07-03 11:57:54 +0100905def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100906 """
Azim Khane3b26af2018-06-29 02:36:57 +0100907 This function reads test case name, dependencies and test vectors
908 from the .data file. This information is correlated with the test
909 functions file for generating an intermediate data file replacing
910 the strings for test function names, dependencies and integer
911 constant expressions with identifiers. Mainly for optimising
912 space for on-target execution.
913 It also generates test case dependency check code and expression
914 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100915
Azim Khanf0e42fb2017-08-02 14:47:13 +0100916 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100917 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100918 :param func_info: Dict keyed by function and with function id
919 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100920 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100921 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100922 """
Azim Khanb31aa442018-07-03 11:57:54 +0100923 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100924 unique_expressions = []
925 dep_check_code = ''
926 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100927 for test_name, function_name, test_dependencies, test_args in \
928 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100929 out_data_f.write(test_name + '\n')
930
Azim Khanb31aa442018-07-03 11:57:54 +0100931 # Write dependencies
932 dep_check_code += write_dependencies(out_data_f, test_dependencies,
933 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100934
Azim Khan599cd242017-07-06 17:34:27 +0100935 # Write test function name
936 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100937 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100938 raise GeneratorInputError("Function %s not found!" %
939 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100940 func_id, func_args = func_info[test_function_name]
941 out_data_f.write(str(func_id))
942
943 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100944 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100945 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100946 "%s. See function %s signature." %
947 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100948 expression_code += write_parameters(out_data_f, test_args, func_args,
949 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100950
Azim Khan599cd242017-07-06 17:34:27 +0100951 # Write a newline as test case separator
952 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100953
Azim Khanb31aa442018-07-03 11:57:54 +0100954 dep_check_code, expression_code = gen_suite_dep_checks(
955 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100956 return dep_check_code, expression_code
957
958
Azim Khanb31aa442018-07-03 11:57:54 +0100959def add_input_info(funcs_file, data_file, template_file,
960 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100961 """
Azim Khanb31aa442018-07-03 11:57:54 +0100962 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100963
Azim Khanf0e42fb2017-08-02 14:47:13 +0100964 :param funcs_file: Functions file object
965 :param data_file: Data file object
966 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100967 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100968 :param snippets: Dictionary to contain code pieces to be
969 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100970 :return:
971 """
Azim Khanb31aa442018-07-03 11:57:54 +0100972 snippets['test_file'] = c_file
973 snippets['test_main_file'] = template_file
974 snippets['test_case_file'] = funcs_file
975 snippets['test_case_data_file'] = data_file
976
977
978def read_code_from_input_files(platform_file, helpers_file,
979 out_data_file, snippets):
980 """
981 Read code from input files and create substitutions for replacement
982 strings in the template file.
983
984 :param platform_file: Platform file object
985 :param helpers_file: Helper functions file object
986 :param out_data_file: Output intermediate data file object
987 :param snippets: Dictionary to contain code pieces to be
988 substituted in the template.
989 :return:
990 """
991 # Read helpers
992 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
993 platform_f:
994 snippets['test_common_helper_file'] = helpers_file
995 snippets['test_common_helpers'] = help_f.read()
996 snippets['test_platform_file'] = platform_file
997 snippets['platform_code'] = platform_f.read().replace(
998 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
999
1000
1001def write_test_source_file(template_file, c_file, snippets):
1002 """
1003 Write output source file with generated source code.
1004
1005 :param template_file: Template file name
1006 :param c_file: Output source file
1007 :param snippets: Generated and code snippets
1008 :return:
1009 """
1010 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001011 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001012 # Update line number. +1 as #line directive sets next line number
1013 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +01001014 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001015 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001016
1017
1018def parse_function_file(funcs_file, snippets):
1019 """
1020 Parse function file and generate function dispatch code.
1021
1022 :param funcs_file: Functions file name
1023 :param snippets: Dictionary to contain code pieces to be
1024 substituted in the template.
1025 :return:
1026 """
1027 with FileWrapper(funcs_file) as funcs_f:
1028 suite_dependencies, dispatch_code, func_code, func_info = \
1029 parse_functions(funcs_f)
1030 snippets['functions_code'] = func_code
1031 snippets['dispatch_code'] = dispatch_code
1032 return suite_dependencies, func_info
1033
1034
1035def generate_intermediate_data_file(data_file, out_data_file,
1036 suite_dependencies, func_info, snippets):
1037 """
1038 Generates intermediate data file from input data file and
1039 information read from functions file.
1040
1041 :param data_file: Data file name
1042 :param out_data_file: Output/Intermediate data file
1043 :param suite_dependencies: List of suite dependencies.
1044 :param func_info: Function info parsed from functions file.
1045 :param snippets: Dictionary to contain code pieces to be
1046 substituted in the template.
1047 :return:
1048 """
1049 with FileWrapper(data_file) as data_f, \
1050 open(out_data_file, 'w') as out_data_f:
1051 dep_check_code, expression_code = gen_from_test_data(
1052 data_f, out_data_f, func_info, suite_dependencies)
1053 snippets['dep_check_code'] = dep_check_code
1054 snippets['expression_code'] = expression_code
1055
1056
1057def generate_code(**input_info):
1058 """
1059 Generates C source code from test suite file, data file, common
1060 helpers file and platform file.
1061
1062 input_info expands to following parameters:
1063 funcs_file: Functions file object
1064 data_file: Data file object
1065 template_file: Template file object
1066 platform_file: Platform file object
1067 helpers_file: Helper functions file object
1068 suites_dir: Test suites dir
1069 c_file: Output C file object
1070 out_data_file: Output intermediate data file object
1071 :return:
1072 """
1073 funcs_file = input_info['funcs_file']
1074 data_file = input_info['data_file']
1075 template_file = input_info['template_file']
1076 platform_file = input_info['platform_file']
1077 helpers_file = input_info['helpers_file']
1078 suites_dir = input_info['suites_dir']
1079 c_file = input_info['c_file']
1080 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001081 for name, path in [('Functions file', funcs_file),
1082 ('Data file', data_file),
1083 ('Template file', template_file),
1084 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001085 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001086 ('Suites dir', suites_dir)]:
1087 if not os.path.exists(path):
1088 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1089
Azim Khanb31aa442018-07-03 11:57:54 +01001090 snippets = {'generator_script': os.path.basename(__file__)}
1091 read_code_from_input_files(platform_file, helpers_file,
1092 out_data_file, snippets)
1093 add_input_info(funcs_file, data_file, template_file,
1094 c_file, snippets)
1095 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1096 generate_intermediate_data_file(data_file, out_data_file,
1097 suite_dependencies, func_info, snippets)
1098 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001099
1100
Azim Khan8d686bf2018-07-04 23:29:46 +01001101def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001102 """
1103 Command line parser.
1104
1105 :return:
1106 """
Azim Khan040b6a22018-06-28 16:49:13 +01001107 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001108 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001109
1110 parser.add_argument("-f", "--functions-file",
1111 dest="funcs_file",
1112 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001113 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001114 required=True)
1115
1116 parser.add_argument("-d", "--data-file",
1117 dest="data_file",
1118 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001119 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001120 required=True)
1121
1122 parser.add_argument("-t", "--template-file",
1123 dest="template_file",
1124 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001125 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001126 required=True)
1127
1128 parser.add_argument("-s", "--suites-dir",
1129 dest="suites_dir",
1130 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001131 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001132 required=True)
1133
Azim Khane3b26af2018-06-29 02:36:57 +01001134 parser.add_argument("--helpers-file",
1135 dest="helpers_file",
1136 help="Helpers file",
1137 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001138 required=True)
1139
1140 parser.add_argument("-p", "--platform-file",
1141 dest="platform_file",
1142 help="Platform code file",
1143 metavar="PLATFORM_FILE",
1144 required=True)
1145
1146 parser.add_argument("-o", "--out-dir",
1147 dest="out_dir",
1148 help="Dir where generated code and scripts are copied",
1149 metavar="OUT_DIR",
1150 required=True)
1151
1152 args = parser.parse_args()
1153
1154 data_file_name = os.path.basename(args.data_file)
1155 data_name = os.path.splitext(data_file_name)[0]
1156
1157 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001158 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001159
1160 out_c_file_dir = os.path.dirname(out_c_file)
1161 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001162 for directory in [out_c_file_dir, out_data_file_dir]:
1163 if not os.path.exists(directory):
1164 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001165
Azim Khanb31aa442018-07-03 11:57:54 +01001166 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1167 template_file=args.template_file,
1168 platform_file=args.platform_file,
1169 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1170 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001171
1172
1173if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001174 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001175 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001176 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001177 sys.exit("%s: input error: %s" %
1178 (os.path.basename(sys.argv[0]), str(err)))