Пример #1
0
def request(self, method, url, name=None, **kwargs):

    self.init_meta_data()

    # record test name
    self.meta_data["name"] = name

    # record original request info
    self.meta_data["data"][0]["request"]["method"] = method
    self.meta_data["data"][0]["request"]["url"] = url
    kwargs.setdefault("timeout", 120)
    self.meta_data["data"][0]["request"].update(kwargs)

    # prepend url with hostname unless it's already an absolute URL
    url = build_url(self.base_url, url)

    start_timestamp = time.time()
    response = self._send_request_safe_mode(method, url, **kwargs)

    # requests包get响应内容中文乱码解决
    if response.apparent_encoding:
        response.encoding = response.apparent_encoding

    response_time_ms = round((time.time() - start_timestamp) * 1000, 2)

    # get the length of the content, but if the argument stream is set to True, we take
    # the size from the content-length header, in order to not trigger fetching of the body
    if kwargs.get("stream", False):
        content_size = int(dict(response.headers).get("content-length") or 0)
    else:
        content_size = len(response.content or "")

    # record the consumed time
    self.meta_data["stat"] = {
        "response_time_ms": response_time_ms,
        "elapsed_ms": response.elapsed.microseconds / 1000.0,
        "content_size": content_size
    }
    # record request and response histories, include 30X redirection
    response_list = response.history + [response]
    self.meta_data["data"] = [
        self.get_req_resp_record(resp_obj) for resp_obj in response_list
    ]
    self.meta_data["data"][0]["request"].update(kwargs)
    try:
        response.raise_for_status()
    except RequestException as e:
        logger.log_error(u"{exception}".format(exception=str(e)))
    else:
        logger.log_info(
            """status_code: {}, response_time(ms): {} ms, response_length: {} bytes\n"""
            .format(response.status_code, response_time_ms, content_size))

    return response
Пример #2
0
    def _run_test(self, test_dict):
        """ run single teststep.

        Args:
            test_dict (dict): teststep info
                {
                    "name": "teststep description",
                    "skip": "skip this test unconditionally",
                    "times": 3,
                    "variables": [],            # optional, override
                    "request": {
                        "url": "http://127.0.0.1:5000/api/users/1000",
                        "method": "POST",
                        "headers": {
                            "Content-Type": "application/json",
                            "authorization": "$authorization",
                            "random": "$random"
                        },
                        "json": {"name": "user", "password": "******"}
                    },
                    "extract": {},              # optional
                    "validate": [],             # optional
                    "setup_hooks": [],          # optional
                    "teardown_hooks": []        # optional
                }

        Raises:
            exceptions.ParamsError
            exceptions.ValidationFailure
            exceptions.ExtractFailure

        """
        # clear meta data first to ensure independence for each test
        self.__clear_test_data()

        # check skip
        self._handle_skip_feature(test_dict)

        # prepare
        test_dict = utils.lower_test_dict_keys(test_dict)
        test_variables = test_dict.get("variables", {})
        self.session_context.init_test_variables(test_variables)

        # teststep name
        test_name = self.session_context.eval_content(test_dict.get(
            "name", ""))

        # parse test request
        raw_request = test_dict.get('request', {})
        parsed_test_request = self.session_context.eval_content(raw_request)
        self.session_context.update_test_variables("request",
                                                   parsed_test_request)

        # setup hooks
        setup_hooks = test_dict.get("setup_hooks", [])
        if setup_hooks:
            self.do_hook_actions(setup_hooks, HookTypeEnum.SETUP)

        # prepend url with base_url unless it's already an absolute URL
        url = parsed_test_request.pop('url')
        base_url = self.session_context.eval_content(
            test_dict.get("base_url", ""))
        parsed_url = utils.build_url(base_url, url)

        request_headers = parsed_test_request.setdefault("headers", {})
        if "HRUN-Request-ID" not in request_headers:
            parsed_test_request["headers"]["HRUN-Request-ID"] = \
                self.session_context.session_variables_mapping["HRUN-Request-ID"]

        try:
            method = parsed_test_request.pop('method')
            parsed_test_request.setdefault("verify", self.verify)
            group_name = parsed_test_request.pop("group", None)
        except KeyError:
            raise exceptions.ParamsError("URL or METHOD missed!")

        logger.info(f"{method} {parsed_url}")
        logger.debug(f"request kwargs(raw): {parsed_test_request}")

        # request
        resp = self.http_client_session.request(method,
                                                parsed_url,
                                                name=(group_name or test_name),
                                                **parsed_test_request)
        resp_obj = response.ResponseObject(resp)

        def log_req_resp_details():
            err_msg = "{} DETAILED REQUEST & RESPONSE {}\n".format(
                "*" * 32, "*" * 32)

            # log request
            err_msg += "====== request details ======\n"
            err_msg += f"url: {parsed_url}\n"
            err_msg += f"method: {method}\n"
            headers = parsed_test_request.pop("headers", {})
            err_msg += f"headers: {headers}\n"
            for k, v in parsed_test_request.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_obj.status_code}\n"
            err_msg += f"headers: {resp_obj.headers}\n"
            err_msg += f"body: {repr(resp_obj.text)}\n"
            logger.error(err_msg)

        # teardown hooks
        teardown_hooks = test_dict.get("teardown_hooks", [])
        if teardown_hooks:
            self.session_context.update_test_variables("response", resp_obj)
            self.do_hook_actions(teardown_hooks, HookTypeEnum.TEARDOWN)
            self.http_client_session.update_last_req_resp_record(resp_obj)

        # extract
        extractors = test_dict.get("extract", {})
        try:
            extracted_variables_mapping = resp_obj.extract_response(extractors)
            self.session_context.update_session_variables(
                extracted_variables_mapping)
        except (exceptions.ParamsError, exceptions.ExtractFailure):
            log_req_resp_details()
            raise

        # validate
        validators = test_dict.get("validate") or test_dict.get(
            "validators") or []
        validate_script = test_dict.get("validate_script", [])
        if validate_script:
            validators.append({
                "type": "python_script",
                "script": validate_script
            })

        validator = Validator(self.session_context, resp_obj)
        try:
            validator.validate(validators)
        except exceptions.ValidationFailure:
            log_req_resp_details()
            raise
        finally:
            self.validation_results = validator.validation_results
Пример #3
0
def __parse_testcase_tests(tests, config, project_mapping):
    """ override tests with testcase config variables, base_url and verify.
        test maybe nested testcase.

        variables priority:
        testcase config > testcase test > testcase_def config > testcase_def test > api

        base_url/verify priority:
        testcase test > testcase config > testsuite test > testsuite config > api

    Args:
        tests (list):
        config (dict):
        project_mapping (dict):

    """
    config_variables = config.pop("variables", {})
    config_base_url = config.pop("base_url", "")
    config_verify = config.pop("verify", True)
    functions = project_mapping.get("functions", {})

    for test_dict in tests:

        # 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

        test_dict.setdefault("verify", config_verify)

        # 1, testcase config => testcase tests
        # override test_dict variables
        test_dict["variables"] = utils.extend_variables(
            test_dict.pop("variables", {}),
            config_variables
        )
        test_dict["variables"] = parse_data(
            test_dict["variables"],
            test_dict["variables"],
            functions,
            raise_if_variable_not_found=False
        )

        # parse test_dict name
        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

            # 2, testcase test_dict => testcase_def config
            testcase_def = test_dict.pop("testcase_def")
            _extend_with_testcase(test_dict, testcase_def)

            # 3, testcase_def config => testcase_def 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
                )
Пример #4
0
    def _run_test(self, test_dict):
        """ run single teststep.

        Args:
            test_dict (dict): teststep info
                {
                    "name": "teststep description",
                    "skip": "skip this test unconditionally",
                    "times": 3,
                    "variables": [],            # optional, override
                    "request": {
                        "url": "http://127.0.0.1:5000/api/users/1000",
                        "method": "POST",
                        "headers": {
                            "Content-Type": "application/json",
                            "authorization": "$authorization",
                            "random": "$random"
                        },
                        "json": {"name": "user", "password": "******"}
                    },
                    "extract": {},              # optional
                    "validate": [],             # optional
                    "setup_hooks": [],          # optional
                    "teardown_hooks": []        # optional
                }

        Raises:
            exceptions.ParamsError
            exceptions.ValidationFailure
            exceptions.ExtractFailure

        """
        # clear meta data first to ensure independence for each test
        self.__clear_test_data()

        # check skip
        self._handle_skip_feature(test_dict)

        # prepare
        test_dict = utils.lower_test_dict_keys(test_dict)
        test_variables = test_dict.get("variables", {})
        self.session_context.init_test_variables(test_variables)

        # teststep name
        test_name = self.session_context.eval_content(test_dict.get(
            "name", ""))

        # parse test request
        raw_request = test_dict.get('request', {})
        parsed_test_request = self.session_context.eval_content(raw_request)
        self.session_context.update_test_variables("request",
                                                   parsed_test_request)

        # prepend url with base_url unless it's already an absolute URL
        url = parsed_test_request.pop('url')
        base_url = self.session_context.eval_content(
            test_dict.get("base_url", ""))
        parsed_url = utils.build_url(base_url, url)

        # setup hooks
        setup_hooks = test_dict.get("setup_hooks", [])
        if setup_hooks:
            self.do_hook_actions(setup_hooks, "setup")

        try:
            method = parsed_test_request.pop('method')
            parsed_test_request.setdefault("verify", self.verify)
            group_name = parsed_test_request.pop("group", None)
        except KeyError:
            raise exceptions.ParamsError("URL or METHOD missed!")

        # TODO: move method validation to json schema
        valid_methods = [
            "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"
        ]
        if method.upper() not in valid_methods:
            err_msg = u"Invalid HTTP method! => {}\n".format(method)
            err_msg += "Available HTTP methods: {}".format(
                "/".join(valid_methods))
            logger.log_error(err_msg)
            raise exceptions.ParamsError(err_msg)

        logger.log_info("{method} {url}".format(method=method, url=parsed_url))
        logger.log_debug(
            "request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request))

        # request
        resp = self.http_client_session.request(method,
                                                parsed_url,
                                                name=(group_name or test_name),
                                                **parsed_test_request)
        resp_obj = response.ResponseObject(resp)

        # teardown hooks
        teardown_hooks = test_dict.get("teardown_hooks", [])
        if teardown_hooks:
            self.session_context.update_test_variables("response", resp_obj)
            self.do_hook_actions(teardown_hooks, "teardown")
            self.http_client_session.update_last_req_resp_record(resp_obj)

        # extract
        extractors = test_dict.get("extract", {})
        extracted_variables_mapping = resp_obj.extract_response(extractors)
        self.session_context.update_session_variables(
            extracted_variables_mapping)

        # validate
        validators = test_dict.get("validate") or test_dict.get(
            "validators") or []
        validate_script = test_dict.get("validate_script", [])
        if validate_script:
            validators.append({
                "type": "python_script",
                "script": validate_script
            })

        validator = Validator(self.session_context, resp_obj)
        try:
            validator.validate(validators)
        except (exceptions.ParamsError, exceptions.ValidationFailure,
                exceptions.ExtractFailure):
            err_msg = "{} DETAILED REQUEST & RESPONSE {}\n".format(
                "*" * 32, "*" * 32)

            # log request
            err_msg += "====== request details ======\n"
            err_msg += "url: {}\n".format(parsed_url)
            err_msg += "method: {}\n".format(method)
            err_msg += "headers: {}\n".format(
                parsed_test_request.pop("headers", {}))
            for k, v in parsed_test_request.items():
                v = utils.omit_long_data(v)
                err_msg += "{}: {}\n".format(k, repr(v))

            err_msg += "\n"

            # log response
            err_msg += "====== response details ======\n"
            err_msg += "status_code: {}\n".format(resp_obj.status_code)
            err_msg += "headers: {}\n".format(resp_obj.headers)
            err_msg += "body: {}\n".format(repr(resp_obj.text))
            logger.log_error(err_msg)

            raise

        finally:
            # get request/response data and validate results
            self.meta_datas = getattr(self.http_client_session, "meta_data",
                                      {})
            self.meta_datas["validators"] = validator.validation_results
Пример #5
0
    def request(self, method, url, name=None, **kwargs):
        """
        Constructs and sends a :py:class:`requests.Request`.
        Returns :py:class:`requests.Response` object.

        :param method:
            method for the new :class:`Request` object.
        :param url:
            URL for the new :class:`Request` object.
        :param name: (optional)
            Placeholder, make compatible with Locust's HttpSession
        :param params: (optional)
            Dictionary or bytes to be sent in the query string for the :class:`Request`.
        :param data: (optional)
            Dictionary or bytes to send in the body of the :class:`Request`.
        :param headers: (optional)
            Dictionary of HTTP Headers to send with the :class:`Request`.
        :param cookies: (optional)
            Dict or CookieJar object to send with the :class:`Request`.
        :param files: (optional)
            Dictionary of ``'filename': file-like-objects`` for multipart encoding upload.
        :param auth: (optional)
            Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth.
        :param timeout: (optional)
            How long to wait for the server to send data before giving up, as a float, or \
            a (`connect timeout, read timeout <user/advanced.html#timeouts>`_) tuple.
            :type timeout: float or tuple
        :param allow_redirects: (optional)
            Set to True by default.
        :type allow_redirects: bool
        :param proxies: (optional)
            Dictionary mapping protocol to the URL of the proxy.
        :param stream: (optional)
            whether to immediately download the response content. Defaults to ``False``.
        :param verify: (optional)
            if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
        :param cert: (optional)
            if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
        """
        self.init_meta_data()

        # record test name
        self.meta_data["name"] = name

        # record original request info
        self.meta_data["data"][0]["request"]["method"] = method
        self.meta_data["data"][0]["request"]["url"] = url
        kwargs.setdefault("timeout", 120)
        self.meta_data["data"][0]["request"].update(kwargs)

        # prepend url with hostname unless it's already an absolute URL
        url = build_url(self.base_url, url)

        start_timestamp = time.time()
        response = self._send_request_safe_mode(method, url, **kwargs)
        response_time_ms = round((time.time() - start_timestamp) * 1000, 2)

        # get the length of the content, but if the argument stream is set to True, we take
        # the size from the content-length header, in order to not trigger fetching of the body
        if kwargs.get("stream", False):
            content_size = int(dict(response.headers).get("content-length") or 0)
        else:
            content_size = len(response.content or "")

        # record the consumed time
        self.meta_data["stat"] = {
            "response_time_ms": response_time_ms,
            "elapsed_ms": response.elapsed.microseconds / 1000.0,
            "content_size": content_size
        }

        # record request and response histories, include 30X redirection
        response_list = response.history + [response]
        self.meta_data["data"] = [
            self.get_req_resp_record(resp_obj)
            for resp_obj in response_list
        ]

        try:
            response.raise_for_status()
        except RequestException as e:
            logger.log_error(u"{exception}".format(exception=str(e)))
        else:
            logger.log_info(
                """status_code: {}, response_time(ms): {} ms, response_length: {} bytes\n""".format(
                    response.status_code,
                    response_time_ms,
                    content_size
                )
            )

        return response