def test_parse_data_func_var_duplicate(self): variables_mapping = { "var_1": "abc", "var_2": "def", "var_3": 123, "var_4": { "a": 1 }, "var_5": True, "var_6": None, } functions_mapping = {"func1": lambda x, y: str(x) + str(y)} value = parser.parse_data( "ABC${func1($var_1, $var_3)}--${func1($var_1, $var_3)}", variables_mapping, functions_mapping, ) self.assertEqual(value, "ABCabc123--abc123") value = parser.parse_data("ABC${func1($var_1, $var_3)}$var_1", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc123abc") value = parser.parse_data( "ABC${func1($var_1, $var_3)}$var_1--${func1($var_1, $var_3)}$var_1", variables_mapping, functions_mapping, ) self.assertEqual(value, "ABCabc123abc--abc123abc")
def __parse_config(self, config: TConfig) -> NoReturn: config.variables.update(self.__session_variables) config.variables = parse_variables_mapping( config.variables, self.__project_meta.functions) config.name = parse_data(config.name, config.variables, self.__project_meta.functions) config.base_url = parse_data(config.base_url, config.variables, self.__project_meta.functions)
def __call_hooks( self, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text, ) -> NoReturn: """ call hook actions. Args: hooks (list): each hook in hooks list maybe in two format. format1 (str): only call hook functions. ${func()} format2 (dict): assignment, the value returned by hook function will be assigned to variable. {"var": "${func()}"} step_variables: current step variables to call hook, include two special variables request: parsed request dict response: ResponseObject for current response hook_msg: setup/teardown request/testcase """ logger.info(f"call hook actions: {hook_msg}") if not isinstance(hooks, List): logger.error(f"Invalid hooks format: {hooks}") return for hook in hooks: if isinstance(hook, Text): # format 1: ["${func()}"] logger.debug(f"call hook function: {hook}") parse_data(hook, step_variables, self.__project_meta.functions) elif isinstance(hook, Dict) and len(hook) == 1: # format 2: {"var": "${func()}"} var_name, hook_content = list(hook.items())[0] hook_content_eval = parse_data(hook_content, step_variables, self.__project_meta.functions) logger.debug( f"call hook function: {hook_content}, got value: {hook_content_eval}" ) logger.debug( f"assign variable: {var_name} = {hook_content_eval}") step_variables[var_name] = hook_content_eval else: logger.error(f"Invalid hook format: {hook}")
def test_parse_data_testcase(self): variables = { "uid": "1000", "random": "A2dEx", "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", "data": { "name": "user", "password": "******" }, } functions = { "add_two_nums": lambda a, b=1: a + b, "get_timestamp": lambda: int(time.time() * 1000), } testcase_template = { "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1,2)}", "method": "POST", "headers": { "Content-Type": "application/json", "authorization": "$authorization", "random": "$random", "sum": "${add_two_nums(1, 2)}", }, "body": "$data", } parsed_testcase = parser.parse_data(testcase_template, variables, functions) self.assertEqual(parsed_testcase["url"], "http://127.0.0.1:5000/api/users/1000/3") self.assertEqual(parsed_testcase["headers"]["authorization"], variables["authorization"]) self.assertEqual(parsed_testcase["headers"]["random"], variables["random"]) self.assertEqual(parsed_testcase["body"], variables["data"]) self.assertEqual(parsed_testcase["headers"]["sum"], 3)
def test_parse_data_request(self): content = { "request": { "url": "/api/users/$uid", "method": "$method", "headers": { "token": "$token" }, "data": { "null": None, "true": True, "false": False, "empty_str": "", "value": "abc${add_one(3)}def", }, } } variables_mapping = {"uid": 1000, "method": "POST", "token": "abc123"} functions_mapping = {"add_one": lambda x: x + 1} result = parser.parse_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"]) self.assertIsNone(result["request"]["data"]["null"]) self.assertTrue(result["request"]["data"]["true"]) self.assertFalse(result["request"]["data"]["false"]) self.assertEqual("", result["request"]["data"]["empty_str"]) self.assertEqual("abc4def", result["request"]["data"]["value"])
def test_start(self, param: Dict = None) -> "HttpRunner": """main entrance, discovered by pytest""" self.__init_tests__() self.__project_meta = self.__project_meta or load_project_meta( self.__config.path) self.__case_id = self.__case_id or str(uuid.uuid4()) self.__log_path = self.__log_path or os.path.join( self.__project_meta.RootDir, "logs", f"{self.__case_id}.run.log") log_handler = logger.add(self.__log_path, level="DEBUG") # parse config name config_variables = self.__config.variables if param: config_variables.update(param) config_variables.update(self.__session_variables) self.__config.name = parse_data(self.__config.name, config_variables, self.__project_meta.functions) if USE_ALLURE: # update allure report meta allure.dynamic.title(self.__config.name) allure.dynamic.description(f"TestCase ID: {self.__case_id}") logger.info( f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}" ) try: return self.run_testcase( TestCase(config=self.__config, teststeps=self.__teststeps)) finally: logger.remove(log_handler) logger.info(f"generate testcase log: {self.__log_path}")
def __execute( self, aspect: Text, step: TStep, variables_mapping=None, functions_mapping=None, ) -> NoReturn: need_configured_attr = ["mysql", "redis", "mongodb"] not_need_configured_attr = ["cmd"] has_attr = False for attr in need_configured_attr: # 判断是否有数据源 if attr in step.variables: has_attr = True break if aspect == "setup": if not has_attr: for attr in not_need_configured_attr: # 判断是否有数据源 for setup in step.setup: if attr in setup: has_attr = True break if has_attr is True: if step.setup: logger.info("setup begin execute >>>>>>") for s in step.setup: parse_data(s, variables_mapping, functions_mapping) elif aspect == "teardown": if not has_attr: for attr in not_need_configured_attr: # 判断是否有数据源 for teardown in step.teardown: if attr in teardown: has_attr = True break if has_attr is True: if step.teardown: logger.info("teardown begin execute >>>>>>") for s in step.teardown: parse_data(s, variables_mapping, functions_mapping)
def test_parse_data_multiple_identical_variables(self): variables_mapping = { "var_1": "abc", "var_2": "def", } self.assertEqual( parser.parse_data("/$var_1/$var_2/$var_1", variables_mapping), "/abc/def/abc", ) variables_mapping = {"userid": 100, "data": 1498} content = "/users/$userid/training/$data?userId=$userid&data=$data" self.assertEqual( parser.parse_data(content, variables_mapping), "/users/100/training/1498?userId=100&data=1498", ) variables_mapping = {"user": 100, "userid": 1000, "data": 1498} content = "/users/$user/$userid/$data?userId=$userid&data=$data" self.assertEqual( parser.parse_data(content, variables_mapping), "/users/100/1000/1498?userId=1000&data=1498", )
def extract(self, extractors: Dict[Text, Text], variables_mapping: VariablesMapping = None, functions_mapping: FunctionsMapping = None) -> Dict[Text, Any]: if not extractors: return {} extract_mapping = {} for key, field in extractors.items(): field_value = self._search_jmespath(field) if field_value == field: # if not jmespath syntax field_value = parse_data(field, variables_mapping, functions_mapping) extract_mapping[key] = field_value logger.info(f"extract mapping: {extract_mapping}") return extract_mapping
def test_parse_data_func_abnormal(self): variables_mapping = { "var_1": "abc", "var_2": "def", "var_3": 123, "var_4": { "a": 1 }, "var_5": True, "var_6": None, } functions_mapping = {"func1": lambda x, y: str(x) + str(y)} # { value = parser.parse_data("ABC$var_1{", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc{") value = parser.parse_data("{ABC$var_1{}a}", variables_mapping, functions_mapping) self.assertEqual(value, "{ABCabc{}a}") value = parser.parse_data("AB{C$var_1{}a}", variables_mapping, functions_mapping) self.assertEqual(value, "AB{Cabc{}a}") # } value = parser.parse_data("ABC$var_1}", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc}") # $$ value = parser.parse_data("ABC$$var_1{", variables_mapping, functions_mapping) self.assertEqual(value, "ABC$var_1{") # $$$ value = parser.parse_data("ABC$$$var_1{", variables_mapping, functions_mapping) self.assertEqual(value, "ABC$abc{") # $$$$ value = parser.parse_data("ABC$$$$var_1{", variables_mapping, functions_mapping) self.assertEqual(value, "ABC$$var_1{") # ${ value = parser.parse_data("ABC$var_1${", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc${") value = parser.parse_data("ABC$var_1${a", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc${a") # $} value = parser.parse_data("ABC$var_1$}a", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc$}a") # }{ value = parser.parse_data("ABC$var_1}{a", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc}{a") # {} value = parser.parse_data("ABC$var_1{}a", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc{}a")
def test_parse_data_string_with_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)) } result = parser.parse_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), 2, ) self.assertEqual( parser.parse_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), "/api/3", ) with self.assertRaises(FunctionNotFound): parser.parse_data("/api/${gen_md5(abc)}") variables_mapping = { "var_1": "abc", "var_2": "def", "var_3": 123, "var_4": { "a": 1 }, "var_5": True, "var_6": None, } functions_mapping = {"func1": lambda x, y: str(x) + str(y)} value = parser.parse_data("${func1($var_1, $var_3)}", variables_mapping, functions_mapping) self.assertEqual(value, "abc123") value = parser.parse_data("ABC${func1($var_1, $var_3)}DE", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc123DE") value = parser.parse_data("ABC${func1($var_1, $var_3)}$var_5", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc123True") value = parser.parse_data("ABC${func1($var_1, $var_3)}DE$var_4", variables_mapping, functions_mapping) self.assertEqual(value, "ABCabc123DE{'a': 1}") value = parser.parse_data("ABC$var_5${func1($var_1, $var_3)}", variables_mapping, functions_mapping) self.assertEqual(value, "ABCTrueabc123") value = parser.parse_data("ABC${ord(a)}DEF${len(abcd)}", variables_mapping, functions_mapping) self.assertEqual(value, "ABC97DEF4")
def test_parse_data_string_with_variables(self): variables_mapping = { "var_1": "abc", "var_2": "def", "var_3": 123, "var_4": { "a": 1 }, "var_5": True, "var_6": None, } self.assertEqual(parser.parse_data("$var_1", variables_mapping), "abc") self.assertEqual(parser.parse_data("${var_1}", variables_mapping), "abc") self.assertEqual(parser.parse_data("var_1", variables_mapping), "var_1") self.assertEqual(parser.parse_data("$var_1#XYZ", variables_mapping), "abc#XYZ") self.assertEqual(parser.parse_data("${var_1}#XYZ", variables_mapping), "abc#XYZ") self.assertEqual( parser.parse_data("/$var_1/$var_2/var3", variables_mapping), "/abc/def/var3") self.assertEqual(parser.parse_data("$var_3", variables_mapping), 123) self.assertEqual(parser.parse_data("$var_4", variables_mapping), {"a": 1}) self.assertEqual(parser.parse_data("$var_5", variables_mapping), True) self.assertEqual(parser.parse_data("abc$var_5", variables_mapping), "abcTrue") self.assertEqual(parser.parse_data("abc$var_4", variables_mapping), "abc{'a': 1}") self.assertEqual(parser.parse_data("$var_6", variables_mapping), None) with self.assertRaises(VariableNotFound): parser.parse_data("/api/$SECRET_KEY", variables_mapping) self.assertEqual( parser.parse_data(["$var_1", "$var_2"], variables_mapping), ["abc", "def"]) self.assertEqual( parser.parse_data({"$var_1": "$var_2"}, variables_mapping), {"abc": "def"}) # format: $var value = parser.parse_data("ABC$var_1", variables_mapping) self.assertEqual(value, "ABCabc") value = parser.parse_data("ABC$var_1$var_3", variables_mapping) self.assertEqual(value, "ABCabc123") value = parser.parse_data("ABC$var_1/$var_3", variables_mapping) self.assertEqual(value, "ABCabc/123") value = parser.parse_data("ABC$var_1/", variables_mapping) self.assertEqual(value, "ABCabc/") value = parser.parse_data("ABC$var_1$", variables_mapping) self.assertEqual(value, "ABCabc$") value = parser.parse_data("ABC$var_1/123$var_1/456", variables_mapping) self.assertEqual(value, "ABCabc/123abc/456") value = parser.parse_data("ABC$var_1/$var_2/$var_1", variables_mapping) self.assertEqual(value, "ABCabc/def/abc") value = parser.parse_data("func1($var_1, $var_3)", variables_mapping) self.assertEqual(value, "func1(abc, 123)") # format: ${var} value = parser.parse_data("ABC${var_1}", variables_mapping) self.assertEqual(value, "ABCabc") value = parser.parse_data("ABC${var_1}${var_3}", variables_mapping) self.assertEqual(value, "ABCabc123") value = parser.parse_data("ABC${var_1}/${var_3}", variables_mapping) self.assertEqual(value, "ABCabc/123") value = parser.parse_data("ABC${var_1}/", variables_mapping) self.assertEqual(value, "ABCabc/") value = parser.parse_data("ABC${var_1}123", variables_mapping) self.assertEqual(value, "ABCabc123") value = parser.parse_data("ABC${var_1}/123${var_1}/456", variables_mapping) self.assertEqual(value, "ABCabc/123abc/456") value = parser.parse_data("ABC${var_1}/${var_2}/${var_1}", variables_mapping) self.assertEqual(value, "ABCabc/def/abc") value = parser.parse_data("func1(${var_1}, ${var_3})", variables_mapping) self.assertEqual(value, "func1(abc, 123)")
def uniform_validator(validator, variables_mapping: VariablesMapping = None, functions_mapping: FunctionsMapping = None, ): """ unify validator Args: functions_mapping: variables_mapping: validator (dict): validator maybe in two formats: format1: this is kept for compatibility with the previous versions. {"check": "status_code", "comparator": "eq", "expect": 201} {"check": "$resp_body_success", "comparator": "eq", "expect": True} format2: recommended new version, {assert: [check_item, expected_value]} {'eq': ['status_code', 201]} {'eq': ['$resp_body_success', True]} Returns dict: validator info { "check": "status_code", "expect": 201, "assert": "equals" } """ check_item = "" expect_value = "" message = "" comparator = "" if not isinstance(validator, dict): raise ParamsError(f"invalid validator: {validator}") if "check" in validator and "expect" in validator: # format1 check_item = validator["check"] expect_value = validator["expect"] message = validator.get("message", "") comparator = validator.get("comparator", "eq") elif len(validator) == 1: # format2 comparator = list(validator.keys())[0] compare_values = validator[comparator] if not isinstance(compare_values, list) or len(compare_values) not in [2, 3, 4, 5]: raise ParamsError(f"invalid validator: {validator}") if len(compare_values) == 2 or len(compare_values) == 3: check_item = compare_values[0] expect_value = compare_values[1] if len(compare_values) == 4 or len(compare_values) == 5: condition = parse_data( compare_values[0], variables_mapping, functions_mapping ) check_item = compare_values[1] if eval(condition) is True: expect_value = compare_values[2] else: expect_value = compare_values[3] if len(compare_values) != 3 and len(compare_values) != 5: message = "" elif len(compare_values) == 3: message = compare_values[2] elif len(compare_values) == 5: message = compare_values[4] else: raise ParamsError(f"invalid validator: {validator}") # uniform comparator, e.g. lt => less_than, eq => equals assert_method = get_uniform_comparator(comparator) return { "check": check_item, "expect": expect_value, "assert": assert_method, "message": message, }
def validate( self, validators: Validators, variables_mapping: VariablesMapping = None, functions_mapping: FunctionsMapping = None, ) -> NoReturn: variables_mapping = variables_mapping or {} functions_mapping = functions_mapping or {} self.validation_results = {} if not validators: return validate_pass = True failures = [] for v in validators: if "validate_extractor" not in self.validation_results: self.validation_results["validate_extractor"] = [] u_validator = uniform_validator(v, variables_mapping, functions_mapping) # check item check_item = u_validator["check"] if isinstance(check_item, str): if "$" in check_item: # check_item is variable or function check_item = parse_data( check_item, variables_mapping, functions_mapping ) check_item = parse_string_value(check_item) if check_item and isinstance(check_item, Text): check_value = self._search_jmespath(check_item) else: # variable or function evaluation result is "" or not text check_value = check_item # comparator assert_method = u_validator["assert"] assert_func = get_mapping_function(assert_method, functions_mapping) # expect item expect_item = u_validator["expect"] # parse expected value with config/teststep/extracted variables expect_value = parse_data(expect_item, variables_mapping, functions_mapping) # message message = u_validator["message"] # parse message with config/teststep/extracted variables message = parse_data(message, variables_mapping, functions_mapping) validate_msg = f"assert {check_item} {assert_method} {expect_value}({type(expect_value).__name__})" validator_dict = { "comparator": assert_method, "check": check_item, "check_value": check_value, "expect": expect_item, "expect_value": expect_value, "message": message, } try: assert_func(check_value, expect_value, message) validate_msg += "\t==> pass" logger.info(validate_msg) validator_dict["check_result"] = "pass" except AssertionError as ex: validate_pass = False validator_dict["check_result"] = "fail" validate_msg += "\t==> fail" validate_msg += ( f"\n" f"check_item: {check_item}\n" f"check_value: {check_value}({type(check_value).__name__})\n" f"assert_method: {assert_method}\n" f"expect_value: {expect_value}({type(expect_value).__name__})" ) message = str(ex) if message: validate_msg += f"\nmessage: {message}" logger.error(validate_msg) failures.append(validate_msg) self.validation_results["validate_extractor"].append(validator_dict) if not validate_pass: failures_string = "\n".join([failure for failure in failures]) raise ValidationFailure(failures_string)
def __run_step_request(self, step: TStep) -> StepData: """run teststep: request""" step_data = StepData(name=step.name) # parse prepare_upload_step(step, self.__project_meta.functions) request_dict = step.request.dict() request_dict.pop("upload", None) parsed_request_dict = parse_data(request_dict, step.variables, self.__project_meta.functions) parsed_request_dict["headers"].setdefault( "HRUN-Request-ID", f"HRUN-{self.__case_id}-{str(int(time.time() * 1000))[-6:]}", ) step.variables["request"] = parsed_request_dict # setup hooks if step.setup_hooks: self.__call_hooks(step.setup_hooks, step.variables, "setup request") variables_mapping = step.variables # variables_mapping.update(extract_mapping) # execute setup if step.setup: self.__execute("setup", step, variables_mapping, self.__project_meta.functions) # prepare arguments method = parsed_request_dict.pop("method") url_path = parsed_request_dict.pop("url") url = build_url(self.__config.base_url, url_path) parsed_request_dict["verify"] = self.__config.verify parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {}) # request resp = self.__session.request(method, url, **parsed_request_dict) resp_obj = ResponseObject(resp) step.variables["response"] = resp_obj # teardown hooks if step.teardown_hooks: self.__call_hooks(step.teardown_hooks, step.variables, "teardown request") def log_req_resp_details(): err_msg = "\n{} DETAILED REQUEST & RESPONSE {}\n".format( "*" * 32, "*" * 32) # log request err_msg += "====== request details ======\n" err_msg += f"url: {url}\n" err_msg += f"method: {method}\n" headers = parsed_request_dict.pop("headers", {}) err_msg += f"headers: {headers}\n" for k, v in parsed_request_dict.items(): v = utils.omit_long_data(v) err_msg += f"{k}: {repr(v)}\n" err_msg += "\n" # log response err_msg += "====== response details ======\n" err_msg += f"status_code: {resp.status_code}\n" err_msg += f"headers: {resp.headers}\n" err_msg += f"body: {repr(resp.text)}\n" logger.error(err_msg) # extract extractors = step.extract extract_mapping = resp_obj.extract(extractors, variables_mapping, self.__project_meta.functions) step_data.export_vars = extract_mapping variables_mapping = step.variables variables_mapping.update(extract_mapping) # 执行teardown if step.teardown: self.__execute("teardown", step, variables_mapping, self.__project_meta.functions) # validate validators = step.validators session_success = False try: resp_obj.validate(validators, variables_mapping, self.__project_meta.functions) session_success = True except ValidationFailure: session_success = False log_req_resp_details() # log testcase duration before raise ValidationFailure self.__duration = time.time() - self.__start_at raise finally: self.success = session_success step_data.success = session_success if hasattr(self.__session, "data"): # rrtv_httprunner.client.HttpSession, not locust.clients.HttpSession # save request & response meta data self.__session.data.success = session_success self.__session.data.validators = resp_obj.validation_results # save step data step_data.data = self.__session.data return step_data