blob: cb80c764bbdc615d1f23df1e4dad3c20d929df4c [file] [log] [blame]
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +01001#!/usr/bin/env python3
Azim Khanf0e42fb2017-08-02 14:47:13 +01002# Test suites code generator.
3#
Azim Khan8d686bf2018-07-04 23:29:46 +01004# Copyright (C) 2018, Arm Limited, All Rights Reserved
Azim Khanf0e42fb2017-08-02 14:47:13 +01005# SPDX-License-Identifier: Apache-2.0
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
Azim Khanb31aa442018-07-03 11:57:54 +010019# This file is part of Mbed TLS (https://tls.mbed.org)
Azim Khanf0e42fb2017-08-02 14:47:13 +010020
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010021"""
Azim Khanaee05bb2018-07-02 16:01:04 +010022This script is a key part of Mbed TLS test suites framework. For
23understanding the script it is important to understand the
24framework. This doc string contains a summary of the framework
25and explains the function of this script.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +010026
Azim Khanaee05bb2018-07-02 16:01:04 +010027Mbed TLS test suites:
28=====================
29Scope:
30------
31The test suites focus on unit testing the crypto primitives and also
Azim Khanb31aa442018-07-03 11:57:54 +010032include x509 parser tests. Tests can be added to test any Mbed TLS
Azim Khanaee05bb2018-07-02 16:01:04 +010033module. However, the framework is not capable of testing SSL
34protocol, since that requires full stack execution and that is best
35tested as part of the system test.
36
37Test case definition:
38---------------------
39Tests are defined in a test_suite_<module>[.<optional sub module>].data
40file. A test definition contains:
41 test name
42 optional build macro dependencies
43 test function
44 test parameters
45
46Test dependencies are build macros that can be specified to indicate
47the build config in which the test is valid. For example if a test
48depends on a feature that is only enabled by defining a macro. Then
49that macro should be specified as a dependency of the test.
50
51Test function is the function that implements the test steps. This
52function is specified for different tests that perform same steps
53with different parameters.
54
55Test parameters are specified in string form separated by ':'.
56Parameters can be of type string, binary data specified as hex
57string and integer constants specified as integer, macro or
58as an expression. Following is an example test definition:
59
Mohammad Azim Khand2d01122018-07-18 17:48:37 +010060 AES 128 GCM Encrypt and decrypt 8 bytes
61 depends_on:MBEDTLS_AES_C:MBEDTLS_GCM_C
62 enc_dec_buf:MBEDTLS_CIPHER_AES_128_GCM:"AES-128-GCM":128:8:-1
Azim Khanaee05bb2018-07-02 16:01:04 +010063
64Test functions:
65---------------
66Test functions are coded in C in test_suite_<module>.function files.
67Functions file is itself not compilable and contains special
68format patterns to specify test suite dependencies, start and end
69of functions and function dependencies. Check any existing functions
70file for example.
71
72Execution:
73----------
74Tests are executed in 3 steps:
75- Generating test_suite_<module>[.<optional sub module>].c file
76 for each corresponding .data file.
77- Building each source file into executables.
78- Running each executable and printing report.
79
80Generating C test source requires more than just the test functions.
81Following extras are required:
82- Process main()
83- Reading .data file and dispatching test cases.
84- Platform specific test case execution
85- Dependency checking
86- Integer expression evaluation
87- Test function dispatch
88
89Build dependencies and integer expressions (in the test parameters)
90are specified as strings in the .data file. Their run time value is
91not known at the generation stage. Hence, they need to be translated
92into run time evaluations. This script generates the run time checks
93for dependencies and integer expressions.
94
95Similarly, function names have to be translated into function calls.
96This script also generates code for function dispatch.
97
98The extra code mentioned here is either generated by this script
99or it comes from the input files: helpers file, platform file and
100the template file.
101
102Helper file:
103------------
104Helpers file contains common helper/utility functions and data.
105
106Platform file:
107--------------
108Platform file contains platform specific setup code and test case
109dispatch code. For example, host_test.function reads test data
110file from host's file system and dispatches tests.
111In case of on-target target_test.function tests are not dispatched
112on target. Target code is kept minimum and only test functions are
113dispatched. Test case dispatch is done on the host using tools like
114Greentea.
115
116Template file:
117---------
118Template file for example main_test.function is a template C file in
119which generated code and code from input files is substituted to
120generate a compilable C file. It also contains skeleton functions for
121dependency checks, expression evaluation and function dispatch. These
122functions are populated with checks and return codes by this script.
123
124Template file contains "replacement" fields that are formatted
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100125strings processed by Python string.Template.substitute() method.
Azim Khanaee05bb2018-07-02 16:01:04 +0100126
127This script:
128============
129Core function of this script is to fill the template file with
130code that is generated or read from helpers and platform files.
131
132This script replaces following fields in the template and generates
133the test source file:
134
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100135$test_common_helpers <-- All common code from helpers.function
Azim Khanaee05bb2018-07-02 16:01:04 +0100136 is substituted here.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100137$functions_code <-- Test functions are substituted here
Azim Khanaee05bb2018-07-02 16:01:04 +0100138 from the input test_suit_xyz.function
139 file. C preprocessor checks are generated
140 for the build dependencies specified
141 in the input file. This script also
142 generates wrappers for the test
143 functions with code to expand the
144 string parameters read from the data
145 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100146$expression_code <-- This script enumerates the
Azim Khanaee05bb2018-07-02 16:01:04 +0100147 expressions in the .data file and
148 generates code to handle enumerated
149 expression Ids and return the values.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100150$dep_check_code <-- This script enumerates all
Azim Khanaee05bb2018-07-02 16:01:04 +0100151 build dependencies and generate
152 code to handle enumerated build
153 dependency Id and return status: if
154 the dependency is defined or not.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100155$dispatch_code <-- This script enumerates the functions
Azim Khanaee05bb2018-07-02 16:01:04 +0100156 specified in the input test data file
157 and generates the initializer for the
158 function table in the template
159 file.
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100160$platform_code <-- Platform specific setup and test
Azim Khanaee05bb2018-07-02 16:01:04 +0100161 dispatch code.
162
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100163"""
164
Azim Khanf0e42fb2017-08-02 14:47:13 +0100165
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100166import io
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100167import os
168import re
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100169import sys
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100170import string
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100171import argparse
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100172
173
Azim Khanb31aa442018-07-03 11:57:54 +0100174BEGIN_HEADER_REGEX = r'/\*\s*BEGIN_HEADER\s*\*/'
175END_HEADER_REGEX = r'/\*\s*END_HEADER\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100176
Azim Khanb31aa442018-07-03 11:57:54 +0100177BEGIN_SUITE_HELPERS_REGEX = r'/\*\s*BEGIN_SUITE_HELPERS\s*\*/'
178END_SUITE_HELPERS_REGEX = r'/\*\s*END_SUITE_HELPERS\s*\*/'
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000179
Azim Khanb31aa442018-07-03 11:57:54 +0100180BEGIN_DEP_REGEX = r'BEGIN_DEPENDENCIES'
181END_DEP_REGEX = r'END_DEPENDENCIES'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100182
Azim Khan8d686bf2018-07-04 23:29:46 +0100183BEGIN_CASE_REGEX = r'/\*\s*BEGIN_CASE\s*(?P<depends_on>.*?)\s*\*/'
Azim Khanb31aa442018-07-03 11:57:54 +0100184END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100185
Azim Khan8d686bf2018-07-04 23:29:46 +0100186DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
Ron Eldora2579be2018-11-26 14:23:14 +0200187C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*(([<>]=?|==)[0-9]*)?$'
Azim Khanfcdf6852018-07-05 17:31:46 +0100188TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
Azim Khan8d686bf2018-07-04 23:29:46 +0100189INT_CHECK_REGEX = r'int\s+.*'
190CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
191DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
Azim Khan8d686bf2018-07-04 23:29:46 +0100192FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
193EXIT_LABEL_REGEX = r'^exit:'
194
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100195
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100196class GeneratorInputError(Exception):
197 """
Azim Khane3b26af2018-06-29 02:36:57 +0100198 Exception to indicate error in the input files to this script.
199 This includes missing patterns, test function names and other
200 parsing errors.
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100201 """
202 pass
203
204
Azim Khanb31aa442018-07-03 11:57:54 +0100205class FileWrapper(io.FileIO, object):
Azim Khan4b543232017-06-30 09:35:21 +0100206 """
Azim Khane3b26af2018-06-29 02:36:57 +0100207 This class extends built-in io.FileIO class with attribute line_no,
208 that indicates line number for the line that is read.
Azim Khan4b543232017-06-30 09:35:21 +0100209 """
210
211 def __init__(self, file_name):
212 """
Azim Khane3b26af2018-06-29 02:36:57 +0100213 Instantiate the base class and initialize the line number to 0.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100214
Azim Khanf0e42fb2017-08-02 14:47:13 +0100215 :param file_name: File path to open.
Azim Khan4b543232017-06-30 09:35:21 +0100216 """
217 super(FileWrapper, self).__init__(file_name, 'r')
Azim Khanb31aa442018-07-03 11:57:54 +0100218 self._line_no = 0
Azim Khan4b543232017-06-30 09:35:21 +0100219
Azim Khanb31aa442018-07-03 11:57:54 +0100220 def next(self):
Azim Khan4b543232017-06-30 09:35:21 +0100221 """
Azim Khane3b26af2018-06-29 02:36:57 +0100222 Python 2 iterator method. This method overrides base class's
223 next method and extends the next method to count the line
224 numbers as each line is read.
225
226 It works for both Python 2 and Python 3 by checking iterator
227 method name in the base iterator object.
228
Azim Khanf0e42fb2017-08-02 14:47:13 +0100229 :return: Line read from file.
Azim Khan4b543232017-06-30 09:35:21 +0100230 """
Gilles Peskine667f7f82018-06-18 17:51:56 +0200231 parent = super(FileWrapper, self)
232 if hasattr(parent, '__next__'):
Azim Khanb31aa442018-07-03 11:57:54 +0100233 line = parent.__next__() # Python 3
Gilles Peskine667f7f82018-06-18 17:51:56 +0200234 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100235 line = parent.next() # Python 2
236 if line is not None:
237 self._line_no += 1
Azim Khan936ea932018-06-28 16:47:12 +0100238 # Convert byte array to string with correct encoding and
239 # strip any whitespaces added in the decoding process.
Azim Khan8d686bf2018-07-04 23:29:46 +0100240 return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
Mohammad Azim Khan1ec7e6f2018-04-11 23:46:37 +0100241 return None
Azim Khane3b26af2018-06-29 02:36:57 +0100242
243 # Python 3 iterator method
Azim Khanb31aa442018-07-03 11:57:54 +0100244 __next__ = next
245
246 def get_line_no(self):
247 """
248 Gives current line number.
249 """
250 return self._line_no
251
252 line_no = property(get_line_no)
Azim Khan4b543232017-06-30 09:35:21 +0100253
254
255def split_dep(dep):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100256 """
Azim Khanb31aa442018-07-03 11:57:54 +0100257 Split NOT character '!' from dependency. Used by gen_dependencies()
Ron Eldora2579be2018-11-26 14:23:14 +0200258 Determine condition MACRO and definition MACRO.
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
Azim Khanfcdf6852018-07-05 17:31:46 +0100454def parse_function_arguments(line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100455 """
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.
Azim Khanfcdf6852018-07-05 17:31:46 +0100462 :return: argument list, local variables for
Azim Khan040b6a22018-06-28 16:49:13 +0100463 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 = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100468 arg_idx = 0
Azim Khanfcdf6852018-07-05 17:31:46 +0100469 # Remove characters before arguments
470 line = line[line.find('(') + 1:]
Azim Khan8d686bf2018-07-04 23:29:46 +0100471 # Process arguments, ex: <type> arg1, <type> arg2 )
472 # This script assumes that the argument list is terminated by ')'
473 # i.e. the test functions will not have a function pointer
474 # argument.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100475 for arg in line[:line.find(')')].split(','):
476 arg = arg.strip()
477 if arg == '':
478 continue
Azim Khan8d686bf2018-07-04 23:29:46 +0100479 if re.search(INT_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100480 args.append('int')
481 args_dispatch.append('*( (int *) params[%d] )' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100482 elif re.search(CHAR_CHECK_REGEX, arg.strip()):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100483 args.append('char*')
484 args_dispatch.append('(char *) params[%d]' % arg_idx)
Azim Khan8d686bf2018-07-04 23:29:46 +0100485 elif re.search(DATA_T_CHECK_REGEX, arg.strip()):
Azim Khana57a4202017-05-31 20:32:32 +0100486 args.append('hex')
Azim Khan2397bba2017-06-09 04:35:03 +0100487 # create a structure
Azim Khan040b6a22018-06-28 16:49:13 +0100488 pointer_initializer = '(uint8_t *) params[%d]' % arg_idx
489 len_initializer = '*( (uint32_t *) params[%d] )' % (arg_idx+1)
Azim Khanb31aa442018-07-03 11:57:54 +0100490 local_vars += """ data_t data%d = {%s, %s};
Azim Khan040b6a22018-06-28 16:49:13 +0100491""" % (arg_idx, pointer_initializer, len_initializer)
Azim Khan2397bba2017-06-09 04:35:03 +0100492
Azim Khan5fcca462018-06-29 11:05:32 +0100493 args_dispatch.append('&data%d' % arg_idx)
Azim Khan2397bba2017-06-09 04:35:03 +0100494 arg_idx += 1
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100495 else:
Azim Khan040b6a22018-06-28 16:49:13 +0100496 raise ValueError("Test function arguments can only be 'int', "
Azim Khan5fcca462018-06-29 11:05:32 +0100497 "'char *' or 'data_t'\n%s" % line)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100498 arg_idx += 1
499
Azim Khanfcdf6852018-07-05 17:31:46 +0100500 return args, local_vars, args_dispatch
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100501
502
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100503def generate_function_code(name, code, local_vars, args_dispatch,
504 dependencies):
505 """
506 Generate function code with preprocessor checks and parameter dispatch
507 wrapper.
508
509 :param name: Function name
510 :param code: Function code
511 :param local_vars: Local variables for function wrapper
512 :param args_dispatch: Argument dispatch code
513 :param dependencies: Preprocessor dependencies list
514 :return: Final function code
515 """
516 # Add exit label if not present
517 if code.find('exit:') == -1:
518 split_code = code.rsplit('}', 1)
519 if len(split_code) == 2:
520 code = """exit:
521 ;
522}""".join(split_code)
523
524 code += gen_function_wrapper(name, local_vars, args_dispatch)
525 preprocessor_check_start, preprocessor_check_end = \
526 gen_dependencies(dependencies)
527 return preprocessor_check_start + code + preprocessor_check_end
528
529
Azim Khanb31aa442018-07-03 11:57:54 +0100530def parse_function_code(funcs_f, dependencies, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100531 """
Azim Khan040b6a22018-06-28 16:49:13 +0100532 Parses out a function from function file object and generates
533 function and dispatch code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100534
Azim Khanf0e42fb2017-08-02 14:47:13 +0100535 :param funcs_f: file object of the functions file.
Azim Khanb31aa442018-07-03 11:57:54 +0100536 :param dependencies: List of dependencies
537 :param suite_dependencies: List of test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100538 :return: Function name, arguments, function code and dispatch code.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100539 """
Azim Khanfcdf6852018-07-05 17:31:46 +0100540 line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
541 code = ''
Azim Khan8d686bf2018-07-04 23:29:46 +0100542 has_exit_label = False
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100543 for line in funcs_f:
Azim Khanfcdf6852018-07-05 17:31:46 +0100544 # Check function signature. Function signature may be split
545 # across multiple lines. Here we try to find the start of
546 # arguments list, then remove '\n's and apply the regex to
547 # detect function start.
548 up_to_arg_list_start = code + line[:line.find('(') + 1]
549 match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
550 up_to_arg_list_start.replace('\n', ' '), re.I)
Azim Khanb31aa442018-07-03 11:57:54 +0100551 if match:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100552 # check if we have full signature i.e. split in more lines
Azim Khanfcdf6852018-07-05 17:31:46 +0100553 name = match.group('func_name')
Azim Khan8d686bf2018-07-04 23:29:46 +0100554 if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100555 for lin in funcs_f:
556 line += lin
Azim Khan8d686bf2018-07-04 23:29:46 +0100557 if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100558 break
Azim Khanfcdf6852018-07-05 17:31:46 +0100559 args, local_vars, args_dispatch = parse_function_arguments(
Azim Khanb31aa442018-07-03 11:57:54 +0100560 line)
Azim Khan8d686bf2018-07-04 23:29:46 +0100561 code += line
Azim Khanfcdf6852018-07-05 17:31:46 +0100562 break
563 code += line
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100564 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100565 raise GeneratorInputError("file: %s - Test functions not found!" %
Azim Khanb31aa442018-07-03 11:57:54 +0100566 funcs_f.name)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100567
Azim Khanfcdf6852018-07-05 17:31:46 +0100568 # Prefix test function name with 'test_'
569 code = code.replace(name, 'test_' + name, 1)
570 name = 'test_' + name
571
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100572 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100573 if re.search(END_CASE_REGEX, line):
574 break
Azim Khan8d686bf2018-07-04 23:29:46 +0100575 if not has_exit_label:
576 has_exit_label = \
577 re.search(EXIT_LABEL_REGEX, line.strip()) is not None
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100578 code += line
579 else:
Azim Khane3b26af2018-06-29 02:36:57 +0100580 raise GeneratorInputError("file: %s - end case pattern [%s] not "
Azim Khanb31aa442018-07-03 11:57:54 +0100581 "found!" % (funcs_f.name, END_CASE_REGEX))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100582
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100583 code = line_directive + code
584 code = generate_function_code(name, code, local_vars, args_dispatch,
585 dependencies)
Azim Khanb31aa442018-07-03 11:57:54 +0100586 dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100587 return (name, args, code, dispatch_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100588
589
590def parse_functions(funcs_f):
591 """
Azim Khane3b26af2018-06-29 02:36:57 +0100592 Parses a test_suite_xxx.function file and returns information
593 for generating a C source file for the test suite.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100594
Azim Khanf0e42fb2017-08-02 14:47:13 +0100595 :param funcs_f: file object of the functions file.
Azim Khan040b6a22018-06-28 16:49:13 +0100596 :return: List of test suite dependencies, test function dispatch
597 code, function code and a dict with function identifiers
598 and arguments info.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100599 """
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000600 suite_helpers = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100601 suite_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100602 suite_functions = ''
603 func_info = {}
604 function_idx = 0
605 dispatch_code = ''
606 for line in funcs_f:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100607 if re.search(BEGIN_HEADER_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100608 suite_helpers += parse_until_pattern(funcs_f, END_HEADER_REGEX)
Mohammad Azim Khanb5229292018-02-06 13:08:01 +0000609 elif re.search(BEGIN_SUITE_HELPERS_REGEX, line):
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100610 suite_helpers += parse_until_pattern(funcs_f,
611 END_SUITE_HELPERS_REGEX)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100612 elif re.search(BEGIN_DEP_REGEX, line):
Azim Khanb31aa442018-07-03 11:57:54 +0100613 suite_dependencies += parse_suite_dependencies(funcs_f)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100614 elif re.search(BEGIN_CASE_REGEX, line):
Azim Khan8d686bf2018-07-04 23:29:46 +0100615 try:
616 dependencies = parse_function_dependencies(line)
617 except GeneratorInputError as error:
618 raise GeneratorInputError(
619 "%s:%d: %s" % (funcs_f.name, funcs_f.line_no,
620 str(error)))
Azim Khan040b6a22018-06-28 16:49:13 +0100621 func_name, args, func_code, func_dispatch =\
Azim Khanb31aa442018-07-03 11:57:54 +0100622 parse_function_code(funcs_f, dependencies, suite_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100623 suite_functions += func_code
624 # Generate dispatch code and enumeration info
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100625 if func_name in func_info:
626 raise GeneratorInputError(
Azim Khanb31aa442018-07-03 11:57:54 +0100627 "file: %s - function %s re-declared at line %d" %
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100628 (funcs_f.name, func_name, funcs_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100629 func_info[func_name] = (function_idx, args)
630 dispatch_code += '/* Function Id: %d */\n' % function_idx
631 dispatch_code += func_dispatch
632 function_idx += 1
633
Azim Khanb31aa442018-07-03 11:57:54 +0100634 func_code = (suite_helpers +
635 suite_functions).join(gen_dependencies(suite_dependencies))
636 return suite_dependencies, dispatch_code, func_code, func_info
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100637
638
Azim Khanb31aa442018-07-03 11:57:54 +0100639def escaped_split(inp_str, split_char):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100640 """
Azim Khanb31aa442018-07-03 11:57:54 +0100641 Split inp_str on character split_char but ignore if escaped.
Azim Khan040b6a22018-06-28 16:49:13 +0100642 Since, return value is used to write back to the intermediate
643 data file, any escape characters in the input are retained in the
644 output.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100645
Azim Khanb31aa442018-07-03 11:57:54 +0100646 :param inp_str: String to split
Azim Khan8d686bf2018-07-04 23:29:46 +0100647 :param split_char: Split character
Azim Khanf0e42fb2017-08-02 14:47:13 +0100648 :return: List of splits
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100649 """
Azim Khanb31aa442018-07-03 11:57:54 +0100650 if len(split_char) > 1:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100651 raise ValueError('Expected split character. Found string!')
Azim Khan63028132018-07-05 17:53:11 +0100652 out = re.sub(r'(\\.)|' + split_char,
653 lambda m: m.group(1) or '\n', inp_str,
654 len(inp_str)).split('\n')
Mohammad Azim Khan32cbcda2018-07-06 00:29:09 +0100655 out = [x for x in out if x]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100656 return out
657
658
Azim Khanb31aa442018-07-03 11:57:54 +0100659def parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100660 """
Azim Khane3b26af2018-06-29 02:36:57 +0100661 Parses .data file for each test case name, test function name,
662 test dependencies and test arguments. This information is
663 correlated with the test functions file for generating an
664 intermediate data file replacing the strings for test function
665 names, dependencies and integer constant expressions with
666 identifiers. Mainly for optimising space for on-target
667 execution.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100668
Azim Khanf0e42fb2017-08-02 14:47:13 +0100669 :param data_f: file object of the data file.
Azim Khan040b6a22018-06-28 16:49:13 +0100670 :return: Generator that yields test name, function name,
671 dependency list and function argument list.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100672 """
Azim Khanb31aa442018-07-03 11:57:54 +0100673 __state_read_name = 0
674 __state_read_args = 1
675 state = __state_read_name
676 dependencies = []
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100677 name = ''
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100678 for line in data_f:
679 line = line.strip()
Azim Khan8d686bf2018-07-04 23:29:46 +0100680 # Skip comments
681 if line.startswith('#'):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100682 continue
683
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100684 # Blank line indicates end of test
Azim Khanb31aa442018-07-03 11:57:54 +0100685 if not line:
686 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100687 raise GeneratorInputError("[%s:%d] Newline before arguments. "
688 "Test function and arguments "
689 "missing for %s" %
690 (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100691 continue
692
Azim Khanb31aa442018-07-03 11:57:54 +0100693 if state == __state_read_name:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100694 # Read test name
695 name = line
Azim Khanb31aa442018-07-03 11:57:54 +0100696 state = __state_read_args
697 elif state == __state_read_args:
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100698 # Check dependencies
Azim Khan8d686bf2018-07-04 23:29:46 +0100699 match = re.search(DEPENDENCY_REGEX, line)
Azim Khanb31aa442018-07-03 11:57:54 +0100700 if match:
Azim Khan8d686bf2018-07-04 23:29:46 +0100701 try:
702 dependencies = parse_dependencies(
703 match.group('dependencies'))
704 except GeneratorInputError as error:
705 raise GeneratorInputError(
706 str(error) + " - %s:%d" %
707 (data_f.name, data_f.line_no))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100708 else:
709 # Read test vectors
710 parts = escaped_split(line, ':')
Azim Khanb31aa442018-07-03 11:57:54 +0100711 test_function = parts[0]
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100712 args = parts[1:]
Azim Khanb31aa442018-07-03 11:57:54 +0100713 yield name, test_function, dependencies, args
714 dependencies = []
715 state = __state_read_name
716 if state == __state_read_args:
Azim Khan040b6a22018-06-28 16:49:13 +0100717 raise GeneratorInputError("[%s:%d] Newline before arguments. "
718 "Test function and arguments missing for "
719 "%s" % (data_f.name, data_f.line_no, name))
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100720
721
722def gen_dep_check(dep_id, dep):
723 """
Azim Khane3b26af2018-06-29 02:36:57 +0100724 Generate code for checking dependency with the associated
725 identifier.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100726
Azim Khanf0e42fb2017-08-02 14:47:13 +0100727 :param dep_id: Dependency identifier
728 :param dep: Dependency macro
729 :return: Dependency check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100730 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100731 if dep_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100732 raise GeneratorInputError("Dependency Id should be a positive "
733 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100734 _not, dep = ('!', dep[1:]) if dep[0] == '!' else ('', dep)
Ron Eldora2579be2018-11-26 14:23:14 +0200735 _defined = '' if re.search(r'(<=?|>=?|==)', dep) else 'defined'
Azim Khanb31aa442018-07-03 11:57:54 +0100736 if not dep:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100737 raise GeneratorInputError("Dependency should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100738 dep_check = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100739 case {id}:
740 {{
Ron Eldora2579be2018-11-26 14:23:14 +0200741#if {_not}{_defined}({macro})
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100742 ret = DEPENDENCY_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100743#else
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100744 ret = DEPENDENCY_NOT_SUPPORTED;
Azim Khand61b8372017-07-10 11:54:01 +0100745#endif
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100746 }}
Ron Eldora2579be2018-11-26 14:23:14 +0200747 break;'''.format(_not=_not, _defined=_defined, macro=dep, id=dep_id)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100748 return dep_check
749
750
751def gen_expression_check(exp_id, exp):
752 """
Azim Khane3b26af2018-06-29 02:36:57 +0100753 Generates code for evaluating an integer expression using
754 associated expression Id.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100755
Azim Khanf0e42fb2017-08-02 14:47:13 +0100756 :param exp_id: Expression Identifier
757 :param exp: Expression/Macro
758 :return: Expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100759 """
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100760 if exp_id < 0:
Azim Khan040b6a22018-06-28 16:49:13 +0100761 raise GeneratorInputError("Expression Id should be a positive "
762 "integer.")
Azim Khanb31aa442018-07-03 11:57:54 +0100763 if not exp:
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100764 raise GeneratorInputError("Expression should not be an empty string.")
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100765 exp_code = '''
Azim Khanb1c2d0f2017-07-07 17:14:02 +0100766 case {exp_id}:
767 {{
768 *out_value = {expression};
769 }}
770 break;'''.format(exp_id=exp_id, expression=exp)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100771 return exp_code
772
773
Azim Khanb31aa442018-07-03 11:57:54 +0100774def write_dependencies(out_data_f, test_dependencies, unique_dependencies):
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100775 """
Azim Khane3b26af2018-06-29 02:36:57 +0100776 Write dependencies to intermediate test data file, replacing
777 the string form with identifiers. Also, generates dependency
778 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100779
Azim Khanf0e42fb2017-08-02 14:47:13 +0100780 :param out_data_f: Output intermediate data file
Azim Khanb31aa442018-07-03 11:57:54 +0100781 :param test_dependencies: Dependencies
782 :param unique_dependencies: Mutable list to track unique dependencies
Azim Khan040b6a22018-06-28 16:49:13 +0100783 that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100784 :return: returns dependency check code.
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100785 """
Azim Khan599cd242017-07-06 17:34:27 +0100786 dep_check_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100787 if test_dependencies:
Azim Khan599cd242017-07-06 17:34:27 +0100788 out_data_f.write('depends_on')
Azim Khanb31aa442018-07-03 11:57:54 +0100789 for dep in test_dependencies:
790 if dep not in unique_dependencies:
791 unique_dependencies.append(dep)
792 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100793 dep_check_code += gen_dep_check(dep_id, dep)
794 else:
Azim Khanb31aa442018-07-03 11:57:54 +0100795 dep_id = unique_dependencies.index(dep)
Azim Khan599cd242017-07-06 17:34:27 +0100796 out_data_f.write(':' + str(dep_id))
797 out_data_f.write('\n')
798 return dep_check_code
799
800
801def write_parameters(out_data_f, test_args, func_args, unique_expressions):
802 """
Azim Khane3b26af2018-06-29 02:36:57 +0100803 Writes test parameters to the intermediate data file, replacing
804 the string form with identifiers. Also, generates expression
805 check code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100806
Azim Khanf0e42fb2017-08-02 14:47:13 +0100807 :param out_data_f: Output intermediate data file
808 :param test_args: Test parameters
809 :param func_args: Function arguments
Azim Khan040b6a22018-06-28 16:49:13 +0100810 :param unique_expressions: Mutable list to track unique
811 expressions that are global to this re-entrant function.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100812 :return: Returns expression check code.
Azim Khan599cd242017-07-06 17:34:27 +0100813 """
814 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100815 for i, _ in enumerate(test_args):
Azim Khan599cd242017-07-06 17:34:27 +0100816 typ = func_args[i]
817 val = test_args[i]
818
Azim Khan040b6a22018-06-28 16:49:13 +0100819 # check if val is a non literal int val (i.e. an expression)
Azim Khan8d686bf2018-07-04 23:29:46 +0100820 if typ == 'int' and not re.match(r'(\d+|0x[0-9a-f]+)$',
821 val, re.I):
Azim Khan599cd242017-07-06 17:34:27 +0100822 typ = 'exp'
823 if val not in unique_expressions:
824 unique_expressions.append(val)
Azim Khan040b6a22018-06-28 16:49:13 +0100825 # exp_id can be derived from len(). But for
826 # readability and consistency with case of existing
827 # let's use index().
Azim Khan599cd242017-07-06 17:34:27 +0100828 exp_id = unique_expressions.index(val)
829 expression_code += gen_expression_check(exp_id, val)
830 val = exp_id
831 else:
832 val = unique_expressions.index(val)
833 out_data_f.write(':' + typ + ':' + str(val))
834 out_data_f.write('\n')
835 return expression_code
836
837
Azim Khanb31aa442018-07-03 11:57:54 +0100838def gen_suite_dep_checks(suite_dependencies, dep_check_code, expression_code):
Azim Khan599cd242017-07-06 17:34:27 +0100839 """
Azim Khane3b26af2018-06-29 02:36:57 +0100840 Generates preprocessor checks for test suite dependencies.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100841
Azim Khanb31aa442018-07-03 11:57:54 +0100842 :param suite_dependencies: Test suite dependencies read from the
Azim Khan8d686bf2018-07-04 23:29:46 +0100843 .function file.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100844 :param dep_check_code: Dependency check code
845 :param expression_code: Expression check code
Azim Khan040b6a22018-06-28 16:49:13 +0100846 :return: Dependency and expression code guarded by test suite
847 dependencies.
Azim Khan599cd242017-07-06 17:34:27 +0100848 """
Azim Khanb31aa442018-07-03 11:57:54 +0100849 if suite_dependencies:
850 preprocessor_check = gen_dependencies_one_line(suite_dependencies)
Azim Khan599cd242017-07-06 17:34:27 +0100851 dep_check_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100852{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100853{code}
Azim Khan599cd242017-07-06 17:34:27 +0100854#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100855'''.format(preprocessor_check=preprocessor_check, code=dep_check_code)
Azim Khan599cd242017-07-06 17:34:27 +0100856 expression_code = '''
Azim Khanb31aa442018-07-03 11:57:54 +0100857{preprocessor_check}
Azim Khan599cd242017-07-06 17:34:27 +0100858{code}
Azim Khan599cd242017-07-06 17:34:27 +0100859#endif
Azim Khanb31aa442018-07-03 11:57:54 +0100860'''.format(preprocessor_check=preprocessor_check, code=expression_code)
Azim Khan599cd242017-07-06 17:34:27 +0100861 return dep_check_code, expression_code
Azim Khan5e2ac1f2017-07-03 13:58:20 +0100862
863
Azim Khanb31aa442018-07-03 11:57:54 +0100864def gen_from_test_data(data_f, out_data_f, func_info, suite_dependencies):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100865 """
Azim Khane3b26af2018-06-29 02:36:57 +0100866 This function reads test case name, dependencies and test vectors
867 from the .data file. This information is correlated with the test
868 functions file for generating an intermediate data file replacing
869 the strings for test function names, dependencies and integer
870 constant expressions with identifiers. Mainly for optimising
871 space for on-target execution.
872 It also generates test case dependency check code and expression
873 evaluation code.
Mohammad Azim Khanb73159d2018-06-13 16:31:26 +0100874
Azim Khanf0e42fb2017-08-02 14:47:13 +0100875 :param data_f: Data file object
Azim Khan8d686bf2018-07-04 23:29:46 +0100876 :param out_data_f: Output intermediate data file
Azim Khan040b6a22018-06-28 16:49:13 +0100877 :param func_info: Dict keyed by function and with function id
878 and arguments info
Azim Khanb31aa442018-07-03 11:57:54 +0100879 :param suite_dependencies: Test suite dependencies
Azim Khanf0e42fb2017-08-02 14:47:13 +0100880 :return: Returns dependency and expression check code
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100881 """
Azim Khanb31aa442018-07-03 11:57:54 +0100882 unique_dependencies = []
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100883 unique_expressions = []
884 dep_check_code = ''
885 expression_code = ''
Azim Khanb31aa442018-07-03 11:57:54 +0100886 for test_name, function_name, test_dependencies, test_args in \
887 parse_test_data(data_f):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100888 out_data_f.write(test_name + '\n')
889
Azim Khanb31aa442018-07-03 11:57:54 +0100890 # Write dependencies
891 dep_check_code += write_dependencies(out_data_f, test_dependencies,
892 unique_dependencies)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100893
Azim Khan599cd242017-07-06 17:34:27 +0100894 # Write test function name
895 test_function_name = 'test_' + function_name
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100896 if test_function_name not in func_info:
Azim Khan040b6a22018-06-28 16:49:13 +0100897 raise GeneratorInputError("Function %s not found!" %
898 test_function_name)
Azim Khan599cd242017-07-06 17:34:27 +0100899 func_id, func_args = func_info[test_function_name]
900 out_data_f.write(str(func_id))
901
902 # Write parameters
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +0100903 if len(test_args) != len(func_args):
Azim Khan040b6a22018-06-28 16:49:13 +0100904 raise GeneratorInputError("Invalid number of arguments in test "
Azim Khanb31aa442018-07-03 11:57:54 +0100905 "%s. See function %s signature." %
906 (test_name, function_name))
Azim Khan040b6a22018-06-28 16:49:13 +0100907 expression_code += write_parameters(out_data_f, test_args, func_args,
908 unique_expressions)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100909
Azim Khan599cd242017-07-06 17:34:27 +0100910 # Write a newline as test case separator
911 out_data_f.write('\n')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100912
Azim Khanb31aa442018-07-03 11:57:54 +0100913 dep_check_code, expression_code = gen_suite_dep_checks(
914 suite_dependencies, dep_check_code, expression_code)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100915 return dep_check_code, expression_code
916
917
Azim Khanb31aa442018-07-03 11:57:54 +0100918def add_input_info(funcs_file, data_file, template_file,
919 c_file, snippets):
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100920 """
Azim Khanb31aa442018-07-03 11:57:54 +0100921 Add generator input info in snippets.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100922
Azim Khanf0e42fb2017-08-02 14:47:13 +0100923 :param funcs_file: Functions file object
924 :param data_file: Data file object
925 :param template_file: Template file object
Azim Khanf0e42fb2017-08-02 14:47:13 +0100926 :param c_file: Output C file object
Azim Khanb31aa442018-07-03 11:57:54 +0100927 :param snippets: Dictionary to contain code pieces to be
928 substituted in the template.
Mohammad Azim Khanfff49042017-03-28 01:48:31 +0100929 :return:
930 """
Azim Khanb31aa442018-07-03 11:57:54 +0100931 snippets['test_file'] = c_file
932 snippets['test_main_file'] = template_file
933 snippets['test_case_file'] = funcs_file
934 snippets['test_case_data_file'] = data_file
935
936
937def read_code_from_input_files(platform_file, helpers_file,
938 out_data_file, snippets):
939 """
940 Read code from input files and create substitutions for replacement
941 strings in the template file.
942
943 :param platform_file: Platform file object
944 :param helpers_file: Helper functions file object
945 :param out_data_file: Output intermediate data file object
946 :param snippets: Dictionary to contain code pieces to be
947 substituted in the template.
948 :return:
949 """
950 # Read helpers
951 with open(helpers_file, 'r') as help_f, open(platform_file, 'r') as \
952 platform_f:
953 snippets['test_common_helper_file'] = helpers_file
954 snippets['test_common_helpers'] = help_f.read()
955 snippets['test_platform_file'] = platform_file
956 snippets['platform_code'] = platform_f.read().replace(
957 'DATA_FILE', out_data_file.replace('\\', '\\\\')) # escape '\'
958
959
960def write_test_source_file(template_file, c_file, snippets):
961 """
962 Write output source file with generated source code.
963
964 :param template_file: Template file name
965 :param c_file: Output source file
966 :param snippets: Generated and code snippets
967 :return:
968 """
969 with open(template_file, 'r') as template_f, open(c_file, 'w') as c_f:
Mohammad Azim Khand2d01122018-07-18 17:48:37 +0100970 for line_no, line in enumerate(template_f.readlines(), 1):
Azim Khanb31aa442018-07-03 11:57:54 +0100971 # Update line number. +1 as #line directive sets next line number
972 snippets['line_no'] = line_no + 1
Mohammad Azim Khan5cb70172018-07-19 11:32:30 +0100973 code = string.Template(line).substitute(**snippets)
Azim Khanb31aa442018-07-03 11:57:54 +0100974 c_f.write(code)
Azim Khanb31aa442018-07-03 11:57:54 +0100975
976
977def parse_function_file(funcs_file, snippets):
978 """
979 Parse function file and generate function dispatch code.
980
981 :param funcs_file: Functions file name
982 :param snippets: Dictionary to contain code pieces to be
983 substituted in the template.
984 :return:
985 """
986 with FileWrapper(funcs_file) as funcs_f:
987 suite_dependencies, dispatch_code, func_code, func_info = \
988 parse_functions(funcs_f)
989 snippets['functions_code'] = func_code
990 snippets['dispatch_code'] = dispatch_code
991 return suite_dependencies, func_info
992
993
994def generate_intermediate_data_file(data_file, out_data_file,
995 suite_dependencies, func_info, snippets):
996 """
997 Generates intermediate data file from input data file and
998 information read from functions file.
999
1000 :param data_file: Data file name
1001 :param out_data_file: Output/Intermediate data file
1002 :param suite_dependencies: List of suite dependencies.
1003 :param func_info: Function info parsed from functions file.
1004 :param snippets: Dictionary to contain code pieces to be
1005 substituted in the template.
1006 :return:
1007 """
1008 with FileWrapper(data_file) as data_f, \
1009 open(out_data_file, 'w') as out_data_f:
1010 dep_check_code, expression_code = gen_from_test_data(
1011 data_f, out_data_f, func_info, suite_dependencies)
1012 snippets['dep_check_code'] = dep_check_code
1013 snippets['expression_code'] = expression_code
1014
1015
1016def generate_code(**input_info):
1017 """
1018 Generates C source code from test suite file, data file, common
1019 helpers file and platform file.
1020
1021 input_info expands to following parameters:
1022 funcs_file: Functions file object
1023 data_file: Data file object
1024 template_file: Template file object
1025 platform_file: Platform file object
1026 helpers_file: Helper functions file object
1027 suites_dir: Test suites dir
1028 c_file: Output C file object
1029 out_data_file: Output intermediate data file object
1030 :return:
1031 """
1032 funcs_file = input_info['funcs_file']
1033 data_file = input_info['data_file']
1034 template_file = input_info['template_file']
1035 platform_file = input_info['platform_file']
1036 helpers_file = input_info['helpers_file']
1037 suites_dir = input_info['suites_dir']
1038 c_file = input_info['c_file']
1039 out_data_file = input_info['out_data_file']
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001040 for name, path in [('Functions file', funcs_file),
1041 ('Data file', data_file),
1042 ('Template file', template_file),
1043 ('Platform file', platform_file),
Azim Khane3b26af2018-06-29 02:36:57 +01001044 ('Helpers code file', helpers_file),
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001045 ('Suites dir', suites_dir)]:
1046 if not os.path.exists(path):
1047 raise IOError("ERROR: %s [%s] not found!" % (name, path))
1048
Azim Khanb31aa442018-07-03 11:57:54 +01001049 snippets = {'generator_script': os.path.basename(__file__)}
1050 read_code_from_input_files(platform_file, helpers_file,
1051 out_data_file, snippets)
1052 add_input_info(funcs_file, data_file, template_file,
1053 c_file, snippets)
1054 suite_dependencies, func_info = parse_function_file(funcs_file, snippets)
1055 generate_intermediate_data_file(data_file, out_data_file,
1056 suite_dependencies, func_info, snippets)
1057 write_test_source_file(template_file, c_file, snippets)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001058
1059
Azim Khan8d686bf2018-07-04 23:29:46 +01001060def main():
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001061 """
1062 Command line parser.
1063
1064 :return:
1065 """
Azim Khan040b6a22018-06-28 16:49:13 +01001066 parser = argparse.ArgumentParser(
Azim Khane3b26af2018-06-29 02:36:57 +01001067 description='Dynamically generate test suite code.')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001068
1069 parser.add_argument("-f", "--functions-file",
1070 dest="funcs_file",
1071 help="Functions file",
Azim Khane3b26af2018-06-29 02:36:57 +01001072 metavar="FUNCTIONS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001073 required=True)
1074
1075 parser.add_argument("-d", "--data-file",
1076 dest="data_file",
1077 help="Data file",
Azim Khane3b26af2018-06-29 02:36:57 +01001078 metavar="DATA_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001079 required=True)
1080
1081 parser.add_argument("-t", "--template-file",
1082 dest="template_file",
1083 help="Template file",
Azim Khane3b26af2018-06-29 02:36:57 +01001084 metavar="TEMPLATE_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001085 required=True)
1086
1087 parser.add_argument("-s", "--suites-dir",
1088 dest="suites_dir",
1089 help="Suites dir",
Azim Khane3b26af2018-06-29 02:36:57 +01001090 metavar="SUITES_DIR",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001091 required=True)
1092
Azim Khane3b26af2018-06-29 02:36:57 +01001093 parser.add_argument("--helpers-file",
1094 dest="helpers_file",
1095 help="Helpers file",
1096 metavar="HELPERS_FILE",
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001097 required=True)
1098
1099 parser.add_argument("-p", "--platform-file",
1100 dest="platform_file",
1101 help="Platform code file",
1102 metavar="PLATFORM_FILE",
1103 required=True)
1104
1105 parser.add_argument("-o", "--out-dir",
1106 dest="out_dir",
1107 help="Dir where generated code and scripts are copied",
1108 metavar="OUT_DIR",
1109 required=True)
1110
1111 args = parser.parse_args()
1112
1113 data_file_name = os.path.basename(args.data_file)
1114 data_name = os.path.splitext(data_file_name)[0]
1115
1116 out_c_file = os.path.join(args.out_dir, data_name + '.c')
Mohammad Azim Khan00c4b092018-06-28 13:10:19 +01001117 out_data_file = os.path.join(args.out_dir, data_name + '.datax')
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001118
1119 out_c_file_dir = os.path.dirname(out_c_file)
1120 out_data_file_dir = os.path.dirname(out_data_file)
Azim Khanb31aa442018-07-03 11:57:54 +01001121 for directory in [out_c_file_dir, out_data_file_dir]:
1122 if not os.path.exists(directory):
1123 os.makedirs(directory)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001124
Azim Khanb31aa442018-07-03 11:57:54 +01001125 generate_code(funcs_file=args.funcs_file, data_file=args.data_file,
1126 template_file=args.template_file,
1127 platform_file=args.platform_file,
1128 helpers_file=args.helpers_file, suites_dir=args.suites_dir,
1129 c_file=out_c_file, out_data_file=out_data_file)
Mohammad Azim Khanfff49042017-03-28 01:48:31 +01001130
1131
1132if __name__ == "__main__":
Mohammad Azim Khan3b06f222018-06-26 14:35:25 +01001133 try:
Azim Khan8d686bf2018-07-04 23:29:46 +01001134 main()
Azim Khanb31aa442018-07-03 11:57:54 +01001135 except GeneratorInputError as err:
Mohammad Azim Khan440d8732018-07-18 12:50:49 +01001136 sys.exit("%s: input error: %s" %
1137 (os.path.basename(sys.argv[0]), str(err)))