Esempio n. 1
0
    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")
Esempio n. 2
0
 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)
Esempio n. 3
0
    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}")
Esempio n. 4
0
 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)
Esempio n. 5
0
 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"])
Esempio n. 6
0
    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}")
Esempio n. 7
0
    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)
Esempio n. 8
0
    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",
        )
Esempio n. 9
0
    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
Esempio n. 10
0
    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")
Esempio n. 11
0
    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")
Esempio n. 12
0
    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)")
Esempio n. 13
0
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,
    }
Esempio n. 14
0
    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)
Esempio n. 15
0
    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