def extract_field(self, field): """ extract value from requests.Response. """ msg = "extract field: {}".format(field) try: if text_extractor_regexp_compile.match(field): value = self._extract_field_with_regex(field) else: value = self._extract_field_with_delimiter(field) msg += "\t=> {}".format(value) logger.log_debug(msg) # TODO: unify ParseResponseError type except (exception.ParseResponseError, TypeError): logger.log_error("failed to extract field: {}".format(field)) raise return value
def render_html_report(summary, report_template=None, report_dir=None): """ render html report with specified report name and template Args: report_template (str): specify html report template path report_dir (str): specify html report save directory """ if not report_template: 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(report_template)) logger.log_info("Start to render Html report ...") report_dir = report_dir or os.path.join(os.getcwd(), "reports") if not os.path.isdir(report_dir): os.makedirs(report_dir) start_at_timestamp = int(summary["time"]["start_at"]) summary["time"]["start_datetime"] = datetime.fromtimestamp( start_at_timestamp).strftime('%Y-%m-%d %H:%M:%S') report_path = os.path.join(report_dir, "{}.html".format(start_at_timestamp)) with io.open(report_template, "r", encoding='utf-8') as fp_r: template_content = fp_r.read() 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
def print_output(output): if not output: return content = "\n================== Output ==================\n" content += '{:<16}: {:<}\n'.format("Variable", "Value") content += '{:<16}: {:<}\n'.format("--------", "-----") for variable, value in output.items(): if PYTHON_VERSION == 2: if isinstance(variable, unicode): variable = variable.encode("utf-8") if isinstance(value, unicode): value = value.encode("utf-8") content += '{:<16}: {:<}\n'.format(variable, value) content += "============================================\n" logger.log_debug(content)
def _send_request_safe_mode(self, method, url, **kwargs): """ Send a HTTP request, and catch any exception that might occur due to connection problems. 发送一个HTTP请求,并捕捉任何可能由于连接问题而发生的异常 Safe mode has been removed from requests 1.x. 安全模式已经从请求1.x中删除。 """ try: msg = "processed request:\n" msg += "> {method} {url}\n".format(method=method, url=url) msg += "> kwargs: {kwargs}".format(kwargs=kwargs) logger.log_debug(msg) return requests.Session.request(self, method, url, **kwargs) except (MissingSchema, InvalidSchema, InvalidURL): raise except RequestException as ex: resp = ApiResponse() resp.error = ex resp.status_code = 0 # with this status_code, content returns None使用这个status_code,内容将返回None resp.request = Request(method, url).prepare() return resp
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", "default_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.path.abspath('..') + r'/reports') start_datetime = summary["time"]["start_at"] 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_datetime) else: summary["html_report_name"] = "" if not os.path.isdir(report_dir_path): os.makedirs(report_dir_path) for record in summary.get("records"): meta_data = record['meta_data'] stringify_body(meta_data, 'request') stringify_body(meta_data, 'response') with io.open(html_report_template, "r", encoding='utf-8') as fp_r: template_content = fp_r.read() rendered_content = Template(template_content).render(summary) return rendered_content
def do_hook_actions(self, actions, hook_type): """ call hook actions. Args: actions (list): each action in actions list maybe in two format. format1 (dict): assignment, the value returned by hook function will be assigned to variable. {"var": "${func()}"} format2 (str): only call hook functions. ${func()} hook_type (enum): setup/teardown """ logger.log_debug("call {} hook actions.".format(hook_type)) for action in actions: if isinstance(action, dict) and len(action) == 1: # format 1 # {"var": "${func()}"} var_name, hook_content = list(action.items())[0] logger.log_debug("assignment with hook: {} = {}".format( var_name, hook_content)) self.session_context.update_test_variables( var_name, self.session_context.eval_content(hook_content)) else: # format 2 logger.log_debug("call hook function: {}".format(action)) # TODO: check hook function if valid self.session_context.eval_content(action)
def do_hook_actions(self, actions, hook_type): """ call hook actions. Args: actions (list): each action in actions list maybe in two format.动作列表中的每个动作可能有两种格式 format1 (dict): assignment, the value returned by hook function will be assigned to variable. 赋值,钩子函数返回的值将被赋给变量 {"var": "${func()}"} format2 (str): only call hook functions.只调用钩子函数 ${func()} hook_type (HookTypeEnum): setup/teardown """ logger.log_debug("call {} hook actions.".format(hook_type.name)) for action in actions: if isinstance(action, dict) and len(action) == 1: # format 1 # {"var": "${func()}"} var_name, hook_content = list(action.items())[0] hook_content_eval = self.session_context.eval_content( hook_content) logger.log_debug("assignment with hook: {} = {} => {}".format( var_name, hook_content, hook_content_eval)) self.session_context.update_test_variables( var_name, hook_content_eval) else: # format 2 logger.log_debug("call hook function: {}".format(action)) # TODO: check hook function if valid 检查钩函数是否有效 self.session_context.eval_content(action)
def load_dot_env_file(): """ load .env file, .env file should be located in project working directory. Returns: dict: environment variables mapping { "UserName": "******", "Password": "******", "PROJECT_KEY": "ABCDEFGH" } Raises: exceptions.FileFormatError: If env file format is invalid. """ path = os.path.join(project_working_directory, ".env") if not os.path.isfile(path): logger.log_debug( ".env file not exist in : {}".format(project_working_directory)) return {} logger.log_info("Loading environment variables from {}".format(path)) env_variables_mapping = {} with io.open(path, 'r', encoding='utf-8') as fp: for line in fp: if "=" in line: variable, value = line.split("=") elif ":" in line: variable, value = line.split(":") else: raise exceptions.FileFormatError(".env format error") env_variables_mapping[variable.strip()] = value.strip() project_mapping["env"] = env_variables_mapping utils.set_os_environ(env_variables_mapping) return env_variables_mapping
def render_html_report(self, 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 = self.default_report_template_path logger.log_debug("No html report template specified, use default.") else: logger.log_info("render with html report template: {}".format(html_report_template)) with open(html_report_template, "r") as fp: template_content = fp.read() summary = self.summary 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_datetime = summary["time"]["start_at"].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_datetime) else: summary["html_report_name"] = "" html_report_name = "{}.html".format(start_datetime) if not os.path.isdir(report_dir_path): os.makedirs(report_dir_path) report_path = os.path.join(report_dir_path, html_report_name) with io.open(report_path, 'w', encoding='utf-8') as fp: rendered_content = Template(template_content).render(summary) fp.write(rendered_content) logger.log_info("Generated Html report: {}".format(report_path)) return report_path
def extract_field(self, field): """ extract value from requests.Response. """ if not isinstance(field, basestring): err_msg = u"Invalid extractor! => {}\n".format(field) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) msg = "extract: {}".format(field) if text_extractor_regexp_compile.match(field): value = self._extract_field_with_regex(field) else: value = self._extract_field_with_delimiter(field) if is_py2 and isinstance(value, unicode): value = value.encode("utf-8") msg += "\t=> {}".format(value) logger.log_debug(msg) return value
def do_validation(self, validator_dict): """ validate with functions """ # TODO: move comparator uniform to init_test_suites comparator = utils.get_uniform_comparator(validator_dict["comparator"]) validate_func = self.testcase_parser.get_bind_function(comparator) if not validate_func: raise exceptions.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 exceptions.ParamsError( "Null value can only be compared with comparator: eq/equals/==" ) validate_msg = "validate: {} {} {}({})".format( check_item, comparator, expect_value, type(expect_value).__name__) try: validator_dict["check_result"] = "pass" validate_func(check_value, expect_value) validate_msg += "\t==> pass" logger.log_debug(validate_msg) except (AssertionError, TypeError): validate_msg += "\t==> fail" validate_msg += "\n{}({}) {} {}({})".format( check_value, type(check_value).__name__, comparator, expect_value, type(expect_value).__name__) logger.log_error(validate_msg) validator_dict["check_result"] = "fail" raise exceptions.ValidationFailure(validate_msg)
def load_test_dependency_map_by_path(path): """Get test_dependency_map by the specified path(Relative or Absolute path). Example: dict1,dict2 = load_test_dependency_map_by_path('../my_test') print('{},{}'.format(dict1,dict2)) >> {'nested_para': [''], 'nested_5': [''], 'nested_1': ['nested_3']}, {'nested':'/Users/..../xxx.yml'} @param path String. Relative or Absolute path is accepted. @return List. Two dict in the list. """ all_result = TestcaseLoader.load_testsets_by_path(path) logger.log_debug('{}'.format(all_result)) testcase_dep_dict = collections.defaultdict(list) testcase_path_dict = collections.defaultdict(list) for result in all_result: testcase_name = result['testcases'][0].get('name', '') raw_testcase_dep = result['testcases'][0].get('dependent', '') testcase_path = result['config'].get('path', '') if not testcase_dep_dict[testcase_name]: if not isinstance(raw_testcase_dep, (list, set)): testcase_list = raw_testcase_dep.split(',') testcase_dep_dict[testcase_name].extend(testcase_list) else: testcase_dep_dict[testcase_name].extend(raw_testcase_dep) else: logger.log_warning("Duplicate testcase found!! Please check-->"\ "testcase_name:{},testcase_path:{}".format(testcase_name,testcase_path)) # raise Exception("Duplicate testcase found!!") #根据需要决定是否抛出异常 testcase_path_dict[testcase_name] = testcase_path return testcase_dep_dict, testcase_path_dict
def print_output(outputs): if not outputs: return content = "\n================== Variables & Output ==================\n" content += '{:<6} | {:<16} : {:<}\n'.format("Type", "Variable", "Value") content += '{:<6} | {:<16} : {:<}\n'.format("-" * 6, "-" * 16, "-" * 27) def prepare_content(var_type, in_out): content = "" for variable, value in in_out.items(): if is_py2: if isinstance(variable, unicode): variable = variable.encode("utf-8") if isinstance(value, unicode): value = value.encode("utf-8") content += '{:<6} | {:<16} : {:<}\n'.format( var_type, variable, value) return content for output in outputs: _in = output["in"] _out = output["out"] if not _out: continue content += prepare_content("Var", _in) content += "\n" content += prepare_content("Out", _out) content += "-" * 56 + "\n" logger.log_debug(content)
def load_env_file(): ''' load .env file, .env file should be located in project working directory. Returns: dict: enviroment variables mapping { 'username':'******', 'password':'******', 'PROJECT_KEY':'ABCDEFGH' } Raises: exceptions.FileFormatError: If env file format is invalid. ''' path = os.path.join(project_working_directory, '.env') if not os.path.isfile(path): logger.log_debug( f'.env file not exist in: {project_working_directory}') return {} logger.log_info(f'Loading enviroment variables from {path}') env_variables_mapping = {} with open(path, 'r', encoding='utf-8') as fp: for line in fp: if '=' in line: variable, value = line.split('=') elif ':' in line: variable, value = line.split(':') else: raise exceptions.FileFormatError('.env format error') env_variables_mapping[variable.strip()] = value.strip() project_mapping['env'] = env_variables_mapping utils.set_os_environ(env_variables_mapping) return env_variables_mapping
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
def log_print(req_resp_dict, r_type): msg = "\n================== {} details ==================\n".format( r_type) for key, value in req_resp_dict[r_type].items(): msg += "{:<16} : {}\n".format(key, repr(value)) logger.log_debug(msg)
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. """ # record original request info self.meta_data["method"] = method self.meta_data["url"] = url self.meta_data["request_time"] = 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_time_ms"] = round( (time.time() - self.meta_data["request_time"]) * 1000, 2) self.meta_data["elapsed_ms"] = response.elapsed.microseconds / 1000.0 # record actual request info self.meta_data["url"] = (response.history and response.history[0] or response).request.url self.meta_data["request_headers"] = response.request.headers self.meta_data["request_body"] = response.request.body # record response info self.meta_data["status_code"] = response.status_code self.meta_data["response_headers"] = response.headers try: self.meta_data["response_body"] = response.json() except ValueError: self.meta_data["response_body"] = response.content # log response details in debug mode msg = "response details:\n" msg += "> status_code: {}\n".format(self.meta_data["status_code"]) msg += "> headers: {}\n".format(self.meta_data["response_headers"]) msg += "> body: {}".format(self.meta_data["response_body"]) logger.log_debug(msg) # 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["content_size"] = int( self.meta_data["response_headers"].get("content-length") or 0) else: self.meta_data["content_size"] = len(response.content or "") 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["status_code"], self.meta_data["response_time_ms"], self.meta_data["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前面加上base_url,除非它已经是一个绝对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) 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.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) def log_req_resp_details(): 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) # 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): #Extract Failure提取失败 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 do_hook_actions(self, actions): for action in actions: logger.log_debug("call hook: {}".format(action)) # TODO: check hook function if valid self.context.eval_content(action)
def dependent_runner(self, Origion_dic): # if self.login_flag: # for item in Origion_dic: # if item.get("test",""): # if item.get("test","").get("name","") in self.dependent_root: # logger.log_debug('Inside dependent_runner login_flag-------------````````````````````````````') # logger.log_debug('test name:{}'.format(item.get("test","").get("name",""))) # logger.log_debug('global_variables: {}'.format(self.global_variables)) # logger.log_debug('http_client_session: {}'.format(self.http_client_session)) # return {} dependent_variables = defaultdict(list) dependent_list = [] logger.log_debug('Origion_dic: {}'.format(Origion_dic)) for item in Origion_dic: if item.get("test", ""): testcase_name = item.get("test", "").get("name", "") idempotency = item.get("test", "").pop("idempotency", "") session = item.get("test", "").pop("session", "") if session: if not self.http_client_session_dict.get( testcase_name, ""): self.http_client_session_dict[ testcase_name] = self.http_client_session_dict[ "default"] self.http_client_session_dict[ "default"] = self.http_client_session_dict[ testcase_name] if check_case_result(testcase_name): item.get("test", "")["skip"] = "case fail" else: last_result = get_case_result(testcase_name) if isinstance(last_result, dict): return last_result raw_testcase_dep = item.get("test", "").pop("dependent", "") if raw_testcase_dep and not isinstance(raw_testcase_dep, (list, set)): testcase_list = raw_testcase_dep.split(',') dependent_list.extend(testcase_list) else: dependent_list.extend(raw_testcase_dep) for dependent_testcase_name in dependent_list: for key, value in self.dependent_runner( self.get_dependent_testcase( dependent_testcase_name)).items(): if key.startswith("global_"): self.global_variables[key] = value #所有全testsuit的参数使用k-v? else: if dependent_variables.get(key, ""): if not isinstance(dependent_variables.get(key, ""), list): temp = dependent_variables[key] dependent_variables[key] = [temp] dependent_variables[key].append(value) else: dependent_variables[key] = value for key, value in self.global_variables.items(): if not dependent_variables.get(key, ""): dependent_variables[key] = value for item in Origion_dic: if item.get("test", ""): if any([ check_case_result(dependent_case) for dependent_case in dependent_list ]): item.get("test", "")["skip"] = "dependent case fail" if item.get("config", ""): for variables in (item.get("config", "").pop("variables", "")): for key, value in variables.items(): dependent_variables[key] = value logger.log_debug('dependent_variabls------:') logger.log_debug('{}'.format(dependent_variables)) dic = dependency_parser.load_test_file(Origion_dic) logger.log_debug('dic-----:') logger.log_debug('{}'.format(dic)) testcase = YDHTestSuite( dic, http_client_session=self.http_client_session_dict["default"], variables_mapping=dependent_variables) self.runner.run(testcase) logger.log_debug('testcase output------:') logger.log_debug('{}'.format(testcase.output)) if idempotency: set_case_result(testcase_name, testcase.output[0].get('out', "")) return testcase.output[0].get('out', "")
def do_hook_actions(self, actions): for action in actions: logger.log_debug("call hook: {}".format(action)) self.context.eval_content(action)
def set_os_environ(variables_mapping): """ set variables mapping to os.environ """ for variable in variables_mapping: os.environ[variable] = variables_mapping[variable] logger.log_debug("Set OS environment variable: {}".format(variable))
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)
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. """ # store detail data of request and response self.meta_data = {} # prepend url with hostname unless it's already an absolute URL url = self._build_url(url) logger.log_info("{method} {url}".format(method=method, url=url)) logger.log_debug("request kwargs(raw): {kwargs}".format(kwargs=kwargs)) # set up pre_request hook for attaching meta data to the request object self.meta_data["method"] = method if "httpntlmauth" in kwargs: from requests_ntlm import HttpNtlmAuth auth_account = kwargs.pop("httpntlmauth") kwargs["auth"] = HttpNtlmAuth(auth_account["username"], auth_account["password"]) kwargs.setdefault("timeout", 120) self.meta_data["request_time"] = time.time() response = self._send_request_safe_mode(method, url, **kwargs) # record the consumed time self.meta_data["response_time"] = int( (time.time() - self.meta_data["request_time"]) * 1000) self.meta_data["elapsed"] = response.elapsed.total_seconds() self.meta_data["url"] = (response.history and response.history[0] or response) \ .request.path_url self.meta_data["request_headers"] = response.request.headers self.meta_data["request_body"] = response.request.body self.meta_data["status_code"] = response.status_code self.meta_data["response_headers"] = response.headers self.meta_data["response_body"] = response.text logger.log_debug("response status_code: {}".format( self.meta_data["status_code"])) logger.log_debug("response headers: {}".format( self.meta_data["response_headers"])) logger.log_debug("response body: {}".format( self.meta_data["response_body"])) # 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["content_size"] = int( self.meta_data["response_headers"].get("content-length") or 0) else: self.meta_data["content_size"] = len(response.content or "") 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, response_length: {} bytes""" .format(self.meta_data["status_code"], self.meta_data["response_time"], self.meta_data["content_size"])) return response
def _run_dubbo_test(self, test_dict): # 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) # 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 host = parsed_test_request.pop('host', '') port = parsed_test_request.pop('port', '') # setup hooks setup_hooks = test_dict.get("setup_hooks", []) if setup_hooks: self.do_hook_actions(setup_hooks, HookTypeEnum.SETUP) try: service = parsed_test_request.pop('service') method = parsed_test_request.pop('method') params = parsed_test_request.pop("params", "") parsed_test_request.setdefault("verify", self.verify) group_name = parsed_test_request.pop("group", None) except KeyError: raise exceptions.ParamsError("SERVICE or METHOD missed!") logger.log_info("{host}:{port} {service}.{method}".format( host=host, port=port, service=service, method=method)) logger.log_debug( "request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request)) # request resp = self.dubbo_client_session.request( host, port, service, method, params, name=(group_name or self.session_context.eval_content( test_dict.get("name", ""))), **parsed_test_request) # TODO: responseObject resp_obj = DubboResponseObject(resp) def log_req_resp_details(): err_msg = "{} DETAILED REQUEST & RESPONSE {}\n".format( "*" * 32, "*" * 32) # log request err_msg += "====== request details ======\n" err_msg += "host: {}\n".format(host) err_msg += "port: {}\n".format(port) err_msg += "service: {}\n".format(service) err_msg += "method: {}\n".format(method) err_msg += "params: {}\n".format(params) 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 += "body: {}\n".format(repr(resp_obj.json)) logger.log_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.dubbo_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 return validator.validation_results
def _run_test_once(self, test_dict, wait_validators=None): # 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 = 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, "setup") try: url = parsed_test_request.pop('url') 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=url)) logger.log_debug( "request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request)) # request resp = self.http_client_session.request(method, 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") # extract extractors = test_dict.get("extract", {}) extracted_variables_mapping = resp_obj.extract_response(extractors) self.session_context.update_session_variables( extracted_variables_mapping) # validate if wait_validators: wait_validate_pass, wait_failures = self.session_context.validate( wait_validators, resp_obj) if not wait_validate_pass: # err_msg = "{} DETAILED REQUEST & RESPONSE {}\n".format("*" * 32, "*" * 32) # # # log request # err_msg += "====== request details ======\n" # err_msg += "url: {}\n".format(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("During the wait: \n" + err_msg) logger.log_error( "====== During the wait: the expect conditions can not Meet the requirements! ======" ) failures_string = "\n".join( [failure for failure in wait_failures]) self.validation_results = self.session_context.validation_results raise exceptions.ValidationFailure(failures_string) validators = test_dict.get("validate", []) validate_pass, failures = self.session_context.validate( validators, resp_obj) self.validation_results = self.session_context.validation_results # try: # validate_pass, failures = self.session_context.validate(validators, resp_obj) # 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(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: # self.validation_results = self.session_context.validation_results return validate_pass, failures
def run_test(self, testcase_dict): """ run single testcase. @param (dict) testcase_dict { "name": "testcase 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 } @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 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 = 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,context=self.context) 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 (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
def validate(self, validators): """ make validation with comparators """ self.validation_results = {} if not validators: return logger.log_debug("start to validate.") validate_pass = True failures = [] for validator in validators: if isinstance(validator, dict) and validator.get("type") == "python_script": script = self.session_context.eval_content(validator["script"]) validator_dict, ex = self.validate_script(script) if ex: validate_pass = False failures.append(ex) self.validation_results["validate_script"] = validator_dict continue if "validate_extractor" not in self.validation_results: self.validation_results["validate_extractor"] = [] # validator should be LazyFunction object if not isinstance(validator, parser.LazyFunction): raise exceptions.ValidationFailure( "validator should be parsed first: {}".format(validators)) # evaluate validator args with context variable mapping. validator_args = validator.get_args() check_item, expect_item = validator_args check_value = self.__eval_validator_check(check_item) expect_value = self.__eval_validator_expect(expect_item) validator.update_args([check_value, expect_value]) comparator = validator.func_name validator_dict = { "comparator": comparator, "check": check_item, "check_value": check_value, "expect": expect_item, "expect_value": expect_value } validate_msg = "\nvalidate: {} {} {}({})".format( check_item, comparator, expect_value, type(expect_value).__name__ ) try: validator.to_value(self.session_context.test_variables_mapping) validator_dict["check_result"] = "pass" validate_msg += "\t==> pass" logger.log_debug(validate_msg) except (AssertionError, TypeError): validate_pass = False validator_dict["check_result"] = "fail" validate_msg += "\t==> fail" validate_msg += "\n{}({}) {} {}({})".format( check_value, type(check_value).__name__, comparator, expect_value, type(expect_value).__name__ ) logger.log_error(validate_msg) failures.append(validate_msg) self.validation_results["validate_extractor"].append(validator_dict) # restore validator args, in test_case of running multiple times validator.update_args(validator_args) if not validate_pass: failures_string = "\n".join([failure for failure in failures]) raise exceptions.ValidationFailure(failures_string)
def unset_os_environ(variables_mapping): """ set variables mapping to os.environ """ for variable in variables_mapping: os.environ.pop(variable) logger.log_debug("Unset OS environment variable: {}".format(variable))
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 = 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, "setup") try: url = parsed_test_request.pop('url') 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=url)) logger.log_debug( "request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request)) # request resp = self.http_client_session.request(method, 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") # 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", []) try: self.session_context.validate(validators, resp_obj) 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(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: self.validation_results = self.session_context.validation_results