blob: f452b3767a8f4cba48d3cac0592a00b35e80e165 [file] [log] [blame]
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +01001#!/usr/bin/env python3
Azim Khanf0e42fb2017-08-02 14:47:13 +01002# Test suites code generator.
3#
Bence Szépkútia2947ac2020-08-19 16:37:36 +02004# Copyright The Mbed TLS Contributors
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# **********
Azim Khanf0e42fb2017-08-02 14:47:13 +010045
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010046"""
Azim Khanaee05bb2018-07-02 16:01:04 +010047This script is a key part of Mbed TLS test suites framework. For
48understanding the script it is important to understand the
49framework. This doc string contains a summary of the framework
50and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010051
Azim Khanaee05bb2018-07-02 16:01:04 +010052Mbed TLS test suites:
53=====================
54Scope:
55------
56The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010057include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010058module. However, the framework is not capable of testing SSL
59protocol, since that requires full stack execution and that is best
60tested as part of the system test.
61
62Test case definition:
63---------------------
64Tests are defined in a test_suite_<module>[.<optional sub module>].data
65file. A test definition contains:
66 test name
67 optional build macro dependencies
68 test function
69 test parameters
70
71Test dependencies are build macros that can be specified to indicate
72the build config in which the test is valid. For example if a test
73depends on a feature that is only enabled by defining a macro. Then
74that macro should be specified as a dependency of the test.
75
76Test function is the function that implements the test steps. This
77function is specified for different tests that perform same steps
78with different parameters.
79
80Test parameters are specified in string form separated by ':'.
81Parameters can be of type string, binary data specified as hex
82string and integer constants specified as integer, macro or
83as an expression. Following is an example test definition:
84
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010085 AES 128 GCM Encrypt and decrypt 8 bytes
86 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
87 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010088
89Test functions:
90---------------
91Test functions are coded in C in test_suite_<module>.function files.
92Functions file is itself not compilable and contains special
93format patterns to specify test suite dependencies, start and end
94of functions and function dependencies. Check any existing functions
95file for example.
96
97Execution:
98----------
99Tests are executed in 3 steps:
100- Generating test_suite_<module>[.<optional sub module>].c file
101 for each corresponding .data file.
102- Building each source file into executables.
103- Running each executable and printing report.
104
105Generating C test source requires more than just the test functions.
106Following extras are required:
107- Process main()
108- Reading .data file and dispatching test cases.
109- Platform specific test case execution
110- Dependency checking
111- Integer expression evaluation
112- Test function dispatch
113
114Build dependencies and integer expressions (in the test parameters)
115are specified as strings in the .data file. Their run time value is
116not known at the generation stage. Hence, they need to be translated
117into run time evaluations. This script generates the run time checks
118for dependencies and integer expressions.
119
120Similarly, function names have to be translated into function calls.
121This script also generates code for function dispatch.
122
123The extra code mentioned here is either generated by this script
124or it comes from the input files: helpers file, platform file and
125the template file.
126
127Helper file:
128------------
129Helpers file contains common helper/utility functions and data.
130
131Platform file:
132--------------
133Platform file contains platform specific setup code and test case
134dispatch code. For example, host_test.function reads test data
135file from host's file system and dispatches tests.
136In case of on-target target_test.function tests are not dispatched
137on target. Target code is kept minimum and only test functions are
138dispatched. Test case dispatch is done on the host using tools like
139Greentea.
140
141Template file:
142---------
143Template file for example main_test.function is a template C file in
144which generated code and code from input files is substituted to
145generate a compilable C file. It also contains skeleton functions for
146dependency checks, expression evaluation and function dispatch. These
147functions are populated with checks and return codes by this script.
148
149Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100150strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100151
152This script:
153============
154Core function of this script is to fill the template file with
155code that is generated or read from helpers and platform files.
156
157This script replaces following fields in the template and generates
158the test source file:
159
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100160$test_common_helpers <-- All common code from helpers.function
Azim Khanaee05bb2018-07-02 16:01:04 +0100161 is substituted here.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100162$functions_code <-- Test functions are substituted here
Azim Khanaee05bb2018-07-02 16:01:04 +0100163 from the input test_suit_xyz.function
164 file. C preprocessor checks are generated
165 for the build dependencies specified
166 in the input file. This script also
167 generates wrappers for the test
168 functions with code to expand the
169 string parameters read from the data
170 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100171$expression_code <-- This script enumerates the
Azim Khanaee05bb2018-07-02 16:01:04 +0100172 expressions in the .data file and
173 generates code to handle enumerated
174 expression Ids and return the values.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100175$dep_check_code <-- This script enumerates all
Azim Khanaee05bb2018-07-02 16:01:04 +0100176 build dependencies and generate
177 code to handle enumerated build
178 dependency Id and return status: if
179 the dependency is defined or not.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100180$dispatch_code <-- This script enumerates the functions
Azim Khanaee05bb2018-07-02 16:01:04 +0100181 specified in the input test data file
182 and generates the initializer for the
183 function table in the template
184 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100185$platform_code <-- Platform specific setup and test
Azim Khanaee05bb2018-07-02 16:01:04 +0100186 dispatch code.
187
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100188"""
189
Azim Khanf0e42fb2017-08-02 14:47:13 +0100190
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100191import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100192import os
193import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100194import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100195import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100196import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100197
198
Azim Khanb31aa442018-07-03 11:57:54 +0100199BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
200END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100201
Azim Khanb31aa442018-07-03 11:57:54 +0100202BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
203END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000204
Azim Khanb31aa442018-07-03 11:57:54 +0100205BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
206END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100207
Azim Khan8d686bf2018-07-04 23:29:46 +0100208BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100209END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100210
Azim Khan8d686bf2018-07-04 23:29:46 +0100211DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldor016f9252018-11-27 16:35:20 +0200212C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
213CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
214# forbid 0ddd which might be accidentally octal or accidentally decimal
215CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
216CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
217 CONDITION_OPERATOR_REGEX,
218 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100219TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100220INT_CHECK_REGEX = r'int\s+.*'
221CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
222DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100223FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
224EXIT_LABEL_REGEX = r'^exit:'
225
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100226
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100227class GeneratorInputError(Exception):
228 """
Azim Khane3b26af2018-06-29 02:36:57 +0100229 Exception to indicate error in the input files to this script.
230 This includes missing patterns, test function names and other
231 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100232 """
233 pass
234
235
Gilles Peskine5d1dfd42020-03-24 18:25:17 +0100236class FileWrapper(io.FileIO):
Azim Khan4b543232017-06-30 09:35:21 +0100237 """
Azim Khane3b26af2018-06-29 02:36:57 +0100238 This class extends built-in io.FileIO class with attribute line_no,
239 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100240 """
241
242 def __init__(self, file_name):
243 """
Azim Khane3b26af2018-06-29 02:36:57 +0100244 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100245
Azim Khanf0e42fb2017-08-02 14:47:13 +0100246 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100247 """
248 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100249 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100250
Azim Khanb31aa442018-07-03 11:57:54 +0100251 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100252 """
Azim Khane3b26af2018-06-29 02:36:57 +0100253 Python 2 iterator method. This method overrides base class's
254 next method and extends the next method to count the line
255 numbers as each line is read.
256
257 It works for both Python 2 and Python 3 by checking iterator
258 method name in the base iterator object.
259
Azim Khanf0e42fb2017-08-02 14:47:13 +0100260 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100261 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200262 parent = super(FileWrapper, self)
263 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100264 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200265 else:
Gilles Peskineafd19dd2019-02-25 21:39:42 +0100266 line = parent.next() # Python 2 # pylint: disable=no-member
Azim Khanb31aa442018-07-03 11:57:54 +0100267 if line is not None:
268 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100269 # Convert byte array to string with correct encoding and
270 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100271 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100272 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100273
274 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100275 __next__ = next
276
277 def get_line_no(self):
278 """
279 Gives current line number.
280 """
281 return self._line_no
282
283 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100284
285
286def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100287 """
Azim Khanb31aa442018-07-03 11:57:54 +0100288 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100289
290 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100291 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
292 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100293 """
Azim Khan4b543232017-06-30 09:35:21 +0100294 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
295
296
Azim Khanb31aa442018-07-03 11:57:54 +0100297def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100298 """
Azim Khane3b26af2018-06-29 02:36:57 +0100299 Test suite data and functions specifies compile time dependencies.
300 This function generates C preprocessor code from the input
301 dependency list. Caller uses the generated preprocessor code to
302 wrap dependent code.
303 A dependency in the input list can have a leading '!' character
304 to negate a condition. '!' is separated from the dependency using
305 function split_dep() and proper preprocessor check is generated
306 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
Azim Khanb31aa442018-07-03 11:57:54 +0100308 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100309 :return: if defined and endif code with macro annotations for
310 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100311 """
Azim Khanb31aa442018-07-03 11:57:54 +0100312 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
313 map(split_dep, dependencies)])
314 dep_end = ''.join(['#endif /* %s */\n' %
315 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100316
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100317 return dep_start, dep_end
318
319
Azim Khanb31aa442018-07-03 11:57:54 +0100320def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100321 """
Azim Khanb31aa442018-07-03 11:57:54 +0100322 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100323 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100324
Azim Khanb31aa442018-07-03 11:57:54 +0100325 :param dependencies: List of dependencies.
326 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100327 """
Azim Khanb31aa442018-07-03 11:57:54 +0100328 defines = '#if ' if dependencies else ''
329 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
330 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100331 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100332
333
Azim Khanb31aa442018-07-03 11:57:54 +0100334def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100335 """
Azim Khan040b6a22018-06-28 16:49:13 +0100336 Creates test function wrapper code. A wrapper has the code to
337 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100338
Azim Khanf0e42fb2017-08-02 14:47:13 +0100339 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100340 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100341 :param args_dispatch: List of dispatch arguments.
342 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100343 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100344 """
345 # Then create the wrapper
346 wrapper = '''
347void {name}_wrapper( void ** params )
348{{
Gilles Peskine77761412018-06-18 17:51:40 +0200349{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100350 {name}( {args} );
351}}
Gilles Peskine77761412018-06-18 17:51:40 +0200352'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100353 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100354 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100355 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100356 return wrapper
357
358
Azim Khanb31aa442018-07-03 11:57:54 +0100359def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100360 """
Azim Khane3b26af2018-06-29 02:36:57 +0100361 Test suite code template main_test.function defines a C function
362 array to contain test case functions. This function generates an
363 initializer entry for a function in that array. The entry is
364 composed of a compile time check for the test function
365 dependencies. At compile time the test function is assigned when
366 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100367
Azim Khanf0e42fb2017-08-02 14:47:13 +0100368 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100369 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100370 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100371 """
Azim Khanb31aa442018-07-03 11:57:54 +0100372 if dependencies:
373 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100375{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100376 {name}_wrapper,
377#else
378 NULL,
379#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100380'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100381 else:
382 dispatch_code = '''
383 {name}_wrapper,
384'''.format(name=name)
385
386 return dispatch_code
387
388
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000389def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100390 """
Azim Khane3b26af2018-06-29 02:36:57 +0100391 Matches pattern end_regex to the lines read from the file object.
392 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100393
Azim Khan8d686bf2018-07-04 23:29:46 +0100394 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000395 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100396 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100397 """
Azim Khan4b543232017-06-30 09:35:21 +0100398 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100399 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000400 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100401 break
402 headers += line
403 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100404 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100405 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100406
Azim Khan4b543232017-06-30 09:35:21 +0100407 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100408
409
Azim Khan8d686bf2018-07-04 23:29:46 +0100410def validate_dependency(dependency):
411 """
412 Validates a C macro and raises GeneratorInputError on invalid input.
413 :param dependency: Input macro dependency
414 :return: input dependency stripped of leading & trailing white spaces.
415 """
416 dependency = dependency.strip()
Ron Eldor016f9252018-11-27 16:35:20 +0200417 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100418 raise GeneratorInputError('Invalid dependency %s' % dependency)
419 return dependency
420
421
422def parse_dependencies(inp_str):
423 """
424 Parses dependencies out of inp_str, validates them and returns a
425 list of macros.
426
427 :param inp_str: Input string with macros delimited by ':'.
428 :return: list of dependencies
429 """
Gilles Peskine399b82f2020-03-24 18:36:56 +0100430 dependencies = list(map(validate_dependency, inp_str.split(':')))
Azim Khan8d686bf2018-07-04 23:29:46 +0100431 return dependencies
432
433
Azim Khanb31aa442018-07-03 11:57:54 +0100434def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100435 """
Azim Khane3b26af2018-06-29 02:36:57 +0100436 Parses test suite dependencies specified at the top of a
437 .function file, that starts with pattern BEGIN_DEPENDENCIES
438 and end with END_DEPENDENCIES. Dependencies are specified
439 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100440
Azim Khan8d686bf2018-07-04 23:29:46 +0100441 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100442 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100443 """
Azim Khanb31aa442018-07-03 11:57:54 +0100444 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100445 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100446 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100447 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100448 try:
449 dependencies = parse_dependencies(match.group('dependencies'))
450 except GeneratorInputError as error:
451 raise GeneratorInputError(
452 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100453 if re.search(END_DEP_REGEX, line):
454 break
455 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100456 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100457 " not found!" % (funcs_f.name,
458 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100459
Azim Khanb31aa442018-07-03 11:57:54 +0100460 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100461
462
Azim Khanb31aa442018-07-03 11:57:54 +0100463def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100464 """
Azim Khane3b26af2018-06-29 02:36:57 +0100465 Parses function dependencies, that are in the same line as
466 comment BEGIN_CASE. Dependencies are specified after pattern
467 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100468
Azim Khan8d686bf2018-07-04 23:29:46 +0100469 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100470 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100471 """
Azim Khanb31aa442018-07-03 11:57:54 +0100472 dependencies = []
473 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100474 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100475 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100476 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100477 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100478 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100479
Azim Khan8d686bf2018-07-04 23:29:46 +0100480 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100481
Azim Khan4084ec72018-07-05 14:20:08 +0100482
Azim Khanfcdf6852018-07-05 17:31:46 +0100483def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100484 """
Azim Khane3b26af2018-06-29 02:36:57 +0100485 Parses test function signature for validation and generates
486 a dispatch wrapper function that translates input test vectors
487 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100488
Azim Khan8d686bf2018-07-04 23:29:46 +0100489 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100490 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100491 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100492 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100493 """
494 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100495 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100496 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100497 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100498 # Remove characters before arguments
499 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100500 # Process arguments, ex: <type> arg1, <type> arg2 )
501 # This script assumes that the argument list is terminated by ')'
502 # i.e. the test functions will not have a function pointer
503 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100504 for arg in line[:line.find(')')].split(','):
505 arg = arg.strip()
506 if arg == '':
507 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100508 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100509 args.append('int')
510 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100511 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100512 args.append('char*')
513 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100514 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100515 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100516 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100517 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
518 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100519 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100520""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100521
Azim Khan5fcca462018-06-29 11:05:32 +0100522 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100523 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100524 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100525 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100526 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100527 arg_idx += 1
528
Azim Khanfcdf6852018-07-05 17:31:46 +0100529 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100530
531
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100532def generate_function_code(name, code, local_vars, args_dispatch,
533 dependencies):
534 """
535 Generate function code with preprocessor checks and parameter dispatch
536 wrapper.
537
538 :param name: Function name
539 :param code: Function code
540 :param local_vars: Local variables for function wrapper
541 :param args_dispatch: Argument dispatch code
542 :param dependencies: Preprocessor dependencies list
543 :return: Final function code
544 """
545 # Add exit label if not present
546 if code.find('exit:') == -1:
547 split_code = code.rsplit('}', 1)
548 if len(split_code) == 2:
549 code = """exit:
550 ;
551}""".join(split_code)
552
553 code += gen_function_wrapper(name, local_vars, args_dispatch)
554 preprocessor_check_start, preprocessor_check_end = \
555 gen_dependencies(dependencies)
556 return preprocessor_check_start + code + preprocessor_check_end
557
558
Azim Khanb31aa442018-07-03 11:57:54 +0100559def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100560 """
Azim Khan040b6a22018-06-28 16:49:13 +0100561 Parses out a function from function file object and generates
562 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100563
Azim Khanf0e42fb2017-08-02 14:47:13 +0100564 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100565 :param dependencies: List of dependencies
566 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100567 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100568 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100569 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
570 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100571 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100572 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100573 # Check function signature. Function signature may be split
574 # across multiple lines. Here we try to find the start of
575 # arguments list, then remove '\n's and apply the regex to
576 # detect function start.
577 up_to_arg_list_start = code + line[:line.find('(') + 1]
578 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
579 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100580 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100581 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100582 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100583 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100584 for lin in funcs_f:
585 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100586 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100587 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100588 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100589 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100590 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100591 break
592 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100593 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100594 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100595 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100596
Azim Khanfcdf6852018-07-05 17:31:46 +0100597 # Prefix test function name with 'test_'
598 code = code.replace(name, 'test_' + name, 1)
599 name = 'test_' + name
600
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100601 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100602 if re.search(END_CASE_REGEX, line):
603 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100604 if not has_exit_label:
605 has_exit_label = \
606 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100607 code += line
608 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100609 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100610 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100611
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100612 code = line_directive + code
613 code = generate_function_code(name, code, local_vars, args_dispatch,
614 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100615 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100616 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100617
618
619def parse_functions(funcs_f):
620 """
Azim Khane3b26af2018-06-29 02:36:57 +0100621 Parses a test_suite_xxx.function file and returns information
622 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100623
Azim Khanf0e42fb2017-08-02 14:47:13 +0100624 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100625 :return: List of test suite dependencies, test function dispatch
626 code, function code and a dict with function identifiers
627 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000629 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100630 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100631 suite_functions = ''
632 func_info = {}
633 function_idx = 0
634 dispatch_code = ''
635 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100636 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100637 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000638 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100639 suite_helpers += parse_until_pattern(funcs_f,
640 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100641 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100642 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100643 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100644 try:
645 dependencies = parse_function_dependencies(line)
646 except GeneratorInputError as error:
647 raise GeneratorInputError(
648 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
649 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100650 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100651 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100652 suite_functions += func_code
653 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100654 if func_name in func_info:
655 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100656 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100657 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100658 func_info[func_name] = (function_idx, args)
659 dispatch_code += '/* Function Id: %d */\n' % function_idx
660 dispatch_code += func_dispatch
661 function_idx += 1
662
Azim Khanb31aa442018-07-03 11:57:54 +0100663 func_code = (suite_helpers +
664 suite_functions).join(gen_dependencies(suite_dependencies))
665 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100666
667
Azim Khanb31aa442018-07-03 11:57:54 +0100668def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100669 """
Azim Khanb31aa442018-07-03 11:57:54 +0100670 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100671 Since, return value is used to write back to the intermediate
672 data file, any escape characters in the input are retained in the
673 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100674
Azim Khanb31aa442018-07-03 11:57:54 +0100675 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100676 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100677 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100678 """
Azim Khanb31aa442018-07-03 11:57:54 +0100679 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100680 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100681 out = re.sub(r'(\\.)|' + split_char,
682 lambda m: m.group(1) or '\n', inp_str,
683 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100684 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100685 return out
686
687
Azim Khanb31aa442018-07-03 11:57:54 +0100688def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100689 """
Azim Khane3b26af2018-06-29 02:36:57 +0100690 Parses .data file for each test case name, test function name,
691 test dependencies and test arguments. This information is
692 correlated with the test functions file for generating an
693 intermediate data file replacing the strings for test function
694 names, dependencies and integer constant expressions with
695 identifiers. Mainly for optimising space for on-target
696 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100697
Azim Khanf0e42fb2017-08-02 14:47:13 +0100698 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100699 :return: Generator that yields test name, function name,
700 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100701 """
Azim Khanb31aa442018-07-03 11:57:54 +0100702 __state_read_name = 0
703 __state_read_args = 1
704 state = __state_read_name
705 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100706 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100707 for line in data_f:
708 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100709 # Skip comments
710 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100711 continue
712
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100713 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100714 if not line:
715 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100716 raise GeneratorInputError("[%s:%d] Newline before arguments. "
717 "Test function and arguments "
718 "missing for %s" %
719 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100720 continue
721
Azim Khanb31aa442018-07-03 11:57:54 +0100722 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100723 # Read test name
724 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100725 state = __state_read_args
726 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100727 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100728 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100729 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100730 try:
731 dependencies = parse_dependencies(
732 match.group('dependencies'))
733 except GeneratorInputError as error:
734 raise GeneratorInputError(
735 str(error) + " - %s:%d" %
736 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100737 else:
738 # Read test vectors
739 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100740 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100741 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100742 yield name, test_function, dependencies, args
743 dependencies = []
744 state = __state_read_name
745 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100746 raise GeneratorInputError("[%s:%d] Newline before arguments. "
747 "Test function and arguments missing for "
748 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100749
750
751def gen_dep_check(dep_id, dep):
752 """
Azim Khane3b26af2018-06-29 02:36:57 +0100753 Generate code for checking dependency with the associated
754 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100755
Azim Khanf0e42fb2017-08-02 14:47:13 +0100756 :param dep_id: Dependency identifier
757 :param dep: Dependency macro
758 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100759 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100760 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100761 raise GeneratorInputError("Dependency Id should be a positive "
762 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100763 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
764 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100765 raise GeneratorInputError("Dependency should not be an empty string.")
Ron Eldor016f9252018-11-27 16:35:20 +0200766
767 dependency = re.match(CONDITION_REGEX, dep, re.I)
768 if not dependency:
769 raise GeneratorInputError('Invalid dependency %s' % dep)
770
771 _defined = '' if dependency.group(2) else 'defined'
772 _cond = dependency.group(2) if dependency.group(2) else ''
773 _value = dependency.group(3) if dependency.group(3) else ''
774
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100775 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100776 case {id}:
777 {{
Ron Eldor016f9252018-11-27 16:35:20 +0200778#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100779 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100780#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100781 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100782#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100783 }}
Ron Eldor016f9252018-11-27 16:35:20 +0200784 break;'''.format(_not=_not, _defined=_defined,
785 macro=dependency.group(1), id=dep_id,
786 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100787 return dep_check
788
789
790def gen_expression_check(exp_id, exp):
791 """
Azim Khane3b26af2018-06-29 02:36:57 +0100792 Generates code for evaluating an integer expression using
793 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100794
Azim Khanf0e42fb2017-08-02 14:47:13 +0100795 :param exp_id: Expression Identifier
796 :param exp: Expression/Macro
797 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100798 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100799 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100800 raise GeneratorInputError("Expression Id should be a positive "
801 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100802 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100803 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100804 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100805 case {exp_id}:
806 {{
807 *out_value = {expression};
808 }}
809 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100810 return exp_code
811
812
Azim Khanb31aa442018-07-03 11:57:54 +0100813def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100814 """
Azim Khane3b26af2018-06-29 02:36:57 +0100815 Write dependencies to intermediate test data file, replacing
816 the string form with identifiers. Also, generates dependency
817 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100818
Azim Khanf0e42fb2017-08-02 14:47:13 +0100819 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100820 :param test_dependencies: Dependencies
821 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100822 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100823 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100824 """
Azim Khan599cd242017-07-06 17:34:27 +0100825 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100826 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100827 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100828 for dep in test_dependencies:
829 if dep not in unique_dependencies:
830 unique_dependencies.append(dep)
831 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100832 dep_check_code += gen_dep_check(dep_id, dep)
833 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100834 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100835 out_data_f.write(':' + str(dep_id))
836 out_data_f.write('\n')
837 return dep_check_code
838
839
840def write_parameters(out_data_f, test_args, func_args, unique_expressions):
841 """
Azim Khane3b26af2018-06-29 02:36:57 +0100842 Writes test parameters to the intermediate data file, replacing
843 the string form with identifiers. Also, generates expression
844 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100845
Azim Khanf0e42fb2017-08-02 14:47:13 +0100846 :param out_data_f: Output intermediate data file
847 :param test_args: Test parameters
848 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100849 :param unique_expressions: Mutable list to track unique
850 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100851 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100852 """
853 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100854 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100855 typ = func_args[i]
856 val = test_args[i]
857
Azim Khan040b6a22018-06-28 16:49:13 +0100858 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100859 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
860 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100861 typ = 'exp'
862 if val not in unique_expressions:
863 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100864 # exp_id can be derived from len(). But for
865 # readability and consistency with case of existing
866 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100867 exp_id = unique_expressions.index(val)
868 expression_code += gen_expression_check(exp_id, val)
869 val = exp_id
870 else:
871 val = unique_expressions.index(val)
872 out_data_f.write(':' + typ + ':' + str(val))
873 out_data_f.write('\n')
874 return expression_code
875
876
Azim Khanb31aa442018-07-03 11:57:54 +0100877def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100878 """
Azim Khane3b26af2018-06-29 02:36:57 +0100879 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100880
Azim Khanb31aa442018-07-03 11:57:54 +0100881 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100882 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100883 :param dep_check_code: Dependency check code
884 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100885 :return: Dependency and expression code guarded by test suite
886 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100887 """
Azim Khanb31aa442018-07-03 11:57:54 +0100888 if suite_dependencies:
889 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100890 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100891{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100892{code}
Azim Khan599cd242017-07-06 17:34:27 +0100893#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100894'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100895 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100896{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100897{code}
Azim Khan599cd242017-07-06 17:34:27 +0100898#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100899'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100900 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100901
902
Azim Khanb31aa442018-07-03 11:57:54 +0100903def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100904 """
Azim Khane3b26af2018-06-29 02:36:57 +0100905 This function reads test case name, dependencies and test vectors
906 from the .data file. This information is correlated with the test
907 functions file for generating an intermediate data file replacing
908 the strings for test function names, dependencies and integer
909 constant expressions with identifiers. Mainly for optimising
910 space for on-target execution.
911 It also generates test case dependency check code and expression
912 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100913
Azim Khanf0e42fb2017-08-02 14:47:13 +0100914 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100915 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100916 :param func_info: Dict keyed by function and with function id
917 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100918 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100919 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100920 """
Azim Khanb31aa442018-07-03 11:57:54 +0100921 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100922 unique_expressions = []
923 dep_check_code = ''
924 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100925 for test_name, function_name, test_dependencies, test_args in \
926 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100927 out_data_f.write(test_name + '\n')
928
Azim Khanb31aa442018-07-03 11:57:54 +0100929 # Write dependencies
930 dep_check_code += write_dependencies(out_data_f, test_dependencies,
931 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100932
Azim Khan599cd242017-07-06 17:34:27 +0100933 # Write test function name
934 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100935 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100936 raise GeneratorInputError("Function %s not found!" %
937 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100938 func_id, func_args = func_info[test_function_name]
939 out_data_f.write(str(func_id))
940
941 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100942 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100943 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100944 "%s. See function %s signature." %
945 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100946 expression_code += write_parameters(out_data_f, test_args, func_args,
947 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100948
Azim Khan599cd242017-07-06 17:34:27 +0100949 # Write a newline as test case separator
950 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100951
Azim Khanb31aa442018-07-03 11:57:54 +0100952 dep_check_code, expression_code = gen_suite_dep_checks(
953 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100954 return dep_check_code, expression_code
955
956
Azim Khanb31aa442018-07-03 11:57:54 +0100957def add_input_info(funcs_file, data_file, template_file,
958 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100959 """
Azim Khanb31aa442018-07-03 11:57:54 +0100960 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100961
Azim Khanf0e42fb2017-08-02 14:47:13 +0100962 :param funcs_file: Functions file object
963 :param data_file: Data file object
964 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100965 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100966 :param snippets: Dictionary to contain code pieces to be
967 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100968 :return:
969 """
Azim Khanb31aa442018-07-03 11:57:54 +0100970 snippets['test_file'] = c_file
971 snippets['test_main_file'] = template_file
972 snippets['test_case_file'] = funcs_file
973 snippets['test_case_data_file'] = data_file
974
975
976def read_code_from_input_files(platform_file, helpers_file,
977 out_data_file, snippets):
978 """
979 Read code from input files and create substitutions for replacement
980 strings in the template file.
981
982 :param platform_file: Platform file object
983 :param helpers_file: Helper functions file object
984 :param out_data_file: Output intermediate data file object
985 :param snippets: Dictionary to contain code pieces to be
986 substituted in the template.
987 :return:
988 """
989 # Read helpers
990 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
991 platform_f:
992 snippets['test_common_helper_file'] = helpers_file
993 snippets['test_common_helpers'] = help_f.read()
994 snippets['test_platform_file'] = platform_file
995 snippets['platform_code'] = platform_f.read().replace(
996 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
997
998
999def write_test_source_file(template_file, c_file, snippets):
1000 """
1001 Write output source file with generated source code.
1002
1003 :param template_file: Template file name
1004 :param c_file: Output source file
1005 :param snippets: Generated and code snippets
1006 :return:
1007 """
1008 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +01001009 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +01001010 # Update line number. +1 as #line directive sets next line number
1011 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +01001012 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +01001013 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +01001014
1015
1016def parse_function_file(funcs_file, snippets):
1017 """
1018 Parse function file and generate function dispatch code.
1019
1020 :param funcs_file: Functions file name
1021 :param snippets: Dictionary to contain code pieces to be
1022 substituted in the template.
1023 :return:
1024 """
1025 with FileWrapper(funcs_file) as funcs_f:
1026 suite_dependencies, dispatch_code, func_code, func_info = \
1027 parse_functions(funcs_f)
1028 snippets['functions_code'] = func_code
1029 snippets['dispatch_code'] = dispatch_code
1030 return suite_dependencies, func_info
1031
1032
1033def generate_intermediate_data_file(data_file, out_data_file,
1034 suite_dependencies, func_info, snippets):
1035 """
1036 Generates intermediate data file from input data file and
1037 information read from functions file.
1038
1039 :param data_file: Data file name
1040 :param out_data_file: Output/Intermediate data file
1041 :param suite_dependencies: List of suite dependencies.
1042 :param func_info: Function info parsed from functions file.
1043 :param snippets: Dictionary to contain code pieces to be
1044 substituted in the template.
1045 :return:
1046 """
1047 with FileWrapper(data_file) as data_f, \
1048 open(out_data_file, 'w') as out_data_f:
1049 dep_check_code, expression_code = gen_from_test_data(
1050 data_f, out_data_f, func_info, suite_dependencies)
1051 snippets['dep_check_code'] = dep_check_code
1052 snippets['expression_code'] = expression_code
1053
1054
1055def generate_code(**input_info):
1056 """
1057 Generates C source code from test suite file, data file, common
1058 helpers file and platform file.
1059
1060 input_info expands to following parameters:
1061 funcs_file: Functions file object
1062 data_file: Data file object
1063 template_file: Template file object
1064 platform_file: Platform file object
1065 helpers_file: Helper functions file object
1066 suites_dir: Test suites dir
1067 c_file: Output C file object
1068 out_data_file: Output intermediate data file object
1069 :return:
1070 """
1071 funcs_file = input_info['funcs_file']
1072 data_file = input_info['data_file']
1073 template_file = input_info['template_file']
1074 platform_file = input_info['platform_file']
1075 helpers_file = input_info['helpers_file']
1076 suites_dir = input_info['suites_dir']
1077 c_file = input_info['c_file']
1078 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001079 for name, path in [('Functions file', funcs_file),
1080 ('Data file', data_file),
1081 ('Template file', template_file),
1082 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001083 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001084 ('Suites dir', suites_dir)]:
1085 if not os.path.exists(path):
1086 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1087
Azim Khanb31aa442018-07-03 11:57:54 +01001088 snippets = {'generator_script': os.path.basename(__file__)}
1089 read_code_from_input_files(platform_file, helpers_file,
1090 out_data_file, snippets)
1091 add_input_info(funcs_file, data_file, template_file,
1092 c_file, snippets)
1093 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1094 generate_intermediate_data_file(data_file, out_data_file,
1095 suite_dependencies, func_info, snippets)
1096 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001097
1098
Azim Khan8d686bf2018-07-04 23:29:46 +01001099def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001100 """
1101 Command line parser.
1102
1103 :return:
1104 """
Azim Khan040b6a22018-06-28 16:49:13 +01001105 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001106 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001107
1108 parser.add_argument("-f", "--functions-file",
1109 dest="funcs_file",
1110 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001111 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001112 required=True)
1113
1114 parser.add_argument("-d", "--data-file",
1115 dest="data_file",
1116 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001117 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001118 required=True)
1119
1120 parser.add_argument("-t", "--template-file",
1121 dest="template_file",
1122 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001123 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001124 required=True)
1125
1126 parser.add_argument("-s", "--suites-dir",
1127 dest="suites_dir",
1128 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001129 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001130 required=True)
1131
Azim Khane3b26af2018-06-29 02:36:57 +01001132 parser.add_argument("--helpers-file",
1133 dest="helpers_file",
1134 help="Helpers file",
1135 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001136 required=True)
1137
1138 parser.add_argument("-p", "--platform-file",
1139 dest="platform_file",
1140 help="Platform code file",
1141 metavar="PLATFORM_FILE",
1142 required=True)
1143
1144 parser.add_argument("-o", "--out-dir",
1145 dest="out_dir",
1146 help="Dir where generated code and scripts are copied",
1147 metavar="OUT_DIR",
1148 required=True)
1149
1150 args = parser.parse_args()
1151
1152 data_file_name = os.path.basename(args.data_file)
1153 data_name = os.path.splitext(data_file_name)[0]
1154
1155 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001156 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001157
1158 out_c_file_dir = os.path.dirname(out_c_file)
1159 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001160 for directory in [out_c_file_dir, out_data_file_dir]:
1161 if not os.path.exists(directory):
1162 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001163
Azim Khanb31aa442018-07-03 11:57:54 +01001164 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1165 template_file=args.template_file,
1166 platform_file=args.platform_file,
1167 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1168 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001169
1170
1171if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001172 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001173 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001174 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001175 sys.exit("%s: input error: %s" %
1176 (os.path.basename(sys.argv[0]), str(err)))