blob: b744d7c07a161c3a8ece4e369413aa1e2df51ee4 [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
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 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>.*)'
187C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
188TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(\w+)\s*\('
189INT_CHECK_REGEX = r'int\s+.*'
190CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
191DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
192FUNCTION_ARG_LIST_START_REGEX = r'.*?\s+(\w+)\s*\('
193FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
194EXIT_LABEL_REGEX = r'^exit:'
195
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100196
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100197class GeneratorInputError(Exception):
198 """
Azim Khane3b26af2018-06-29 02:36:57 +0100199 Exception to indicate error in the input files to this script.
200 This includes missing patterns, test function names and other
201 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100202 """
203 pass
204
205
Azim Khanb31aa442018-07-03 11:57:54 +0100206class FileWrapper(io.FileIO, object):
Azim Khan4b543232017-06-30 09:35:21 +0100207 """
Azim Khane3b26af2018-06-29 02:36:57 +0100208 This class extends built-in io.FileIO class with attribute line_no,
209 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100210 """
211
212 def __init__(self, file_name):
213 """
Azim Khane3b26af2018-06-29 02:36:57 +0100214 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100215
Azim Khanf0e42fb2017-08-02 14:47:13 +0100216 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100217 """
218 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100219 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100220
Azim Khanb31aa442018-07-03 11:57:54 +0100221 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100222 """
Azim Khane3b26af2018-06-29 02:36:57 +0100223 Python 2 iterator method. This method overrides base class's
224 next method and extends the next method to count the line
225 numbers as each line is read.
226
227 It works for both Python 2 and Python 3 by checking iterator
228 method name in the base iterator object.
229
Azim Khanf0e42fb2017-08-02 14:47:13 +0100230 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100231 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200232 parent = super(FileWrapper, self)
233 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100234 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200235 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100236 line = parent.next() # Python 2
237 if line is not None:
238 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100239 # Convert byte array to string with correct encoding and
240 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100241 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100242 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100243
244 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100245 __next__ = next
246
247 def get_line_no(self):
248 """
249 Gives current line number.
250 """
251 return self._line_no
252
253 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100254
255
256def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100257 """
Azim Khanb31aa442018-07-03 11:57:54 +0100258 Split NOT character '!' from dependency. Used by gen_dependencies()
Azim Khanf0e42fb2017-08-02 14:47:13 +0100259
260 :param dep: Dependency list
Azim Khane3b26af2018-06-29 02:36:57 +0100261 :return: string tuple. Ex: ('!', MACRO) for !MACRO and ('', MACRO) for
262 MACRO.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100263 """
Azim Khan4b543232017-06-30 09:35:21 +0100264 return ('!', dep[1:]) if dep[0] == '!' else ('', dep)
265
266
Azim Khanb31aa442018-07-03 11:57:54 +0100267def gen_dependencies(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100268 """
Azim Khane3b26af2018-06-29 02:36:57 +0100269 Test suite data and functions specifies compile time dependencies.
270 This function generates C preprocessor code from the input
271 dependency list. Caller uses the generated preprocessor code to
272 wrap dependent code.
273 A dependency in the input list can have a leading '!' character
274 to negate a condition. '!' is separated from the dependency using
275 function split_dep() and proper preprocessor check is generated
276 accordingly.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100277
Azim Khanb31aa442018-07-03 11:57:54 +0100278 :param dependencies: List of dependencies.
Azim Khan040b6a22018-06-28 16:49:13 +0100279 :return: if defined and endif code with macro annotations for
280 readability.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100281 """
Azim Khanb31aa442018-07-03 11:57:54 +0100282 dep_start = ''.join(['#if %sdefined(%s)\n' % (x, y) for x, y in
283 map(split_dep, dependencies)])
284 dep_end = ''.join(['#endif /* %s */\n' %
285 x for x in reversed(dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100286
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100287 return dep_start, dep_end
288
289
Azim Khanb31aa442018-07-03 11:57:54 +0100290def gen_dependencies_one_line(dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100291 """
Azim Khanb31aa442018-07-03 11:57:54 +0100292 Similar to gen_dependencies() but generates dependency checks in one line.
Azim Khane3b26af2018-06-29 02:36:57 +0100293 Useful for generating code with #else block.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100294
Azim Khanb31aa442018-07-03 11:57:54 +0100295 :param dependencies: List of dependencies.
296 :return: Preprocessor check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100297 """
Azim Khanb31aa442018-07-03 11:57:54 +0100298 defines = '#if ' if dependencies else ''
299 defines += ' && '.join(['%sdefined(%s)' % (x, y) for x, y in map(
300 split_dep, dependencies)])
Azim Khan4b543232017-06-30 09:35:21 +0100301 return defines
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100302
303
Azim Khanb31aa442018-07-03 11:57:54 +0100304def gen_function_wrapper(name, local_vars, args_dispatch):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100305 """
Azim Khan040b6a22018-06-28 16:49:13 +0100306 Creates test function wrapper code. A wrapper has the code to
307 unpack parameters from parameters[] array.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100308
Azim Khanf0e42fb2017-08-02 14:47:13 +0100309 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100310 :param local_vars: Local variables declaration code
Azim Khan040b6a22018-06-28 16:49:13 +0100311 :param args_dispatch: List of dispatch arguments.
312 Ex: ['(char *)params[0]', '*((int *)params[1])']
Azim Khanf0e42fb2017-08-02 14:47:13 +0100313 :return: Test function wrapper.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100314 """
315 # Then create the wrapper
316 wrapper = '''
317void {name}_wrapper( void ** params )
318{{
Gilles Peskine77761412018-06-18 17:51:40 +0200319{unused_params}{locals}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100320 {name}( {args} );
321}}
Gilles Peskine77761412018-06-18 17:51:40 +0200322'''.format(name=name,
Mohammad Azim Khanc3521df2018-06-26 14:06:52 +0100323 unused_params='' if args_dispatch else ' (void)params;\n',
Azim Khan4b543232017-06-30 09:35:21 +0100324 args=', '.join(args_dispatch),
Azim Khanb31aa442018-07-03 11:57:54 +0100325 locals=local_vars)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100326 return wrapper
327
328
Azim Khanb31aa442018-07-03 11:57:54 +0100329def gen_dispatch(name, dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100330 """
Azim Khane3b26af2018-06-29 02:36:57 +0100331 Test suite code template main_test.function defines a C function
332 array to contain test case functions. This function generates an
333 initializer entry for a function in that array. The entry is
334 composed of a compile time check for the test function
335 dependencies. At compile time the test function is assigned when
336 dependencies are met, else NULL is assigned.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100337
Azim Khanf0e42fb2017-08-02 14:47:13 +0100338 :param name: Test function name
Azim Khanb31aa442018-07-03 11:57:54 +0100339 :param dependencies: List of dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100340 :return: Dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100341 """
Azim Khanb31aa442018-07-03 11:57:54 +0100342 if dependencies:
343 preprocessor_check = gen_dependencies_one_line(dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100344 dispatch_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100345{preprocessor_check}
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100346 {name}_wrapper,
347#else
348 NULL,
349#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100350'''.format(preprocessor_check=preprocessor_check, name=name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100351 else:
352 dispatch_code = '''
353 {name}_wrapper,
354'''.format(name=name)
355
356 return dispatch_code
357
358
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000359def parse_until_pattern(funcs_f, end_regex):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100360 """
Azim Khane3b26af2018-06-29 02:36:57 +0100361 Matches pattern end_regex to the lines read from the file object.
362 Returns the lines read until end pattern is matched.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100363
Azim Khan8d686bf2018-07-04 23:29:46 +0100364 :param funcs_f: file object for .function file
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000365 :param end_regex: Pattern to stop parsing
Azim Khane3b26af2018-06-29 02:36:57 +0100366 :return: Lines read before the end pattern
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100367 """
Azim Khan4b543232017-06-30 09:35:21 +0100368 headers = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100369 for line in funcs_f:
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000370 if re.search(end_regex, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100371 break
372 headers += line
373 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100374 raise GeneratorInputError("file: %s - end pattern [%s] not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100375 (funcs_f.name, end_regex))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100376
Azim Khan4b543232017-06-30 09:35:21 +0100377 return headers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100378
379
Azim Khan8d686bf2018-07-04 23:29:46 +0100380def validate_dependency(dependency):
381 """
382 Validates a C macro and raises GeneratorInputError on invalid input.
383 :param dependency: Input macro dependency
384 :return: input dependency stripped of leading & trailing white spaces.
385 """
386 dependency = dependency.strip()
387 if not re.match(C_IDENTIFIER_REGEX, dependency, re.I):
388 raise GeneratorInputError('Invalid dependency %s' % dependency)
389 return dependency
390
391
392def parse_dependencies(inp_str):
393 """
394 Parses dependencies out of inp_str, validates them and returns a
395 list of macros.
396
397 :param inp_str: Input string with macros delimited by ':'.
398 :return: list of dependencies
399 """
400 dependencies = [dep for dep in map(validate_dependency,
401 inp_str.split(':'))]
402 return dependencies
403
404
Azim Khanb31aa442018-07-03 11:57:54 +0100405def parse_suite_dependencies(funcs_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100406 """
Azim Khane3b26af2018-06-29 02:36:57 +0100407 Parses test suite dependencies specified at the top of a
408 .function file, that starts with pattern BEGIN_DEPENDENCIES
409 and end with END_DEPENDENCIES. Dependencies are specified
410 after pattern 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100411
Azim Khan8d686bf2018-07-04 23:29:46 +0100412 :param funcs_f: file object for .function file
Azim Khanf0e42fb2017-08-02 14:47:13 +0100413 :return: List of test suite dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100414 """
Azim Khanb31aa442018-07-03 11:57:54 +0100415 dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100416 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100417 match = re.search(DEPENDENCY_REGEX, line.strip())
Azim Khanb31aa442018-07-03 11:57:54 +0100418 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100419 try:
420 dependencies = parse_dependencies(match.group('dependencies'))
421 except GeneratorInputError as error:
422 raise GeneratorInputError(
423 str(error) + " - %s:%d" % (funcs_f.name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100424 if re.search(END_DEP_REGEX, line):
425 break
426 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100427 raise GeneratorInputError("file: %s - end dependency pattern [%s]"
Azim Khanb31aa442018-07-03 11:57:54 +0100428 " not found!" % (funcs_f.name,
429 END_DEP_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100430
Azim Khanb31aa442018-07-03 11:57:54 +0100431 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100432
433
Azim Khanb31aa442018-07-03 11:57:54 +0100434def parse_function_dependencies(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100435 """
Azim Khane3b26af2018-06-29 02:36:57 +0100436 Parses function dependencies, that are in the same line as
437 comment BEGIN_CASE. Dependencies are specified after pattern
438 'depends_on:' and are delimited by ':'.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100439
Azim Khan8d686bf2018-07-04 23:29:46 +0100440 :param line: Line from .function file that has dependencies.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100441 :return: List of dependencies.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100442 """
Azim Khanb31aa442018-07-03 11:57:54 +0100443 dependencies = []
444 match = re.search(BEGIN_CASE_REGEX, line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100445 dep_str = match.group('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100446 if dep_str:
Azim Khan8d686bf2018-07-04 23:29:46 +0100447 match = re.search(DEPENDENCY_REGEX, dep_str)
Azim Khanb31aa442018-07-03 11:57:54 +0100448 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100449 dependencies += parse_dependencies(match.group('dependencies'))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100450
Azim Khan8d686bf2018-07-04 23:29:46 +0100451 return dependencies
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100452
Azim Khan4084ec72018-07-05 14:20:08 +0100453
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100454def parse_function_signature(line):
455 """
Azim Khane3b26af2018-06-29 02:36:57 +0100456 Parses test function signature for validation and generates
457 a dispatch wrapper function that translates input test vectors
458 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100459
Azim Khan8d686bf2018-07-04 23:29:46 +0100460 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100461 signature.
462 :return: function name, argument list, local variables for
463 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100464 """
465 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100466 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100467 args_dispatch = []
Azim Khane3b26af2018-06-29 02:36:57 +0100468 # Check if the test function returns void.
Azim Khan8d686bf2018-07-04 23:29:46 +0100469 match = re.search(TEST_FUNCTION_VALIDATION_REGEX, line, re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100470 if not match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100471 raise ValueError("Test function should return 'void'\n%s" % line)
Azim Khanb31aa442018-07-03 11:57:54 +0100472 name = match.group(1)
473 line = line[len(match.group(0)):]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100474 arg_idx = 0
Azim Khan8d686bf2018-07-04 23:29:46 +0100475 # Process arguments, ex: <type> arg1, <type> arg2 )
476 # This script assumes that the argument list is terminated by ')'
477 # i.e. the test functions will not have a function pointer
478 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100479 for arg in line[:line.find(')')].split(','):
480 arg = arg.strip()
481 if arg == '':
482 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100483 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100484 args.append('int')
485 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100486 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100487 args.append('char*')
488 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100489 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100490 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100491 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100492 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
493 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100494 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100495""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100496
Azim Khan5fcca462018-06-29 11:05:32 +0100497 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100498 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100499 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100500 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100501 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100502 arg_idx += 1
503
Azim Khanb31aa442018-07-03 11:57:54 +0100504 return name, args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100505
506
Azim Khanb31aa442018-07-03 11:57:54 +0100507def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100508 """
Azim Khan040b6a22018-06-28 16:49:13 +0100509 Parses out a function from function file object and generates
510 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100511
Azim Khanf0e42fb2017-08-02 14:47:13 +0100512 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100513 :param dependencies: List of dependencies
514 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100515 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100516 """
Azim Khan4b543232017-06-30 09:35:21 +0100517 code = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Azim Khan8d686bf2018-07-04 23:29:46 +0100518 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100519 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100520 # Check function signature. This script expects function name
521 # and return type to be specified at the same line.
522 match = re.match(FUNCTION_ARG_LIST_START_REGEX, line, re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100523 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100524 # check if we have full signature i.e. split in more lines
Azim Khan8d686bf2018-07-04 23:29:46 +0100525 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100526 for lin in funcs_f:
527 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100528 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100529 break
Azim Khanb31aa442018-07-03 11:57:54 +0100530 name, args, local_vars, args_dispatch = parse_function_signature(
531 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100532 code += line.replace(name, 'test_' + name, 1)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100533 name = 'test_' + name
534 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100535 else:
536 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100537 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100538 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100539 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100540
541 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100542 if re.search(END_CASE_REGEX, line):
543 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100544 if not has_exit_label:
545 has_exit_label = \
546 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100547 code += line
548 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100549 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100550 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100551
552 # Add exit label if not present
553 if code.find('exit:') == -1:
Azim Khanb31aa442018-07-03 11:57:54 +0100554 split_code = code.rsplit('}', 1)
555 if len(split_code) == 2:
Azim Khan4b543232017-06-30 09:35:21 +0100556 code = """exit:
Azim Khan8d686bf2018-07-04 23:29:46 +0100557 ;
Azim Khanb31aa442018-07-03 11:57:54 +0100558}""".join(split_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100559
Azim Khanb31aa442018-07-03 11:57:54 +0100560 code += gen_function_wrapper(name, local_vars, args_dispatch)
561 preprocessor_check_start, preprocessor_check_end = \
562 gen_dependencies(dependencies)
563 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
564 return (name, args, preprocessor_check_start + code +
565 preprocessor_check_end, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100566
567
568def parse_functions(funcs_f):
569 """
Azim Khane3b26af2018-06-29 02:36:57 +0100570 Parses a test_suite_xxx.function file and returns information
571 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100572
Azim Khanf0e42fb2017-08-02 14:47:13 +0100573 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100574 :return: List of test suite dependencies, test function dispatch
575 code, function code and a dict with function identifiers
576 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100577 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000578 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100579 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100580 suite_functions = ''
581 func_info = {}
582 function_idx = 0
583 dispatch_code = ''
584 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100585 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000586 headers = parse_until_pattern(funcs_f, END_HEADER_REGEX)
Azim Khanb31aa442018-07-03 11:57:54 +0100587 suite_helpers += headers
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000588 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
589 helpers = parse_until_pattern(funcs_f, END_SUITE_HELPERS_REGEX)
590 suite_helpers += helpers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100591 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100592 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100593 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100594 try:
595 dependencies = parse_function_dependencies(line)
596 except GeneratorInputError as error:
597 raise GeneratorInputError(
598 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
599 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100600 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100601 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100602 suite_functions += func_code
603 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100604 if func_name in func_info:
605 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100606 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100607 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100608 func_info[func_name] = (function_idx, args)
609 dispatch_code += '/* Function Id: %d */\n' % function_idx
610 dispatch_code += func_dispatch
611 function_idx += 1
612
Azim Khanb31aa442018-07-03 11:57:54 +0100613 func_code = (suite_helpers +
614 suite_functions).join(gen_dependencies(suite_dependencies))
615 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100616
617
Azim Khanb31aa442018-07-03 11:57:54 +0100618def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100619 """
Azim Khanb31aa442018-07-03 11:57:54 +0100620 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100621 Since, return value is used to write back to the intermediate
622 data file, any escape characters in the input are retained in the
623 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100624
Azim Khanb31aa442018-07-03 11:57:54 +0100625 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100626 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100627 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100628 """
Azim Khanb31aa442018-07-03 11:57:54 +0100629 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100630 raise ValueError('Expected split character. Found string!')
631 out = []
632 part = ''
633 escape = False
Azim Khanb31aa442018-07-03 11:57:54 +0100634 for character in inp_str:
635 if not escape and character == split_char:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100636 out.append(part)
637 part = ''
638 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100639 part += character
640 escape = not escape and character == '\\'
641 if part:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100642 out.append(part)
643 return out
644
645
Azim Khanb31aa442018-07-03 11:57:54 +0100646def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100647 """
Azim Khane3b26af2018-06-29 02:36:57 +0100648 Parses .data file for each test case name, test function name,
649 test dependencies and test arguments. This information is
650 correlated with the test functions file for generating an
651 intermediate data file replacing the strings for test function
652 names, dependencies and integer constant expressions with
653 identifiers. Mainly for optimising space for on-target
654 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100655
Azim Khanf0e42fb2017-08-02 14:47:13 +0100656 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100657 :return: Generator that yields test name, function name,
658 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100659 """
Azim Khanb31aa442018-07-03 11:57:54 +0100660 __state_read_name = 0
661 __state_read_args = 1
662 state = __state_read_name
663 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100664 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100665 for line in data_f:
666 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100667 # Skip comments
668 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100669 continue
670
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100671 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100672 if not line:
673 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100674 raise GeneratorInputError("[%s:%d] Newline before arguments. "
675 "Test function and arguments "
676 "missing for %s" %
677 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100678 continue
679
Azim Khanb31aa442018-07-03 11:57:54 +0100680 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100681 # Read test name
682 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100683 state = __state_read_args
684 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100685 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100686 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100687 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100688 try:
689 dependencies = parse_dependencies(
690 match.group('dependencies'))
691 except GeneratorInputError as error:
692 raise GeneratorInputError(
693 str(error) + " - %s:%d" %
694 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100695 else:
696 # Read test vectors
697 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100698 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100699 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100700 yield name, test_function, dependencies, args
701 dependencies = []
702 state = __state_read_name
703 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100704 raise GeneratorInputError("[%s:%d] Newline before arguments. "
705 "Test function and arguments missing for "
706 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100707
708
709def gen_dep_check(dep_id, dep):
710 """
Azim Khane3b26af2018-06-29 02:36:57 +0100711 Generate code for checking dependency with the associated
712 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100713
Azim Khanf0e42fb2017-08-02 14:47:13 +0100714 :param dep_id: Dependency identifier
715 :param dep: Dependency macro
716 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100717 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100718 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100719 raise GeneratorInputError("Dependency Id should be a positive "
720 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100721 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
722 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100723 raise GeneratorInputError("Dependency should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100724 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100725 case {id}:
726 {{
Azim Khanb31aa442018-07-03 11:57:54 +0100727#if {_not}defined({macro})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100728 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100729#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100730 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100731#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100732 }}
Azim Khanb31aa442018-07-03 11:57:54 +0100733 break;'''.format(_not=_not, macro=dep, id=dep_id)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100734 return dep_check
735
736
737def gen_expression_check(exp_id, exp):
738 """
Azim Khane3b26af2018-06-29 02:36:57 +0100739 Generates code for evaluating an integer expression using
740 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100741
Azim Khanf0e42fb2017-08-02 14:47:13 +0100742 :param exp_id: Expression Identifier
743 :param exp: Expression/Macro
744 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100745 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100746 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100747 raise GeneratorInputError("Expression Id should be a positive "
748 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100749 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100750 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100751 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100752 case {exp_id}:
753 {{
754 *out_value = {expression};
755 }}
756 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100757 return exp_code
758
759
Azim Khanb31aa442018-07-03 11:57:54 +0100760def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100761 """
Azim Khane3b26af2018-06-29 02:36:57 +0100762 Write dependencies to intermediate test data file, replacing
763 the string form with identifiers. Also, generates dependency
764 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100765
Azim Khanf0e42fb2017-08-02 14:47:13 +0100766 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100767 :param test_dependencies: Dependencies
768 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100769 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100770 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100771 """
Azim Khan599cd242017-07-06 17:34:27 +0100772 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100773 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100774 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100775 for dep in test_dependencies:
776 if dep not in unique_dependencies:
777 unique_dependencies.append(dep)
778 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100779 dep_check_code += gen_dep_check(dep_id, dep)
780 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100781 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100782 out_data_f.write(':' + str(dep_id))
783 out_data_f.write('\n')
784 return dep_check_code
785
786
787def write_parameters(out_data_f, test_args, func_args, unique_expressions):
788 """
Azim Khane3b26af2018-06-29 02:36:57 +0100789 Writes test parameters to the intermediate data file, replacing
790 the string form with identifiers. Also, generates expression
791 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100792
Azim Khanf0e42fb2017-08-02 14:47:13 +0100793 :param out_data_f: Output intermediate data file
794 :param test_args: Test parameters
795 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100796 :param unique_expressions: Mutable list to track unique
797 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100798 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100799 """
800 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100801 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100802 typ = func_args[i]
803 val = test_args[i]
804
Azim Khan040b6a22018-06-28 16:49:13 +0100805 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100806 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
807 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100808 typ = 'exp'
809 if val not in unique_expressions:
810 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100811 # exp_id can be derived from len(). But for
812 # readability and consistency with case of existing
813 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100814 exp_id = unique_expressions.index(val)
815 expression_code += gen_expression_check(exp_id, val)
816 val = exp_id
817 else:
818 val = unique_expressions.index(val)
819 out_data_f.write(':' + typ + ':' + str(val))
820 out_data_f.write('\n')
821 return expression_code
822
823
Azim Khanb31aa442018-07-03 11:57:54 +0100824def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100825 """
Azim Khane3b26af2018-06-29 02:36:57 +0100826 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100827
Azim Khanb31aa442018-07-03 11:57:54 +0100828 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100829 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100830 :param dep_check_code: Dependency check code
831 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100832 :return: Dependency and expression code guarded by test suite
833 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100834 """
Azim Khanb31aa442018-07-03 11:57:54 +0100835 if suite_dependencies:
836 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100837 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100838{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100839{code}
Azim Khan599cd242017-07-06 17:34:27 +0100840#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100841'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100842 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100843{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100844{code}
Azim Khan599cd242017-07-06 17:34:27 +0100845#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100846'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100847 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100848
849
Azim Khanb31aa442018-07-03 11:57:54 +0100850def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100851 """
Azim Khane3b26af2018-06-29 02:36:57 +0100852 This function reads test case name, dependencies and test vectors
853 from the .data file. This information is correlated with the test
854 functions file for generating an intermediate data file replacing
855 the strings for test function names, dependencies and integer
856 constant expressions with identifiers. Mainly for optimising
857 space for on-target execution.
858 It also generates test case dependency check code and expression
859 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100860
Azim Khanf0e42fb2017-08-02 14:47:13 +0100861 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100862 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100863 :param func_info: Dict keyed by function and with function id
864 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100865 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100866 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100867 """
Azim Khanb31aa442018-07-03 11:57:54 +0100868 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100869 unique_expressions = []
870 dep_check_code = ''
871 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100872 for test_name, function_name, test_dependencies, test_args in \
873 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100874 out_data_f.write(test_name + '\n')
875
Azim Khanb31aa442018-07-03 11:57:54 +0100876 # Write dependencies
877 dep_check_code += write_dependencies(out_data_f, test_dependencies,
878 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100879
Azim Khan599cd242017-07-06 17:34:27 +0100880 # Write test function name
881 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100882 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100883 raise GeneratorInputError("Function %s not found!" %
884 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100885 func_id, func_args = func_info[test_function_name]
886 out_data_f.write(str(func_id))
887
888 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100889 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100890 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100891 "%s. See function %s signature." %
892 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100893 expression_code += write_parameters(out_data_f, test_args, func_args,
894 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100895
Azim Khan599cd242017-07-06 17:34:27 +0100896 # Write a newline as test case separator
897 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100898
Azim Khanb31aa442018-07-03 11:57:54 +0100899 dep_check_code, expression_code = gen_suite_dep_checks(
900 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100901 return dep_check_code, expression_code
902
903
Azim Khanb31aa442018-07-03 11:57:54 +0100904def add_input_info(funcs_file, data_file, template_file,
905 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100906 """
Azim Khanb31aa442018-07-03 11:57:54 +0100907 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100908
Azim Khanf0e42fb2017-08-02 14:47:13 +0100909 :param funcs_file: Functions file object
910 :param data_file: Data file object
911 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100912 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100913 :param snippets: Dictionary to contain code pieces to be
914 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100915 :return:
916 """
Azim Khanb31aa442018-07-03 11:57:54 +0100917 snippets['test_file'] = c_file
918 snippets['test_main_file'] = template_file
919 snippets['test_case_file'] = funcs_file
920 snippets['test_case_data_file'] = data_file
921
922
923def read_code_from_input_files(platform_file, helpers_file,
924 out_data_file, snippets):
925 """
926 Read code from input files and create substitutions for replacement
927 strings in the template file.
928
929 :param platform_file: Platform file object
930 :param helpers_file: Helper functions file object
931 :param out_data_file: Output intermediate data file object
932 :param snippets: Dictionary to contain code pieces to be
933 substituted in the template.
934 :return:
935 """
936 # Read helpers
937 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
938 platform_f:
939 snippets['test_common_helper_file'] = helpers_file
940 snippets['test_common_helpers'] = help_f.read()
941 snippets['test_platform_file'] = platform_file
942 snippets['platform_code'] = platform_f.read().replace(
943 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
944
945
946def write_test_source_file(template_file, c_file, snippets):
947 """
948 Write output source file with generated source code.
949
950 :param template_file: Template file name
951 :param c_file: Output source file
952 :param snippets: Generated and code snippets
953 :return:
954 """
955 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
956 line_no = 1
957 for line in template_f.readlines():
958 # Update line number. +1 as #line directive sets next line number
959 snippets['line_no'] = line_no + 1
960 code = line.format(**snippets)
961 c_f.write(code)
962 line_no += 1
963
964
965def parse_function_file(funcs_file, snippets):
966 """
967 Parse function file and generate function dispatch code.
968
969 :param funcs_file: Functions file name
970 :param snippets: Dictionary to contain code pieces to be
971 substituted in the template.
972 :return:
973 """
974 with FileWrapper(funcs_file) as funcs_f:
975 suite_dependencies, dispatch_code, func_code, func_info = \
976 parse_functions(funcs_f)
977 snippets['functions_code'] = func_code
978 snippets['dispatch_code'] = dispatch_code
979 return suite_dependencies, func_info
980
981
982def generate_intermediate_data_file(data_file, out_data_file,
983 suite_dependencies, func_info, snippets):
984 """
985 Generates intermediate data file from input data file and
986 information read from functions file.
987
988 :param data_file: Data file name
989 :param out_data_file: Output/Intermediate data file
990 :param suite_dependencies: List of suite dependencies.
991 :param func_info: Function info parsed from functions file.
992 :param snippets: Dictionary to contain code pieces to be
993 substituted in the template.
994 :return:
995 """
996 with FileWrapper(data_file) as data_f, \
997 open(out_data_file, 'w') as out_data_f:
998 dep_check_code, expression_code = gen_from_test_data(
999 data_f, out_data_f, func_info, suite_dependencies)
1000 snippets['dep_check_code'] = dep_check_code
1001 snippets['expression_code'] = expression_code
1002
1003
1004def generate_code(**input_info):
1005 """
1006 Generates C source code from test suite file, data file, common
1007 helpers file and platform file.
1008
1009 input_info expands to following parameters:
1010 funcs_file: Functions file object
1011 data_file: Data file object
1012 template_file: Template file object
1013 platform_file: Platform file object
1014 helpers_file: Helper functions file object
1015 suites_dir: Test suites dir
1016 c_file: Output C file object
1017 out_data_file: Output intermediate data file object
1018 :return:
1019 """
1020 funcs_file = input_info['funcs_file']
1021 data_file = input_info['data_file']
1022 template_file = input_info['template_file']
1023 platform_file = input_info['platform_file']
1024 helpers_file = input_info['helpers_file']
1025 suites_dir = input_info['suites_dir']
1026 c_file = input_info['c_file']
1027 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001028 for name, path in [('Functions file', funcs_file),
1029 ('Data file', data_file),
1030 ('Template file', template_file),
1031 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001032 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001033 ('Suites dir', suites_dir)]:
1034 if not os.path.exists(path):
1035 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1036
Azim Khanb31aa442018-07-03 11:57:54 +01001037 snippets = {'generator_script': os.path.basename(__file__)}
1038 read_code_from_input_files(platform_file, helpers_file,
1039 out_data_file, snippets)
1040 add_input_info(funcs_file, data_file, template_file,
1041 c_file, snippets)
1042 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1043 generate_intermediate_data_file(data_file, out_data_file,
1044 suite_dependencies, func_info, snippets)
1045 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001046
1047
Azim Khan8d686bf2018-07-04 23:29:46 +01001048def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001049 """
1050 Command line parser.
1051
1052 :return:
1053 """
Azim Khan040b6a22018-06-28 16:49:13 +01001054 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001055 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001056
1057 parser.add_argument("-f", "--functions-file",
1058 dest="funcs_file",
1059 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001060 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001061 required=True)
1062
1063 parser.add_argument("-d", "--data-file",
1064 dest="data_file",
1065 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001066 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001067 required=True)
1068
1069 parser.add_argument("-t", "--template-file",
1070 dest="template_file",
1071 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001072 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001073 required=True)
1074
1075 parser.add_argument("-s", "--suites-dir",
1076 dest="suites_dir",
1077 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001078 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001079 required=True)
1080
Azim Khane3b26af2018-06-29 02:36:57 +01001081 parser.add_argument("--helpers-file",
1082 dest="helpers_file",
1083 help="Helpers file",
1084 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001085 required=True)
1086
1087 parser.add_argument("-p", "--platform-file",
1088 dest="platform_file",
1089 help="Platform code file",
1090 metavar="PLATFORM_FILE",
1091 required=True)
1092
1093 parser.add_argument("-o", "--out-dir",
1094 dest="out_dir",
1095 help="Dir where generated code and scripts are copied",
1096 metavar="OUT_DIR",
1097 required=True)
1098
1099 args = parser.parse_args()
1100
1101 data_file_name = os.path.basename(args.data_file)
1102 data_name = os.path.splitext(data_file_name)[0]
1103
1104 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001105 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001106
1107 out_c_file_dir = os.path.dirname(out_c_file)
1108 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001109 for directory in [out_c_file_dir, out_data_file_dir]:
1110 if not os.path.exists(directory):
1111 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001112
Azim Khanb31aa442018-07-03 11:57:54 +01001113 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1114 template_file=args.template_file,
1115 platform_file=args.platform_file,
1116 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1117 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001118
1119
1120if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001121 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001122 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001123 except GeneratorInputError as err:
1124 print("%s: input error: %s" %
1125 (os.path.basename(sys.argv[0]), str(err)))