From 6ae173d637fafc9f3131d89d6bd44f273c3368ed Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 24 Jul 2018 18:04:10 +0800 Subject: [PATCH 01/22] validate all validators even if failed --- httprunner/__about__.py | 2 +- httprunner/context.py | 66 ++++++++++++------- httprunner/runner.py | 4 +- httprunner/task.py | 2 +- .../templates/default_report_template.html | 4 +- tests/test_context.py | 12 ++-- 6 files changed, 51 insertions(+), 39 deletions(-) diff --git a/httprunner/__about__.py b/httprunner/__about__.py index 9fc5c801..0cde284c 100644 --- a/httprunner/__about__.py +++ b/httprunner/__about__.py @@ -1,7 +1,7 @@ __title__ = 'HttpRunner' __description__ = 'One-stop solution for HTTP(S) testing.' __url__ = 'https://github.com/HttpRunner/HttpRunner' -__version__ = '1.5.6' +__version__ = '1.5.7' __author__ = 'debugtalk' __author_email__ = 'mail@debugtalk.com' __license__ = 'MIT' diff --git a/httprunner/context.py b/httprunner/context.py index 9decb7ca..1739e741 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -5,7 +5,7 @@ import os import re import sys -from httprunner import exception, testcase, utils +from httprunner import exception, logger, testcase, utils from httprunner.compat import OrderedDict @@ -237,32 +237,50 @@ class Context(object): and comparator not in ["is", "eq", "equals", "=="]: raise exception.ParamsError("Null value can only be compared with comparator: eq/equals/==") - try: - validator_dict["check_result"] = "passed" - 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__) - ]) - validator_dict["check_result"] = "failed" - raise exception.ValidationError(err_msg) + validate_msg = "validate: {} {} {}({})".format( + check_item, + comparator, + expect_value, + type(expect_value).__name__ + ) - def eval_validators(self, validators, resp_obj): - """ evaluate validators with context variable mapping. + 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 exception.ValidationError(validate_msg) + + def validate(self, validators, resp_obj): + """ make validations """ - return [ - self.eval_check_item( + self.evaluated_validators = [] + validate_pass = True + + for validator in validators: + # evaluate validators with context variable mapping. + evaluated_validator = self.eval_check_item( testcase.parse_validator(validator), resp_obj ) - for validator in validators - ] - def validate(self, validators): - """ make validations - """ - for validator_dict in validators: - self.do_validation(validator_dict) + try: + self.do_validation(evaluated_validator) + except exception.ValidationError: + validate_pass = False + + self.evaluated_validators.append(evaluated_validator) + + if not validate_pass: + raise exception.ValidationError diff --git a/httprunner/runner.py b/httprunner/runner.py index fbf8031f..72dd7a4d 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -11,7 +11,6 @@ class Runner(object): def __init__(self, config_dict=None, http_client_session=None): self.http_client_session = http_client_session - self.evaluated_validators = [] self.context = Context() config_dict = config_dict or {} @@ -183,8 +182,7 @@ class Runner(object): # validate validators = testcase_dict.get("validate", []) or testcase_dict.get("validators", []) try: - self.evaluated_validators = self.context.eval_validators(validators, resp_obj) - self.context.validate(self.evaluated_validators) + self.context.validate(validators, resp_obj) except (exception.ParamsError, exception.ResponseError, \ exception.ValidationError, exception.ParseResponseError): # log request diff --git a/httprunner/task.py b/httprunner/task.py index c65a7809..ca03c406 100644 --- a/httprunner/task.py +++ b/httprunner/task.py @@ -28,7 +28,7 @@ class TestCase(unittest.TestCase): finally: if hasattr(self.test_runner.http_client_session, "meta_data"): self.meta_data = self.test_runner.http_client_session.meta_data - self.meta_data["validators"] = self.test_runner.evaluated_validators + self.meta_data["validators"] = self.test_runner.context.evaluated_validators self.test_runner.http_client_session.init_meta_data() diff --git a/httprunner/templates/default_report_template.html b/httprunner/templates/default_report_template.html index 4c7b4441..12cc8d94 100644 --- a/httprunner/templates/default_report_template.html +++ b/httprunner/templates/default_report_template.html @@ -307,9 +307,9 @@ {% for validator in record.meta_data.validators %} - {% if validator.check_result == "passed" %} + {% if validator.check_result == "pass" %} - {% elif validator.check_result == "failed" %} + {% elif validator.check_result == "fail" %} {% elif validator.check_result == "unchecked" %} diff --git a/tests/test_context.py b/tests/test_context.py index 746c79e3..585a45cf 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -271,8 +271,7 @@ class VariableBindsUnittest(ApiServerUnittest): self.context.bind_variables(variables) with self.assertRaises(exception.ValidationError): - evaluated_validators = self.context.eval_validators(validators, resp_obj) - self.context.validate(evaluated_validators) + self.context.validate(validators, resp_obj) validators = [ {"eq": ["$resp_status_code", 201]}, @@ -291,8 +290,7 @@ class VariableBindsUnittest(ApiServerUnittest): } self.context.bind_functions(functions) - evaluated_validators = self.context.eval_validators(validators, resp_obj) - self.context.validate(evaluated_validators) + self.context.validate(validators, resp_obj) def test_validate_exception(self): url = "http://127.0.0.1:5000/" @@ -308,8 +306,7 @@ class VariableBindsUnittest(ApiServerUnittest): self.context.bind_variables(variables) with self.assertRaises(exception.ParamsError): - evaluated_validators = self.context.eval_validators(validators, resp_obj) - self.context.validate(evaluated_validators) + self.context.validate(validators, resp_obj) # expected value missed in variables mapping variables = [ @@ -318,5 +315,4 @@ class VariableBindsUnittest(ApiServerUnittest): self.context.bind_variables(variables) with self.assertRaises(exception.ValidationError): - evaluated_validators = self.context.eval_validators(validators, resp_obj) - self.context.validate(evaluated_validators) + self.context.validate(validators, resp_obj) From 9b38ad949e066aaa99e46ed73a2690ea1d812db5 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 24 Jul 2018 18:18:42 +0800 Subject: [PATCH 02/22] update extract log --- httprunner/response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index 9a2a3b89..ead684f5 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -127,14 +127,14 @@ class ResponseObject(object): 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) + err_msg += u"extract: {}\n".format(field) logger.log_error(err_msg) raise exception.ParamsError(err_msg) def extract_field(self, field): """ extract value from requests.Response. """ - msg = "extract field: {}".format(field) + msg = "extract: {}".format(field) try: if text_extractor_regexp_compile.match(field): From 7a2920fac2ef58059190590296e459db1553380b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 24 Jul 2018 20:22:26 +0800 Subject: [PATCH 03/22] bugfix: display default test suite name if not specified --- httprunner/report.py | 4 +- .../templates/default_report_template.html | 300 +++++++++--------- 2 files changed, 153 insertions(+), 151 deletions(-) diff --git a/httprunner/report.py b/httprunner/report.py index 7cb6b172..73381570 100644 --- a/httprunner/report.py +++ b/httprunner/report.py @@ -90,7 +90,9 @@ def render_html_report(summary, html_report_name=None, html_report_template=None if not os.path.isdir(report_dir_path): os.makedirs(report_dir_path) - for suite_summary in summary["details"]: + for index, suite_summary in enumerate(summary["details"]): + if not suite_summary.get("name"): + suite_summary["name"] = "test suite {}".format(index) for record in suite_summary.get("records"): meta_data = record['meta_data'] stringify_data(meta_data, 'request') diff --git a/httprunner/templates/default_report_template.html b/httprunner/templates/default_report_template.html index 12cc8d94..78d36986 100644 --- a/httprunner/templates/default_report_template.html +++ b/httprunner/templates/default_report_template.html @@ -187,69 +187,91 @@ {% for test_suite_summary in details %} {% set suite_index = loop.index %}

{{test_suite_summary.name}}

- - - - - - - - - - - - - - - - - - - - {% for record in test_suite_summary.records %} - {% set record_index = "{}_{}".format(suite_index, loop.index) %} - - - - + + {% endfor %} +
base_url{{test_suite_summary.base_url}} - parameters & output -
- -
- -
TOTAL: {{test_suite_summary.stat.testsRun}}SUCCESS: {{test_suite_summary.stat.successes}}FAILED: {{test_suite_summary.stat.failures}}ERROR: {{test_suite_summary.stat.errors}}SKIPPED: {{test_suite_summary.stat.skipped}}
StatusNameResponse TimeDetail
{{record.status}} - {{record.name}}{{ record.meta_data.response.response_time_ms }} ms - - log -
{% endfor %} \ No newline at end of file From 7478331cb545d4102d60b40388a3f222c5234797 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 10:40:20 +0800 Subject: [PATCH 04/22] group exceptions to 2 types: failure and error --- httprunner/compat.py | 11 +++++++---- httprunner/context.py | 10 +++++----- httprunner/exception.py | 41 ++++++++++++++++++++++------------------ httprunner/response.py | 6 +++--- httprunner/runner.py | 4 ++-- httprunner/utils.py | 10 +++++----- tests/test_context.py | 4 ++-- tests/test_httprunner.py | 4 ++-- tests/test_response.py | 6 +++--- tests/test_runner.py | 2 +- tests/test_testcase.py | 4 +--- tests/test_utils.py | 12 ++++++------ 12 files changed, 60 insertions(+), 54 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index 3753affc..607c8b3d 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -8,6 +8,7 @@ This module handles import compatibility issues between Python 2 and Python 3. """ +import json import sys # ------- @@ -23,10 +24,6 @@ is_py2 = (_ver[0] == 2) #: Python 3.x? is_py3 = (_ver[0] == 3) -try: - import simplejson as json -except ImportError: - import json # --------- # Specifics @@ -42,6 +39,9 @@ if is_py2: numeric_types = (int, long, float) integer_types = (int, long) + FileNotFoundError = IOError + JSONDecodeError = json.decoder.JSONDecodeError + elif is_py3: from collections import OrderedDict @@ -51,3 +51,6 @@ elif is_py3: basestring = (str, bytes) numeric_types = (int, float) integer_types = (int,) + + FileNotFoundError = FileNotFoundError + JSONDecodeError = ValueError diff --git a/httprunner/context.py b/httprunner/context.py index 1739e741..93e3176e 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -204,10 +204,10 @@ class Context(object): try: # format 4/5 check_value = resp_obj.extract_field(check_item) - except exception.ParseResponseError: + except exception.ParseResponseFailure: msg = "failed to extract check item from response!\n" msg += "response content: {}".format(resp_obj.content) - raise exception.ParseResponseError(msg) + raise exception.ParseResponseFailure(msg) validator["check_value"] = check_value @@ -260,7 +260,7 @@ class Context(object): ) logger.log_error(validate_msg) validator_dict["check_result"] = "fail" - raise exception.ValidationError(validate_msg) + raise exception.ValidationFailure(validate_msg) def validate(self, validators, resp_obj): """ make validations @@ -277,10 +277,10 @@ class Context(object): try: self.do_validation(evaluated_validator) - except exception.ValidationError: + except exception.ValidationFailure: validate_pass = False self.evaluated_validators.append(evaluated_validator) if not validate_pass: - raise exception.ValidationError + raise exception.ValidationFailure diff --git a/httprunner/exception.py b/httprunner/exception.py index 4bcef4d8..0e717f89 100644 --- a/httprunner/exception.py +++ b/httprunner/exception.py @@ -1,16 +1,27 @@ # encoding: utf-8 -import json +from httprunner.compat import JSONDecodeError, FileNotFoundError -try: - FileNotFoundError = FileNotFoundError -except NameError: - FileNotFoundError = IOError +""" failure type exceptions + these exceptions will mark test as failure +""" -try: - JSONDecodeError = json.decoder.JSONDecodeError -except AttributeError: - JSONDecodeError = ValueError +class MyBaseFailure(BaseException): + pass + +class ValidationFailure(MyBaseFailure): + pass + +class ResponseFailure(MyBaseFailure): + pass + +class ParseResponseFailure(MyBaseFailure): + pass + + +""" error type exceptions + these exceptions will mark test as error +""" class MyBaseError(BaseException): pass @@ -21,18 +32,12 @@ class FileFormatError(MyBaseError): class ParamsError(MyBaseError): pass -class ResponseError(MyBaseError): - pass - -class ParseResponseError(MyBaseError): - pass - -class ValidationError(MyBaseError): - pass - class NotFoundError(MyBaseError): pass +class FileNotFound(FileNotFoundError, NotFoundError): + pass + class FunctionNotFound(NotFoundError): pass diff --git a/httprunner/response.py b/httprunner/response.py index ead684f5..e9f3e7b4 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -111,7 +111,7 @@ class ResponseObject(object): top_query_content = top_query_content.__dict__ else: top_query_content = json.loads(top_query_content) - except json.decoder.JSONDecodeError: + except exception.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) @@ -145,8 +145,8 @@ class ResponseObject(object): msg += "\t=> {}".format(value) logger.log_debug(msg) - # TODO: unify ParseResponseError type - except (exception.ParseResponseError, TypeError): + # TODO: unify ParseResponseFailure type + except (exception.ParseResponseFailure, TypeError): logger.log_error("failed to extract field: {}".format(field)) raise diff --git a/httprunner/runner.py b/httprunner/runner.py index 72dd7a4d..820b2a97 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -183,8 +183,8 @@ class Runner(object): 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): + except (exception.ParamsError, exception.ResponseFailure, \ + exception.ValidationFailure, exception.ParseResponseFailure): # log request err_req_msg = "request: \n" err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {})) diff --git a/httprunner/utils.py b/httprunner/utils.py index 3a508d86..c4401f37 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -117,7 +117,7 @@ class FileUtils(object): @staticmethod def load_file(file_path): if not os.path.isfile(file_path): - raise exception.FileNotFoundError("{} does not exist.".format(file_path)) + raise exception.FileNotFound("{} does not exist.".format(file_path)) file_suffix = os.path.splitext(file_path)[1].lower() if file_suffix == '.json': @@ -190,7 +190,7 @@ def query_json(json_content, query, delimiter='.'): @return queried result """ if json_content == "": - raise exception.ResponseError("response content is empty!") + raise exception.ResponseFailure("response content is empty!") try: for key in query.split(delimiter): @@ -199,10 +199,10 @@ def query_json(json_content, query, delimiter='.'): elif isinstance(json_content, (dict, CaseInsensitiveDict)): json_content = json_content[key] else: - raise exception.ParseResponseError( + raise exception.ParseResponseFailure( "response content is in text format! failed to query key {}!".format(key)) except (KeyError, ValueError, IndexError): - raise exception.ParseResponseError("failed to query json when extracting response!") + raise exception.ParseResponseFailure("failed to query json when extracting response!") return json_content @@ -507,7 +507,7 @@ def load_dot_env_file(path): return else: if not os.path.isfile(path): - raise exception.FileNotFoundError("env file not exist: {}".format(path)) + raise exception.FileNotFound("env file not exist: {}".format(path)) logger.log_info("Loading environment variables from {}".format(path)) with io.open(path, 'r', encoding='utf-8') as fp: diff --git a/tests/test_context.py b/tests/test_context.py index 585a45cf..c6511845 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -270,7 +270,7 @@ class VariableBindsUnittest(ApiServerUnittest): ] self.context.bind_variables(variables) - with self.assertRaises(exception.ValidationError): + with self.assertRaises(exception.ValidationFailure): self.context.validate(validators, resp_obj) validators = [ @@ -314,5 +314,5 @@ class VariableBindsUnittest(ApiServerUnittest): ] self.context.bind_variables(variables) - with self.assertRaises(exception.ValidationError): + with self.assertRaises(exception.ValidationFailure): self.context.validate(validators, resp_obj) diff --git a/tests/test_httprunner.py b/tests/test_httprunner.py index cadcae15..869c6cdc 100644 --- a/tests/test_httprunner.py +++ b/tests/test_httprunner.py @@ -2,7 +2,7 @@ import os import shutil from httprunner import HttpRunner -from httprunner.exception import FileNotFoundError +from httprunner.exception import FileNotFound from tests.base import ApiServerUnittest @@ -163,5 +163,5 @@ class TestHttpRunner(ApiServerUnittest): self.assertEqual(os.environ["UserName"], "debugtalk") def test_load_env_path_not_exist(self): - with self.assertRaises(FileNotFoundError): + with self.assertRaises(FileNotFound): HttpRunner(dot_env_path="not_exist.env").run(self.testset_path) diff --git a/tests/test_response.py b/tests/test_response.py index f0879b3e..12fe3c0e 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -138,7 +138,7 @@ class TestResponse(ApiServerUnittest): ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): resp_obj.extract_response(extract_binds_list) extract_binds_list = [ @@ -146,7 +146,7 @@ class TestResponse(ApiServerUnittest): ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): resp_obj.extract_response(extract_binds_list) def test_extract_response_json_string(self): @@ -225,5 +225,5 @@ class TestResponse(ApiServerUnittest): {"resp_content_body": "content.data.def"} ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): resp_obj.extract_response(extract_binds_list) diff --git a/tests/test_runner.py b/tests/test_runner.py index 390956eb..7d829320 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -66,7 +66,7 @@ class TestRunner(ApiServerUnittest): ] } - with self.assertRaises(exception.ValidationError): + with self.assertRaises(exception.ValidationFailure): self.test_runner.run_test(test) def test_run_testset_with_hooks(self): diff --git a/tests/test_testcase.py b/tests/test_testcase.py index 2e4af88c..443cf4fd 100644 --- a/tests/test_testcase.py +++ b/tests/test_testcase.py @@ -3,9 +3,7 @@ import time import unittest from httprunner import testcase -from httprunner.exception import (ApiNotFound, FileFormatError, - FileNotFoundError, ParamsError, - SuiteNotFound) +from httprunner.exception import ApiNotFound, ParamsError, SuiteNotFound from httprunner.testcase import TestcaseLoader diff --git a/tests/test_utils.py b/tests/test_utils.py index 76bd64d8..a8dc781e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -62,7 +62,7 @@ class TestFileUtils(unittest.TestCase): def test_load_testcases_bad_filepath(self): testcase_file_path = os.path.join(os.getcwd(), 'tests/data/demo') - with self.assertRaises(exception.FileNotFoundError): + with self.assertRaises(exception.FileNotFound): FileUtils.load_file(testcase_file_path) def test_load_json_testcases(self): @@ -163,11 +163,11 @@ class TestUtils(ApiServerUnittest): self.assertEqual(result, 3) query = "ids.str_key" - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): utils.query_json(json_content, query) query = "ids.5" - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): utils.query_json(json_content, query) query = "person.age" @@ -175,7 +175,7 @@ class TestUtils(ApiServerUnittest): self.assertEqual(result, 29) query = "person.not_exist_key" - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): utils.query_json(json_content, query) query = "person.cities.0" @@ -189,12 +189,12 @@ class TestUtils(ApiServerUnittest): def test_query_json_content_is_text(self): json_content = "" query = "key" - with self.assertRaises(exception.ResponseError): + with self.assertRaises(exception.ResponseFailure): utils.query_json(json_content, query) json_content = "content" query = "key" - with self.assertRaises(exception.ParseResponseError): + with self.assertRaises(exception.ParseResponseFailure): utils.query_json(json_content, query) def test_get_uniform_comparator(self): From 4a87af51e75dfe2db4da09d910ade7aebb270230 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 10:52:16 +0800 Subject: [PATCH 05/22] rename excpetion module to exceptions --- httprunner/built_in.py | 2 +- httprunner/client.py | 2 +- httprunner/context.py | 16 ++++----- httprunner/{exception.py => exceptions.py} | 0 httprunner/response.py | 22 ++++++------ httprunner/runner.py | 8 ++--- httprunner/task.py | 10 +++--- httprunner/testcase.py | 40 +++++++++++----------- httprunner/utils.py | 26 +++++++------- tests/test_context.py | 8 ++--- tests/test_httprunner.py | 2 +- tests/test_response.py | 10 +++--- tests/test_runner.py | 4 +-- tests/test_testcase.py | 2 +- tests/test_utils.py | 36 +++++++++---------- 15 files changed, 94 insertions(+), 94 deletions(-) rename httprunner/{exception.py => exceptions.py} (100%) diff --git a/httprunner/built_in.py b/httprunner/built_in.py index 32f94aad..e2fb0e69 100644 --- a/httprunner/built_in.py +++ b/httprunner/built_in.py @@ -13,7 +13,7 @@ import string import time from httprunner.compat import basestring, builtin_str, integer_types, str -from httprunner.exception import ParamsError +from httprunner.exceptions import ParamsError from requests_toolbelt import MultipartEncoder diff --git a/httprunner/client.py b/httprunner/client.py index 9b2ecfd5..0644b0bc 100644 --- a/httprunner/client.py +++ b/httprunner/client.py @@ -6,7 +6,7 @@ import time import requests import urllib3 from httprunner import logger -from httprunner.exception import ParamsError +from httprunner.exceptions import ParamsError from requests import Request, Response from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema, RequestException) diff --git a/httprunner/context.py b/httprunner/context.py index 93e3176e..7a160b5f 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -5,7 +5,7 @@ import os import re import sys -from httprunner import exception, logger, testcase, utils +from httprunner import exceptions, logger, testcase, utils from httprunner.compat import OrderedDict @@ -204,10 +204,10 @@ class Context(object): try: # format 4/5 check_value = resp_obj.extract_field(check_item) - except exception.ParseResponseFailure: + except exceptions.ParseResponseFailure: msg = "failed to extract check item from response!\n" msg += "response content: {}".format(resp_obj.content) - raise exception.ParseResponseFailure(msg) + raise exceptions.ParseResponseFailure(msg) validator["check_value"] = check_value @@ -227,7 +227,7 @@ class Context(object): validate_func = self.testcase_parser.get_bind_function(comparator) if not validate_func: - raise exception.FunctionNotFound("comparator not found: {}".format(comparator)) + raise exceptions.FunctionNotFound("comparator not found: {}".format(comparator)) check_item = validator_dict["check"] check_value = validator_dict["check_value"] @@ -235,7 +235,7 @@ class Context(object): 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/==") + raise exceptions.ParamsError("Null value can only be compared with comparator: eq/equals/==") validate_msg = "validate: {} {} {}({})".format( check_item, @@ -260,7 +260,7 @@ class Context(object): ) logger.log_error(validate_msg) validator_dict["check_result"] = "fail" - raise exception.ValidationFailure(validate_msg) + raise exceptions.ValidationFailure(validate_msg) def validate(self, validators, resp_obj): """ make validations @@ -277,10 +277,10 @@ class Context(object): try: self.do_validation(evaluated_validator) - except exception.ValidationFailure: + except exceptions.ValidationFailure: validate_pass = False self.evaluated_validators.append(evaluated_validator) if not validate_pass: - raise exception.ValidationFailure + raise exceptions.ValidationFailure diff --git a/httprunner/exception.py b/httprunner/exceptions.py similarity index 100% rename from httprunner/exception.py rename to httprunner/exceptions.py diff --git a/httprunner/response.py b/httprunner/response.py index e9f3e7b4..6065cece 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -3,7 +3,7 @@ import json import re -from httprunner import exception, logger, testcase, utils +from httprunner import exceptions, logger, testcase, utils from httprunner.compat import OrderedDict, basestring from requests.structures import CaseInsensitiveDict from requests.models import PreparedRequest @@ -31,7 +31,7 @@ class ResponseObject(object): except AttributeError: err_msg = "ResponseObject does not have attribute: {}".format(key) logger.log_error(err_msg) - raise exception.ParamsError(err_msg) + raise exceptions.ParamsError(err_msg) def _extract_field_with_regex(self, field): """ extract field from response content with regex. @@ -48,7 +48,7 @@ class ResponseObject(object): 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) + raise exceptions.ParamsError(err_msg) return matched.group(1) @@ -81,7 +81,7 @@ class ResponseObject(object): err_msg += u"cookies: {}\n".format(cookies) err_msg += u"attribute: {}".format(sub_query) logger.log_error(err_msg) - raise exception.ParamsError(err_msg) + raise exceptions.ParamsError(err_msg) elif top_query == "elapsed": if sub_query in ["days", "seconds", "microseconds"]: return getattr(self.elapsed, sub_query) @@ -91,14 +91,14 @@ class ResponseObject(object): 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) + raise exceptions.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) + raise exceptions.ParamsError(err_msg) if sub_query: if not isinstance(top_query_content, (dict, CaseInsensitiveDict, list)): @@ -111,12 +111,12 @@ class ResponseObject(object): top_query_content = top_query_content.__dict__ else: top_query_content = json.loads(top_query_content) - except exception.JSONDecodeError: + except exceptions.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) + raise exceptions.ParamsError(err_msg) # e.g. key: resp_headers_content_type, sub_query = "content-type" return utils.query_json(top_query_content, sub_query) @@ -129,7 +129,7 @@ class ResponseObject(object): err_msg += u"response content: {}\n".format(self.content) err_msg += u"extract: {}\n".format(field) logger.log_error(err_msg) - raise exception.ParamsError(err_msg) + raise exceptions.ParamsError(err_msg) def extract_field(self, field): """ extract value from requests.Response. @@ -146,7 +146,7 @@ class ResponseObject(object): logger.log_debug(msg) # TODO: unify ParseResponseFailure type - except (exception.ParseResponseFailure, TypeError): + except (exceptions.ParseResponseFailure, TypeError): logger.log_error("failed to extract field: {}".format(field)) raise @@ -172,7 +172,7 @@ class ResponseObject(object): for key, field in extract_binds_order_dict.items(): if not isinstance(field, basestring): - raise exception.ParamsError("invalid extractors in testcase!") + raise exceptions.ParamsError("invalid extractors in testcase!") extracted_variables_mapping[key] = self.extract_field(field) diff --git a/httprunner/runner.py b/httprunner/runner.py index 820b2a97..7ad195b1 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -2,7 +2,7 @@ from unittest.case import SkipTest -from httprunner import exception, logger, response, utils +from httprunner import exceptions, logger, response, utils from httprunner.client import HttpSession from httprunner.context import Context @@ -154,7 +154,7 @@ class Runner(object): method = parsed_request.pop('method') group_name = parsed_request.pop("group", None) except KeyError: - raise exception.ParamsError("URL or METHOD missed!") + raise exceptions.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)) @@ -183,8 +183,8 @@ class Runner(object): validators = testcase_dict.get("validate", []) or testcase_dict.get("validators", []) try: self.context.validate(validators, resp_obj) - except (exception.ParamsError, exception.ResponseFailure, \ - exception.ValidationFailure, exception.ParseResponseFailure): + except (exceptions.ParamsError, exceptions.ResponseFailure, \ + exceptions.ValidationFailure, exceptions.ParseResponseFailure): # log request err_req_msg = "request: \n" err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {})) diff --git a/httprunner/task.py b/httprunner/task.py index ca03c406..d9b0f0ab 100644 --- a/httprunner/task.py +++ b/httprunner/task.py @@ -4,7 +4,7 @@ import copy import sys import unittest -from httprunner import exception, logger, runner, testcase, utils +from httprunner import exceptions, logger, runner, testcase, utils from httprunner.compat import is_py3 from httprunner.report import (HtmlTestResult, get_platform, get_summary, render_html_report) @@ -106,7 +106,7 @@ class TestSuite(unittest.TestSuite): self.testcase_parser.update_binded_variables(variables) try: testcase_name = self.testcase_parser.eval_content_with_bindings(testcase_dict["name"]) - except (AssertionError, exception.ParamsError): + except (AssertionError, exceptions.ParamsError): logger.log_warning("failed to eval testcase name: {}".format(testcase_dict["name"])) testcase_name = testcase_dict["name"] self.test_runner_list.append((test_runner, variables)) @@ -189,7 +189,7 @@ def init_test_suites(path_or_testsets, mapping=None, http_client_session=None): mapping = mapping or {} if not testsets: - raise exception.TestcaseNotFound + raise exceptions.TestcaseNotFound if isinstance(testsets, dict): testsets = [testsets] @@ -236,7 +236,7 @@ class HttpRunner(object): """ try: test_suite_list = init_test_suites(path_or_testsets, mapping) - except exception.TestcaseNotFound: + except exceptions.TestcaseNotFound: logger.log_error("Testcases not found in {}".format(path_or_testsets)) sys.exit(1) @@ -301,7 +301,7 @@ class LocustTask(object): for test in test_suite: try: test.runTest() - except exception.MyBaseError as ex: + except exceptions.MyBaseError as ex: from locust.events import request_failure request_failure.fire( request_type=test.testcase_dict.get("request", {}).get("method"), diff --git a/httprunner/testcase.py b/httprunner/testcase.py index e25eda8b..ac4fef16 100644 --- a/httprunner/testcase.py +++ b/httprunner/testcase.py @@ -9,7 +9,7 @@ import os import random import re -from httprunner import exception, logger, utils +from httprunner import exceptions, logger, utils from httprunner.compat import OrderedDict, basestring, numeric_types from httprunner.utils import FileUtils @@ -77,7 +77,7 @@ def parse_function(content): """ matched = function_regexp_compile.match(content) if not matched: - raise exception.FunctionNotFound("{} not found!".format(content)) + raise exceptions.FunctionNotFound("{} not found!".format(content)) function_meta = { "func_name": matched.group(1), @@ -126,7 +126,7 @@ class TestcaseLoader(object): 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)) + raise exceptions.ParamsError("def missed in suite file: {}!".format(suite_file)) call_func = suite["config"]["def"] function_meta = parse_function(call_func) @@ -156,15 +156,15 @@ class TestcaseLoader(object): """ api_items = FileUtils.load_file(file_path) if not isinstance(api_items, list): - raise exception.FileFormatError("API format error: {}".format(file_path)) + raise exceptions.FileFormatError("API format error: {}".format(file_path)) for api_item in api_items: if not isinstance(api_item, dict) or len(api_item) != 1: - raise exception.FileFormatError("API format error: {}".format(file_path)) + raise exceptions.FileFormatError("API format error: {}".format(file_path)) key, api_dict = api_item.popitem() if key != "api" or not isinstance(api_dict, dict) or "def" not in api_dict: - raise exception.FileFormatError("API format error: {}".format(file_path)) + raise exceptions.FileFormatError("API format error: {}".format(file_path)) api_def = api_dict.pop("def") function_meta = parse_function(api_def) @@ -218,11 +218,11 @@ class TestcaseLoader(object): } 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)) + raise exceptions.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)) + raise exceptions.FileFormatError("Testcase format error: {}".format(file_path)) if key == "config": testset["config"].update(test_block) @@ -261,7 +261,7 @@ class TestcaseLoader(object): def_args = block.get("function_meta").get("args", []) if len(call_args) != len(def_args): - raise exception.ParamsError("call args mismatch defined args!") + raise exceptions.ParamsError("call args mismatch defined args!") args_mapping = {} for index, item in enumerate(def_args): @@ -289,10 +289,10 @@ class TestcaseLoader(object): if not block: err_msg = "{} not found!".format(name) if ref_type == "api": - raise exception.ApiNotFound(err_msg) + raise exceptions.ApiNotFound(err_msg) else: # ref_type == "suite": - raise exception.SuiteNotFound(err_msg) + raise exceptions.SuiteNotFound(err_msg) return block @@ -380,7 +380,7 @@ class TestcaseLoader(object): testcases_list = [testset] else: testcases_list = [] - except exception.FileFormatError: + except exceptions.FileFormatError: testcases_list = [] else: @@ -407,7 +407,7 @@ def parse_validator(validator): } """ if not isinstance(validator, dict): - raise exception.ParamsError("invalid validator: {}".format(validator)) + raise exceptions.ParamsError("invalid validator: {}".format(validator)) if "check" in validator and len(validator) > 1: # format1 @@ -418,7 +418,7 @@ def parse_validator(validator): elif "expected" in validator: expect_value = validator.get("expected") else: - raise exception.ParamsError("invalid validator: {}".format(validator)) + raise exceptions.ParamsError("invalid validator: {}".format(validator)) comparator = validator.get("comparator", "eq") @@ -428,12 +428,12 @@ def parse_validator(validator): compare_values = validator[comparator] if not isinstance(compare_values, list) or len(compare_values) != 2: - raise exception.ParamsError("invalid validator: {}".format(validator)) + raise exceptions.ParamsError("invalid validator: {}".format(validator)) check_item, expect_value = compare_values else: - raise exception.ParamsError("invalid validator: {}".format(validator)) + raise exceptions.ParamsError("invalid validator: {}".format(validator)) return { "check": check_item, @@ -711,7 +711,7 @@ def parse_parameters(parameters, testset_path=None): # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}] # e.g. [{"username": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}] if not isinstance(parsed_parameter_content, list): - raise exception.ParamsError("parameters syntax error!") + raise exceptions.ParamsError("parameters syntax error!") parameter_content_list = [ # get subset by parameter name @@ -769,13 +769,13 @@ class TestcaseParser(object): if item_name in self.variables: return self.variables[item_name] else: - raise exception.ParamsError("bind item should only be function or variable.") + raise exceptions.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( + except (AssertionError, exceptions.FunctionNotFound): + raise exceptions.ParamsError( "{} is not defined in bind {}s!".format(item_name, item_type)) def get_bind_function(self, func_name): diff --git a/httprunner/utils.py b/httprunner/utils.py index c4401f37..48cc1683 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -16,7 +16,7 @@ import types from datetime import datetime import yaml -from httprunner import exception, logger +from httprunner import exceptions, logger from httprunner.compat import OrderedDict, is_py2, is_py3 from requests.structures import CaseInsensitiveDict @@ -54,13 +54,13 @@ class FileUtils(object): # testcase file content is empty err_msg = u"Testcase file content is empty: {}".format(file_path) logger.log_error(err_msg) - raise exception.FileFormatError(err_msg) + raise exceptions.FileFormatError(err_msg) elif not isinstance(content, (list, dict)): # testcase file content does not match testcase format err_msg = u"Testcase file content format invalid: {}".format(file_path) logger.log_error(err_msg) - raise exception.FileFormatError(err_msg) + raise exceptions.FileFormatError(err_msg) @staticmethod def _load_yaml_file(yaml_file): @@ -78,10 +78,10 @@ class FileUtils(object): with io.open(json_file, encoding='utf-8') as data_file: try: json_content = json.load(data_file) - except exception.JSONDecodeError: + except exceptions.JSONDecodeError: err_msg = u"JSONDecodeError: JSON file format error: {}".format(json_file) logger.log_error(err_msg) - raise exception.FileFormatError(err_msg) + raise exceptions.FileFormatError(err_msg) FileUtils._check_format(json_file, json_content) return json_content @@ -117,7 +117,7 @@ class FileUtils(object): @staticmethod def load_file(file_path): if not os.path.isfile(file_path): - raise exception.FileNotFound("{} does not exist.".format(file_path)) + raise exceptions.FileNotFound("{} does not exist.".format(file_path)) file_suffix = os.path.splitext(file_path)[1].lower() if file_suffix == '.json': @@ -190,7 +190,7 @@ def query_json(json_content, query, delimiter='.'): @return queried result """ if json_content == "": - raise exception.ResponseFailure("response content is empty!") + raise exceptions.ResponseFailure("response content is empty!") try: for key in query.split(delimiter): @@ -199,10 +199,10 @@ def query_json(json_content, query, delimiter='.'): elif isinstance(json_content, (dict, CaseInsensitiveDict)): json_content = json_content[key] else: - raise exception.ParseResponseFailure( + raise exceptions.ParseResponseFailure( "response content is in text format! failed to query key {}!".format(key)) except (KeyError, ValueError, IndexError): - raise exception.ParseResponseFailure("failed to query json when extracting response!") + raise exceptions.ParseResponseFailure("failed to query json when extracting response!") return json_content @@ -333,9 +333,9 @@ def search_conf_item(start_path, item_type, item_name): # system root path err_msg = "{} not found in recursive upward path!".format(item_name) if item_type == "function": - raise exception.FunctionNotFound(err_msg) + raise exceptions.FunctionNotFound(err_msg) else: - raise exception.VariableNotFound(err_msg) + raise exceptions.VariableNotFound(err_msg) return search_conf_item(dir_path, item_type, item_name) @@ -422,7 +422,7 @@ def override_variables_binds(variables, new_mapping): elif isinstance(variables, (OrderedDict, dict)): variables_ordered_dict = variables else: - raise exception.ParamsError("variables error!") + raise exceptions.ParamsError("variables error!") return update_ordered_dict( variables_ordered_dict, @@ -507,7 +507,7 @@ def load_dot_env_file(path): return else: if not os.path.isfile(path): - raise exception.FileNotFound("env file not exist: {}".format(path)) + raise exceptions.FileNotFound("env file not exist: {}".format(path)) logger.log_info("Loading environment variables from {}".format(path)) with io.open(path, 'r', encoding='utf-8') as fp: diff --git a/tests/test_context.py b/tests/test_context.py index c6511845..1d4220f9 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -2,7 +2,7 @@ import os import time import requests -from httprunner import exception, response, runner, testcase +from httprunner import exceptions, response, runner, testcase from httprunner.context import Context from httprunner.utils import FileUtils, gen_md5 from tests.base import ApiServerUnittest @@ -270,7 +270,7 @@ class VariableBindsUnittest(ApiServerUnittest): ] self.context.bind_variables(variables) - with self.assertRaises(exception.ValidationFailure): + with self.assertRaises(exceptions.ValidationFailure): self.context.validate(validators, resp_obj) validators = [ @@ -305,7 +305,7 @@ class VariableBindsUnittest(ApiServerUnittest): variables = [] self.context.bind_variables(variables) - with self.assertRaises(exception.ParamsError): + with self.assertRaises(exceptions.ParamsError): self.context.validate(validators, resp_obj) # expected value missed in variables mapping @@ -314,5 +314,5 @@ class VariableBindsUnittest(ApiServerUnittest): ] self.context.bind_variables(variables) - with self.assertRaises(exception.ValidationFailure): + with self.assertRaises(exceptions.ValidationFailure): self.context.validate(validators, resp_obj) diff --git a/tests/test_httprunner.py b/tests/test_httprunner.py index 869c6cdc..2b5ad47c 100644 --- a/tests/test_httprunner.py +++ b/tests/test_httprunner.py @@ -2,7 +2,7 @@ import os import shutil from httprunner import HttpRunner -from httprunner.exception import FileNotFound +from httprunner.exceptions import FileNotFound from tests.base import ApiServerUnittest diff --git a/tests/test_response.py b/tests/test_response.py index 12fe3c0e..9a3948ad 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,5 +1,5 @@ import requests -from httprunner import exception, response, utils +from httprunner import exceptions, response, utils from httprunner.compat import bytes from tests.base import ApiServerUnittest @@ -138,7 +138,7 @@ class TestResponse(ApiServerUnittest): ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): resp_obj.extract_response(extract_binds_list) extract_binds_list = [ @@ -146,7 +146,7 @@ class TestResponse(ApiServerUnittest): ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): resp_obj.extract_response(extract_binds_list) def test_extract_response_json_string(self): @@ -202,7 +202,7 @@ class TestResponse(ApiServerUnittest): {"resp_content_key1": "LB123.*RB789"} ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParamsError): + with self.assertRaises(exceptions.ParamsError): resp_obj.extract_response(extract_binds_list) def test_extract_response_empty(self): @@ -225,5 +225,5 @@ class TestResponse(ApiServerUnittest): {"resp_content_body": "content.data.def"} ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): resp_obj.extract_response(extract_binds_list) diff --git a/tests/test_runner.py b/tests/test_runner.py index 7d829320..dee4b754 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -1,7 +1,7 @@ import os import time -from httprunner import HttpRunner, exception, runner +from httprunner import HttpRunner, exceptions, runner from httprunner.testcase import TestcaseLoader from httprunner.utils import FileUtils, deep_update_dict from tests.base import ApiServerUnittest @@ -66,7 +66,7 @@ class TestRunner(ApiServerUnittest): ] } - with self.assertRaises(exception.ValidationFailure): + with self.assertRaises(exceptions.ValidationFailure): self.test_runner.run_test(test) def test_run_testset_with_hooks(self): diff --git a/tests/test_testcase.py b/tests/test_testcase.py index 443cf4fd..b7ef5191 100644 --- a/tests/test_testcase.py +++ b/tests/test_testcase.py @@ -3,7 +3,7 @@ import time import unittest from httprunner import testcase -from httprunner.exception import ApiNotFound, ParamsError, SuiteNotFound +from httprunner.exceptions import ApiNotFound, ParamsError, SuiteNotFound from httprunner.testcase import TestcaseLoader diff --git a/tests/test_utils.py b/tests/test_utils.py index a8dc781e..851e97fc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,7 +2,7 @@ import os import shutil import unittest -from httprunner import exception, utils +from httprunner import exceptions, utils from httprunner.compat import OrderedDict from httprunner.utils import FileUtils from tests.base import ApiServerUnittest @@ -16,7 +16,7 @@ class TestFileUtils(unittest.TestCase): with open(yaml_tmp_file, 'w') as f: f.write("") - with self.assertRaises(exception.FileFormatError): + with self.assertRaises(exceptions.FileFormatError): FileUtils._load_yaml_file(yaml_tmp_file) os.remove(yaml_tmp_file) @@ -25,7 +25,7 @@ class TestFileUtils(unittest.TestCase): with open(yaml_tmp_file, 'w') as f: f.write("abc") - with self.assertRaises(exception.FileFormatError): + with self.assertRaises(exceptions.FileFormatError): FileUtils._load_yaml_file(yaml_tmp_file) os.remove(yaml_tmp_file) @@ -37,7 +37,7 @@ class TestFileUtils(unittest.TestCase): with open(json_tmp_file, 'w') as f: f.write("") - with self.assertRaises(exception.FileFormatError): + with self.assertRaises(exceptions.FileFormatError): FileUtils._load_json_file(json_tmp_file) os.remove(json_tmp_file) @@ -46,7 +46,7 @@ class TestFileUtils(unittest.TestCase): with open(json_tmp_file, 'w') as f: f.write("{}") - with self.assertRaises(exception.FileFormatError): + with self.assertRaises(exceptions.FileFormatError): FileUtils._load_json_file(json_tmp_file) os.remove(json_tmp_file) @@ -55,14 +55,14 @@ class TestFileUtils(unittest.TestCase): with open(json_tmp_file, 'w') as f: f.write("abc") - with self.assertRaises(exception.FileFormatError): + with self.assertRaises(exceptions.FileFormatError): FileUtils._load_json_file(json_tmp_file) os.remove(json_tmp_file) def test_load_testcases_bad_filepath(self): testcase_file_path = os.path.join(os.getcwd(), 'tests/data/demo') - with self.assertRaises(exception.FileNotFound): + with self.assertRaises(exceptions.FileNotFound): FileUtils.load_file(testcase_file_path) def test_load_json_testcases(self): @@ -163,11 +163,11 @@ class TestUtils(ApiServerUnittest): self.assertEqual(result, 3) query = "ids.str_key" - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): utils.query_json(json_content, query) query = "ids.5" - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): utils.query_json(json_content, query) query = "person.age" @@ -175,7 +175,7 @@ class TestUtils(ApiServerUnittest): self.assertEqual(result, 29) query = "person.not_exist_key" - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): utils.query_json(json_content, query) query = "person.cities.0" @@ -189,12 +189,12 @@ class TestUtils(ApiServerUnittest): def test_query_json_content_is_text(self): json_content = "" query = "key" - with self.assertRaises(exception.ResponseFailure): + with self.assertRaises(exceptions.ResponseFailure): utils.query_json(json_content, query) json_content = "content" query = "key" - with self.assertRaises(exception.ParseResponseFailure): + with self.assertRaises(exceptions.ParseResponseFailure): utils.query_json(json_content, query) def test_get_uniform_comparator(self): @@ -302,7 +302,7 @@ class TestUtils(ApiServerUnittest): self.assertIn("gen_md5", functions_dict) self.assertNotIn("urllib", functions_dict) - with self.assertRaises(exception.FileNotFoundError): + with self.assertRaises(exceptions.FileNotFoundError): utils.get_imported_module_from_file("tests/debugtalk2.py") def test_search_conf_function(self): @@ -314,10 +314,10 @@ class TestUtils(ApiServerUnittest): self.assertTrue(utils.is_function(("_", gen_md5))) self.assertEqual(gen_md5("abc"), "900150983cd24fb0d6963f7d28e17f72") - with self.assertRaises(exception.FunctionNotFound): + with self.assertRaises(exceptions.FunctionNotFound): utils.search_conf_item("tests/data/subfolder/test.yml", "function", "func_not_exist") - with self.assertRaises(exception.FunctionNotFound): + with self.assertRaises(exceptions.FunctionNotFound): utils.search_conf_item("/user/local/bin", "function", "gen_md5") def test_search_conf_variable(self): @@ -329,10 +329,10 @@ class TestUtils(ApiServerUnittest): self.assertTrue(utils.is_variable(("SECRET_KEY", SECRET_KEY))) self.assertEqual(SECRET_KEY, "DebugTalk") - with self.assertRaises(exception.VariableNotFound): + with self.assertRaises(exceptions.VariableNotFound): utils.search_conf_item("tests/data/subfolder/test.yml", "variable", "variable_not_exist") - with self.assertRaises(exception.VariableNotFound): + with self.assertRaises(exceptions.VariableNotFound): utils.search_conf_item("/user/local/bin", "variable", "SECRET_KEY") def test_is_variable(self): @@ -443,7 +443,7 @@ class TestUtils(ApiServerUnittest): map_list = "invalid" override_mapping = {"a": 3, "c": 4} - with self.assertRaises(exception.ParamsError): + with self.assertRaises(exceptions.ParamsError): utils.override_variables_binds(map_list, override_mapping) def test_create_scaffold(self): From 22632d711003c561974b0d32a39077fc26cb74bd Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 11:25:12 +0800 Subject: [PATCH 06/22] refactor: extract status_code --- httprunner/response.py | 15 ++++++++++++++- tests/test_response.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/httprunner/response.py b/httprunner/response.py index 6065cece..fc9c36b8 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -72,7 +72,18 @@ class ResponseObject(object): top_query = field sub_query = None - if top_query == "cookies": + # status_code + if top_query == "status_code": + if sub_query: + # status_code.XX + err_msg = u"ParamsError: {}\n".format(field) + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + + return self.status_code + + # cookies + elif top_query == "cookies": cookies = self.cookies try: return cookies[sub_query] @@ -82,6 +93,8 @@ class ResponseObject(object): err_msg += u"attribute: {}".format(sub_query) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) + + # elapsed elif top_query == "elapsed": if sub_query in ["days", "seconds", "microseconds"]: return getattr(self.elapsed, sub_query) diff --git a/tests/test_response.py b/tests/test_response.py index 9a3948ad..da28be27 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -27,6 +27,39 @@ class TestResponse(ApiServerUnittest): resp_obj = response.ResponseObject(resp) self.assertEqual(bytes, type(resp_obj.content)) + def test_extract_response_status_code(self): + resp = requests.post( + url="http://127.0.0.1:3458/anything", + json={ + 'success': False, + "person": { + "name": { + "first_name": "Leo", + "last_name": "Lee", + }, + "age": 29, + "cities": ["Guangzhou", "Shenzhen"] + } + } + ) + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_status_code": "status_code"} + ] + extract_binds_dict = resp_obj.extract_response(extract_binds_list) + + self.assertEqual( + extract_binds_dict["resp_status_code"], + 200 + ) + + extract_binds_list = [ + {"resp_status_code": "status_code.xx"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + def test_extract_response_json(self): resp = requests.post( url="http://127.0.0.1:3458/anything", From 0b92b38143a09447792cbf7c155c507996e45004 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 11:47:10 +0800 Subject: [PATCH 07/22] refactor: extract cookie --- httprunner/response.py | 11 +++++++---- tests/test_response.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index fc9c36b8..a36cbb35 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -84,13 +84,16 @@ class ResponseObject(object): # cookies elif top_query == "cookies": - cookies = self.cookies + cookies = self.cookies.get_dict() + if not sub_query: + # extract cookies + return 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) + err_msg = u"ParamsError: Failed to extract cookie! => {}\n".format(field) + err_msg += u"response cookies: {}\n".format(cookies) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) diff --git a/tests/test_response.py b/tests/test_response.py index da28be27..963acc81 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -60,6 +60,30 @@ class TestResponse(ApiServerUnittest): with self.assertRaises(exceptions.ParamsError): resp_obj.extract_response(extract_binds_list) + def test_extract_response_cookies(self): + resp = requests.get( + url="http://127.0.0.1:3458/cookies", + headers={ + "accept": "application/json" + } + ) + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_cookies": "cookies"} + ] + extract_binds_dict = resp_obj.extract_response(extract_binds_list) + self.assertEqual( + extract_binds_dict["resp_cookies"], + {} + ) + + extract_binds_list = [ + {"resp_cookies": "cookies.xx"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + def test_extract_response_json(self): resp = requests.post( url="http://127.0.0.1:3458/anything", From a2bd9c25745e51a491135546efcc3f2eb7c26102 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 12:04:07 +0800 Subject: [PATCH 08/22] refactor: extract elapsed --- httprunner/response.py | 12 +++++++++--- tests/test_response.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index a36cbb35..fb434663 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -99,13 +99,19 @@ class ResponseObject(object): # elapsed elif top_query == "elapsed": - if sub_query in ["days", "seconds", "microseconds"]: + available_attributes = u"available attributes: days, seconds, microseconds, total_seconds" + if not sub_query: + err_msg = u"ParamsError: elapsed is datetime.timedelta instance, attribute should also be specified!\n" + err_msg += available_attributes + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + elif 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" + err_msg = "ParamsError: {} is not valid datetime.timedelta attribute.\n".format(sub_query) + err_msg += available_attributes logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) diff --git a/tests/test_response.py b/tests/test_response.py index 963acc81..efdbd56b 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -84,6 +84,47 @@ class TestResponse(ApiServerUnittest): with self.assertRaises(exceptions.ParamsError): resp_obj.extract_response(extract_binds_list) + def test_extract_response_elapsed(self): + resp = requests.post( + url="http://127.0.0.1:3458/anything", + json={ + 'success': False, + "person": { + "name": { + "first_name": "Leo", + "last_name": "Lee", + }, + "age": 29, + "cities": ["Guangzhou", "Shenzhen"] + } + } + ) + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_elapsed": "elapsed"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + + extract_binds_list = [ + {"resp_elapsed_microseconds": "elapsed.microseconds"}, + {"resp_elapsed_seconds": "elapsed.seconds"}, + {"resp_elapsed_days": "elapsed.days"}, + {"resp_elapsed_total_seconds": "elapsed.total_seconds"} + ] + extract_binds_dict = resp_obj.extract_response(extract_binds_list) + self.assertGreater(extract_binds_dict["resp_elapsed_microseconds"], 1000) + self.assertEqual(extract_binds_dict["resp_elapsed_seconds"], 0) + self.assertEqual(extract_binds_dict["resp_elapsed_days"], 0) + self.assertGreater(extract_binds_dict["resp_elapsed_total_seconds"], 0) + + extract_binds_list = [ + {"resp_elapsed": "elapsed.years"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + def test_extract_response_json(self): resp = requests.post( url="http://127.0.0.1:3458/anything", From c33b81a2b7351813a41b86504bddad71c87bf512 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 12:18:33 +0800 Subject: [PATCH 09/22] refactor: extract headers --- httprunner/response.py | 15 +++++++++++++++ tests/test_response.py | 36 ++++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index fb434663..f3591266 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -115,6 +115,21 @@ class ResponseObject(object): logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) + # headers + elif top_query == "headers": + headers = self.headers + if not sub_query: + # extract headers + return headers + + try: + return headers[sub_query] + except KeyError: + err_msg = u"ParamsError: Failed to extract header! => {}\n".format(field) + err_msg += u"response headers: {}\n".format(headers) + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + try: top_query_content = getattr(self, top_query) except AttributeError: diff --git a/tests/test_response.py b/tests/test_response.py index efdbd56b..fa39a8d2 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -28,20 +28,7 @@ class TestResponse(ApiServerUnittest): self.assertEqual(bytes, type(resp_obj.content)) def test_extract_response_status_code(self): - resp = requests.post( - url="http://127.0.0.1:3458/anything", - json={ - 'success': False, - "person": { - "name": { - "first_name": "Leo", - "last_name": "Lee", - }, - "age": 29, - "cities": ["Guangzhou", "Shenzhen"] - } - } - ) + resp = requests.get(url="http://127.0.0.1:3458/status/200") resp_obj = response.ResponseObject(resp) extract_binds_list = [ @@ -125,6 +112,24 @@ class TestResponse(ApiServerUnittest): with self.assertRaises(exceptions.ParamsError): resp_obj.extract_response(extract_binds_list) + def test_extract_response_headers(self): + resp = requests.get(url="http://127.0.0.1:3458/status/200") + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_headers": "headers"}, + {"resp_headers_content_type": "headers.Content-Type"} + ] + extract_binds_dict = resp_obj.extract_response(extract_binds_list) + self.assertIn("Content-Type", extract_binds_dict["resp_headers"]) + self.assertIn("text/html", extract_binds_dict["resp_headers_content_type"]) + + extract_binds_list = [ + {"resp_headers_xxx": "headers.xxx"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + def test_extract_response_json(self): resp = requests.post( url="http://127.0.0.1:3458/anything", @@ -140,7 +145,7 @@ class TestResponse(ApiServerUnittest): } } ) - # resp.text + # resp.json() # { # "args": {}, # "data": "{\"success\": false, \"person\": {\"name\": {\"first_name\": \"Leo\", \"last_name\": \"Lee\"}, \"age\": 29, \"cities\": [\"Guangzhou\", \"Shenzhen\"]}}", @@ -175,7 +180,6 @@ class TestResponse(ApiServerUnittest): # } extract_binds_list = [ - {"resp_status_code": "status_code"}, {"resp_headers_content_type": "headers.content-type"}, {"resp_content_body_success": "json.json.success"}, {"resp_content_content_success": "content.json.success"}, From 6c7e1be517301b4d20d0bfaeda49acd4f4e3877a Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 16:10:45 +0800 Subject: [PATCH 10/22] refactor: extract content/text/json --- httprunner/response.py | 175 +++++++++++++++++++--------------------- tests/httpbin/hooks.yml | 4 +- tests/test_response.py | 41 ++++++++-- 3 files changed, 118 insertions(+), 102 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index f3591266..47711022 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -63,108 +63,97 @@ class ResponseObject(object): "headers.content-type" "content.person.name.first_name" """ + # string.split(sep=None, maxsplit=-1) -> list of strings + # e.g. "content.person.name" => ["content", "person.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 + top_query, sub_query = field.split('.', 1) + except ValueError: + top_query = field + sub_query = None - # status_code - if top_query == "status_code": - if sub_query: - # status_code.XX - err_msg = u"ParamsError: {}\n".format(field) - logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) - - return self.status_code - - # cookies - elif top_query == "cookies": - cookies = self.cookies.get_dict() - if not sub_query: - # extract cookies - return cookies - - try: - return cookies[sub_query] - except KeyError: - err_msg = u"ParamsError: Failed to extract cookie! => {}\n".format(field) - err_msg += u"response cookies: {}\n".format(cookies) - logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) - - # elapsed - elif top_query == "elapsed": - available_attributes = u"available attributes: days, seconds, microseconds, total_seconds" - if not sub_query: - err_msg = u"ParamsError: elapsed is datetime.timedelta instance, attribute should also be specified!\n" - err_msg += available_attributes - logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) - elif 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 = "ParamsError: {} is not valid datetime.timedelta attribute.\n".format(sub_query) - err_msg += available_attributes - logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) - - # headers - elif top_query == "headers": - headers = self.headers - if not sub_query: - # extract headers - return headers - - try: - return headers[sub_query] - except KeyError: - err_msg = u"ParamsError: Failed to extract header! => {}\n".format(field) - err_msg += u"response headers: {}\n".format(headers) - logger.log_error(err_msg) - raise exceptions.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) + # status_code + if top_query == "status_code": + if sub_query: + # status_code.XX + err_msg = u"ParamsError: {}\n".format(field) logger.log_error(err_msg) raise exceptions.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") + return self.status_code - if isinstance(top_query_content, PreparedRequest): - top_query_content = top_query_content.__dict__ - else: - top_query_content = json.loads(top_query_content) - except exceptions.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 exceptions.ParamsError(err_msg) + # cookies + elif top_query == "cookies": + cookies = self.cookies.get_dict() + if not sub_query: + # extract cookies + return cookies - # e.g. key: resp_headers_content_type, sub_query = "content-type" - return utils.query_json(top_query_content, sub_query) + try: + return cookies[sub_query] + except KeyError: + err_msg = u"ParamsError: Failed to extract cookie! => {}\n".format(field) + err_msg += u"response cookies: {}\n".format(cookies) + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + + # elapsed + elif top_query == "elapsed": + available_attributes = u"available attributes: days, seconds, microseconds, total_seconds" + if not sub_query: + err_msg = u"ParamsError: elapsed is datetime.timedelta instance, attribute should also be specified!\n" + err_msg += available_attributes + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + elif sub_query in ["days", "seconds", "microseconds"]: + return getattr(self.elapsed, sub_query) + elif sub_query == "total_seconds": + return self.elapsed.total_seconds() else: - # e.g. key: resp_status_code, resp_content - return top_query_content + err_msg = "ParamsError: {} is not valid datetime.timedelta attribute.\n".format(sub_query) + err_msg += available_attributes + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) - 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: {}\n".format(field) + # headers + elif top_query == "headers": + headers = self.headers + if not sub_query: + # extract headers + return headers + + try: + return headers[sub_query] + except KeyError: + err_msg = u"ParamsError: Failed to extract header! => {}\n".format(field) + err_msg += u"response headers: {}\n".format(headers) + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + + # response body + elif top_query in ["content", "text", "json"]: + try: + body = self.json + except exceptions.JSONDecodeError: + body = self.text + + if not sub_query: + # extract response body + return body + + if isinstance(body, (dict, list)): + # content = {"xxx": 123}, content.xxx + return utils.query_json(body, sub_query) + else: + # content = "abcdefg", content.xxx + err_msg = u"ParamsError: Failed to extract attribute from response body! => {}\n".format(field) + err_msg += u"response body: {}\n".format(body) + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + + # others + else: + err_msg = u"ParamsError: Failed to extract attribute from response! => {}\n".format(field) + err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, text, json." logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) diff --git a/tests/httpbin/hooks.yml b/tests/httpbin/hooks.yml index 87339639..c35c306c 100644 --- a/tests/httpbin/hooks.yml +++ b/tests/httpbin/hooks.yml @@ -32,5 +32,5 @@ - eq: ["status_code", 500] - eq: ["headers.content-type", "html/text"] - eq: [json.headers.Host, "127.0.0.1:8888"] - - eq: [content.headers.Host, "127.0.0.1:3458"] - - eq: [text.headers.Host, "127.0.0.1:3458"] + - eq: [content.headers.Host, "127.0.0.1:8888"] + - eq: [text.headers.Host, "127.0.0.1:8888"] diff --git a/tests/test_response.py b/tests/test_response.py index fa39a8d2..20146baf 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,6 +1,6 @@ import requests from httprunner import exceptions, response, utils -from httprunner.compat import bytes +from httprunner.compat import bytes, str from tests.base import ApiServerUnittest @@ -118,11 +118,13 @@ class TestResponse(ApiServerUnittest): extract_binds_list = [ {"resp_headers": "headers"}, - {"resp_headers_content_type": "headers.Content-Type"} + {"resp_headers_content_type": "headers.Content-Type"}, + {"resp_headers_content_type_lowercase": "headers.content-type"} ] extract_binds_dict = resp_obj.extract_response(extract_binds_list) self.assertIn("Content-Type", extract_binds_dict["resp_headers"]) self.assertIn("text/html", extract_binds_dict["resp_headers_content_type"]) + self.assertIn("text/html", extract_binds_dict["resp_headers_content_type_lowercase"]) extract_binds_list = [ {"resp_headers_xxx": "headers.xxx"} @@ -130,7 +132,7 @@ class TestResponse(ApiServerUnittest): with self.assertRaises(exceptions.ParamsError): resp_obj.extract_response(extract_binds_list) - def test_extract_response_json(self): + def test_extract_response_body_json(self): resp = requests.post( url="http://127.0.0.1:3458/anything", json={ @@ -190,10 +192,6 @@ class TestResponse(ApiServerUnittest): resp_obj = response.ResponseObject(resp) extract_binds_dict = resp_obj.extract_response(extract_binds_list) - self.assertEqual( - extract_binds_dict["resp_status_code"], - 200 - ) self.assertEqual( extract_binds_dict["resp_headers_content_type"], "application/json" @@ -219,6 +217,35 @@ class TestResponse(ApiServerUnittest): "Shenzhen" ) + def test_extract_response_body_html(self): + resp = requests.get(url="http://127.0.0.1:3458/") + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_content": "content"} + ] + extract_binds_dict = resp_obj.extract_response(extract_binds_list) + + self.assertIsInstance(extract_binds_dict["resp_content"], str) + self.assertIn("python-requests.org", extract_binds_dict["resp_content"]) + + extract_binds_list = [ + {"resp_content": "content.xxx"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + + def test_extract_response_others(self): + resp = requests.get(url="http://127.0.0.1:3458/status/200") + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_others_encoding": "encoding"}, + {"resp_others_history": "history"} + ] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + def test_extract_response_fail(self): resp = requests.post( url="http://127.0.0.1:3458/anything", From b88b66b904393abf834e836841dd7205db4656c3 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 16:22:08 +0800 Subject: [PATCH 11/22] refactor: extract encoding/ok/reason/url --- httprunner/response.py | 6 +++--- tests/test_response.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index 47711022..4d59e339 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -72,14 +72,14 @@ class ResponseObject(object): sub_query = None # status_code - if top_query == "status_code": + if top_query in ["status_code", "encoding", "ok", "reason", "url"]: if sub_query: # status_code.XX err_msg = u"ParamsError: {}\n".format(field) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) - return self.status_code + return getattr(self, top_query) # cookies elif top_query == "cookies": @@ -153,7 +153,7 @@ class ResponseObject(object): # others else: err_msg = u"ParamsError: Failed to extract attribute from response! => {}\n".format(field) - err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, text, json." + err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, text, json, encoding, ok, reason, url." logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) diff --git a/tests/test_response.py b/tests/test_response.py index 20146baf..44cf333d 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -47,6 +47,39 @@ class TestResponse(ApiServerUnittest): with self.assertRaises(exceptions.ParamsError): resp_obj.extract_response(extract_binds_list) + def test_extract_response_encoding_ok_reason_url(self): + resp = requests.get(url="http://127.0.0.1:3458/status/200") + resp_obj = response.ResponseObject(resp) + + extract_binds_list = [ + {"resp_encoding": "encoding"}, + {"resp_ok": "ok"}, + {"resp_reason": "reason"}, + {"resp_url": "url"} + ] + extract_binds_dict = resp_obj.extract_response(extract_binds_list) + + self.assertEqual(extract_binds_dict["resp_encoding"], "utf-8") + self.assertEqual(extract_binds_dict["resp_ok"], True) + self.assertEqual(extract_binds_dict["resp_reason"], "OK") + self.assertEqual(extract_binds_dict["resp_url"], "http://127.0.0.1:3458/status/200") + + extract_binds_list = [{"resp_encoding": "encoding.xx"}] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + + extract_binds_list = [{"resp_ok": "ok.xx"}] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + + extract_binds_list = [{"resp_reason": "reason.xx"}] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + + extract_binds_list = [{"resp_url": "url.xx"}] + with self.assertRaises(exceptions.ParamsError): + resp_obj.extract_response(extract_binds_list) + def test_extract_response_cookies(self): resp = requests.get( url="http://127.0.0.1:3458/cookies", From 59f3d64537bb19b07c8fc7d5983f4c7e1dcb0143 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 16:39:05 +0800 Subject: [PATCH 12/22] fix compatibility with python2.7 --- httprunner/compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index 607c8b3d..e58ea033 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -40,7 +40,7 @@ if is_py2: integer_types = (int, long) FileNotFoundError = IOError - JSONDecodeError = json.decoder.JSONDecodeError + JSONDecodeError = ValueError elif is_py3: from collections import OrderedDict @@ -53,4 +53,4 @@ elif is_py3: integer_types = (int,) FileNotFoundError = FileNotFoundError - JSONDecodeError = ValueError + JSONDecodeError = json.decoder.JSONDecodeError From fe9d5c497ba11e5a8b3d462ffe2b07371417ca01 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 16:47:55 +0800 Subject: [PATCH 13/22] remove exception type: ResponseFailure --- httprunner/exceptions.py | 3 --- httprunner/runner.py | 4 ++-- httprunner/utils.py | 7 ++----- tests/test_utils.py | 11 ----------- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/httprunner/exceptions.py b/httprunner/exceptions.py index 0e717f89..6323c896 100644 --- a/httprunner/exceptions.py +++ b/httprunner/exceptions.py @@ -12,9 +12,6 @@ class MyBaseFailure(BaseException): class ValidationFailure(MyBaseFailure): pass -class ResponseFailure(MyBaseFailure): - pass - class ParseResponseFailure(MyBaseFailure): pass diff --git a/httprunner/runner.py b/httprunner/runner.py index 7ad195b1..55532ee9 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -183,8 +183,8 @@ class Runner(object): validators = testcase_dict.get("validate", []) or testcase_dict.get("validators", []) try: self.context.validate(validators, resp_obj) - except (exceptions.ParamsError, exceptions.ResponseFailure, \ - exceptions.ValidationFailure, exceptions.ParseResponseFailure): + except (exceptions.ParamsError, \ + exceptions.ValidationFailure, exceptions.ParseResponseFailure): # log request err_req_msg = "request: \n" err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {})) diff --git a/httprunner/utils.py b/httprunner/utils.py index 48cc1683..2e67abb7 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -172,7 +172,7 @@ class FileUtils(object): def query_json(json_content, query, delimiter='.'): """ Do an xpath-like query with json_content. - @param (json_content) json_content + @param (dict/list) json_content json_content = { "ids": [1, 2, 3, 4], "person": { @@ -189,14 +189,11 @@ def query_json(json_content, query, delimiter='.'): "person.cities.0" => "Guangzhou" @return queried result """ - if json_content == "": - raise exceptions.ResponseFailure("response content is empty!") - try: for key in query.split(delimiter): if isinstance(json_content, list): json_content = json_content[int(key)] - elif isinstance(json_content, (dict, CaseInsensitiveDict)): + elif isinstance(json_content, dict): json_content = json_content[key] else: raise exceptions.ParseResponseFailure( diff --git a/tests/test_utils.py b/tests/test_utils.py index 851e97fc..dbdbe553 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -186,17 +186,6 @@ class TestUtils(ApiServerUnittest): result = utils.query_json(json_content, query) self.assertEqual(result, "Leo") - def test_query_json_content_is_text(self): - json_content = "" - query = "key" - with self.assertRaises(exceptions.ResponseFailure): - utils.query_json(json_content, query) - - json_content = "content" - query = "key" - with self.assertRaises(exceptions.ParseResponseFailure): - utils.query_json(json_content, query) - def test_get_uniform_comparator(self): self.assertEqual(utils.get_uniform_comparator("eq"), "equals") self.assertEqual(utils.get_uniform_comparator("=="), "equals") From dee51b66820bc83c5169c0ad90a346ee247b393b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 17:32:12 +0800 Subject: [PATCH 14/22] refactor: query json content --- httprunner/context.py | 9 ++------- httprunner/exceptions.py | 2 +- httprunner/response.py | 7 +++++-- httprunner/runner.py | 2 +- httprunner/utils.py | 18 +++++++++++++----- tests/test_response.py | 6 +++--- tests/test_utils.py | 10 +++++++--- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/httprunner/context.py b/httprunner/context.py index 7a160b5f..11d7736d 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -201,13 +201,8 @@ class Context(object): # format 1/2/3 check_value = self.eval_content(check_item) else: - try: - # format 4/5 - check_value = resp_obj.extract_field(check_item) - except exceptions.ParseResponseFailure: - msg = "failed to extract check item from response!\n" - msg += "response content: {}".format(resp_obj.content) - raise exceptions.ParseResponseFailure(msg) + # format 4/5 + check_value = resp_obj.extract_field(check_item) validator["check_value"] = check_value diff --git a/httprunner/exceptions.py b/httprunner/exceptions.py index 6323c896..29b743d7 100644 --- a/httprunner/exceptions.py +++ b/httprunner/exceptions.py @@ -12,7 +12,7 @@ class MyBaseFailure(BaseException): class ValidationFailure(MyBaseFailure): pass -class ParseResponseFailure(MyBaseFailure): +class ExtractFailure(MyBaseFailure): pass diff --git a/httprunner/response.py b/httprunner/response.py index 4d59e339..cf59715b 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -143,6 +143,9 @@ class ResponseObject(object): if isinstance(body, (dict, list)): # content = {"xxx": 123}, content.xxx return utils.query_json(body, sub_query) + elif sub_query.isdigit(): + # content = "abcdefg", content.3 => d + return utils.query_json(body, sub_query) else: # content = "abcdefg", content.xxx err_msg = u"ParamsError: Failed to extract attribute from response body! => {}\n".format(field) @@ -171,8 +174,8 @@ class ResponseObject(object): msg += "\t=> {}".format(value) logger.log_debug(msg) - # TODO: unify ParseResponseFailure type - except (exceptions.ParseResponseFailure, TypeError): + # TODO: remove except here + except (TypeError): logger.log_error("failed to extract field: {}".format(field)) raise diff --git a/httprunner/runner.py b/httprunner/runner.py index 55532ee9..ca7b49db 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -184,7 +184,7 @@ class Runner(object): try: self.context.validate(validators, resp_obj) except (exceptions.ParamsError, \ - exceptions.ValidationFailure, exceptions.ParseResponseFailure): + exceptions.ValidationFailure, exceptions.ExtractFailure): # log request err_req_msg = "request: \n" err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {})) diff --git a/httprunner/utils.py b/httprunner/utils.py index 2e67abb7..40592dfb 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -17,7 +17,7 @@ from datetime import datetime import yaml from httprunner import exceptions, logger -from httprunner.compat import OrderedDict, is_py2, is_py3 +from httprunner.compat import OrderedDict, is_py2, is_py3, str from requests.structures import CaseInsensitiveDict SECRET_KEY = "DebugTalk" @@ -186,20 +186,28 @@ def query_json(json_content, query, delimiter='.'): } @param (str) query "person.name.first_name" => "Leo" + "person.name.first_name.0" => "L" "person.cities.0" => "Guangzhou" @return queried result """ + raise_flag = False + response_body = u"from: {}\n".format(json_content) try: for key in query.split(delimiter): - if isinstance(json_content, list): + if isinstance(json_content, (list, str)): json_content = json_content[int(key)] elif isinstance(json_content, dict): json_content = json_content[key] else: - raise exceptions.ParseResponseFailure( - "response content is in text format! failed to query key {}!".format(key)) + raise_flag = True except (KeyError, ValueError, IndexError): - raise exceptions.ParseResponseFailure("failed to query json when extracting response!") + raise_flag = True + + if raise_flag: + err_msg = u"ExtractFailure: Failed to extract! => {}\n".format(query) + err_msg += response_body + logger.log_error(err_msg) + raise exceptions.ExtractFailure(err_msg) return json_content diff --git a/tests/test_response.py b/tests/test_response.py index 44cf333d..4a9ff9ac 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -300,7 +300,7 @@ class TestResponse(ApiServerUnittest): ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exceptions.ParseResponseFailure): + with self.assertRaises(exceptions.ExtractFailure): resp_obj.extract_response(extract_binds_list) extract_binds_list = [ @@ -308,7 +308,7 @@ class TestResponse(ApiServerUnittest): ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exceptions.ParseResponseFailure): + with self.assertRaises(exceptions.ExtractFailure): resp_obj.extract_response(extract_binds_list) def test_extract_response_json_string(self): @@ -387,5 +387,5 @@ class TestResponse(ApiServerUnittest): {"resp_content_body": "content.data.def"} ] resp_obj = response.ResponseObject(resp) - with self.assertRaises(exceptions.ParseResponseFailure): + with self.assertRaises(exceptions.ExtractFailure): resp_obj.extract_response(extract_binds_list) diff --git a/tests/test_utils.py b/tests/test_utils.py index dbdbe553..7b6e24dc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -163,11 +163,11 @@ class TestUtils(ApiServerUnittest): self.assertEqual(result, 3) query = "ids.str_key" - with self.assertRaises(exceptions.ParseResponseFailure): + with self.assertRaises(exceptions.ExtractFailure): utils.query_json(json_content, query) query = "ids.5" - with self.assertRaises(exceptions.ParseResponseFailure): + with self.assertRaises(exceptions.ExtractFailure): utils.query_json(json_content, query) query = "person.age" @@ -175,7 +175,7 @@ class TestUtils(ApiServerUnittest): self.assertEqual(result, 29) query = "person.not_exist_key" - with self.assertRaises(exceptions.ParseResponseFailure): + with self.assertRaises(exceptions.ExtractFailure): utils.query_json(json_content, query) query = "person.cities.0" @@ -186,6 +186,10 @@ class TestUtils(ApiServerUnittest): result = utils.query_json(json_content, query) self.assertEqual(result, "Leo") + query = "person.name.first_name.0" + result = utils.query_json(json_content, query) + self.assertEqual(result, "L") + def test_get_uniform_comparator(self): self.assertEqual(utils.get_uniform_comparator("eq"), "equals") self.assertEqual(utils.get_uniform_comparator("=="), "equals") From 9aeb64c5a05105d283cddff6aa6dcd344d84b24b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 19:13:56 +0800 Subject: [PATCH 15/22] refactor: extract field --- httprunner/response.py | 43 +++++++++++++++++++----------------------- httprunner/utils.py | 2 +- tests/test_response.py | 6 +++--- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index cf59715b..76b3a9d6 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -44,11 +44,10 @@ class ResponseObject(object): """ matched = re.search(field, self.text) if not matched: - err_msg = u"Failed to extract data with regex!\n" + err_msg = u"ExtractFailure: Failed to extract data with regex! => {}\n".format(field) err_msg += u"response content: {}\n".format(self.content) - err_msg += u"regex: {}\n".format(field) logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) + raise exceptions.ExtractFailure(err_msg) return matched.group(1) @@ -91,10 +90,10 @@ class ResponseObject(object): try: return cookies[sub_query] except KeyError: - err_msg = u"ParamsError: Failed to extract cookie! => {}\n".format(field) + err_msg = u"ExtractFailure: Failed to extract cookie! => {}\n".format(field) err_msg += u"response cookies: {}\n".format(cookies) logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) + raise exceptions.ExtractFailure(err_msg) # elapsed elif top_query == "elapsed": @@ -124,10 +123,10 @@ class ResponseObject(object): try: return headers[sub_query] except KeyError: - err_msg = u"ParamsError: Failed to extract header! => {}\n".format(field) + err_msg = u"ExtractFailure: Failed to extract header! => {}\n".format(field) err_msg += u"response headers: {}\n".format(headers) logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) + raise exceptions.ExtractFailure(err_msg) # response body elif top_query in ["content", "text", "json"]: @@ -148,10 +147,10 @@ class ResponseObject(object): return utils.query_json(body, sub_query) else: # content = "abcdefg", content.xxx - err_msg = u"ParamsError: Failed to extract attribute from response body! => {}\n".format(field) + err_msg = u"ExtractFailure: Failed to extract attribute from response body! => {}\n".format(field) err_msg += u"response body: {}\n".format(body) logger.log_error(err_msg) - raise exceptions.ParamsError(err_msg) + raise exceptions.ExtractFailure(err_msg) # others else: @@ -163,21 +162,20 @@ class ResponseObject(object): def extract_field(self, field): """ extract value from requests.Response. """ + if not isinstance(field, basestring): + err_msg = u"ParamsError: Invalid extractor! => {}\n".format(field) + logger.log_error(err_msg) + raise exceptions.ParamsError(err_msg) + msg = "extract: {}".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) + 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: remove except here - except (TypeError): - logger.log_error("failed to extract field: {}".format(field)) - raise + msg += "\t=> {}".format(value) + logger.log_debug(msg) return value @@ -200,9 +198,6 @@ class ResponseObject(object): 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 exceptions.ParamsError("invalid extractors in testcase!") - extracted_variables_mapping[key] = self.extract_field(field) return extracted_variables_mapping diff --git a/httprunner/utils.py b/httprunner/utils.py index 40592dfb..6c4e5c99 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -191,7 +191,7 @@ def query_json(json_content, query, delimiter='.'): @return queried result """ raise_flag = False - response_body = u"from: {}\n".format(json_content) + response_body = u"response body: {}\n".format(json_content) try: for key in query.split(delimiter): if isinstance(json_content, (list, str)): diff --git a/tests/test_response.py b/tests/test_response.py index 4a9ff9ac..71d74583 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -101,7 +101,7 @@ class TestResponse(ApiServerUnittest): extract_binds_list = [ {"resp_cookies": "cookies.xx"} ] - with self.assertRaises(exceptions.ParamsError): + with self.assertRaises(exceptions.ExtractFailure): resp_obj.extract_response(extract_binds_list) def test_extract_response_elapsed(self): @@ -162,7 +162,7 @@ class TestResponse(ApiServerUnittest): extract_binds_list = [ {"resp_headers_xxx": "headers.xxx"} ] - with self.assertRaises(exceptions.ParamsError): + with self.assertRaises(exceptions.ExtractFailure): resp_obj.extract_response(extract_binds_list) def test_extract_response_body_json(self): @@ -265,7 +265,7 @@ class TestResponse(ApiServerUnittest): extract_binds_list = [ {"resp_content": "content.xxx"} ] - with self.assertRaises(exceptions.ParamsError): + with self.assertRaises(exceptions.ExtractFailure): resp_obj.extract_response(extract_binds_list) def test_extract_response_others(self): From aea172385d378e1dadd02bf0365dfbb59c688375 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 19:43:54 +0800 Subject: [PATCH 16/22] bugfix: check HTTP method before request --- httprunner/context.py | 1 + httprunner/runner.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/httprunner/context.py b/httprunner/context.py index 11d7736d..e4799c8c 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -17,6 +17,7 @@ class Context(object): self.testset_shared_variables_mapping = OrderedDict() self.testcase_variables_mapping = OrderedDict() self.testcase_parser = testcase.TestcaseParser() + self.evaluated_validators = [] self.init_context() def init_context(self, level='testset'): diff --git a/httprunner/runner.py b/httprunner/runner.py index ca7b49db..538143f9 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -156,6 +156,14 @@ class Runner(object): 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"ParamsError: 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)) From ccad55733f381808bb6d8cee2fb51e79332b1394 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 19:45:31 +0800 Subject: [PATCH 17/22] fix #26: distinguish failure and error --- httprunner/task.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/httprunner/task.py b/httprunner/task.py index d9b0f0ab..44004c8e 100644 --- a/httprunner/task.py +++ b/httprunner/task.py @@ -25,6 +25,8 @@ class TestCase(unittest.TestCase): """ try: self.test_runner.run_test(self.testcase_dict) + except exceptions.MyBaseFailure as ex: + self.fail(str(ex)) finally: if hasattr(self.test_runner.http_client_session, "meta_data"): self.meta_data = self.test_runner.http_client_session.meta_data From 22a084d3011a6848fe8ae96fdce591cd75c7b037 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 25 Jul 2018 23:53:25 +0800 Subject: [PATCH 18/22] bugfix: JSONDecodeError in Python3.4 --- httprunner/compat.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index e58ea033..ae88f4c9 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -8,7 +8,11 @@ This module handles import compatibility issues between Python 2 and Python 3. """ -import json +try: + import simplejson as json +except ImportError: + import json + import sys # ------- @@ -29,6 +33,11 @@ is_py3 = (_ver[0] == 3) # Specifics # --------- +try: + JSONDecodeError = json.JSONDecodeError +except AttributeError: + JSONDecodeError = ValueError + if is_py2: from urllib3.packages.ordered_dict import OrderedDict @@ -40,7 +49,6 @@ if is_py2: integer_types = (int, long) FileNotFoundError = IOError - JSONDecodeError = ValueError elif is_py3: from collections import OrderedDict @@ -53,4 +61,3 @@ elif is_py3: integer_types = (int,) FileNotFoundError = FileNotFoundError - JSONDecodeError = json.decoder.JSONDecodeError From 62bc08dde62c8f108e476f3e7b615e007ea90e6e Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 26 Jul 2018 00:09:41 +0800 Subject: [PATCH 19/22] update error message --- httprunner/response.py | 18 +++++++++--------- httprunner/runner.py | 4 ++-- httprunner/utils.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index 76b3a9d6..1447ae02 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -44,7 +44,7 @@ class ResponseObject(object): """ matched = re.search(field, self.text) if not matched: - err_msg = u"ExtractFailure: Failed to extract data with regex! => {}\n".format(field) + err_msg = u"Failed to extract data with regex! => {}\n".format(field) err_msg += u"response content: {}\n".format(self.content) logger.log_error(err_msg) raise exceptions.ExtractFailure(err_msg) @@ -74,7 +74,7 @@ class ResponseObject(object): if top_query in ["status_code", "encoding", "ok", "reason", "url"]: if sub_query: # status_code.XX - err_msg = u"ParamsError: {}\n".format(field) + err_msg = u"Failed to extract: {}\n".format(field) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) @@ -90,7 +90,7 @@ class ResponseObject(object): try: return cookies[sub_query] except KeyError: - err_msg = u"ExtractFailure: Failed to extract cookie! => {}\n".format(field) + err_msg = u"Failed to extract cookie! => {}\n".format(field) err_msg += u"response cookies: {}\n".format(cookies) logger.log_error(err_msg) raise exceptions.ExtractFailure(err_msg) @@ -99,7 +99,7 @@ class ResponseObject(object): elif top_query == "elapsed": available_attributes = u"available attributes: days, seconds, microseconds, total_seconds" if not sub_query: - err_msg = u"ParamsError: elapsed is datetime.timedelta instance, attribute should also be specified!\n" + err_msg = u"elapsed is datetime.timedelta instance, attribute should also be specified!\n" err_msg += available_attributes logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) @@ -108,7 +108,7 @@ class ResponseObject(object): elif sub_query == "total_seconds": return self.elapsed.total_seconds() else: - err_msg = "ParamsError: {} is not valid datetime.timedelta attribute.\n".format(sub_query) + err_msg = "{} is not valid datetime.timedelta attribute.\n".format(sub_query) err_msg += available_attributes logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) @@ -123,7 +123,7 @@ class ResponseObject(object): try: return headers[sub_query] except KeyError: - err_msg = u"ExtractFailure: Failed to extract header! => {}\n".format(field) + err_msg = u"Failed to extract header! => {}\n".format(field) err_msg += u"response headers: {}\n".format(headers) logger.log_error(err_msg) raise exceptions.ExtractFailure(err_msg) @@ -147,14 +147,14 @@ class ResponseObject(object): return utils.query_json(body, sub_query) else: # content = "abcdefg", content.xxx - err_msg = u"ExtractFailure: Failed to extract attribute from response body! => {}\n".format(field) + err_msg = u"Failed to extract attribute from response body! => {}\n".format(field) err_msg += u"response body: {}\n".format(body) logger.log_error(err_msg) raise exceptions.ExtractFailure(err_msg) # others else: - err_msg = u"ParamsError: Failed to extract attribute from response! => {}\n".format(field) + err_msg = u"Failed to extract attribute from response! => {}\n".format(field) err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, text, json, encoding, ok, reason, url." logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) @@ -163,7 +163,7 @@ class ResponseObject(object): """ extract value from requests.Response. """ if not isinstance(field, basestring): - err_msg = u"ParamsError: Invalid extractor! => {}\n".format(field) + err_msg = u"Invalid extractor! => {}\n".format(field) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) diff --git a/httprunner/runner.py b/httprunner/runner.py index 538143f9..12c31b13 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -159,8 +159,8 @@ class Runner(object): # 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"ParamsError: invalid HTTP method! => {}\n".format(method) - err_msg += "available HTTP methods: {}".format("/".join(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) diff --git a/httprunner/utils.py b/httprunner/utils.py index 6c4e5c99..04ad8825 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -204,7 +204,7 @@ def query_json(json_content, query, delimiter='.'): raise_flag = True if raise_flag: - err_msg = u"ExtractFailure: Failed to extract! => {}\n".format(query) + err_msg = u"Failed to extract! => {}\n".format(query) err_msg += response_body logger.log_error(err_msg) raise exceptions.ExtractFailure(err_msg) From e3d17d27d14b7006a11999e52d52c725d5d6c382 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 26 Jul 2018 11:46:46 +0800 Subject: [PATCH 20/22] bugfix: UnicodeDecodeError in Python2.7 --- httprunner/client.py | 2 +- httprunner/context.py | 4 ++++ httprunner/runner.py | 4 ++-- httprunner/task.py | 2 +- httprunner/testcase.py | 12 +++++++++--- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/httprunner/client.py b/httprunner/client.py index 0644b0bc..0f9e4d9a 100644 --- a/httprunner/client.py +++ b/httprunner/client.py @@ -115,7 +115,7 @@ class HttpSession(requests.Session): 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, value) + msg += "{:<16} : {}\n".format(key, repr(value)) logger.log_debug(msg) # record original request info diff --git a/httprunner/context.py b/httprunner/context.py index e4799c8c..59734b29 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -261,6 +261,10 @@ class Context(object): def validate(self, validators, resp_obj): """ make validations """ + if not validators: + return + + logger.log_info("start to validate.") self.evaluated_validators = [] validate_pass = True diff --git a/httprunner/runner.py b/httprunner/runner.py index 12c31b13..9f38f1a7 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -197,14 +197,14 @@ class Runner(object): 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) + 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 += "content: {}\n".format(resp_obj.content) + err_resp_msg += "content: {}\n".format(repr(resp_obj.text)) logger.log_error(err_resp_msg) raise diff --git a/httprunner/task.py b/httprunner/task.py index 44004c8e..437ae6d3 100644 --- a/httprunner/task.py +++ b/httprunner/task.py @@ -26,7 +26,7 @@ class TestCase(unittest.TestCase): try: self.test_runner.run_test(self.testcase_dict) except exceptions.MyBaseFailure as ex: - self.fail(str(ex)) + self.fail(repr(ex)) finally: if hasattr(self.test_runner.http_client_session, "meta_data"): self.meta_data = self.test_runner.http_client_session.meta_data diff --git a/httprunner/testcase.py b/httprunner/testcase.py index ac4fef16..b7a52a2e 100644 --- a/httprunner/testcase.py +++ b/httprunner/testcase.py @@ -10,7 +10,8 @@ import random import re from httprunner import exceptions, logger, utils -from httprunner.compat import OrderedDict, basestring, numeric_types +from httprunner.compat import (OrderedDict, basestring, builtin_str, + numeric_types, str) from httprunner.utils import FileUtils variable_regexp = r"\$([\w_]+)" @@ -627,7 +628,9 @@ def substitute_variables_with_mapping(content, mapping): # content is a variable content = value else: - content = content.replace(var, str(value)) + if not isinstance(value, str): + value = builtin_str(value) + content = content.replace(var, value) return content @@ -850,9 +853,12 @@ class TestcaseParser(object): content = variable_value else: # content contains one or several variables + if not isinstance(variable_value, str): + variable_value = builtin_str(variable_value) + content = content.replace( "${}".format(variable_name), - str(variable_value), 1 + variable_value, 1 ) return content From 7d7fbfbb7067ff9ea3df996ccfe35319c649f100 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 26 Jul 2018 12:00:47 +0800 Subject: [PATCH 21/22] update log --- httprunner/response.py | 2 +- httprunner/runner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/httprunner/response.py b/httprunner/response.py index 1447ae02..d562769a 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -45,7 +45,7 @@ class ResponseObject(object): matched = re.search(field, self.text) if not matched: err_msg = u"Failed to extract data with regex! => {}\n".format(field) - err_msg += u"response content: {}\n".format(self.content) + err_msg += u"response body: {}\n".format(self.text) logger.log_error(err_msg) raise exceptions.ExtractFailure(err_msg) diff --git a/httprunner/runner.py b/httprunner/runner.py index 9f38f1a7..0174bb9c 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -204,7 +204,7 @@ class Runner(object): 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(repr(resp_obj.text)) + err_resp_msg += "body: {}\n".format(repr(resp_obj.text)) logger.log_error(err_resp_msg) raise From e2363b1f2d739b3b8706caa19cc6b37c9cfb5f61 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 26 Jul 2018 12:17:57 +0800 Subject: [PATCH 22/22] bugfix: ExtractFailure in Python2.7 --- httprunner/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/httprunner/utils.py b/httprunner/utils.py index 04ad8825..edc98c73 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -17,7 +17,7 @@ from datetime import datetime import yaml from httprunner import exceptions, logger -from httprunner.compat import OrderedDict, is_py2, is_py3, str +from httprunner.compat import OrderedDict, basestring, is_py2, is_py3, str from requests.structures import CaseInsensitiveDict SECRET_KEY = "DebugTalk" @@ -172,7 +172,7 @@ class FileUtils(object): def query_json(json_content, query, delimiter='.'): """ Do an xpath-like query with json_content. - @param (dict/list) json_content + @param (dict/list/string) json_content json_content = { "ids": [1, 2, 3, 4], "person": { @@ -194,11 +194,13 @@ def query_json(json_content, query, delimiter='.'): response_body = u"response body: {}\n".format(json_content) try: for key in query.split(delimiter): - if isinstance(json_content, (list, str)): + if isinstance(json_content, (list, basestring)): json_content = json_content[int(key)] elif isinstance(json_content, dict): json_content = json_content[key] else: + logger.log_error( + "invalid type value: {}({})".format(json_content, type(json_content))) raise_flag = True except (KeyError, ValueError, IndexError): raise_flag = True