Make test function parsing robust

This commit enhances parsing of the test function in generate_test_code.py for
cases where return type and function name are on separate lines.
This commit is contained in:
Azim Khan 2018-07-05 17:31:46 +01:00 committed by Mohammad Azim Khan
parent 4084ec7ae5
commit fcdf685302
2 changed files with 112 additions and 56 deletions

View File

@ -185,11 +185,10 @@ END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/'
DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)' DEPENDENCY_REGEX = r'depends_on:(?P<dependencies>.*)'
C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*' C_IDENTIFIER_REGEX = r'!?[a-z_][a-z0-9_]*'
TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(\w+)\s*\(' TEST_FUNCTION_VALIDATION_REGEX = r'\s*void\s+(?P<func_name>\w+)\s*\('
INT_CHECK_REGEX = r'int\s+.*' INT_CHECK_REGEX = r'int\s+.*'
CHAR_CHECK_REGEX = r'char\s*\*\s*.*' CHAR_CHECK_REGEX = r'char\s*\*\s*.*'
DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*' DATA_T_CHECK_REGEX = r'data_t\s*\*\s*.*'
FUNCTION_ARG_LIST_START_REGEX = r'.*?\s+(\w+)\s*\('
FUNCTION_ARG_LIST_END_REGEX = r'.*\)' FUNCTION_ARG_LIST_END_REGEX = r'.*\)'
EXIT_LABEL_REGEX = r'^exit:' EXIT_LABEL_REGEX = r'^exit:'
@ -451,7 +450,7 @@ def parse_function_dependencies(line):
return dependencies return dependencies
def parse_function_signature(line): def parse_function_arguments(line):
""" """
Parses test function signature for validation and generates Parses test function signature for validation and generates
a dispatch wrapper function that translates input test vectors a dispatch wrapper function that translates input test vectors
@ -459,19 +458,15 @@ def parse_function_signature(line):
:param line: Line from .function file that has a function :param line: Line from .function file that has a function
signature. signature.
:return: function name, argument list, local variables for :return: argument list, local variables for
wrapper function and argument dispatch code. wrapper function and argument dispatch code.
""" """
args = [] args = []
local_vars = '' local_vars = ''
args_dispatch = [] args_dispatch = []
# Check if the test function returns void.
match = re.search(TEST_FUNCTION_VALIDATION_REGEX, line, re.I)
if not match:
raise ValueError("Test function should return 'void'\n%s" % line)
name = match.group(1)
line = line[len(match.group(0)):]
arg_idx = 0 arg_idx = 0
# Remove characters before arguments
line = line[line.find('(') + 1:]
# Process arguments, ex: <type> arg1, <type> arg2 ) # Process arguments, ex: <type> arg1, <type> arg2 )
# This script assumes that the argument list is terminated by ')' # This script assumes that the argument list is terminated by ')'
# i.e. the test functions will not have a function pointer # i.e. the test functions will not have a function pointer
@ -501,7 +496,7 @@ def parse_function_signature(line):
"'char *' or 'data_t'\n%s" % line) "'char *' or 'data_t'\n%s" % line)
arg_idx += 1 arg_idx += 1
return name, args, local_vars, args_dispatch return args, local_vars, args_dispatch
def parse_function_code(funcs_f, dependencies, suite_dependencies): def parse_function_code(funcs_f, dependencies, suite_dependencies):
@ -514,30 +509,38 @@ def parse_function_code(funcs_f, dependencies, suite_dependencies):
:param suite_dependencies: List of test suite dependencies :param suite_dependencies: List of test suite dependencies
:return: Function name, arguments, function code and dispatch code. :return: Function name, arguments, function code and dispatch code.
""" """
code = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name) line_directive = '#line %d "%s"\n' % (funcs_f.line_no + 1, funcs_f.name)
code = ''
has_exit_label = False has_exit_label = False
for line in funcs_f: for line in funcs_f:
# Check function signature. This script expects function name # Check function signature. Function signature may be split
# and return type to be specified at the same line. # across multiple lines. Here we try to find the start of
match = re.match(FUNCTION_ARG_LIST_START_REGEX, line, re.I) # arguments list, then remove '\n's and apply the regex to
# detect function start.
up_to_arg_list_start = code + line[:line.find('(') + 1]
match = re.match(TEST_FUNCTION_VALIDATION_REGEX,
up_to_arg_list_start.replace('\n', ' '), re.I)
if match: if match:
# check if we have full signature i.e. split in more lines # check if we have full signature i.e. split in more lines
name = match.group('func_name')
if not re.match(FUNCTION_ARG_LIST_END_REGEX, line): if not re.match(FUNCTION_ARG_LIST_END_REGEX, line):
for lin in funcs_f: for lin in funcs_f:
line += lin line += lin
if re.search(FUNCTION_ARG_LIST_END_REGEX, line): if re.search(FUNCTION_ARG_LIST_END_REGEX, line):
break break
name, args, local_vars, args_dispatch = parse_function_signature( args, local_vars, args_dispatch = parse_function_arguments(
line) line)
code += line.replace(name, 'test_' + name, 1)
name = 'test_' + name
break
else:
code += line code += line
break
code += line
else: else:
raise GeneratorInputError("file: %s - Test functions not found!" % raise GeneratorInputError("file: %s - Test functions not found!" %
funcs_f.name) funcs_f.name)
# Prefix test function name with 'test_'
code = code.replace(name, 'test_' + name, 1)
name = 'test_' + name
for line in funcs_f: for line in funcs_f:
if re.search(END_CASE_REGEX, line): if re.search(END_CASE_REGEX, line):
break break
@ -557,7 +560,8 @@ def parse_function_code(funcs_f, dependencies, suite_dependencies):
; ;
}""".join(split_code) }""".join(split_code)
code += gen_function_wrapper(name, local_vars, args_dispatch) code = line_directive + code + gen_function_wrapper(name, local_vars,
args_dispatch)
preprocessor_check_start, preprocessor_check_end = \ preprocessor_check_start, preprocessor_check_end = \
gen_dependencies(dependencies) gen_dependencies(dependencies)
dispatch_code = gen_dispatch(name, suite_dependencies + dependencies) dispatch_code = gen_dispatch(name, suite_dependencies + dependencies)

View File

@ -31,7 +31,7 @@ from generate_test_code import gen_function_wrapper, gen_dispatch
from generate_test_code import parse_until_pattern, GeneratorInputError from generate_test_code import parse_until_pattern, GeneratorInputError
from generate_test_code import parse_suite_dependencies from generate_test_code import parse_suite_dependencies
from generate_test_code import parse_function_dependencies from generate_test_code import parse_function_dependencies
from generate_test_code import parse_function_signature, parse_function_code from generate_test_code import parse_function_arguments, parse_function_code
from generate_test_code import parse_functions, END_HEADER_REGEX from generate_test_code import parse_functions, END_HEADER_REGEX
from generate_test_code import END_SUITE_HELPERS_REGEX, escaped_split from generate_test_code import END_SUITE_HELPERS_REGEX, escaped_split
from generate_test_code import parse_test_data, gen_dep_check from generate_test_code import parse_test_data, gen_dep_check
@ -476,7 +476,7 @@ class ParseFuncDependencies(TestCase):
class ParseFuncSignature(TestCase): class ParseFuncSignature(TestCase):
""" """
Test Suite for parse_function_signature(). Test Suite for parse_function_arguments().
""" """
def test_int_and_char_params(self): def test_int_and_char_params(self):
@ -485,8 +485,7 @@ class ParseFuncSignature(TestCase):
:return: :return:
""" """
line = 'void entropy_threshold( char * a, int b, int result )' line = 'void entropy_threshold( char * a, int b, int result )'
name, args, local, arg_dispatch = parse_function_signature(line) args, local, arg_dispatch = parse_function_arguments(line)
self.assertEqual(name, 'entropy_threshold')
self.assertEqual(args, ['char*', 'int', 'int']) self.assertEqual(args, ['char*', 'int', 'int'])
self.assertEqual(local, '') self.assertEqual(local, '')
self.assertEqual(arg_dispatch, ['(char *) params[0]', self.assertEqual(arg_dispatch, ['(char *) params[0]',
@ -499,8 +498,7 @@ class ParseFuncSignature(TestCase):
:return: :return:
""" """
line = 'void entropy_threshold( char * a, data_t * h, int result )' line = 'void entropy_threshold( char * a, data_t * h, int result )'
name, args, local, arg_dispatch = parse_function_signature(line) args, local, arg_dispatch = parse_function_arguments(line)
self.assertEqual(name, 'entropy_threshold')
self.assertEqual(args, ['char*', 'hex', 'int']) self.assertEqual(args, ['char*', 'hex', 'int'])
self.assertEqual(local, self.assertEqual(local,
' data_t data1 = {(uint8_t *) params[1], ' ' data_t data1 = {(uint8_t *) params[1], '
@ -509,21 +507,13 @@ class ParseFuncSignature(TestCase):
'&data1', '&data1',
'*( (int *) params[3] )']) '*( (int *) params[3] )'])
def test_non_void_function(self):
"""
Test invalid signature (non void).
:return:
"""
line = 'int entropy_threshold( char * a, data_t * h, int result )'
self.assertRaises(ValueError, parse_function_signature, line)
def test_unsupported_arg(self): def test_unsupported_arg(self):
""" """
Test unsupported arguments (not among int, char * and data_t) Test unsupported arguments (not among int, char * and data_t)
:return: :return:
""" """
line = 'int entropy_threshold( char * a, data_t * h, int * result )' line = 'void entropy_threshold( char * a, data_t * h, char result )'
self.assertRaises(ValueError, parse_function_signature, line) self.assertRaises(ValueError, parse_function_arguments, line)
def test_no_params(self): def test_no_params(self):
""" """
@ -531,8 +521,7 @@ class ParseFuncSignature(TestCase):
:return: :return:
""" """
line = 'void entropy_threshold()' line = 'void entropy_threshold()'
name, args, local, arg_dispatch = parse_function_signature(line) args, local, arg_dispatch = parse_function_arguments(line)
self.assertEqual(name, 'entropy_threshold')
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual(local, '') self.assertEqual(local, '')
self.assertEqual(arg_dispatch, []) self.assertEqual(arg_dispatch, [])
@ -554,8 +543,9 @@ test
function function
''' '''
stream = StringIOWrapper('test_suite_ut.function', data) stream = StringIOWrapper('test_suite_ut.function', data)
self.assertRaises(GeneratorInputError, parse_function_code, stream, [], err_msg = 'file: test_suite_ut.function - Test functions not found!'
[]) self.assertRaisesRegexp(GeneratorInputError, err_msg,
parse_function_code, stream, [], [])
def test_no_end_case_comment(self): def test_no_end_case_comment(self):
""" """
@ -568,17 +558,19 @@ void test_func()
} }
''' '''
stream = StringIOWrapper('test_suite_ut.function', data) stream = StringIOWrapper('test_suite_ut.function', data)
self.assertRaises(GeneratorInputError, parse_function_code, stream, [], err_msg = r'file: test_suite_ut.function - '\
[]) 'end case pattern .*? not found!'
self.assertRaisesRegexp(GeneratorInputError, err_msg,
parse_function_code, stream, [], [])
@patch("generate_test_code.parse_function_signature") @patch("generate_test_code.parse_function_arguments")
def test_function_called(self, def test_function_called(self,
parse_function_signature_mock): parse_function_arguments_mock):
""" """
Test parse_function_code() Test parse_function_code()
:return: :return:
""" """
parse_function_signature_mock.return_value = ('test_func', [], '', []) parse_function_arguments_mock.return_value = ([], '', [])
data = ''' data = '''
void test_func() void test_func()
{ {
@ -587,14 +579,14 @@ void test_func()
stream = StringIOWrapper('test_suite_ut.function', data) stream = StringIOWrapper('test_suite_ut.function', data)
self.assertRaises(GeneratorInputError, parse_function_code, self.assertRaises(GeneratorInputError, parse_function_code,
stream, [], []) stream, [], [])
self.assertTrue(parse_function_signature_mock.called) self.assertTrue(parse_function_arguments_mock.called)
parse_function_signature_mock.assert_called_with('void test_func()\n') parse_function_arguments_mock.assert_called_with('void test_func()\n')
@patch("generate_test_code.gen_dispatch") @patch("generate_test_code.gen_dispatch")
@patch("generate_test_code.gen_dependencies") @patch("generate_test_code.gen_dependencies")
@patch("generate_test_code.gen_function_wrapper") @patch("generate_test_code.gen_function_wrapper")
@patch("generate_test_code.parse_function_signature") @patch("generate_test_code.parse_function_arguments")
def test_return(self, parse_function_signature_mock, def test_return(self, parse_function_arguments_mock,
gen_function_wrapper_mock, gen_function_wrapper_mock,
gen_dependencies_mock, gen_dependencies_mock,
gen_dispatch_mock): gen_dispatch_mock):
@ -602,7 +594,7 @@ void test_func()
Test generated code. Test generated code.
:return: :return:
""" """
parse_function_signature_mock.return_value = ('func', [], '', []) parse_function_arguments_mock.return_value = ([], '', [])
gen_function_wrapper_mock.return_value = '' gen_function_wrapper_mock.return_value = ''
gen_dependencies_mock.side_effect = gen_dependencies gen_dependencies_mock.side_effect = gen_dependencies
gen_dispatch_mock.side_effect = gen_dispatch gen_dispatch_mock.side_effect = gen_dispatch
@ -617,8 +609,8 @@ void func()
stream = StringIOWrapper('test_suite_ut.function', data) stream = StringIOWrapper('test_suite_ut.function', data)
name, arg, code, dispatch_code = parse_function_code(stream, [], []) name, arg, code, dispatch_code = parse_function_code(stream, [], [])
self.assertTrue(parse_function_signature_mock.called) self.assertTrue(parse_function_arguments_mock.called)
parse_function_signature_mock.assert_called_with('void func()\n') parse_function_arguments_mock.assert_called_with('void func()\n')
gen_function_wrapper_mock.assert_called_with('test_func', '', []) gen_function_wrapper_mock.assert_called_with('test_func', '', [])
self.assertEqual(name, 'test_func') self.assertEqual(name, 'test_func')
self.assertEqual(arg, []) self.assertEqual(arg, [])
@ -638,8 +630,8 @@ exit:
@patch("generate_test_code.gen_dispatch") @patch("generate_test_code.gen_dispatch")
@patch("generate_test_code.gen_dependencies") @patch("generate_test_code.gen_dependencies")
@patch("generate_test_code.gen_function_wrapper") @patch("generate_test_code.gen_function_wrapper")
@patch("generate_test_code.parse_function_signature") @patch("generate_test_code.parse_function_arguments")
def test_with_exit_label(self, parse_function_signature_mock, def test_with_exit_label(self, parse_function_arguments_mock,
gen_function_wrapper_mock, gen_function_wrapper_mock,
gen_dependencies_mock, gen_dependencies_mock,
gen_dispatch_mock): gen_dispatch_mock):
@ -647,7 +639,7 @@ exit:
Test when exit label is present. Test when exit label is present.
:return: :return:
""" """
parse_function_signature_mock.return_value = ('func', [], '', []) parse_function_arguments_mock.return_value = ([], '', [])
gen_function_wrapper_mock.return_value = '' gen_function_wrapper_mock.return_value = ''
gen_dependencies_mock.side_effect = gen_dependencies gen_dependencies_mock.side_effect = gen_dependencies
gen_dispatch_mock.side_effect = gen_dispatch gen_dispatch_mock.side_effect = gen_dispatch
@ -675,6 +667,66 @@ exit:
yes sir yes sir yes sir yes sir
3 bags full 3 bags full
} }
'''
self.assertEqual(code, expected)
def test_non_void_function(self):
"""
Test invalid signature (non void).
:return:
"""
data = 'int entropy_threshold( char * a, data_t * h, int result )'
err_msg = 'file: test_suite_ut.function - Test functions not found!'
stream = StringIOWrapper('test_suite_ut.function', data)
self.assertRaisesRegexp(GeneratorInputError, err_msg,
parse_function_code, stream, [], [])
@patch("generate_test_code.gen_dispatch")
@patch("generate_test_code.gen_dependencies")
@patch("generate_test_code.gen_function_wrapper")
@patch("generate_test_code.parse_function_arguments")
def test_functio_name_on_newline(self, parse_function_arguments_mock,
gen_function_wrapper_mock,
gen_dependencies_mock,
gen_dispatch_mock):
"""
Test when exit label is present.
:return:
"""
parse_function_arguments_mock.return_value = ([], '', [])
gen_function_wrapper_mock.return_value = ''
gen_dependencies_mock.side_effect = gen_dependencies
gen_dispatch_mock.side_effect = gen_dispatch
data = '''
void
func()
{
ba ba black sheep
have you any wool
exit:
yes sir yes sir
3 bags full
}
/* END_CASE */
'''
stream = StringIOWrapper('test_suite_ut.function', data)
_, _, code, _ = parse_function_code(stream, [], [])
expected = '''#line 1 "test_suite_ut.function"
void
test_func()
{
ba ba black sheep
have you any wool
exit:
yes sir yes sir
3 bags full
}
''' '''
self.assertEqual(code, expected) self.assertEqual(code, expected)