blob: a28a73669de05ce563330e87cfa7756f09fe9cbd [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#
Mohammad Azim Khan78befd92018-03-06 11:49:41 +00004# 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
60X509 CRL Unsupported critical extension (issuingDistributionPoint)
61depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_RSA_C:MBEDTLS_SHA256_C
Azim Khanb31aa442018-07-03 11:57:54 +010062mbedtls_x509_crl_parse:"data_files/crl-idp.pem":\
63 MBEDTLS_ERR_X509_INVALID_EXTENSIONS + MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
Azim Khanaee05bb2018-07-02 16:01:04 +010064
65Test functions:
66---------------
67Test functions are coded in C in test_suite_<module>.function files.
68Functions file is itself not compilable and contains special
69format patterns to specify test suite dependencies, start and end
70of functions and function dependencies. Check any existing functions
71file for example.
72
73Execution:
74----------
75Tests are executed in 3 steps:
76- Generating test_suite_<module>[.<optional sub module>].c file
77 for each corresponding .data file.
78- Building each source file into executables.
79- Running each executable and printing report.
80
81Generating C test source requires more than just the test functions.
82Following extras are required:
83- Process main()
84- Reading .data file and dispatching test cases.
85- Platform specific test case execution
86- Dependency checking
87- Integer expression evaluation
88- Test function dispatch
89
90Build dependencies and integer expressions (in the test parameters)
91are specified as strings in the .data file. Their run time value is
92not known at the generation stage. Hence, they need to be translated
93into run time evaluations. This script generates the run time checks
94for dependencies and integer expressions.
95
96Similarly, function names have to be translated into function calls.
97This script also generates code for function dispatch.
98
99The extra code mentioned here is either generated by this script
100or it comes from the input files: helpers file, platform file and
101the template file.
102
103Helper file:
104------------
105Helpers file contains common helper/utility functions and data.
106
107Platform file:
108--------------
109Platform file contains platform specific setup code and test case
110dispatch code. For example, host_test.function reads test data
111file from host's file system and dispatches tests.
112In case of on-target target_test.function tests are not dispatched
113on target. Target code is kept minimum and only test functions are
114dispatched. Test case dispatch is done on the host using tools like
115Greentea.
116
117Template file:
118---------
119Template file for example main_test.function is a template C file in
120which generated code and code from input files is substituted to
121generate a compilable C file. It also contains skeleton functions for
122dependency checks, expression evaluation and function dispatch. These
123functions are populated with checks and return codes by this script.
124
125Template file contains "replacement" fields that are formatted
126strings processed by Python str.format() method.
127
128This script:
129============
130Core function of this script is to fill the template file with
131code that is generated or read from helpers and platform files.
132
133This script replaces following fields in the template and generates
134the test source file:
135
136{test_common_helpers} <-- All common code from helpers.function
137 is substituted here.
138{functions_code} <-- Test functions are substituted here
139 from the input test_suit_xyz.function
140 file. C preprocessor checks are generated
141 for the build dependencies specified
142 in the input file. This script also
143 generates wrappers for the test
144 functions with code to expand the
145 string parameters read from the data
146 file.
147{expression_code} <-- This script enumerates the
148 expressions in the .data file and
149 generates code to handle enumerated
150 expression Ids and return the values.
151{dep_check_code} <-- This script enumerates all
152 build dependencies and generate
153 code to handle enumerated build
154 dependency Id and return status: if
155 the dependency is defined or not.
156{dispatch_code} <-- This script enumerates the functions
157 specified in the input test data file
158 and generates the initializer for the
159 function table in the template
160 file.
161{platform_code} <-- Platform specific setup and test
162 dispatch code.
163
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100164"""
165
Azim Khanf0e42fb2017-08-02 14:47:13 +0100166
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100167import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100168import os
169import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100170import sys
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 Khanb31aa442018-07-03 11:57:54 +0100183BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(.*?)\s*\*/'
184END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100185
186
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100187class GeneratorInputError(Exception):
188 """
Azim Khane3b26af2018-06-29 02:36:57 +0100189 Exception to indicate error in the input files to this script.
190 This includes missing patterns, test function names and other
191 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100192 """
193 pass
194
195
Azim Khanb31aa442018-07-03 11:57:54 +0100196class FileWrapper(io.FileIO, object):
Azim Khan4b543232017-06-30 09:35:21 +0100197 """
Azim Khane3b26af2018-06-29 02:36:57 +0100198 This class extends built-in io.FileIO class with attribute line_no,
199 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100200 """
201
202 def __init__(self, file_name):
203 """
Azim Khane3b26af2018-06-29 02:36:57 +0100204 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100205
Azim Khanf0e42fb2017-08-02 14:47:13 +0100206 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100207 """
208 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100209 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100210
Azim Khanb31aa442018-07-03 11:57:54 +0100211 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100212 """
Azim Khane3b26af2018-06-29 02:36:57 +0100213 Python 2 iterator method. This method overrides base class's
214 next method and extends the next method to count the line
215 numbers as each line is read.
216
217 It works for both Python 2 and Python 3 by checking iterator
218 method name in the base iterator object.
219
Azim Khanf0e42fb2017-08-02 14:47:13 +0100220 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100221 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200222 parent = super(FileWrapper, self)
223 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100224 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200225 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100226 line = parent.next() # Python 2
227 if line is not None:
228 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100229 # Convert byte array to string with correct encoding and
230 # strip any whitespaces added in the decoding process.
231 return line.decode(sys.getdefaultencoding()).strip() + "\n"
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100232 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100233
234 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100235 __next__ = next
236
237 def get_line_no(self):
238 """
239 Gives current line number.
240 """
241 return self._line_no
242
243 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100244
245
246def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100247 """
Azim Khanb31aa442018-07-03 11:57:54 +0100248 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100249
250 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100251 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
252 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100253 """
Azim Khan4b543232017-06-30 09:35:21 +0100254 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
255
256
Azim Khanb31aa442018-07-03 11:57:54 +0100257def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100258 """
Azim Khane3b26af2018-06-29 02:36:57 +0100259 Test suite data and functions specifies compile time dependencies.
260 This function generates C preprocessor code from the input
261 dependency list. Caller uses the generated preprocessor code to
262 wrap dependent code.
263 A dependency in the input list can have a leading '!' character
264 to negate a condition. '!' is separated from the dependency using
265 function split_dep() and proper preprocessor check is generated
266 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100267
Azim Khanb31aa442018-07-03 11:57:54 +0100268 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100269 :return: if defined and endif code with macro annotations for
270 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100271 """
Azim Khanb31aa442018-07-03 11:57:54 +0100272 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
273 map(split_dep, dependencies)])
274 dep_end = ''.join(['#endif /* %s */\n' %
275 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100276
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100277 return dep_start, dep_end
278
279
Azim Khanb31aa442018-07-03 11:57:54 +0100280def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100281 """
Azim Khanb31aa442018-07-03 11:57:54 +0100282 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100283 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100284
Azim Khanb31aa442018-07-03 11:57:54 +0100285 :param dependencies: List of dependencies.
286 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100287 """
Azim Khanb31aa442018-07-03 11:57:54 +0100288 defines = '#if ' if dependencies else ''
289 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
290 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100291 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100292
293
Azim Khanb31aa442018-07-03 11:57:54 +0100294def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100295 """
Azim Khan040b6a22018-06-28 16:49:13 +0100296 Creates test function wrapper code. A wrapper has the code to
297 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100298
Azim Khanf0e42fb2017-08-02 14:47:13 +0100299 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100300 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100301 :param args_dispatch: List of dispatch arguments.
302 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100303 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100304 """
305 # Then create the wrapper
306 wrapper = '''
307void {name}_wrapper( void ** params )
308{{
Gilles Peskine77761412018-06-18 17:51:40 +0200309{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100310 {name}( {args} );
311}}
Gilles Peskine77761412018-06-18 17:51:40 +0200312'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100313 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100314 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100315 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100316 return wrapper
317
318
Azim Khanb31aa442018-07-03 11:57:54 +0100319def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100320 """
Azim Khane3b26af2018-06-29 02:36:57 +0100321 Test suite code template main_test.function defines a C function
322 array to contain test case functions. This function generates an
323 initializer entry for a function in that array. The entry is
324 composed of a compile time check for the test function
325 dependencies. At compile time the test function is assigned when
326 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100327
Azim Khanf0e42fb2017-08-02 14:47:13 +0100328 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100329 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100330 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100331 """
Azim Khanb31aa442018-07-03 11:57:54 +0100332 if dependencies:
333 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100334 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100335{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100336 {name}_wrapper,
337#else
338 NULL,
339#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100340'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100341 else:
342 dispatch_code = '''
343 {name}_wrapper,
344'''.format(name=name)
345
346 return dispatch_code
347
348
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000349def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100350 """
Azim Khane3b26af2018-06-29 02:36:57 +0100351 Matches pattern end_regex to the lines read from the file object.
352 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100353
Azim Khanf0e42fb2017-08-02 14:47:13 +0100354 :param funcs_f: file object for .functions file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000355 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100356 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100357 """
Azim Khan4b543232017-06-30 09:35:21 +0100358 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100359 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000360 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100361 break
362 headers += line
363 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100364 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100365 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100366
Azim Khan4b543232017-06-30 09:35:21 +0100367 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100368
369
Azim Khanb31aa442018-07-03 11:57:54 +0100370def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100371 """
Azim Khane3b26af2018-06-29 02:36:57 +0100372 Parses test suite dependencies specified at the top of a
373 .function file, that starts with pattern BEGIN_DEPENDENCIES
374 and end with END_DEPENDENCIES. Dependencies are specified
375 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100376
Azim Khanf0e42fb2017-08-02 14:47:13 +0100377 :param funcs_f: file object for .functions file
378 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100379 """
Azim Khanb31aa442018-07-03 11:57:54 +0100380 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100381 for line in funcs_f:
Azim Khanb31aa442018-07-03 11:57:54 +0100382 match = re.search('depends_on:(.*)', line.strip())
383 if match:
384 dependencies += [x.strip() for x in match.group(1).split(':')]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100385 if re.search(END_DEP_REGEX, line):
386 break
387 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100388 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100389 " not found!" % (funcs_f.name,
390 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100391
Azim Khanb31aa442018-07-03 11:57:54 +0100392 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100393
394
Azim Khanb31aa442018-07-03 11:57:54 +0100395def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100396 """
Azim Khane3b26af2018-06-29 02:36:57 +0100397 Parses function dependencies, that are in the same line as
398 comment BEGIN_CASE. Dependencies are specified after pattern
399 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100400
Azim Khanf0e42fb2017-08-02 14:47:13 +0100401 :param line: Line from .functions file that has dependencies.
402 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100403 """
Azim Khanb31aa442018-07-03 11:57:54 +0100404 dependencies = []
405 match = re.search(BEGIN_CASE_REGEX, line)
406 dep_str = match.group(1)
407 if dep_str:
408 match = re.search('depends_on:(.*)', dep_str)
409 if match:
410 dependencies = [x.strip()
411 for x in match.group(1).strip().split(':')]
412 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100413
414
415def parse_function_signature(line):
416 """
Azim Khane3b26af2018-06-29 02:36:57 +0100417 Parses test function signature for validation and generates
418 a dispatch wrapper function that translates input test vectors
419 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100420
Azim Khan040b6a22018-06-28 16:49:13 +0100421 :param line: Line from .functions file that has a function
422 signature.
423 :return: function name, argument list, local variables for
424 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100425 """
426 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100427 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100428 args_dispatch = []
Azim Khane3b26af2018-06-29 02:36:57 +0100429 # Check if the test function returns void.
Azim Khanb31aa442018-07-03 11:57:54 +0100430 match = re.search(r'\s*void\s+(\w+)\s*\(', line, re.I)
431 if not match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100432 raise ValueError("Test function should return 'void'\n%s" % line)
Azim Khanb31aa442018-07-03 11:57:54 +0100433 name = match.group(1)
434 line = line[len(match.group(0)):]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100435 arg_idx = 0
436 for arg in line[:line.find(')')].split(','):
437 arg = arg.strip()
438 if arg == '':
439 continue
Azim Khanb31aa442018-07-03 11:57:54 +0100440 if re.search(r'int\s+.*', arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100441 args.append('int')
442 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khanb31aa442018-07-03 11:57:54 +0100443 elif re.search(r'char\s*\*\s*.*', arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100444 args.append('char*')
445 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khanb31aa442018-07-03 11:57:54 +0100446 elif re.search(r'data_t\s*\*\s*.*', arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100447 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100448 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100449 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
450 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100451 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100452""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100453
Azim Khan5fcca462018-06-29 11:05:32 +0100454 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100455 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100456 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100457 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100458 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100459 arg_idx += 1
460
Azim Khanb31aa442018-07-03 11:57:54 +0100461 return name, args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100462
463
Azim Khanb31aa442018-07-03 11:57:54 +0100464def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100465 """
Azim Khan040b6a22018-06-28 16:49:13 +0100466 Parses out a function from function file object and generates
467 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100468
Azim Khanf0e42fb2017-08-02 14:47:13 +0100469 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100470 :param dependencies: List of dependencies
471 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100472 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100473 """
Azim Khan4b543232017-06-30 09:35:21 +0100474 code = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100475 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100476 # Check function signature
Azim Khanb31aa442018-07-03 11:57:54 +0100477 match = re.match(r'.*?\s+(\w+)\s*\(', line, re.I)
478 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100479 # check if we have full signature i.e. split in more lines
Azim Khanb31aa442018-07-03 11:57:54 +0100480 if not re.match(r'.*\)', line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100481 for lin in funcs_f:
482 line += lin
Azim Khanb31aa442018-07-03 11:57:54 +0100483 if re.search(r'.*?\)', line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100484 break
Azim Khanb31aa442018-07-03 11:57:54 +0100485 name, args, local_vars, args_dispatch = parse_function_signature(
486 line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100487 code += line.replace(name, 'test_' + name)
488 name = 'test_' + name
489 break
490 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100491 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100492 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100493
494 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100495 if re.search(END_CASE_REGEX, line):
496 break
497 code += line
498 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100499 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100500 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100501
502 # Add exit label if not present
503 if code.find('exit:') == -1:
Azim Khanb31aa442018-07-03 11:57:54 +0100504 split_code = code.rsplit('}', 1)
505 if len(split_code) == 2:
Azim Khan4b543232017-06-30 09:35:21 +0100506 code = """exit:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100507 ;;
Azim Khanb31aa442018-07-03 11:57:54 +0100508}""".join(split_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100509
Azim Khanb31aa442018-07-03 11:57:54 +0100510 code += gen_function_wrapper(name, local_vars, args_dispatch)
511 preprocessor_check_start, preprocessor_check_end = \
512 gen_dependencies(dependencies)
513 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
514 return (name, args, preprocessor_check_start + code +
515 preprocessor_check_end, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100516
517
518def parse_functions(funcs_f):
519 """
Azim Khane3b26af2018-06-29 02:36:57 +0100520 Parses a test_suite_xxx.function file and returns information
521 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100522
Azim Khanf0e42fb2017-08-02 14:47:13 +0100523 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100524 :return: List of test suite dependencies, test function dispatch
525 code, function code and a dict with function identifiers
526 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100527 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000528 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100529 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100530 suite_functions = ''
531 func_info = {}
532 function_idx = 0
533 dispatch_code = ''
534 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100535 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000536 headers = parse_until_pattern(funcs_f, END_HEADER_REGEX)
Azim Khanb31aa442018-07-03 11:57:54 +0100537 suite_helpers += headers
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000538 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
539 helpers = parse_until_pattern(funcs_f, END_SUITE_HELPERS_REGEX)
540 suite_helpers += helpers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100541 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100542 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100543 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100544 dependencies = parse_function_dependencies(line)
Azim Khan040b6a22018-06-28 16:49:13 +0100545 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100546 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100547 suite_functions += func_code
548 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100549 if func_name in func_info:
550 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100551 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100552 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100553 func_info[func_name] = (function_idx, args)
554 dispatch_code += '/* Function Id: %d */\n' % function_idx
555 dispatch_code += func_dispatch
556 function_idx += 1
557
Azim Khanb31aa442018-07-03 11:57:54 +0100558 func_code = (suite_helpers +
559 suite_functions).join(gen_dependencies(suite_dependencies))
560 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100561
562
Azim Khanb31aa442018-07-03 11:57:54 +0100563def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100564 """
Azim Khanb31aa442018-07-03 11:57:54 +0100565 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100566 Since, return value is used to write back to the intermediate
567 data file, any escape characters in the input are retained in the
568 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100569
Azim Khanb31aa442018-07-03 11:57:54 +0100570 :param inp_str: String to split
571 :param split_char: split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100572 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100573 """
Azim Khanb31aa442018-07-03 11:57:54 +0100574 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100575 raise ValueError('Expected split character. Found string!')
576 out = []
577 part = ''
578 escape = False
Azim Khanb31aa442018-07-03 11:57:54 +0100579 for character in inp_str:
580 if not escape and character == split_char:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100581 out.append(part)
582 part = ''
583 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100584 part += character
585 escape = not escape and character == '\\'
586 if part:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100587 out.append(part)
588 return out
589
590
Azim Khanb31aa442018-07-03 11:57:54 +0100591def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100592 """
Azim Khane3b26af2018-06-29 02:36:57 +0100593 Parses .data file for each test case name, test function name,
594 test dependencies and test arguments. This information is
595 correlated with the test functions file for generating an
596 intermediate data file replacing the strings for test function
597 names, dependencies and integer constant expressions with
598 identifiers. Mainly for optimising space for on-target
599 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100600
Azim Khanf0e42fb2017-08-02 14:47:13 +0100601 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100602 :return: Generator that yields test name, function name,
603 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100604 """
Azim Khanb31aa442018-07-03 11:57:54 +0100605 __state_read_name = 0
606 __state_read_args = 1
607 state = __state_read_name
608 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100609 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100610 for line in data_f:
611 line = line.strip()
Azim Khanb31aa442018-07-03 11:57:54 +0100612 if line and line[0] == '#': # Skip comments
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100613 continue
614
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100615 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100616 if not line:
617 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100618 raise GeneratorInputError("[%s:%d] Newline before arguments. "
619 "Test function and arguments "
620 "missing for %s" %
621 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100622 continue
623
Azim Khanb31aa442018-07-03 11:57:54 +0100624 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100625 # Read test name
626 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100627 state = __state_read_args
628 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100629 # Check dependencies
Azim Khanb31aa442018-07-03 11:57:54 +0100630 match = re.search('depends_on:(.*)', line)
631 if match:
632 dependencies = [x.strip() for x in match.group(1).split(':')
633 if len(x.strip())]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100634 else:
635 # Read test vectors
636 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100637 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100638 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100639 yield name, test_function, dependencies, args
640 dependencies = []
641 state = __state_read_name
642 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100643 raise GeneratorInputError("[%s:%d] Newline before arguments. "
644 "Test function and arguments missing for "
645 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100646
647
648def gen_dep_check(dep_id, dep):
649 """
Azim Khane3b26af2018-06-29 02:36:57 +0100650 Generate code for checking dependency with the associated
651 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100652
Azim Khanf0e42fb2017-08-02 14:47:13 +0100653 :param dep_id: Dependency identifier
654 :param dep: Dependency macro
655 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100656 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100657 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100658 raise GeneratorInputError("Dependency Id should be a positive "
659 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100660 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
661 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100662 raise GeneratorInputError("Dependency should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100663 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100664 case {id}:
665 {{
Azim Khanb31aa442018-07-03 11:57:54 +0100666#if {_not}defined({macro})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100667 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100668#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100669 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100670#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100671 }}
Azim Khanb31aa442018-07-03 11:57:54 +0100672 break;'''.format(_not=_not, macro=dep, id=dep_id)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100673 return dep_check
674
675
676def gen_expression_check(exp_id, exp):
677 """
Azim Khane3b26af2018-06-29 02:36:57 +0100678 Generates code for evaluating an integer expression using
679 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100680
Azim Khanf0e42fb2017-08-02 14:47:13 +0100681 :param exp_id: Expression Identifier
682 :param exp: Expression/Macro
683 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100684 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100685 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100686 raise GeneratorInputError("Expression Id should be a positive "
687 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100688 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100689 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100690 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100691 case {exp_id}:
692 {{
693 *out_value = {expression};
694 }}
695 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100696 return exp_code
697
698
Azim Khanb31aa442018-07-03 11:57:54 +0100699def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100700 """
Azim Khane3b26af2018-06-29 02:36:57 +0100701 Write dependencies to intermediate test data file, replacing
702 the string form with identifiers. Also, generates dependency
703 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100704
Azim Khanf0e42fb2017-08-02 14:47:13 +0100705 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100706 :param test_dependencies: Dependencies
707 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100708 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100709 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100710 """
Azim Khan599cd242017-07-06 17:34:27 +0100711 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100712 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100713 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100714 for dep in test_dependencies:
715 if dep not in unique_dependencies:
716 unique_dependencies.append(dep)
717 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100718 dep_check_code += gen_dep_check(dep_id, dep)
719 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100720 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100721 out_data_f.write(':' + str(dep_id))
722 out_data_f.write('\n')
723 return dep_check_code
724
725
726def write_parameters(out_data_f, test_args, func_args, unique_expressions):
727 """
Azim Khane3b26af2018-06-29 02:36:57 +0100728 Writes test parameters to the intermediate data file, replacing
729 the string form with identifiers. Also, generates expression
730 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100731
Azim Khanf0e42fb2017-08-02 14:47:13 +0100732 :param out_data_f: Output intermediate data file
733 :param test_args: Test parameters
734 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100735 :param unique_expressions: Mutable list to track unique
736 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100737 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100738 """
739 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100740 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100741 typ = func_args[i]
742 val = test_args[i]
743
Azim Khan040b6a22018-06-28 16:49:13 +0100744 # check if val is a non literal int val (i.e. an expression)
Azim Khanb31aa442018-07-03 11:57:54 +0100745 if typ == 'int' and not re.match(r'(\d+$)|((0x)?[0-9a-fA-F]+$)', val):
Azim Khan599cd242017-07-06 17:34:27 +0100746 typ = 'exp'
747 if val not in unique_expressions:
748 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100749 # exp_id can be derived from len(). But for
750 # readability and consistency with case of existing
751 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100752 exp_id = unique_expressions.index(val)
753 expression_code += gen_expression_check(exp_id, val)
754 val = exp_id
755 else:
756 val = unique_expressions.index(val)
757 out_data_f.write(':' + typ + ':' + str(val))
758 out_data_f.write('\n')
759 return expression_code
760
761
Azim Khanb31aa442018-07-03 11:57:54 +0100762def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100763 """
Azim Khane3b26af2018-06-29 02:36:57 +0100764 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100765
Azim Khanb31aa442018-07-03 11:57:54 +0100766 :param suite_dependencies: Test suite dependencies read from the
Azim Khan040b6a22018-06-28 16:49:13 +0100767 .functions file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100768 :param dep_check_code: Dependency check code
769 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100770 :return: Dependency and expression code guarded by test suite
771 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100772 """
Azim Khanb31aa442018-07-03 11:57:54 +0100773 if suite_dependencies:
774 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100775 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100776{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100777{code}
Azim Khan599cd242017-07-06 17:34:27 +0100778#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100779'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100780 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100781{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100782{code}
Azim Khan599cd242017-07-06 17:34:27 +0100783#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100784'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100785 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100786
787
Azim Khanb31aa442018-07-03 11:57:54 +0100788def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100789 """
Azim Khane3b26af2018-06-29 02:36:57 +0100790 This function reads test case name, dependencies and test vectors
791 from the .data file. This information is correlated with the test
792 functions file for generating an intermediate data file replacing
793 the strings for test function names, dependencies and integer
794 constant expressions with identifiers. Mainly for optimising
795 space for on-target execution.
796 It also generates test case dependency check code and expression
797 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100798
Azim Khanf0e42fb2017-08-02 14:47:13 +0100799 :param data_f: Data file object
800 :param out_data_f:Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100801 :param func_info: Dict keyed by function and with function id
802 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100803 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100804 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100805 """
Azim Khanb31aa442018-07-03 11:57:54 +0100806 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100807 unique_expressions = []
808 dep_check_code = ''
809 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100810 for test_name, function_name, test_dependencies, test_args in \
811 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100812 out_data_f.write(test_name + '\n')
813
Azim Khanb31aa442018-07-03 11:57:54 +0100814 # Write dependencies
815 dep_check_code += write_dependencies(out_data_f, test_dependencies,
816 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100817
Azim Khan599cd242017-07-06 17:34:27 +0100818 # Write test function name
819 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100820 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100821 raise GeneratorInputError("Function %s not found!" %
822 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100823 func_id, func_args = func_info[test_function_name]
824 out_data_f.write(str(func_id))
825
826 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100827 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100828 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100829 "%s. See function %s signature." %
830 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100831 expression_code += write_parameters(out_data_f, test_args, func_args,
832 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100833
Azim Khan599cd242017-07-06 17:34:27 +0100834 # Write a newline as test case separator
835 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100836
Azim Khanb31aa442018-07-03 11:57:54 +0100837 dep_check_code, expression_code = gen_suite_dep_checks(
838 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100839 return dep_check_code, expression_code
840
841
Azim Khanb31aa442018-07-03 11:57:54 +0100842def add_input_info(funcs_file, data_file, template_file,
843 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100844 """
Azim Khanb31aa442018-07-03 11:57:54 +0100845 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100846
Azim Khanf0e42fb2017-08-02 14:47:13 +0100847 :param funcs_file: Functions file object
848 :param data_file: Data file object
849 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100850 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100851 :param snippets: Dictionary to contain code pieces to be
852 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100853 :return:
854 """
Azim Khanb31aa442018-07-03 11:57:54 +0100855 snippets['test_file'] = c_file
856 snippets['test_main_file'] = template_file
857 snippets['test_case_file'] = funcs_file
858 snippets['test_case_data_file'] = data_file
859
860
861def read_code_from_input_files(platform_file, helpers_file,
862 out_data_file, snippets):
863 """
864 Read code from input files and create substitutions for replacement
865 strings in the template file.
866
867 :param platform_file: Platform file object
868 :param helpers_file: Helper functions file object
869 :param out_data_file: Output intermediate data file object
870 :param snippets: Dictionary to contain code pieces to be
871 substituted in the template.
872 :return:
873 """
874 # Read helpers
875 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
876 platform_f:
877 snippets['test_common_helper_file'] = helpers_file
878 snippets['test_common_helpers'] = help_f.read()
879 snippets['test_platform_file'] = platform_file
880 snippets['platform_code'] = platform_f.read().replace(
881 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
882
883
884def write_test_source_file(template_file, c_file, snippets):
885 """
886 Write output source file with generated source code.
887
888 :param template_file: Template file name
889 :param c_file: Output source file
890 :param snippets: Generated and code snippets
891 :return:
892 """
893 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
894 line_no = 1
895 for line in template_f.readlines():
896 # Update line number. +1 as #line directive sets next line number
897 snippets['line_no'] = line_no + 1
898 code = line.format(**snippets)
899 c_f.write(code)
900 line_no += 1
901
902
903def parse_function_file(funcs_file, snippets):
904 """
905 Parse function file and generate function dispatch code.
906
907 :param funcs_file: Functions file name
908 :param snippets: Dictionary to contain code pieces to be
909 substituted in the template.
910 :return:
911 """
912 with FileWrapper(funcs_file) as funcs_f:
913 suite_dependencies, dispatch_code, func_code, func_info = \
914 parse_functions(funcs_f)
915 snippets['functions_code'] = func_code
916 snippets['dispatch_code'] = dispatch_code
917 return suite_dependencies, func_info
918
919
920def generate_intermediate_data_file(data_file, out_data_file,
921 suite_dependencies, func_info, snippets):
922 """
923 Generates intermediate data file from input data file and
924 information read from functions file.
925
926 :param data_file: Data file name
927 :param out_data_file: Output/Intermediate data file
928 :param suite_dependencies: List of suite dependencies.
929 :param func_info: Function info parsed from functions file.
930 :param snippets: Dictionary to contain code pieces to be
931 substituted in the template.
932 :return:
933 """
934 with FileWrapper(data_file) as data_f, \
935 open(out_data_file, 'w') as out_data_f:
936 dep_check_code, expression_code = gen_from_test_data(
937 data_f, out_data_f, func_info, suite_dependencies)
938 snippets['dep_check_code'] = dep_check_code
939 snippets['expression_code'] = expression_code
940
941
942def generate_code(**input_info):
943 """
944 Generates C source code from test suite file, data file, common
945 helpers file and platform file.
946
947 input_info expands to following parameters:
948 funcs_file: Functions file object
949 data_file: Data file object
950 template_file: Template file object
951 platform_file: Platform file object
952 helpers_file: Helper functions file object
953 suites_dir: Test suites dir
954 c_file: Output C file object
955 out_data_file: Output intermediate data file object
956 :return:
957 """
958 funcs_file = input_info['funcs_file']
959 data_file = input_info['data_file']
960 template_file = input_info['template_file']
961 platform_file = input_info['platform_file']
962 helpers_file = input_info['helpers_file']
963 suites_dir = input_info['suites_dir']
964 c_file = input_info['c_file']
965 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100966 for name, path in [('Functions file', funcs_file),
967 ('Data file', data_file),
968 ('Template file', template_file),
969 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +0100970 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100971 ('Suites dir', suites_dir)]:
972 if not os.path.exists(path):
973 raise IOError("ERROR: %s [%s] not found!" % (name, path))
974
Azim Khanb31aa442018-07-03 11:57:54 +0100975 snippets = {'generator_script': os.path.basename(__file__)}
976 read_code_from_input_files(platform_file, helpers_file,
977 out_data_file, snippets)
978 add_input_info(funcs_file, data_file, template_file,
979 c_file, snippets)
980 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
981 generate_intermediate_data_file(data_file, out_data_file,
982 suite_dependencies, func_info, snippets)
983 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100984
985
986def check_cmd():
987 """
988 Command line parser.
989
990 :return:
991 """
Azim Khan040b6a22018-06-28 16:49:13 +0100992 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +0100993 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100994
995 parser.add_argument("-f", "--functions-file",
996 dest="funcs_file",
997 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +0100998 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100999 required=True)
1000
1001 parser.add_argument("-d", "--data-file",
1002 dest="data_file",
1003 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001004 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001005 required=True)
1006
1007 parser.add_argument("-t", "--template-file",
1008 dest="template_file",
1009 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001010 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001011 required=True)
1012
1013 parser.add_argument("-s", "--suites-dir",
1014 dest="suites_dir",
1015 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001016 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001017 required=True)
1018
Azim Khane3b26af2018-06-29 02:36:57 +01001019 parser.add_argument("--helpers-file",
1020 dest="helpers_file",
1021 help="Helpers file",
1022 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001023 required=True)
1024
1025 parser.add_argument("-p", "--platform-file",
1026 dest="platform_file",
1027 help="Platform code file",
1028 metavar="PLATFORM_FILE",
1029 required=True)
1030
1031 parser.add_argument("-o", "--out-dir",
1032 dest="out_dir",
1033 help="Dir where generated code and scripts are copied",
1034 metavar="OUT_DIR",
1035 required=True)
1036
1037 args = parser.parse_args()
1038
1039 data_file_name = os.path.basename(args.data_file)
1040 data_name = os.path.splitext(data_file_name)[0]
1041
1042 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001043 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001044
1045 out_c_file_dir = os.path.dirname(out_c_file)
1046 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001047 for directory in [out_c_file_dir, out_data_file_dir]:
1048 if not os.path.exists(directory):
1049 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001050
Azim Khanb31aa442018-07-03 11:57:54 +01001051 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1052 template_file=args.template_file,
1053 platform_file=args.platform_file,
1054 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1055 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001056
1057
1058if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001059 try:
1060 check_cmd()
Azim Khanb31aa442018-07-03 11:57:54 +01001061 except GeneratorInputError as err:
1062 print("%s: input error: %s" %
1063 (os.path.basename(sys.argv[0]), str(err)))