fix bug when nested function and variable exists in variables:

e.g.

"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)}"
}
This commit is contained in:
debugtalk
2018-12-19 19:36:42 +08:00
parent 363420053c
commit b6a1a25b3e
2 changed files with 234 additions and 67 deletions
+75 -42
View File
@@ -464,7 +464,7 @@ def parse_string_functions(content, variables_mapping, functions_mapping):
return content return content
def parse_string_variables(content, variables_mapping): def parse_string_variables(content, variables_mapping, functions_mapping):
""" parse string content with variables mapping. """ parse string content with variables mapping.
Args: Args:
@@ -477,7 +477,7 @@ def parse_string_variables(content, variables_mapping):
Examples: Examples:
>>> content = "/api/users/$uid" >>> content = "/api/users/$uid"
>>> variables_mapping = {"$uid": 1000} >>> variables_mapping = {"$uid": 1000}
>>> parse_string_variables(content, variables_mapping) >>> parse_string_variables(content, variables_mapping, {})
"/api/users/1000" "/api/users/1000"
""" """
@@ -485,30 +485,52 @@ def parse_string_variables(content, variables_mapping):
for variable_name in variables_list: for variable_name in variables_list:
variable_value = get_mapping_variable(variable_name, variables_mapping) 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}} # TODO: replace variable label from $var to {{var}}
if "${}".format(variable_name) == content: if "${}".format(variable_name) == content:
# content is a variable # content is a variable
content = variable_value content = parsed_variable_value
else: else:
# content contains one or several variables # content contains one or several variables
if not isinstance(variable_value, str): if not isinstance(parsed_variable_value, str):
variable_value = builtin_str(variable_value) parsed_variable_value = builtin_str(parsed_variable_value)
content = content.replace( content = content.replace(
"${}".format(variable_name), "${}".format(variable_name),
variable_value, 1 parsed_variable_value, 1
) )
return content 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 """ parse content with variables mapping
Args: Args:
content (str/dict/list/numeric/bool/type): content to be parsed content (str/dict/list/numeric/bool/type): content to be parsed
variables_mapping (dict): variables mapping. variables_mapping (dict): variables mapping.
functions_mapping (dict): functions mapping. functions_mapping (dict): functions mapping.
raise_if_variable_not_found (bool): if set False, exception will not raise when VariableNotFound occurred.
Returns: Returns:
parsed content. parsed content.
@@ -536,15 +558,30 @@ def parse_data(content, variables_mapping=None, functions_mapping=None):
if isinstance(content, (list, set, tuple)): if isinstance(content, (list, set, tuple)):
return [ return [
parse_data(item, variables_mapping, functions_mapping) parse_data(
item,
variables_mapping,
functions_mapping,
raise_if_variable_not_found
)
for item in content for item in content
] ]
if isinstance(content, dict): if isinstance(content, dict):
parsed_content = {} parsed_content = {}
for key, value in content.items(): for key, value in content.items():
parsed_key = parse_data(key, variables_mapping, functions_mapping) parsed_key = parse_data(
parsed_value = parse_data(value, variables_mapping, functions_mapping) 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 parsed_content[parsed_key] = parsed_value
return parsed_content return parsed_content
@@ -555,12 +592,23 @@ def parse_data(content, variables_mapping=None, functions_mapping=None):
functions_mapping = functions_mapping or {} functions_mapping = functions_mapping or {}
content = content.strip() content = content.strip()
try:
# replace functions with evaluated value # replace functions with evaluated value
# Notice: _eval_content_functions must be called before _eval_content_variables # Notice: parse_string_functions must be called before parse_string_variables
content = parse_string_functions(content, variables_mapping, functions_mapping) content = parse_string_functions(
content,
variables_mapping,
functions_mapping
)
# replace variables with binding value # replace variables with binding value
content = parse_string_variables(content, variables_mapping) content = parse_string_variables(
content,
variables_mapping,
functions_mapping
)
except exceptions.VariableNotFound:
if raise_if_variable_not_found:
raise
return content return content
@@ -707,14 +755,12 @@ def __parse_config(config, project_mapping):
# parse config variables # parse config variables
parsed_config_variables = {} parsed_config_variables = {}
for key, value in raw_config_variables_mapping.items(): for key, value in raw_config_variables_mapping.items():
try:
parsed_value = parse_data( parsed_value = parse_data(
value, value,
raw_config_variables_mapping, raw_config_variables_mapping,
functions functions,
raise_if_variable_not_found=False
) )
except exceptions.VariableNotFound:
pass
parsed_config_variables[key] = parsed_value parsed_config_variables[key] = parsed_value
if parsed_config_variables: if parsed_config_variables:
@@ -771,16 +817,20 @@ def __parse_testcase_tests(tests, config, project_mapping):
test_dict.pop("variables", {}), test_dict.pop("variables", {}),
config_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 # parse test_dict name
try:
test_dict["name"] = parse_data( test_dict["name"] = parse_data(
test_dict.pop("name", ""), test_dict.pop("name", ""),
test_dict["variables"], test_dict["variables"],
functions functions,
raise_if_variable_not_found=False
) )
except exceptions.VariableNotFound:
pass
if "testcase_def" in test_dict: if "testcase_def" in test_dict:
# test_dict is nested testcase # test_dict is nested testcase
@@ -808,30 +858,13 @@ def __parse_testcase_tests(tests, config, project_mapping):
) )
# build path with base_url # build path with base_url
try: # variable in current url maybe extracted from former api
request_url = parse_data( request_url = parse_data(
test_dict["request"]["url"], test_dict["request"]["url"],
test_dict["variables"], test_dict["variables"],
functions functions,
raise_if_variable_not_found=False
) )
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( test_dict["request"]["url"] = utils.build_url(
base_url, base_url,
request_url request_url
+141 -7
View File
@@ -212,7 +212,7 @@ class TestParser(unittest.TestCase):
"/abc/def/abc" "/abc/def/abc"
) )
self.assertEqual( 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)}" "${func(abc, def, xyz)}"
) )
self.assertEqual( self.assertEqual(
@@ -483,7 +483,7 @@ class TestParser(unittest.TestCase):
parsed_tests_mapping = parser.parse_tests(tests_mapping) parsed_tests_mapping = parser.parse_tests(tests_mapping)
test_dict1_variables = parsed_tests_mapping["testcases"][0]["teststeps"][0]["variables"] 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["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): def test_parse_tests_base_url_priority(self):
""" base_url & verify: priority test_dict > config """ base_url & verify: priority test_dict > config
@@ -495,7 +495,7 @@ class TestParser(unittest.TestCase):
'name': '', 'name': '',
"base_url": "$host", "base_url": "$host",
'variables': { 'variables': {
"host": "https://debugtalk" "host": "https://debugtalk.com"
}, },
"verify": False "verify": False
}, },
@@ -522,7 +522,7 @@ class TestParser(unittest.TestCase):
'name': '', 'name': '',
"base_url": "$host1", "base_url": "$host1",
'variables': { 'variables': {
"host1": "https://debugtalk" "host1": "https://debugtalk.com"
} }
}, },
"teststeps": [ "teststeps": [
@@ -549,7 +549,7 @@ class TestParser(unittest.TestCase):
'name': '', 'name': '',
"base_url": "$host1", "base_url": "$host1",
'variables': { 'variables': {
"host1": "https://debugtalk" "host1": "https://debugtalk.com"
} }
}, },
"teststeps": [ "teststeps": [
@@ -569,6 +569,140 @@ class TestParser(unittest.TestCase):
test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0] test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0]
self.assertEqual(test_dict["request"]["url"], "https://httprunner.org/api1") 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): def test_parse_tests_base_url_teststep_empty(self):
""" base_url & verify: priority test_dict > config """ base_url & verify: priority test_dict > config
""" """
@@ -579,7 +713,7 @@ class TestParser(unittest.TestCase):
'name': '', 'name': '',
"base_url": "$host", "base_url": "$host",
'variables': { 'variables': {
"host": "https://debugtalk" "host": "https://debugtalk.com"
}, },
"verify": False "verify": False
}, },
@@ -595,7 +729,7 @@ class TestParser(unittest.TestCase):
} }
parsed_tests_mapping = parser.parse_tests(tests_mapping) parsed_tests_mapping = parser.parse_tests(tests_mapping)
test_dict = parsed_tests_mapping["testcases"][0]["teststeps"][0] 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) self.assertEqual(test_dict["request"]["verify"], True)
def test_parse_environ(self): def test_parse_environ(self):