diff --git a/httprunner/parser.py b/httprunner/parser.py index 898e4cca..9e014066 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -464,7 +464,7 @@ def parse_string_functions(content, variables_mapping, functions_mapping): return content -def parse_string_variables(content, variables_mapping): +def parse_string_variables(content, variables_mapping, functions_mapping): """ parse string content with variables mapping. Args: @@ -477,7 +477,7 @@ def parse_string_variables(content, variables_mapping): Examples: >>> content = "/api/users/$uid" >>> variables_mapping = {"$uid": 1000} - >>> parse_string_variables(content, variables_mapping) + >>> parse_string_variables(content, variables_mapping, {}) "/api/users/1000" """ @@ -485,30 +485,52 @@ def parse_string_variables(content, variables_mapping): for variable_name in variables_list: variable_value = get_mapping_variable(variable_name, variables_mapping) + if variable_name == "request" and isinstance(variable_value, dict) \ + and "url" in variable_value and "method" in variable_value: + # call setup_hooks action with $request + for key, value in variable_value.items(): + variable_value[key] = parse_data( + value, + variables_mapping, + functions_mapping + ) + parsed_variable_value = variable_value + elif "${}".format(variable_name) == variable_value: + # variable_name = "token" + # variables_mapping = {"token": "$token"} + parsed_variable_value = variable_value + else: + parsed_variable_value = parse_data( + variable_value, + variables_mapping, + functions_mapping + ) + # TODO: replace variable label from $var to {{var}} if "${}".format(variable_name) == content: # content is a variable - content = variable_value + content = parsed_variable_value else: # content contains one or several variables - if not isinstance(variable_value, str): - variable_value = builtin_str(variable_value) + if not isinstance(parsed_variable_value, str): + parsed_variable_value = builtin_str(parsed_variable_value) content = content.replace( "${}".format(variable_name), - variable_value, 1 + parsed_variable_value, 1 ) return content -def parse_data(content, variables_mapping=None, functions_mapping=None): +def parse_data(content, variables_mapping=None, functions_mapping=None, raise_if_variable_not_found=True): """ parse content with variables mapping Args: content (str/dict/list/numeric/bool/type): content to be parsed variables_mapping (dict): variables mapping. functions_mapping (dict): functions mapping. + raise_if_variable_not_found (bool): if set False, exception will not raise when VariableNotFound occurred. Returns: parsed content. @@ -536,15 +558,30 @@ def parse_data(content, variables_mapping=None, functions_mapping=None): if isinstance(content, (list, set, tuple)): return [ - parse_data(item, variables_mapping, functions_mapping) + parse_data( + item, + variables_mapping, + functions_mapping, + raise_if_variable_not_found + ) for item in content ] if isinstance(content, dict): parsed_content = {} for key, value in content.items(): - parsed_key = parse_data(key, variables_mapping, functions_mapping) - parsed_value = parse_data(value, variables_mapping, functions_mapping) + parsed_key = parse_data( + key, + variables_mapping, + functions_mapping, + raise_if_variable_not_found + ) + parsed_value = parse_data( + value, + variables_mapping, + functions_mapping, + raise_if_variable_not_found + ) parsed_content[parsed_key] = parsed_value return parsed_content @@ -555,12 +592,23 @@ def parse_data(content, variables_mapping=None, functions_mapping=None): functions_mapping = functions_mapping or {} content = content.strip() - # replace functions with evaluated value - # Notice: _eval_content_functions must be called before _eval_content_variables - content = parse_string_functions(content, variables_mapping, functions_mapping) - - # replace variables with binding value - content = parse_string_variables(content, variables_mapping) + try: + # replace functions with evaluated value + # Notice: parse_string_functions must be called before parse_string_variables + content = parse_string_functions( + content, + variables_mapping, + functions_mapping + ) + # replace variables with binding value + content = parse_string_variables( + content, + variables_mapping, + functions_mapping + ) + except exceptions.VariableNotFound: + if raise_if_variable_not_found: + raise return content @@ -707,14 +755,12 @@ def __parse_config(config, project_mapping): # parse config variables parsed_config_variables = {} for key, value in raw_config_variables_mapping.items(): - try: - parsed_value = parse_data( - value, - raw_config_variables_mapping, - functions - ) - except exceptions.VariableNotFound: - pass + parsed_value = parse_data( + value, + raw_config_variables_mapping, + functions, + raise_if_variable_not_found=False + ) parsed_config_variables[key] = parsed_value if parsed_config_variables: @@ -771,16 +817,20 @@ def __parse_testcase_tests(tests, config, project_mapping): test_dict.pop("variables", {}), config_variables ) + test_dict["variables"] = parse_data( + test_dict["variables"], + test_dict["variables"], + functions, + raise_if_variable_not_found=False + ) # parse test_dict name - try: - test_dict["name"] = parse_data( - test_dict.pop("name", ""), - test_dict["variables"], - functions - ) - except exceptions.VariableNotFound: - pass + test_dict["name"] = parse_data( + test_dict.pop("name", ""), + test_dict["variables"], + functions, + raise_if_variable_not_found=False + ) if "testcase_def" in test_dict: # test_dict is nested testcase @@ -808,34 +858,17 @@ def __parse_testcase_tests(tests, config, project_mapping): ) # build path with base_url - try: - request_url = parse_data( - test_dict["request"]["url"], - test_dict["variables"], - functions - ) - except exceptions.VariableNotFound: - """ variable in current url maybe extracted from former api - "teststeps": [ - { - "request": {}, - "extract": { - "host1": content.host1 - } - }, - { - "request": { - "url": "$host1/path1" - } - } - ] - """ - request_url = test_dict["request"]["url"] - finally: - test_dict["request"]["url"] = utils.build_url( - base_url, - request_url - ) + # variable in current url maybe extracted from former api + request_url = parse_data( + test_dict["request"]["url"], + test_dict["variables"], + functions, + raise_if_variable_not_found=False + ) + test_dict["request"]["url"] = utils.build_url( + base_url, + request_url + ) def _parse_testcase(testcase, project_mapping): diff --git a/tests/test_parser.py b/tests/test_parser.py index a38cc305..9f64dce4 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -212,7 +212,7 @@ class TestParser(unittest.TestCase): "/abc/def/abc" ) self.assertEqual( - parser.parse_string_variables("${func($var_1, $var_2, xyz)}", variables_mapping), + parser.parse_string_variables("${func($var_1, $var_2, xyz)}", variables_mapping, {}), "${func(abc, def, xyz)}" ) self.assertEqual( @@ -483,7 +483,7 @@ class TestParser(unittest.TestCase): parsed_tests_mapping = parser.parse_tests(tests_mapping) test_dict1_variables = parsed_tests_mapping["testcases"][0]["teststeps"][0]["variables"] self.assertEqual(test_dict1_variables["creator"], "user_test_001") - self.assertEqual(test_dict1_variables["username"], "$creator") + self.assertEqual(test_dict1_variables["username"], "user_test_001") def test_parse_tests_base_url_priority(self): """ base_url & verify: priority test_dict > config @@ -495,7 +495,7 @@ class TestParser(unittest.TestCase): 'name': '', "base_url": "$host", 'variables': { - "host": "https://debugtalk" + "host": "https://debugtalk.com" }, "verify": False }, @@ -522,7 +522,7 @@ class TestParser(unittest.TestCase): 'name': '', "base_url": "$host1", 'variables': { - "host1": "https://debugtalk" + "host1": "https://debugtalk.com" } }, "teststeps": [ @@ -549,7 +549,7 @@ class TestParser(unittest.TestCase): 'name': '', "base_url": "$host1", 'variables': { - "host1": "https://debugtalk" + "host1": "https://debugtalk.com" } }, "teststeps": [ @@ -569,6 +569,140 @@ class TestParser(unittest.TestCase): test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0] self.assertEqual(test_dict["request"]["url"], "https://httprunner.org/api1") + def test_parse_data_with_variables(self): + variables = { + "host2": "https://httprunner.org", + "num3": "${sum_two($num2, 4)}", + "num2": "${sum_two($num1, 3)}", + "num1": "${sum_two(1, 2)}" + } + from tests.debugtalk import sum_two + functions = { + "sum_two": sum_two + } + parsed_testcase = parser.parse_data(variables, variables, functions) + self.assertEqual(parsed_testcase["num3"], 10) + self.assertEqual(parsed_testcase["num2"], 6) + self.assertEqual(parsed_testcase["num1"], 3) + + def test_parse_data_with_variables_not_found(self): + variables = { + "host": "https://httprunner.org", + "num4": "${sum_two($num0, 5)}", + "num3": "${sum_two($num2, 4)}", + "num2": "${sum_two($num1, 3)}", + "num1": "${sum_two(1, 2)}" + } + from tests.debugtalk import sum_two + functions = { + "sum_two": sum_two + } + with self.assertRaises(exceptions.VariableNotFound): + parser.parse_data(variables, variables, functions) + + parsed_testcase = parser.parse_data( + variables, + variables, + functions, + raise_if_variable_not_found=False + ) + self.assertEqual(parsed_testcase["num3"], 10) + self.assertEqual(parsed_testcase["num2"], 6) + self.assertEqual(parsed_testcase["num1"], 3) + self.assertEqual(parsed_testcase["num4"], "${sum_two($num0, 5)}") + + def test_parse_tests_variable_with_function(self): + from tests.debugtalk import sum_two + tests_mapping = { + "project_mapping": { + "functions": { + "sum_two": sum_two + } + }, + 'testcases': [ + { + "config": { + 'name': '', + "base_url": "$host1", + 'variables': { + "host1": "https://debugtalk.com" + } + }, + "teststeps": [ + { + 'name': 'testcase1', + "base_url": "$host2", + "variables": { + "host2": "https://httprunner.org", + "num3": "${sum_two($num2, 4)}", + "num2": "${sum_two($num1, 3)}", + "num1": "${sum_two(1, 2)}" + }, + 'request': { + 'url': '/api1/?num1=$num1&num2=$num2&num3=$num3', + 'method': 'GET' + } + } + ] + } + ] + } + parsed_tests_mapping = parser.parse_tests(tests_mapping) + test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0] + self.assertEqual(test_dict["variables"]["num3"], 10) + self.assertEqual(test_dict["variables"]["num2"], 6) + self.assertEqual( + test_dict["request"]["url"], + "https://httprunner.org/api1/?num1=3&num2=6&num3=10" + ) + + def test_parse_tests_variable_not_found(self): + from tests.debugtalk import sum_two + tests_mapping = { + "project_mapping": { + "functions": { + "sum_two": sum_two + } + }, + 'testcases': [ + { + "config": { + 'name': '', + "base_url": "$host1", + 'variables': { + "host1": "https://debugtalk.com" + } + }, + "teststeps": [ + { + 'name': 'testcase1', + "base_url": "$host2", + "variables": { + "host2": "https://httprunner.org", + "num4": "${sum_two($num0, 5)}", + "num3": "${sum_two($num2, 4)}", + "num2": "${sum_two($num1, 3)}", + "num1": "${sum_two(1, 2)}" + }, + 'request': { + 'url': '/api1/?num1=$num1&num2=$num2&num3=$num3&num4=$num4', + 'method': 'GET' + } + } + ] + } + ] + } + parsed_tests_mapping = parser.parse_tests(tests_mapping) + test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0] + self.assertEqual(test_dict["variables"]["num3"], 10) + self.assertEqual(test_dict["variables"]["num2"], 6) + self.assertEqual(test_dict["variables"]["num4"], "${sum_two($num0, 5)}") + self.assertEqual( + test_dict["request"]["url"], + "https://httprunner.org/api1/?num1=$num1&num2=$num2&num3=$num3&num4=$num4" + ) + def test_parse_tests_base_url_teststep_empty(self): """ base_url & verify: priority test_dict > config """ @@ -579,7 +713,7 @@ class TestParser(unittest.TestCase): 'name': '', "base_url": "$host", 'variables': { - "host": "https://debugtalk" + "host": "https://debugtalk.com" }, "verify": False }, @@ -595,7 +729,7 @@ class TestParser(unittest.TestCase): } parsed_tests_mapping = parser.parse_tests(tests_mapping) test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0] - self.assertEqual(test_dict["request"]["url"], "https://debugtalk/api1") + self.assertEqual(test_dict["request"]["url"], "https://debugtalk.com/api1") self.assertEqual(test_dict["request"]["verify"], True) def test_parse_environ(self):