diff --git a/README.md b/README.md index 0e87c3c1..e29230b9 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ To ensure the installation or upgrade is successful, you can execute command `at ```text $ ate -V jenkins-mail-py version: 0.2.5 -ApiTestEngine version: 0.4.0 +ApiTestEngine version: 0.5.0 ``` Execute the command `ate -h` to view command help. diff --git a/ate/context.py b/ate/context.py index ebd21238..6d396716 100644 --- a/ate/context.py +++ b/ate/context.py @@ -6,7 +6,8 @@ import sys import types from collections import OrderedDict -from ate import testcase, utils +from ate.testcase import TestcaseParser +from ate import utils def is_function(tup): @@ -22,6 +23,7 @@ class Context(object): def __init__(self): self.testset_shared_variables_mapping = OrderedDict() self.testcase_variables_mapping = OrderedDict() + self.testcase_parser = TestcaseParser() self.init_context() def init_context(self, level='testset'): @@ -40,6 +42,9 @@ class Context(object): self.testcase_request_config = {} self.testcase_variables_mapping = copy.deepcopy(self.testset_shared_variables_mapping) + self.testcase_parser.bind_functions(self.testcase_functions_config) + self.testcase_parser.bind_variables(self.testcase_variables_mapping) + def import_requires(self, modules): """ import required modules dynamicly """ @@ -88,16 +93,13 @@ class Context(object): """ for variable_bind in variable_binds: for variable_name, value in variable_bind.items(): - variable_evale_value = testcase.parse_content_with_bindings( - value, - self.testcase_variables_mapping, - self.testcase_functions_config - ) + variable_evale_value = self.testcase_parser.parse_content_with_bindings(value) if level == "testset": self.testset_shared_variables_mapping[variable_name] = variable_evale_value self.testcase_variables_mapping[variable_name] = variable_evale_value + self.testcase_parser.bind_variables(self.testcase_variables_mapping) def __update_context_functions_config(self, level, config_mapping): """ @@ -109,6 +111,7 @@ class Context(object): self.testset_functions_config.update(config_mapping) self.testcase_functions_config.update(config_mapping) + self.testcase_parser.bind_functions(self.testcase_functions_config) def register_request(self, request_dict, level="testcase"): self.__update_context_request_config(level, request_dict) @@ -130,10 +133,8 @@ class Context(object): def get_parsed_request(self): """ get parsed request, with each variable replaced by bind value. """ - parsed_request = testcase.parse_content_with_bindings( - self.testcase_request_config, - self.testcase_variables_mapping, - self.testcase_functions_config + parsed_request = self.testcase_parser.parse_content_with_bindings( + self.testcase_request_config ) return parsed_request diff --git a/ate/testcase.py b/ate/testcase.py index 428b489f..9b5f5146 100644 --- a/ate/testcase.py +++ b/ate/testcase.py @@ -24,44 +24,8 @@ def extract_variables(content): except TypeError: return [] -def eval_content_variables(content, variable_mapping): - """ replace all variables of string content with mapping value. - @param (str) content - @return (str) parsed content - - e.g. - variable_mapping = { - "var_1": "abc", - "var_2": "def" - } - $var_1 => "abc" - $var_1#XYZ => "abc#XYZ" - /$var_1/$var_2/var3 => "/abc/def/var3" - ${func($var_1, $var_2, xyz)} => "${func(abc, def, xyz)}" - """ - variables_list = extract_variables(content) - for variable_name in variables_list: - if variable_name not in variable_mapping: - raise ParamsError( - "%s is not defined in bind variables!" % variable_name) - - variable_value = variable_mapping.get(variable_name) - if "${}".format(variable_name) == content: - # content is a variable - content = variable_value - else: - # content contains one or many variables - content = content.replace( - "${}".format(variable_name), - str(variable_value), 1 - ) - - return content - def extract_functions(content): """ extract all functions from string content, which are in format ${fun()} - Notice: extract_functions should be called after eval_content_variables, thus - there will not be any variables in given content @param (str) content @return (list) functions list @@ -76,35 +40,6 @@ def extract_functions(content): except TypeError: return [] -def eval_content_functions(content, variables_binds, functions_binds): - functions_list = extract_functions(content) - for func_content in functions_list: - function_meta = parse_function(func_content) - func_name = function_meta['func_name'] - - func = functions_binds.get(func_name) - if func is None: - raise ParamsError( - "%s is not defined in bind functions!" % func_name) - - args = function_meta.get('args', []) - kwargs = function_meta.get('kwargs', {}) - args = parse_content_with_bindings(args, variables_binds, functions_binds) - kwargs = parse_content_with_bindings(kwargs, variables_binds, functions_binds) - eval_value = func(*args, **kwargs) - - if func_content == content: - # content is a variable - content = eval_value - else: - # content contains one or many variables - content = content.replace( - func_content, - str(eval_value), 1 - ) - - return content - def parse_string_value(str_value): """ parse string to number if possible e.g. "123" => 123 @@ -152,76 +87,152 @@ def parse_function(content): return function_meta -def parse_content_with_bindings(content, variables_binds, functions_binds): - """ evaluate content recursively, each variable in content will be - evaluated with bind variables and functions. +def eval_content_variables(content, variable_mapping): + """ replace all variables of string content with mapping value. + @param (str) content + @return (str) parsed content - variables marker: $variable. - @param (dict) content in any data structure - { - "url": "http://127.0.0.1:5000/api/users/$uid", - "method": "POST", - "headers": { - "Content-Type": "application/json", - "authorization": "$authorization", - "random": "$random", - "sum": "${add_two_nums(1, 2)}" - }, - "body": "$data" - } - @param (dict) variables_binds, variables binds mapping - { - "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", - "random": "A2dEx", - "data": {"name": "user", "password": "123456"}, - "uuid": 1000 - } - @param (dict) functions_binds, functions binds mapping - { - "add_two_nums": lambda a, b=1: a + b - } - @return (dict) parsed content with evaluated bind values - { - "url": "http://127.0.0.1:5000/api/users/1000", - "method": "POST", - "headers": { - "Content-Type": "application/json", - "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", - "random": "A2dEx", - "sum": 3 - }, - "body": {"name": "user", "password": "123456"} + e.g. + variable_mapping = { + "var_1": "abc", + "var_2": "def" } + $var_1 => "abc" + $var_1#XYZ => "abc#XYZ" + /$var_1/$var_2/var3 => "/abc/def/var3" + ${func($var_1, $var_2, xyz)} => "${func(abc, def, xyz)}" """ + variables_list = extract_variables(content) + for variable_name in variables_list: + if variable_name not in variable_mapping: + raise ParamsError( + "%s is not defined in bind variables!" % variable_name) - if isinstance(content, (list, tuple)): - return [ - parse_content_with_bindings(item, variables_binds, functions_binds) - for item in content - ] - - if isinstance(content, dict): - evaluated_data = {} - for key, value in content.items(): - eval_key = parse_content_with_bindings( - key, variables_binds, functions_binds) - eval_value = parse_content_with_bindings( - value, variables_binds, functions_binds) - evaluated_data[eval_key] = eval_value - - return evaluated_data - - if isinstance(content, (int, float)): - return content - - # content is in string format here - content = "" if content is None else content.strip() - - # replace functions with evaluated value - # Notice: eval_content_functions must be called before eval_content_variables - content = eval_content_functions(content, variables_binds, functions_binds) - - # replace variables with binding value - content = eval_content_variables(content, variables_binds) + variable_value = variable_mapping.get(variable_name) + if "${}".format(variable_name) == content: + # content is a variable + content = variable_value + else: + # content contains one or many variables + content = content.replace( + "${}".format(variable_name), + str(variable_value), 1 + ) return content + + +class TestcaseParser(object): + + def __init__(self, variables_binds={}, functions_binds={}): + self.bind_variables(variables_binds) + self.bind_functions(functions_binds) + + def bind_variables(self, variables_binds): + """ bind variables to current testcase parser + @param (dict) variables_binds, variables binds mapping + { + "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", + "random": "A2dEx", + "data": {"name": "user", "password": "123456"}, + "uuid": 1000 + } + """ + self.variables_binds = variables_binds + + def bind_functions(self, functions_binds): + """ bind functions to current testcase parser + @param (dict) functions_binds, functions binds mapping + { + "add_two_nums": lambda a, b=1: a + b + } + """ + self.functions_binds = functions_binds + + def eval_content_functions(self, content): + functions_list = extract_functions(content) + for func_content in functions_list: + function_meta = parse_function(func_content) + func_name = function_meta['func_name'] + + func = self.functions_binds.get(func_name) + if func is None: + raise ParamsError( + "%s is not defined in bind functions!" % func_name) + + args = function_meta.get('args', []) + kwargs = function_meta.get('kwargs', {}) + args = self.parse_content_with_bindings(args) + kwargs = self.parse_content_with_bindings(kwargs) + eval_value = func(*args, **kwargs) + + if func_content == content: + # content is a variable + content = eval_value + else: + # content contains one or many variables + content = content.replace( + func_content, + str(eval_value), 1 + ) + + return content + + def parse_content_with_bindings(self, content): + """ parse content recursively, each variable and function in content will be evaluated. + + @param (dict) content in any data structure + { + "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1, 1)}", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "authorization": "$authorization", + "random": "$random", + "sum": "${add_two_nums(1, 2)}" + }, + "body": "$data" + } + @return (dict) parsed content with evaluated bind values + { + "url": "http://127.0.0.1:5000/api/users/1000/2", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", + "random": "A2dEx", + "sum": 3 + }, + "body": {"name": "user", "password": "123456"} + } + """ + + if isinstance(content, (list, tuple)): + return [ + self.parse_content_with_bindings(item) + for item in content + ] + + if isinstance(content, dict): + evaluated_data = {} + for key, value in content.items(): + eval_key = self.parse_content_with_bindings(key) + eval_value = self.parse_content_with_bindings(value) + evaluated_data[eval_key] = eval_value + + return evaluated_data + + if isinstance(content, (int, float)): + return content + + # content is in string format here + content = "" if content is None else content.strip() + + # replace functions with evaluated value + # Notice: eval_content_functions must be called before eval_content_variables + content = self.eval_content_functions(content) + + # replace variables with binding value + content = eval_content_variables(content, self.variables_binds) + + return content diff --git a/tests/test_testcase.py b/tests/test_testcase.py index 3fb07a9a..0ead19b3 100644 --- a/tests/test_testcase.py +++ b/tests/test_testcase.py @@ -149,24 +149,25 @@ class TestcaseParserUnittest(unittest.TestCase): "str_1": "str_value1", "str_2": "str_value2" } + testcase_parser = testcase.TestcaseParser(variables_binds=variables_binds) self.assertEqual( - testcase.parse_content_with_bindings("$str_1", variables_binds, {}), + testcase_parser.parse_content_with_bindings("$str_1"), "str_value1" ) self.assertEqual( - testcase.parse_content_with_bindings("123$str_1/456", variables_binds, {}), + testcase_parser.parse_content_with_bindings("123$str_1/456"), "123str_value1/456" ) with self.assertRaises(ParamsError): - testcase.parse_content_with_bindings("$str_3", variables_binds, {}) + testcase_parser.parse_content_with_bindings("$str_3") self.assertEqual( - testcase.parse_content_with_bindings(["$str_1", "str3"], variables_binds, {}), + testcase_parser.parse_content_with_bindings(["$str_1", "str3"]), ["str_value1", "str3"] ) self.assertEqual( - testcase.parse_content_with_bindings({"key": "$str_1"}, variables_binds, {}), + testcase_parser.parse_content_with_bindings({"key": "$str_1"}), {"key": "str_value1"} ) @@ -175,9 +176,10 @@ class TestcaseParserUnittest(unittest.TestCase): "userid": 100, "data": 1498 } + testcase_parser = testcase.TestcaseParser(variables_binds=variables_binds) content = "/users/$userid/training/$data?userId=$userid&data=$data" self.assertEqual( - testcase.parse_content_with_bindings(content, variables_binds, {}), + testcase_parser.parse_content_with_bindings(content), "/users/100/training/1498?userId=100&data=1498" ) @@ -187,9 +189,10 @@ class TestcaseParserUnittest(unittest.TestCase): "userid": 1000, "data": 1498 } + testcase_parser = testcase.TestcaseParser(variables_binds=variables_binds) content = "/users/$user/$userid/$data?userId=$userid&data=$data" self.assertEqual( - testcase.parse_content_with_bindings(content, variables_binds, {}), + testcase_parser.parse_content_with_bindings(content), "/users/100/1000/1498?userId=1000&data=1498" ) @@ -199,18 +202,19 @@ class TestcaseParserUnittest(unittest.TestCase): "gen_random_string": lambda str_len: ''.join(random.choice(string.ascii_letters + string.digits) \ for _ in range(str_len)) } + testcase_parser = testcase.TestcaseParser(functions_binds=functions_binds) - result = testcase.parse_content_with_bindings("${gen_random_string(5)}", {}, functions_binds) + result = testcase_parser.parse_content_with_bindings("${gen_random_string(5)}") self.assertEqual(len(result), 5) add_two_nums = lambda a, b=1: a + b functions_binds["add_two_nums"] = add_two_nums self.assertEqual( - testcase.parse_content_with_bindings("${add_two_nums(1)}", {}, functions_binds), + testcase_parser.parse_content_with_bindings("${add_two_nums(1)}"), 2 ) self.assertEqual( - testcase.parse_content_with_bindings("${add_two_nums(1, 2)}", {}, functions_binds), + testcase_parser.parse_content_with_bindings("${add_two_nums(1, 2)}"), 3 ) @@ -252,12 +256,13 @@ class TestcaseParserUnittest(unittest.TestCase): functions_binds = { "add_two_nums": lambda a, b=1: a + b } + testcase_parser = testcase.TestcaseParser(functions_binds=functions_binds) self.assertEqual( - testcase.eval_content_functions("${add_two_nums(1, 2)}", {}, functions_binds), + testcase_parser.eval_content_functions("${add_two_nums(1, 2)}"), 3 ) self.assertEqual( - testcase.eval_content_functions("/api/${add_two_nums(1, 2)}", {}, functions_binds), + testcase_parser.eval_content_functions("/api/${add_two_nums(1, 2)}"), "/api/3" ) @@ -283,8 +288,8 @@ class TestcaseParserUnittest(unittest.TestCase): }, "body": "$data" } - parsed_testcase = testcase.parse_content_with_bindings( - testcase_template, variables_binds, functions_binds) + parsed_testcase = testcase.TestcaseParser(variables_binds, functions_binds)\ + .parse_content_with_bindings(testcase_template) self.assertEqual( parsed_testcase["url"],