From 9a7bccb52f58a86fd411c39363813354934c55e4 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 3 Nov 2019 15:49:08 +0800 Subject: [PATCH 1/8] change: format code, two blank lines between function --- httprunner/built_in.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/httprunner/built_in.py b/httprunner/built_in.py index 821304ca..792fe638 100644 --- a/httprunner/built_in.py +++ b/httprunner/built_in.py @@ -30,6 +30,7 @@ def gen_random_string(str_len): return ''.join( random.choice(string.ascii_letters + string.digits) for _ in range(str_len)) + def get_timestamp(str_len=13): """ get timestamp string, length can only between 0 and 16 """ @@ -38,6 +39,7 @@ def get_timestamp(str_len=13): raise ParamsError("timestamp length can only between 0 and 16.") + def get_current_date(fmt="%Y-%m-%d"): """ get current date, default format is %Y-%m-%d """ @@ -66,6 +68,7 @@ def get_current_date(fmt="%Y-%m-%d"): def multipart_encoder(**kwargs): """ initialize MultipartEncoder with uploading fields. """ + def get_filetype(file_path): file_type = filetype.guess(file_path) if file_type: @@ -108,52 +111,66 @@ def multipart_content_type(multipart_encoder): def equals(check_value, expect_value): assert check_value == expect_value + def less_than(check_value, expect_value): assert check_value < expect_value + def less_than_or_equals(check_value, expect_value): assert check_value <= expect_value + def greater_than(check_value, expect_value): assert check_value > expect_value + def greater_than_or_equals(check_value, expect_value): assert check_value >= expect_value + def not_equals(check_value, expect_value): assert check_value != expect_value + def string_equals(check_value, expect_value): assert builtin_str(check_value) == builtin_str(expect_value) + def length_equals(check_value, expect_value): assert isinstance(expect_value, integer_types) assert len(check_value) == expect_value + def length_greater_than(check_value, expect_value): assert isinstance(expect_value, integer_types) assert len(check_value) > expect_value + def length_greater_than_or_equals(check_value, expect_value): assert isinstance(expect_value, integer_types) assert len(check_value) >= expect_value + def length_less_than(check_value, expect_value): assert isinstance(expect_value, integer_types) assert len(check_value) < expect_value + def length_less_than_or_equals(check_value, expect_value): assert isinstance(expect_value, integer_types) assert len(check_value) <= expect_value + def contains(check_value, expect_value): assert isinstance(check_value, (list, tuple, dict, basestring)) assert expect_value in check_value + def contained_by(check_value, expect_value): assert isinstance(expect_value, (list, tuple, dict, basestring)) assert check_value in expect_value + def type_match(check_value, expect_value): def get_type(name): if isinstance(name, type): @@ -168,19 +185,24 @@ def type_match(check_value, expect_value): assert isinstance(check_value, get_type(expect_value)) + def regex_match(check_value, expect_value): assert isinstance(expect_value, basestring) assert isinstance(check_value, basestring) assert re.match(expect_value, check_value) + def startswith(check_value, expect_value): assert builtin_str(check_value).startswith(builtin_str(expect_value)) + def endswith(check_value, expect_value): assert builtin_str(check_value).endswith(builtin_str(expect_value)) + """ built-in hooks """ + def sleep_N_secs(n_secs): """ sleep n seconds """ From 36ebe221f1db7c5ecf9ca8ff5827b03027c443d3 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 3 Nov 2019 15:54:59 +0800 Subject: [PATCH 2/8] change: rename builtin function, sleep_N_secs => sleep --- httprunner/built_in.py | 15 ++++++--------- tests/test_runner.py | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/httprunner/built_in.py b/httprunner/built_in.py index 792fe638..43524d3f 100644 --- a/httprunner/built_in.py +++ b/httprunner/built_in.py @@ -46,6 +46,12 @@ def get_current_date(fmt="%Y-%m-%d"): return datetime.datetime.now().strftime(fmt) +def sleep(n_secs): + """ sleep n seconds + """ + time.sleep(n_secs) + + ############################################################################### ## upload files with requests-toolbelt # e.g. @@ -198,12 +204,3 @@ def startswith(check_value, expect_value): def endswith(check_value, expect_value): assert builtin_str(check_value).endswith(builtin_str(expect_value)) - - -""" built-in hooks -""" - -def sleep_N_secs(n_secs): - """ sleep n seconds - """ - time.sleep(n_secs) diff --git a/tests/test_runner.py b/tests/test_runner.py index df8c734d..2bc6fc8b 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -52,11 +52,11 @@ class TestRunner(ApiServerUnittest): "name": "basic test with httpbin", "base_url": HTTPBIN_SERVER, "setup_hooks": [ - "${sleep_N_secs(0.5)}", + "${sleep(0.5)}", "${hook_print(setup)}" ], "teardown_hooks": [ - "${sleep_N_secs(1)}", + "${sleep(1)}", "${hook_print(teardown)}" ] }, From b04e21b81b74b77173a32b994c4728449bc07950 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 3 Nov 2019 16:06:09 +0800 Subject: [PATCH 3/8] doc: add FAQ, HTTPS SSLError #394 --- docs/FAQ.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/FAQ.md b/docs/FAQ.md index b43101d1..782cb020 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1 +1,14 @@ # 常见问题 + +## HTTPS SSLError + +请求 HTTPS 接口时,若本地开启了代理软件(Charles/Fiddler),由于 HTTPS 证书的原因,会导致 SSLError 的报错。 + +解决的方式是,在 config 中增加 `verify: False`,原理见 requests 的 [`SSL Cert Verification`](https://requests.kennethreitz.org/en/master/user/advanced/#ssl-cert-verification) 部分。 + +```yaml +config: + name: XXX + base_url: XXX + verify: False +``` From 57d627cc1753413286773cae656184b76dadb462 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 1 Dec 2019 15:49:10 +0800 Subject: [PATCH 4/8] change: code format --- httprunner/validator.py | 24 ++++++++++++------------ tests/test_validator.py | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/httprunner/validator.py b/httprunner/validator.py index 36f085a2..f8a4e4f2 100644 --- a/httprunner/validator.py +++ b/httprunner/validator.py @@ -159,13 +159,13 @@ def get_uniform_comparator(comparator): return "length_equals" elif comparator in ["len_gt", "count_gt", "length_greater_than", "count_greater_than"]: return "length_greater_than" - elif comparator in ["len_ge", "count_ge", "length_greater_than_or_equals", \ - "count_greater_than_or_equals"]: + elif comparator in ["len_ge", "count_ge", "length_greater_than_or_equals", + "count_greater_than_or_equals"]: return "length_greater_than_or_equals" elif comparator in ["len_lt", "count_lt", "length_less_than", "count_less_than"]: return "length_less_than" - elif comparator in ["len_le", "count_le", "length_less_than_or_equals", \ - "count_less_than_or_equals"]: + elif comparator in ["len_le", "count_le", "length_less_than_or_equals", + "count_less_than_or_equals"]: return "length_less_than_or_equals" else: return comparator @@ -237,14 +237,14 @@ def _convert_validators_to_mapping(validators): Examples: >>> validators = [ - {"check": "v1", "expect": 201, "comparator": "eq"}, - {"check": {"b": 1}, "expect": 200, "comparator": "eq"} - ] - >>> _convert_validators_to_mapping(validators) - { - ("v1", "eq"): {"check": "v1", "expect": 201, "comparator": "eq"}, - ('{"b": 1}', "eq"): {"check": {"b": 1}, "expect": 200, "comparator": "eq"} - } + {"check": "v1", "expect": 201, "comparator": "eq"}, + {"check": {"b": 1}, "expect": 200, "comparator": "eq"} + ] + >>> print(_convert_validators_to_mapping(validators)) + { + ("v1", "eq"): {"check": "v1", "expect": 201, "comparator": "eq"}, + ('{"b": 1}', "eq"): {"check": {"b": 1}, "expect": 200, "comparator": "eq"} + } """ validators_mapping = {} diff --git a/tests/test_validator.py b/tests/test_validator.py index 63a8cc5a..cdaf9563 100644 --- a/tests/test_validator.py +++ b/tests/test_validator.py @@ -117,7 +117,6 @@ class TestValidator(unittest.TestCase): {"check": "status_code", "comparator": "equals", "expect": 201} ) - def test_extend_validators(self): def_validators = [ {'eq': ['v1', 200]}, From eb357b806b4bdd2e3b9d9f6feaa44dd4a5c74c8a Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 4 Dec 2019 12:57:50 +0800 Subject: [PATCH 5/8] 2.3.3 fix #768: dump json file path error when folder name contains dot, such as a.b.c --- docs/CHANGELOG.md | 6 +++++ httprunner/__init__.py | 2 +- httprunner/api.py | 1 + httprunner/loader.py | 20 +++++++++------ httprunner/utils.py | 51 ++++++++++++++++++++++++------------- pyproject.toml | 2 +- tests/httpbin/a.b.c/rpc.yml | 10 ++++++++ tests/test_utils.py | 34 +++++++++++++++++++++++++ 8 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 tests/httpbin/a.b.c/rpc.yml diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9fcb2272..e387cb76 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## 2.3.3 (2019-12-04) + +**Fixed** + +- fix #768: dump json file path error when folder name contains dot, such as `a.b.c` + ## 2.3.2 (2019-11-01) **Added** diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 5b67afa6..4d5c880e 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.3.2" +__version__ = "2.3.3" __description__ = "One-stop solution for HTTP(S) testing." __all__ = ["__version__", "__description__"] diff --git a/httprunner/api.py b/httprunner/api.py index 8608a189..d8cf9941 100644 --- a/httprunner/api.py +++ b/httprunner/api.py @@ -257,6 +257,7 @@ class HttpRunner(object): """ # load tests self.exception_stage = "load tests" + path = loader.prepare_path(path) tests_mapping = loader.load_tests(path, dot_env_path) tests_mapping["project_mapping"]["test_path"] = path diff --git a/httprunner/loader.py b/httprunner/loader.py index b4735ad1..27bd9f56 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -816,6 +816,18 @@ def load_project_tests(test_path, dot_env_path=None): tests_def_mapping["PWD"] = project_working_directory +def prepare_path(path): + if not os.path.exists(path): + err_msg = "path not exist: {}".format(path) + logger.log_error(err_msg) + raise exceptions.FileNotFound(err_msg) + + if not os.path.isabs(path): + path = os.path.join(os.getcwd(), path) + + return path + + def load_tests(path, dot_env_path=None): """ load testcases from file path, extend and merge with api/testcase definitions. @@ -869,14 +881,6 @@ def load_tests(path, dot_env_path=None): } """ - if not os.path.exists(path): - err_msg = "path not exist: {}".format(path) - logger.log_error(err_msg) - raise exceptions.FileNotFound(err_msg) - - if not os.path.isabs(path): - path = os.path.join(os.getcwd(), path) - load_project_tests(path, dot_env_path) tests_mapping = { "project_mapping": project_mapping diff --git a/httprunner/utils.py b/httprunner/utils.py index 133329f3..5f24b396 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -607,7 +607,7 @@ def omit_long_data(body, omit_len=512): return omitted_body + appendix_str -def dump_json_file(json_data, pwd_dir_path, dump_file_name): +def dump_json_file(json_data, json_file_abs_path): """ dump json data to file """ class PythonObjectEncoder(json.JSONEncoder): @@ -617,14 +617,8 @@ def dump_json_file(json_data, pwd_dir_path, dump_file_name): except TypeError: return str(obj) - logs_dir_path = os.path.join(pwd_dir_path, "logs") - if not os.path.isdir(logs_dir_path): - os.makedirs(logs_dir_path) - - dump_file_path = os.path.join(logs_dir_path, dump_file_name) - try: - with io.open(dump_file_path, 'w', encoding='utf-8') as outfile: + with io.open(json_file_abs_path, 'w', encoding='utf-8') as outfile: if is_py2: outfile.write( unicode(json.dumps( @@ -645,23 +639,44 @@ def dump_json_file(json_data, pwd_dir_path, dump_file_name): cls=PythonObjectEncoder ) - msg = "dump file: {}".format(dump_file_path) + msg = "dump file: {}".format(json_file_abs_path) logger.color_print(msg, "BLUE") except TypeError as ex: - msg = "Failed to dump json file: {}\nReason: {}".format(dump_file_path, ex) + msg = "Failed to dump json file: {}\nReason: {}".format(json_file_abs_path, ex) logger.color_print(msg, "RED") -def _prepare_dump_info(project_mapping, tag_name): - """ prepare dump file info. +def prepare_dump_json_file_abs_path(project_mapping, tag_name): + """ prepare dump json file absolute path. """ - test_path = project_mapping.get("test_path") or "tests_mapping" pwd_dir_path = project_mapping.get("PWD") or os.getcwd() - file_name, file_suffix = os.path.splitext(os.path.basename(test_path.rstrip("/"))) - dump_file_name = "{}.{}.json".format(file_name, tag_name) + test_path = project_mapping.get("test_path") - return pwd_dir_path, dump_file_name + if not test_path: + # running passed in testcase/testsuite data structure + dump_file_name = "tests_mapping.{}.json".format(tag_name) + dumped_json_file_abs_path = os.path.join(pwd_dir_path, "logs", dump_file_name) + return dumped_json_file_abs_path + + # both test_path and pwd_dir_path are absolute path + logs_dir_path = os.path.join(pwd_dir_path, "logs") + test_path_relative_path = test_path[len(pwd_dir_path)+1:] + + if os.path.isdir(test_path): + file_foder_path = os.path.join(logs_dir_path, test_path_relative_path) + dump_file_name = "all.{}.json".format(tag_name) + else: + file_relative_folder_path, test_file = os.path.split(test_path_relative_path) + file_foder_path = os.path.join(logs_dir_path, file_relative_folder_path) + test_file_name, _file_suffix = os.path.splitext(test_file) + dump_file_name = "{}.{}.json".format(test_file_name, tag_name) + + if not os.path.isdir(file_foder_path): + os.makedirs(file_foder_path, exist_ok=True) + + dumped_json_file_abs_path = os.path.join(file_foder_path, dump_file_name) + return dumped_json_file_abs_path def dump_logs(json_data, project_mapping, tag_name): @@ -674,8 +689,8 @@ def dump_logs(json_data, project_mapping, tag_name): tag_name (str): tag name, loaded/parsed/summary """ - pwd_dir_path, dump_file_name = _prepare_dump_info(project_mapping, tag_name) - dump_json_file(json_data, pwd_dir_path, dump_file_name) + json_file_abs_path = prepare_dump_json_file_abs_path(project_mapping, tag_name) + dump_json_file(json_data, json_file_abs_path) def get_python2_retire_msg(): diff --git a/pyproject.toml b/pyproject.toml index 08c8742c..29385917 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "2.3.2" +version = "2.3.3" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" diff --git a/tests/httpbin/a.b.c/rpc.yml b/tests/httpbin/a.b.c/rpc.yml new file mode 100644 index 00000000..ef31c546 --- /dev/null +++ b/tests/httpbin/a.b.c/rpc.yml @@ -0,0 +1,10 @@ +name: rpc api +base_url: http://httpbin.org +variables: + expected_status_code: 200 +request: + url: /headers + method: GET +validate: + - eq: ["status_code", $expected_status_code] + - eq: [content.headers.Host, "httpbin.org"] diff --git a/tests/test_utils.py b/tests/test_utils.py index 5a32588f..7952a5ac 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -275,3 +275,37 @@ class TestUtils(ApiServerUnittest): "d": [4, 5] } utils.print_info(info_mapping) + + def test_prepare_dump_json_file_path_for_folder(self): + # hrun tests/httpbin/a.b.c/ --save-tests + project_working_directory = os.path.join(os.getcwd(), "tests") + project_mapping = { + "PWD": project_working_directory, + "test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c") + } + self.assertEqual( + utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"), + os.path.join(project_working_directory, "logs", "httpbin/a.b.c/all.loaded.json") + ) + + def test_prepare_dump_json_file_path_for_file(self): + # hrun tests/httpbin/a.b.c/rpc.yml --save-tests + project_working_directory = os.path.join(os.getcwd(), "tests") + project_mapping = { + "PWD": project_working_directory, + "test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c", "rpc.yml") + } + self.assertEqual( + utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"), + os.path.join(project_working_directory, "logs", "httpbin/a.b.c/rpc.loaded.json") + ) + + def test_prepare_dump_json_file_path_for_passed_testcase(self): + project_working_directory = os.path.join(os.getcwd(), "tests") + project_mapping = { + "PWD": project_working_directory + } + self.assertEqual( + utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"), + os.path.join(project_working_directory, "logs", "tests_mapping.loaded.json") + ) From 8a77b23c718e019d0fe38ed3f99c94c42f2add2e Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 4 Dec 2019 14:19:01 +0800 Subject: [PATCH 6/8] fix: fix load_tests for not exist path --- httprunner/api.py | 2 -- httprunner/loader.py | 26 ++++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/httprunner/api.py b/httprunner/api.py index d8cf9941..7eda3126 100644 --- a/httprunner/api.py +++ b/httprunner/api.py @@ -257,9 +257,7 @@ class HttpRunner(object): """ # load tests self.exception_stage = "load tests" - path = loader.prepare_path(path) tests_mapping = loader.load_tests(path, dot_env_path) - tests_mapping["project_mapping"]["test_path"] = path if mapping: tests_mapping["project_mapping"]["variables"] = mapping diff --git a/httprunner/loader.py b/httprunner/loader.py index 27bd9f56..191011e5 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -779,6 +779,19 @@ def load_project_tests(test_path, dot_env_path=None): environments and debugtalk.py functions. """ + + def prepare_path(path): + if not os.path.exists(path): + err_msg = "path not exist: {}".format(path) + logger.log_error(err_msg) + raise exceptions.FileNotFound(err_msg) + + if not os.path.isabs(path): + path = os.path.join(os.getcwd(), path) + + return path + + test_path = prepare_path(test_path) # locate debugtalk.py file debugtalk_path = locate_debugtalk_py(test_path) @@ -810,24 +823,13 @@ def load_project_tests(test_path, dot_env_path=None): project_mapping["PWD"] = project_working_directory built_in.PWD = project_working_directory project_mapping["functions"] = debugtalk_functions + project_mapping["test_path"] = test_path # load api tests_def_mapping["api"] = load_api_folder(os.path.join(project_working_directory, "api")) tests_def_mapping["PWD"] = project_working_directory -def prepare_path(path): - if not os.path.exists(path): - err_msg = "path not exist: {}".format(path) - logger.log_error(err_msg) - raise exceptions.FileNotFound(err_msg) - - if not os.path.isabs(path): - path = os.path.join(os.getcwd(), path) - - return path - - def load_tests(path, dot_env_path=None): """ load testcases from file path, extend and merge with api/testcase definitions. From 25fdc8660af65db7b2db68a396103438a0134d60 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 4 Dec 2019 14:31:02 +0800 Subject: [PATCH 7/8] fix compatibility for Python 2.7, makedirs exsit_ok --- httprunner/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httprunner/utils.py b/httprunner/utils.py index 5f24b396..928b4882 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -673,7 +673,7 @@ def prepare_dump_json_file_abs_path(project_mapping, tag_name): dump_file_name = "{}.{}.json".format(test_file_name, tag_name) if not os.path.isdir(file_foder_path): - os.makedirs(file_foder_path, exist_ok=True) + os.makedirs(file_foder_path) dumped_json_file_abs_path = os.path.join(file_foder_path, dump_file_name) return dumped_json_file_abs_path From e5cb2ab4fed199c143c49a9a86d7b25e3aec07d0 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 4 Dec 2019 14:37:00 +0800 Subject: [PATCH 8/8] doc: update changelog --- docs/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e387cb76..5a937ad0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,10 @@ - fix #768: dump json file path error when folder name contains dot, such as `a.b.c` +**Changed** + +- change: rename builtin function, sleep_N_secs => sleep + ## 2.3.2 (2019-11-01) **Added**