blob: 036ed1c02e0255597eac80213ac4608cf187b091 [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
453def parse_function_signature(line):
454 """
Azim Khane3b26af2018-06-29 02:36:57 +0100455 Parses test function signature for validation and generates
456 a dispatch wrapper function that translates input test vectors
457 read from the data file into test function arguments.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100458
Azim Khan8d686bf2018-07-04 23:29:46 +0100459 :param line: Line from .function file that has a function
Azim Khan040b6a22018-06-28 16:49:13 +0100460 signature.
461 :return: function name, argument list, local variables for
462 wrapper function and argument dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100463 """
464 args = []
Azim Khanb31aa442018-07-03 11:57:54 +0100465 local_vars = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100466 args_dispatch = []
Azim Khane3b26af2018-06-29 02:36:57 +0100467 # Check if the test function returns void.
Azim Khan8d686bf2018-07-04 23:29:46 +0100468 match = re.search(TEST_FUNCTION_VALIDATION_REGEX, line, re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100469 if not match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100470 raise ValueError("Test function should return 'void'\n%s" % line)
Azim Khanb31aa442018-07-03 11:57:54 +0100471 name = match.group(1)
472 line = line[len(match.group(0)):]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100473 arg_idx = 0
Azim Khan8d686bf2018-07-04 23:29:46 +0100474 # Process arguments, ex: <type> arg1, <type> arg2 )
475 # This script assumes that the argument list is terminated by ')'
476 # i.e. the test functions will not have a function pointer
477 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100478 for arg in line[:line.find(')')].split(','):
479 arg = arg.strip()
480 if arg == '':
481 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100482 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100483 args.append('int')
484 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100485 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100486 args.append('char*')
487 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100488 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100489 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100490 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100491 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
492 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100493 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100494""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100495
Azim Khan5fcca462018-06-29 11:05:32 +0100496 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100497 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100498 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100499 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100500 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100501 arg_idx += 1
502
Azim Khanb31aa442018-07-03 11:57:54 +0100503 return name, args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100504
505
Azim Khanb31aa442018-07-03 11:57:54 +0100506def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100507 """
Azim Khan040b6a22018-06-28 16:49:13 +0100508 Parses out a function from function file object and generates
509 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100510
Azim Khanf0e42fb2017-08-02 14:47:13 +0100511 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100512 :param dependencies: List of dependencies
513 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100514 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100515 """
Azim Khan4b543232017-06-30 09:35:21 +0100516 code = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
Azim Khan8d686bf2018-07-04 23:29:46 +0100517 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100518 for line in funcs_f:
Azim Khan8d686bf2018-07-04 23:29:46 +0100519 # Check function signature. This script expects function name
520 # and return type to be specified at the same line.
521 match = re.match(FUNCTION_ARG_LIST_START_REGEX, line, re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100522 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100523 # check if we have full signature i.e. split in more lines
Azim Khan8d686bf2018-07-04 23:29:46 +0100524 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100525 for lin in funcs_f:
526 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100527 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100528 break
Azim Khanb31aa442018-07-03 11:57:54 +0100529 name, args, local_vars, args_dispatch = parse_function_signature(
530 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100531 code += line.replace(name, 'test_' + name, 1)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100532 name = 'test_' + name
533 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100534 else:
535 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100536 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100537 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100538 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100539
540 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100541 if re.search(END_CASE_REGEX, line):
542 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100543 if not has_exit_label:
544 has_exit_label = \
545 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100546 code += line
547 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100548 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100549 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100550
551 # Add exit label if not present
552 if code.find('exit:') == -1:
Azim Khanb31aa442018-07-03 11:57:54 +0100553 split_code = code.rsplit('}', 1)
554 if len(split_code) == 2:
Azim Khan4b543232017-06-30 09:35:21 +0100555 code = """exit:
Azim Khan8d686bf2018-07-04 23:29:46 +0100556 ;
Azim Khanb31aa442018-07-03 11:57:54 +0100557}""".join(split_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100558
Azim Khanb31aa442018-07-03 11:57:54 +0100559 code += gen_function_wrapper(name, local_vars, args_dispatch)
560 preprocessor_check_start, preprocessor_check_end = \
561 gen_dependencies(dependencies)
562 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
563 return (name, args, preprocessor_check_start + code +
564 preprocessor_check_end, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100565
566
567def parse_functions(funcs_f):
568 """
Azim Khane3b26af2018-06-29 02:36:57 +0100569 Parses a test_suite_xxx.function file and returns information
570 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100571
Azim Khanf0e42fb2017-08-02 14:47:13 +0100572 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100573 :return: List of test suite dependencies, test function dispatch
574 code, function code and a dict with function identifiers
575 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100576 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000577 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100578 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100579 suite_functions = ''
580 func_info = {}
581 function_idx = 0
582 dispatch_code = ''
583 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100584 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000585 headers = parse_until_pattern(funcs_f, END_HEADER_REGEX)
Azim Khanb31aa442018-07-03 11:57:54 +0100586 suite_helpers += headers
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000587 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
588 helpers = parse_until_pattern(funcs_f, END_SUITE_HELPERS_REGEX)
589 suite_helpers += helpers
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100590 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100591 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100592 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100593 try:
594 dependencies = parse_function_dependencies(line)
595 except GeneratorInputError as error:
596 raise GeneratorInputError(
597 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
598 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100599 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100600 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100601 suite_functions += func_code
602 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100603 if func_name in func_info:
604 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100605 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100606 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100607 func_info[func_name] = (function_idx, args)
608 dispatch_code += '/* Function Id: %d */\n' % function_idx
609 dispatch_code += func_dispatch
610 function_idx += 1
611
Azim Khanb31aa442018-07-03 11:57:54 +0100612 func_code = (suite_helpers +
613 suite_functions).join(gen_dependencies(suite_dependencies))
614 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100615
616
Azim Khanb31aa442018-07-03 11:57:54 +0100617def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100618 """
Azim Khanb31aa442018-07-03 11:57:54 +0100619 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100620 Since, return value is used to write back to the intermediate
621 data file, any escape characters in the input are retained in the
622 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100623
Azim Khanb31aa442018-07-03 11:57:54 +0100624 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100625 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100626 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100627 """
Azim Khanb31aa442018-07-03 11:57:54 +0100628 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100629 raise ValueError('Expected split character. Found string!')
630 out = []
631 part = ''
632 escape = False
Azim Khanb31aa442018-07-03 11:57:54 +0100633 for character in inp_str:
634 if not escape and character == split_char:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100635 out.append(part)
636 part = ''
637 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100638 part += character
639 escape = not escape and character == '\\'
640 if part:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100641 out.append(part)
642 return out
643
644
Azim Khanb31aa442018-07-03 11:57:54 +0100645def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100646 """
Azim Khane3b26af2018-06-29 02:36:57 +0100647 Parses .data file for each test case name, test function name,
648 test dependencies and test arguments. This information is
649 correlated with the test functions file for generating an
650 intermediate data file replacing the strings for test function
651 names, dependencies and integer constant expressions with
652 identifiers. Mainly for optimising space for on-target
653 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100654
Azim Khanf0e42fb2017-08-02 14:47:13 +0100655 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100656 :return: Generator that yields test name, function name,
657 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100658 """
Azim Khanb31aa442018-07-03 11:57:54 +0100659 __state_read_name = 0
660 __state_read_args = 1
661 state = __state_read_name
662 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100663 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100664 for line in data_f:
665 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100666 # Skip comments
667 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100668 continue
669
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100670 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100671 if not line:
672 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100673 raise GeneratorInputError("[%s:%d] Newline before arguments. "
674 "Test function and arguments "
675 "missing for %s" %
676 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100677 continue
678
Azim Khanb31aa442018-07-03 11:57:54 +0100679 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100680 # Read test name
681 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100682 state = __state_read_args
683 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100684 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100685 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100686 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100687 try:
688 dependencies = parse_dependencies(
689 match.group('dependencies'))
690 except GeneratorInputError as error:
691 raise GeneratorInputError(
692 str(error) + " - %s:%d" %
693 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100694 else:
695 # Read test vectors
696 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100697 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100698 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100699 yield name, test_function, dependencies, args
700 dependencies = []
701 state = __state_read_name
702 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100703 raise GeneratorInputError("[%s:%d] Newline before arguments. "
704 "Test function and arguments missing for "
705 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100706
707
708def gen_dep_check(dep_id, dep):
709 """
Azim Khane3b26af2018-06-29 02:36:57 +0100710 Generate code for checking dependency with the associated
711 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100712
Azim Khanf0e42fb2017-08-02 14:47:13 +0100713 :param dep_id: Dependency identifier
714 :param dep: Dependency macro
715 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100716 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100717 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100718 raise GeneratorInputError("Dependency Id should be a positive "
719 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100720 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
721 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100722 raise GeneratorInputError("Dependency should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100723 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100724 case {id}:
725 {{
Azim Khanb31aa442018-07-03 11:57:54 +0100726#if {_not}defined({macro})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100727 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100728#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100729 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100730#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100731 }}
Azim Khanb31aa442018-07-03 11:57:54 +0100732 break;'''.format(_not=_not, macro=dep, id=dep_id)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100733 return dep_check
734
735
736def gen_expression_check(exp_id, exp):
737 """
Azim Khane3b26af2018-06-29 02:36:57 +0100738 Generates code for evaluating an integer expression using
739 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100740
Azim Khanf0e42fb2017-08-02 14:47:13 +0100741 :param exp_id: Expression Identifier
742 :param exp: Expression/Macro
743 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100744 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100745 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100746 raise GeneratorInputError("Expression Id should be a positive "
747 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100748 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100749 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100750 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100751 case {exp_id}:
752 {{
753 *out_value = {expression};
754 }}
755 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100756 return exp_code
757
758
Azim Khanb31aa442018-07-03 11:57:54 +0100759def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100760 """
Azim Khane3b26af2018-06-29 02:36:57 +0100761 Write dependencies to intermediate test data file, replacing
762 the string form with identifiers. Also, generates dependency
763 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100764
Azim Khanf0e42fb2017-08-02 14:47:13 +0100765 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100766 :param test_dependencies: Dependencies
767 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100768 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100769 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100770 """
Azim Khan599cd242017-07-06 17:34:27 +0100771 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100772 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100773 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100774 for dep in test_dependencies:
775 if dep not in unique_dependencies:
776 unique_dependencies.append(dep)
777 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100778 dep_check_code += gen_dep_check(dep_id, dep)
779 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100780 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100781 out_data_f.write(':' + str(dep_id))
782 out_data_f.write('\n')
783 return dep_check_code
784
785
786def write_parameters(out_data_f, test_args, func_args, unique_expressions):
787 """
Azim Khane3b26af2018-06-29 02:36:57 +0100788 Writes test parameters to the intermediate data file, replacing
789 the string form with identifiers. Also, generates expression
790 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100791
Azim Khanf0e42fb2017-08-02 14:47:13 +0100792 :param out_data_f: Output intermediate data file
793 :param test_args: Test parameters
794 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100795 :param unique_expressions: Mutable list to track unique
796 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100797 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100798 """
799 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100800 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100801 typ = func_args[i]
802 val = test_args[i]
803
Azim Khan040b6a22018-06-28 16:49:13 +0100804 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100805 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
806 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100807 typ = 'exp'
808 if val not in unique_expressions:
809 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100810 # exp_id can be derived from len(). But for
811 # readability and consistency with case of existing
812 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100813 exp_id = unique_expressions.index(val)
814 expression_code += gen_expression_check(exp_id, val)
815 val = exp_id
816 else:
817 val = unique_expressions.index(val)
818 out_data_f.write(':' + typ + ':' + str(val))
819 out_data_f.write('\n')
820 return expression_code
821
822
Azim Khanb31aa442018-07-03 11:57:54 +0100823def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100824 """
Azim Khane3b26af2018-06-29 02:36:57 +0100825 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100826
Azim Khanb31aa442018-07-03 11:57:54 +0100827 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100828 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100829 :param dep_check_code: Dependency check code
830 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100831 :return: Dependency and expression code guarded by test suite
832 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100833 """
Azim Khanb31aa442018-07-03 11:57:54 +0100834 if suite_dependencies:
835 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100836 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100837{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100838{code}
Azim Khan599cd242017-07-06 17:34:27 +0100839#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100840'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100841 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100842{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100843{code}
Azim Khan599cd242017-07-06 17:34:27 +0100844#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100845'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100846 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100847
848
Azim Khanb31aa442018-07-03 11:57:54 +0100849def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100850 """
Azim Khane3b26af2018-06-29 02:36:57 +0100851 This function reads test case name, dependencies and test vectors
852 from the .data file. This information is correlated with the test
853 functions file for generating an intermediate data file replacing
854 the strings for test function names, dependencies and integer
855 constant expressions with identifiers. Mainly for optimising
856 space for on-target execution.
857 It also generates test case dependency check code and expression
858 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100859
Azim Khanf0e42fb2017-08-02 14:47:13 +0100860 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100861 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100862 :param func_info: Dict keyed by function and with function id
863 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100864 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100865 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100866 """
Azim Khanb31aa442018-07-03 11:57:54 +0100867 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100868 unique_expressions = []
869 dep_check_code = ''
870 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100871 for test_name, function_name, test_dependencies, test_args in \
872 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100873 out_data_f.write(test_name + '\n')
874
Azim Khanb31aa442018-07-03 11:57:54 +0100875 # Write dependencies
876 dep_check_code += write_dependencies(out_data_f, test_dependencies,
877 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100878
Azim Khan599cd242017-07-06 17:34:27 +0100879 # Write test function name
880 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100881 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100882 raise GeneratorInputError("Function %s not found!" %
883 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100884 func_id, func_args = func_info[test_function_name]
885 out_data_f.write(str(func_id))
886
887 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100888 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100889 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100890 "%s. See function %s signature." %
891 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100892 expression_code += write_parameters(out_data_f, test_args, func_args,
893 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100894
Azim Khan599cd242017-07-06 17:34:27 +0100895 # Write a newline as test case separator
896 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100897
Azim Khanb31aa442018-07-03 11:57:54 +0100898 dep_check_code, expression_code = gen_suite_dep_checks(
899 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100900 return dep_check_code, expression_code
901
902
Azim Khanb31aa442018-07-03 11:57:54 +0100903def add_input_info(funcs_file, data_file, template_file,
904 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100905 """
Azim Khanb31aa442018-07-03 11:57:54 +0100906 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100907
Azim Khanf0e42fb2017-08-02 14:47:13 +0100908 :param funcs_file: Functions file object
909 :param data_file: Data file object
910 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100911 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100912 :param snippets: Dictionary to contain code pieces to be
913 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100914 :return:
915 """
Azim Khanb31aa442018-07-03 11:57:54 +0100916 snippets['test_file'] = c_file
917 snippets['test_main_file'] = template_file
918 snippets['test_case_file'] = funcs_file
919 snippets['test_case_data_file'] = data_file
920
921
922def read_code_from_input_files(platform_file, helpers_file,
923 out_data_file, snippets):
924 """
925 Read code from input files and create substitutions for replacement
926 strings in the template file.
927
928 :param platform_file: Platform file object
929 :param helpers_file: Helper functions file object
930 :param out_data_file: Output intermediate data file object
931 :param snippets: Dictionary to contain code pieces to be
932 substituted in the template.
933 :return:
934 """
935 # Read helpers
936 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
937 platform_f:
938 snippets['test_common_helper_file'] = helpers_file
939 snippets['test_common_helpers'] = help_f.read()
940 snippets['test_platform_file'] = platform_file
941 snippets['platform_code'] = platform_f.read().replace(
942 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
943
944
945def write_test_source_file(template_file, c_file, snippets):
946 """
947 Write output source file with generated source code.
948
949 :param template_file: Template file name
950 :param c_file: Output source file
951 :param snippets: Generated and code snippets
952 :return:
953 """
954 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
955 line_no = 1
956 for line in template_f.readlines():
957 # Update line number. +1 as #line directive sets next line number
958 snippets['line_no'] = line_no + 1
959 code = line.format(**snippets)
960 c_f.write(code)
961 line_no += 1
962
963
964def parse_function_file(funcs_file, snippets):
965 """
966 Parse function file and generate function dispatch code.
967
968 :param funcs_file: Functions file name
969 :param snippets: Dictionary to contain code pieces to be
970 substituted in the template.
971 :return:
972 """
973 with FileWrapper(funcs_file) as funcs_f:
974 suite_dependencies, dispatch_code, func_code, func_info = \
975 parse_functions(funcs_f)
976 snippets['functions_code'] = func_code
977 snippets['dispatch_code'] = dispatch_code
978 return suite_dependencies, func_info
979
980
981def generate_intermediate_data_file(data_file, out_data_file,
982 suite_dependencies, func_info, snippets):
983 """
984 Generates intermediate data file from input data file and
985 information read from functions file.
986
987 :param data_file: Data file name
988 :param out_data_file: Output/Intermediate data file
989 :param suite_dependencies: List of suite dependencies.
990 :param func_info: Function info parsed from functions file.
991 :param snippets: Dictionary to contain code pieces to be
992 substituted in the template.
993 :return:
994 """
995 with FileWrapper(data_file) as data_f, \
996 open(out_data_file, 'w') as out_data_f:
997 dep_check_code, expression_code = gen_from_test_data(
998 data_f, out_data_f, func_info, suite_dependencies)
999 snippets['dep_check_code'] = dep_check_code
1000 snippets['expression_code'] = expression_code
1001
1002
1003def generate_code(**input_info):
1004 """
1005 Generates C source code from test suite file, data file, common
1006 helpers file and platform file.
1007
1008 input_info expands to following parameters:
1009 funcs_file: Functions file object
1010 data_file: Data file object
1011 template_file: Template file object
1012 platform_file: Platform file object
1013 helpers_file: Helper functions file object
1014 suites_dir: Test suites dir
1015 c_file: Output C file object
1016 out_data_file: Output intermediate data file object
1017 :return:
1018 """
1019 funcs_file = input_info['funcs_file']
1020 data_file = input_info['data_file']
1021 template_file = input_info['template_file']
1022 platform_file = input_info['platform_file']
1023 helpers_file = input_info['helpers_file']
1024 suites_dir = input_info['suites_dir']
1025 c_file = input_info['c_file']
1026 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001027 for name, path in [('Functions file', funcs_file),
1028 ('Data file', data_file),
1029 ('Template file', template_file),
1030 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001031 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001032 ('Suites dir', suites_dir)]:
1033 if not os.path.exists(path):
1034 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1035
Azim Khanb31aa442018-07-03 11:57:54 +01001036 snippets = {'generator_script': os.path.basename(__file__)}
1037 read_code_from_input_files(platform_file, helpers_file,
1038 out_data_file, snippets)
1039 add_input_info(funcs_file, data_file, template_file,
1040 c_file, snippets)
1041 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1042 generate_intermediate_data_file(data_file, out_data_file,
1043 suite_dependencies, func_info, snippets)
1044 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001045
1046
Azim Khan8d686bf2018-07-04 23:29:46 +01001047def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001048 """
1049 Command line parser.
1050
1051 :return:
1052 """
Azim Khan040b6a22018-06-28 16:49:13 +01001053 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001054 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001055
1056 parser.add_argument("-f", "--functions-file",
1057 dest="funcs_file",
1058 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001059 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001060 required=True)
1061
1062 parser.add_argument("-d", "--data-file",
1063 dest="data_file",
1064 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001065 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001066 required=True)
1067
1068 parser.add_argument("-t", "--template-file",
1069 dest="template_file",
1070 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001071 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001072 required=True)
1073
1074 parser.add_argument("-s", "--suites-dir",
1075 dest="suites_dir",
1076 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001077 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001078 required=True)
1079
Azim Khane3b26af2018-06-29 02:36:57 +01001080 parser.add_argument("--helpers-file",
1081 dest="helpers_file",
1082 help="Helpers file",
1083 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001084 required=True)
1085
1086 parser.add_argument("-p", "--platform-file",
1087 dest="platform_file",
1088 help="Platform code file",
1089 metavar="PLATFORM_FILE",
1090 required=True)
1091
1092 parser.add_argument("-o", "--out-dir",
1093 dest="out_dir",
1094 help="Dir where generated code and scripts are copied",
1095 metavar="OUT_DIR",
1096 required=True)
1097
1098 args = parser.parse_args()
1099
1100 data_file_name = os.path.basename(args.data_file)
1101 data_name = os.path.splitext(data_file_name)[0]
1102
1103 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001104 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001105
1106 out_c_file_dir = os.path.dirname(out_c_file)
1107 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001108 for directory in [out_c_file_dir, out_data_file_dir]:
1109 if not os.path.exists(directory):
1110 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001111
Azim Khanb31aa442018-07-03 11:57:54 +01001112 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1113 template_file=args.template_file,
1114 platform_file=args.platform_file,
1115 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1116 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001117
1118
1119if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001120 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001121 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001122 except GeneratorInputError as err:
1123 print("%s: input error: %s" %
1124 (os.path.basename(sys.argv[0]), str(err)))