blob: ce6f88c3cf7020c75770c99d63b9fea9dc2b4b7a [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>.*)'
Mohammad Azim Khan440d8732018-07-18 12:50:49 +0100187C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*$'
Azim Khanfcdf6852018-07-05 17:31:46 +0100188TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100189INT_CHECK_REGEX = r'int\s+.*'
190CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
191DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100192FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
193EXIT_LABEL_REGEX = r'^exit:'
194
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100195
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100196class GeneratorInputError(Exception):
197 """
Azim Khane3b26af2018-06-29 02:36:57 +0100198 Exception to indicate error in the input files to this script.
199 This includes missing patterns, test function names and other
200 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100201 """
202 pass
203
204
Azim Khanb31aa442018-07-03 11:57:54 +0100205class FileWrapper(io.FileIO, object):
Azim Khan4b543232017-06-30 09:35:21 +0100206 """
Azim Khane3b26af2018-06-29 02:36:57 +0100207 This class extends built-in io.FileIO class with attribute line_no,
208 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100209 """
210
211 def __init__(self, file_name):
212 """
Azim Khane3b26af2018-06-29 02:36:57 +0100213 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100214
Azim Khanf0e42fb2017-08-02 14:47:13 +0100215 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100216 """
217 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100218 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100219
Azim Khanb31aa442018-07-03 11:57:54 +0100220 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100221 """
Azim Khane3b26af2018-06-29 02:36:57 +0100222 Python 2 iterator method. This method overrides base class's
223 next method and extends the next method to count the line
224 numbers as each line is read.
225
226 It works for both Python 2 and Python 3 by checking iterator
227 method name in the base iterator object.
228
Azim Khanf0e42fb2017-08-02 14:47:13 +0100229 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100230 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200231 parent = super(FileWrapper, self)
232 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100233 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200234 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100235 line = parent.next() # Python 2
236 if line is not None:
237 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100238 # Convert byte array to string with correct encoding and
239 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100240 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100241 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100242
243 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100244 __next__ = next
245
246 def get_line_no(self):
247 """
248 Gives current line number.
249 """
250 return self._line_no
251
252 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100253
254
255def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100256 """
Azim Khanb31aa442018-07-03 11:57:54 +0100257 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100258
259 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100260 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
261 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100262 """
Azim Khan4b543232017-06-30 09:35:21 +0100263 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
264
265
Azim Khanb31aa442018-07-03 11:57:54 +0100266def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100267 """
Azim Khane3b26af2018-06-29 02:36:57 +0100268 Test suite data and functions specifies compile time dependencies.
269 This function generates C preprocessor code from the input
270 dependency list. Caller uses the generated preprocessor code to
271 wrap dependent code.
272 A dependency in the input list can have a leading '!' character
273 to negate a condition. '!' is separated from the dependency using
274 function split_dep() and proper preprocessor check is generated
275 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100276
Azim Khanb31aa442018-07-03 11:57:54 +0100277 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100278 :return: if defined and endif code with macro annotations for
279 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100280 """
Azim Khanb31aa442018-07-03 11:57:54 +0100281 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
282 map(split_dep, dependencies)])
283 dep_end = ''.join(['#endif /* %s */\n' %
284 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100285
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100286 return dep_start, dep_end
287
288
Azim Khanb31aa442018-07-03 11:57:54 +0100289def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100290 """
Azim Khanb31aa442018-07-03 11:57:54 +0100291 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100292 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100293
Azim Khanb31aa442018-07-03 11:57:54 +0100294 :param dependencies: List of dependencies.
295 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100296 """
Azim Khanb31aa442018-07-03 11:57:54 +0100297 defines = '#if ' if dependencies else ''
298 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
299 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100300 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100301
302
Azim Khanb31aa442018-07-03 11:57:54 +0100303def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100304 """
Azim Khan040b6a22018-06-28 16:49:13 +0100305 Creates test function wrapper code. A wrapper has the code to
306 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100307
Azim Khanf0e42fb2017-08-02 14:47:13 +0100308 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100309 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100310 :param args_dispatch: List of dispatch arguments.
311 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100312 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100313 """
314 # Then create the wrapper
315 wrapper = '''
316void {name}_wrapper( void ** params )
317{{
Gilles Peskine77761412018-06-18 17:51:40 +0200318{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100319 {name}( {args} );
320}}
Gilles Peskine77761412018-06-18 17:51:40 +0200321'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100322 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100323 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100324 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100325 return wrapper
326
327
Azim Khanb31aa442018-07-03 11:57:54 +0100328def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100329 """
Azim Khane3b26af2018-06-29 02:36:57 +0100330 Test suite code template main_test.function defines a C function
331 array to contain test case functions. This function generates an
332 initializer entry for a function in that array. The entry is
333 composed of a compile time check for the test function
334 dependencies. At compile time the test function is assigned when
335 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100336
Azim Khanf0e42fb2017-08-02 14:47:13 +0100337 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100338 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100339 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100340 """
Azim Khanb31aa442018-07-03 11:57:54 +0100341 if dependencies:
342 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100343 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100344{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100345 {name}_wrapper,
346#else
347 NULL,
348#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100349'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100350 else:
351 dispatch_code = '''
352 {name}_wrapper,
353'''.format(name=name)
354
355 return dispatch_code
356
357
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000358def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100359 """
Azim Khane3b26af2018-06-29 02:36:57 +0100360 Matches pattern end_regex to the lines read from the file object.
361 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100362
Azim Khan8d686bf2018-07-04 23:29:46 +0100363 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000364 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100365 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100366 """
Azim Khan4b543232017-06-30 09:35:21 +0100367 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100368 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000369 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100370 break
371 headers += line
372 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100373 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100374 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100375
Azim Khan4b543232017-06-30 09:35:21 +0100376 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100377
378
Azim Khan8d686bf2018-07-04 23:29:46 +0100379def validate_dependency(dependency):
380 """
381 Validates a C macro and raises GeneratorInputError on invalid input.
382 :param dependency: Input macro dependency
383 :return: input dependency stripped of leading & trailing white spaces.
384 """
385 dependency = dependency.strip()
386 if not re.match(C_IDENTIFIER_REGEX, dependency, re.I):
387 raise GeneratorInputError('Invalid dependency %s' % dependency)
388 return dependency
389
390
391def parse_dependencies(inp_str):
392 """
393 Parses dependencies out of inp_str, validates them and returns a
394 list of macros.
395
396 :param inp_str: Input string with macros delimited by ':'.
397 :return: list of dependencies
398 """
399 dependencies = [dep for dep in map(validate_dependency,
400 inp_str.split(':'))]
401 return dependencies
402
403
Azim Khanb31aa442018-07-03 11:57:54 +0100404def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100405 """
Azim Khane3b26af2018-06-29 02:36:57 +0100406 Parses test suite dependencies specified at the top of a
407 .function file, that starts with pattern BEGIN_DEPENDENCIES
408 and end with END_DEPENDENCIES. Dependencies are specified
409 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100410
Azim Khan8d686bf2018-07-04 23:29:46 +0100411 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100412 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100413 """
Azim Khanb31aa442018-07-03 11:57:54 +0100414 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100415 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100416 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100417 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100418 try:
419 dependencies = parse_dependencies(match.group('dependencies'))
420 except GeneratorInputError as error:
421 raise GeneratorInputError(
422 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100423 if re.search(END_DEP_REGEX, line):
424 break
425 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100426 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100427 " not found!" % (funcs_f.name,
428 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100429
Azim Khanb31aa442018-07-03 11:57:54 +0100430 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100431
432
Azim Khanb31aa442018-07-03 11:57:54 +0100433def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100434 """
Azim Khane3b26af2018-06-29 02:36:57 +0100435 Parses function dependencies, that are in the same line as
436 comment BEGIN_CASE. Dependencies are specified after pattern
437 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100438
Azim Khan8d686bf2018-07-04 23:29:46 +0100439 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100440 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100441 """
Azim Khanb31aa442018-07-03 11:57:54 +0100442 dependencies = []
443 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100444 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100445 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100446 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100447 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100448 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100449
Azim Khan8d686bf2018-07-04 23:29:46 +0100450 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100451
Azim Khan4084ec72018-07-05 14:20:08 +0100452
Azim Khanfcdf6852018-07-05 17:31:46 +0100453def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100454 """
Azim Khane3b26af2018-06-29 02:36:57 +0100455 Parses test function signature for validation and generates
456 a dispatch wrapper function that translates input test vectors
457 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100458
Azim Khan8d686bf2018-07-04 23:29:46 +0100459 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100460 signature.
Azim Khanfcdf6852018-07-05 17:31:46 +0100461 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100462 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100463 """
464 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100465 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100466 args_dispatch = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100467 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100468 # Remove characters before arguments
469 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100470 # Process arguments, ex: <type> arg1, <type> arg2 )
471 # This script assumes that the argument list is terminated by ')'
472 # i.e. the test functions will not have a function pointer
473 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100474 for arg in line[:line.find(')')].split(','):
475 arg = arg.strip()
476 if arg == '':
477 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100478 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100479 args.append('int')
480 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100481 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100482 args.append('char*')
483 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100484 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100485 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100486 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100487 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
488 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100489 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100490""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100491
Azim Khan5fcca462018-06-29 11:05:32 +0100492 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100493 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100494 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100495 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100496 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100497 arg_idx += 1
498
Azim Khanfcdf6852018-07-05 17:31:46 +0100499 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100500
501
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100502def generate_function_code(name, code, local_vars, args_dispatch,
503 dependencies):
504 """
505 Generate function code with preprocessor checks and parameter dispatch
506 wrapper.
507
508 :param name: Function name
509 :param code: Function code
510 :param local_vars: Local variables for function wrapper
511 :param args_dispatch: Argument dispatch code
512 :param dependencies: Preprocessor dependencies list
513 :return: Final function code
514 """
515 # Add exit label if not present
516 if code.find('exit:') == -1:
517 split_code = code.rsplit('}', 1)
518 if len(split_code) == 2:
519 code = """exit:
520 ;
521}""".join(split_code)
522
523 code += gen_function_wrapper(name, local_vars, args_dispatch)
524 preprocessor_check_start, preprocessor_check_end = \
525 gen_dependencies(dependencies)
526 return preprocessor_check_start + code + preprocessor_check_end
527
528
Azim Khanb31aa442018-07-03 11:57:54 +0100529def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100530 """
Azim Khan040b6a22018-06-28 16:49:13 +0100531 Parses out a function from function file object and generates
532 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100533
Azim Khanf0e42fb2017-08-02 14:47:13 +0100534 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100535 :param dependencies: List of dependencies
536 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100537 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100538 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100539 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
540 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100541 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100542 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100543 # Check function signature. Function signature may be split
544 # across multiple lines. Here we try to find the start of
545 # arguments list, then remove '\n's and apply the regex to
546 # detect function start.
547 up_to_arg_list_start = code + line[:line.find('(') + 1]
548 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
549 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100550 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100551 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100552 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100553 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100554 for lin in funcs_f:
555 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100556 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100557 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100558 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100559 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100560 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100561 break
562 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100563 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100564 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100565 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100566
Azim Khanfcdf6852018-07-05 17:31:46 +0100567 # Prefix test function name with 'test_'
568 code = code.replace(name, 'test_' + name, 1)
569 name = 'test_' + name
570
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100571 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100572 if re.search(END_CASE_REGEX, line):
573 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100574 if not has_exit_label:
575 has_exit_label = \
576 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100577 code += line
578 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100579 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100580 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100581
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100582 code = line_directive + code
583 code = generate_function_code(name, code, local_vars, args_dispatch,
584 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100585 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100586 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100587
588
589def parse_functions(funcs_f):
590 """
Azim Khane3b26af2018-06-29 02:36:57 +0100591 Parses a test_suite_xxx.function file and returns information
592 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100593
Azim Khanf0e42fb2017-08-02 14:47:13 +0100594 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100595 :return: List of test suite dependencies, test function dispatch
596 code, function code and a dict with function identifiers
597 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100598 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000599 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100600 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100601 suite_functions = ''
602 func_info = {}
603 function_idx = 0
604 dispatch_code = ''
605 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100606 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100607 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000608 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100609 suite_helpers += parse_until_pattern(funcs_f,
610 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100611 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100612 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100613 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100614 try:
615 dependencies = parse_function_dependencies(line)
616 except GeneratorInputError as error:
617 raise GeneratorInputError(
618 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
619 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100620 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100621 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100622 suite_functions += func_code
623 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100624 if func_name in func_info:
625 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100626 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100627 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 func_info[func_name] = (function_idx, args)
629 dispatch_code += '/* Function Id: %d */\n' % function_idx
630 dispatch_code += func_dispatch
631 function_idx += 1
632
Azim Khanb31aa442018-07-03 11:57:54 +0100633 func_code = (suite_helpers +
634 suite_functions).join(gen_dependencies(suite_dependencies))
635 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100636
637
Azim Khanb31aa442018-07-03 11:57:54 +0100638def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100639 """
Azim Khanb31aa442018-07-03 11:57:54 +0100640 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100641 Since, return value is used to write back to the intermediate
642 data file, any escape characters in the input are retained in the
643 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100644
Azim Khanb31aa442018-07-03 11:57:54 +0100645 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100646 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100647 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100648 """
Azim Khanb31aa442018-07-03 11:57:54 +0100649 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100650 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100651 out = re.sub(r'(\\.)|' + split_char,
652 lambda m: m.group(1) or '\n', inp_str,
653 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100654 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100655 return out
656
657
Azim Khanb31aa442018-07-03 11:57:54 +0100658def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100659 """
Azim Khane3b26af2018-06-29 02:36:57 +0100660 Parses .data file for each test case name, test function name,
661 test dependencies and test arguments. This information is
662 correlated with the test functions file for generating an
663 intermediate data file replacing the strings for test function
664 names, dependencies and integer constant expressions with
665 identifiers. Mainly for optimising space for on-target
666 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100667
Azim Khanf0e42fb2017-08-02 14:47:13 +0100668 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100669 :return: Generator that yields test name, function name,
670 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100671 """
Azim Khanb31aa442018-07-03 11:57:54 +0100672 __state_read_name = 0
673 __state_read_args = 1
674 state = __state_read_name
675 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100676 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100677 for line in data_f:
678 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100679 # Skip comments
680 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100681 continue
682
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100683 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100684 if not line:
685 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100686 raise GeneratorInputError("[%s:%d] Newline before arguments. "
687 "Test function and arguments "
688 "missing for %s" %
689 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100690 continue
691
Azim Khanb31aa442018-07-03 11:57:54 +0100692 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100693 # Read test name
694 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100695 state = __state_read_args
696 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100697 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100698 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100699 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100700 try:
701 dependencies = parse_dependencies(
702 match.group('dependencies'))
703 except GeneratorInputError as error:
704 raise GeneratorInputError(
705 str(error) + " - %s:%d" %
706 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100707 else:
708 # Read test vectors
709 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100710 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100711 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100712 yield name, test_function, dependencies, args
713 dependencies = []
714 state = __state_read_name
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 missing for "
718 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100719
720
721def gen_dep_check(dep_id, dep):
722 """
Azim Khane3b26af2018-06-29 02:36:57 +0100723 Generate code for checking dependency with the associated
724 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100725
Azim Khanf0e42fb2017-08-02 14:47:13 +0100726 :param dep_id: Dependency identifier
727 :param dep: Dependency macro
728 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100729 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100730 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100731 raise GeneratorInputError("Dependency Id should be a positive "
732 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100733 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
734 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100735 raise GeneratorInputError("Dependency should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100736 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100737 case {id}:
738 {{
Azim Khanb31aa442018-07-03 11:57:54 +0100739#if {_not}defined({macro})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100740 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100741#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100742 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100743#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100744 }}
Azim Khanb31aa442018-07-03 11:57:54 +0100745 break;'''.format(_not=_not, macro=dep, id=dep_id)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100746 return dep_check
747
748
749def gen_expression_check(exp_id, exp):
750 """
Azim Khane3b26af2018-06-29 02:36:57 +0100751 Generates code for evaluating an integer expression using
752 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100753
Azim Khanf0e42fb2017-08-02 14:47:13 +0100754 :param exp_id: Expression Identifier
755 :param exp: Expression/Macro
756 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100757 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100758 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100759 raise GeneratorInputError("Expression Id should be a positive "
760 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100761 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100762 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100763 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100764 case {exp_id}:
765 {{
766 *out_value = {expression};
767 }}
768 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100769 return exp_code
770
771
Azim Khanb31aa442018-07-03 11:57:54 +0100772def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100773 """
Azim Khane3b26af2018-06-29 02:36:57 +0100774 Write dependencies to intermediate test data file, replacing
775 the string form with identifiers. Also, generates dependency
776 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100777
Azim Khanf0e42fb2017-08-02 14:47:13 +0100778 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100779 :param test_dependencies: Dependencies
780 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100781 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100782 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100783 """
Azim Khan599cd242017-07-06 17:34:27 +0100784 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100785 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100786 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100787 for dep in test_dependencies:
788 if dep not in unique_dependencies:
789 unique_dependencies.append(dep)
790 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100791 dep_check_code += gen_dep_check(dep_id, dep)
792 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100793 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100794 out_data_f.write(':' + str(dep_id))
795 out_data_f.write('\n')
796 return dep_check_code
797
798
799def write_parameters(out_data_f, test_args, func_args, unique_expressions):
800 """
Azim Khane3b26af2018-06-29 02:36:57 +0100801 Writes test parameters to the intermediate data file, replacing
802 the string form with identifiers. Also, generates expression
803 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100804
Azim Khanf0e42fb2017-08-02 14:47:13 +0100805 :param out_data_f: Output intermediate data file
806 :param test_args: Test parameters
807 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100808 :param unique_expressions: Mutable list to track unique
809 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100810 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100811 """
812 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100813 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100814 typ = func_args[i]
815 val = test_args[i]
816
Azim Khan040b6a22018-06-28 16:49:13 +0100817 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100818 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
819 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100820 typ = 'exp'
821 if val not in unique_expressions:
822 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100823 # exp_id can be derived from len(). But for
824 # readability and consistency with case of existing
825 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100826 exp_id = unique_expressions.index(val)
827 expression_code += gen_expression_check(exp_id, val)
828 val = exp_id
829 else:
830 val = unique_expressions.index(val)
831 out_data_f.write(':' + typ + ':' + str(val))
832 out_data_f.write('\n')
833 return expression_code
834
835
Azim Khanb31aa442018-07-03 11:57:54 +0100836def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100837 """
Azim Khane3b26af2018-06-29 02:36:57 +0100838 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100839
Azim Khanb31aa442018-07-03 11:57:54 +0100840 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100841 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100842 :param dep_check_code: Dependency check code
843 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100844 :return: Dependency and expression code guarded by test suite
845 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100846 """
Azim Khanb31aa442018-07-03 11:57:54 +0100847 if suite_dependencies:
848 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100849 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100850{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100851{code}
Azim Khan599cd242017-07-06 17:34:27 +0100852#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100853'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100854 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100855{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100856{code}
Azim Khan599cd242017-07-06 17:34:27 +0100857#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100858'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100859 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100860
861
Azim Khanb31aa442018-07-03 11:57:54 +0100862def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100863 """
Azim Khane3b26af2018-06-29 02:36:57 +0100864 This function reads test case name, dependencies and test vectors
865 from the .data file. This information is correlated with the test
866 functions file for generating an intermediate data file replacing
867 the strings for test function names, dependencies and integer
868 constant expressions with identifiers. Mainly for optimising
869 space for on-target execution.
870 It also generates test case dependency check code and expression
871 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100872
Azim Khanf0e42fb2017-08-02 14:47:13 +0100873 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100874 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100875 :param func_info: Dict keyed by function and with function id
876 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100877 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100878 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100879 """
Azim Khanb31aa442018-07-03 11:57:54 +0100880 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100881 unique_expressions = []
882 dep_check_code = ''
883 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100884 for test_name, function_name, test_dependencies, test_args in \
885 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100886 out_data_f.write(test_name + '\n')
887
Azim Khanb31aa442018-07-03 11:57:54 +0100888 # Write dependencies
889 dep_check_code += write_dependencies(out_data_f, test_dependencies,
890 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100891
Azim Khan599cd242017-07-06 17:34:27 +0100892 # Write test function name
893 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100894 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100895 raise GeneratorInputError("Function %s not found!" %
896 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100897 func_id, func_args = func_info[test_function_name]
898 out_data_f.write(str(func_id))
899
900 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100901 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100902 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100903 "%s. See function %s signature." %
904 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100905 expression_code += write_parameters(out_data_f, test_args, func_args,
906 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100907
Azim Khan599cd242017-07-06 17:34:27 +0100908 # Write a newline as test case separator
909 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100910
Azim Khanb31aa442018-07-03 11:57:54 +0100911 dep_check_code, expression_code = gen_suite_dep_checks(
912 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100913 return dep_check_code, expression_code
914
915
Azim Khanb31aa442018-07-03 11:57:54 +0100916def add_input_info(funcs_file, data_file, template_file,
917 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100918 """
Azim Khanb31aa442018-07-03 11:57:54 +0100919 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100920
Azim Khanf0e42fb2017-08-02 14:47:13 +0100921 :param funcs_file: Functions file object
922 :param data_file: Data file object
923 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100924 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100925 :param snippets: Dictionary to contain code pieces to be
926 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100927 :return:
928 """
Azim Khanb31aa442018-07-03 11:57:54 +0100929 snippets['test_file'] = c_file
930 snippets['test_main_file'] = template_file
931 snippets['test_case_file'] = funcs_file
932 snippets['test_case_data_file'] = data_file
933
934
935def read_code_from_input_files(platform_file, helpers_file,
936 out_data_file, snippets):
937 """
938 Read code from input files and create substitutions for replacement
939 strings in the template file.
940
941 :param platform_file: Platform file object
942 :param helpers_file: Helper functions file object
943 :param out_data_file: Output intermediate data file object
944 :param snippets: Dictionary to contain code pieces to be
945 substituted in the template.
946 :return:
947 """
948 # Read helpers
949 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
950 platform_f:
951 snippets['test_common_helper_file'] = helpers_file
952 snippets['test_common_helpers'] = help_f.read()
953 snippets['test_platform_file'] = platform_file
954 snippets['platform_code'] = platform_f.read().replace(
955 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
956
957
958def write_test_source_file(template_file, c_file, snippets):
959 """
960 Write output source file with generated source code.
961
962 :param template_file: Template file name
963 :param c_file: Output source file
964 :param snippets: Generated and code snippets
965 :return:
966 """
967 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +0100968 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +0100969 # Update line number. +1 as #line directive sets next line number
970 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100971 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +0100972 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +0100973
974
975def parse_function_file(funcs_file, snippets):
976 """
977 Parse function file and generate function dispatch code.
978
979 :param funcs_file: Functions file name
980 :param snippets: Dictionary to contain code pieces to be
981 substituted in the template.
982 :return:
983 """
984 with FileWrapper(funcs_file) as funcs_f:
985 suite_dependencies, dispatch_code, func_code, func_info = \
986 parse_functions(funcs_f)
987 snippets['functions_code'] = func_code
988 snippets['dispatch_code'] = dispatch_code
989 return suite_dependencies, func_info
990
991
992def generate_intermediate_data_file(data_file, out_data_file,
993 suite_dependencies, func_info, snippets):
994 """
995 Generates intermediate data file from input data file and
996 information read from functions file.
997
998 :param data_file: Data file name
999 :param out_data_file: Output/Intermediate data file
1000 :param suite_dependencies: List of suite dependencies.
1001 :param func_info: Function info parsed from functions file.
1002 :param snippets: Dictionary to contain code pieces to be
1003 substituted in the template.
1004 :return:
1005 """
1006 with FileWrapper(data_file) as data_f, \
1007 open(out_data_file, 'w') as out_data_f:
1008 dep_check_code, expression_code = gen_from_test_data(
1009 data_f, out_data_f, func_info, suite_dependencies)
1010 snippets['dep_check_code'] = dep_check_code
1011 snippets['expression_code'] = expression_code
1012
1013
1014def generate_code(**input_info):
1015 """
1016 Generates C source code from test suite file, data file, common
1017 helpers file and platform file.
1018
1019 input_info expands to following parameters:
1020 funcs_file: Functions file object
1021 data_file: Data file object
1022 template_file: Template file object
1023 platform_file: Platform file object
1024 helpers_file: Helper functions file object
1025 suites_dir: Test suites dir
1026 c_file: Output C file object
1027 out_data_file: Output intermediate data file object
1028 :return:
1029 """
1030 funcs_file = input_info['funcs_file']
1031 data_file = input_info['data_file']
1032 template_file = input_info['template_file']
1033 platform_file = input_info['platform_file']
1034 helpers_file = input_info['helpers_file']
1035 suites_dir = input_info['suites_dir']
1036 c_file = input_info['c_file']
1037 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001038 for name, path in [('Functions file', funcs_file),
1039 ('Data file', data_file),
1040 ('Template file', template_file),
1041 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001042 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001043 ('Suites dir', suites_dir)]:
1044 if not os.path.exists(path):
1045 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1046
Azim Khanb31aa442018-07-03 11:57:54 +01001047 snippets = {'generator_script': os.path.basename(__file__)}
1048 read_code_from_input_files(platform_file, helpers_file,
1049 out_data_file, snippets)
1050 add_input_info(funcs_file, data_file, template_file,
1051 c_file, snippets)
1052 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1053 generate_intermediate_data_file(data_file, out_data_file,
1054 suite_dependencies, func_info, snippets)
1055 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001056
1057
Azim Khan8d686bf2018-07-04 23:29:46 +01001058def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001059 """
1060 Command line parser.
1061
1062 :return:
1063 """
Azim Khan040b6a22018-06-28 16:49:13 +01001064 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001065 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001066
1067 parser.add_argument("-f", "--functions-file",
1068 dest="funcs_file",
1069 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001070 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001071 required=True)
1072
1073 parser.add_argument("-d", "--data-file",
1074 dest="data_file",
1075 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001076 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001077 required=True)
1078
1079 parser.add_argument("-t", "--template-file",
1080 dest="template_file",
1081 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001082 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001083 required=True)
1084
1085 parser.add_argument("-s", "--suites-dir",
1086 dest="suites_dir",
1087 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001088 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001089 required=True)
1090
Azim Khane3b26af2018-06-29 02:36:57 +01001091 parser.add_argument("--helpers-file",
1092 dest="helpers_file",
1093 help="Helpers file",
1094 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001095 required=True)
1096
1097 parser.add_argument("-p", "--platform-file",
1098 dest="platform_file",
1099 help="Platform code file",
1100 metavar="PLATFORM_FILE",
1101 required=True)
1102
1103 parser.add_argument("-o", "--out-dir",
1104 dest="out_dir",
1105 help="Dir where generated code and scripts are copied",
1106 metavar="OUT_DIR",
1107 required=True)
1108
1109 args = parser.parse_args()
1110
1111 data_file_name = os.path.basename(args.data_file)
1112 data_name = os.path.splitext(data_file_name)[0]
1113
1114 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001115 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001116
1117 out_c_file_dir = os.path.dirname(out_c_file)
1118 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001119 for directory in [out_c_file_dir, out_data_file_dir]:
1120 if not os.path.exists(directory):
1121 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001122
Azim Khanb31aa442018-07-03 11:57:54 +01001123 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1124 template_file=args.template_file,
1125 platform_file=args.platform_file,
1126 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1127 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001128
1129
1130if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001131 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001132 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001133 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001134 sys.exit("%s: input error: %s" %
1135 (os.path.basename(sys.argv[0]), str(err)))