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
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
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 )
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
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