Beispiel #1
0
    def validate(self, validators, resp_obj):
        """ make validations
        """
        evaluated_validators = []
        if not validators:
            return evaluated_validators

        logger.log_info("start to validate.")
        validate_pass = True
        failures = []

        for validator in validators:
            # evaluate validators with context variable mapping.
            evaluated_validator = self.__eval_check_item(
                parser.parse_validator(validator), resp_obj)

            try:
                self._do_validation(evaluated_validator)
            except exceptions.ValidationFailure as ex:
                validate_pass = False
                failures.append(str(ex))

            evaluated_validators.append(evaluated_validator)

        if not validate_pass:
            failures_string = "\n".join([failure for failure in failures])
            raise exceptions.ValidationFailure(failures_string)

        return evaluated_validators
Beispiel #2
0
    def _run_suite(self, test_suite):
        """ run tests in test_suite

        Args:
            test_suite: unittest.TestSuite()

        Returns:
            list: tests_results

        """
        tests_results = []

        for testcase in test_suite:
            testcase_name = testcase.config.get("name")
            logger.log_info("Start to run testcase: {}".format(testcase_name))

            result = self.unittest_runner.run(testcase)
            tests_results.append((testcase, result))

        return tests_results
    def extract_response(self, extractors):
        """ extract value from requests.Response and store in OrderedDict.
        @param (list) extractors
            [
                {"resp_status_code": "status_code"},
                {"resp_headers_content_type": "headers.content-type"},
                {"resp_content": "content"},
                {"resp_content_person_first_name": "content.person.name.first_name"}
            ]
        @return (OrderDict) variable binds ordered dict
        """
        if not extractors:
            return {}

        logger.log_info("start to extract from response object.")
        extracted_variables_mapping = OrderedDict()
        extract_binds_order_dict = utils.convert_mappinglist_to_orderdict(extractors)

        for key, field in extract_binds_order_dict.items():
            extracted_variables_mapping[key] = self.extract_field(field)

        return extracted_variables_mapping
def load_dot_env_file(dot_env_path):
    """ load .env file.

    Args:
        dot_env_path (str): .env file path

    Returns:
        dict: environment variables mapping

            {
                "UserName": "******",
                "Password": "******",
                "PROJECT_KEY": "ABCDEFGH"
            }

    Raises:
        exceptions.FileFormatError: If .env file format is invalid.

    """
    if not os.path.isfile(dot_env_path):
        raise exceptions.FileNotFound(".env file path is not exist.")

    logger.log_info(
        "Loading environment variables from {}".format(dot_env_path))
    env_variables_mapping = {}
    with io.open(dot_env_path, 'r', encoding='utf-8') as fp:
        for line in fp:
            # maxsplit=1
            if "=" in line:
                variable, value = line.split("=", 1)
            elif ":" in line:
                variable, value = line.split(":", 1)
            else:
                raise exceptions.FileFormatError(".env format error")

            env_variables_mapping[variable.strip()] = value.strip()

    utils.set_os_environ(env_variables_mapping)
    return env_variables_mapping
Beispiel #5
0
def render_html_report(summary,
                       html_report_name=None,
                       html_report_template=None):
    """ render html report with specified report name and template
        if html_report_name is not specified, use current datetime
        if html_report_template is not specified, use default report template
    """
    if not html_report_template:
        html_report_template = os.path.join(
            os.path.abspath(os.path.dirname(__file__)), "templates",
            "report_template.html")
        logger.log_debug("No html report template specified, use default.")
    else:
        logger.log_info("render with html report template: {}".format(
            html_report_template))

    logger.log_info("Start to render Html report ...")
    logger.log_debug("render data: {}".format(summary))

    report_dir_path = os.path.join(os.getcwd(), "reports")
    start_at_timestamp = int(summary["time"]["start_at"])
    summary["time"]["start_datetime"] = datetime.fromtimestamp(
        start_at_timestamp).strftime('%Y-%m-%d %H:%M:%S')
    if html_report_name:
        summary["html_report_name"] = html_report_name
        report_dir_path = os.path.join(report_dir_path, html_report_name)
        html_report_name += "-{}.html".format(start_at_timestamp)
    else:
        summary["html_report_name"] = ""
        html_report_name = "{}.html".format(start_at_timestamp)

    if not os.path.isdir(report_dir_path):
        os.makedirs(report_dir_path)

    for index, suite_summary in enumerate(summary["details"]):
        if not suite_summary.get("name"):
            suite_summary["name"] = "test suite {}".format(index)
        for record in suite_summary.get("records"):
            meta_data = record['meta_data']
            stringify_data(meta_data, 'request')
            stringify_data(meta_data, 'response')

    with io.open(html_report_template, "r", encoding='utf-8') as fp_r:
        template_content = fp_r.read()
        report_path = os.path.join(report_dir_path, html_report_name)
        with io.open(report_path, 'w', encoding='utf-8') as fp_w:
            rendered_content = Template(template_content,
                                        extensions=["jinja2.ext.loopcontrols"
                                                    ]).render(summary)
            fp_w.write(rendered_content)

    logger.log_info("Generated Html report: {}".format(report_path))

    return report_path
Beispiel #6
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.
        """
        def log_print(request_response):
            msg = "\n================== {} details ==================\n".format(
                request_response)
            for key, value in self.meta_data[request_response].items():
                msg += "{:<16} : {}\n".format(key, repr(value))
            logger.log_debug(msg)

        # record original request info
        self.meta_data["request"]["method"] = method
        self.meta_data["request"]["url"] = url
        self.meta_data["request"].update(kwargs)
        self.meta_data["request"]["start_timestamp"] = time.time()

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

        kwargs.setdefault("timeout", 120)
        response = self._send_request_safe_mode(method, url, **kwargs)

        # record the consumed time
        self.meta_data["response"]["response_time_ms"] = \
            round((time.time() - self.meta_data["request"]["start_timestamp"]) * 1000, 2)
        self.meta_data["response"][
            "elapsed_ms"] = response.elapsed.microseconds / 1000.0

        # record actual request info
        self.meta_data["request"]["url"] = (response.history
                                            and response.history[0]
                                            or response).request.url
        self.meta_data["request"]["headers"] = dict(response.request.headers)
        self.meta_data["request"]["body"] = response.request.body

        # log request details in debug mode
        log_print("request")

        # record response info
        self.meta_data["response"]["ok"] = response.ok
        self.meta_data["response"]["url"] = response.url
        self.meta_data["response"]["status_code"] = response.status_code
        self.meta_data["response"]["reason"] = response.reason
        self.meta_data["response"]["headers"] = dict(response.headers)
        self.meta_data["response"]["cookies"] = response.cookies or {}
        self.meta_data["response"]["encoding"] = response.encoding
        self.meta_data["response"]["content"] = response.content
        self.meta_data["response"]["text"] = response.text
        self.meta_data["response"]["content_type"] = response.headers.get(
            "Content-Type", "")

        try:
            self.meta_data["response"]["json"] = response.json()
        except ValueError:
            self.meta_data["response"]["json"] = None

        # 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):
            self.meta_data["response"]["content_size"] = int(
                self.meta_data["response"]["headers"].get("content-length")
                or 0)
        else:
            self.meta_data["response"]["content_size"] = len(response.content
                                                             or "")

        # log response details in debug mode
        log_print("response")

        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"""
                .format(self.meta_data["response"]["status_code"],
                        self.meta_data["response"]["response_time_ms"],
                        self.meta_data["response"]["content_size"]))

        return response
    def run_test(self, teststep_dict):
        """ run single teststep.

        Args:
            teststep_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"
                        },
                        "body": '{"name": "user", "password": "******"}'
                    },
                    "extract": [],              # optional
                    "validate": [],             # optional
                    "setup_hooks": [],          # optional
                    "teardown_hooks": []        # optional
                }

        Raises:
            exceptions.ParamsError
            exceptions.ValidationFailure
            exceptions.ExtractFailure

        """
        # check skip
        self._handle_skip_feature(teststep_dict)

        # prepare
        extractors = teststep_dict.get("extract", []) or teststep_dict.get(
            "extractors", [])
        validators = teststep_dict.get("validate", []) or teststep_dict.get(
            "validators", [])
        parsed_request = self.init_test(teststep_dict, level="teststep")
        self.context.update_teststep_variables_mapping("request",
                                                       parsed_request)

        # setup hooks
        setup_hooks = teststep_dict.get("setup_hooks", [])
        setup_hooks.insert(0, "${setup_hook_prepare_kwargs($request)}")
        self.do_hook_actions(setup_hooks)

        try:
            url = parsed_request.pop('url')
            method = parsed_request.pop('method')
            group_name = parsed_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=url))
        logger.log_debug(
            "request kwargs(raw): {kwargs}".format(kwargs=parsed_request))

        # request
        resp = self.http_client_session.request(method,
                                                url,
                                                name=group_name,
                                                **parsed_request)
        resp_obj = response.ResponseObject(resp)

        # teardown hooks
        teardown_hooks = teststep_dict.get("teardown_hooks", [])
        if teardown_hooks:
            logger.log_info("start to run teardown hooks")
            self.context.update_teststep_variables_mapping(
                "response", resp_obj)
            self.do_hook_actions(teardown_hooks)

        # extract
        extracted_variables_mapping = resp_obj.extract_response(extractors)
        self.context.update_testcase_runtime_variables_mapping(
            extracted_variables_mapping)

        # validate
        try:
            self.evaluated_validators = self.context.validate(
                validators, resp_obj)
        except (exceptions.ParamsError, exceptions.ValidationFailure,
                exceptions.ExtractFailure):
            # log request
            err_req_msg = "request: \n"
            err_req_msg += "headers: {}\n".format(
                parsed_request.pop("headers", {}))
            for k, v in parsed_request.items():
                err_req_msg += "{}: {}\n".format(k, repr(v))
            logger.log_error(err_req_msg)

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

            raise
Beispiel #8
0
    def render_html_report(self, **kwargs):
        """ render html report with specified report name and template
            if html_report_name is not specified, use current datetime
            if html_report_template is not specified, use default report template
        """
        # 获取所需参数 -------------------------------------------------------------------------------------------------
        summary = self.summary
        result_stastic= {
            "testsRun": summary["stat"]["testsRun"],
            "successes": summary["stat"]["successes"],
            "errors": summary["stat"]["errors"],
            "failures": summary["stat"]["failures"],
            "skipped": summary["stat"]["skipped"]}
        report_name = kwargs.get("report_name", None)
        file_name = kwargs.get("file_name", None)
        dir_name = kwargs.get("dir_name", None)
        html_report_template = kwargs.get("html_report_template", None)

        # 选择模板------------------------------------------------------------------------------------------------------
        if not html_report_template:
            html_report_template = os.path.join(
                os.path.abspath(os.path.dirname(__file__)),
                "templates",
                "report_template.html"
            )
            logger.log_debug("No html report template specified, use default.")
        else:
            logger.log_info("render with html report template: {}".format(html_report_template))

        logger.log_info("Start to render Html report ...")
        logger.log_debug("render data: {}".format(summary))

        # 在reports文件夹下建立dir_name文件夹,如果没有就创建 ----------------------------------------------------------
        report_dir_path = os.path.join(os.getcwd(), "reports")
        report_dir_path = os.path.join(report_dir_path, dir_name)
        if not os.path.isdir(report_dir_path):
            os.makedirs(report_dir_path)

        #  在dir_name文件夹下建立file_name报告 -------------------------------------------------------------------------
        start_at_timestamp = int(summary["time"]["start_at"])
        summary["time"]["start_datetime"] = datetime.fromtimestamp(start_at_timestamp).strftime('%Y-%m-%d %H_%M_%S')
        start_time = summary["time"]["start_datetime"]
        if file_name:
            summary["html_report_name"] = report_name
            file_name += "-{}.html".format(start_time)
        else:
            summary["html_report_name"] = ""
            file_name = "{}.html".format(start_time)

        # 准备报告的Details部分数据 ------------------------------------------------------------------------------------
        for index, suite_summary in enumerate(summary["details"]):
            if not suite_summary.get("name"):
                suite_summary["name"] = "test suite {}".format(index)
            for record in suite_summary.get("records"):
                meta_data = record['meta_data']
                report.stringify_data(meta_data, 'request')
                report.stringify_data(meta_data, 'response')

        # 模板渲染 -----------------------------------------------------------------------------------------------------
        with open(html_report_template, "r", encoding='utf-8') as fp_r:
            template_content = fp_r.read()
            # report_path = os.path.join(report_dir_path, file_name)  # 报告完整路径
            rendered_content = Template(template_content).render(summary)
            # with open(report_path, 'w', encoding='utf-8') as fp_w:
            #     rendered_content = Template(template_content).render(summary)  # 模板渲染
            #     fp_w.write(rendered_content)
            #     logger.log_info("Generated Html report: {}".format(report_path))
            return rendered_content, result_stastic