diff --git a/httprunner/api.py b/httprunner/api.py index 798293c2..4e9f27dc 100644 --- a/httprunner/api.py +++ b/httprunner/api.py @@ -60,9 +60,16 @@ class HttpRunner(object): if "config" in test_dict: # run nested testcase test.__doc__ = test_dict["config"].get("name") + variables = test_dict["config"].get("variables", {}) else: # run api test test.__doc__ = test_dict.get("name") + variables = test_dict.get("variables", {}) + + if isinstance(test.__doc__, parser.LazyString): + parsed_variables = parser.parse_variables_mapping(variables, ignore=True) + test.__doc__ = parser.parse_lazy_data( + test.__doc__, parsed_variables) return test diff --git a/httprunner/context.py b/httprunner/context.py index 7d402fbf..2f1f3700 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -36,16 +36,14 @@ class SessionContext(object): """ variables_mapping = variables_mapping or {} variables_mapping = utils.ensure_mapping_format(variables_mapping) + variables_mapping.update(self.session_variables_mapping) + parsed_variables_mapping = parser.parse_variables_mapping(variables_mapping) self.test_variables_mapping = {} # priority: extracted variable > teststep variable - self.test_variables_mapping.update(variables_mapping) + self.test_variables_mapping.update(parsed_variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping) - for variable_name, variable_value in variables_mapping.items(): - variable_value = self.eval_content(variable_value) - self.update_test_variables(variable_name, variable_value) - def update_test_variables(self, variable_name, variable_value): """ update test variables, these variables are only valid in the current test. """ @@ -63,11 +61,7 @@ class SessionContext(object): """ evaluate content recursively, take effect on each variable and function in content. content may be in any data structure, include dict, list, tuple, number, string, etc. """ - return parser.parse_data( - content, - self.test_variables_mapping, - self.FUNCTIONS_MAPPING - ) + return parser.parse_lazy_data(content, self.test_variables_mapping) def __eval_check_item(self, validator, resp_obj): """ evaluate check item in validator. @@ -95,10 +89,8 @@ class SessionContext(object): # 3, dict or list, maybe containing variable/function reference, e.g. {"var": "$abc"} # 4, string joined by delimiter. e.g. "status_code", "headers.content-type" # 5, regex string, e.g. "LB[\d]*(.*)RB[\d]*" - if isinstance(check_item, (dict, list)) \ - or parser.extract_variables(check_item) \ - or parser.extract_functions(check_item): + or isinstance(check_item, parser.LazyString): # format 1/2/3 check_value = self.eval_content(check_item) else: diff --git a/httprunner/parser.py b/httprunner/parser.py index f537ef85..af4f07c6 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -7,9 +7,11 @@ import re from httprunner import exceptions, utils from httprunner.compat import basestring, builtin_str, numeric_types, str -variable_regexp = r"\$([\w_]+)" -function_regexp = r"\$\{([\w_]+\([\$\w\.\-/_ =,]*\))\}" -function_regexp_compile = re.compile(r"^([\w_]+)\(([\$\w\.\-/_ =,]*)\)$") +# TODO: change variable notation from $var to {{var}} +# $var_1 +variable_regex_compile = re.compile(r"\$(\w+)") +# ${func1($var_1, $var_3)} +function_regex_compile = re.compile(r"\$\{(\w+)\(([\$\w\.\-/\s=,]*)\)\}") def parse_string_value(str_value): @@ -28,7 +30,21 @@ def parse_string_value(str_value): return str_value -def extract_variables(content): +def is_variable_exist(content): + if not isinstance(content, basestring): + return False + + return True if variable_regex_compile.search(content) else False + + +def is_function_exist(content): + if not isinstance(content, basestring): + return False + + return True if function_regex_compile.search(content) else False + + +def regex_findall_variables(content): """ extract all variable names from content, which is in format $variable Args: @@ -38,27 +54,26 @@ def extract_variables(content): list: variables list extracted from string content Examples: - >>> extract_variables("$variable") + >>> regex_findall_variables("$variable") ["variable"] - >>> extract_variables("/blog/$postid") + >>> regex_findall_variables("/blog/$postid") ["postid"] - >>> extract_variables("/$var1/$var2") + >>> regex_findall_variables("/$var1/$var2") ["var1", "var2"] - >>> extract_variables("abc") + >>> regex_findall_variables("abc") [] """ - # TODO: change variable notation from $var to {{var}} try: - return re.findall(variable_regexp, content) + return variable_regex_compile.findall(content) except TypeError: return [] -def extract_functions(content): +def regex_findall_functions(content): """ extract all functions from string content, which are in format ${fun()} Args: @@ -68,86 +83,28 @@ def extract_functions(content): list: functions list extracted from string content Examples: - >>> extract_functions("${func(5)}") + >>> regex_findall_functions("${func(5)}") ["func(5)"] - >>> extract_functions("${func(a=1, b=2)}") + >>> regex_findall_functions("${func(a=1, b=2)}") ["func(a=1, b=2)"] - >>> extract_functions("/api/1000?_t=${get_timestamp()}") + >>> regex_findall_functions("/api/1000?_t=${get_timestamp()}") ["get_timestamp()"] - >>> extract_functions("/api/${add(1, 2)}") + >>> regex_findall_functions("/api/${add(1, 2)}") ["add(1, 2)"] - >>> extract_functions("/api/${add(1, 2)}?_t=${get_timestamp()}") + >>> regex_findall_functions("/api/${add(1, 2)}?_t=${get_timestamp()}") ["add(1, 2)", "get_timestamp()"] """ try: - return re.findall(function_regexp, content) + return function_regex_compile.findall(content) except TypeError: return [] -def parse_function(content): - """ parse function name and args from string content. - - Args: - content (str): string content - - Returns: - dict: function meta dict - - { - "func_name": "xxx", - "args": [], - "kwargs": {} - } - - Examples: - >>> parse_function("func()") - {'func_name': 'func', 'args': [], 'kwargs': {}} - - >>> parse_function("func(5)") - {'func_name': 'func', 'args': [5], 'kwargs': {}} - - >>> parse_function("func(1, 2)") - {'func_name': 'func', 'args': [1, 2], 'kwargs': {}} - - >>> parse_function("func(a=1, b=2)") - {'func_name': 'func', 'args': [], 'kwargs': {'a': 1, 'b': 2}} - - >>> parse_function("func(1, 2, a=3, b=4)") - {'func_name': 'func', 'args': [1, 2], 'kwargs': {'a':3, 'b':4}} - - """ - matched = function_regexp_compile.match(content) - if not matched: - raise exceptions.FunctionNotFound("{} not found!".format(content)) - - function_meta = { - "func_name": matched.group(1), - "args": [], - "kwargs": {} - } - - args_str = matched.group(2).strip() - if args_str == "": - return function_meta - - args_list = args_str.split(',') - for arg in args_list: - arg = arg.strip() - if '=' in arg: - key, value = arg.split('=') - function_meta["kwargs"][key.strip()] = parse_string_value(value.strip()) - else: - function_meta["args"].append(parse_string_value(arg)) - - return function_meta - - def parse_validator(validator): """ parse validator @@ -259,7 +216,14 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None) parameter_content_list.append(parameter_content_dict) else: # (2) & (3) - parsed_parameter_content = parse_data(parameter_content, variables_mapping, functions_mapping) + parsed_variables_mapping = parse_variables_mapping( + variables_mapping + ) + parsed_parameter_content = eval_lazy_data( + parameter_content, + parsed_variables_mapping, + functions_mapping + ) if not isinstance(parsed_parameter_content, list): raise exceptions.ParamsError("parameters syntax error!") @@ -335,6 +299,13 @@ def get_mapping_function(function_name, functions_mapping): if function_name in functions_mapping: return functions_mapping[function_name] + elif function_name in ["parameterize", "P"]: + from httprunner import loader + return loader.load_csv_file + + elif function_name in ["environ", "ENV"]: + return utils.get_os_environ + try: # check if HttpRunner builtin functions from httprunner import loader @@ -354,211 +325,415 @@ def get_mapping_function(function_name, functions_mapping): raise exceptions.FunctionNotFound("{} is not found.".format(function_name)) -def parse_string_functions(content, variables_mapping, functions_mapping): - """ parse string content with functions mapping. +def parse_function_params(params): + """ parse function params to args and kwargs. Args: - content (str): string content to be parsed. - variables_mapping (dict): variables mapping. - functions_mapping (dict): functions mapping. + params (str): function param in string Returns: - str: parsed string content. + dict: function meta dict - Examples: - >>> content = "abc${add_one(3)}def" - >>> functions_mapping = {"add_one": lambda x: x + 1} - >>> parse_string_functions(content, functions_mapping) - "abc4def" - - """ - functions_list = extract_functions(content) - for func_content in functions_list: - function_meta = parse_function(func_content) - func_name = function_meta["func_name"] - - args = function_meta.get("args", []) - kwargs = function_meta.get("kwargs", {}) - args = parse_data(args, variables_mapping, functions_mapping) - kwargs = parse_data(kwargs, variables_mapping, functions_mapping) - - if func_name in ["parameterize", "P"]: - if len(args) != 1 or kwargs: - raise exceptions.ParamsError("P() should only pass in one argument!") - from httprunner import loader - eval_value = loader.load_csv_file(args[0]) - elif func_name in ["environ", "ENV"]: - if len(args) != 1 or kwargs: - raise exceptions.ParamsError("ENV() should only pass in one argument!") - eval_value = utils.get_os_environ(args[0]) - else: - func = get_mapping_function(func_name, functions_mapping) - eval_value = func(*args, **kwargs) - - func_content = "${" + func_content + "}" - if func_content == content: - # content is a function, e.g. "${add_one(3)}" - content = eval_value - else: - # content contains one or many functions, e.g. "abc${add_one(3)}def" - content = content.replace( - func_content, - str(eval_value), 1 - ) - - return content - - -def parse_string_variables(content, variables_mapping, functions_mapping): - """ parse string content with variables mapping. - - Args: - content (str): string content to be parsed. - variables_mapping (dict): variables mapping. - - Returns: - str: parsed string content. - - Examples: - >>> content = "/api/users/$uid" - >>> variables_mapping = {"$uid": 1000} - >>> parse_string_variables(content, variables_mapping, {}) - "/api/users/1000" - - """ - variables_list = extract_variables(content) - 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, - raise_if_variable_not_found=False - ) - variables_mapping[variable_name] = parsed_variable_value - # TODO: replace variable label from $var to {{var}} - if "${}".format(variable_name) == content: - # content is a variable - content = parsed_variable_value - else: - # content contains one or several variables - if not isinstance(parsed_variable_value, str): - parsed_variable_value = builtin_str(parsed_variable_value) - - content = content.replace( - "${}".format(variable_name), - parsed_variable_value, 1 - ) - - return content - - -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. - - Examples: - >>> content = { - 'request': { - 'url': '/api/users/$uid', - 'headers': {'token': '$token'} - } - } - >>> variables_mapping = {"uid": 1000, "token": "abcdef"} - >>> parse_data(content, variables_mapping) { - 'request': { - 'url': '/api/users/1000', - 'headers': {'token': 'abcdef'} - } + "args": [], + "kwargs": {} } + Examples: + >>> parse_function_params("") + {'args': [], 'kwargs': {}} + + >>> parse_function_params("5") + {'args': [5], 'kwargs': {}} + + >>> parse_function_params("1, 2") + {'args': [1, 2], 'kwargs': {}} + + >>> parse_function_params("a=1, b=2") + {'args': [], 'kwargs': {'a': 1, 'b': 2}} + + >>> parse_function_params("1, 2, a=3, b=4") + {'args': [1, 2], 'kwargs': {'a':3, 'b':4}} + + """ + function_meta = { + "args": [], + "kwargs": {} + } + + params_str = params.strip() + if params_str == "": + return function_meta + + args_list = params_str.split(',') + for arg in args_list: + arg = arg.strip() + if '=' in arg: + key, value = arg.split('=') + function_meta["kwargs"][key.strip()] = parse_string_value(value.strip()) + else: + function_meta["args"].append(parse_string_value(arg)) + + return function_meta + + +class LazyFunction(object): + """ call function lazily. + """ + def __init__(self, function_meta, functions_mapping=None, check_variables_set=None): + """ init LazyFunction object with function_meta + + Args: + function_meta (dict): function name, args and kwargs. + { + "func_name": "func", + "args": [1, 2] + "kwargs": {"a": 3, "b": 4} + } + + """ + self.functions_mapping = functions_mapping or {} + self.check_variables_set = check_variables_set or set() + self.cache_key = None + self.__parse(function_meta) + + def __parse(self, function_meta): + """ init func as lazy functon instance + + Args: + function_meta (dict): function meta including name, args and kwargs + """ + self._func = get_mapping_function( + function_meta["func_name"], + self.functions_mapping + ) + self._args = prepare_lazy_data( + function_meta.get("args", []), + self.functions_mapping, + self.check_variables_set + ) + self._kwargs = prepare_lazy_data( + function_meta.get("kwargs", {}), + self.functions_mapping, + self.check_variables_set + ) + + if self._func.__name__ == "load_csv_file": + if len(self._args) != 1 or self._kwargs: + raise exceptions.ParamsError("P() should only pass in one argument!") + self._args = [self._args[0]] + elif self._func.__name__ == "get_os_environ": + if len(self._args) != 1 or self._kwargs: + raise exceptions.ParamsError("ENV() should only pass in one argument!") + self._args = [self._args[0]] + + def __repr__(self): + return "LazyFunction({})".format(self._func.__name__) + + def __prepare_cache_key(self, args, kwargs): + return (self._func.__name__, repr(args), repr(kwargs)) + + def to_value(self, variables_mapping=None): + """ parse lazy data with evaluated variables mapping. + Notice: variables_mapping should not contain any variable or function. + """ + variables_mapping = variables_mapping or {} + args = parse_lazy_data(self._args, variables_mapping) + kwargs = parse_lazy_data(self._kwargs, variables_mapping) + self.cache_key = self.__prepare_cache_key(args, kwargs) + return self._func(*args, **kwargs) + + +cached_functions_mapping = {} +""" cached function calling results. +""" + + +class LazyString(object): + """ evaluate string lazily. + """ + def __init__(self, raw_string, functions_mapping=None, check_variables_set=None, cached=False): + """ make raw_string as lazy object with functions_mapping + check if any variable undefined in check_variables_set + """ + self.raw_string = raw_string + self.functions_mapping = functions_mapping or {} + self.check_variables_set = check_variables_set or set() + self.cached = cached + self.__parse(raw_string) + + def __parse(self, raw_string): + """ parse raw string, replace function and variable with {} + + Args: + raw_string(str): string with functions or varialbes + e.g. "ABC${func2($a, $b)}DE$c" + + Returns: + string: "ABC{}DE{}" + args: ["${func2($a, $b)}", "$c"] + + """ + self._string = raw_string + args_mapping = {} + + # Notice: functions must be handled before variables + # search function like ${func($a, $b)} + func_match_list = regex_findall_functions(self._string) + match_start_position = 0 + for func_match in func_match_list: + func_str = "${%s(%s)}" % (func_match[0], func_match[1]) + match_start_position = raw_string.index(func_str, match_start_position) + self._string = self._string.replace(func_str, "{}", 1) + function_meta = parse_function_params(func_match[1]) + function_meta = { + "func_name": func_match[0] + } + function_meta.update(parse_function_params(func_match[1])) + lazy_func = LazyFunction( + function_meta, + self.functions_mapping, + self.check_variables_set + ) + args_mapping[match_start_position] = lazy_func + + # search variable like $var + var_match_list = regex_findall_variables(self._string) + match_start_position = 0 + for var_name in var_match_list: + # check if any variable undefined in check_variables_set + if var_name not in self.check_variables_set: + raise exceptions.VariableNotFound(var_name) + + var = "${}".format(var_name) + match_start_position = raw_string.index(var, match_start_position) + # TODO: escape '{' and '}' + # self._string = self._string.replace("{", "{{") + # self._string = self._string.replace("}", "}}") + self._string = self._string.replace(var, "{}", 1) + args_mapping[match_start_position] = var_name + + self._args = [args_mapping[key] for key in sorted(args_mapping.keys())] + + def __repr__(self): + return "LazyString({})".format(self.raw_string) + + def to_value(self, variables_mapping=None): + """ parse lazy data with evaluated variables mapping. + Notice: variables_mapping should not contain any variable or function. + """ + variables_mapping = variables_mapping or {} + + args = [] + for arg in self._args: + if isinstance(arg, LazyFunction): + if self.cached and arg.cache_key and arg.cache_key in cached_functions_mapping: + value = cached_functions_mapping[arg.cache_key] + else: + value = arg.to_value(variables_mapping) + cached_functions_mapping[arg.cache_key] = value + args.append(value) + else: + # variable + var_value = get_mapping_variable(arg, variables_mapping) + args.append(var_value) + + if self._string == "{}": + return args[0] + else: + return self._string.format(*args) + + +def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None, cached=False): + """ make string in content as lazy object with functions_mapping + + Raises: + exceptions.VariableNotFound: if any variable undefined in check_variables_set + """ # TODO: refactor type check if content is None or isinstance(content, (numeric_types, bool, type)): return content - if isinstance(content, (list, set, tuple)): + elif isinstance(content, (list, set, tuple)): return [ - parse_data( + prepare_lazy_data( item, - variables_mapping, functions_mapping, - raise_if_variable_not_found + check_variables_set, + cached ) for item in content ] - if isinstance(content, dict): + elif isinstance(content, dict): parsed_content = {} for key, value in content.items(): - parsed_key = parse_data( + parsed_key = prepare_lazy_data( key, - variables_mapping, functions_mapping, - raise_if_variable_not_found + check_variables_set, + cached ) - parsed_value = parse_data( + parsed_value = prepare_lazy_data( value, - variables_mapping, functions_mapping, - raise_if_variable_not_found + check_variables_set, + cached ) parsed_content[parsed_key] = parsed_value return parsed_content - if isinstance(content, basestring): + elif isinstance(content, basestring): # content is in string format here - variables_mapping = utils.ensure_mapping_format(variables_mapping or {}) - functions_mapping = functions_mapping or {} - content = content.strip() + if not (is_variable_exist(content) or is_function_exist(content)): + # content is neither variable nor function + return content - 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 + functions_mapping = functions_mapping or {} + check_variables_set = check_variables_set or set() + content = content.strip() + content = LazyString(content, functions_mapping, check_variables_set, cached) return content +def parse_lazy_data(content, variables_mapping=None): + """ parse lazy data with evaluated variables mapping. + Notice: variables_mapping should not contain any variable or function. + """ + # TODO: refactor type check + if content is None or isinstance(content, (numeric_types, bool, type)): + return content + + elif isinstance(content, LazyString): + variables_mapping = utils.ensure_mapping_format(variables_mapping or {}) + return content.to_value(variables_mapping) + + elif isinstance(content, (list, set, tuple)): + return [ + parse_lazy_data(item, variables_mapping) + for item in content + ] + + elif isinstance(content, dict): + parsed_content = {} + for key, value in content.items(): + parsed_key = parse_lazy_data(key, variables_mapping) + parsed_value = parse_lazy_data(value, variables_mapping) + parsed_content[parsed_key] = parsed_value + + return parsed_content + + return content + + +def eval_lazy_data(content, variables_mapping=None, functions_mapping=None): + """ evaluate data instantly. + Notice: variables_mapping should not contain any variable or function. + """ + variables_mapping = variables_mapping or {} + check_variables_set = set(variables_mapping.keys()) + return parse_lazy_data( + prepare_lazy_data( + content, + functions_mapping, + check_variables_set + ), + variables_mapping + ) + + +def extract_variables(content): + """ extract all variables in content recursively. + """ + if isinstance(content, (list, set, tuple)): + variables = set() + for item in content: + variables = variables | extract_variables(item) + return variables + + elif isinstance(content, dict): + variables = set() + for key, value in content.items(): + variables = variables | extract_variables(value) + return variables + + elif isinstance(content, LazyString): + return set(regex_findall_variables(content.raw_string)) + + return set() + + +def parse_variables_mapping(variables_mapping, ignore=False): + """ eval each prepared variable and function in variables_mapping. + + Args: + variables_mapping (dict): + { + "varA": LazyString(123$varB), + "varB": LazyString(456$varC), + "varC": LazyString(${sum_two($a, $b)}), + "a": 1, + "b": 2, + "c": {"key": LazyString($b)}, + "d": [LazyString($a), 3] + } + ignore (bool): If set True, VariableNotFound will be ignored. + This is used when initializing tests. + + Returns: + dict: parsed variables_mapping should not contain any variable or function. + { + "varA": "1234563", + "varB": "4563", + "varC": "3", + "a": 1, + "b": 2, + "c": {"key": 2}, + "d": [1, 3] + } + + """ + variables_mapping = variables_mapping or {} + ref_variables_set = set() + + parsed_variables_mapping = {} + while len(parsed_variables_mapping) != len(variables_mapping): + for var_name in variables_mapping: + if var_name in parsed_variables_mapping: + continue + + value = variables_mapping[var_name] + variables = extract_variables(value) + + # check if reference variable itself + if var_name in variables: + # e.g. + # var_name = "token" + # variables_mapping = {"token": LazyString($token)} + # var_name = "key" + # variables_mapping = {"key": [LazyString($key), 2]} + if ignore: + parsed_variables_mapping[var_name] = value + continue + raise exceptions.VariableNotFound(var_name) + + if variables: + # reference other variable, or function call with other variable + # e.g. {"varA": "123$varB", "varB": "456$varC"} + # e.g. {"varC": "${sum_two($a, $b)}"} + if any([var_name not in parsed_variables_mapping for var_name in variables]): + # reference variable not parsed + continue + + parsed_value = parse_lazy_data(value, parsed_variables_mapping) + parsed_variables_mapping[var_name] = parsed_value + + return parsed_variables_mapping + + def _extend_with_api(test_dict, api_def_dict): """ extend test with api definition, test will merge and override api definition. @@ -687,8 +862,9 @@ def _extend_with_testcase(test_dict, testcase_def_dict): test_dict.update(testcase_def_dict) -def __parse_config(config, project_mapping): - """ parse testcase/testsuite config, include variables and name. +def __prepare_config(config, project_mapping): + """ parse testcase/testsuite config, + including everything (name and base_url) except variables. """ # get config variables raw_config_variables = config.pop("variables", {}) @@ -699,39 +875,15 @@ def __parse_config(config, project_mapping): # override config variables with passed in variables raw_config_variables_mapping.update(override_variables) - # parse config variables - parsed_config_variables = {} + if raw_config_variables_mapping: + config["variables"] = raw_config_variables_mapping - for key in raw_config_variables_mapping: - parsed_value = parse_data( - raw_config_variables_mapping[key], - raw_config_variables_mapping, - functions, - raise_if_variable_not_found=False - ) - raw_config_variables_mapping[key] = parsed_value - parsed_config_variables[key] = parsed_value - - if parsed_config_variables: - config["variables"] = parsed_config_variables - - # parse config name - config["name"] = parse_data( - config.get("name", ""), - parsed_config_variables, - functions - ) - - # parse config base_url - if "base_url" in config: - config["base_url"] = parse_data( - config["base_url"], - parsed_config_variables, - functions - ) + check_variables_set = raw_config_variables_mapping.keys() + prepared_config = prepare_lazy_data(config, functions, check_variables_set, cached=True) + return prepared_config -def __parse_testcase_tests(tests, config, project_mapping): +def __prepare_testcase_tests(tests, config, project_mapping): """ override tests with testcase config variables, base_url and verify. test maybe nested testcase. @@ -751,47 +903,26 @@ def __parse_testcase_tests(tests, config, project_mapping): """ config_variables = config.get("variables", {}) - config_base_url = config.pop("base_url", "") - config_verify = config.pop("verify", True) + config_base_url = config.get("base_url", "") + config_verify = config.get("verify", True) functions = project_mapping.get("functions", {}) + prepared_testcase_tests = [] + session_variables = {} for test_dict in tests: + # 1, testcase config => testcase tests + # override test_dict variables + test_dict_variables = utils.extend_variables( + test_dict.pop("variables", {}), + config_variables + ) + test_dict["variables"] = test_dict_variables + # base_url & verify: priority test_dict > config if (not test_dict.get("base_url")) and config_base_url: test_dict["base_url"] = config_base_url - # 1, testcase config => testcase tests - # override test_dict variables - test_dict["variables"] = utils.extend_variables( - test_dict.pop("variables", {}), - config_variables - ) - - for key in test_dict["variables"]: - parsed_key = parse_data( - key, - test_dict["variables"], - functions, - raise_if_variable_not_found=False - ) - parsed_value = parse_data( - test_dict["variables"][key], - test_dict["variables"], - functions, - raise_if_variable_not_found=False - ) - if parsed_key in test_dict["variables"]: - test_dict["variables"][parsed_key] = parsed_value - - # parse test_dict name - 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 @@ -803,40 +934,37 @@ def __parse_testcase_tests(tests, config, project_mapping): test_dict["config"].setdefault("verify", config_verify) # 3, testcase_def config => testcase_def test_dict - _parse_testcase(test_dict, project_mapping) + test_dict = _parse_testcase(test_dict, project_mapping) - else: - if "api_def" in test_dict: - # test_dict has API reference - # 2, test_dict => api - api_def_dict = test_dict.pop("api_def") - _extend_with_api(test_dict, api_def_dict) - - if test_dict.get("base_url"): - # parse base_url - base_url = parse_data( - test_dict.pop("base_url"), - test_dict["variables"], - functions - ) - - # build path with base_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 - ) + elif "api_def" in test_dict: + # test_dict has API reference + # 2, test_dict => api + api_def_dict = test_dict.pop("api_def") + _extend_with_api(test_dict, api_def_dict) # verify priority: testcase teststep > testcase config if "request" in test_dict and "verify" not in test_dict["request"]: test_dict["request"]["verify"] = config_verify + # move extracted variable to session variables + if "extract" in test_dict: + extract_mapping = utils.ensure_mapping_format(test_dict["extract"]) + session_variables.update(extract_mapping) + + check_variables_set = set(test_dict_variables.keys()) \ + | set(session_variables.keys()) | {"request", "response"} + + # convert variables and functions to lazy object. + # raises VariableNotFound if undefined variable exists in test_dict + prepared_test_dict = prepare_lazy_data( + test_dict, + functions, + check_variables_set + ) + prepared_testcase_tests.append(prepared_test_dict) + + return prepared_testcase_tests + def _parse_testcase(testcase, project_mapping): """ parse testcase @@ -850,8 +978,19 @@ def _parse_testcase(testcase, project_mapping): """ testcase.setdefault("config", {}) - __parse_config(testcase["config"], project_mapping) - __parse_testcase_tests(testcase["teststeps"], testcase["config"], project_mapping) + prepared_config = __prepare_config( + testcase["config"], + project_mapping + ) + prepared_testcase_tests = __prepare_testcase_tests( + testcase["teststeps"], + prepared_config, + project_mapping + ) + return { + "config": prepared_config, + "teststeps": prepared_testcase_tests + } def __get_parsed_testsuite_testcases(testcases, testsuite_config, project_mapping): @@ -924,28 +1063,17 @@ def __get_parsed_testsuite_testcases(testcases, testsuite_config, project_mappin # 2, testcase config > testcase_def config # override testcase_def config variables - parsed_testcase_config_variables = utils.extend_variables( + overrided_testcase_config_variables = utils.extend_variables( parsed_testcase["config"].pop("variables", {}), testcase_config_variables ) + if overrided_testcase_config_variables: + parsed_testcase["config"]["variables"] = overrided_testcase_config_variables + # parse config variables - parsed_config_variables = {} - - for key in parsed_testcase_config_variables: - try: - parsed_value = parse_data( - parsed_testcase_config_variables[key], - parsed_testcase_config_variables, - functions - ) - except exceptions.VariableNotFound: - pass - parsed_testcase_config_variables[key] = parsed_value - parsed_config_variables[key] = parsed_value - - if parsed_config_variables: - parsed_testcase["config"]["variables"] = parsed_config_variables + parsed_config_variables = parse_variables_mapping( + overrided_testcase_config_variables, functions) # parse parameters if "parameters" in testcase and testcase["parameters"]: @@ -957,17 +1085,21 @@ def __get_parsed_testsuite_testcases(testcases, testsuite_config, project_mappin for parameter_variables in cartesian_product_parameters: # deepcopy to avoid influence between parameters - parsed_testcase_copied = utils.deepcopy_dict(parsed_testcase) + testcase_copied = utils.deepcopy_dict(parsed_testcase) parsed_config_variables_copied = utils.deepcopy_dict(parsed_config_variables) - parsed_testcase_copied["config"]["variables"] = utils.extend_variables( + testcase_copied["config"]["variables"] = utils.extend_variables( parsed_config_variables_copied, parameter_variables ) - _parse_testcase(parsed_testcase_copied, project_mapping) + parsed_testcase_copied = _parse_testcase(testcase_copied, project_mapping) + parsed_testcase_copied["config"]["name"] = parse_lazy_data( + parsed_testcase_copied["config"]["name"], + testcase_copied["config"]["variables"] + ) parsed_testcase_list.append(parsed_testcase_copied) else: - _parse_testcase(parsed_testcase, project_mapping) + parsed_testcase = _parse_testcase(parsed_testcase, project_mapping) parsed_testcase_list.append(parsed_testcase) return parsed_testcase_list @@ -975,10 +1107,10 @@ def __get_parsed_testsuite_testcases(testcases, testsuite_config, project_mappin def _parse_testsuite(testsuite, project_mapping): testsuite.setdefault("config", {}) - __parse_config(testsuite["config"], project_mapping) + prepared_config = __prepare_config(testsuite["config"], project_mapping) parsed_testcase_list = __get_parsed_testsuite_testcases( testsuite["testcases"], - testsuite["config"], + prepared_config, project_mapping ) return parsed_testcase_list @@ -1067,8 +1199,8 @@ def parse_tests(tests_mapping): elif test_type == "testcases": for testcase in tests_mapping["testcases"]: - _parse_testcase(testcase, project_mapping) - parsed_tests_mapping["testcases"].append(testcase) + parsed_testcase = _parse_testcase(testcase, project_mapping) + parsed_tests_mapping["testcases"].append(parsed_testcase) elif test_type == "apis": # encapsulate api as a testcase @@ -1076,7 +1208,7 @@ def parse_tests(tests_mapping): testcase = { "teststeps": [api_content] } - _parse_testcase(testcase, project_mapping) - parsed_tests_mapping["testcases"].append(testcase) + parsed_testcase = _parse_testcase(testcase, project_mapping) + parsed_tests_mapping["testcases"].append(parsed_testcase) return parsed_tests_mapping diff --git a/httprunner/runner.py b/httprunner/runner.py index 3cf23987..162aad10 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -199,7 +199,9 @@ class Runner(object): self.session_context.init_test_variables(test_variables) # teststep name - test_name = test_dict.get("name", "") + test_name = self.session_context.eval_content(test_dict.get("name", "")) + # TODO: refactor + self.http_client_session.base_url = self.session_context.eval_content(test_dict.get("base_url", "")) # parse test request raw_request = test_dict.get('request', {}) @@ -254,7 +256,6 @@ class Runner(object): validators = test_dict.get("validate", []) try: self.session_context.validate(validators, resp_obj) - except (exceptions.ParamsError, exceptions.ValidationFailure, exceptions.ExtractFailure): err_msg = "{} DETAILED REQUEST & RESPONSE {}\n".format("*" * 32, "*" * 32) diff --git a/tests/data/bugfix_verify.yml b/tests/data/bugfix_verify.yml new file mode 100644 index 00000000..d3cd0c47 --- /dev/null +++ b/tests/data/bugfix_verify.yml @@ -0,0 +1,13 @@ +- config: + name: basic test with httpbin + base_url: https://httpbin.org/ + verify: False + +- test: + name: headers + request: + url: /headers + method: GET + validate: + - eq: ["status_code", 200] + - eq: [content.headers.Host, "httpbin.org"] diff --git a/tests/data/demo_testcase.yml b/tests/data/demo_testcase.yml index 36924cf3..2a495b98 100644 --- a/tests/data/demo_testcase.yml +++ b/tests/data/demo_testcase.yml @@ -1,8 +1,9 @@ - config: - name: "123$var_a" + name: "123t$var_a" variables: - var_a: 0 - var_c: "${sum_two(1, 2)}" + var_a: 1 + var_b: 2 + var_c: "${sum_two($var_a, $var_b)}" var_d: "${gen_random_string(5)}" var_e: $var_d PROJECT_KEY: ${ENV(PROJECT_KEY)} diff --git a/tests/test_api.py b/tests/test_api.py index b0b240f3..f4f111b2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -589,7 +589,7 @@ class TestApi(ApiServerUnittest): self.assertEqual(test_dict1["name"], "get token (setup)") self.assertNotIn("api_def", test_dict1) self.assertEqual(test_dict1["variables"]["device_sn"], "TESTCASE_SETUP_XXX") - self.assertEqual(test_dict1["request"]["url"], "http://127.0.0.1:5000/api/get-token") + self.assertEqual(test_dict1["request"]["url"], "/api/get-token") self.assertEqual(test_dict1["request"]["verify"], False) test_dict2 = parsed_testcases[0]["teststeps"][1] @@ -696,17 +696,14 @@ class TestApi(ApiServerUnittest): self.assertEqual(len(parsed_testcases[0]["teststeps"]), 4) testcase1 = parsed_testcases[0]["teststeps"][0] - self.assertIn("setup and reset all (override)", testcase1["config"]["name"]) - self.assertEqual(testcase1["teststeps"][0]["variables"]["var_c"], testcase1["teststeps"][0]["variables"]["var_d"]) - self.assertEqual(testcase1["teststeps"][0]["variables"]["var_a"], testcase1["teststeps"][0]["variables"]["var_b"]) - self.assertNotEqual(testcase1["teststeps"][0]["variables"]["var_a"], testcase1["teststeps"][0]["variables"]["var_c"]) + self.assertIn("setup and reset all (override)", testcase1["config"]["name"].raw_string) + teststeps = testcase1["teststeps"] self.assertNotIn("testcase_def", testcase1) - self.assertEqual(len(testcase1["teststeps"]), 2) + self.assertEqual(len(teststeps), 2) self.assertEqual( - testcase1["teststeps"][0]["request"]["url"], - "http://127.0.0.1:5000/api/get-token" + teststeps[0]["request"]["url"], + "/api/get-token" ) - self.assertEqual(len(testcase1["teststeps"][0]["variables"]["device_sn"]), 15) def test_testsuite_add_tests(self): testcase_path = "tests/testsuites/create_users.yml" @@ -718,7 +715,7 @@ class TestApi(ApiServerUnittest): self.assertEqual(len(test_suite._tests), 2) tests = test_suite._tests[0].teststeps - self.assertIn("setup and reset all (override)", tests[0]["config"]["name"]) + self.assertIn("setup and reset all (override)", tests[0]["config"]["name"].raw_string) def test_testsuite_run_suite(self): testcase_path = "tests/testsuites/create_users.yml" diff --git a/tests/test_context.py b/tests/test_context.py index a5c6a835..b1ad8f61 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -2,8 +2,8 @@ import os import time import requests -from httprunner import context, exceptions, loader, response, utils -from tests.base import ApiServerUnittest +from httprunner import context, exceptions, loader, parser, response, utils +from tests.base import ApiServerUnittest, gen_md5, gen_random_string class TestContext(ApiServerUnittest): @@ -11,8 +11,9 @@ class TestContext(ApiServerUnittest): def setUp(self): loader.load_project_tests(os.path.join(os.getcwd(), "tests")) project_mapping = loader.project_mapping + self.functions = project_mapping["functions"] self.context = context.SessionContext( - functions=project_mapping["functions"], + functions=self.functions, variables={"SECRET_KEY": "DebugTalk"} ) @@ -30,16 +31,24 @@ class TestContext(ApiServerUnittest): variables = { "random": "${gen_random_string($num)}", "authorization": "${gen_md5($TOKEN, $data, $random)}", - "data": '{"name": "$username", "password": "123456"}', + "data": "$username", + # TODO: escape '{' and '}' + # "data": '{"name": "$username", "password": "123456"}', "TOKEN": "debugtalk", "username": "user1", "num": 6 } + functions = { + "gen_random_string": gen_random_string, + "gen_md5": gen_md5 + } + variables = parser.prepare_lazy_data(variables, functions, variables.keys()) + variables = parser.parse_variables_mapping(variables) self.context.init_test_variables(variables) variables_mapping = self.context.test_variables_mapping self.assertEqual(len(variables_mapping["random"]), 6) self.assertEqual(len(variables_mapping["authorization"]), 32) - self.assertEqual(variables_mapping["data"], '{"name": "user1", "password": "123456"}') + self.assertEqual(variables_mapping["data"], 'user1') def test_update_seesion_variables(self): self.context.update_session_variables({"TOKEN": "debugtalk"}) @@ -49,14 +58,17 @@ class TestContext(ApiServerUnittest): ) def test_eval_content_functions(self): - content = "${sleep_N_secs(1)}" + content = parser.prepare_lazy_data("${sleep_N_secs(1)}", self.functions) start_time = time.time() self.context.eval_content(content) elapsed_time = time.time() - start_time self.assertGreater(elapsed_time, 1) def test_eval_content_variables(self): - content = "abc$SECRET_KEY" + variables = { + "SECRET_KEY": "DebugTalk" + } + content = parser.prepare_lazy_data("abc$SECRET_KEY", {}, variables.keys()) self.assertEqual( self.context.eval_content(content), "abcDebugTalk" @@ -76,7 +88,12 @@ class TestContext(ApiServerUnittest): "authorization": "${gen_md5($TOKEN, $data, $random)}", "TOKEN": "debugtalk" } - + functions = { + "gen_random_string": gen_random_string, + "gen_md5": gen_md5 + } + variables = parser.prepare_lazy_data(variables, functions, variables.keys()) + variables = parser.parse_variables_mapping(variables) self.context.init_test_variables(variables) request = { @@ -90,7 +107,12 @@ class TestContext(ApiServerUnittest): }, "data": "$data" } - parsed_request = self.context.eval_content(request) + prepared_request = parser.prepare_lazy_data( + request, + functions, + {"authorization", "random", "SECRET_KEY", "data"} + ) + parsed_request = self.context.eval_content(prepared_request) self.assertIn("authorization", parsed_request["headers"]) self.assertEqual(len(parsed_request["headers"]["authorization"]), 32) self.assertIn("random", parsed_request["headers"]) @@ -123,6 +145,7 @@ class TestContext(ApiServerUnittest): {"check": "$resp_status_code", "comparator": "eq", "expect": 201}, {"check": "$resp_body_success", "comparator": "eq", "expect": True} ] + validators = parser.prepare_lazy_data(validators, {}, {"resp_status_code", "resp_body_success"}) variables = { "resp_status_code": 200, "resp_body_success": True @@ -139,6 +162,12 @@ class TestContext(ApiServerUnittest): {"check": "$resp_body_success", "comparator": "eq", "expect": True}, {"check": "${is_status_code_200($resp_status_code)}", "comparator": "eq", "expect": False} ] + from tests.debugtalk import is_status_code_200 + functions = { + "is_status_code_200": is_status_code_200 + } + validators = parser.prepare_lazy_data( + validators, functions, {"resp_status_code", "resp_body_success"}) variables = [ {"resp_status_code": 201}, {"resp_body_success": True} @@ -159,6 +188,7 @@ class TestContext(ApiServerUnittest): {"eq": ["$resp_status_code", 201]}, {"check": "$resp_status_code", "comparator": "eq", "expect": 201} ] + validators = parser.prepare_lazy_data(validators, {}, {"resp_status_code"}) variables = [] self.context.init_test_variables(variables) diff --git a/tests/test_loader.py b/tests/test_loader.py index 10d79cdf..97845ddd 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -348,14 +348,14 @@ class TestSuiteLoader(unittest.TestCase): tests_mapping = loader.load_tests(testcase_file_path) testcases = tests_mapping["testcases"] self.assertIsInstance(testcases, list) - self.assertEqual(testcases[0]["config"]["name"], '123$var_a') + self.assertEqual(testcases[0]["config"]["name"], '123t$var_a') self.assertIn( "sum_two", tests_mapping["project_mapping"]["functions"] ) self.assertEqual( testcases[0]["config"]["variables"]["var_c"], - "${sum_two(1, 2)}" + "${sum_two($var_a, $var_b)}" ) self.assertEqual( testcases[0]["config"]["variables"]["PROJECT_KEY"], diff --git a/tests/test_parser.py b/tests/test_parser.py index d98cd0dd..9fe6c266 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,11 +1,13 @@ import os +import re import time import unittest from httprunner import exceptions, loader, parser +from tests.debugtalk import gen_random_string, sum_two -class TestParser(unittest.TestCase): +class TestParserBasic(unittest.TestCase): def test_parse_string_value(self): self.assertEqual(parser.parse_string_value("123"), 123) @@ -14,92 +16,92 @@ class TestParser(unittest.TestCase): self.assertEqual(parser.parse_string_value("$var"), "$var") self.assertEqual(parser.parse_string_value("${func}"), "${func}") - def test_extract_variables(self): + def test_regex_findall_variables(self): self.assertEqual( - parser.extract_variables("$var"), + parser.regex_findall_variables("$var"), ["var"] ) self.assertEqual( - parser.extract_variables("$var123"), + parser.regex_findall_variables("$var123"), ["var123"] ) self.assertEqual( - parser.extract_variables("$var_name"), + parser.regex_findall_variables("$var_name"), ["var_name"] ) self.assertEqual( - parser.extract_variables("var"), + parser.regex_findall_variables("var"), [] ) self.assertEqual( - parser.extract_variables("a$var"), + parser.regex_findall_variables("a$var"), ["var"] ) self.assertEqual( - parser.extract_variables("$v ar"), + parser.regex_findall_variables("$v ar"), ["v"] ) self.assertEqual( - parser.extract_variables(" "), + parser.regex_findall_variables(" "), [] ) self.assertEqual( - parser.extract_variables("$abc*"), + parser.regex_findall_variables("$abc*"), ["abc"] ) self.assertEqual( - parser.extract_variables("${func()}"), + parser.regex_findall_variables("${func()}"), [] ) self.assertEqual( - parser.extract_variables("${func(1,2)}"), + parser.regex_findall_variables("${func(1,2)}"), [] ) self.assertEqual( - parser.extract_variables("${gen_md5($TOKEN, $data, $random)}"), + parser.regex_findall_variables("${gen_md5($TOKEN, $data, $random)}"), ["TOKEN", "data", "random"] ) - def test_parse_function(self): + def test_parse_function_params(self): self.assertEqual( - parser.parse_function("func()"), - {'func_name': 'func', 'args': [], 'kwargs': {}} + parser.parse_function_params(""), + {'args': [], 'kwargs': {}} ) self.assertEqual( - parser.parse_function("func(5)"), - {'func_name': 'func', 'args': [5], 'kwargs': {}} + parser.parse_function_params("5"), + {'args': [5], 'kwargs': {}} ) self.assertEqual( - parser.parse_function("func(1, 2)"), - {'func_name': 'func', 'args': [1, 2], 'kwargs': {}} + parser.parse_function_params("1, 2"), + {'args': [1, 2], 'kwargs': {}} ) self.assertEqual( - parser.parse_function("func(a=1, b=2)"), - {'func_name': 'func', 'args': [], 'kwargs': {'a': 1, 'b': 2}} + parser.parse_function_params("a=1, b=2"), + {'args': [], 'kwargs': {'a': 1, 'b': 2}} ) self.assertEqual( - parser.parse_function("func(a= 1, b =2)"), - {'func_name': 'func', 'args': [], 'kwargs': {'a': 1, 'b': 2}} + parser.parse_function_params("a= 1, b =2"), + {'args': [], 'kwargs': {'a': 1, 'b': 2}} ) self.assertEqual( - parser.parse_function("func(1, 2, a=3, b=4)"), - {'func_name': 'func', 'args': [1, 2], 'kwargs': {'a': 3, 'b': 4}} + parser.parse_function_params("1, 2, a=3, b=4"), + {'args': [1, 2], 'kwargs': {'a': 3, 'b': 4}} ) self.assertEqual( - parser.parse_function("func($request, 123)"), - {'func_name': 'func', 'args': ["$request", 123], 'kwargs': {}} + parser.parse_function_params("$request, 123"), + {'args': ["$request", 123], 'kwargs': {}} ) self.assertEqual( - parser.parse_function("func( )"), - {'func_name': 'func', 'args': [], 'kwargs': {}} + parser.parse_function_params(" "), + {'args': [], 'kwargs': {}} ) self.assertEqual( - parser.parse_function("func(hello world, a=3, b=4)"), - {'func_name': 'func', 'args': ["hello world"], 'kwargs': {'a': 3, 'b': 4}} + parser.parse_function_params("hello world, a=3, b=4"), + {'args': ["hello world"], 'kwargs': {'a': 3, 'b': 4}} ) self.assertEqual( - parser.parse_function("func($request, 12 3)"), - {'func_name': 'func', 'args': ["$request", '12 3'], 'kwargs': {}} + parser.parse_function_params("$request, 12 3"), + {'args': ["$request", '12 3'], 'kwargs': {}} ) def test_parse_validator(self): @@ -115,38 +117,82 @@ class TestParser(unittest.TestCase): {"check": "status_code", "comparator": "eq", "expect": 201} ) + def test_extract_variables(self): + prepared_content = parser.prepare_lazy_data("123$a", {}, {"a"}) + self.assertEqual( + parser.extract_variables(prepared_content), + {"a"} + ) + prepared_content = parser.prepare_lazy_data("$a$b", {}, {"a", "b"}) + self.assertEqual( + parser.extract_variables(prepared_content), + {"a", "b"} + ) + prepared_content = parser.prepare_lazy_data(["$a$b", "$c", "d"], {}, {"a", "b", "c", "d"}) + self.assertEqual( + parser.extract_variables(prepared_content), + {"a", "b", "c"} + ) + prepared_content = parser.prepare_lazy_data( + {"a": 1, "b": {"c": "$d", "e": 3}}, + {}, + {"d"} + ) + self.assertEqual( + parser.extract_variables(prepared_content), + {"d"} + ) + prepared_content = parser.prepare_lazy_data( + {"a": ["$b"], "b": {"c": "$d", "e": 3}}, + {}, + {"b", "d"} + ) + self.assertEqual( + parser.extract_variables(prepared_content), + {"b", "d"} + ) + prepared_content = parser.prepare_lazy_data( + ["$a$b", "$c", {"c": "$d"}], + {}, + {"a", "b", "c", "d"} + ) + self.assertEqual( + parser.extract_variables(prepared_content), + {"a", "b", "c", "d"} + ) + def test_extract_functions(self): self.assertEqual( - parser.extract_functions("${func()}"), - ["func()"] + parser.regex_findall_functions("${func()}"), + [('func', '')] ) self.assertEqual( - parser.extract_functions("${func(5)}"), - ["func(5)"] + parser.regex_findall_functions("${func(5)}"), + [('func', '5')] ) self.assertEqual( - parser.extract_functions("${func(a=1, b=2)}"), - ["func(a=1, b=2)"] + parser.regex_findall_functions("${func(a=1, b=2)}"), + [('func', 'a=1, b=2')] ) self.assertEqual( - parser.extract_functions("${func(1, $b, c=$x, d=4)}"), - ["func(1, $b, c=$x, d=4)"] + parser.regex_findall_functions("${func(1, $b, c=$x, d=4)}"), + [('func', '1, $b, c=$x, d=4')] ) self.assertEqual( - parser.extract_functions("/api/1000?_t=${get_timestamp()}"), - ["get_timestamp()"] + parser.regex_findall_functions("/api/1000?_t=${get_timestamp()}"), + [('get_timestamp', '')] ) self.assertEqual( - parser.extract_functions("/api/${add(1, 2)}"), - ["add(1, 2)"] + parser.regex_findall_functions("/api/${add(1, 2)}"), + [('add', '1, 2')] ) self.assertEqual( - parser.extract_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"), - ["add(1, 2)", "get_timestamp()"] + parser.regex_findall_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"), + [('add', '1, 2'), ('get_timestamp', '')] ) self.assertEqual( - parser.extract_functions("abc${func(1, 2, a=3, b=4)}def"), - ["func(1, 2, a=3, b=4)"] + parser.regex_findall_functions("abc${func(1, 2, a=3, b=4)}def"), + [('func', '1, 2, a=3, b=4')] ) def test_parse_data(self): @@ -172,7 +218,7 @@ class TestParser(unittest.TestCase): functions_mapping = { "add_one": lambda x: x + 1 } - result = parser.parse_data(content, variables_mapping, functions_mapping) + result = parser.eval_lazy_data(content, variables_mapping, functions_mapping) self.assertEqual("/api/users/1000", result["request"]["url"]) self.assertEqual("abc123", result["request"]["headers"]["token"]) self.assertEqual("POST", result["request"]["method"]) @@ -182,7 +228,7 @@ class TestParser(unittest.TestCase): self.assertEqual("", result["request"]["data"]["empty_str"]) self.assertEqual("abc4def", result["request"]["data"]["value"]) - def test_parse_data_variables(self): + def test_eval_lazy_data(self): variables_mapping = { "var_1": "abc", "var_2": "def", @@ -192,66 +238,150 @@ class TestParser(unittest.TestCase): "var_6": None } self.assertEqual( - parser.parse_data("$var_1", variables_mapping), + parser.eval_lazy_data("$var_1", variables_mapping=variables_mapping), "abc" ) self.assertEqual( - parser.parse_data("var_1", variables_mapping), + parser.eval_lazy_data("var_1", variables_mapping=variables_mapping), "var_1" ) self.assertEqual( - parser.parse_data("$var_1#XYZ", variables_mapping), + parser.eval_lazy_data("$var_1#XYZ", variables_mapping=variables_mapping), "abc#XYZ" ) self.assertEqual( - parser.parse_data("/$var_1/$var_2/var3", variables_mapping), + parser.eval_lazy_data("/$var_1/$var_2/var3", variables_mapping=variables_mapping), "/abc/def/var3" ) self.assertEqual( - parser.parse_data("/$var_1/$var_2/$var_1", variables_mapping), + parser.eval_lazy_data("/$var_1/$var_2/$var_1", variables_mapping=variables_mapping), "/abc/def/abc" ) self.assertEqual( - parser.parse_string_variables("${func($var_1, $var_2, xyz)}", variables_mapping, {}), - "${func(abc, def, xyz)}" - ) - self.assertEqual( - parser.parse_data("$var_3", variables_mapping), + parser.eval_lazy_data("$var_3", variables_mapping=variables_mapping), 123 ) self.assertEqual( - parser.parse_data("$var_4", variables_mapping), + parser.eval_lazy_data("$var_4", variables_mapping=variables_mapping), {"a": 1} ) self.assertEqual( - parser.parse_data("$var_5", variables_mapping), + parser.eval_lazy_data("$var_5", variables_mapping=variables_mapping), True ) self.assertEqual( - parser.parse_data("abc$var_5", variables_mapping), + parser.eval_lazy_data("abc$var_5", variables_mapping=variables_mapping), "abcTrue" ) self.assertEqual( - parser.parse_data("abc$var_4", variables_mapping), + parser.eval_lazy_data("abc$var_4", variables_mapping=variables_mapping), "abc{'a': 1}" ) self.assertEqual( - parser.parse_data("$var_6", variables_mapping), + parser.eval_lazy_data("$var_6", variables_mapping=variables_mapping), None ) with self.assertRaises(exceptions.VariableNotFound): - parser.parse_data("/api/$SECRET_KEY", variables_mapping) + parser.eval_lazy_data("/api/$SECRET_KEY", variables_mapping=variables_mapping) self.assertEqual( - parser.parse_data(["$var_1", "$var_2"], variables_mapping), + parser.eval_lazy_data(["$var_1", "$var_2"], variables_mapping=variables_mapping), ["abc", "def"] ) self.assertEqual( - parser.parse_data({"$var_1": "$var_2"}, variables_mapping), + parser.eval_lazy_data({"$var_1": "$var_2"}, variables_mapping=variables_mapping), {"abc": "def"} ) + def test_lazy_string(self): + variables_mapping = { + "var_1": "abc", + "var_2": "def", + "var_3": 123, + "var_4": {"a": 1}, + "var_5": True, + "var_6": None + } + check_variables_set = variables_mapping.keys() + functions_mapping = { + "func1": lambda x,y: str(x) + str(y) + } + + var = parser.LazyString("ABC$var_1", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}") + self.assertEqual(var._args, ["var_1"]) + self.assertEqual(var.to_value(variables_mapping), "ABCabc") + + var = parser.LazyString("ABC$var_1$var_3", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}{}") + self.assertEqual(var._args, ["var_1", "var_3"]) + self.assertEqual(var.to_value(variables_mapping), "ABCabc123") + + var = parser.LazyString("ABC$var_1/$var_3", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}/{}") + self.assertEqual(var._args, ["var_1", "var_3"]) + self.assertEqual(var.to_value(variables_mapping), "ABCabc/123") + + var = parser.LazyString("ABC$var_1/", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}/") + self.assertEqual(var._args, ["var_1"]) + self.assertEqual(var.to_value(variables_mapping), "ABCabc/") + + var = parser.LazyString("ABC$var_1$", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}$") + self.assertEqual(var._args, ["var_1"]) + self.assertEqual(var.to_value(variables_mapping), "ABCabc$") + + var = parser.LazyString("ABC$var_1{", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}{") + self.assertEqual(var._args, ["var_1"]) + # self.assertEqual(var.to_value(variables_mapping), "ABCabc{") + + var = parser.LazyString("ABC$$var_1{", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC${}{") + self.assertEqual(var._args, ["var_1"]) + + var = parser.LazyString("ABC$var_1${", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}${") + self.assertEqual(var._args, ["var_1"]) + + var = parser.LazyString("ABC$var_1${a", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}${a") + self.assertEqual(var._args, ["var_1"]) + + var = parser.LazyString("ABC$var_1/$var_2/$var_1", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}/{}/{}") + self.assertEqual(var._args, ["var_1", "var_2", "var_1"]) + self.assertEqual(var.to_value(variables_mapping), "ABCabc/def/abc") + + var = parser.LazyString("func1($var_1, $var_3)", functions_mapping, check_variables_set) + self.assertEqual(var._string, "func1({}, {})") + self.assertEqual(var._args, ["var_1", "var_3"]) + self.assertEqual(var.to_value(variables_mapping), "func1(abc, 123)") + + var = parser.LazyString("${func1($var_1, $var_3)}", functions_mapping, check_variables_set) + self.assertEqual(var._string, "{}") + self.assertIsInstance(var._args[0], parser.LazyFunction) + self.assertEqual(var.to_value(variables_mapping), "abc123") + + var = parser.LazyString("ABC${func1($var_1, $var_3)}DE", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}DE") + self.assertIsInstance(var._args[0], parser.LazyFunction) + self.assertEqual(var.to_value(variables_mapping), "ABCabc123DE") + + var = parser.LazyString("ABC${func1($var_1, $var_3)}$var_5", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}{}") + self.assertEqual(var.to_value(variables_mapping), "ABCabc123True") + + var = parser.LazyString("ABC${func1($var_1, $var_3)}DE$var_4", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}DE{}") + self.assertEqual(var.to_value(variables_mapping), "ABCabc123DE{'a': 1}") + + var = parser.LazyString("ABC$var_5${func1($var_1, $var_3)}", functions_mapping, check_variables_set) + self.assertEqual(var._string, "ABC{}{}") + self.assertEqual(var.to_value(variables_mapping), "ABCTrueabc123") + def test_parse_data_multiple_identical_variables(self): variables_mapping = { "userid": 100, @@ -259,7 +389,7 @@ class TestParser(unittest.TestCase): } content = "/users/$userid/training/$data?userId=$userid&data=$data" self.assertEqual( - parser.parse_data(content, variables_mapping), + parser.eval_lazy_data(content, variables_mapping=variables_mapping), "/users/100/training/1498?userId=100&data=1498" ) @@ -270,36 +400,35 @@ class TestParser(unittest.TestCase): } content = "/users/$user/$userid/$data?userId=$userid&data=$data" self.assertEqual( - parser.parse_data(content, variables_mapping), + parser.eval_lazy_data(content, variables_mapping=variables_mapping), "/users/100/1000/1498?userId=1000&data=1498" ) def test_parse_data_functions(self): import random, string functions_mapping = { - "gen_random_string": lambda str_len: ''.join(random.choice(string.ascii_letters + string.digits) \ - for _ in range(str_len)) + "gen_random_string": gen_random_string } - result = parser.parse_data("${gen_random_string(5)}", functions_mapping=functions_mapping) + result = parser.eval_lazy_data("${gen_random_string(5)}", functions_mapping=functions_mapping) self.assertEqual(len(result), 5) add_two_nums = lambda a, b=1: a + b functions_mapping["add_two_nums"] = add_two_nums self.assertEqual( - parser.parse_data("${add_two_nums(1)}", functions_mapping=functions_mapping), + parser.eval_lazy_data("${add_two_nums(1)}", functions_mapping=functions_mapping), 2 ) self.assertEqual( - parser.parse_data("${add_two_nums(1, 2)}", functions_mapping=functions_mapping), + parser.eval_lazy_data("${add_two_nums(1, 2)}", functions_mapping=functions_mapping), 3 ) self.assertEqual( - parser.parse_data("/api/${add_two_nums(1, 2)}", functions_mapping=functions_mapping), + parser.eval_lazy_data("/api/${add_two_nums(1, 2)}", functions_mapping=functions_mapping), "/api/3" ) with self.assertRaises(exceptions.FunctionNotFound): - parser.parse_data("/api/${gen_md5(abc)}") + parser.eval_lazy_data("/api/${gen_md5(abc)}", functions_mapping=functions_mapping) def test_parse_data_testcase(self): variables = { @@ -323,7 +452,11 @@ class TestParser(unittest.TestCase): }, "body": "$data" } - parsed_testcase = parser.parse_data(testcase_template, variables, functions) + parsed_testcase = parser.eval_lazy_data( + testcase_template, + variables_mapping=variables, + functions_mapping=functions + ) self.assertEqual( parsed_testcase["url"], "http://127.0.0.1:5000/api/users/1000/3" @@ -345,13 +478,123 @@ class TestParser(unittest.TestCase): 3 ) + def test_parse_variables_mapping(self): + variables = { + "varA": "123$varB", + "varB": "456$varC", + "varC": "${sum_two($a, $b)}", + "a": 1, + "b": 2 + } + functions = { + "sum_two": sum_two + } + prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) + parsed_variables = parser.parse_variables_mapping(prepared_variables) + self.assertEqual(parsed_variables["varA"], "1234563") + self.assertEqual(parsed_variables["varB"], "4563") + self.assertEqual(parsed_variables["varC"], 3) + + def test_parse_variables_mapping_fix_duplicate_function_call(self): + # fix duplicate function calling + variables = { + "varA": "$varB", + "varB": "${gen_random_string(5)}" + } + functions = { + "gen_random_string": gen_random_string + } + prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) + parsed_variables = parser.parse_variables_mapping(prepared_variables) + self.assertEqual(parsed_variables["varA"], parsed_variables["varB"]) + + def test_parse_variables_mapping_not_found(self): + variables = { + "varA": "123$varB", + "varB": "456$varC", + "varC": "${sum_two($a, $b)}", + "b": 2 + } + functions = { + "sum_two": sum_two + } + with self.assertRaises(exceptions.VariableNotFound): + parser.prepare_lazy_data(variables, functions, variables.keys()) + + def test_parse_variables_mapping_ref_self(self): + variables = { + "varC": "${sum_two($a, $b)}", + "a": 1, + "b": 2, + "token": "$token" + } + functions = { + "sum_two": sum_two + } + prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) + with self.assertRaises(exceptions.VariableNotFound): + parser.parse_variables_mapping(prepared_variables) + + def test_parse_variables_mapping_2(self): + variables = { + "host2": "https://httprunner.org", + "num3": "${sum_two($num2, 4)}", + "num2": "${sum_two($num1, 3)}", + "num1": "${sum_two(1, 2)}" + } + functions = { + "sum_two": sum_two + } + prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) + parsed_testcase = parser.parse_variables_mapping(prepared_variables) + self.assertEqual(parsed_testcase["num3"], 10) + self.assertEqual(parsed_testcase["num2"], 6) + self.assertEqual(parsed_testcase["num1"], 3) + + def test_prepare_lazy_data(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)}", + "num0": 0 + } + functions = { + "sum_two": sum_two + } + parser.prepare_lazy_data( + variables, + functions, + variables.keys() + ) + + def test_prepare_lazy_data_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)}" + } + functions = { + "sum_two": sum_two + } + with self.assertRaises(exceptions.VariableNotFound): + parser.prepare_lazy_data( + variables, + functions, + variables.keys() + ) + + +class TestParser(unittest.TestCase): + def test_parse_parameters_raw_list(self): parameters = [ {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, {"username-password": [("user1", "111111"), ["test2", "222222"]]} ] - variables_mapping = {} - functions_mapping = {} cartesian_product_parameters = parser.parse_parameters(parameters) self.assertEqual( len(cartesian_product_parameters), @@ -429,7 +672,7 @@ class TestParser(unittest.TestCase): testcases = tests_mapping["testcases"] self.assertEqual( testcases[0]["config"]["variables"]["var_c"], - "${sum_two(1, 2)}" + "${sum_two($var_a, $var_b)}" ) self.assertEqual( testcases[0]["config"]["variables"]["PROJECT_KEY"], @@ -439,10 +682,9 @@ class TestParser(unittest.TestCase): parsed_testcases = parsed_tests_mapping["testcases"] self.assertIsInstance(parsed_testcases, list) test_dict1 = parsed_testcases[0]["teststeps"][0] - self.assertEqual(test_dict1["variables"]["var_c"], 3) - self.assertEqual(test_dict1["variables"]["PROJECT_KEY"], "ABCDEFGH") - self.assertEqual(test_dict1["variables"]["var_d"], test_dict1["variables"]["var_e"]) - self.assertEqual(parsed_testcases[0]["config"]["name"], '1230') + self.assertEqual(test_dict1["variables"]["var_c"].raw_string, "${sum_two($var_a, $var_b)}") + self.assertEqual(test_dict1["variables"]["PROJECT_KEY"].raw_string, "${ENV(PROJECT_KEY)}") + self.assertIsInstance(parsed_testcases[0]["config"]["name"], parser.LazyString) def test_parse_tests_override_variables(self): tests_mapping = { @@ -471,7 +713,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"], "user_test_001") + self.assertEqual(test_dict1_variables["username"].raw_string, "$creator") def test_parse_tests_base_url_priority(self): """ base_url & verify: priority test_dict > config @@ -499,7 +741,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://httprunner.org/api1") + self.assertEqual(test_dict["request"]["url"], "/api1") self.assertEqual(test_dict["request"]["verify"], True) def test_parse_tests_base_url_path_with_variable(self): @@ -527,7 +769,9 @@ 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://httprunner.org/api1") + self.assertEqual(test_dict["variables"]["host2"], "https://httprunner.org") + parsed_test_dict = parser.parse_lazy_data(test_dict, test_dict["variables"]) + self.assertEqual(parsed_test_dict["request"]["url"], "https://httprunner.org/api1") def test_parse_tests_base_url_test_dict(self): tests_mapping = { @@ -555,52 +799,10 @@ 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://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)}") + parsed_test_dict = parser.parse_lazy_data(test_dict, test_dict["variables"]) + self.assertEqual(parsed_test_dict["base_url"], "https://httprunner.org") def test_parse_tests_variable_with_function(self): - from tests.debugtalk import sum_two, gen_random_string tests_mapping = { "project_mapping": { "functions": { @@ -642,16 +844,18 @@ 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["variables"]["num3"], 10) - self.assertEqual(test_dict["variables"]["num2"], 6) - self.assertEqual(test_dict["variables"]["str1"], test_dict["variables"]["str2"]) + variables = parser.parse_variables_mapping(test_dict["variables"]) + self.assertEqual(variables["num3"], 10) + self.assertEqual(variables["num2"], 6) + parsed_test_dict = parser.parse_lazy_data(test_dict, variables) + self.assertEqual(parsed_test_dict["base_url"], "https://httprunner.org") self.assertEqual( - test_dict["request"]["url"], - "https://httprunner.org/api1/?num1=3&num2=6&num3=10" + parsed_test_dict["request"]["url"], + "/api1/?num1=3&num2=6&num3=10" ) + self.assertEqual(variables["str1"], variables["str2"]) def test_parse_tests_variable_not_found(self): - from tests.debugtalk import sum_two tests_mapping = { "project_mapping": { "functions": { @@ -687,15 +891,8 @@ 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["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=3&num2=6&num3=10&num4=${sum_two($num0, 5)}" - ) + with self.assertRaises(exceptions.VariableNotFound): + parser.parse_tests(tests_mapping) def test_parse_tests_base_url_teststep_empty(self): """ base_url & verify: priority test_dict > config @@ -723,7 +920,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.com/api1") + self.assertEqual(str(test_dict["base_url"]), 'LazyString($host)') self.assertEqual(test_dict["request"]["verify"], True) def test_parse_tests_verify_config_set(self): @@ -839,7 +1036,7 @@ class TestParser(unittest.TestCase): {"PROJECT_KEY": "${ENV(PROJECT_KEY)}"} ] } - result = parser.parse_data(content) + result = parser.eval_lazy_data(content) content = { "variables": [ @@ -847,7 +1044,7 @@ class TestParser(unittest.TestCase): ] } with self.assertRaises(exceptions.ParamsError): - parser.parse_data(content) + parser.eval_lazy_data(content) content = { "variables": [ @@ -855,7 +1052,7 @@ class TestParser(unittest.TestCase): ] } with self.assertRaises(exceptions.ParamsError): - parser.parse_data(content) + parser.eval_lazy_data(content) def test_extend_with_api(self): loader.load_project_tests(os.path.join(os.getcwd(), "tests")) diff --git a/tests/test_runner.py b/tests/test_runner.py index 936c879b..b4f6115f 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -1,7 +1,7 @@ import os import time -from httprunner import exceptions, loader, runner +from httprunner import exceptions, loader, parser, runner from httprunner.utils import deep_update_dict from tests.api_server import HTTPBIN_SERVER from tests.base import ApiServerUnittest @@ -37,7 +37,11 @@ class TestRunner(ApiServerUnittest): for testcase_file_path in testcase_file_path_list: testcases = loader.load_file(testcase_file_path) - + testcases = parser.prepare_lazy_data( + testcases, + self.debugtalk_functions, + {"expect_status_code", "token_len", "token", "success"} + ) config_dict = {} test_runner = runner.Runner(config_dict, self.debugtalk_functions) @@ -83,7 +87,7 @@ class TestRunner(ApiServerUnittest): "name": "basic test with httpbin", "base_url": HTTPBIN_SERVER, "setup_hooks": [ - "${sleep_N_secs(0.5)}" + "${sleep_N_secs(0.5)}", "${hook_print(setup)}" ], "teardown_hooks": [ @@ -91,6 +95,13 @@ class TestRunner(ApiServerUnittest): "${hook_print(teardown)}" ] } + prepared_config_dict = parser.prepare_lazy_data(config_dict, self.debugtalk_functions) + test_runner = runner.Runner(prepared_config_dict, self.debugtalk_functions) + end_time = time.time() + # check if testcase setup hook executed + self.assertGreater(end_time - start_time, 0.5) + + start_time = time.time() test = { "name": "get token", "request": { @@ -111,12 +122,6 @@ class TestRunner(ApiServerUnittest): {"check": "status_code", "expect": 200} ] } - test_runner = runner.Runner(config_dict, self.debugtalk_functions) - end_time = time.time() - # check if testcase setup hook executed - self.assertGreater(end_time - start_time, 0.5) - - start_time = time.time() test_runner.run_test(test) test_runner.run_test(test) end_time = time.time() @@ -130,6 +135,7 @@ class TestRunner(ApiServerUnittest): } test = { "name": "modify request headers", + "base_url": HTTPBIN_SERVER, "request": { "url": "/anything", "method": "POST", @@ -146,8 +152,9 @@ class TestRunner(ApiServerUnittest): {"check": "status_code", "expect": 200} ] } + parsed_test = parser.prepare_lazy_data(test, self.debugtalk_functions) test_runner = runner.Runner(config_dict, self.debugtalk_functions) - test_runner.run_test(test) + test_runner.run_test(parsed_test) test_variables_mapping = test_runner.session_context.test_variables_mapping self.assertEqual(test_variables_mapping["total"], 6) self.assertEqual(test_variables_mapping["request"]["data"], "a=1&b=2") @@ -159,6 +166,7 @@ class TestRunner(ApiServerUnittest): } test = { "name": "modify request headers", + "base_url": HTTPBIN_SERVER, "request": { "url": "/anything", "method": "POST", @@ -180,7 +188,8 @@ class TestRunner(ApiServerUnittest): ] } test_runner = runner.Runner(config_dict, self.debugtalk_functions) - test_runner.run_test(test) + parsed_test = parser.prepare_lazy_data(test, self.debugtalk_functions, {"request"}) + test_runner.run_test(parsed_test) def test_run_testcase_with_teardown_hooks_success(self): test = { @@ -232,8 +241,9 @@ class TestRunner(ApiServerUnittest): ], "teardown_hooks": ["${teardown_hook_sleep_N_secs($response, 2)}"] } + prepared_test = parser.prepare_lazy_data(test, self.debugtalk_functions, {"response"}) start_time = time.time() - self.test_runner.run_test(test) + self.test_runner.run_test(prepared_test) end_time = time.time() # check if teardown function executed self.assertGreater(end_time - start_time, 2)