blob: 12580244289510cf1e6ba7450d6150c0afd9e7c1 [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
Azim Khanf0e42fb2017-08-02 14:47:13 +01005# SPDX-License-Identifier: Apache-2.0
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
Azim Khanb31aa442018-07-03 11:57:54 +010019# This file is part of Mbed TLS (https://tls.mbed.org)
Azim Khanf0e42fb2017-08-02 14:47:13 +010020
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010021"""
Azim Khanaee05bb2018-07-02 16:01:04 +010022This script is a key part of Mbed TLS test suites framework. For
23understanding the script it is important to understand the
24framework. This doc string contains a summary of the framework
25and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010026
Azim Khanaee05bb2018-07-02 16:01:04 +010027Mbed TLS test suites:
28=====================
29Scope:
30------
31The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010032include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010033module. However, the framework is not capable of testing SSL
34protocol, since that requires full stack execution and that is best
35tested as part of the system test.
36
37Test case definition:
38---------------------
39Tests are defined in a test_suite_<module>[.<optional sub module>].data
40file. A test definition contains:
41 test name
42 optional build macro dependencies
43 test function
44 test parameters
45
46Test dependencies are build macros that can be specified to indicate
47the build config in which the test is valid. For example if a test
48depends on a feature that is only enabled by defining a macro. Then
49that macro should be specified as a dependency of the test.
50
51Test function is the function that implements the test steps. This
52function is specified for different tests that perform same steps
53with different parameters.
54
55Test parameters are specified in string form separated by ':'.
56Parameters can be of type string, binary data specified as hex
57string and integer constants specified as integer, macro or
58as an expression. Following is an example test definition:
59
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010060 AES 128 GCM Encrypt and decrypt 8 bytes
61 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
62 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010063
64Test functions:
65---------------
66Test functions are coded in C in test_suite_<module>.function files.
67Functions file is itself not compilable and contains special
68format patterns to specify test suite dependencies, start and end
69of functions and function dependencies. Check any existing functions
70file for example.
71
72Execution:
73----------
74Tests are executed in 3 steps:
75- Generating test_suite_<module>[.<optional sub module>].c file
76 for each corresponding .data file.
77- Building each source file into executables.
78- Running each executable and printing report.
79
80Generating C test source requires more than just the test functions.
81Following extras are required:
82- Process main()
83- Reading .data file and dispatching test cases.
84- Platform specific test case execution
85- Dependency checking
86- Integer expression evaluation
87- Test function dispatch
88
89Build dependencies and integer expressions (in the test parameters)
90are specified as strings in the .data file. Their run time value is
91not known at the generation stage. Hence, they need to be translated
92into run time evaluations. This script generates the run time checks
93for dependencies and integer expressions.
94
95Similarly, function names have to be translated into function calls.
96This script also generates code for function dispatch.
97
98The extra code mentioned here is either generated by this script
99or it comes from the input files: helpers file, platform file and
100the template file.
101
102Helper file:
103------------
104Helpers file contains common helper/utility functions and data.
105
106Platform file:
107--------------
108Platform file contains platform specific setup code and test case
109dispatch code. For example, host_test.function reads test data
110file from host's file system and dispatches tests.
111In case of on-target target_test.function tests are not dispatched
112on target. Target code is kept minimum and only test functions are
113dispatched. Test case dispatch is done on the host using tools like
114Greentea.
115
116Template file:
117---------
118Template file for example main_test.function is a template C file in
119which generated code and code from input files is substituted to
120generate a compilable C file. It also contains skeleton functions for
121dependency checks, expression evaluation and function dispatch. These
122functions are populated with checks and return codes by this script.
123
124Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100125strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100126
127This script:
128============
129Core function of this script is to fill the template file with
130code that is generated or read from helpers and platform files.
131
132This script replaces following fields in the template and generates
133the test source file:
134
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100135$test_common_helpers <-- All common code from helpers.function
Azim Khanaee05bb2018-07-02 16:01:04 +0100136 is substituted here.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100137$functions_code <-- Test functions are substituted here
Azim Khanaee05bb2018-07-02 16:01:04 +0100138 from the input test_suit_xyz.function
139 file. C preprocessor checks are generated
140 for the build dependencies specified
141 in the input file. This script also
142 generates wrappers for the test
143 functions with code to expand the
144 string parameters read from the data
145 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100146$expression_code <-- This script enumerates the
Azim Khanaee05bb2018-07-02 16:01:04 +0100147 expressions in the .data file and
148 generates code to handle enumerated
149 expression Ids and return the values.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100150$dep_check_code <-- This script enumerates all
Azim Khanaee05bb2018-07-02 16:01:04 +0100151 build dependencies and generate
152 code to handle enumerated build
153 dependency Id and return status: if
154 the dependency is defined or not.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100155$dispatch_code <-- This script enumerates the functions
Azim Khanaee05bb2018-07-02 16:01:04 +0100156 specified in the input test data file
157 and generates the initializer for the
158 function table in the template
159 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100160$platform_code <-- Platform specific setup and test
Azim Khanaee05bb2018-07-02 16:01:04 +0100161 dispatch code.
162
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100163"""
164
Azim Khanf0e42fb2017-08-02 14:47:13 +0100165
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100166import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100167import os
168import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100169import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100170import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100171import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100172
173
Azim Khanb31aa442018-07-03 11:57:54 +0100174BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
175END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100176
Azim Khanb31aa442018-07-03 11:57:54 +0100177BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
178END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000179
Azim Khanb31aa442018-07-03 11:57:54 +0100180BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
181END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100182
Azim Khan8d686bf2018-07-04 23:29:46 +0100183BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100184END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100185
Azim Khan8d686bf2018-07-04 23:29:46 +0100186DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Andrzej Kurekc470b6b2019-01-31 08:20:20 -0500187C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
188CONDITION_OPERATOR_REGEX = r'[!=]=|[<>]=?'
189# forbid 0ddd which might be accidentally octal or accidentally decimal
190CONDITION_VALUE_REGEX = r'[-+]?(0x[0-9a-f]+|0|[1-9][0-9]*)'
191CONDITION_REGEX = r'({})(?:\s*({})\s*({}))?$'.format(C_IDENTIFIER_REGEX,
192 CONDITION_OPERATOR_REGEX,
193 CONDITION_VALUE_REGEX)
Azim Khanfcdf6852018-07-05 17:31:46 +0100194TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100195INT_CHECK_REGEX = r'int\s+.*'
196CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
197DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100198FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
199EXIT_LABEL_REGEX = r'^exit:'
200
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100201
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100202class GeneratorInputError(Exception):
203 """
Azim Khane3b26af2018-06-29 02:36:57 +0100204 Exception to indicate error in the input files to this script.
205 This includes missing patterns, test function names and other
206 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100207 """
208 pass
209
210
Azim Khanb31aa442018-07-03 11:57:54 +0100211class FileWrapper(io.FileIO, object):
Azim Khan4b543232017-06-30 09:35:21 +0100212 """
Azim Khane3b26af2018-06-29 02:36:57 +0100213 This class extends built-in io.FileIO class with attribute line_no,
214 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100215 """
216
217 def __init__(self, file_name):
218 """
Azim Khane3b26af2018-06-29 02:36:57 +0100219 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100220
Azim Khanf0e42fb2017-08-02 14:47:13 +0100221 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100222 """
223 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100224 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100225
Azim Khanb31aa442018-07-03 11:57:54 +0100226 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100227 """
Azim Khane3b26af2018-06-29 02:36:57 +0100228 Python 2 iterator method. This method overrides base class's
229 next method and extends the next method to count the line
230 numbers as each line is read.
231
232 It works for both Python 2 and Python 3 by checking iterator
233 method name in the base iterator object.
234
Azim Khanf0e42fb2017-08-02 14:47:13 +0100235 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100236 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200237 parent = super(FileWrapper, self)
238 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100239 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200240 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100241 line = parent.next() # Python 2
242 if line is not None:
243 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100244 # Convert byte array to string with correct encoding and
245 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100246 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100247 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100248
249 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100250 __next__ = next
251
252 def get_line_no(self):
253 """
254 Gives current line number.
255 """
256 return self._line_no
257
258 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100259
260
261def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262 """
Azim Khanb31aa442018-07-03 11:57:54 +0100263 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100264
265 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100266 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
267 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100268 """
Azim Khan4b543232017-06-30 09:35:21 +0100269 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
270
271
Azim Khanb31aa442018-07-03 11:57:54 +0100272def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100273 """
Azim Khane3b26af2018-06-29 02:36:57 +0100274 Test suite data and functions specifies compile time dependencies.
275 This function generates C preprocessor code from the input
276 dependency list. Caller uses the generated preprocessor code to
277 wrap dependent code.
278 A dependency in the input list can have a leading '!' character
279 to negate a condition. '!' is separated from the dependency using
280 function split_dep() and proper preprocessor check is generated
281 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100282
Azim Khanb31aa442018-07-03 11:57:54 +0100283 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100284 :return: if defined and endif code with macro annotations for
285 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100286 """
Azim Khanb31aa442018-07-03 11:57:54 +0100287 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
288 map(split_dep, dependencies)])
289 dep_end = ''.join(['#endif /* %s */\n' %
290 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100291
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100292 return dep_start, dep_end
293
294
Azim Khanb31aa442018-07-03 11:57:54 +0100295def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100296 """
Azim Khanb31aa442018-07-03 11:57:54 +0100297 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100298 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100299
Azim Khanb31aa442018-07-03 11:57:54 +0100300 :param dependencies: List of dependencies.
301 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100302 """
Azim Khanb31aa442018-07-03 11:57:54 +0100303 defines = '#if ' if dependencies else ''
304 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
305 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100306 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
308
Azim Khanb31aa442018-07-03 11:57:54 +0100309def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100310 """
Azim Khan040b6a22018-06-28 16:49:13 +0100311 Creates test function wrapper code. A wrapper has the code to
312 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100313
Azim Khanf0e42fb2017-08-02 14:47:13 +0100314 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100315 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100316 :param args_dispatch: List of dispatch arguments.
317 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100318 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100319 """
320 # Then create the wrapper
321 wrapper = '''
322void {name}_wrapper( void ** params )
323{{
Gilles Peskine77761412018-06-18 17:51:40 +0200324{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100325 {name}( {args} );
326}}
Gilles Peskine77761412018-06-18 17:51:40 +0200327'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100328 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100329 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100330 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100331 return wrapper
332
333
Azim Khanb31aa442018-07-03 11:57:54 +0100334def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100335 """
Azim Khane3b26af2018-06-29 02:36:57 +0100336 Test suite code template main_test.function defines a C function
337 array to contain test case functions. This function generates an
338 initializer entry for a function in that array. The entry is
339 composed of a compile time check for the test function
340 dependencies. At compile time the test function is assigned when
341 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100342
Azim Khanf0e42fb2017-08-02 14:47:13 +0100343 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100344 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100345 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100346 """
Azim Khanb31aa442018-07-03 11:57:54 +0100347 if dependencies:
348 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100349 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100350{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100351 {name}_wrapper,
352#else
353 NULL,
354#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100355'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100356 else:
357 dispatch_code = '''
358 {name}_wrapper,
359'''.format(name=name)
360
361 return dispatch_code
362
363
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000364def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100365 """
Azim Khane3b26af2018-06-29 02:36:57 +0100366 Matches pattern end_regex to the lines read from the file object.
367 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100368
Azim Khan8d686bf2018-07-04 23:29:46 +0100369 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000370 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100371 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100372 """
Azim Khan4b543232017-06-30 09:35:21 +0100373 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100374 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000375 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100376 break
377 headers += line
378 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100379 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100380 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100381
Azim Khan4b543232017-06-30 09:35:21 +0100382 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100383
384
Azim Khan8d686bf2018-07-04 23:29:46 +0100385def validate_dependency(dependency):
386 """
387 Validates a C macro and raises GeneratorInputError on invalid input.
388 :param dependency: Input macro dependency
389 :return: input dependency stripped of leading & trailing white spaces.
390 """
391 dependency = dependency.strip()
Andrzej Kurekc470b6b2019-01-31 08:20:20 -0500392 if not re.match(CONDITION_REGEX, dependency, re.I):
Azim Khan8d686bf2018-07-04 23:29:46 +0100393 raise GeneratorInputError('Invalid dependency %s' % dependency)
394 return dependency
395
396
397def parse_dependencies(inp_str):
398 """
399 Parses dependencies out of inp_str, validates them and returns a
400 list of macros.
401
402 :param inp_str: Input string with macros delimited by ':'.
403 :return: list of dependencies
404 """
405 dependencies = [dep for dep in map(validate_dependency,
406 inp_str.split(':'))]
407 return dependencies
408
409
Azim Khanb31aa442018-07-03 11:57:54 +0100410def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100411 """
Azim Khane3b26af2018-06-29 02:36:57 +0100412 Parses test suite dependencies specified at the top of a
413 .function file, that starts with pattern BEGIN_DEPENDENCIES
414 and end with END_DEPENDENCIES. Dependencies are specified
415 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100416
Azim Khan8d686bf2018-07-04 23:29:46 +0100417 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100418 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100419 """
Azim Khanb31aa442018-07-03 11:57:54 +0100420 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100421 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100422 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100423 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100424 try:
425 dependencies = parse_dependencies(match.group('dependencies'))
426 except GeneratorInputError as error:
427 raise GeneratorInputError(
428 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100429 if re.search(END_DEP_REGEX, line):
430 break
431 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100432 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100433 " not found!" % (funcs_f.name,
434 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100435
Azim Khanb31aa442018-07-03 11:57:54 +0100436 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100437
438
Azim Khanb31aa442018-07-03 11:57:54 +0100439def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100440 """
Azim Khane3b26af2018-06-29 02:36:57 +0100441 Parses function dependencies, that are in the same line as
442 comment BEGIN_CASE. Dependencies are specified after pattern
443 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100444
Azim Khan8d686bf2018-07-04 23:29:46 +0100445 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100446 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100447 """
Azim Khanb31aa442018-07-03 11:57:54 +0100448 dependencies = []
449 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100450 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100451 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100452 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100453 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100454 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100455
Azim Khan8d686bf2018-07-04 23:29:46 +0100456 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100457
Azim Khan4084ec72018-07-05 14:20:08 +0100458
Azim Khanfcdf6852018-07-05 17:31:46 +0100459def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100460 """
Azim Khane3b26af2018-06-29 02:36:57 +0100461 Parses test function signature for validation and generates
462 a dispatch wrapper function that translates input test vectors
463 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100464
Azim Khan8d686bf2018-07-04 23:29:46 +0100465 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100466 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100467 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100468 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100469 """
470 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100471 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100472 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100473 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100474 # Remove characters before arguments
475 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100476 # Process arguments, ex: <type> arg1, <type> arg2 )
477 # This script assumes that the argument list is terminated by ')'
478 # i.e. the test functions will not have a function pointer
479 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100480 for arg in line[:line.find(')')].split(','):
481 arg = arg.strip()
482 if arg == '':
483 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100484 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100485 args.append('int')
486 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100487 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100488 args.append('char*')
489 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100490 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100491 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100492 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100493 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
494 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100495 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100496""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100497
Azim Khan5fcca462018-06-29 11:05:32 +0100498 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100499 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100500 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100501 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100502 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100503 arg_idx += 1
504
Azim Khanfcdf6852018-07-05 17:31:46 +0100505 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100506
507
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100508def generate_function_code(name, code, local_vars, args_dispatch,
509 dependencies):
510 """
511 Generate function code with preprocessor checks and parameter dispatch
512 wrapper.
513
514 :param name: Function name
515 :param code: Function code
516 :param local_vars: Local variables for function wrapper
517 :param args_dispatch: Argument dispatch code
518 :param dependencies: Preprocessor dependencies list
519 :return: Final function code
520 """
521 # Add exit label if not present
522 if code.find('exit:') == -1:
523 split_code = code.rsplit('}', 1)
524 if len(split_code) == 2:
525 code = """exit:
526 ;
527}""".join(split_code)
528
529 code += gen_function_wrapper(name, local_vars, args_dispatch)
530 preprocessor_check_start, preprocessor_check_end = \
531 gen_dependencies(dependencies)
532 return preprocessor_check_start + code + preprocessor_check_end
533
534
Azim Khanb31aa442018-07-03 11:57:54 +0100535def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100536 """
Azim Khan040b6a22018-06-28 16:49:13 +0100537 Parses out a function from function file object and generates
538 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100539
Azim Khanf0e42fb2017-08-02 14:47:13 +0100540 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100541 :param dependencies: List of dependencies
542 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100543 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100544 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100545 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
546 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100547 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100548 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100549 # Check function signature. Function signature may be split
550 # across multiple lines. Here we try to find the start of
551 # arguments list, then remove '\n's and apply the regex to
552 # detect function start.
553 up_to_arg_list_start = code + line[:line.find('(') + 1]
554 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
555 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100556 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100557 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100558 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100559 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100560 for lin in funcs_f:
561 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100562 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100563 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100564 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100565 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100566 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100567 break
568 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100569 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100570 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100571 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100572
Azim Khanfcdf6852018-07-05 17:31:46 +0100573 # Prefix test function name with 'test_'
574 code = code.replace(name, 'test_' + name, 1)
575 name = 'test_' + name
576
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100577 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100578 if re.search(END_CASE_REGEX, line):
579 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100580 if not has_exit_label:
581 has_exit_label = \
582 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100583 code += line
584 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100585 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100586 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100587
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100588 code = line_directive + code
589 code = generate_function_code(name, code, local_vars, args_dispatch,
590 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100591 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100592 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100593
594
595def parse_functions(funcs_f):
596 """
Azim Khane3b26af2018-06-29 02:36:57 +0100597 Parses a test_suite_xxx.function file and returns information
598 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100599
Azim Khanf0e42fb2017-08-02 14:47:13 +0100600 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100601 :return: List of test suite dependencies, test function dispatch
602 code, function code and a dict with function identifiers
603 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100604 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000605 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100606 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100607 suite_functions = ''
608 func_info = {}
609 function_idx = 0
610 dispatch_code = ''
611 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100612 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100613 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000614 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100615 suite_helpers += parse_until_pattern(funcs_f,
616 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100617 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100618 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100619 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100620 try:
621 dependencies = parse_function_dependencies(line)
622 except GeneratorInputError as error:
623 raise GeneratorInputError(
624 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
625 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100626 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100627 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 suite_functions += func_code
629 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100630 if func_name in func_info:
631 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100632 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100633 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100634 func_info[func_name] = (function_idx, args)
635 dispatch_code += '/* Function Id: %d */\n' % function_idx
636 dispatch_code += func_dispatch
637 function_idx += 1
638
Azim Khanb31aa442018-07-03 11:57:54 +0100639 func_code = (suite_helpers +
640 suite_functions).join(gen_dependencies(suite_dependencies))
641 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100642
643
Azim Khanb31aa442018-07-03 11:57:54 +0100644def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100645 """
Azim Khanb31aa442018-07-03 11:57:54 +0100646 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100647 Since, return value is used to write back to the intermediate
648 data file, any escape characters in the input are retained in the
649 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100650
Azim Khanb31aa442018-07-03 11:57:54 +0100651 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100652 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100653 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100654 """
Azim Khanb31aa442018-07-03 11:57:54 +0100655 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100656 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100657 out = re.sub(r'(\\.)|' + split_char,
658 lambda m: m.group(1) or '\n', inp_str,
659 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100660 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100661 return out
662
663
Azim Khanb31aa442018-07-03 11:57:54 +0100664def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100665 """
Azim Khane3b26af2018-06-29 02:36:57 +0100666 Parses .data file for each test case name, test function name,
667 test dependencies and test arguments. This information is
668 correlated with the test functions file for generating an
669 intermediate data file replacing the strings for test function
670 names, dependencies and integer constant expressions with
671 identifiers. Mainly for optimising space for on-target
672 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100673
Azim Khanf0e42fb2017-08-02 14:47:13 +0100674 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100675 :return: Generator that yields test name, function name,
676 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100677 """
Azim Khanb31aa442018-07-03 11:57:54 +0100678 __state_read_name = 0
679 __state_read_args = 1
680 state = __state_read_name
681 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100682 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100683 for line in data_f:
684 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100685 # Skip comments
686 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100687 continue
688
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100689 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100690 if not line:
691 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100692 raise GeneratorInputError("[%s:%d] Newline before arguments. "
693 "Test function and arguments "
694 "missing for %s" %
695 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100696 continue
697
Azim Khanb31aa442018-07-03 11:57:54 +0100698 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100699 # Read test name
700 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100701 state = __state_read_args
702 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100703 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100704 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100705 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100706 try:
707 dependencies = parse_dependencies(
708 match.group('dependencies'))
709 except GeneratorInputError as error:
710 raise GeneratorInputError(
711 str(error) + " - %s:%d" %
712 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100713 else:
714 # Read test vectors
715 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100716 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100717 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100718 yield name, test_function, dependencies, args
719 dependencies = []
720 state = __state_read_name
721 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100722 raise GeneratorInputError("[%s:%d] Newline before arguments. "
723 "Test function and arguments missing for "
724 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100725
726
727def gen_dep_check(dep_id, dep):
728 """
Azim Khane3b26af2018-06-29 02:36:57 +0100729 Generate code for checking dependency with the associated
730 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100731
Azim Khanf0e42fb2017-08-02 14:47:13 +0100732 :param dep_id: Dependency identifier
733 :param dep: Dependency macro
734 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100735 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100736 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100737 raise GeneratorInputError("Dependency Id should be a positive "
738 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100739 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
740 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100741 raise GeneratorInputError("Dependency should not be an empty string.")
Andrzej Kurekc470b6b2019-01-31 08:20:20 -0500742
743 dependency = re.match(CONDITION_REGEX, dep, re.I)
744 if not dependency:
745 raise GeneratorInputError('Invalid dependency %s' % dep)
746
747 _defined = '' if dependency.group(2) else 'defined'
748 _cond = dependency.group(2) if dependency.group(2) else ''
749 _value = dependency.group(3) if dependency.group(3) else ''
750
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100751 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100752 case {id}:
753 {{
Andrzej Kurekc470b6b2019-01-31 08:20:20 -0500754#if {_not}{_defined}({macro}{_cond}{_value})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100755 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100756#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100757 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100758#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100759 }}
Andrzej Kurekc470b6b2019-01-31 08:20:20 -0500760 break;'''.format(_not=_not, _defined=_defined,
761 macro=dependency.group(1), id=dep_id,
762 _cond=_cond, _value=_value)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100763 return dep_check
764
765
766def gen_expression_check(exp_id, exp):
767 """
Azim Khane3b26af2018-06-29 02:36:57 +0100768 Generates code for evaluating an integer expression using
769 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100770
Azim Khanf0e42fb2017-08-02 14:47:13 +0100771 :param exp_id: Expression Identifier
772 :param exp: Expression/Macro
773 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100774 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100775 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100776 raise GeneratorInputError("Expression Id should be a positive "
777 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100778 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100779 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100780 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100781 case {exp_id}:
782 {{
783 *out_value = {expression};
784 }}
785 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100786 return exp_code
787
788
Azim Khanb31aa442018-07-03 11:57:54 +0100789def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100790 """
Azim Khane3b26af2018-06-29 02:36:57 +0100791 Write dependencies to intermediate test data file, replacing
792 the string form with identifiers. Also, generates dependency
793 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100794
Azim Khanf0e42fb2017-08-02 14:47:13 +0100795 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100796 :param test_dependencies: Dependencies
797 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100798 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100799 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100800 """
Azim Khan599cd242017-07-06 17:34:27 +0100801 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100802 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100803 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100804 for dep in test_dependencies:
805 if dep not in unique_dependencies:
806 unique_dependencies.append(dep)
807 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100808 dep_check_code += gen_dep_check(dep_id, dep)
809 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100810 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100811 out_data_f.write(':' + str(dep_id))
812 out_data_f.write('\n')
813 return dep_check_code
814
815
816def write_parameters(out_data_f, test_args, func_args, unique_expressions):
817 """
Azim Khane3b26af2018-06-29 02:36:57 +0100818 Writes test parameters to the intermediate data file, replacing
819 the string form with identifiers. Also, generates expression
820 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100821
Azim Khanf0e42fb2017-08-02 14:47:13 +0100822 :param out_data_f: Output intermediate data file
823 :param test_args: Test parameters
824 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100825 :param unique_expressions: Mutable list to track unique
826 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100827 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100828 """
829 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100830 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100831 typ = func_args[i]
832 val = test_args[i]
833
Azim Khan040b6a22018-06-28 16:49:13 +0100834 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100835 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
836 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100837 typ = 'exp'
838 if val not in unique_expressions:
839 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100840 # exp_id can be derived from len(). But for
841 # readability and consistency with case of existing
842 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100843 exp_id = unique_expressions.index(val)
844 expression_code += gen_expression_check(exp_id, val)
845 val = exp_id
846 else:
847 val = unique_expressions.index(val)
848 out_data_f.write(':' + typ + ':' + str(val))
849 out_data_f.write('\n')
850 return expression_code
851
852
Azim Khanb31aa442018-07-03 11:57:54 +0100853def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100854 """
Azim Khane3b26af2018-06-29 02:36:57 +0100855 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100856
Azim Khanb31aa442018-07-03 11:57:54 +0100857 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100858 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100859 :param dep_check_code: Dependency check code
860 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100861 :return: Dependency and expression code guarded by test suite
862 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100863 """
Azim Khanb31aa442018-07-03 11:57:54 +0100864 if suite_dependencies:
865 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100866 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100867{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100868{code}
Azim Khan599cd242017-07-06 17:34:27 +0100869#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100870'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100871 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100872{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100873{code}
Azim Khan599cd242017-07-06 17:34:27 +0100874#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100875'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100876 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100877
878
Azim Khanb31aa442018-07-03 11:57:54 +0100879def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100880 """
Azim Khane3b26af2018-06-29 02:36:57 +0100881 This function reads test case name, dependencies and test vectors
882 from the .data file. This information is correlated with the test
883 functions file for generating an intermediate data file replacing
884 the strings for test function names, dependencies and integer
885 constant expressions with identifiers. Mainly for optimising
886 space for on-target execution.
887 It also generates test case dependency check code and expression
888 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100889
Azim Khanf0e42fb2017-08-02 14:47:13 +0100890 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100891 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100892 :param func_info: Dict keyed by function and with function id
893 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100894 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100895 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100896 """
Azim Khanb31aa442018-07-03 11:57:54 +0100897 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100898 unique_expressions = []
899 dep_check_code = ''
900 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100901 for test_name, function_name, test_dependencies, test_args in \
902 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100903 out_data_f.write(test_name + '\n')
904
Azim Khanb31aa442018-07-03 11:57:54 +0100905 # Write dependencies
906 dep_check_code += write_dependencies(out_data_f, test_dependencies,
907 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100908
Azim Khan599cd242017-07-06 17:34:27 +0100909 # Write test function name
910 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100911 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100912 raise GeneratorInputError("Function %s not found!" %
913 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100914 func_id, func_args = func_info[test_function_name]
915 out_data_f.write(str(func_id))
916
917 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100918 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100919 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100920 "%s. See function %s signature." %
921 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100922 expression_code += write_parameters(out_data_f, test_args, func_args,
923 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100924
Azim Khan599cd242017-07-06 17:34:27 +0100925 # Write a newline as test case separator
926 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100927
Azim Khanb31aa442018-07-03 11:57:54 +0100928 dep_check_code, expression_code = gen_suite_dep_checks(
929 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100930 return dep_check_code, expression_code
931
932
Azim Khanb31aa442018-07-03 11:57:54 +0100933def add_input_info(funcs_file, data_file, template_file,
934 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100935 """
Azim Khanb31aa442018-07-03 11:57:54 +0100936 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100937
Azim Khanf0e42fb2017-08-02 14:47:13 +0100938 :param funcs_file: Functions file object
939 :param data_file: Data file object
940 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100941 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100942 :param snippets: Dictionary to contain code pieces to be
943 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100944 :return:
945 """
Azim Khanb31aa442018-07-03 11:57:54 +0100946 snippets['test_file'] = c_file
947 snippets['test_main_file'] = template_file
948 snippets['test_case_file'] = funcs_file
949 snippets['test_case_data_file'] = data_file
950
951
952def read_code_from_input_files(platform_file, helpers_file,
953 out_data_file, snippets):
954 """
955 Read code from input files and create substitutions for replacement
956 strings in the template file.
957
958 :param platform_file: Platform file object
959 :param helpers_file: Helper functions file object
960 :param out_data_file: Output intermediate data file object
961 :param snippets: Dictionary to contain code pieces to be
962 substituted in the template.
963 :return:
964 """
965 # Read helpers
966 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
967 platform_f:
968 snippets['test_common_helper_file'] = helpers_file
969 snippets['test_common_helpers'] = help_f.read()
970 snippets['test_platform_file'] = platform_file
971 snippets['platform_code'] = platform_f.read().replace(
972 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
973
974
975def write_test_source_file(template_file, c_file, snippets):
976 """
977 Write output source file with generated source code.
978
979 :param template_file: Template file name
980 :param c_file: Output source file
981 :param snippets: Generated and code snippets
982 :return:
983 """
984 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +0100985 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +0100986 # Update line number. +1 as #line directive sets next line number
987 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100988 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +0100989 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +0100990
991
992def parse_function_file(funcs_file, snippets):
993 """
994 Parse function file and generate function dispatch code.
995
996 :param funcs_file: Functions file name
997 :param snippets: Dictionary to contain code pieces to be
998 substituted in the template.
999 :return:
1000 """
1001 with FileWrapper(funcs_file) as funcs_f:
1002 suite_dependencies, dispatch_code, func_code, func_info = \
1003 parse_functions(funcs_f)
1004 snippets['functions_code'] = func_code
1005 snippets['dispatch_code'] = dispatch_code
1006 return suite_dependencies, func_info
1007
1008
1009def generate_intermediate_data_file(data_file, out_data_file,
1010 suite_dependencies, func_info, snippets):
1011 """
1012 Generates intermediate data file from input data file and
1013 information read from functions file.
1014
1015 :param data_file: Data file name
1016 :param out_data_file: Output/Intermediate data file
1017 :param suite_dependencies: List of suite dependencies.
1018 :param func_info: Function info parsed from functions file.
1019 :param snippets: Dictionary to contain code pieces to be
1020 substituted in the template.
1021 :return:
1022 """
1023 with FileWrapper(data_file) as data_f, \
1024 open(out_data_file, 'w') as out_data_f:
1025 dep_check_code, expression_code = gen_from_test_data(
1026 data_f, out_data_f, func_info, suite_dependencies)
1027 snippets['dep_check_code'] = dep_check_code
1028 snippets['expression_code'] = expression_code
1029
1030
1031def generate_code(**input_info):
1032 """
1033 Generates C source code from test suite file, data file, common
1034 helpers file and platform file.
1035
1036 input_info expands to following parameters:
1037 funcs_file: Functions file object
1038 data_file: Data file object
1039 template_file: Template file object
1040 platform_file: Platform file object
1041 helpers_file: Helper functions file object
1042 suites_dir: Test suites dir
1043 c_file: Output C file object
1044 out_data_file: Output intermediate data file object
1045 :return:
1046 """
1047 funcs_file = input_info['funcs_file']
1048 data_file = input_info['data_file']
1049 template_file = input_info['template_file']
1050 platform_file = input_info['platform_file']
1051 helpers_file = input_info['helpers_file']
1052 suites_dir = input_info['suites_dir']
1053 c_file = input_info['c_file']
1054 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001055 for name, path in [('Functions file', funcs_file),
1056 ('Data file', data_file),
1057 ('Template file', template_file),
1058 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001059 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001060 ('Suites dir', suites_dir)]:
1061 if not os.path.exists(path):
1062 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1063
Azim Khanb31aa442018-07-03 11:57:54 +01001064 snippets = {'generator_script': os.path.basename(__file__)}
1065 read_code_from_input_files(platform_file, helpers_file,
1066 out_data_file, snippets)
1067 add_input_info(funcs_file, data_file, template_file,
1068 c_file, snippets)
1069 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1070 generate_intermediate_data_file(data_file, out_data_file,
1071 suite_dependencies, func_info, snippets)
1072 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001073
1074
Azim Khan8d686bf2018-07-04 23:29:46 +01001075def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001076 """
1077 Command line parser.
1078
1079 :return:
1080 """
Azim Khan040b6a22018-06-28 16:49:13 +01001081 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001082 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001083
1084 parser.add_argument("-f", "--functions-file",
1085 dest="funcs_file",
1086 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001087 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001088 required=True)
1089
1090 parser.add_argument("-d", "--data-file",
1091 dest="data_file",
1092 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001093 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001094 required=True)
1095
1096 parser.add_argument("-t", "--template-file",
1097 dest="template_file",
1098 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001099 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001100 required=True)
1101
1102 parser.add_argument("-s", "--suites-dir",
1103 dest="suites_dir",
1104 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001105 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001106 required=True)
1107
Azim Khane3b26af2018-06-29 02:36:57 +01001108 parser.add_argument("--helpers-file",
1109 dest="helpers_file",
1110 help="Helpers file",
1111 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001112 required=True)
1113
1114 parser.add_argument("-p", "--platform-file",
1115 dest="platform_file",
1116 help="Platform code file",
1117 metavar="PLATFORM_FILE",
1118 required=True)
1119
1120 parser.add_argument("-o", "--out-dir",
1121 dest="out_dir",
1122 help="Dir where generated code and scripts are copied",
1123 metavar="OUT_DIR",
1124 required=True)
1125
1126 args = parser.parse_args()
1127
1128 data_file_name = os.path.basename(args.data_file)
1129 data_name = os.path.splitext(data_file_name)[0]
1130
1131 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001132 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001133
1134 out_c_file_dir = os.path.dirname(out_c_file)
1135 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001136 for directory in [out_c_file_dir, out_data_file_dir]:
1137 if not os.path.exists(directory):
1138 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001139
Azim Khanb31aa442018-07-03 11:57:54 +01001140 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1141 template_file=args.template_file,
1142 platform_file=args.platform_file,
1143 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1144 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001145
1146
1147if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001148 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001149 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001150 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001151 sys.exit("%s: input error: %s" %
1152 (os.path.basename(sys.argv[0]), str(err)))