Example #1
0
    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_to_order_dict(extractors)

        for key, field in extract_binds_order_dict.items():
            if not isinstance(field, basestring):
                raise exception.ParamsError("invalid extractors in testcase!")
            result = self.extract_field(field)
            extracted_variables_mapping[key] = result
            if not (isinstance(result, bool)
                    or bool(result)):  # False 可以return
                err_msg = u"extract data with delimiter can be None!\n"
                err_msg += u"response: {}\n".format(self.parsed_dict())
                err_msg += u"regex: {}\n".format(field)
                logger.log_error(err_msg)
                raise exception.ParamsError(err_msg)
        return extracted_variables_mapping
Example #2
0
    def _get_bind_item(self, item_type, item_name):
        if item_type == "function":
            if item_name in self.functions:
                return self.functions[item_name]

            try:
                # check if builtin functions
                item_func = eval(item_name)
                if callable(item_func):
                    # is builtin function
                    return item_func
            except (NameError, TypeError):
                # is not builtin function, continue to search
                pass
        elif item_type == "variable":
            if item_name in self.variables:
                return self.variables[item_name]
        else:
            raise exception.ParamsError(
                "bind item should only be function or variable.")

        try:
            assert self.file_path is not None
            return utils.search_conf_item(self.file_path, item_type, item_name)
        except (AssertionError, exception.FunctionNotFound):
            raise exception.ParamsError(
                "{} is not defined in bind {}s!".format(item_name, item_type))
Example #3
0
    def _extract_field_with_delimiter(self, field):
        """ response content could be json or html text.
        @param (str) field should be string joined by delimiter.
        e.g.
            "status_code"
            "content"
            "headers.content-type"
            "content.person.name.first_name"
        """
        try:
            # string.split(sep=None, maxsplit=-1) -> list of strings
            # e.g. "content.person.name" => ["content", "person.name"]
            try:
                top_query, sub_query = field.split('.', 1)
            except ValueError:
                top_query = field
                sub_query = None

            if top_query in ["body", "content", "text"]:
                top_query_content = self.parsed_body()
            elif top_query == "cookies":
                cookies = self.resp_obj.cookies
                try:
                    return cookies[sub_query]
                except KeyError:
                    err_msg = u"Failed to extract attribute from cookies!\n"
                    err_msg += u"cookies: {}\n".format(cookies)
                    err_msg += u"attribute: {}".format(sub_query)
                    logger.log_error(err_msg)
                    raise exception.ParamsError(err_msg)
            else:
                try:
                    top_query_content = getattr(self.resp_obj, top_query)
                except AttributeError:
                    err_msg = u"Failed to extract attribute from response object: resp_obj.{}".format(
                        top_query)
                    logger.log_error(err_msg)
                    raise exception.ParamsError(err_msg)

            if sub_query:
                if not isinstance(top_query_content,
                                  (dict, CaseInsensitiveDict, list)):
                    err_msg = u"Failed to extract data with delimiter!\n"
                    err_msg += u"response: {}\n".format(self.parsed_dict())
                    err_msg += u"regex: {}\n".format(field)
                    logger.log_error(err_msg)
                    raise exception.ParamsError(err_msg)

                # e.g. key: resp_headers_content_type, sub_query = "content-type"
                return utils.query_json(top_query_content, sub_query)
            else:
                # e.g. key: resp_status_code, resp_content
                return top_query_content

        except AttributeError:
            err_msg = u"Failed to extract value from response!\n"
            err_msg += u"response: {}\n".format(self.parsed_dict())
            err_msg += u"extract field: {}\n".format(field)
            logger.log_error(err_msg)
            raise exception.ParamsError(err_msg)
Example #4
0
def parse_validator(validator):
    """ parse validator, validator maybe in two format
    @param (dict) validator
        format1: this is kept for compatiblity with the previous versions.
            {"check": "status_code", "comparator": "eq", "expect": 201}
            {"check": "$resp_body_success", "comparator": "eq", "expect": True}
        format2: recommended new version
            {'eq': ['status_code', 201]}
            {'eq': ['$resp_body_success', True]}
    @return (dict) validator info
        {
            "check": "status_code",
            "expect": 201,
            "comparator": "eq"
        }
    """
    if not isinstance(validator, dict):
        raise exception.ParamsError("invalid validator: {}".format(validator))

    if "check" in validator and len(validator) > 1:
        # format1
        check_item = validator.get("check")

        if "expect" in validator:
            expect_value = validator.get("expect")
        elif "expected" in validator:
            expect_value = validator.get("expected")
        else:
            raise exception.ParamsError(
                "invalid validator: {}".format(validator))

        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) != 2:
            raise exception.ParamsError(
                "invalid validator: {}".format(validator))

        check_item, expect_value = compare_values

    else:
        raise exception.ParamsError("invalid validator: {}".format(validator))

    return {
        "check": check_item,
        "expect": expect_value,
        "comparator": comparator
    }
Example #5
0
    def validate(self, validators, variables_mapping):
        """ Bind named validators to value within the context.
        @param (list) validators
            [
                {"check": "status_code", "comparator": "eq", "expect": 201},
                {"check": "resp_body_success", "comparator": "eq", "expect": True}
            ]
        @param (dict) variables_mapping
            {
                "resp_body_success": True
            }
        @return (list) content differences
            [
                {
                    "check": "status_code",
                    "comparator": "eq", "expect": 201, "value": 200
                }
            ]
        """
        for validator_dict in validators:

            check_item = validator_dict.get("check")
            if not check_item:
                raise exception.ParamsError(
                    "check item invalid: {}".format(check_item))

            if "expect" in validator_dict:
                expect_value = validator_dict.get("expect")
            elif "expected" in validator_dict:
                expect_value = validator_dict.get("expected")
            else:
                raise exception.ParamsError(
                    "expected value missed in testcase validator!")

            comparator = validator_dict.get("comparator", "eq")

            if check_item in variables_mapping:
                validator_dict["actual_value"] = variables_mapping[check_item]
            else:
                try:
                    validator_dict["actual_value"] = self.extract_field(
                        check_item)
                except exception.ParseResponseError:
                    raise exception.ParseResponseError(
                        "failed to extract check item in response!")

            utils.match_expected(validator_dict["actual_value"], expect_value,
                                 comparator, check_item)

        return True
Example #6
0
 def _extract_field_with_jsonpath(self, field):
     result = jsonpath.jsonpath(self.resp_body, field)
     if result:
         return result
     else:
         raise exception.ParamsError(
             "jsonpath {} get nothing".format(field))
Example #7
0
    def _parse_steps(self, step_content):
        """ parse steps from list or xls
        @params
            (list) steps: value in list
                step value may be in three types:
                    (1) data list
                    (2) call custom function
                e.g.
                    [
                        {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
                        {"username-password": "******"},
                        {"app_version": "${gen_app_version()}"}
                    ]
        @return steps in list
        """
        testcase_parser = self.context.testcase_parser

        steps_list = []

        if isinstance(step_content, list):
            # (1) data list
            return step_content
        else:
            steps_list = testcase_parser.eval_content_with_bindings(
                step_content)
            # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}]
            # e.g. [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}]
            if not isinstance(steps_list, list):
                raise exception.ParamsError("step syntax error!")

        return steps_list
Example #8
0
    def _get_block_by_name(ref_call, ref_type):
        """ get test content by reference name
        @params:
            ref_call: e.g. api_v1_Account_Login_POST($UserName, $Password)
            ref_type: "api" or "suite"
        """
        function_meta = parse_function(ref_call)
        func_name = function_meta["func_name"]
        call_args = function_meta["args"]
        block = TestcaseLoader._get_test_definition(func_name, ref_type)
        def_args = block.get("function_meta").get("args", [])

        if len(call_args) != len(def_args):
            raise exception.ParamsError("call args mismatch defined args!")

        args_mapping = {}
        for index, item in enumerate(def_args):
            if call_args[index] == item:
                continue

            args_mapping[item] = call_args[index]

        if args_mapping:
            block = substitute_variables_with_mapping(block, args_mapping)

        return block
Example #9
0
    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_to_order_dict(extractors)

        for key, field in extract_binds_order_dict.items():
            if not isinstance(field, basestring):
                raise exception.ParamsError("invalid extractors in testcase!")

            extracted_variables_mapping[key] = self.extract_field(field)

        return extracted_variables_mapping
Example #10
0
    def do_validation(self, validator_dict):
        """ validate with functions
        """
        comparator = utils.get_uniform_comparator(validator_dict["comparator"])
        validate_func = self.testcase_parser.get_bind_item(
            "function", comparator)

        if not validate_func:
            raise exception.FunctionNotFound(
                "comparator not found: {}".format(comparator))

        check_item = validator_dict["check"]
        check_value = validator_dict["check_value"]
        expect_value = validator_dict["expect"]

        if (check_value is None or expect_value is None) \
                and comparator not in ["is", "eq", "equals", "=="]:
            raise exception.ParamsError(
                "Null value can only be compared with comparator: eq/equals/=="
            )

        try:
            validate_func(validator_dict["check_value"],
                          validator_dict["expect"])
        except (AssertionError, TypeError):
            err_msg = "\n" + "\n".join([
                "\tcheck item name: %s;" % check_item,
                "\tcheck item value: %s (%s);" %
                (check_value, type(check_value).__name__),
                "\tcomparator: %s;" % comparator,
                "\texpected value: %s (%s)." %
                (expect_value, type(expect_value).__name__)
            ])
            raise exception.ValidationError(err_msg)
Example #11
0
    def get_bind_item(self, item_type, item_name):
        if item_type == "function":
            if item_name in self.functions:
                return self.functions[item_name]
        elif item_type == "variable":
            if item_name in self.variables:
                return self.variables[item_name]
        else:
            raise exception.ParamsError("bind item should only be function or variable.")

        try:
            assert self.file_path is not None
            return utils.search_conf_item(self.file_path, item_type, item_name)
        except (AssertionError, exception.FunctionNotFound):
            raise exception.ParamsError(
                "{} is not defined in bind {}s!".format(item_name, item_type))
Example #12
0
def load_test_dependencies():
    """ load all api and suite definitions.
        default api folder is "$CWD/tests/api/".
        default suite folder is "$CWD/tests/suite/".
    """
    test_def_overall_dict["loaded"] = True
    test_def_overall_dict["api"] = {}
    test_def_overall_dict["suite"] = {}

    # load api definitions
    api_def_folder = os.path.join(os.getcwd(), "tests", "api")
    api_files = utils.load_folder_files(api_def_folder)

    for test_file in api_files:
        testset = load_test_file(test_file)
        test_def_overall_dict["api"].update(testset["api"])

    # load suite definitions
    suite_def_folder = os.path.join(os.getcwd(), "tests", "suite")
    suite_files = utils.load_folder_files(suite_def_folder)

    for suite_file in suite_files:
        suite = load_test_file(suite_file)
        if "def" not in suite["config"]:
            raise exception.ParamsError(
                "def missed in suite file: {}!".format(suite_file))

        call_func = suite["config"]["def"]
        function_meta = parse_function(call_func)
        suite["function_meta"] = function_meta
        test_def_overall_dict["suite"][function_meta["func_name"]] = suite
Example #13
0
def parse_parameters(parameters, testset_path=None):
    """ parse parameters and generate cartesian product
    @params
        (list) parameters: parameter name and value in list
            parameter value may be in three types:
                (1) data list
                (2) call built-in parameterize function
                (3) call custom function in debugtalk.py
            e.g.
                [
                    {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
                    {"username-password": "******"},
                    {"app_version": "${gen_app_version()}"}
                ]
        (str) testset_path: testset file path, used for locating csv file and debugtalk.py
    @return cartesian product in list
    """
    testcase_parser = TestcaseParser(file_path=testset_path)

    parsed_parameters_list = []
    for parameter in parameters:
        parameter_name, parameter_content = list(parameter.items())[0]
        parameter_name_list = parameter_name.split("-")

        if isinstance(parameter_content, list):
            # (1) data list
            # e.g. {"app_version": ["2.8.5", "2.8.6"]}
            #       => [{"app_version": "2.8.5", "app_version": "2.8.6"}]
            # e.g. {"username-password": [["user1", "111111"], ["test2", "222222"]}
            #       => [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}]
            parameter_content_list = []
            for parameter_item in parameter_content:
                if not isinstance(parameter_item, (list, tuple)):
                    # "2.8.5" => ["2.8.5"]
                    parameter_item = [parameter_item]

                # ["app_version"], ["2.8.5"] => {"app_version": "2.8.5"}
                # ["username", "password"], ["user1", "111111"] => {"username": "******", "password": "******"}
                parameter_content_dict = dict(zip(parameter_name_list, parameter_item))

                parameter_content_list.append(parameter_content_dict)
        else:
            # (2) & (3)
            parsed_parameter_content = testcase_parser.eval_content_with_bindings(parameter_content)
            # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}]
            # e.g. [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}]
            if not isinstance(parsed_parameter_content, list):
                raise exception.ParamsError("parameters syntax error!")

            parameter_content_list = [
                # get subset by parameter name
                {key: parameter_item[key] for key in parameter_name_list}
                for parameter_item in parsed_parameter_content
            ]

        parsed_parameters_list.append(parameter_content_list)

    return gen_cartesian_product(*parsed_parameters_list)
Example #14
0
def override_variables_binds(variables, new_mapping):
    """ convert variables in testcase to ordered mapping, with new_mapping overrided
    """
    if isinstance(variables, list):
        variables_ordered_dict = convert_to_order_dict(variables)
    elif isinstance(variables, (OrderedDict, dict)):
        variables_ordered_dict = variables
    else:
        raise exception.ParamsError("variables error!")

    return update_ordered_dict(variables_ordered_dict, new_mapping)
Example #15
0
    def __getattr__(self, key):
        try:
            if key == "json":
                value = self.resp_obj.json()
            else:
                value = getattr(self.resp_obj, key)

            self.__dict__[key] = value
            return value
        except AttributeError:
            err_msg = "ResponseObject does not have attribute: {}".format(key)
            logger.log_error(err_msg)
            raise exception.ParamsError(err_msg)
Example #16
0
    def _extract_field_with_regex(self, field):
        """ extract field from response content with regex.
            requests.Response body could be json or html text.
        @param (str) field should only be regex string that matched r".*\(.*\).*"
        e.g.
            self.resp_text: "LB123abcRB789"
            field: "LB[\d]*(.*)RB[\d]*"
            return: abc
        """
        matched = re.search(field, self.resp_text)
        if not matched:
            err_msg = u"Failed to extract data with regex!\n"
            err_msg += u"response body: {}\n".format(self.resp_text)
            err_msg += u"regex: {}\n".format(field)
            logger.log_error(err_msg)
            raise exception.ParamsError(err_msg)

        return matched.group(1)
Example #17
0
    def load_test_dependencies():
        """ load all api and suite definitions.
            default api folder is "$CWD/tests/api/".
            default suite folder is "$CWD/tests/suite/".
        """
        # TODO: cache api and suite loading
        # load api definitions
        api_def_folder = os.path.join(os.getcwd(), "tests", "api")
        for test_file in FileUtils.load_folder_files(api_def_folder):
            TestcaseLoader.load_api_file(test_file)

        # load suite definitions
        suite_def_folder = os.path.join(os.getcwd(), "tests", "suite")
        for suite_file in FileUtils.load_folder_files(suite_def_folder):
            suite = TestcaseLoader.load_test_file(suite_file)
            if "def" not in suite["config"]:
                raise exception.ParamsError("def missed in suite file: {}!".format(suite_file))

            call_func = suite["config"]["def"]
            function_meta = parse_function(call_func)
            suite["function_meta"] = function_meta
            TestcaseLoader.overall_def_dict["suite"][function_meta["func_name"]] = suite
Example #18
0
def get_test_definition(name, ref_type):
    """ get expected api or suite.
    @params:
        name: api or suite name
        ref_type: "api" or "suite"
    @return
        expected api info if found, otherwise raise ApiNotFound exception
    """
    if not test_def_overall_dict.get("loaded", False):
        load_test_dependencies()

    test_info = test_def_overall_dict.get(ref_type, {}).get(name)
    if not test_info:
        err_msg = "{} {} not found!".format(ref_type, name)
        if ref_type == "api":
            raise exception.ApiNotFound(err_msg)
        elif ref_type == "suite":
            raise exception.SuiteNotFound(err_msg)
        else:
            raise exception.ParamsError("ref_type can only be api or suite!")

    return test_info
Example #19
0
    def _extract_field_with_delimiter(self, field):
        """ response content could be json or html text.
        @param (str) field should be string joined by delimiter.
        e.g.
            "status_code"
            "headers"
            "cookies"
            "content"
            "headers.content-type"
            "content.person.name.first_name"
        """
        try:
            # string.split(sep=None, maxsplit=-1) -> list of strings
            # e.g. "content.person.name" => ["content", "person.name"]
            try:
                top_query, sub_query = field.split('.', 1)
            except ValueError:
                top_query = field
                sub_query = None

            if top_query == "cookies":
                cookies = self.cookies
                try:
                    return cookies[sub_query]
                except KeyError:
                    err_msg = u"Failed to extract attribute from cookies!\n"
                    err_msg += u"cookies: {}\n".format(cookies)
                    err_msg += u"attribute: {}".format(sub_query)
                    logger.log_error(err_msg)
                    raise exception.ParamsError(err_msg)
            elif top_query == "elapsed":
                if sub_query in ["days", "seconds", "microseconds"]:
                    return getattr(self.elapsed, sub_query)
                elif sub_query == "total_seconds":
                    return self.elapsed.total_seconds()
                else:
                    err_msg = "{}: {} is not valid timedelta attribute.\n".format(
                        field, sub_query)
                    err_msg += "elapsed only support attributes: days, seconds, microseconds, total_seconds.\n"
                    logger.log_error(err_msg)
                    raise exception.ParamsError(err_msg)

            try:
                top_query_content = getattr(self, top_query)
            except AttributeError:
                err_msg = u"Failed to extract attribute from response object: resp_obj.{}".format(
                    top_query)
                logger.log_error(err_msg)
                raise exception.ParamsError(err_msg)

            if sub_query:
                if not isinstance(top_query_content,
                                  (dict, CaseInsensitiveDict, list)):
                    try:
                        # TODO: remove compatibility for content, text
                        if isinstance(top_query_content, bytes):
                            top_query_content = top_query_content.decode(
                                "utf-8")

                        if isinstance(top_query_content, PreparedRequest):
                            top_query_content = top_query_content.__dict__
                        else:
                            top_query_content = json.loads(top_query_content)
                    except json.decoder.JSONDecodeError:
                        err_msg = u"Failed to extract data with delimiter!\n"
                        err_msg += u"response content: {}\n".format(
                            self.content)
                        err_msg += u"regex: {}\n".format(field)
                        logger.log_error(err_msg)
                        raise exception.ParamsError(err_msg)

                # e.g. key: resp_headers_content_type, sub_query = "content-type"
                return utils.query_json(top_query_content, sub_query)
            else:
                # e.g. key: resp_status_code, resp_content
                return top_query_content

        except AttributeError:
            err_msg = u"Failed to extract value from response!\n"
            err_msg += u"response content: {}\n".format(self.content)
            err_msg += u"extract field: {}\n".format(field)
            logger.log_error(err_msg)
            raise exception.ParamsError(err_msg)
Example #20
0
    def run_test(self, testcase_dict):
        """ run single testcase.
        @param (dict) testcase_dict
            {
                "name": "testcase description",
                "skip": "skip this test unconditionally",
                "times": 3,
                "requires": [],         # optional, override
                "function_binds": {},   # optional, override
                "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": [],         # optional
                "teardown": []       # optional
            }
        @return True or raise exception during test
        """
        parsed_request = self.init_config(testcase_dict, level="testcase")

        try:
            url = parsed_request.pop('url')
            method = parsed_request.pop('method')
            group_name = parsed_request.pop("group", None)
        except KeyError:
            raise exception.ParamsError("URL or METHOD missed!")

        extractors = testcase_dict.get("extract", [])
        validators = testcase_dict.get("validate", [])
        setup_actions = testcase_dict.get("setup", [])
        teardown_actions = testcase_dict.get("teardown", [])

        self._handle_skip_feature(testcase_dict)

        def setup_teardown(actions):
            for action in actions:
                self.context.eval_content(action)

        setup_teardown(setup_actions)

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

        extracted_variables_mapping = resp_obj.extract_response(extractors)
        self.context.bind_extracted_variables(extracted_variables_mapping)

        try:
            self.context.validate(validators, resp_obj)
        except (exception.ParamsError, exception.ResponseError, exception.ValidationError):
            # 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, v)
            logger.log_error(err_req_msg)

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

            raise
        finally:
            setup_teardown(teardown_actions)
Example #21
0
    def run_test(self, testcase_dict):
        """ run single testcase.
        @param (dict) testcase_dict
            {
                "name": "testcase description",
                "skip": "skip this test unconditionally",
                "times": 3,
                "requires": [],         # optional, override
                "function_binds": {},   # optional, override
                "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": [
                    {"error_code": "errorcode"},
                    {"ID": "Data.AreaList.Area.ID"},
                ],              # optional
                "validate": [],             # optional
                "setup_hooks": [],          # optional
                "teardown_hooks": []        # optional
            }
        @return True or raise exception during test
        """
        # check skip
        self._handle_skip_feature(testcase_dict)

        # prepare
        parsed_request = self.init_config(testcase_dict, level="testcase")
        self.context.bind_testcase_variable("request", parsed_request)

        # setup hooks
        setup_hooks = testcase_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 exception.ParamsError("URL or METHOD missed!")

        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 = testcase_dict.get("teardown_hooks", [])
        if teardown_hooks:
            self.context.bind_testcase_variable("response", resp_obj)
            self.do_hook_actions(teardown_hooks)

        # extract
        extractors = testcase_dict.get("extract", []) or testcase_dict.get(
            "extractors", [])
        extracted_variables_mapping = resp_obj.extract_response(extractors)
        self.context.bind_extracted_variables(extracted_variables_mapping)

        # validate
        validators = testcase_dict.get("validate", []) or testcase_dict.get(
            "validators", [])
        try:
            self.context.validate(validators, resp_obj)
        except (exception.ParamsError, exception.ResponseError, \
            exception.ValidationError, exception.ParseResponseError):
            # 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, 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 += "content: {}\n".format(resp_obj.content)
            logger.log_error(err_resp_msg)

            raise
Example #22
0
    def _run_test(self, testcase_dict):
        """ run single testcase.
        @param (dict) testcase_dict
            {
                "name": "testcase description",
                "times": 3,
                "requires": [],         # optional, override
                "function_binds": {},   # optional, override
                "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": [],         # optional
                "teardown": []       # optional
            }
        @return True or raise exception during test
        """
        parsed_request = self.init_config(testcase_dict, level="testcase")

        try:
            url = parsed_request.pop('url')
            method = parsed_request.pop('method')
            group_name = parsed_request.pop("group", None)
        except KeyError:
            raise exception.ParamsError("URL or METHOD missed!")

        run_times = int(testcase_dict.get("times", 1))
        extractors = testcase_dict.get("extract") \
                     or testcase_dict.get("extractors") \
                     or testcase_dict.get("extract_binds", [])
        validators = testcase_dict.get("validate") \
                     or testcase_dict.get("validators", [])
        setup_actions = testcase_dict.get("setup", [])
        teardown_actions = testcase_dict.get("teardown", [])

        def setup_teardown(actions):
            for action in actions:
                self.context.exec_content_functions(action)

        for _ in range(run_times):
            setup_teardown(setup_actions)

            resp = self.http_client_session.request(method,
                                                    url,
                                                    name=group_name,
                                                    **parsed_request)

            resp_obj = response.ResponseObject(resp)

            extracted_variables_mapping = resp_obj.extract_response(extractors)
            self.context.bind_extracted_variables(extracted_variables_mapping)

            try:
                self.context.validate(validators, resp_obj)
            except (exception.ParamsError, exception.ResponseError,
                    exception.ValidationError):
                err_msg = u"Exception occured.\n"
                err_msg += u"HTTP request url: {}\n".format(url)
                err_msg += u"HTTP request kwargs: {}\n".format(parsed_request)
                err_msg += u"HTTP response status_code: {}\n".format(
                    resp.status_code)
                err_msg += u"HTTP response content: \n{}".format(resp.text)
                logging.error(err_msg)
                raise
            finally:
                setup_teardown(teardown_actions)

        return True
Example #23
0
    def load_test_file(file_path):
        """ load testcase file or suite file
        @param file_path: absolute valid file path
            file_path should be in format below:
                [
                    {
                        "config": {
                            "name": "",
                            "def": "suite_order()",
                            "request": {}
                        }
                    },
                    {
                        "test": {
                            "name": "add product to cart",
                            "api": "api_add_cart()",
                            "validate": []
                        }
                    },
                    {
                        "test": {
                            "name": "checkout cart",
                            "request": {},
                            "validate": []
                        }
                    }
                ]
        @return testset dict
            {
                "name": "desc1",
                "config": {},
                "testcases": [testcase11, testcase12]
            }
        """
        testset = {
            "name": "",
            "config": {
                "path": file_path
            },
            "testcases": []  # TODO: rename to tests
        }
        for item in FileUtils.load_file(file_path):
            if not isinstance(item, dict) or len(item) != 1:
                raise exception.FileFormatError(
                    "Testcase format error: {}".format(file_path))

            key, test_block = item.popitem()
            if not isinstance(test_block, dict):
                raise exception.FileFormatError(
                    "Testcase format error: {}".format(file_path))

            if key == "config":
                testset["config"].update(test_block)
                testset["name"] = test_block.get("name", "")
                #add by zhengchun, can define runner
                if "runner" in test_block:
                    # module_name, cls_name=item["config"]["runner"].split(".")[0:2]
                    s = test_block["runner"]
                    inx = s.rfind(".")
                    if (inx <= 0):
                        raise exception.ParamsError("runner format error[%s]" %
                                                    (s))
                    module_name = s[0:inx]
                    cls_name = s[inx + 1:]
                    imported_module = utils.get_imported_module(module_name)
                    ip_module_cls = getattr(imported_module, cls_name)
                    testset["runner"] = ip_module_cls
                else:
                    #defalut runner
                    imported_module = utils.get_imported_module("runner")
                    ip_module_cls = getattr(imported_module, "Runner")
                    testset["runner"] = ip_module_cls

            elif key == "test":
                if "api" in test_block:
                    ref_call = test_block["api"]
                    def_block = TestcaseLoader._get_block_by_name(
                        ref_call, "api")
                    TestcaseLoader._override_block(def_block, test_block)
                    testset["testcases"].append(test_block)
                elif "suite" in test_block:
                    ref_call = test_block["suite"]
                    block = TestcaseLoader._get_block_by_name(
                        ref_call, "suite")
                    testset["testcases"].extend(block["testcases"])
                else:
                    testset["testcases"].append(test_block)

            else:
                logger.log_warning(
                    "unexpected block key: {}. block key should only be 'config' or 'test'."
                    .format(key))

        return testset
Example #24
0
def match_expected(value, expected, comparator="eq", check_item=""):
    """ check if value matches expected value.
    @param value: actual value that get from response.
    @param expected: expected result described in testcase
    @param comparator: compare method
    @param check_item: check item name
    """
    try:
        if value is None or expected is None:
            assert comparator in ["is", "eq", "equals", "=="]
            assert value is None
            assert expected is None

        if comparator in ["eq", "equals", "=="]:
            assert value == expected
        elif comparator in ["lt", "less_than"]:
            assert value < expected
        elif comparator in ["le", "less_than_or_equals"]:
            assert value <= expected
        elif comparator in ["gt", "greater_than"]:
            assert value > expected
        elif comparator in ["ge", "greater_than_or_equals"]:
            assert value >= expected
        elif comparator in ["ne", "not_equals"]:
            assert value != expected
        elif comparator in ["str_eq", "string_equals"]:
            assert str(value) == str(expected)
        elif comparator in ["len_eq", "length_equals", "count_eq"]:
            assert isinstance(expected, int)
            assert len(value) == expected
        elif comparator in [
                "len_gt", "count_gt", "length_greater_than",
                "count_greater_than"
        ]:
            assert isinstance(expected, int)
            assert len(value) > expected
        elif comparator in ["len_ge", "count_ge", "length_greater_than_or_equals", \
            "count_greater_than_or_equals"]:
            assert isinstance(expected, int)
            assert len(value) >= expected
        elif comparator in [
                "len_lt", "count_lt", "length_less_than", "count_less_than"
        ]:
            assert isinstance(expected, int)
            assert len(value) < expected
        elif comparator in ["len_le", "count_le", "length_less_than_or_equals", \
            "count_less_than_or_equals"]:
            assert isinstance(expected, int)
            assert len(value) <= expected
        elif comparator in ["contains"]:
            assert isinstance(value, (list, tuple, dict, string_type))
            assert expected in value
        elif comparator in ["contained_by"]:
            assert isinstance(expected, (list, tuple, dict, string_type))
            assert value in expected
        elif comparator in ["type"]:
            assert isinstance(value, expected)
        elif comparator in ["regex"]:
            assert isinstance(expected, string_type)
            assert isinstance(value, string_type)
            assert re.match(expected, value)
        elif comparator in ["startswith"]:
            assert str(value).startswith(str(expected))
        elif comparator in ["endswith"]:
            assert str(value).endswith(str(expected))
        else:
            raise exception.ParamsError("comparator not supported!")

        return True

    except (AssertionError, TypeError):
        err_msg = "\n".join([
            "check item name: %s;" % check_item,
            "check item value: %s (%s);" % (value, type(value).__name__),
            "comparator: %s;" % comparator,
            "expected value: %s (%s)." % (expected, type(expected).__name__)
        ])
        raise exception.ValidationError(err_msg)
Example #25
0
    def parse_validator(self, validator, resp_obj):
        """ parse validator, validator maybe in two format
        @param (dict) validator
            format1: this is kept for compatiblity with the previous versions.
                {"check": "status_code", "comparator": "eq", "expect": 201}
                {"check": "$resp_body_success", "comparator": "eq", "expect": True}
            format2: recommended new version
                {'eq': ['status_code', 201]}
                {'eq': ['$resp_body_success', True]}
        @param (object) resp_obj
        @return (dict) validator info
            {
                "check_item": check_item,
                "check_value": check_value,
                "expect_value": expect_value,
                "comparator": comparator
            }
        """
        if not isinstance(validator, dict):
            raise exception.ParamsError(
                "invalid validator: {}".format(validator))

        if "check" in validator and len(validator) > 1:
            # format1
            check_item = validator.get("check")

            if "expect" in validator:
                expect_value = validator.get("expect")
            elif "expected" in validator:
                expect_value = validator.get("expected")
            else:
                raise exception.ParamsError(
                    "invalid validator: {}".format(validator))

            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) != 2:
                raise exception.ParamsError(
                    "invalid validator: {}".format(validator))

            check_item, expect_value = compare_values

        else:
            raise exception.ParamsError(
                "invalid validator: {}".format(validator))

        # check_item should only be in 3 type:
        # 1, variable reference, e.g. $token
        # 2, string joined by delimiter. e.g. "status_code", "headers.content-type"
        # 3, regex string, e.g. "LB[\d]*(.*)RB[\d]*"
        if testcase.extract_variables(check_item):
            # type 1
            check_value = self.testcase_parser.eval_content_variables(
                check_item)
        else:
            try:
                # type 2 or type 3
                check_value = resp_obj.extract_field(check_item)
            except exception.ParseResponseError:
                raise exception.ParseResponseError(
                    "failed to extract check item in response!")

        expect_value = self.testcase_parser.eval_content_variables(
            expect_value)

        validator_dict = {
            "check_item": check_item,
            "check_value": check_value,
            "expect_value": expect_value,
            "comparator": comparator
        }
        return validator_dict