From fcdf68530265ecb15d0b846aae915c2df6fb203b Mon Sep 17 00:00:00 2001 From: Azim Khan Date: Thu, 5 Jul 2018 17:31:46 +0100 Subject: [PATCH] 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. --- tests/scripts/generate_test_code.py | 46 +++++---- tests/scripts/test_generate_test_code.py | 122 ++++++++++++++++------- 2 files changed, 112 insertions(+), 56 deletions(-) diff --git a/tests/scripts/generate_test_code.py b/tests/scripts/generate_test_code.py index b744d7c07..b01bd3511 100755 --- a/tests/scripts/generate_test_code.py +++ b/tests/scripts/generate_test_code.py @@ -185,11 +185,10 @@ END_CASE_REGEX = r'/\*\s*END_CASE\s*\*/' DEPENDENCY_REGEX = r'depends_on:(?P.*)' 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\w+)\s*\(' INT_CHECK_REGEX = r'int\s+.*' CHAR_CHECK_REGEX = r'char\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'.*\)' EXIT_LABEL_REGEX = r'^exit:' @@ -451,7 +450,7 @@ def parse_function_dependencies(line): return dependencies -def parse_function_signature(line): +def parse_function_arguments(line): """ Parses test function signature for validation and generates 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 signature. - :return: function name, argument list, local variables for + :return: argument list, local variables for wrapper function and argument dispatch code. """ args = [] local_vars = '' 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 + # Remove characters before arguments + line = line[line.find('(') + 1:] # Process arguments, ex: arg1, arg2 ) # This script assumes that the argument list is terminated by ')' # 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) 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): @@ -514,30 +509,38 @@ def parse_function_code(funcs_f, dependencies, suite_dependencies): :param suite_dependencies: List of test suite dependencies :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 for line in funcs_f: - # Check function signature. This script expects function name - # and return type to be specified at the same line. - match = re.match(FUNCTION_ARG_LIST_START_REGEX, line, re.I) + # Check function signature. Function signature may be split + # across multiple lines. Here we try to find the start of + # 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: # 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): for lin in funcs_f: line += lin if re.search(FUNCTION_ARG_LIST_END_REGEX, line): break - name, args, local_vars, args_dispatch = parse_function_signature( + args, local_vars, args_dispatch = parse_function_arguments( line) - code += line.replace(name, 'test_' + name, 1) - name = 'test_' + name - break - else: code += line + break + code += line else: raise GeneratorInputError("file: %s - Test functions not found!" % funcs_f.name) + # Prefix test function name with 'test_' + code = code.replace(name, 'test_' + name, 1) + name = 'test_' + name + for line in funcs_f: if re.search(END_CASE_REGEX, line): break @@ -557,7 +560,8 @@ def parse_function_code(funcs_f, dependencies, suite_dependencies): ; }""".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 = \ gen_dependencies(dependencies) dispatch_code = gen_dispatch(name, suite_dependencies + dependencies) diff --git a/tests/scripts/test_generate_test_code.py b/tests/scripts/test_generate_test_code.py index 29d9e4f44..149159c8c 100755 --- a/tests/scripts/test_generate_test_code.py +++ b/tests/scripts/test_generate_test_code.py @@ -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_suite_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 END_SUITE_HELPERS_REGEX, escaped_split from generate_test_code import parse_test_data, gen_dep_check @@ -476,7 +476,7 @@ class ParseFuncDependencies(TestCase): class ParseFuncSignature(TestCase): """ - Test Suite for parse_function_signature(). + Test Suite for parse_function_arguments(). """ def test_int_and_char_params(self): @@ -485,8 +485,7 @@ class ParseFuncSignature(TestCase): :return: """ line = 'void entropy_threshold( char * a, int b, int result )' - name, args, local, arg_dispatch = parse_function_signature(line) - self.assertEqual(name, 'entropy_threshold') + args, local, arg_dispatch = parse_function_arguments(line) self.assertEqual(args, ['char*', 'int', 'int']) self.assertEqual(local, '') self.assertEqual(arg_dispatch, ['(char *) params[0]', @@ -499,8 +498,7 @@ class ParseFuncSignature(TestCase): :return: """ line = 'void entropy_threshold( char * a, data_t * h, int result )' - name, args, local, arg_dispatch = parse_function_signature(line) - self.assertEqual(name, 'entropy_threshold') + args, local, arg_dispatch = parse_function_arguments(line) self.assertEqual(args, ['char*', 'hex', 'int']) self.assertEqual(local, ' data_t data1 = {(uint8_t *) params[1], ' @@ -509,21 +507,13 @@ class ParseFuncSignature(TestCase): '&data1', '*( (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): """ Test unsupported arguments (not among int, char * and data_t) :return: """ - line = 'int entropy_threshold( char * a, data_t * h, int * result )' - self.assertRaises(ValueError, parse_function_signature, line) + line = 'void entropy_threshold( char * a, data_t * h, char result )' + self.assertRaises(ValueError, parse_function_arguments, line) def test_no_params(self): """ @@ -531,8 +521,7 @@ class ParseFuncSignature(TestCase): :return: """ line = 'void entropy_threshold()' - name, args, local, arg_dispatch = parse_function_signature(line) - self.assertEqual(name, 'entropy_threshold') + args, local, arg_dispatch = parse_function_arguments(line) self.assertEqual(args, []) self.assertEqual(local, '') self.assertEqual(arg_dispatch, []) @@ -554,8 +543,9 @@ test function ''' 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): """ @@ -568,17 +558,19 @@ void test_func() } ''' 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, - parse_function_signature_mock): + parse_function_arguments_mock): """ Test parse_function_code() :return: """ - parse_function_signature_mock.return_value = ('test_func', [], '', []) + parse_function_arguments_mock.return_value = ([], '', []) data = ''' void test_func() { @@ -587,14 +579,14 @@ void test_func() stream = StringIOWrapper('test_suite_ut.function', data) self.assertRaises(GeneratorInputError, parse_function_code, stream, [], []) - self.assertTrue(parse_function_signature_mock.called) - parse_function_signature_mock.assert_called_with('void test_func()\n') + self.assertTrue(parse_function_arguments_mock.called) + parse_function_arguments_mock.assert_called_with('void test_func()\n') @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_signature") - def test_return(self, parse_function_signature_mock, + @patch("generate_test_code.parse_function_arguments") + def test_return(self, parse_function_arguments_mock, gen_function_wrapper_mock, gen_dependencies_mock, gen_dispatch_mock): @@ -602,7 +594,7 @@ void test_func() Test generated code. :return: """ - parse_function_signature_mock.return_value = ('func', [], '', []) + 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 @@ -617,8 +609,8 @@ void func() stream = StringIOWrapper('test_suite_ut.function', data) name, arg, code, dispatch_code = parse_function_code(stream, [], []) - self.assertTrue(parse_function_signature_mock.called) - parse_function_signature_mock.assert_called_with('void func()\n') + self.assertTrue(parse_function_arguments_mock.called) + parse_function_arguments_mock.assert_called_with('void func()\n') gen_function_wrapper_mock.assert_called_with('test_func', '', []) self.assertEqual(name, 'test_func') self.assertEqual(arg, []) @@ -638,8 +630,8 @@ exit: @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_signature") - def test_with_exit_label(self, parse_function_signature_mock, + @patch("generate_test_code.parse_function_arguments") + def test_with_exit_label(self, parse_function_arguments_mock, gen_function_wrapper_mock, gen_dependencies_mock, gen_dispatch_mock): @@ -647,7 +639,7 @@ exit: Test when exit label is present. :return: """ - parse_function_signature_mock.return_value = ('func', [], '', []) + 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 @@ -675,6 +667,66 @@ exit: yes sir yes sir 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)