From c38630205279c697f7478987a9ac94d71c393e73 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 25 Mar 2022 16:50:45 +0800 Subject: [PATCH 01/13] change: remove locust for python httprunner --- httprunner/ext/locust/__init__.py | 113 ---- httprunner/ext/locust/locustfile.py | 30 -- poetry.lock | 776 ++-------------------------- pyproject.toml | 3 - 4 files changed, 30 insertions(+), 892 deletions(-) delete mode 100644 httprunner/ext/locust/__init__.py delete mode 100644 httprunner/ext/locust/locustfile.py diff --git a/httprunner/ext/locust/__init__.py b/httprunner/ext/locust/__init__.py deleted file mode 100644 index eb4f4c69..00000000 --- a/httprunner/ext/locust/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -import sys - -if "locust" in sys.argv[0]: - try: - # monkey patch all at beginning to avoid RecursionError when running locust. - # `from gevent import monkey; monkey.patch_all()` will be triggered when importing locust - from locust import main as locust_main - - print("NOTICE: gevent monkey patches have been applied !!!") - except ImportError: - msg = """ -Locust is not installed, install first and try again. -install with pip: -$ pip install locust -""" - print(msg) - sys.exit(1) - -import importlib.util -import inspect -import os -from typing import List - -from loguru import logger - - -""" converted pytest files from YAML/JSON testcases -""" -pytest_files: List = [] - - -def is_httprunner_testcase(item): - """ check if a variable is a HttpRunner testcase class - """ - from httprunner import HttpRunner - - # TODO: skip referenced testcase - return bool( - inspect.isclass(item) - and issubclass(item, HttpRunner) - and item.__name__ != "HttpRunner" - ) - - -def prepare_locust_tests() -> List: - """ prepare locust testcases - - Returns: - list: testcase class list - """ - - locust_tests = [] - - for pytest_file in pytest_files: - spec = importlib.util.spec_from_file_location("module.name", pytest_file) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - for name, item in vars(module).items(): - - if not is_httprunner_testcase(item): - continue - - for _ in range(item.config.weight): - locust_tests.append(item) - - return locust_tests - - -def main_locusts(): - """ locusts entrance - """ - from httprunner.utils import ga_client - ga_client.track_event("RunLoadTests", "locust") - - # avoid print too much log details in console - logger.remove() - logger.add(sys.stderr, level="WARNING") - - sys.argv[0] = "locust" - if len(sys.argv) == 1: - sys.argv.extend(["-h"]) - - if sys.argv[1] in ["-h", "--help", "-V", "--version"]: - locust_main.main() - - def get_arg_index(*target_args): - for arg in target_args: - if arg not in sys.argv: - continue - - return sys.argv.index(arg) + 1 - - return None - - # get testcase file path - testcase_index = get_arg_index("-f", "--locustfile") - if not testcase_index: - print("Testcase file is not specified, exit 1.") - sys.exit(1) - - from httprunner.make import main_make - - global pytest_files - testcase_file_path = sys.argv[testcase_index] - pytest_files = main_make([testcase_file_path]) - if not pytest_files: - print("No valid testcases found, exit 1.") - sys.exit(1) - - sys.argv[testcase_index] = os.path.join(os.path.dirname(__file__), "locustfile.py") - - locust_main.main() diff --git a/httprunner/ext/locust/locustfile.py b/httprunner/ext/locust/locustfile.py deleted file mode 100644 index 77279db1..00000000 --- a/httprunner/ext/locust/locustfile.py +++ /dev/null @@ -1,30 +0,0 @@ -import random - -from locust import task, HttpUser, between - -from httprunner.ext.locust import prepare_locust_tests - - -class HttpRunnerUser(HttpUser): - host = "" - wait_time = between(5, 15) - - def on_start(self): - locust_tests = prepare_locust_tests() - self.testcase_runners = [ - testcase().with_session(self.client) for testcase in locust_tests - ] - - @task - def test_any(self): - test_runner = random.choice(self.testcase_runners) - try: - test_runner.run() - except Exception as ex: - self.environment.events.request_failure.fire( - request_type="Failed", - name=test_runner.config.name, - response_time=0, - response_length=0, - exception=ex, - ) diff --git a/poetry.lock b/poetry.lock index 611d2123..14395b78 100644 --- a/poetry.lock +++ b/poetry.lock @@ -172,22 +172,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "cffi" -version = "1.15.0" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = true -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "charset-normalizer" version = "2.0.12" @@ -206,11 +190,15 @@ reference = "tsinghua" [[package]] name = "click" -version = "7.1.2" +version = "8.0.4" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.source] type = "legacy" @@ -230,23 +218,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "configargparse" -version = "1.5.3" -description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -test = ["mock", "pyyaml", "pytest"] -yaml = ["pyyaml"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "coverage" version = "4.5.4" @@ -296,131 +267,16 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "flask" -version = "1.1.2" -description = "A simple framework for building complex web applications." -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -click = ">=5.1" -itsdangerous = ">=0.24" -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" - -[package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -dotenv = ["python-dotenv"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "flask-basicauth" -version = "0.2.0" -description = "HTTP basic access authentication for Flask." -category = "main" -optional = true -python-versions = "*" - -[package.dependencies] -Flask = "*" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "flask-cors" -version = "3.0.10" -description = "A Flask extension adding a decorator for CORS support" -category = "main" -optional = true -python-versions = "*" - -[package.dependencies] -Flask = ">=0.9" -Six = "*" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "gevent" -version = "21.12.0" -description = "Coroutine-based network library" -category = "main" -optional = true -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" - -[package.dependencies] -cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=1.1.0,<2.0", markers = "platform_python_implementation == \"CPython\""} -"zope.event" = "*" -"zope.interface" = "*" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] -docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0)"] -recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "selectors2", "backports.socketpair", "psutil (>=5.7.0)"] -test = ["requests", "objgraph", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "geventhttpclient" -version = "1.5.3" -description = "http client library for gevent" -category = "main" -optional = true -python-versions = "*" - -[package.dependencies] -brotli = "*" -certifi = "*" -gevent = ">=0.13" -six = "*" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "greenlet" -version = "1.1.2" -description = "Lightweight in-process concurrent programming" -category = "main" -optional = true -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" - -[package.extras] -docs = ["sphinx"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "h11" -version = "0.9.0" +version = "0.13.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.source] type = "legacy" @@ -475,26 +331,13 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "itsdangerous" -version = "2.1.1" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = true -python-versions = ">=3.7" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "jinja2" -version = "3.0.3" +version = "3.1.0" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] MarkupSafe = ">=2.0" @@ -520,33 +363,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "locust" -version = "1.6.0" -description = "Developer friendly load testing framework" -category = "main" -optional = true -python-versions = ">=3.6" - -[package.dependencies] -ConfigArgParse = ">=1.0" -flask = "1.1.2" -Flask-BasicAuth = ">=0.2.0" -Flask-Cors = ">=3.0.10" -gevent = ">=20.9.0" -geventhttpclient = ">=1.4.4" -msgpack = ">=0.6.2" -psutil = ">=5.6.7" -pywin32 = {version = "*", markers = "sys_platform == \"win32\""} -pyzmq = ">=16.0.2" -requests = ">=2.9.1" -Werkzeug = ">=1.0.1" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "loguru" version = "0.4.1" @@ -580,19 +396,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "msgpack" -version = "1.0.3" -description = "MessagePack (de)serializer." -category = "main" -optional = true -python-versions = "*" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "packaging" version = "21.3" @@ -624,33 +427,18 @@ reference = "tsinghua" [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "psutil" -version = "5.9.0" -description = "Cross-platform lib for process and system monitoring in Python." -category = "main" -optional = true -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] +testing = ["pytest", "pytest-benchmark"] [package.source] type = "legacy" @@ -670,19 +458,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "pydantic" version = "1.8.2" @@ -765,27 +540,14 @@ reference = "tsinghua" [[package]] name = "pytest-metadata" -version = "1.11.0" +version = "2.0.0" description = "pytest plugin for test session metadata" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7,<4.0" [package.dependencies] -pytest = ">=2.9.0" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "pywin32" -version = "303" -description = "Python for Window Extensions" -category = "main" -optional = true -python-versions = "*" +pytest = ">=7.1.1,<8.0.0" [package.source] type = "legacy" @@ -805,23 +567,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "pyzmq" -version = "22.3.0" -description = "Python bindings for 0MQ" -category = "main" -optional = true -python-versions = ">=3.6" - -[package.dependencies] -cffi = {version = "*", markers = "implementation_name == \"pypy\""} -py = {version = "*", markers = "implementation_name == \"pypy\""} - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "regex" version = "2022.3.15" @@ -1043,22 +788,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "werkzeug" -version = "2.0.3" -description = "The comprehensive WSGI web application library." -category = "main" -optional = true -python-versions = ">=3.6" - -[package.extras] -watchdog = ["watchdog"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "win32-setctime" version = "1.1.0" @@ -1092,50 +821,14 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "zope.event" -version = "4.5.0" -description = "Very basic event publishing system" -category = "main" -optional = true -python-versions = "*" - -[package.extras] -docs = ["sphinx"] -test = ["zope.testrunner"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "zope.interface" -version = "5.4.0" -description = "Interfaces for Python" -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -docs = ["sphinx", "repoze.sphinx.autointerface"] -test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] -testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [extras] allure = ["allure-pytest"] -locust = ["locust"] upload = ["requests-toolbelt", "filetype"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "32778bf7f65f9a923dbb14acc6d0818863787fc70266b78f61bc03738181f47d" +content-hash = "484e6bfffc0e5b3b998b150e7bf0ced340577a216d56576a9b702baff511fe2c" [metadata.files] allure-pytest = [ @@ -1223,74 +916,18 @@ certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] -cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, -] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] -configargparse = [ - {file = "ConfigArgParse-1.5.3-py3-none-any.whl", hash = "sha256:18f6535a2db9f6e02bd5626cc7455eac3e96b9ab3d969d366f9aafd5c5c00fe7"}, - {file = "ConfigArgParse-1.5.3.tar.gz", hash = "sha256:1b0b3cbf664ab59dada57123c81eff3d9737e0d11d8cf79e3d6eb10823f1739f"}, -] coverage = [ {file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"}, {file = "coverage-4.5.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c"}, @@ -1333,156 +970,9 @@ filetype = [ {file = "filetype-1.0.10-py2.py3-none-any.whl", hash = "sha256:63fbe6e818a3d1cfac1d62b196574a7a4b7fc8e06a6c500d53577c018ef127d9"}, {file = "filetype-1.0.10.tar.gz", hash = "sha256:323a13500731b6c65a253bc3930bbce9a56dfba71e90b60ffd968ab69d9ae937"}, ] -flask = [ - {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, - {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, -] -flask-basicauth = [ - {file = "Flask-BasicAuth-0.2.0.tar.gz", hash = "sha256:df5ebd489dc0914c224419da059d991eb72988a01cdd4b956d52932ce7d501ff"}, -] -flask-cors = [ - {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, - {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, -] -gevent = [ - {file = "gevent-21.12.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:2afa3f3ad528155433f6ac8bd64fa5cc303855b97004416ec719a6b1ca179481"}, - {file = "gevent-21.12.0-cp27-cp27m-win32.whl", hash = "sha256:177f93a3a90f46a5009e0841fef561601e5c637ba4332ab8572edd96af650101"}, - {file = "gevent-21.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:a5ad4ed8afa0a71e1927623589f06a9b5e8b5e77810be3125cb4d93050d3fd1f"}, - {file = "gevent-21.12.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:eae3c46f9484eaacd67ffcdf4eaf6ca830f587edd543613b0f5c4eb3c11d052d"}, - {file = "gevent-21.12.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1899b921219fc8959ff9afb94dae36be82e0769ed13d330a393594d478a0b3a"}, - {file = "gevent-21.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21cb5c9f4e14d75b3fe0b143ec875d7dbd1495fad6d49704b00e57e781ee0f"}, - {file = "gevent-21.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:542ae891e2aa217d2cf6d8446538fcd2f3263a40eec123b970b899bac391c47a"}, - {file = "gevent-21.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0082d8a5d23c35812ce0e716a91ede597f6dd2c5ff508a02a998f73598c59397"}, - {file = "gevent-21.12.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da8d2d51a49b2a5beb02ad619ca9ddbef806ef4870ba04e5ac7b8b41a5b61db3"}, - {file = "gevent-21.12.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfff82f05f14b7f5d9ed53ccb7a609ae8604df522bb05c971bca78ec9d8b2b9"}, - {file = "gevent-21.12.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7909780f0cf18a1fc32aafd8c8e130cdd93c6e285b11263f7f2d1a0f3678bc50"}, - {file = "gevent-21.12.0-cp36-cp36m-win32.whl", hash = "sha256:bb5cb8db753469c7a9a0b8a972d2660fe851aa06eee699a1ca42988afb0aaa02"}, - {file = "gevent-21.12.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c43f081cbca41d27fd8fef9c6a32cf83cb979345b20abc07bf68df165cdadb24"}, - {file = "gevent-21.12.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:74fc1ef16b86616cfddcc74f7292642b0f72dde4dd95aebf4c45bb236744be54"}, - {file = "gevent-21.12.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc2fef0f98ee180704cf95ec84f2bc2d86c6c3711bb6b6740d74e0afe708b62c"}, - {file = "gevent-21.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08b4c17064e28f4eb85604486abc89f442c7407d2aed249cf54544ce5c9baee6"}, - {file = "gevent-21.12.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:973749bacb7bc4f4181a8fb2a7e0e2ff44038de56d08e856dd54a5ac1d7331b4"}, - {file = "gevent-21.12.0-cp37-cp37m-win32.whl", hash = "sha256:6a02a88723ed3f0fd92cbf1df3c4cd2fbd87d82b0a4bac3e36a8875923115214"}, - {file = "gevent-21.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f289fae643a3f1c3b909d6b033e6921b05234a4907e9c9c8c3f1fe403e6ac452"}, - {file = "gevent-21.12.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3baeeccc4791ba3f8db27179dff11855a8f9210ddd754f6c9b48e0d2561c2aea"}, - {file = "gevent-21.12.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05c5e8a50cd6868dd36536c92fb4468d18090e801bd63611593c0717bab63692"}, - {file = "gevent-21.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d86438ede1cbe0fde6ef4cc3f72bf2f1ecc9630d8b633ff344a3aeeca272cdd"}, - {file = "gevent-21.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01928770972181ad8866ee37ea3504f1824587b188fcab782ef1619ce7538766"}, - {file = "gevent-21.12.0-cp38-cp38-win32.whl", hash = "sha256:3c012c73e6c61f13c75e3a4869dbe6a2ffa025f103421a6de9c85e627e7477b1"}, - {file = "gevent-21.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:b7709c64afa8bb3000c28bb91ec42c79594a7cb0f322e20427d57f9762366a5b"}, - {file = "gevent-21.12.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ec21f9eaaa6a7b1e62da786132d6788675b314f25f98d9541f1bf00584ed4749"}, - {file = "gevent-21.12.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ce1f38fdfe2149ffe8ec2131ca45281791c1e464db34b3b4321ae9d8d2efbb"}, - {file = "gevent-21.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ccffcf708094564e442ac6fde46f0ae9e40015cb69d995f4b39cc29a7643881"}, - {file = "gevent-21.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24d3550fbaeef5fddd794819c2853bca45a86c3d64a056a2c268d981518220d1"}, - {file = "gevent-21.12.0-cp39-cp39-win32.whl", hash = "sha256:2bcec9f80196c751fdcf389ca9f7141e7b0db960d8465ed79be5e685bfcad682"}, - {file = "gevent-21.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:3dad62f55fad839d498c801e139481348991cee6e1c7706041b5fe096cb6a279"}, - {file = "gevent-21.12.0-pp27-pypy_73-win_amd64.whl", hash = "sha256:9f9652d1e4062d4b5b5a0a49ff679fa890430b5f76969d35dccb2df114c55e0f"}, - {file = "gevent-21.12.0.tar.gz", hash = "sha256:f48b64578c367b91fa793bf8eaaaf4995cb93c8bc45860e473bf868070ad094e"}, -] -geventhttpclient = [ - {file = "geventhttpclient-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9dbb29de564deb0d76464b9fd16c853f19f4210bf9e162f6f38c712e83d1f8cb"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:01c9f59dc508a82378ca132e4a6fd1717e869aa590dbc8e7e492986a085a72b8"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e001608847d06cc0e5c3c0a619c5c5313bb1f9c6e6e4bccd3e78572f8fb9ebb"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc2164ca08f170d996c8862b43787b31950496f9a58da167679c332d231d8ea"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:31a650e38c9bc9d96b66d574af7cca4206244ab5e2daad6209420c63fbdc2f83"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f0c9a31cc52cf2730527875bc06bde65b983c45f14d7692afeebad18456adc9"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-win32.whl", hash = "sha256:8bc0e28d1cc5d9c10909e4c646f8e652594cadb94fa475af5f3e4d8499ed5bb9"}, - {file = "geventhttpclient-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:6a32a11a3ee1e475957d15da0fff93d7cf135b54a5ea92307236a6ac2ac0d863"}, - {file = "geventhttpclient-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3ca71445decdc90f28f2e43212da51acb6129139bb465324737a5779c9fbada1"}, - {file = "geventhttpclient-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845800cb2f544ca835e764dfbaade57eba35a35a57e3fbc6676932d6f3996b9c"}, - {file = "geventhttpclient-1.5.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:76069c60cf24719fc1395ed2c1b8b8b2b041080fea2501aca24fa2a3625e6bf6"}, - {file = "geventhttpclient-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c2dcb26b92a296dff6c5cc2446b66ac7361ea058b9f0a14dcdf080a5215dc412"}, - {file = "geventhttpclient-1.5.3-cp36-cp36m-win32.whl", hash = "sha256:8527f715d71d6ac743072f2674d2286517577b712dbda153b0f15c3b0be58d2b"}, - {file = "geventhttpclient-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:0b934a7f30f71fca5c3a0a52b258a49d1d0efb9b195a0c0568ebbed891c91d7e"}, - {file = "geventhttpclient-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa290a202446630cc71593712bea548f296b89cb8a4161002c2e03afc3b2ffed"}, - {file = "geventhttpclient-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7153bf3ead545cbc220cd032038bb543073496f8c41c18ec021fad47f8eda164"}, - {file = "geventhttpclient-1.5.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:943dba14695c6ee2b223453681409d7145a252f4e5728b4084d46cf5a0818bc3"}, - {file = "geventhttpclient-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3f975acefdd2e6ed531cc851e9f86bbad0f4216ac1926c4cdb5a8223a20538f4"}, - {file = "geventhttpclient-1.5.3-cp37-cp37m-win32.whl", hash = "sha256:182c0c0e5c00d1ebf780ce1710de558afa4cdf043868238c7a22e136359ff103"}, - {file = "geventhttpclient-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:43dde1d98a194dc27bd9e38df62371d0f5d25a8e5f1dfb09dc1cd2fae5b492fb"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d241e0ea4a1cc27547021ef7b93e7899734343d25f9b1912599af180336b3289"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d14aac14c46a4c9b1cc938aaefefa1eef4d9eb0b70ac32869354cff085295b6"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3dbc316d0e9367626f108d86d14b5e882ef9783c381a684682dc849a8e0c2c9f"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cb141fdeae74f2e078e06eea456809f289f34c1cfb471dae0b3e0afd01b77b"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7237c2ab19e1952598e1a75c01b69595440728bb570dbe528f9805c89b1d3970"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:421a17dab9d17d6357250a8689f8a2b9c3d46177b2b04b26a6f19dc8df134c12"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-win32.whl", hash = "sha256:f0734dae0672a1d6b0bc4117d68c7d043e182583d1437bcdf229dd44f7d038ce"}, - {file = "geventhttpclient-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:073fa8bef9745b3287979148bc5ca688780198b6bbb04d6fade18a1c54544ccc"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:86057e189cafa5a28dfb02b05702930fa5767b265103c34dad38721f9100d76c"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b0dd0c06f8e22f369b35b5f572b7053c3ee5f0f70843fa2137c387d5c07cca29"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:727640fca6ede582aacb528ebceacb7b2bf66ab9686fb9900ea7db2c951a747e"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49a42d0043840436d742fb227fae5135eb3535b740187c55d9cefa13508d15b5"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f1562957ddf70691c737c8fc6b44aa6720882a74feae3e0108a985a04139bb41"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:33d414082ff1f8e00a5396c17a6e8876c7f85ca50c08cb61df305a13757d7bd9"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-win32.whl", hash = "sha256:3c67a1800ba975e8a1c3778c01d53b62e87c50a6a88345a2675d0ccdebf16d61"}, - {file = "geventhttpclient-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:f5cef10107fed1fa6d802c4dea4cfc7fb604ff1c9edbe747084b4089a4d1db3a"}, - {file = "geventhttpclient-1.5.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c4f3e3c7bff985ed157388c33f170f19599235b8019e583670f9d9edb8ba6e67"}, - {file = "geventhttpclient-1.5.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbeb1ed7228fc406229c8693f8a02b8e064f7a9979665ac0181c983667cc383c"}, - {file = "geventhttpclient-1.5.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1b0c15e4da83b724c812e76e79eb85f230a194c1fc88d1f4d47ded32b31a84bb"}, - {file = "geventhttpclient-1.5.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32401a3d018e2e71a05a6427cc3b44fa9361678c6d6162c50ebe03e3af974aa0"}, - {file = "geventhttpclient-1.5.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:2bb160ca3a6d53f9ea9bfd1aa901c5a03d39a6f9a3a802de734a8447ca29df9c"}, - {file = "geventhttpclient-1.5.3.tar.gz", hash = "sha256:d80ec9ff42b7219f33558185499d0b4365597fc55ff886207b45f5632e099780"}, -] -greenlet = [ - {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, - {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, - {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, - {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, - {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, - {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, - {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, - {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, - {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, - {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, - {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, - {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, - {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, - {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, - {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, - {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, - {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, - {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, - {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, - {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, - {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, - {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, - {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, - {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, - {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, - {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, - {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, - {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, - {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, - {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, - {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, - {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, - {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, - {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, - {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, -] h11 = [ - {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, - {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, + {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, + {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -1496,22 +986,14 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -itsdangerous = [ - {file = "itsdangerous-2.1.1-py3-none-any.whl", hash = "sha256:935642cd4b987cdbee7210080004033af76306757ff8b4c0a506a4b6e06f02cf"}, - {file = "itsdangerous-2.1.1.tar.gz", hash = "sha256:7b7d3023cd35d9cb0c1fd91392f8c95c6fa02c59bf8ad64b8849be3401b95afb"}, -] jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, + {file = "Jinja2-3.1.0-py3-none-any.whl", hash = "sha256:da424924c069a4013730d8dd010cbecac7e7bb752be388db3741688bffb48dc6"}, + {file = "Jinja2-3.1.0.tar.gz", hash = "sha256:a2f09a92f358b96b5f6ca6ecb4502669c4acb55d8733bbb2b2c9c4af5564c605"}, ] jmespath = [ {file = "jmespath-0.9.5-py2.py3-none-any.whl", hash = "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec"}, {file = "jmespath-0.9.5.tar.gz", hash = "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9"}, ] -locust = [ - {file = "locust-1.6.0-py3-none-any.whl", hash = "sha256:28e4846d1ac14b88a4f29641d7306108dc652964331bdb57cd2991a6140b0515"}, - {file = "locust-1.6.0.tar.gz", hash = "sha256:95715ed82173d77a338bd91af01814ad2a199ed076880a720c3bc95462b796b3"}, -] loguru = [ {file = "loguru-0.4.1-py3-none-any.whl", hash = "sha256:074b3caa6748452c1e4f2b302093c94b65d5a4c5a4d7743636b4121e06437b0e"}, {file = "loguru-0.4.1.tar.gz", hash = "sha256:a6101fd435ac89ba5205a105a26a6ede9e4ddbb4408a6e167852efca47806d11"}, @@ -1558,42 +1040,6 @@ markupsafe = [ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] -msgpack = [ - {file = "msgpack-1.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079"}, - {file = "msgpack-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3"}, - {file = "msgpack-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73"}, - {file = "msgpack-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147"}, - {file = "msgpack-1.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39"}, - {file = "msgpack-1.0.3-cp310-cp310-win32.whl", hash = "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85"}, - {file = "msgpack-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7"}, - {file = "msgpack-1.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d"}, - {file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b"}, - {file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec"}, - {file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770"}, - {file = "msgpack-1.0.3-cp36-cp36m-win32.whl", hash = "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9"}, - {file = "msgpack-1.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a"}, - {file = "msgpack-1.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a"}, - {file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc"}, - {file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a"}, - {file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920"}, - {file = "msgpack-1.0.3-cp37-cp37m-win32.whl", hash = "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50"}, - {file = "msgpack-1.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba"}, - {file = "msgpack-1.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea"}, - {file = "msgpack-1.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a"}, - {file = "msgpack-1.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef"}, - {file = "msgpack-1.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611"}, - {file = "msgpack-1.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1"}, - {file = "msgpack-1.0.3-cp38-cp38-win32.whl", hash = "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4"}, - {file = "msgpack-1.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a"}, - {file = "msgpack-1.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3"}, - {file = "msgpack-1.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52"}, - {file = "msgpack-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2"}, - {file = "msgpack-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996"}, - {file = "msgpack-1.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2"}, - {file = "msgpack-1.0.3-cp39-cp39-win32.whl", hash = "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88"}, - {file = "msgpack-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d"}, - {file = "msgpack-1.0.3.tar.gz", hash = "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e"}, -] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1603,51 +1049,13 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] -psutil = [ - {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, - {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, - {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"}, - {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"}, - {file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"}, - {file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"}, - {file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"}, - {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"}, - {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"}, - {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"}, - {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"}, - {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"}, - {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"}, - {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"}, - {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"}, - {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"}, - {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"}, - {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"}, - {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"}, - {file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"}, - {file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"}, - {file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"}, - {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"}, - {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"}, - {file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"}, - {file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"}, - {file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"}, - {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"}, - {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"}, - {file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"}, - {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, - {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] pydantic = [ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, @@ -1685,22 +1093,8 @@ pytest-html = [ {file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"}, ] pytest-metadata = [ - {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, - {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, -] -pywin32 = [ - {file = "pywin32-303-cp310-cp310-win32.whl", hash = "sha256:6fed4af057039f309263fd3285d7b8042d41507343cd5fa781d98fcc5b90e8bb"}, - {file = "pywin32-303-cp310-cp310-win_amd64.whl", hash = "sha256:51cb52c5ec6709f96c3f26e7795b0bf169ee0d8395b2c1d7eb2c029a5008ed51"}, - {file = "pywin32-303-cp311-cp311-win32.whl", hash = "sha256:d9b5d87ca944eb3aa4cd45516203ead4b37ab06b8b777c54aedc35975dec0dee"}, - {file = "pywin32-303-cp311-cp311-win_amd64.whl", hash = "sha256:fcf44032f5b14fcda86028cdf49b6ebdaea091230eb0a757282aa656e4732439"}, - {file = "pywin32-303-cp36-cp36m-win32.whl", hash = "sha256:aad484d52ec58008ca36bd4ad14a71d7dd0a99db1a4ca71072213f63bf49c7d9"}, - {file = "pywin32-303-cp36-cp36m-win_amd64.whl", hash = "sha256:2a09632916b6bb231ba49983fe989f2f625cea237219530e81a69239cd0c4559"}, - {file = "pywin32-303-cp37-cp37m-win32.whl", hash = "sha256:b1675d82bcf6dbc96363fca747bac8bff6f6e4a447a4287ac652aa4b9adc796e"}, - {file = "pywin32-303-cp37-cp37m-win_amd64.whl", hash = "sha256:c268040769b48a13367221fced6d4232ed52f044ffafeda247bd9d2c6bdc29ca"}, - {file = "pywin32-303-cp38-cp38-win32.whl", hash = "sha256:5f9ec054f5a46a0f4dfd72af2ce1372f3d5a6e4052af20b858aa7df2df7d355b"}, - {file = "pywin32-303-cp38-cp38-win_amd64.whl", hash = "sha256:793bf74fce164bcffd9d57bb13c2c15d56e43c9542a7b9687b4fccf8f8a41aba"}, - {file = "pywin32-303-cp39-cp39-win32.whl", hash = "sha256:7d3271c98434617a11921c5ccf74615794d97b079e22ed7773790822735cc352"}, - {file = "pywin32-303-cp39-cp39-win_amd64.whl", hash = "sha256:79cbb862c11b9af19bcb682891c1b91942ec2ff7de8151e2aea2e175899cda34"}, + {file = "pytest-metadata-2.0.0.tar.gz", hash = "sha256:08dcc2779f4393309dd6d341ea1ddc15265239b6c4d51671737e784406ec07dc"}, + {file = "pytest_metadata-2.0.0-py3-none-any.whl", hash = "sha256:e25f1a77ed02baf1d83911604247a70d60d7dcb970aa12be38e1ed58d4d38e65"}, ] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, @@ -1733,55 +1127,6 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -pyzmq = [ - {file = "pyzmq-22.3.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:6b217b8f9dfb6628f74b94bdaf9f7408708cb02167d644edca33f38746ca12dd"}, - {file = "pyzmq-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2841997a0d85b998cbafecb4183caf51fd19c4357075dfd33eb7efea57e4c149"}, - {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f89468059ebc519a7acde1ee50b779019535db8dcf9b8c162ef669257fef7a93"}, - {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea12133df25e3a6918718fbb9a510c6ee5d3fdd5a346320421aac3882f4feeea"}, - {file = "pyzmq-22.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c532fd68b93998aab92356be280deec5de8f8fe59cd28763d2cc8a58747b7f"}, - {file = "pyzmq-22.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f907c7359ce8bf7f7e63c82f75ad0223384105f5126f313400b7e8004d9b33c3"}, - {file = "pyzmq-22.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:902319cfe23366595d3fa769b5b751e6ee6750a0a64c5d9f757d624b2ac3519e"}, - {file = "pyzmq-22.3.0-cp310-cp310-win32.whl", hash = "sha256:67db33bea0a29d03e6eeec55a8190e033318cee3cbc732ba8fd939617cbf762d"}, - {file = "pyzmq-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:7661fc1d5cb73481cf710a1418a4e1e301ed7d5d924f91c67ba84b2a1b89defd"}, - {file = "pyzmq-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79244b9e97948eaf38695f4b8e6fc63b14b78cc37f403c6642ba555517ac1268"}, - {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab888624ed68930442a3f3b0b921ad7439c51ba122dbc8c386e6487a658e4a4e"}, - {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18cd854b423fce44951c3a4d3e686bac8f1243d954f579e120a1714096637cc0"}, - {file = "pyzmq-22.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:de8df0684398bd74ad160afdc2a118ca28384ac6f5e234eb0508858d8d2d9364"}, - {file = "pyzmq-22.3.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:62bcade20813796c426409a3e7423862d50ff0639f5a2a95be4b85b09a618666"}, - {file = "pyzmq-22.3.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ea5a79e808baef98c48c884effce05c31a0698c1057de8fc1c688891043c1ce1"}, - {file = "pyzmq-22.3.0-cp36-cp36m-win32.whl", hash = "sha256:3c1895c95be92600233e476fe283f042e71cf8f0b938aabf21b7aafa62a8dac9"}, - {file = "pyzmq-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:851977788b9caa8ed011f5f643d3ee8653af02c5fc723fa350db5125abf2be7b"}, - {file = "pyzmq-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4ebed0977f92320f6686c96e9e8dd29eed199eb8d066936bac991afc37cbb70"}, - {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42abddebe2c6a35180ca549fadc7228d23c1e1f76167c5ebc8a936b5804ea2df"}, - {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1e41b32d6f7f9c26bc731a8b529ff592f31fc8b6ef2be9fa74abd05c8a342d7"}, - {file = "pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:be4e0f229cf3a71f9ecd633566bd6f80d9fa6afaaff5489492be63fe459ef98c"}, - {file = "pyzmq-22.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08c4e315a76ef26eb833511ebf3fa87d182152adf43dedee8d79f998a2162a0b"}, - {file = "pyzmq-22.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:badb868fff14cfd0e200eaa845887b1011146a7d26d579aaa7f966c203736b92"}, - {file = "pyzmq-22.3.0-cp37-cp37m-win32.whl", hash = "sha256:7c58f598d9fcc52772b89a92d72bf8829c12d09746a6d2c724c5b30076c1f11d"}, - {file = "pyzmq-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b97502c16a5ec611cd52410bdfaab264997c627a46b0f98d3f666227fd1ea2d"}, - {file = "pyzmq-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d728b08448e5ac3e4d886b165385a262883c34b84a7fe1166277fe675e1c197a"}, - {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:480b9931bfb08bf8b094edd4836271d4d6b44150da051547d8c7113bf947a8b0"}, - {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7dc09198e4073e6015d9a8ea093fc348d4e59de49382476940c3dd9ae156fba8"}, - {file = "pyzmq-22.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ca6cd58f62a2751728016d40082008d3b3412a7f28ddfb4a2f0d3c130f69e74"}, - {file = "pyzmq-22.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:468bd59a588e276961a918a3060948ae68f6ff5a7fa10bb2f9160c18fe341067"}, - {file = "pyzmq-22.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c88fa7410e9fc471e0858638f403739ee869924dd8e4ae26748496466e27ac59"}, - {file = "pyzmq-22.3.0-cp38-cp38-win32.whl", hash = "sha256:c0f84360dcca3481e8674393bdf931f9f10470988f87311b19d23cda869bb6b7"}, - {file = "pyzmq-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f762442bab706fd874064ca218b33a1d8e40d4938e96c24dafd9b12e28017f45"}, - {file = "pyzmq-22.3.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:954e73c9cd4d6ae319f1c936ad159072b6d356a92dcbbabfd6e6204b9a79d356"}, - {file = "pyzmq-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f43b4a2e6218371dd4f41e547bd919ceeb6ebf4abf31a7a0669cd11cd91ea973"}, - {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:acebba1a23fb9d72b42471c3771b6f2f18dcd46df77482612054bd45c07dfa36"}, - {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf98fd7a6c8aaa08dbc699ffae33fd71175696d78028281bc7b832b26f00ca57"}, - {file = "pyzmq-22.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d072f7dfbdb184f0786d63bda26e8a0882041b1e393fbe98940395f7fab4c5e2"}, - {file = "pyzmq-22.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:53f4fd13976789ffafedd4d46f954c7bb01146121812b72b4ddca286034df966"}, - {file = "pyzmq-22.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1b5d457acbadcf8b27561deeaa386b0217f47626b29672fa7bd31deb6e91e1b"}, - {file = "pyzmq-22.3.0-cp39-cp39-win32.whl", hash = "sha256:e6a02cf7271ee94674a44f4e62aa061d2d049001c844657740e156596298b70b"}, - {file = "pyzmq-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3dcb5548ead4f1123851a5ced467791f6986d68c656bc63bfff1bf9e36671e2"}, - {file = "pyzmq-22.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3a4c9886d61d386b2b493377d980f502186cd71d501fffdba52bd2a0880cef4f"}, - {file = "pyzmq-22.3.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:80e043a89c6cadefd3a0712f8a1322038e819ebe9dbac7eca3bce1721bcb63bf"}, - {file = "pyzmq-22.3.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1621e7a2af72cced1f6ec8ca8ca91d0f76ac236ab2e8828ac8fe909512d566cb"}, - {file = "pyzmq-22.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d6157793719de168b199194f6b6173f0ccd3bf3499e6870fac17086072e39115"}, - {file = "pyzmq-22.3.0.tar.gz", hash = "sha256:8eddc033e716f8c91c6a2112f0a8ebc5e00532b4a6ae1eb0ccc48e027f9c671c"}, -] regex = [ {file = "regex-2022.3.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42eb13b93765c6698a5ab3bcd318d8c39bb42e5fa8a7fcf7d8d98923f3babdb1"}, {file = "regex-2022.3.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9beb03ff6fe509d6455971c2489dceb31687b38781206bcec8e68bdfcf5f1db2"}, @@ -1928,10 +1273,6 @@ uvicorn = [ {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"}, {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"}, ] -werkzeug = [ - {file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"}, - {file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"}, -] win32-setctime = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, @@ -1940,60 +1281,3 @@ zipp = [ {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] -"zope.event" = [ - {file = "zope.event-4.5.0-py2.py3-none-any.whl", hash = "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42"}, - {file = "zope.event-4.5.0.tar.gz", hash = "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"}, -] -"zope.interface" = [ - {file = "zope.interface-5.4.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531"}, - {file = "zope.interface-5.4.0-cp27-cp27m-win32.whl", hash = "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325"}, - {file = "zope.interface-5.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117"}, - {file = "zope.interface-5.4.0-cp35-cp35m-win32.whl", hash = "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8"}, - {file = "zope.interface-5.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63"}, - {file = "zope.interface-5.4.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2"}, - {file = "zope.interface-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78"}, - {file = "zope.interface-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1"}, - {file = "zope.interface-5.4.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf"}, - {file = "zope.interface-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7"}, - {file = "zope.interface-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94"}, - {file = "zope.interface-5.4.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4"}, - {file = "zope.interface-5.4.0-cp38-cp38-win32.whl", hash = "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb"}, - {file = "zope.interface-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54"}, - {file = "zope.interface-5.4.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c"}, - {file = "zope.interface-5.4.0-cp39-cp39-win32.whl", hash = "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e"}, - {file = "zope.interface-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09"}, - {file = "zope.interface-5.4.0.tar.gz", hash = "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e"}, -] diff --git a/pyproject.toml b/pyproject.toml index 2f5614e5..2c16dbaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,14 +42,12 @@ sentry-sdk = "^0.14.4" allure-pytest = {version = "^2.8.16", optional = true} requests-toolbelt = {version = "^0.9.1", optional = true} filetype = {version = "^1.0.7", optional = true} -locust = {version = "^1.0.3", optional = true} Brotli = "^1.0.9" jinja2 = "^3.0.3" [tool.poetry.extras] allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure upload = ["requests-toolbelt", "filetype"] # pip install "httprunner[upload]", poetry install -E upload -locust = ["locust"] # pip install "httprunner[locust]", poetry install -E locust [tool.poetry.dev-dependencies] coverage = "^4.5.4" @@ -61,7 +59,6 @@ httprunner = "httprunner.cli:main" hrun = "httprunner.cli:main_hrun_alias" hmake = "httprunner.cli:main_make_alias" har2case = "httprunner.cli:main_har2case_alias" -locusts = "httprunner.ext.locust:main_locusts" [build-system] requires = ["poetry>=1.0.0"] From 097318ad2f782d3aae53e82ec6dc16ab6c314a34 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 25 Mar 2022 16:54:56 +0800 Subject: [PATCH 02/13] change: remove fastapi and uvicorn for python httprunner --- httprunner/app/__init__.py | 0 httprunner/app/debug_test.py | 50 --------- httprunner/app/main.py | 16 --- httprunner/app/routers/__init__.py | 0 httprunner/app/routers/debug.py | 54 --------- httprunner/app/routers/debugtalk.py | 42 ------- httprunner/app/routers/deps.py | 34 ------ poetry.lock | 166 +--------------------------- pyproject.toml | 2 - 9 files changed, 1 insertion(+), 363 deletions(-) delete mode 100644 httprunner/app/__init__.py delete mode 100644 httprunner/app/debug_test.py delete mode 100644 httprunner/app/main.py delete mode 100644 httprunner/app/routers/__init__.py delete mode 100644 httprunner/app/routers/debug.py delete mode 100644 httprunner/app/routers/debugtalk.py delete mode 100644 httprunner/app/routers/deps.py diff --git a/httprunner/app/__init__.py b/httprunner/app/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/httprunner/app/debug_test.py b/httprunner/app/debug_test.py deleted file mode 100644 index 0ee0f80e..00000000 --- a/httprunner/app/debug_test.py +++ /dev/null @@ -1,50 +0,0 @@ -import unittest - -from starlette.testclient import TestClient - -from httprunner.app.main import app - -client = TestClient(app) - - -class TestDebug(unittest.TestCase): - def test_debug_single_testcase(self): - json_data = { - "project_meta": { - "debugtalk_py": "\ndef hello(name):\n print(f'hello, {name}')\n", - "variables": {}, - "env": {}, - }, - "testcase": { - "config": { - "name": "test demo for debug service", - "verify": False, - "base_url": "", - "variables": {}, - "setup_hooks": [], - "teardown_hooks": [], - "export": [], - }, - "teststeps": [ - { - "name": "get index page", - "request": { - "method": "GET", - "url": "https://httpbin.org/", - "params": {}, - "headers": {}, - "json": {}, - "cookies": {}, - "timeout": 30, - "allow_redirects": True, - "verify": False, - }, - "extract": {}, - "validate": [], - } - ], - }, - } - response = client.post("/hrun/debug/testcase", json=json_data) - assert response.status_code == 200 - assert response.json()["code"] == 0 diff --git a/httprunner/app/main.py b/httprunner/app/main.py deleted file mode 100644 index 0084762d..00000000 --- a/httprunner/app/main.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import FastAPI - -from httprunner import __version__ -from .routers import deps, debugtalk, debug - -app = FastAPI() - - -@app.get("/hrun/version") -async def get_hrun_version(): - return {"code": 0, "message": "success", "result": {"HttpRunner": __version__}} - - -app.include_router(deps.router) -app.include_router(debugtalk.router) -app.include_router(debug.router) diff --git a/httprunner/app/routers/__init__.py b/httprunner/app/routers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/httprunner/app/routers/debug.py b/httprunner/app/routers/debug.py deleted file mode 100644 index 192fbc6e..00000000 --- a/httprunner/app/routers/debug.py +++ /dev/null @@ -1,54 +0,0 @@ -from fastapi import APIRouter - -from httprunner.runner import HttpRunner -from httprunner.models import ProjectMeta, TestCase - -router = APIRouter() -runner = HttpRunner() - - -@router.post("/hrun/debug/testcase", tags=["debug"]) -async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase): - resp = {"code": 0, "message": "success", "result": {}} - - if project_meta.debugtalk_py: - origin_local_keys = list(locals().keys()).copy() - exec(project_meta.debugtalk_py, {}, locals()) - new_local_keys = list(locals().keys()).copy() - new_added_keys = set(new_local_keys) - set(origin_local_keys) - new_added_keys.remove("origin_local_keys") - for func_name in new_added_keys: - project_meta.functions[func_name] = locals()[func_name] - - runner.with_project_meta(project_meta).run_testcase(testcase) - summary = runner.get_summary() - - if not summary.success: - resp["code"] = 1 - resp["message"] = "fail" - - resp["result"] = summary.dict() - return resp - - -# @router.post("/hrun/debug/api", tags=["debug"]) -# async def debug_single_api(): -# resp = { -# "code": 0, -# "message": "success", -# "result": {} -# } -# -# # tests_mapping -# -# # summary = runner.run_tests(tests_mapping) -# -# return resp -# -# -# @router.post("/hrun/debug/testcases", tags=["debug"]) -# async def debug_multiple_testcases(project_meta: ProjectMeta, testcases: TestCases): -# tests_mapping = { -# "project_meta": project_meta, -# "testcases": testcases -# } diff --git a/httprunner/app/routers/debugtalk.py b/httprunner/app/routers/debugtalk.py deleted file mode 100644 index eec92599..00000000 --- a/httprunner/app/routers/debugtalk.py +++ /dev/null @@ -1,42 +0,0 @@ -import contextlib -import sys -from io import StringIO - -from fastapi import APIRouter -from loguru import logger -from starlette.requests import Request - -router = APIRouter() - - -@contextlib.contextmanager -def stdout_io(stdout=None): - old = sys.stdout - if stdout is None: - stdout = StringIO() - sys.stdout = stdout - yield stdout - sys.stdout = old - - -@router.post("/hrun/debug/debugtalk_py", tags=["debugtalk"]) -async def debug_python(request: Request): - body = await request.body() - - if request.headers.get("content-transfer-encoding") == "base64": - # TODO: decode base64 - pass - - resp = {"code": 0, "message": "success", "result": ""} - try: - with stdout_io() as s: - exec(body, globals()) - output = s.getvalue() - resp["result"] = output - except Exception as ex: - resp["code"] = 1 - resp["message"] = "fail" - resp["result"] = str(ex) - logger.error(resp) - - return resp diff --git a/httprunner/app/routers/deps.py b/httprunner/app/routers/deps.py deleted file mode 100644 index b83ec3ee..00000000 --- a/httprunner/app/routers/deps.py +++ /dev/null @@ -1,34 +0,0 @@ -import subprocess -from typing import List - -import pkg_resources -from fastapi import APIRouter -from loguru import logger - -router = APIRouter() - - -@router.get("/hrun/deps", tags=["deps"]) -async def get_installed_dependenies(): - resp = {"code": 0, "message": "success", "result": {}} - for p in pkg_resources.working_set: - resp["result"][p.project_name] = p.version - - return resp - - -@router.post("/hrun/deps", tags=["deps"]) -async def install_dependenies(deps: List[str]): - resp = {"code": 0, "message": "success", "result": {}} - for dep in deps: - try: - p = subprocess.run(["pip", "install", dep]) - assert p.returncode == 0 - resp["result"][dep] = True - except (AssertionError, subprocess.SubprocessError): - resp["result"][dep] = False - resp["code"] = 1 - resp["message"] = "fail" - logger.error(f"failed to install dependency: {dep}") - - return resp diff --git a/poetry.lock b/poetry.lock index 14395b78..8a5e31a4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -34,29 +34,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "anyio" -version = "3.5.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16)"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "appdirs" version = "1.4.4" @@ -70,25 +47,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "asgiref" -version = "3.5.0" -description = "ASGI specs, helper code, and adapters" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "atomicwrites" version = "1.4.0" @@ -231,29 +189,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "fastapi" -version = "0.70.1" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" -starlette = "0.16.0" - -[package.extras] -all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] -dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] -doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] -test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "filetype" version = "1.0.10" @@ -267,22 +202,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "h11" -version = "0.13.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "idna" version = "3.3" @@ -663,39 +582,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "sniffio" -version = "1.2.0" -description = "Sniff out which async library your code is running under" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - -[[package]] -name = "starlette" -version = "0.16.0" -description = "The little ASGI library that shines." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -anyio = ">=3.0.0,<4" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "graphene"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "toml" version = "0.10.2" @@ -766,28 +652,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" -[[package]] -name = "uvicorn" -version = "0.17.6" -description = "The lightning-fast ASGI server." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -asgiref = ">=3.4.0" -click = ">=7.0" -h11 = ">=0.8" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "tsinghua" - [[package]] name = "win32-setctime" version = "1.1.0" @@ -828,7 +692,7 @@ upload = ["requests-toolbelt", "filetype"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "484e6bfffc0e5b3b998b150e7bf0ced340577a216d56576a9b702baff511fe2c" +content-hash = "ce51964d1daf419593be8b6d6f003069bf8626d922b950432e2c6f9a8093d9e6" [metadata.files] allure-pytest = [ @@ -839,18 +703,10 @@ allure-python-commons = [ {file = "allure-python-commons-2.9.45.tar.gz", hash = "sha256:c238d28aeac35e8c7c517d8a2327e25ae5bbf2c30b5e2313d20ef11d75f5549d"}, {file = "allure_python_commons-2.9.45-py3-none-any.whl", hash = "sha256:3572f0526db3946fb14470c58b0b41d343483aad91d37d414e4641815e13691a"}, ] -anyio = [ - {file = "anyio-3.5.0-py3-none-any.whl", hash = "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e"}, - {file = "anyio-3.5.0.tar.gz", hash = "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6"}, -] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] -asgiref = [ - {file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"}, - {file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"}, -] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -962,18 +818,10 @@ coverage = [ {file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"}, {file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"}, ] -fastapi = [ - {file = "fastapi-0.70.1-py3-none-any.whl", hash = "sha256:5367226c7bcd7bfb2e17edaf225fd9a983095b1372281e9a3eb661336fb93748"}, - {file = "fastapi-0.70.1.tar.gz", hash = "sha256:21d03979b5336375c66fa5d1f3126c6beca650d5d2166fbb78345a30d33c8d06"}, -] filetype = [ {file = "filetype-1.0.10-py2.py3-none-any.whl", hash = "sha256:63fbe6e818a3d1cfac1d62b196574a7a4b7fc8e06a6c500d53577c018ef127d9"}, {file = "filetype-1.0.10.tar.gz", hash = "sha256:323a13500731b6c65a253bc3930bbce9a56dfba71e90b60ffd968ab69d9ae937"}, ] -h11 = [ - {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, - {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, -] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -1219,14 +1067,6 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -sniffio = [ - {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, - {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, -] -starlette = [ - {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, - {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, -] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -1269,10 +1109,6 @@ urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] -uvicorn = [ - {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"}, - {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"}, -] win32-setctime = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, diff --git a/pyproject.toml b/pyproject.toml index 2c16dbaa..bb3973c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,8 +51,6 @@ upload = ["requests-toolbelt", "filetype"] # pip install "httprunner[upload]", [tool.poetry.dev-dependencies] coverage = "^4.5.4" -fastapi = "^0.70.0" -uvicorn = "^0.17.6" [tool.poetry.scripts] httprunner = "httprunner.cli:main" From 3d0c6a0d213b29d0d75087713167d70fa8209f7d Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 25 Mar 2022 18:04:07 +0800 Subject: [PATCH 03/13] fix: remove misuse of NoReturn in Python typing --- docs/CHANGELOG.md | 11 +++++++++-- httprunner/ext/uploader/__init__.py | 4 ++-- httprunner/make.py | 10 +++++----- httprunner/response.py | 6 +++--- httprunner/runner.py | 19 +++++++++---------- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5d531ea9..4f287bb0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,9 +3,16 @@ ## 4.0.0 - refactor: merge [hrp] into httprunner repo -- fix: call referenced api/testcase with relative path -- fix: ignore exceptions when reporting GA events + +**go version** + - change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis +- fix: call referenced api/testcase with relative path + +**python version** + +- fix: ignore exceptions when reporting GA events +- fix: remove misuse of NoReturn in Python typing ## hrp-v0.8.0 (2022-03-22) diff --git a/httprunner/ext/uploader/__init__.py b/httprunner/ext/uploader/__init__.py index ef046f14..c375c4d5 100644 --- a/httprunner/ext/uploader/__init__.py +++ b/httprunner/ext/uploader/__init__.py @@ -44,7 +44,7 @@ For compatibility, you can also write upload test script in old way: import os import sys -from typing import Text, NoReturn +from typing import Text from httprunner.models import TStep, FunctionsMapping from httprunner.parser import parse_variables_mapping @@ -75,7 +75,7 @@ def ensure_upload_ready(): sys.exit(1) -def prepare_upload_step(step: TStep, functions: FunctionsMapping) -> "NoReturn": +def prepare_upload_step(step: TStep, functions: FunctionsMapping): """ preprocess for upload test replace `upload` info with MultipartEncoder diff --git a/httprunner/make.py b/httprunner/make.py index f452eb4b..1ce384a5 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -2,7 +2,7 @@ import os import string import subprocess import sys -from typing import Dict, List, NoReturn, Set, Text, Tuple +from typing import Dict, List, Set, Text, Tuple import jinja2 from loguru import logger @@ -133,7 +133,7 @@ def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: return new_file_path -def __ensure_testcase_module(path: Text) -> NoReturn: +def __ensure_testcase_module(path: Text): """ ensure pytest files are in python module, generate __init__.py on demand """ init_file = os.path.join(os.path.dirname(path), "__init__.py") @@ -158,7 +158,7 @@ def convert_testcase_path(testcase_abs_path: Text) -> Tuple[Text, Text]: return testcase_python_abs_path, name_in_title_case -def format_pytest_with_black(*python_paths: Text) -> NoReturn: +def format_pytest_with_black(*python_paths: Text): logger.info("format pytest cases with black ...") try: if is_support_multiprocessing() or len(python_paths) <= 1: @@ -436,7 +436,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: return testcase_python_abs_path -def make_testsuite(testsuite: Dict) -> NoReturn: +def make_testsuite(testsuite: Dict): """convert valid testsuite dict to pytest folder with testcases""" # validate testsuite format load_testsuite(testsuite) @@ -493,7 +493,7 @@ def make_testsuite(testsuite: Dict) -> NoReturn: pytest_files_run_set.add(testcase_pytest_path) -def __make(tests_path: Text) -> NoReturn: +def __make(tests_path: Text): """ make testcase(s) with testcase/testsuite/folder absolute path generated pytest file path will be cached in pytest_files_made_cache_mapping diff --git a/httprunner/response.py b/httprunner/response.py index 142efc8b..7fd43bc5 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -1,4 +1,4 @@ -from typing import Dict, Text, Any, NoReturn +from typing import Dict, Text, Any import jmespath import requests @@ -153,7 +153,7 @@ class ResponseObject(object): } if not expr.startswith(tuple(resp_obj_meta.keys())): return expr - + try: check_value = jmespath.search(expr, resp_obj_meta) except JMESPathError as ex: @@ -193,7 +193,7 @@ class ResponseObject(object): validators: Validators, variables_mapping: VariablesMapping = None, functions_mapping: FunctionsMapping = None, - ) -> NoReturn: + ): variables_mapping = variables_mapping or {} functions_mapping = functions_mapping or {} diff --git a/httprunner/runner.py b/httprunner/runner.py index f68ac3d4..a4b5ec4c 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -2,7 +2,7 @@ import os import time import uuid from datetime import datetime -from typing import List, Dict, Text, NoReturn +from typing import List, Dict, Text try: import allure @@ -55,7 +55,7 @@ class HttpRunner(object): # log __log_path: Text = "" - def __init_tests__(self) -> NoReturn: + def __init_tests__(self): self.__config = self.config.perform() self.__teststeps = [] for step in self.teststeps: @@ -88,9 +88,7 @@ class HttpRunner(object): self.__export = export return self - def __call_hooks( - self, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text, - ) -> NoReturn: + def __call_hooks(self, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text): """ call hook actions. Args: @@ -139,11 +137,12 @@ class HttpRunner(object): step_data = StepData(name=step.name) # parse - prepare_upload_step(step, self.__project_meta.functions) + functions = self.__project_meta.functions + # prepare_upload_step(step, functions) request_dict = step.request.dict() request_dict.pop("upload", None) parsed_request_dict = parse_data( - request_dict, step.variables, self.__project_meta.functions + request_dict, step.variables, functions ) parsed_request_dict["headers"].setdefault( "HRUN-Request-ID", @@ -195,7 +194,7 @@ class HttpRunner(object): # extract extractors = step.extract - extract_mapping = resp_obj.extract(extractors, step.variables, self.__project_meta.functions) + extract_mapping = resp_obj.extract(extractors, step.variables, functions) step_data.export_vars = extract_mapping variables_mapping = step.variables @@ -206,7 +205,7 @@ class HttpRunner(object): session_success = False try: resp_obj.validate( - validators, variables_mapping, self.__project_meta.functions + validators, variables_mapping, functions ) session_success = True except ValidationFailure: @@ -304,7 +303,7 @@ class HttpRunner(object): logger.info(f"run step end: {step.name} <<<<<<\n") return step_data.export_vars - def __parse_config(self, config: TConfig) -> NoReturn: + def __parse_config(self, config: TConfig): config.variables.update(self.__session_variables) config.variables = parse_variables_mapping( config.variables, self.__project_meta.functions From c99097113e320ae6046030f49776e287ede0ccd9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 25 Mar 2022 23:48:14 +0800 Subject: [PATCH 04/13] fix: post json with empty data --- hrp/internal/har2case/core.go | 13 +-- hrp/internal/har2case/core_test.go | 127 +++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/hrp/internal/har2case/core.go b/hrp/internal/har2case/core.go index 684b445d..5cfa5f22 100644 --- a/hrp/internal/har2case/core.go +++ b/hrp/internal/har2case/core.go @@ -182,7 +182,6 @@ func (s *tStep) makeRequestMethod(entry *Entry) error { } func (s *tStep) makeRequestURL(entry *Entry) error { - u, err := url.Parse(entry.Request.URL) if err != nil { log.Error().Err(err).Msg("make request url failed") @@ -230,10 +229,14 @@ func (s *tStep) makeRequestBody(entry *Entry) error { if strings.HasPrefix(mimeType, "application/json") { // post json var body interface{} - err := json.Unmarshal([]byte(entry.Request.PostData.Text), &body) - if err != nil { - log.Error().Err(err).Msg("make request body failed") - return err + if entry.Request.PostData.Text == "" { + body = nil + } else { + err := json.Unmarshal([]byte(entry.Request.PostData.Text), &body) + if err != nil { + log.Error().Err(err).Msg("make request body failed") + return err + } } s.Request.Body = body } else if strings.HasPrefix(mimeType, "application/x-www-form-urlencoded") { diff --git a/hrp/internal/har2case/core_test.go b/hrp/internal/har2case/core_test.go index 81813dc7..385ec07e 100644 --- a/hrp/internal/har2case/core_test.go +++ b/hrp/internal/har2case/core_test.go @@ -120,3 +120,130 @@ func TestGetFilenameWithoutExtension(t *testing.T) { t.Fail() } } + +func TestMakeRequestDataParams(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Request: Request{ + Method: "POST", + PostData: PostData{ + MimeType: "application/x-www-form-urlencoded; charset=utf-8", + Params: []PostParam{ + {Name: "a", Value: "1"}, + {Name: "b", Value: "2"}, + }, + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, "a=1&b=2", step.Request.Body) { + t.Fail() + } +} + +func TestMakeRequestDataJSON(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Request: Request{ + Method: "POST", + PostData: PostData{ + MimeType: "application/json; charset=utf-8", + Text: "{\"a\":\"1\",\"b\":\"2\"}", + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, map[string]interface{}{"a": "1", "b": "2"}, step.Request.Body) { + t.Fail() + } +} + +func TestMakeRequestDataTextEmpty(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Request: Request{ + Method: "POST", + PostData: PostData{ + MimeType: "application/json; charset=utf-8", + Text: "", + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, nil, step.Request.Body) { // TODO + t.Fail() + } +} + +func TestMakeValidate(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Response: Response{ + Status: 200, + Headers: []NVP{ + {Name: "Content-Type", Value: "application/json; charset=utf-8"}, + }, + Content: Content{ + Size: 71, + MimeType: "application/json; charset=utf-8", + // map[Code:200 IsSuccess:true Message: Value:map[BlnResult:true]] + Text: "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=", + Encoding: "base64", + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + validator, ok := step.Validators[0].(hrp.Validator) + if !ok { + t.Fail() + } + if !assert.Equal(t, validator, + hrp.Validator{ + Check: "status_code", + Expect: 200, + Assert: "equals", + Message: "assert response status code"}) { + t.Fail() + } + + validator, ok = step.Validators[1].(hrp.Validator) + if !ok { + t.Fail() + } + if !assert.Equal(t, validator, + hrp.Validator{ + Check: "headers.\"Content-Type\"", + Expect: "application/json; charset=utf-8", + Assert: "equals", + Message: "assert response header Content-Type"}) { + t.Fail() + } + + validator, ok = step.Validators[2].(hrp.Validator) + if !ok { + t.Fail() + } + if !assert.Equal(t, validator, + hrp.Validator{ + Check: "body.Code", + Expect: float64(200), // TODO + Assert: "equals", + Message: "assert response body Code"}) { + t.Fail() + } +} From 1a93f0acb084fef115c7168b7d478ae1665f0bba Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 26 Mar 2022 09:09:02 +0800 Subject: [PATCH 05/13] refactor: move load json/yaml functions to internal --- hrp/convert.go | 67 +--------------------- hrp/convert_test.go | 6 +- hrp/internal/builtin/function.go | 95 ++++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 96 deletions(-) diff --git a/hrp/convert.go b/hrp/convert.go index 94bd3328..af0048b5 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -1,56 +1,15 @@ package hrp import ( - "bytes" "fmt" - "os" "path/filepath" "strings" "github.com/rs/zerolog/log" - "gopkg.in/yaml.v3" - "github.com/httprunner/httprunner/hrp/internal/json" + "github.com/httprunner/httprunner/hrp/internal/builtin" ) -func loadFromJSON(path string, structObj interface{}) error { - path, err := filepath.Abs(path) - if err != nil { - log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") - return err - } - log.Info().Str("path", path).Msg("load json") - - file, err := os.ReadFile(path) - if err != nil { - log.Error().Err(err).Msg("load json path failed") - return err - } - - decoder := json.NewDecoder(bytes.NewReader(file)) - decoder.UseNumber() - err = decoder.Decode(structObj) - return err -} - -func loadFromYAML(path string, structObj interface{}) error { - path, err := filepath.Abs(path) - if err != nil { - log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") - return err - } - log.Info().Str("path", path).Msg("load yaml") - - file, err := os.ReadFile(path) - if err != nil { - log.Error().Err(err).Msg("load yaml path failed") - return err - } - - err = yaml.Unmarshal(file, structObj) - return err -} - func convertCompatValidator(Validators []interface{}) (err error) { for i, iValidator := range Validators { validatorMap := iValidator.(map[string]interface{}) @@ -192,18 +151,8 @@ func (path *APIPath) ToString() string { func (path *APIPath) ToAPI() (*API, error) { api := &API{} - var err error - apiPath := path.ToString() - ext := filepath.Ext(apiPath) - switch ext { - case ".json": - err = loadFromJSON(apiPath, api) - case ".yaml", ".yml": - err = loadFromYAML(apiPath, api) - default: - err = ErrUnsupportedFileExt - } + err := builtin.LoadFile(apiPath, api) if err != nil { return nil, err } @@ -220,18 +169,8 @@ func (path *TestCasePath) ToString() string { func (path *TestCasePath) ToTestCase() (*TestCase, error) { tc := &TCase{} - var err error - casePath := path.ToString() - ext := filepath.Ext(casePath) - switch ext { - case ".json": - err = loadFromJSON(casePath, tc) - case ".yaml", ".yml": - err = loadFromYAML(casePath, tc) - default: - err = ErrUnsupportedFileExt - } + err := builtin.LoadFile(casePath, tc) if err != nil { return nil, err } diff --git a/hrp/convert_test.go b/hrp/convert_test.go index 4e7ed7c2..7c922d7e 100644 --- a/hrp/convert_test.go +++ b/hrp/convert_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/httprunner/httprunner/hrp/internal/builtin" ) var ( @@ -18,11 +20,11 @@ var ( func TestLoadCase(t *testing.T) { tcJSON := &TCase{} tcYAML := &TCase{} - err := loadFromJSON(demoTestCaseJSONPath.ToString(), tcJSON) + err := builtin.LoadFile(demoTestCaseJSONPath.ToString(), tcJSON) if !assert.NoError(t, err) { t.Fail() } - err = loadFromYAML(demoTestCaseYAMLPath.ToString(), tcYAML) + err = builtin.LoadFile(demoTestCaseYAMLPath.ToString(), tcYAML) if !assert.NoError(t, err) { t.Fail() } diff --git a/hrp/internal/builtin/function.go b/hrp/internal/builtin/function.go index f95d01c7..40a676a2 100644 --- a/hrp/internal/builtin/function.go +++ b/hrp/internal/builtin/function.go @@ -62,36 +62,6 @@ func MD5(str string) string { return hex.EncodeToString(hasher.Sum(nil)) } -func loadFromCSV(path string) []map[string]interface{} { - path, err := filepath.Abs(path) - if err != nil { - log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") - panic(err) - } - log.Info().Str("path", path).Msg("load csv file") - - file, err := os.ReadFile(path) - if err != nil { - log.Error().Err(err).Msg("load csv file failed") - panic(err) - } - r := csv.NewReader(strings.NewReader(string(file))) - content, err := r.ReadAll() - if err != nil { - log.Error().Err(err).Msg("parse csv file failed") - panic(err) - } - var result []map[string]interface{} - for i := 1; i < len(content); i++ { - row := make(map[string]interface{}) - for j := 0; j < len(content[i]); j++ { - row[content[0][j]] = content[i][j] - } - result = append(result, row) - } - return result -} - func Dump2JSON(data interface{}, path string) error { path, err := filepath.Abs(path) if err != nil { @@ -258,3 +228,68 @@ func Interface2Float64(i interface{}) (float64, error) { } return 0, errors.New("failed to convert interface to float64") } + +var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension") + +func LoadFile(path string, structObj interface{}) (err error) { + log.Info().Str("path", path).Msg("load file") + file, err := readFile(path) + if err != nil { + log.Error().Err(err).Msg("read file failed") + return err + } + + ext := filepath.Ext(path) + switch ext { + case ".json": + decoder := json.NewDecoder(bytes.NewReader(file)) + decoder.UseNumber() + err = decoder.Decode(structObj) + case ".yaml", ".yml": + err = yaml.Unmarshal(file, structObj) + default: + err = ErrUnsupportedFileExt + } + return err +} + +func loadFromCSV(path string) []map[string]interface{} { + log.Info().Str("path", path).Msg("load csv file") + file, err := readFile(path) + if err != nil { + log.Error().Err(err).Msg("read csv file failed") + panic(err) + } + + r := csv.NewReader(strings.NewReader(string(file))) + content, err := r.ReadAll() + if err != nil { + log.Error().Err(err).Msg("parse csv file failed") + panic(err) + } + var result []map[string]interface{} + for i := 1; i < len(content); i++ { + row := make(map[string]interface{}) + for j := 0; j < len(content[i]); j++ { + row[content[0][j]] = content[i][j] + } + result = append(result, row) + } + return result +} + +func readFile(path string) ([]byte, error) { + var err error + path, err = filepath.Abs(path) + if err != nil { + log.Error().Err(err).Str("path", path).Msg("convert absolute path failed") + return nil, err + } + + file, err := os.ReadFile(path) + if err != nil { + log.Error().Err(err).Msg("read file failed") + return nil, err + } + return file, nil +} From da0bec1f775f5e2a626ca02b7516fa80baac874d Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 26 Mar 2022 10:44:19 +0800 Subject: [PATCH 06/13] feat: add --profile flag for har2case to support overwrite headers/cookies with specified yaml/json profile file --- docs/CHANGELOG.md | 5 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_har2case.md | 3 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- examples/{hrp => data}/har/demo.har | 0 examples/{hrp => data}/har/demo.json | 0 examples/{hrp => data}/har/postman-echo.har | 0 examples/data/{har2case => har}/profile.yml | 2 +- examples/hrp/compat_test.go | 2 +- examples/hrp/har/postman-echo.yaml | 1101 ------------------- hrp/cmd/har2case.go | 8 + hrp/internal/builtin/function.go | 7 +- hrp/internal/har2case/core.go | 70 +- hrp/internal/har2case/core_test.go | 120 +- 16 files changed, 190 insertions(+), 1136 deletions(-) rename examples/{hrp => data}/har/demo.har (100%) rename examples/{hrp => data}/har/demo.json (100%) rename examples/{hrp => data}/har/postman-echo.har (100%) rename examples/data/{har2case => har}/profile.yml (74%) delete mode 100644 examples/hrp/har/postman-echo.yaml diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4f287bb0..5aca4971 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,15 +2,18 @@ ## 4.0.0 -- refactor: merge [hrp] into httprunner repo +- refactor: merge [hrp] into httprunner v4, which will include golang and python dual engine **go version** +- feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json profile file - change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis - fix: call referenced api/testcase with relative path **python version** +- change: remove locust, you should run load tests with go version +- change: remove fastapi and uvicorn dependencies - fix: ignore exceptions when reporting GA events - fix: remove misuse of NoReturn in Python typing diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index cdfed299..203deeaa 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -33,4 +33,4 @@ Copyright 2021 debugtalk * [hrp run](hrp_run.md) - run API test * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 23-Mar-2022 +###### Auto generated by spf13/cobra on 26-Mar-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index fabc691d..3b172a65 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 23-Mar-2022 +###### Auto generated by spf13/cobra on 26-Mar-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 036db7c0..918f27aa 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -15,6 +15,7 @@ hrp har2case $har_path... [flags] ``` -h, --help help for har2case -d, --output-dir string specify output directory, default to the same dir with har file + -p, --profile string specify profile path to override headers and cookies -j, --to-json convert to JSON format (default true) -y, --to-yaml convert to YAML format ``` @@ -23,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 23-Mar-2022 +###### Auto generated by spf13/cobra on 26-Mar-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index d066ff6d..9853458d 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -34,4 +34,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 23-Mar-2022 +###### Auto generated by spf13/cobra on 26-Mar-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 55c4934c..12039f93 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -19,4 +19,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 23-Mar-2022 +###### Auto generated by spf13/cobra on 26-Mar-2022 diff --git a/examples/hrp/har/demo.har b/examples/data/har/demo.har similarity index 100% rename from examples/hrp/har/demo.har rename to examples/data/har/demo.har diff --git a/examples/hrp/har/demo.json b/examples/data/har/demo.json similarity index 100% rename from examples/hrp/har/demo.json rename to examples/data/har/demo.json diff --git a/examples/hrp/har/postman-echo.har b/examples/data/har/postman-echo.har similarity index 100% rename from examples/hrp/har/postman-echo.har rename to examples/data/har/postman-echo.har diff --git a/examples/data/har2case/profile.yml b/examples/data/har/profile.yml similarity index 74% rename from examples/data/har2case/profile.yml rename to examples/data/har/profile.yml index ef8695e3..69963ba2 100644 --- a/examples/data/har2case/profile.yml +++ b/examples/data/har/profile.yml @@ -1,4 +1,4 @@ headers: Content-Type: "application/x-www-form-urlencoded" cookies: - CASTGC: "TGT" \ No newline at end of file + UserName: "debugtalk" \ No newline at end of file diff --git a/examples/hrp/compat_test.go b/examples/hrp/compat_test.go index a66eb843..336b30d6 100644 --- a/examples/hrp/compat_test.go +++ b/examples/hrp/compat_test.go @@ -6,7 +6,7 @@ import ( "github.com/httprunner/httprunner/hrp" ) -// generated by examples/hrp/har/demo.har using HttpRunner v3.1.6 +// generated by examples/data/har/demo.har using HttpRunner v3.1.6 var ( demoHttpRunnerJSONPath hrp.TestCasePath = "demo_httprunner.json" demoHttpRunnerYAMLPath hrp.TestCasePath = "demo_httprunner.yaml" diff --git a/examples/hrp/har/postman-echo.yaml b/examples/hrp/har/postman-echo.yaml deleted file mode 100644 index ea92ed12..00000000 --- a/examples/hrp/har/postman-echo.yaml +++ /dev/null @@ -1,1101 +0,0 @@ -config: - name: testcase description -teststeps: - - name: "" - request: - method: GET - url: https://postman-echo.com/get - params: - foo1: bar1 - foo2: bar2 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: ea19464c-ddd4-4724-abe9-5e2b254c2723 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3ASAXM8INphoz4_-5nCeQNBtrlsWuHs5Mt.83PsbOXUZUoPolzR2vpghXLUghDPLyA3NSrVKI8A8ws - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.url - assert: equals - expect: https://postman-echo.com/get?foo1=bar1&foo2=bar2 - msg: assert response body url - - name: "" - request: - method: POST - url: https://postman-echo.com/post - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Content-Length: "58" - Content-Type: text/plain - Host: postman-echo.com - Postman-Token: 40756814-a974-4fcc-98d2-1f2aec73c295 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3Ack89N2nb1AxU-T-nxvJrvOS1KvUXbiU2.3nAhh%2FjA%2F%2FNvHtWI8NApXa1QWV3hDD6LBsfUwpIdYQc - body: This is expected to be sent back as part of response body. - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.data - assert: equals - expect: This is expected to be sent back as part of response body. - msg: assert response body data - - check: body.json - assert: equals - expect: null - msg: assert response body json - - check: body.url - assert: equals - expect: https://postman-echo.com/post - msg: assert response body url - - name: "" - request: - method: POST - url: https://postman-echo.com/post - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Content-Length: "19" - Content-Type: application/x-www-form-urlencoded - Host: postman-echo.com - Postman-Token: 93843e50-2fe8-422d-b900-91095f9f0cdb - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A4bF7QNsgYKOBRnxJEclo-wiPIm6YxzFY.zmgnSBoVtZ3C40cBCJPsFS6KXTPoQBlKdS2FIdoxFaA - body: foo1=bar1&foo2=bar2 - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.data - assert: equals - expect: "" - msg: assert response body data - - check: body.url - assert: equals - expect: https://postman-echo.com/post - msg: assert response body url - - name: "" - request: - method: PUT - url: https://postman-echo.com/put - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Content-Length: "58" - Content-Type: text/plain - Host: postman-echo.com - Postman-Token: 5d357b2b-0f10-4ded-bc9a-299ebef7a2d5 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A7Kp8q3TlXZgZpLiLQNE4OGvpaqJwWmWX.SkW6gD2iyLO%2FFZYMAbg0bTsfuHwnEBezprz6nbykPWg - body: This is expected to be sent back as part of response body. - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.data - assert: equals - expect: This is expected to be sent back as part of response body. - msg: assert response body data - - check: body.json - assert: equals - expect: null - msg: assert response body json - - check: body.url - assert: equals - expect: https://postman-echo.com/put - msg: assert response body url - - name: "" - request: - method: PATCH - url: https://postman-echo.com/patch - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Content-Length: "58" - Content-Type: text/plain - Host: postman-echo.com - Postman-Token: 27a30a79-5d88-43c0-8c83-fce5bb585729 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3ArMIVJXM1u78IGSzps0LYNjimloLEMdqk.6bzxgShLW4DTNlqRdZREK7OUV1kqu2kMHtEVxR9Xlyg - body: This is expected to be sent back as part of response body. - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.data - assert: equals - expect: This is expected to be sent back as part of response body. - msg: assert response body data - - check: body.json - assert: equals - expect: null - msg: assert response body json - - check: body.url - assert: equals - expect: https://postman-echo.com/patch - msg: assert response body url - - name: "" - request: - method: DELETE - url: https://postman-echo.com/delete - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Content-Length: "58" - Content-Type: text/plain - Host: postman-echo.com - Postman-Token: b11f7819-4c39-41b3-9d06-696b38c3e515 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AlTv3pBzULeMHqjWpJWW-rwLZYYdqzSyW.J5YSZCf1unKehq5zNyuee%2B2xYkqoK%2BcTPTr3RzHYtYM - body: This is expected to be sent back as part of response body. - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.data - assert: equals - expect: This is expected to be sent back as part of response body. - msg: assert response body data - - check: body.json - assert: equals - expect: null - msg: assert response body json - - check: body.url - assert: equals - expect: https://postman-echo.com/delete - msg: assert response body url - - name: "" - request: - method: GET - url: https://postman-echo.com/headers - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 1a4e2039-d29b-4ed7-89e9-584b354246be - User-Agent: PostmanRuntime/7.28.4 - my-sample-header: Lorem ipsum dolor sit amet - cookies: - sails.sid: s%3A6Sj7Mduyb72fC-X0OQbDmFqp77bVEgt8.b5X8H%2BtACzKfkUlH%2FBtSYH%2FdSQ5fHynzHjK8gE3s%2FpI - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/response-headers - params: - foo1: bar1 - foo2: bar2 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: b00d3c25-a84b-4152-bcf8-4c573c06024b - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AvvP5l4Bk7WCLBU9LNXalNk4w4x3Q_2Zi.JiGgykR8RlAGIdRWv%2FdCmCL0Tbmwyni9KkXXgnzn59s - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.foo1 - assert: equals - expect: bar1 - msg: assert response body foo1 - - check: body.foo2 - assert: equals - expect: bar2 - msg: assert response body foo2 - - name: "" - request: - method: GET - url: https://postman-echo.com/basic-auth - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Authorization: Basic cG9zdG1hbjpwYXNzd29yZA== - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: d9f810a2-292d-41c4-95e1-ec9f9ae778d6 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3APA71Iib2-7KqjRMajldmUsDqOqmRDB6-.zpTeobSmlq81Z7R%2FyL7q3o8%2FAP0tfOOZSPQdBlirJ6g - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.authenticated - assert: equals - expect: true - msg: assert response body authenticated - - name: "" - request: - method: GET - url: https://postman-echo.com/digest-auth - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Authorization: Digest username="postman", realm="Users", nonce="W7kT5VowsR0pcTfL9fTwZKv2tRdEiG6c", uri="/digest-auth", algorithm="MD5", response="bab1b1e6534f84b43e9deb17bca9371b" - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 42e8340a-852b-4c7a-ab7d-d0b027f044ca - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AT2IbNG9nLojvklvDr1mo2cCftGUgcAgU.f1XqnM5ebKiLtIs3CKYYvBo7j5iHwiP9EuG9i91RR%2FU - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.authenticated - assert: equals - expect: true - msg: assert response body authenticated - - name: "" - request: - method: GET - url: https://postman-echo.com/auth/hawk - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Authorization: Hawk id="dh37fgj492je", ts="1634367906", nonce="RZKGNz", mac="EASK1an/9fmDhFJcqH8XE4pTuUaSJisuQVM+NCOjNlM=" - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 46645864-583c-446b-9d36-9610fb114d99 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AWyHRwAoLc64u8sF_LqU0BUYAieEguHiH.gb%2BNYX72g6n5lHjLdl5K1hsKmLHYJUwoOwKkDWVl7qY - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.message - assert: equals - expect: Hawk Authentication Successful - msg: assert response body message - - name: "" - request: - method: GET - url: https://postman-echo.com/oauth1 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Authorization: OAuth oauth_consumer_key="RKCGzna7bv9YD57c",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1634367907",oauth_nonce="pAoTV0k5VZa",oauth_signature="ZTkfsaUA1B2s7kyl3HaFm1zFow4%3D" - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 3d9db9bb-5bcf-425e-b0e4-a958c07d7969 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AZQRuQaIb28umtrzP-HOj4fSqeag88Pvj.KVLylhlYJ3JKMHUS0UVeLCT6qRcBgQl%2BM14UxI7EgQs - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.message - assert: equals - expect: OAuth-1.0a signature verification was successful - msg: assert response body message - - check: body.status - assert: equals - expect: pass - msg: assert response body status - - name: "" - request: - method: GET - url: https://postman-echo.com/cookies/set - params: - foo1: bar1 - foo2: bar2 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: ff927796-58d3-4f43-8701-8411747c4313 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AsdmvN2_ZNE0YlwQY5GxY04ptWTOYR5NU.kkH0dnWlEMsblzPMurLX8nsQRRbRqLqteIhA0621onY - validate: - - check: status_code - assert: equals - expect: 302 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: text/plain; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/cookies - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: ff927796-58d3-4f43-8701-8411747c4313 - Referer: https://postman-echo.com/cookies/set?foo1=bar1&foo2=bar2 - User-Agent: PostmanRuntime/7.28.4 - cookies: - foo1: bar1 - foo2: bar2 - sails.sid: s%3AlVpTnkb0ofz6HC7QJMVtiRexW3u_onsT.rmsoerMcOQOu7KYPU80x%2FBiieqBESMNj%2FxuCvbbw%2BsQ - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/cookies - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 2dbc6d22-1713-4b96-a1a2-3358b1a1deaa - User-Agent: PostmanRuntime/7.28.4 - cookies: - foo1: bar1 - foo2: bar2 - sails.sid: s%3Avz13GzkqWaYvFuB3I35udi2vLsikZZgi.YgVWfqmyjPpEduyCIZDFGyDSPYY8%2FFM7HePC5Ok0hQM - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/cookies/delete - params: - foo1: "" - foo2: "" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 8837dd89-9db7-4f06-9187-e7a85a99b945 - User-Agent: PostmanRuntime/7.28.4 - cookies: - foo1: bar1 - foo2: bar2 - sails.sid: s%3AQ8MT5sT-2LAO0Rk7bNLLR18UQWgaJMsg.eOEyhDjqWGwn2rdqWeGLstPmrn5H1OUZGlDLuI%2F1Nng - validate: - - check: status_code - assert: equals - expect: 302 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: text/plain; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/cookies - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 8837dd89-9db7-4f06-9187-e7a85a99b945 - Referer: https://postman-echo.com/cookies/delete?foo1&foo2 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A1atMUPWbEEDiMqdbTqbddbqiFujSi1l2.6n40eqlOkTsKoB6K7xT98PrfQweiPlTjJTfZl%2FpAEsU - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/status/200 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 5f4c6d97-d476-407e-bbf9-532480f618d8 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A5p9FN9UVGZ9XJl6I9FXiz0AwIQRRU1ka.RFuMLR9arGQaLkM1gbvuPosvzPxsREHGEjjiVF4TXnQ - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.status - assert: equals - expect: 200 - msg: assert response body status - - name: "" - request: - method: GET - url: https://postman-echo.com/stream/5 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 24ca01aa-6c3f-4a78-a437-33dfa8dadd0f - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AFD7Hy01JAAenWz9SoQQhJxH4Qxel9sbP.%2Ba5JmTwqOpkc%2FAOLOzzsfStpK2MTfZCYXiCoA39Zt7w - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - name: "" - request: - method: GET - url: https://postman-echo.com/delay/2 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: d2ade32f-4bb8-4e6d-90d3-5fa7560def12 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AqSePO9_VmCbBbVvsCMYMHm3lShKdFNWU.RFuwKJdlZHVyB0gF1x2Yt78v5jKbese6f8HNPIjI5AY - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.delay - assert: equals - expect: "2" - msg: assert response body delay - - name: "" - request: - method: GET - url: https://postman-echo.com/encoding/utf8 - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: bd39f8e4-8072-4ec3-b498-3aaacb621544 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AXrCX-GaGzqizPQY2AdLTLNPO_cFgVsGD.BwOoj2gClsAzDrsP0%2FObypcumuYCfV%2F4vHCrKIWdTAQ - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: text/html; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/gzip - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: ef40db18-75f9-4d0c-9fe8-94274a0a589e - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AdknETdvYiCwRbtxpWR58ZhmohmZJOqdI.SA8%2FR072CZkldOTuVv7TYyKpzEQWpkt%2F2YTTTBFn%2BzU - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.gzipped - assert: equals - expect: true - msg: assert response body gzipped - - check: body.method - assert: equals - expect: GET - msg: assert response body method - - name: "" - request: - method: GET - url: https://postman-echo.com/deflate - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 06b47e94-9131-4ab7-8d0e-d0990f1a1144 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AdknETdvYiCwRbtxpWR58ZhmohmZJOqdI.SA8%2FR072CZkldOTuVv7TYyKpzEQWpkt%2F2YTTTBFn%2BzU - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.deflated - assert: equals - expect: true - msg: assert response body deflated - - check: body.method - assert: equals - expect: GET - msg: assert response body method - - name: "" - request: - method: GET - url: https://postman-echo.com/ip - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 246c423e-9285-4fad-b471-434bf4bf3369 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A_sZ_Nn5QQ0b2Swfp9tMHX9CWKJb9X3is.fa%2FQ9D9WhuFBgpatC2Yo33cPynch4YqbG%2Fw9iB92Jxo - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.ip - assert: equals - expect: 122.14.229.79 - msg: assert response body ip - - name: "" - request: - method: GET - url: https://postman-echo.com/time/now - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: e1107fa9-80cb-4e69-b3dd-6fd0c92832b1 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AFqdFnM7dGE1ds2DZfijQergoGKJKdivs.TZy6jaQuf3wKK7VHSuQRNwDrZuuvCx3pGhhj7lKouQs - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: text/html; charset=utf-8 - msg: assert response header Content-Type - - name: "" - request: - method: GET - url: https://postman-echo.com/time/valid - params: - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 05eb8403-8a83-4bde-bdd4-67952910c00f - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AFqdFnM7dGE1ds2DZfijQergoGKJKdivs.TZy6jaQuf3wKK7VHSuQRNwDrZuuvCx3pGhhj7lKouQs - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.valid - assert: equals - expect: true - msg: assert response body valid - - name: "" - request: - method: GET - url: https://postman-echo.com/time/format - params: - format: mm - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 7bab6bdc-6fe5-4eb8-aff0-3cfa08e5a823 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3Ai_9yOOqBlD9Nq0-5kptXL_qLhgITKpaZ.HU5sTJC0jVIzJvykONaDFYTiMZrZpQgdiwMInhSADss - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.format - assert: equals - expect: "20" - msg: assert response body format - - name: "" - request: - method: GET - url: https://postman-echo.com/time/unit - params: - timestamp: "2016-10-10" - unit: day - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 8dbb7595-3ff0-47cd-8883-4c1f24a840ef - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AlSI63UO-j2SWcK0YQfFAScLu2YKvhtlr.0wPoZkmPHUiNtTVy55Bdt9ulnQxk%2FahmG6a7%2BE6gtg8 - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.unit - assert: equals - expect: 1 - msg: assert response body unit - - name: "" - request: - method: GET - url: https://postman-echo.com/time/add - params: - timestamp: "2016-10-10" - years: "100" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 12c5137f-ee8e-48c2-b1b7-99c85f0667e4 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AlSI63UO-j2SWcK0YQfFAScLu2YKvhtlr.0wPoZkmPHUiNtTVy55Bdt9ulnQxk%2FahmG6a7%2BE6gtg8 - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.sum - assert: equals - expect: Sat Oct 10 2116 00:00:00 GMT+0000 - msg: assert response body sum - - name: "" - request: - method: GET - url: https://postman-echo.com/time/subtract - params: - timestamp: "2016-10-10" - years: "50" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: d903ee32-4361-44a4-af56-819e7fa10cc4 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A5OS8kEURZ8ZYZzfO7we0KvxaGI1AdMRZ.L6C2S4%2B6rTQd5qdQufDhV9rDv9CJgENLudOAk9h0Yow - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.difference - assert: equals - expect: Mon Oct 10 1966 00:00:00 GMT+0000 - msg: assert response body difference - - name: "" - request: - method: GET - url: https://postman-echo.com/time/start - params: - timestamp: "2016-10-10" - unit: month - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 2d666d32-2815-45be-ae8d-266eea519043 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3A2PKCLJCVRo_5V_uagkV5b3Kn9dV0eQUm.Dp5OFZ%2FCtOcDKqB8y8yywFHO6LbN9oe10o4DQ%2FnoKRk - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.start - assert: equals - expect: Sat Oct 01 2016 00:00:00 GMT+0000 - msg: assert response body start - - name: "" - request: - method: GET - url: https://postman-echo.com/time/object - params: - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 6ecae5c7-b9b4-450d-865c-10aea2f6384c - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AWJZnlAAItW8H8a4UMGox8Iz7cv3TM5Zq.YRYNuDnd6fkHDDvlbilW9q4AkvSPwE8SsBs2JRC52HU - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.date - assert: equals - expect: 10 - msg: assert response body date - - check: body.hours - assert: equals - expect: 0 - msg: assert response body hours - - check: body.milliseconds - assert: equals - expect: 0 - msg: assert response body milliseconds - - check: body.minutes - assert: equals - expect: 0 - msg: assert response body minutes - - check: body.months - assert: equals - expect: 9 - msg: assert response body months - - check: body.seconds - assert: equals - expect: 0 - msg: assert response body seconds - - check: body.years - assert: equals - expect: 2016 - msg: assert response body years - - name: "" - request: - method: GET - url: https://postman-echo.com/time/before - params: - target: "2017-10-10" - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: faaa8cb6-13c5-4d0c-a7d2-133520637dde - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AJSsXggdxTpnvv6WVFqDrJ8Sjeuu77nE4.IcUuska8iBP1lkpKISqwIPOaqy5qLB%2F2o8v2Txs%2F5f8 - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.before - assert: equals - expect: true - msg: assert response body before - - name: "" - request: - method: GET - url: https://postman-echo.com/time/after - params: - target: "2017-10-10" - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 28c6c8f1-bb76-4fce-986c-adc2fd5df80d - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AQ9JCfRzQhaoMt6eD7gx_qk3JQ8CWnAxO.g3tHBGmTN8Vc1mqWWnSqGV1VOQdmKk8HG3z29e%2FBzhA - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.after - assert: equals - expect: false - msg: assert response body after - - name: "" - request: - method: GET - url: https://postman-echo.com/time/between - params: - end: "2019-10-10" - start: "2017-10-10" - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 32aaca4e-02a8-4559-9368-5705a1a65e19 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AYE-1ygWzH5aScrDeYC7-Q8-dC1A5zkJv.XyirbigQ0duqX6jD9om1q%2FS%2FqkhbFl43yu7HHYciXkI - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.between - assert: equals - expect: false - msg: assert response body between - - name: "" - request: - method: GET - url: https://postman-echo.com/time/leap - params: - timestamp: "2016-10-10" - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: ff77428a-b157-463a-91e0-e5126d99d6c0 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AYE-1ygWzH5aScrDeYC7-Q8-dC1A5zkJv.XyirbigQ0duqX6jD9om1q%2FS%2FqkhbFl43yu7HHYciXkI - validate: - - check: status_code - assert: equals - expect: 200 - msg: assert response status code - - check: headers."Content-Type" - assert: equals - expect: application/json; charset=utf-8 - msg: assert response header Content-Type - - check: body.leap - assert: equals - expect: true - msg: assert response body leap - - name: "" - request: - method: GET - url: https://postman-echo.com/digest-auth - headers: - Accept: '*/*' - Accept-Encoding: gzip, deflate, br - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - Postman-Token: 8f6b453b-580c-44bc-8f9f-b2baa64ab530 - User-Agent: PostmanRuntime/7.28.4 - cookies: - sails.sid: s%3AhLPrbCV0ByxRorQusdRky8bws0S2qQjf.V4SIDOu%2BdIgGVSCA5qvRYwhi3xR%2Bd0R9gL9RDUPdpI4 - validate: - - check: status_code - assert: equals - expect: 401 - msg: assert response status code diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go index 0b37a296..ee8380c1 100644 --- a/hrp/cmd/har2case.go +++ b/hrp/cmd/har2case.go @@ -34,6 +34,12 @@ var har2caseCmd = &cobra.Command{ if outputDir != "" { har.SetOutputDir(outputDir) } + + // specify profile + if profilePath != "" { + har.SetProfile(profilePath) + } + // generate json/yaml files if genYAMLFlag { outputPath, err = har.GenYAML() @@ -54,6 +60,7 @@ var ( genJSONFlag bool genYAMLFlag bool outputDir string + profilePath string ) func init() { @@ -61,4 +68,5 @@ func init() { har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", true, "convert to JSON format") har2caseCmd.Flags().BoolVarP(&genYAMLFlag, "to-yaml", "y", false, "convert to YAML format") har2caseCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") + har2caseCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies") } diff --git a/hrp/internal/builtin/function.go b/hrp/internal/builtin/function.go index 40a676a2..ec8abf04 100644 --- a/hrp/internal/builtin/function.go +++ b/hrp/internal/builtin/function.go @@ -6,7 +6,6 @@ import ( "encoding/csv" "encoding/hex" builtinJSON "encoding/json" - "errors" "fmt" "math" "math/rand" @@ -17,6 +16,7 @@ import ( "strings" "time" + "github.com/pkg/errors" "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" @@ -235,13 +235,12 @@ func LoadFile(path string, structObj interface{}) (err error) { log.Info().Str("path", path).Msg("load file") file, err := readFile(path) if err != nil { - log.Error().Err(err).Msg("read file failed") - return err + return errors.Wrap(err, "read file failed") } ext := filepath.Ext(path) switch ext { - case ".json": + case ".json", ".har": decoder := json.NewDecoder(bytes.NewReader(file)) decoder.UseNumber() err = decoder.Decode(structObj) diff --git a/hrp/internal/har2case/core.go b/hrp/internal/har2case/core.go index 5cfa5f22..2848fdba 100644 --- a/hrp/internal/har2case/core.go +++ b/hrp/internal/har2case/core.go @@ -3,9 +3,7 @@ package har2case import ( "encoding/base64" "fmt" - "io" "net/url" - "os" "path/filepath" "sort" "strings" @@ -31,13 +29,25 @@ func NewHAR(path string) *har { } type har struct { - path string - filterStr string - excludeStr string - outputDir string + path string + filterStr string + excludeStr string + profileJSON map[string]interface{} + outputDir string +} + +func (h *har) SetProfile(path string) { + log.Info().Str("path", path).Msg("set profile") + h.profileJSON = make(map[string]interface{}) + err := builtin.LoadFile(path, h.profileJSON) + if err != nil { + log.Warn().Str("path", path). + Msg("invalid profile format, ignore!") + } } func (h *har) SetOutputDir(dir string) { + log.Info().Str("dir", dir).Msg("set output directory") h.outputDir = dir } @@ -93,23 +103,11 @@ func (h *har) makeTestCase() (*hrp.TCase, error) { } func (h *har) load() (*Har, error) { - fp, err := os.Open(h.path) - if err != nil { - return nil, fmt.Errorf("open: %w", err) - } - - data, err := io.ReadAll(fp) - fp.Close() - if err != nil { - return nil, fmt.Errorf("read: %w", err) - } - har := &Har{} - err = json.Unmarshal(data, har) + err := builtin.LoadFile(h.path, har) if err != nil { - return nil, fmt.Errorf("json.Unmarshal error: %w", err) + return nil, errors.Wrap(err, "load har failed") } - return har, nil } @@ -147,6 +145,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { Request: &hrp.Request{}, Validators: make([]interface{}, 0), }, + profileJSON: h.profileJSON, } if err := step.makeRequestMethod(entry); err != nil { return nil, err @@ -174,6 +173,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { type tStep struct { hrp.TStep + profileJSON map[string]interface{} } func (s *tStep) makeRequestMethod(entry *Entry) error { @@ -201,6 +201,21 @@ func (s *tStep) makeRequestParams(entry *Entry) error { func (s *tStep) makeRequestCookies(entry *Entry) error { s.Request.Cookies = make(map[string]string) + cookies, ok := s.profileJSON["cookies"] + if ok { + // use cookies from profile + cookies, ok := cookies.(map[string]interface{}) + if ok { + for k, v := range cookies { + s.Request.Cookies[k] = fmt.Sprintf("%v", v) + } + return nil + } + log.Warn().Interface("cookies", cookies). + Msg("cookies from profile is not a map, ignore!") + } + + // use cookies from har for _, cookie := range entry.Request.Cookies { s.Request.Cookies[cookie.Name] = cookie.Value } @@ -209,6 +224,21 @@ func (s *tStep) makeRequestCookies(entry *Entry) error { func (s *tStep) makeRequestHeaders(entry *Entry) error { s.Request.Headers = make(map[string]string) + headers, ok := s.profileJSON["headers"] + if ok { + // use headers from profile + cookies, ok := headers.(map[string]interface{}) + if ok { + for k, v := range cookies { + s.Request.Headers[k] = fmt.Sprintf("%v", v) + } + return nil + } + log.Warn().Interface("headers", headers). + Msg("headers from profile is not a map, ignore!") + } + + // use headers from har for _, header := range entry.Request.Headers { if strings.EqualFold(header.Name, "cookie") { continue diff --git a/hrp/internal/har2case/core_test.go b/hrp/internal/har2case/core_test.go index 385ec07e..d9953a98 100644 --- a/hrp/internal/har2case/core_test.go +++ b/hrp/internal/har2case/core_test.go @@ -9,8 +9,9 @@ import ( ) var ( - harPath = "../../../examples/hrp/har/demo.har" - harPath2 = "../../../examples/hrp/har/postman-echo.har" + harPath = "../../../examples/data/har/demo.har" + harPath2 = "../../../examples/data/har/postman-echo.har" + profilePath = "../../../examples/data/har/profile.yml" ) func TestGenJSON(t *testing.T) { @@ -47,6 +48,26 @@ func TestLoadHAR(t *testing.T) { } } +func TestLoadHARWithProfile(t *testing.T) { + har := NewHAR(harPath) + har.SetProfile(profilePath) + _, err := har.load() + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, + map[string]interface{}{"Content-Type": "application/x-www-form-urlencoded"}, + har.profileJSON["headers"]) { + t.Fail() + } + if !assert.Equal(t, + map[string]interface{}{"UserName": "debugtalk"}, + har.profileJSON["cookies"]) { + t.Fail() + } +} + func TestMakeTestCase(t *testing.T) { har := NewHAR(harPath) tCase, err := har.makeTestCase() @@ -115,12 +136,105 @@ func TestMakeTestCase(t *testing.T) { } func TestGetFilenameWithoutExtension(t *testing.T) { - filename := getFilenameWithoutExtension("../../../examples/hrp/har/postman-echo.har") + filename := getFilenameWithoutExtension(harPath2) if !assert.Equal(t, "postman-echo", filename) { t.Fail() } } +func TestMakeRequestHeaders(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Request: Request{ + Method: "POST", + Headers: []NVP{ + {Name: "Content-Type", Value: "application/json; charset=utf-8"}, + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, map[string]string{ + "Content-Type": "application/json; charset=utf-8", + }, step.Request.Headers) { + t.Fail() + } +} + +func TestMakeRequestHeadersWithProfile(t *testing.T) { + har := NewHAR("") + har.SetProfile(profilePath) + entry := &Entry{ + Request: Request{ + Method: "POST", + Headers: []NVP{ + {Name: "Content-Type", Value: "application/json; charset=utf-8"}, + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + }, step.Request.Headers) { + t.Fail() + } +} + +func TestMakeRequestCookies(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Request: Request{ + Method: "POST", + Cookies: []Cookie{ + {Name: "abc", Value: "123"}, + {Name: "UserName", Value: "leolee"}, + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, map[string]string{ + "abc": "123", + "UserName": "leolee", + }, step.Request.Cookies) { + t.Fail() + } +} + +func TestMakeRequestCookiesWithProfile(t *testing.T) { + har := NewHAR("") + har.SetProfile(profilePath) + entry := &Entry{ + Request: Request{ + Method: "POST", + Cookies: []Cookie{ + {Name: "abc", Value: "123"}, + {Name: "UserName", Value: "leolee"}, + }, + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fail() + } + + if !assert.Equal(t, map[string]string{ + "UserName": "debugtalk", + }, step.Request.Cookies) { + t.Fail() + } +} + func TestMakeRequestDataParams(t *testing.T) { har := NewHAR("") entry := &Entry{ From 6c17ea3adaa848eca76071a12579de1619bff646 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 26 Mar 2022 11:09:32 +0800 Subject: [PATCH 07/13] change: remove har2case, move all features to go version --- .github/workflows/smoketest.yml | 2 - .github/workflows/unittest.yml | 1 - docs/CHANGELOG.md | 3 +- hrp/convert.go | 2 - hrp/internal/builtin/function.go | 1 + hrp/internal/har2case/core.go | 22 +- hrp/internal/har2case/core_test.go | 4 +- httprunner/cli.py | 17 +- httprunner/ext/har2case/__init__.py | 70 ----- httprunner/ext/har2case/core.py | 385 -------------------------- httprunner/ext/har2case/core_test.py | 180 ------------ httprunner/ext/har2case/utils.py | 130 --------- httprunner/ext/har2case/utils_test.py | 59 ---- httprunner/runner.py | 2 +- poetry.lock | 6 +- pyproject.toml | 3 +- 16 files changed, 22 insertions(+), 865 deletions(-) delete mode 100644 httprunner/ext/har2case/__init__.py delete mode 100644 httprunner/ext/har2case/core.py delete mode 100644 httprunner/ext/har2case/core_test.py delete mode 100644 httprunner/ext/har2case/utils.py delete mode 100644 httprunner/ext/har2case/utils_test.py diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index 28196ba4..a11870b6 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -36,10 +36,8 @@ jobs: - name: Test commands run: | poetry run hrun -V - poetry run har2case -h poetry run httprunner run -h poetry run httprunner startproject -h - poetry run httprunner har2case -h - name: Run smoketest - postman echo run: | poetry run hrun examples/postman_echo/request_methods diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index b87734fd..fb6e3b5d 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -33,7 +33,6 @@ jobs: poetry run httprunner poetry run hmake poetry run hrun - poetry run har2case poetry run coverage run --source=httprunner -m pytest httprunner - name: coverage report run: | diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5aca4971..37ac2752 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,7 +12,8 @@ **python version** -- change: remove locust, you should run load tests with go version +- change: remove har2case, move all features to go version, replace with `hrp run` +- change: remove locust, you should run load tests with go version, replace with `hrp boom` - change: remove fastapi and uvicorn dependencies - fix: ignore exceptions when reporting GA events - fix: remove misuse of NoReturn in Python typing diff --git a/hrp/convert.go b/hrp/convert.go index af0048b5..68297e04 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -140,8 +140,6 @@ func (tc *TCase) ToTestCase() (*TestCase, error) { return testCase, nil } -var ErrUnsupportedFileExt = fmt.Errorf("unsupported testcase file extension") - // APIPath implements IAPI interface. type APIPath string diff --git a/hrp/internal/builtin/function.go b/hrp/internal/builtin/function.go index ec8abf04..e271b447 100644 --- a/hrp/internal/builtin/function.go +++ b/hrp/internal/builtin/function.go @@ -231,6 +231,7 @@ func Interface2Float64(i interface{}) (float64, error) { var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension") +// LoadFile loads file content with file extension and assigns to structObj func LoadFile(path string, structObj interface{}) (err error) { log.Info().Str("path", path).Msg("load file") file, err := readFile(path) diff --git a/hrp/internal/har2case/core.go b/hrp/internal/har2case/core.go index 2848fdba..10d75f0a 100644 --- a/hrp/internal/har2case/core.go +++ b/hrp/internal/har2case/core.go @@ -29,17 +29,17 @@ func NewHAR(path string) *har { } type har struct { - path string - filterStr string - excludeStr string - profileJSON map[string]interface{} - outputDir string + path string + filterStr string + excludeStr string + profile map[string]interface{} + outputDir string } func (h *har) SetProfile(path string) { log.Info().Str("path", path).Msg("set profile") - h.profileJSON = make(map[string]interface{}) - err := builtin.LoadFile(path, h.profileJSON) + h.profile = make(map[string]interface{}) + err := builtin.LoadFile(path, h.profile) if err != nil { log.Warn().Str("path", path). Msg("invalid profile format, ignore!") @@ -145,7 +145,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { Request: &hrp.Request{}, Validators: make([]interface{}, 0), }, - profileJSON: h.profileJSON, + profile: h.profile, } if err := step.makeRequestMethod(entry); err != nil { return nil, err @@ -173,7 +173,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { type tStep struct { hrp.TStep - profileJSON map[string]interface{} + profile map[string]interface{} } func (s *tStep) makeRequestMethod(entry *Entry) error { @@ -201,7 +201,7 @@ func (s *tStep) makeRequestParams(entry *Entry) error { func (s *tStep) makeRequestCookies(entry *Entry) error { s.Request.Cookies = make(map[string]string) - cookies, ok := s.profileJSON["cookies"] + cookies, ok := s.profile["cookies"] if ok { // use cookies from profile cookies, ok := cookies.(map[string]interface{}) @@ -224,7 +224,7 @@ func (s *tStep) makeRequestCookies(entry *Entry) error { func (s *tStep) makeRequestHeaders(entry *Entry) error { s.Request.Headers = make(map[string]string) - headers, ok := s.profileJSON["headers"] + headers, ok := s.profile["headers"] if ok { // use headers from profile cookies, ok := headers.(map[string]interface{}) diff --git a/hrp/internal/har2case/core_test.go b/hrp/internal/har2case/core_test.go index d9953a98..93fb5ef6 100644 --- a/hrp/internal/har2case/core_test.go +++ b/hrp/internal/har2case/core_test.go @@ -58,12 +58,12 @@ func TestLoadHARWithProfile(t *testing.T) { if !assert.Equal(t, map[string]interface{}{"Content-Type": "application/x-www-form-urlencoded"}, - har.profileJSON["headers"]) { + har.profile["headers"]) { t.Fail() } if !assert.Equal(t, map[string]interface{}{"UserName": "debugtalk"}, - har.profileJSON["cookies"]) { + har.profile["cookies"]) { t.Fail() } } diff --git a/httprunner/cli.py b/httprunner/cli.py index 9041258d..033312c3 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -8,10 +8,9 @@ from loguru import logger from httprunner import __description__, __version__ from httprunner.compat import ensure_cli_args -from httprunner.ext.har2case import init_har2case_parser, main_har2case from httprunner.make import init_make_parser, main_make from httprunner.scaffold import init_parser_scaffold, main_scaffold -from httprunner.utils import init_sentry_sdk, ga_client +from httprunner.utils import ga_client, init_sentry_sdk init_sentry_sdk() @@ -67,7 +66,6 @@ def main(): subparsers = parser.add_subparsers(help="sub-command help") sub_parser_run = init_parser_run(subparsers) sub_parser_scaffold = init_parser_scaffold(subparsers) - sub_parser_har2case = init_har2case_parser(subparsers) sub_parser_make = init_make_parser(subparsers) if len(sys.argv) == 1: @@ -85,9 +83,6 @@ def main(): elif sys.argv[1] == "startproject": # httprunner startproject sub_parser_scaffold.print_help() - elif sys.argv[1] == "har2case": - # httprunner har2case - sub_parser_har2case.print_help() elif sys.argv[1] == "run": # httprunner run pytest.main(["-h"]) @@ -116,8 +111,6 @@ def main(): sys.exit(main_run(extra_args)) elif sys.argv[1] == "startproject": main_scaffold(args) - elif sys.argv[1] == "har2case": - main_har2case(args) elif sys.argv[1] == "make": main_make(args.testcase_path) @@ -150,13 +143,5 @@ def main_make_alias(): main() -def main_har2case_alias(): - """ command alias - har2case = httprunner har2case - """ - sys.argv.insert(1, "har2case") - main() - - if __name__ == "__main__": main() diff --git a/httprunner/ext/har2case/__init__.py b/httprunner/ext/har2case/__init__.py deleted file mode 100644 index 29920118..00000000 --- a/httprunner/ext/har2case/__init__.py +++ /dev/null @@ -1,70 +0,0 @@ -""" Convert HAR (HTTP Archive) to YAML/JSON testcase for HttpRunner. - -Usage: - # convert to JSON format testcase - $ hrun har2case demo.har - - # convert to YAML format testcase - $ hrun har2case demo.har -2y - -""" - -from httprunner.ext.har2case.core import HarParser -from httprunner.utils import ga_client - - -def init_har2case_parser(subparsers): - """ HAR converter: parse command line options and run commands. - """ - parser = subparsers.add_parser( - "har2case", - help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.", - ) - parser.add_argument("har_source_file", nargs="?", help="Specify HAR source file") - parser.add_argument( - "-2y", - "--to-yml", - "--to-yaml", - dest="to_yaml", - action="store_true", - help="Convert to YAML format, if not specified, convert to pytest format by default.", - ) - parser.add_argument( - "-2j", - "--to-json", - dest="to_json", - action="store_true", - help="Convert to JSON format, if not specified, convert to pytest format by default.", - ) - parser.add_argument( - "--filter", - help="Specify filter keyword, only url include filter string will be converted.", - ) - parser.add_argument( - "--exclude", - help="Specify exclude keyword, url that includes exclude string will be ignored, " - "multiple keywords can be joined with '|'", - ) - parser.add_argument( - "--profile", - dest="profile", - help="Specify yaml file to overwrite headers and cookies in HAR.", - ) - - return parser - - -def main_har2case(args): - har_source_file = args.har_source_file - - if args.to_yaml: - output_file_type = "YAML" - elif args.to_json: - output_file_type = "JSON" - else: - output_file_type = "pytest" - - ga_client.track_event("ConvertTests", f"har2case {output_file_type}") - HarParser(har_source_file, args.filter, args.exclude, args.profile).gen_testcase(output_file_type) - - return 0 diff --git a/httprunner/ext/har2case/core.py b/httprunner/ext/har2case/core.py deleted file mode 100644 index efff6a4c..00000000 --- a/httprunner/ext/har2case/core.py +++ /dev/null @@ -1,385 +0,0 @@ -import base64 -import json -import os -import sys -import urllib.parse as urlparse -from typing import Text - -from httprunner.compat import ensure_path_sep -from loguru import logger -from sentry_sdk import capture_exception - -from httprunner.ext.har2case import utils -from httprunner.make import make_testcase, format_pytest_with_black -from httprunner.loader import load_test_file - -try: - from json.decoder import JSONDecodeError -except ImportError: - JSONDecodeError = ValueError - - -def ensure_file_path(path: Text) -> Text: - - if not path or not path.endswith(".har"): - logger.error("HAR file not specified.") - sys.exit(1) - - path = ensure_path_sep(path) - if not os.path.isfile(path): - logger.error(f"HAR file not exists: {path}") - sys.exit(1) - - if not os.path.isabs(path): - path = os.path.join(os.getcwd(), path) - - return path - - -class HarParser(object): - def __init__(self, har_file_path, filter_str=None, exclude_str=None, profile=None): - self.har_file_path = ensure_file_path(har_file_path) - self.filter_str = filter_str - self.exclude_str = exclude_str or "" - self.profile = profile and load_test_file(profile) - - def __make_request_url(self, teststep_dict, entry_json): - """ parse HAR entry request url and queryString, and make teststep url and params - - Args: - entry_json (dict): - { - "request": { - "url": "https://httprunner.top/home?v=1&w=2", - "queryString": [ - {"name": "v", "value": "1"}, - {"name": "w", "value": "2"} - ], - }, - "response": {} - } - - Returns: - { - "name: "/home", - "request": { - url: "https://httprunner.top/home", - params: {"v": "1", "w": "2"} - } - } - - """ - request_params = utils.convert_list_to_dict( - entry_json["request"].get("queryString", []) - ) - - url = entry_json["request"].get("url") - if not url: - logger.exception("url missed in request.") - sys.exit(1) - - parsed_object = urlparse.urlparse(url) - if request_params: - parsed_object = parsed_object._replace(query="") - teststep_dict["request"]["url"] = parsed_object.geturl() - teststep_dict["request"]["params"] = request_params - else: - teststep_dict["request"]["url"] = url - - teststep_dict["name"] = parsed_object.path - - def __make_request_method(self, teststep_dict, entry_json): - """ parse HAR entry request method, and make teststep method. - """ - method = entry_json["request"].get("method") - if not method: - logger.exception("method missed in request.") - sys.exit(1) - - teststep_dict["request"]["method"] = method - - def __make_request_cookies(self, teststep_dict, entry_json): - if self.profile and self.profile.get("cookies"): - teststep_dict["request"]["cookies"] = self.profile.get("cookies") - else: - cookies = {} - for cookie in entry_json["request"].get("cookies", []): - cookies[cookie["name"]] = cookie["value"] - - if cookies: - teststep_dict["request"]["cookies"] = cookies - - def __make_request_headers(self, teststep_dict, entry_json): - """ parse HAR entry request headers, and make teststep headers. - header in IGNORE_REQUEST_HEADERS will be ignored. - - Args: - entry_json (dict): - { - "request": { - "headers": [ - {"name": "Host", "value": "httprunner.top"}, - {"name": "Content-Type", "value": "application/json"}, - {"name": "User-Agent", "value": "iOS/10.3"} - ], - }, - "response": {} - } - - Returns: - { - "request": { - headers: {"Content-Type": "application/json"} - } - - """ - if self.profile and self.profile.get("headers"): - teststep_dict["request"]["headers"] = self.profile.get("headers") - else: - teststep_headers = {} - for header in entry_json["request"].get("headers", []): - if header["name"] == "cookie" or header["name"].startswith(":"): - continue - - teststep_headers[header["name"]] = header["value"] - - if teststep_headers: - teststep_dict["request"]["headers"] = teststep_headers - - def _make_request_data(self, teststep_dict, entry_json): - """ parse HAR entry request data, and make teststep request data - - Args: - entry_json (dict): - { - "request": { - "method": "POST", - "postData": { - "mimeType": "application/x-www-form-urlencoded; charset=utf-8", - "params": [ - {"name": "a", "value": 1}, - {"name": "b", "value": "2"} - } - }, - }, - "response": {...} - } - - - Returns: - { - "request": { - "method": "POST", - "data": {"v": "1", "w": "2"} - } - } - - """ - method = entry_json["request"].get("method") - if method in ["POST", "PUT", "PATCH"]: - postData = entry_json["request"].get("postData", {}) - mimeType = postData.get("mimeType") - - # Note that text and params fields are mutually exclusive. - if "text" in postData: - post_data = postData.get("text") - else: - params = postData.get("params", []) - post_data = utils.convert_list_to_dict(params) - - request_data_key = "data" - if not mimeType: - pass - elif mimeType.startswith("application/json"): - try: - post_data = json.loads(post_data) - request_data_key = "json" - except JSONDecodeError: - pass - elif mimeType.startswith("application/x-www-form-urlencoded"): - post_data = utils.convert_x_www_form_urlencoded_to_dict(post_data) - else: - # TODO: make compatible with more mimeType - pass - - teststep_dict["request"][request_data_key] = post_data - - def _make_validate(self, teststep_dict, entry_json): - """ parse HAR entry response and make teststep validate. - - Args: - entry_json (dict): - { - "request": {}, - "response": { - "status": 200, - "headers": [ - { - "name": "Content-Type", - "value": "application/json; charset=utf-8" - }, - ], - "content": { - "size": 71, - "mimeType": "application/json; charset=utf-8", - "text": "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=", - "encoding": "base64" - } - } - } - - Returns: - { - "validate": [ - {"eq": ["status_code", 200]} - ] - } - - """ - teststep_dict["validate"].append( - {"eq": ["status_code", entry_json["response"].get("status")]} - ) - - resp_content_dict = entry_json["response"].get("content") - - headers_mapping = utils.convert_list_to_dict( - entry_json["response"].get("headers", []) - ) - if "Content-Type" in headers_mapping: - teststep_dict["validate"].append( - {"eq": ["headers.Content-Type", headers_mapping["Content-Type"]]} - ) - - text = resp_content_dict.get("text") - if not text: - return - - mime_type = resp_content_dict.get("mimeType") - if mime_type and mime_type.startswith("application/json"): - - encoding = resp_content_dict.get("encoding") - if encoding and encoding == "base64": - content = base64.b64decode(text) - try: - content = content.decode("utf-8") - except UnicodeDecodeError: - logger.warning(f"failed to decode base64 content with utf-8 !") - return - else: - content = text - - try: - resp_content_json = json.loads(content) - except JSONDecodeError: - logger.warning(f"response content can not be loaded as json: {content}") - return - - if not isinstance(resp_content_json, dict): - # e.g. ['a', 'b'] - return - - for key, value in resp_content_json.items(): - if isinstance(value, (dict, list)): - continue - - teststep_dict["validate"].append({"eq": ["body.{}".format(key), value]}) - - def _prepare_teststep(self, entry_json): - """ extract info from entry dict and make teststep - - Args: - entry_json (dict): - { - "request": { - "method": "POST", - "url": "https://httprunner.top/api/v1/Account/Login", - "headers": [], - "queryString": [], - "postData": {}, - }, - "response": { - "status": 200, - "headers": [], - "content": {} - } - } - - """ - teststep_dict = {"name": "", "request": {}, "validate": []} - - self.__make_request_url(teststep_dict, entry_json) - self.__make_request_method(teststep_dict, entry_json) - self.__make_request_cookies(teststep_dict, entry_json) - self.__make_request_headers(teststep_dict, entry_json) - self._make_request_data(teststep_dict, entry_json) - self._make_validate(teststep_dict, entry_json) - - return teststep_dict - - def _prepare_config(self): - """ prepare config block. - """ - return {"name": "testcase description", "variables": {}, "verify": False} - - def _prepare_teststeps(self): - """ make teststep list. - teststeps list are parsed from HAR log entries list. - - """ - - def is_exclude(url, exclude_str): - exclude_str_list = exclude_str.split("|") - for exclude_str in exclude_str_list: - if exclude_str and exclude_str in url: - return True - - return False - - teststeps = [] - log_entries = utils.load_har_log_entries(self.har_file_path) - for entry_json in log_entries: - url = entry_json["request"].get("url") - if self.filter_str and self.filter_str not in url: - continue - - if is_exclude(url, self.exclude_str): - continue - - teststeps.append(self._prepare_teststep(entry_json)) - - return teststeps - - def _make_testcase(self): - """ Extract info from HAR file and prepare for testcase - """ - logger.info("Extract info from HAR file and prepare for testcase.") - - config = self._prepare_config() - teststeps = self._prepare_teststeps() - - testcase = {"config": config, "teststeps": teststeps} - return testcase - - def gen_testcase(self, file_type="pytest"): - logger.info(f"Start to generate testcase from {self.har_file_path}") - harfile = os.path.splitext(self.har_file_path)[0] - - try: - testcase = self._make_testcase() - except Exception as ex: - capture_exception(ex) - raise - - if file_type == "JSON": - output_testcase_file = f"{harfile}.json" - utils.dump_json(testcase, output_testcase_file) - elif file_type == "YAML": - output_testcase_file = f"{harfile}.yml" - utils.dump_yaml(testcase, output_testcase_file) - else: - # default to generate pytest file - testcase["config"]["path"] = self.har_file_path - output_testcase_file = make_testcase(testcase) - format_pytest_with_black(output_testcase_file) - - logger.info(f"generated testcase: {output_testcase_file}") diff --git a/httprunner/ext/har2case/core_test.py b/httprunner/ext/har2case/core_test.py deleted file mode 100644 index 0c39df1c..00000000 --- a/httprunner/ext/har2case/core_test.py +++ /dev/null @@ -1,180 +0,0 @@ -import os - -from httprunner.ext.har2case.core import HarParser -from httprunner.ext.har2case.utils import load_har_log_entries -from httprunner.ext.har2case.utils_test import TestHar2CaseUtils - - -class TestHar(TestHar2CaseUtils): - def setUp(self): - self.data_dir = os.path.join(os.getcwd(), "examples", "data", "har2case") - self.har_path = os.path.join(self.data_dir, "demo.har") - self.har_parser = HarParser(self.har_path) - self.profile_path = os.path.join(self.data_dir, "profile.yml") - - def test_prepare_teststep(self): - log_entries = load_har_log_entries(self.har_path) - teststep_dict = self.har_parser._prepare_teststep(log_entries[0]) - self.assertIn("name", teststep_dict) - self.assertIn("request", teststep_dict) - self.assertIn("validate", teststep_dict) - - validators_mapping = { - validator["eq"][0]: validator["eq"][1] - for validator in teststep_dict["validate"] - } - self.assertEqual(validators_mapping["status_code"], 200) - self.assertEqual(validators_mapping["body.IsSuccess"], True) - self.assertEqual(validators_mapping["body.Code"], 200) - self.assertEqual(validators_mapping["body.Message"], None) - - def test_prepare_teststeps(self): - teststeps = self.har_parser._prepare_teststeps() - self.assertIsInstance(teststeps, list) - self.assertIn("name", teststeps[0]) - self.assertIn("request", teststeps[0]) - self.assertIn("validate", teststeps[0]) - - def test_gen_testcase_yaml(self): - yaml_file = os.path.join(self.data_dir, "demo.yml") - - self.har_parser.gen_testcase(file_type="YAML") - self.assertTrue(os.path.isfile(yaml_file)) - os.remove(yaml_file) - - def test_gen_testcase_json(self): - json_file = os.path.join(self.data_dir, "demo.json") - - self.har_parser.gen_testcase(file_type="JSON") - self.assertTrue(os.path.isfile(json_file)) - os.remove(json_file) - - def test_profile(self): - har_parser = HarParser(self.har_path, profile=self.profile_path) - teststeps = har_parser._prepare_teststeps() - self.assertEqual( - teststeps[0]["request"]["headers"], - {"Content-Type": "application/x-www-form-urlencoded"}, - ) - self.assertEqual( - teststeps[0]["request"]["cookies"], - {"CASTGC": "TGT"}, - ) - - def test_filter(self): - filter_str = "httprunner" - har_parser = HarParser(self.har_path, filter_str) - teststeps = har_parser._prepare_teststeps() - self.assertEqual( - teststeps[0]["request"]["url"], - "https://httprunner.top/api/v1/Account/Login", - ) - - filter_str = "debugtalk" - har_parser = HarParser(self.har_path, filter_str) - teststeps = har_parser._prepare_teststeps() - self.assertEqual(teststeps, []) - - def test_exclude(self): - exclude_str = "debugtalk" - har_parser = HarParser(self.har_path, exclude_str=exclude_str) - teststeps = har_parser._prepare_teststeps() - self.assertEqual( - teststeps[0]["request"]["url"], - "https://httprunner.top/api/v1/Account/Login", - ) - - exclude_str = "httprunner" - har_parser = HarParser(self.har_path, exclude_str=exclude_str) - teststeps = har_parser._prepare_teststeps() - self.assertEqual(teststeps, []) - - def test_exclude_multiple(self): - exclude_str = "httprunner|v2" - har_parser = HarParser(self.har_path, exclude_str=exclude_str) - teststeps = har_parser._prepare_teststeps() - self.assertEqual(teststeps, []) - - exclude_str = "http2|v1" - har_parser = HarParser(self.har_path, exclude_str=exclude_str) - teststeps = har_parser._prepare_teststeps() - self.assertEqual(teststeps, []) - - def test_make_request_data_params(self): - testcase_dict = {"name": "", "request": {}, "validate": []} - entry_json = { - "request": { - "method": "POST", - "postData": { - "mimeType": "application/x-www-form-urlencoded; charset=utf-8", - "params": [{"name": "a", "value": 1}, {"name": "b", "value": "2"}], - }, - } - } - self.har_parser._make_request_data(testcase_dict, entry_json) - self.assertEqual(testcase_dict["request"]["data"]["a"], 1) - self.assertEqual(testcase_dict["request"]["data"]["b"], "2") - - def test_make_request_data_json(self): - testcase_dict = {"name": "", "request": {}, "validate": []} - entry_json = { - "request": { - "method": "POST", - "postData": { - "mimeType": "application/json; charset=utf-8", - "text": '{"a":"1","b":"2"}', - }, - } - } - self.har_parser._make_request_data(testcase_dict, entry_json) - self.assertEqual(testcase_dict["request"]["json"], {"a": "1", "b": "2"}) - - def test_make_request_data_text_empty(self): - testcase_dict = {"name": "", "request": {}, "validate": []} - entry_json = { - "request": { - "method": "POST", - "postData": {"mimeType": "application/json; charset=utf-8", "text": ""}, - } - } - self.har_parser._make_request_data(testcase_dict, entry_json) - self.assertEqual(testcase_dict["request"]["data"], "") - - def test_make_validate(self): - testcase_dict = {"name": "", "request": {}, "validate": []} - entry_json = { - "request": {}, - "response": { - "status": 200, - "headers": [ - { - "name": "Content-Type", - "value": "application/json; charset=utf-8", - }, - ], - "content": { - "size": 71, - "mimeType": "application/json; charset=utf-8", - # raw response content text is application/jose type - "text": "ZXlKaGJHY2lPaUpTVTBFeFh6VWlMQ0psYm1NaU9pSkJNVEk0UTBKRExV", - "encoding": "base64", - }, - }, - } - self.har_parser._make_validate(testcase_dict, entry_json) - self.assertEqual(testcase_dict["validate"][0], {"eq": ["status_code", 200]}) - self.assertEqual( - testcase_dict["validate"][1], - {"eq": ["headers.Content-Type", "application/json; charset=utf-8"]}, - ) - - def test_make_testcase(self): - har_path = os.path.join( - self.data_dir, "demo-quickstart.har" - ) - har_parser = HarParser(har_path) - testcase = har_parser._make_testcase() - self.assertIsInstance(testcase, dict) - self.assertIn("config", testcase) - self.assertIn("teststeps", testcase) - self.assertEqual(len(testcase["teststeps"]), 2) diff --git a/httprunner/ext/har2case/utils.py b/httprunner/ext/har2case/utils.py deleted file mode 100644 index ead83b30..00000000 --- a/httprunner/ext/har2case/utils.py +++ /dev/null @@ -1,130 +0,0 @@ -import json -import sys -from json.decoder import JSONDecodeError -from urllib.parse import unquote - -import yaml -from loguru import logger - - -def load_har_log_entries(file_path): - """ load HAR file and return log entries list - - Args: - file_path (str) - - Returns: - list: entries - [ - { - "request": {}, - "response": {} - }, - { - "request": {}, - "response": {} - } - ] - - """ - with open(file_path, mode="rb") as f: - try: - content_json = json.load(f) - return content_json["log"]["entries"] - except (TypeError, JSONDecodeError) as ex: - logger.error(f"failed to load HAR file {file_path}: {ex}") - sys.exit(1) - except KeyError: - logger.error(f"log entries not found in HAR file: {content_json}") - sys.exit(1) - - -def x_www_form_urlencoded(post_data): - """ convert origin dict to x-www-form-urlencoded - - Args: - post_data (dict): - {"a": 1, "b":2} - - Returns: - str: - a=1&b=2 - - """ - if isinstance(post_data, dict): - return "&".join( - ["{}={}".format(key, value) for key, value in post_data.items()] - ) - else: - return post_data - - -def convert_x_www_form_urlencoded_to_dict(post_data): - """ convert x_www_form_urlencoded data to dict - - Args: - post_data (str): a=1&b=2 - - Returns: - dict: {"a":1, "b":2} - - """ - if isinstance(post_data, str): - converted_dict = {} - for k_v in post_data.split("&"): - try: - key, value = k_v.split("=") - except ValueError: - raise Exception( - "Invalid x_www_form_urlencoded data format: {}".format(post_data) - ) - converted_dict[key] = unquote(value) - return converted_dict - else: - return post_data - - -def convert_list_to_dict(origin_list): - """ convert HAR data list to mapping - - Args: - origin_list (list) - [ - {"name": "v", "value": "1"}, - {"name": "w", "value": "2"} - ] - - Returns: - dict: - {"v": "1", "w": "2"} - - """ - return {item["name"]: item.get("value") for item in origin_list} - - -def dump_yaml(testcase, yaml_file): - """ dump HAR entries to yaml testcase - """ - logger.info("dump testcase to YAML format.") - - with open(yaml_file, "w", encoding="utf-8") as outfile: - yaml.dump( - testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4 - ) - - logger.info("Generate YAML testcase successfully: {}".format(yaml_file)) - - -def dump_json(testcase, json_file): - """ dump HAR entries to json testcase - """ - logger.info("dump testcase to JSON format.") - - with open(json_file, "w", encoding="utf-8") as outfile: - my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4) - if isinstance(my_json_str, bytes): - my_json_str = my_json_str.decode("utf-8") - - outfile.write(my_json_str) - - logger.info("Generate JSON testcase successfully: {}".format(json_file)) diff --git a/httprunner/ext/har2case/utils_test.py b/httprunner/ext/har2case/utils_test.py deleted file mode 100644 index 6c414adc..00000000 --- a/httprunner/ext/har2case/utils_test.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -import os -import unittest - -from httprunner.ext.har2case import utils - - -class TestHar2CaseUtils(unittest.TestCase): - - data_dir = os.path.join(os.getcwd(), "examples", "data", "har2case") - - @staticmethod - def create_har_file(file_name, content): - file_path = os.path.join( - TestHar2CaseUtils.data_dir, "{}.har".format(file_name) - ) - with open(file_path, "w") as f: - f.write(json.dumps(content)) - - return file_path - - def test_load_har_log_entries(self): - har_path = os.path.join(TestHar2CaseUtils.data_dir, "demo.har") - log_entries = utils.load_har_log_entries(har_path) - self.assertIsInstance(log_entries, list) - self.assertIn("request", log_entries[0]) - self.assertIn("response", log_entries[0]) - - def test_load_har_log_key_error(self): - empty_json_file_path = TestHar2CaseUtils.create_har_file( - file_name="empty_json", content={} - ) - with self.assertRaises(SystemExit): - utils.load_har_log_entries(empty_json_file_path) - os.remove(empty_json_file_path) - - def test_load_har_log_empty_error(self): - empty_file_path = TestHar2CaseUtils.create_har_file( - file_name="empty", content="" - ) - with self.assertRaises(SystemExit): - utils.load_har_log_entries(empty_file_path) - os.remove(empty_file_path) - - # def test_x_www_form_urlencoded(self): - # origin_dict = {"a":1, "b": "2"} - # self.assertIn("a=1", utils.x_www_form_urlencoded(origin_dict)) - # self.assertIn("b=2", utils.x_www_form_urlencoded(origin_dict)) - - def test_convert_list_to_dict(self): - origin_list = [{"name": "v", "value": "1"}, {"name": "w", "value": "2"}] - self.assertEqual(utils.convert_list_to_dict(origin_list), {"v": "1", "w": "2"}) - - def test_convert_x_www_form_urlencoded_to_dict(self): - origin_str = "a=1&b=2" - converted_dict = utils.convert_x_www_form_urlencoded_to_dict(origin_str) - self.assertIsInstance(converted_dict, dict) - self.assertEqual(converted_dict["a"], "1") - self.assertEqual(converted_dict["b"], "2") diff --git a/httprunner/runner.py b/httprunner/runner.py index a4b5ec4c..3c5600ad 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -138,7 +138,7 @@ class HttpRunner(object): # parse functions = self.__project_meta.functions - # prepare_upload_step(step, functions) + prepare_upload_step(step, functions) request_dict = step.request.dict() request_dict.pop("upload", None) parsed_request_dict = parse_data( diff --git a/poetry.lock b/poetry.lock index 8a5e31a4..7b9eea4b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -252,7 +252,7 @@ reference = "tsinghua" [[package]] name = "jinja2" -version = "3.1.0" +version = "3.1.1" description = "A very fast and expressive template engine." category = "main" optional = false @@ -835,8 +835,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jinja2 = [ - {file = "Jinja2-3.1.0-py3-none-any.whl", hash = "sha256:da424924c069a4013730d8dd010cbecac7e7bb752be388db3741688bffb48dc6"}, - {file = "Jinja2-3.1.0.tar.gz", hash = "sha256:a2f09a92f358b96b5f6ca6ecb4502669c4acb55d8733bbb2b2c9c4af5564c605"}, + {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, + {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, ] jmespath = [ {file = "jmespath-0.9.5-py2.py3-none-any.whl", hash = "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec"}, diff --git a/pyproject.toml b/pyproject.toml index bb3973c5..745deb07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ homepage = "https://github.com/httprunner/httprunner" repository = "https://github.com/httprunner/httprunner" documentation = "https://httprunner.com/docs" -keywords = ["HTTP", "apitest", "perftest", "DEM", "requests", "locustio"] +keywords = ["HTTP", "apitest", "perftest", "requests"] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -56,7 +56,6 @@ coverage = "^4.5.4" httprunner = "httprunner.cli:main" hrun = "httprunner.cli:main_hrun_alias" hmake = "httprunner.cli:main_make_alias" -har2case = "httprunner.cli:main_har2case_alias" [build-system] requires = ["poetry>=1.0.0"] From 878d009d76fb1f4d3aa5aa8af45d3614fed9aea2 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 26 Mar 2022 14:25:17 +0800 Subject: [PATCH 08/13] change: remove startproject in python version, move all features to go version --- .github/workflows/hrp-scaffold.yml | 6 +- .github/workflows/smoketest.yml | 1 - docs/CHANGELOG.md | 1 + hrp/boomer_test.go | 6 +- hrp/cmd/scaffold.go | 1 + hrp/convert_test.go | 183 +++++++++++- hrp/internal/builtin/function.go | 247 ----------------- hrp/internal/builtin/utils.go | 252 +++++++++++++++++ hrp/internal/scaffold/demo.go | 260 ------------------ hrp/internal/scaffold/demo_test.go | 75 ----- hrp/internal/scaffold/main.go | 97 ++++--- hrp/internal/scaffold/templates/env | 2 + hrp/internal/scaffold/templates/gitignore | 15 + .../scaffold/templates/plugin/debugtalk.go | 57 ++++ .../scaffold/templates/plugin}/debugtalk.py | 18 ++ .../scaffold/templates/testcases/__init__.py | 1 + .../templates/testcases/demo_ref_testcase.yml | 33 +++ .../testcases/demo_ref_testcase_test.py | 60 ++++ .../templates/testcases/demo_requests.yml | 65 +++++ .../templates/testcases/demo_requests_test.py | 83 ++++++ .../testcases/demo_with_funplugin.json | 0 .../testcases/demo_with_funplugin.yaml | 0 .../testcases/demo_without_funplugin.json | 170 ++++++++++++ .../testcases/demo_without_funplugin.yaml | 110 ++++++++ hrp/plugin_test.go | 22 +- hrp/runner_test.go | 40 ++- httprunner/cli.py | 7 - httprunner/scaffold.py | 204 -------------- httprunner/scaffold_test.py | 29 -- 29 files changed, 1152 insertions(+), 893 deletions(-) create mode 100644 hrp/internal/builtin/utils.go delete mode 100644 hrp/internal/scaffold/demo.go delete mode 100644 hrp/internal/scaffold/demo_test.go create mode 100644 hrp/internal/scaffold/templates/env create mode 100644 hrp/internal/scaffold/templates/gitignore create mode 100644 hrp/internal/scaffold/templates/plugin/debugtalk.go rename {examples/hrp => hrp/internal/scaffold/templates/plugin}/debugtalk.py (84%) create mode 100644 hrp/internal/scaffold/templates/testcases/__init__.py create mode 100644 hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml create mode 100644 hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py create mode 100644 hrp/internal/scaffold/templates/testcases/demo_requests.yml create mode 100644 hrp/internal/scaffold/templates/testcases/demo_requests_test.py rename examples/hrp/demo.json => hrp/internal/scaffold/templates/testcases/demo_with_funplugin.json (100%) rename examples/hrp/demo.yaml => hrp/internal/scaffold/templates/testcases/demo_with_funplugin.yaml (100%) create mode 100644 hrp/internal/scaffold/templates/testcases/demo_without_funplugin.json create mode 100644 hrp/internal/scaffold/templates/testcases/demo_without_funplugin.yaml delete mode 100644 httprunner/scaffold.py delete mode 100644 httprunner/scaffold_test.py diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 81116817..62b66f1c 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -26,7 +26,7 @@ jobs: - name: Run start project run: ./output/hrp startproject demo - name: Run demo tests - run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml + run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml scaffold-with-go-plugin: strategy: @@ -48,7 +48,7 @@ jobs: - name: Run start project run: ./output/hrp startproject demo --go - name: Run demo tests - run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml + run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml scaffold-without-custom-plugin: strategy: @@ -70,4 +70,4 @@ jobs: - name: Run start project run: ./output/hrp startproject demo --ignore-plugin - name: Run demo tests - run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml + run: ./output/hrp run demo/testcases/demo_without_plugin.json diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index a11870b6..a638edfd 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -37,7 +37,6 @@ jobs: run: | poetry run hrun -V poetry run httprunner run -h - poetry run httprunner startproject -h - name: Run smoketest - postman echo run: | poetry run hrun examples/postman_echo/request_methods diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 37ac2752..e38b7a3c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,7 @@ **python version** +- change: remove startproject, move all features to go version, replace with `hrp startproject` - change: remove har2case, move all features to go version, replace with `hrp run` - change: remove locust, you should run load tests with go version, replace with `hrp boom` - change: remove fastapi and uvicorn dependencies diff --git a/hrp/boomer_test.go b/hrp/boomer_test.go index 79ffd2fe..4edefa38 100644 --- a/hrp/boomer_test.go +++ b/hrp/boomer_test.go @@ -6,8 +6,8 @@ import ( ) func TestBoomerStandaloneRun(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() testcase1 := &TestCase{ Config: NewConfig("TestCase1").SetBaseURL("http://httpbin.org"), @@ -25,7 +25,7 @@ func TestBoomerStandaloneRun(t *testing.T) { NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}), }, } - testcase2 := &demoTestCaseJSONPath + testcase2 := &demoTestCaseWithPluginJSONPath b := NewBoomer(2, 1) go b.Run(testcase1, testcase2) diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index cc7f18a0..0d065839 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -30,6 +30,7 @@ var scaffoldCmd = &cobra.Command{ } else { pluginType = scaffold.Py // default } + err := scaffold.CreateScaffold(args[0], pluginType) if err != nil { log.Error().Err(err).Msg("create scaffold project failed") diff --git a/hrp/convert_test.go b/hrp/convert_test.go index 7c922d7e..351aedf0 100644 --- a/hrp/convert_test.go +++ b/hrp/convert_test.go @@ -8,23 +8,198 @@ import ( "github.com/httprunner/httprunner/hrp/internal/builtin" ) +const templatesDir = "internal/scaffold/templates/" + +var ( + demoTestCaseWithPluginJSONPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.json" + demoTestCaseWithPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.yaml" + demoTestCaseWithoutPluginJSONPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.json" + demoTestCaseWithoutPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.yaml" +) + var ( - demoTestCaseJSONPath TestCasePath = "../examples/hrp/demo.json" - demoTestCaseYAMLPath TestCasePath = "../examples/hrp/demo.yaml" demoRefAPIYAMLPath TestCasePath = "../examples/hrp/ref_api_test.yaml" demoRefTestCaseJSONPath TestCasePath = "../examples/hrp/ref_testcase_test.json" demoThinkTimeJsonPath TestCasePath = "../examples/hrp/think_time_test.json" demoAPIYAMLPath APIPath = "../examples/hrp/api/put.yml" ) +var demoTestCaseWithPlugin = &TestCase{ + Config: NewConfig("demo with complex mechanisms"). + SetBaseURL("https://postman-echo.com"). + WithVariables(map[string]interface{}{ // global level variables + "n": "${sum_ints(1, 2, 2)}", + "a": "${sum(10, 2.3)}", + "b": 3.45, + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function + }), + TestSteps: []IStep{ + NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction + NewStep("get with params"). + WithVariables(map[string]interface{}{ // step level variables + "n": 3, // inherit config level variables if not set in step level, a/varFoo1 + "b": 34.5, // override config level variable if existed, n/b/varFoo2 + "varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again + "name": "get with params", + }). + SetupHook("${setup_hook_example($name)}"). + GET("/get"). + TeardownHook("${teardown_hook_example($name)}"). + WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers + Extract(). + WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath + Validate(). + AssertEqual("status_code", 200, "check response status code"). // validate response status code + AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header + AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath + AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step + AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string + NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction + NewStep("post json data"). + POST("/post"). + WithBody(map[string]interface{}{ + "foo1": "$varFoo1", // reference former extracted variable + "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here + }). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertLengthEqual("body.json.foo1", 5, "check args foo1"). + AssertEqual("body.json.foo2", 12.3, "check args foo2"), + NewStep("post form data"). + POST("/post"). + WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}). + WithBody(map[string]interface{}{ + "foo1": "$varFoo1", // reference former extracted variable + "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here + "time": "${get_timestamp()}", + }). + Extract(). + WithJmesPath("body.form.time", "varTime"). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertLengthEqual("body.form.foo1", 5, "check args foo1"). + AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string + NewStep("get with timestamp"). + GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}). + Validate(). + AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"), + }, +} + +var demoTestCaseWithoutPlugin = &TestCase{ + Config: NewConfig("demo without custom function plugin"). + SetBaseURL("https://postman-echo.com"). + WithVariables(map[string]interface{}{ // global level variables + "n": 5, + "a": 12.3, + "b": 3.45, + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function + }), + TestSteps: []IStep{ + NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction + NewStep("get with params"). + WithVariables(map[string]interface{}{ // step level variables + "n": 3, // inherit config level variables if not set in step level, a/varFoo1 + "b": 34.5, // override config level variable if existed, n/b/varFoo2 + "varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again + "name": "get with params", + }). + GET("/get"). + WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers + Extract(). + WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath + Validate(). + AssertEqual("status_code", 200, "check response status code"). // validate response status code + AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header + AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath + AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step + AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string + NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction + NewStep("post json data"). + POST("/post"). + WithBody(map[string]interface{}{ + "foo1": "$varFoo1", // reference former extracted variable + "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here + }). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertLengthEqual("body.json.foo1", 5, "check args foo1"). + AssertEqual("body.json.foo2", 12.3, "check args foo2"), + NewStep("post form data"). + POST("/post"). + WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}). + WithBody(map[string]interface{}{ + "foo1": "$varFoo1", // reference former extracted variable + "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here + "time": "${get_timestamp()}", + }). + Extract(). + WithJmesPath("body.form.time", "varTime"). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertLengthEqual("body.form.foo1", 5, "check args foo1"). + AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string + NewStep("get with timestamp"). + GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}). + Validate(). + AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"), + }, +} + +func TestGenDemoTestCase(t *testing.T) { + tCase, _ := demoTestCaseWithPlugin.ToTCase() + err := builtin.Dump2JSON(tCase, demoTestCaseWithPluginJSONPath.ToString()) + if err != nil { + t.Fail() + } + err = builtin.Dump2YAML(tCase, demoTestCaseWithPluginYAMLPath.ToString()) + if err != nil { + t.Fail() + } + + tCase, _ = demoTestCaseWithoutPlugin.ToTCase() + err = builtin.Dump2JSON(tCase, demoTestCaseWithoutPluginJSONPath.ToString()) + if err != nil { + t.Fail() + } + err = builtin.Dump2YAML(tCase, demoTestCaseWithoutPluginYAMLPath.ToString()) + if err != nil { + t.Fail() + } +} + +func TestJsonDemoWithPlugin(t *testing.T) { + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() + + err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase) + if err != nil { + t.Fail() + } +} + +func TestYamlDemoWithPlugin(t *testing.T) { + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() + + err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase) + if err != nil { + t.Fail() + } +} + func TestLoadCase(t *testing.T) { tcJSON := &TCase{} tcYAML := &TCase{} - err := builtin.LoadFile(demoTestCaseJSONPath.ToString(), tcJSON) + err := builtin.LoadFile(demoTestCaseWithPluginJSONPath.ToString(), tcJSON) if !assert.NoError(t, err) { t.Fail() } - err = builtin.LoadFile(demoTestCaseYAMLPath.ToString(), tcYAML) + err = builtin.LoadFile(demoTestCaseWithPluginYAMLPath.ToString(), tcYAML) if !assert.NoError(t, err) { t.Fail() } diff --git a/hrp/internal/builtin/function.go b/hrp/internal/builtin/function.go index e271b447..7ce36eb7 100644 --- a/hrp/internal/builtin/function.go +++ b/hrp/internal/builtin/function.go @@ -1,26 +1,11 @@ package builtin import ( - "bytes" "crypto/md5" - "encoding/csv" "encoding/hex" - builtinJSON "encoding/json" - "fmt" "math" "math/rand" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" "time" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "gopkg.in/yaml.v3" - - "github.com/httprunner/httprunner/hrp/internal/json" ) var Functions = map[string]interface{}{ @@ -61,235 +46,3 @@ func MD5(str string) string { hasher.Write([]byte(str)) return hex.EncodeToString(hasher.Sum(nil)) } - -func Dump2JSON(data interface{}, path string) error { - path, err := filepath.Abs(path) - if err != nil { - log.Error().Err(err).Msg("convert absolute path failed") - return err - } - log.Info().Str("path", path).Msg("dump data to json") - file, _ := json.MarshalIndent(data, "", " ") - err = os.WriteFile(path, file, 0644) - if err != nil { - log.Error().Err(err).Msg("dump json path failed") - return err - } - return nil -} - -func Dump2YAML(data interface{}, path string) error { - path, err := filepath.Abs(path) - if err != nil { - log.Error().Err(err).Msg("convert absolute path failed") - return err - } - log.Info().Str("path", path).Msg("dump data to yaml") - - // init yaml encoder - buffer := new(bytes.Buffer) - encoder := yaml.NewEncoder(buffer) - encoder.SetIndent(4) - - // encode - err = encoder.Encode(data) - if err != nil { - return err - } - - err = os.WriteFile(path, buffer.Bytes(), 0644) - if err != nil { - log.Error().Err(err).Msg("dump yaml path failed") - return err - } - return nil -} - -func FormatResponse(raw interface{}) interface{} { - formattedResponse := make(map[string]interface{}) - for key, value := range raw.(map[string]interface{}) { - // convert value to json - if key == "body" { - b, _ := json.MarshalIndent(&value, "", " ") - value = string(b) - } - formattedResponse[key] = value - } - return formattedResponse -} - -func ExecCommand(cmd *exec.Cmd, cwd string) error { - log.Info().Str("cmd", cmd.String()).Str("cwd", cwd).Msg("exec command") - cmd.Dir = cwd - output, err := cmd.CombinedOutput() - out := strings.TrimSpace(string(output)) - if err != nil { - log.Error().Err(err).Str("output", out).Msg("exec command failed") - } else if len(out) != 0 { - log.Info().Str("output", out).Msg("exec command success") - } - return err -} - -func CreateFolder(folderPath string) error { - log.Info().Str("path", folderPath).Msg("create folder") - err := os.MkdirAll(folderPath, os.ModePerm) - if err != nil { - log.Error().Err(err).Msg("create folder failed") - return err - } - return nil -} - -func CreateFile(filePath string, data string) error { - log.Info().Str("path", filePath).Msg("create file") - err := os.WriteFile(filePath, []byte(data), 0o644) - if err != nil { - log.Error().Err(err).Msg("create file failed") - return err - } - return nil -} - -// isFilePathExists returns true if path exists, whether path is file or dir -func isPathExists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} - -// isFilePathExists returns true if path exists and path is file -func isFilePathExists(path string) bool { - info, err := os.Stat(path) - if err != nil { - // path not exists - return false - } - - // path exists - if info.IsDir() { - // path is dir, not file - return false - } - return true -} - -func EnsureFolderExists(folderPath string) error { - if !isPathExists(folderPath) { - err := CreateFolder(folderPath) - return err - } else if isFilePathExists(folderPath) { - return fmt.Errorf("path %v should be directory", folderPath) - } - return nil -} - -func Contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - -func GetRandomNumber(min, max int) int { - if min > max { - return 0 - } - r := rand.Intn(max - min + 1) - return min + r -} - -func Interface2Float64(i interface{}) (float64, error) { - switch i.(type) { - case int: - return float64(i.(int)), nil - case int32: - return float64(i.(int32)), nil - case int64: - return float64(i.(int64)), nil - case float32: - return float64(i.(float32)), nil - case float64: - return i.(float64), nil - case string: - intVar, err := strconv.Atoi(i.(string)) - if err != nil { - return 0, err - } - return float64(intVar), err - } - // json.Number - value, ok := i.(builtinJSON.Number) - if ok { - return value.Float64() - } - return 0, errors.New("failed to convert interface to float64") -} - -var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension") - -// LoadFile loads file content with file extension and assigns to structObj -func LoadFile(path string, structObj interface{}) (err error) { - log.Info().Str("path", path).Msg("load file") - file, err := readFile(path) - if err != nil { - return errors.Wrap(err, "read file failed") - } - - ext := filepath.Ext(path) - switch ext { - case ".json", ".har": - decoder := json.NewDecoder(bytes.NewReader(file)) - decoder.UseNumber() - err = decoder.Decode(structObj) - case ".yaml", ".yml": - err = yaml.Unmarshal(file, structObj) - default: - err = ErrUnsupportedFileExt - } - return err -} - -func loadFromCSV(path string) []map[string]interface{} { - log.Info().Str("path", path).Msg("load csv file") - file, err := readFile(path) - if err != nil { - log.Error().Err(err).Msg("read csv file failed") - panic(err) - } - - r := csv.NewReader(strings.NewReader(string(file))) - content, err := r.ReadAll() - if err != nil { - log.Error().Err(err).Msg("parse csv file failed") - panic(err) - } - var result []map[string]interface{} - for i := 1; i < len(content); i++ { - row := make(map[string]interface{}) - for j := 0; j < len(content[i]); j++ { - row[content[0][j]] = content[i][j] - } - result = append(result, row) - } - return result -} - -func readFile(path string) ([]byte, error) { - var err error - path, err = filepath.Abs(path) - if err != nil { - log.Error().Err(err).Str("path", path).Msg("convert absolute path failed") - return nil, err - } - - file, err := os.ReadFile(path) - if err != nil { - log.Error().Err(err).Msg("read file failed") - return nil, err - } - return file, nil -} diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go new file mode 100644 index 00000000..7d9bd4a8 --- /dev/null +++ b/hrp/internal/builtin/utils.go @@ -0,0 +1,252 @@ +package builtin + +import ( + "bytes" + "encoding/csv" + builtinJSON "encoding/json" + "fmt" + "math/rand" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "gopkg.in/yaml.v3" + + "github.com/httprunner/httprunner/hrp/internal/json" +) + +func Dump2JSON(data interface{}, path string) error { + path, err := filepath.Abs(path) + if err != nil { + log.Error().Err(err).Msg("convert absolute path failed") + return err + } + log.Info().Str("path", path).Msg("dump data to json") + file, _ := json.MarshalIndent(data, "", " ") + err = os.WriteFile(path, file, 0644) + if err != nil { + log.Error().Err(err).Msg("dump json path failed") + return err + } + return nil +} + +func Dump2YAML(data interface{}, path string) error { + path, err := filepath.Abs(path) + if err != nil { + log.Error().Err(err).Msg("convert absolute path failed") + return err + } + log.Info().Str("path", path).Msg("dump data to yaml") + + // init yaml encoder + buffer := new(bytes.Buffer) + encoder := yaml.NewEncoder(buffer) + encoder.SetIndent(4) + + // encode + err = encoder.Encode(data) + if err != nil { + return err + } + + err = os.WriteFile(path, buffer.Bytes(), 0644) + if err != nil { + log.Error().Err(err).Msg("dump yaml path failed") + return err + } + return nil +} + +func FormatResponse(raw interface{}) interface{} { + formattedResponse := make(map[string]interface{}) + for key, value := range raw.(map[string]interface{}) { + // convert value to json + if key == "body" { + b, _ := json.MarshalIndent(&value, "", " ") + value = string(b) + } + formattedResponse[key] = value + } + return formattedResponse +} + +func ExecCommand(cmd *exec.Cmd, cwd string) error { + log.Info().Str("cmd", cmd.String()).Str("cwd", cwd).Msg("exec command") + cmd.Dir = cwd + output, err := cmd.CombinedOutput() + out := strings.TrimSpace(string(output)) + if err != nil { + log.Error().Err(err).Str("output", out).Msg("exec command failed") + } else if len(out) != 0 { + log.Info().Str("output", out).Msg("exec command success") + } + return err +} + +func CreateFolder(folderPath string) error { + log.Info().Str("path", folderPath).Msg("create folder") + err := os.MkdirAll(folderPath, os.ModePerm) + if err != nil { + log.Error().Err(err).Msg("create folder failed") + return err + } + return nil +} + +func CreateFile(filePath string, data string) error { + log.Info().Str("path", filePath).Msg("create file") + err := os.WriteFile(filePath, []byte(data), 0o644) + if err != nil { + log.Error().Err(err).Msg("create file failed") + return err + } + return nil +} + +// isFilePathExists returns true if path exists, whether path is file or dir +func isPathExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} + +// isFilePathExists returns true if path exists and path is file +func isFilePathExists(path string) bool { + info, err := os.Stat(path) + if err != nil { + // path not exists + return false + } + + // path exists + if info.IsDir() { + // path is dir, not file + return false + } + return true +} + +func EnsureFolderExists(folderPath string) error { + if !isPathExists(folderPath) { + err := CreateFolder(folderPath) + return err + } else if isFilePathExists(folderPath) { + return fmt.Errorf("path %v should be directory", folderPath) + } + return nil +} + +func Contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func GetRandomNumber(min, max int) int { + if min > max { + return 0 + } + r := rand.Intn(max - min + 1) + return min + r +} + +func Interface2Float64(i interface{}) (float64, error) { + switch i.(type) { + case int: + return float64(i.(int)), nil + case int32: + return float64(i.(int32)), nil + case int64: + return float64(i.(int64)), nil + case float32: + return float64(i.(float32)), nil + case float64: + return i.(float64), nil + case string: + intVar, err := strconv.Atoi(i.(string)) + if err != nil { + return 0, err + } + return float64(intVar), err + } + // json.Number + value, ok := i.(builtinJSON.Number) + if ok { + return value.Float64() + } + return 0, errors.New("failed to convert interface to float64") +} + +var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension") + +// LoadFile loads file content with file extension and assigns to structObj +func LoadFile(path string, structObj interface{}) (err error) { + log.Info().Str("path", path).Msg("load file") + file, err := readFile(path) + if err != nil { + return errors.Wrap(err, "read file failed") + } + + ext := filepath.Ext(path) + switch ext { + case ".json", ".har": + decoder := json.NewDecoder(bytes.NewReader(file)) + decoder.UseNumber() + err = decoder.Decode(structObj) + case ".yaml", ".yml": + err = yaml.Unmarshal(file, structObj) + default: + err = ErrUnsupportedFileExt + } + return err +} + +func loadFromCSV(path string) []map[string]interface{} { + log.Info().Str("path", path).Msg("load csv file") + file, err := readFile(path) + if err != nil { + log.Error().Err(err).Msg("read csv file failed") + panic(err) + } + + r := csv.NewReader(strings.NewReader(string(file))) + content, err := r.ReadAll() + if err != nil { + log.Error().Err(err).Msg("parse csv file failed") + panic(err) + } + var result []map[string]interface{} + for i := 1; i < len(content); i++ { + row := make(map[string]interface{}) + for j := 0; j < len(content[i]); j++ { + row[content[0][j]] = content[i][j] + } + result = append(result, row) + } + return result +} + +func readFile(path string) ([]byte, error) { + var err error + path, err = filepath.Abs(path) + if err != nil { + log.Error().Err(err).Str("path", path).Msg("convert absolute path failed") + return nil, err + } + + file, err := os.ReadFile(path) + if err != nil { + log.Error().Err(err).Msg("read file failed") + return nil, err + } + return file, nil +} diff --git a/hrp/internal/scaffold/demo.go b/hrp/internal/scaffold/demo.go deleted file mode 100644 index 83338830..00000000 --- a/hrp/internal/scaffold/demo.go +++ /dev/null @@ -1,260 +0,0 @@ -package scaffold - -import "github.com/httprunner/httprunner/hrp" - -var demoTestCase = &hrp.TestCase{ - Config: hrp.NewConfig("demo with complex mechanisms"). - SetBaseURL("https://postman-echo.com"). - WithVariables(map[string]interface{}{ // global level variables - "n": "${sum_ints(1, 2, 2)}", - "a": "${sum(10, 2.3)}", - "b": 3.45, - "varFoo1": "${gen_random_string($n)}", - "varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function - }), - TestSteps: []hrp.IStep{ - hrp.NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction - hrp.NewStep("get with params"). - WithVariables(map[string]interface{}{ // step level variables - "n": 3, // inherit config level variables if not set in step level, a/varFoo1 - "b": 34.5, // override config level variable if existed, n/b/varFoo2 - "varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again - "name": "get with params", - }). - SetupHook("${setup_hook_example($name)}"). - GET("/get"). - TeardownHook("${teardown_hook_example($name)}"). - WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params - WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers - Extract(). - WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath - Validate(). - AssertEqual("status_code", 200, "check response status code"). // validate response status code - AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header - AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath - AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step - AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string - hrp.NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction - hrp.NewStep("post json data"). - POST("/post"). - WithBody(map[string]interface{}{ - "foo1": "$varFoo1", // reference former extracted variable - "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here - }). - Validate(). - AssertEqual("status_code", 200, "check status code"). - AssertLengthEqual("body.json.foo1", 5, "check args foo1"). - AssertEqual("body.json.foo2", 12.3, "check args foo2"), - hrp.NewStep("post form data"). - POST("/post"). - WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}). - WithBody(map[string]interface{}{ - "foo1": "$varFoo1", // reference former extracted variable - "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here - "time": "${get_timestamp()}", - }). - Extract(). - WithJmesPath("body.form.time", "varTime"). - Validate(). - AssertEqual("status_code", 200, "check status code"). - AssertLengthEqual("body.form.foo1", 5, "check args foo1"). - AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string - hrp.NewStep("get with timestamp"). - GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}). - Validate(). - AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"), - }, -} - -var demoTestCaseWithoutPlugin = &hrp.TestCase{ - Config: hrp.NewConfig("demo without custom function plugin"). - SetBaseURL("https://postman-echo.com"). - WithVariables(map[string]interface{}{ // global level variables - "n": 5, - "a": 12.3, - "b": 3.45, - "varFoo1": "${gen_random_string($n)}", - "varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function - }), - TestSteps: []hrp.IStep{ - hrp.NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction - hrp.NewStep("get with params"). - WithVariables(map[string]interface{}{ // step level variables - "n": 3, // inherit config level variables if not set in step level, a/varFoo1 - "b": 34.5, // override config level variable if existed, n/b/varFoo2 - "varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again - "name": "get with params", - }). - GET("/get"). - WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params - WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers - Extract(). - WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath - Validate(). - AssertEqual("status_code", 200, "check response status code"). // validate response status code - AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header - AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath - AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step - AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string - hrp.NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction - hrp.NewStep("post json data"). - POST("/post"). - WithBody(map[string]interface{}{ - "foo1": "$varFoo1", // reference former extracted variable - "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here - }). - Validate(). - AssertEqual("status_code", 200, "check status code"). - AssertLengthEqual("body.json.foo1", 5, "check args foo1"). - AssertEqual("body.json.foo2", 12.3, "check args foo2"), - hrp.NewStep("post form data"). - POST("/post"). - WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}). - WithBody(map[string]interface{}{ - "foo1": "$varFoo1", // reference former extracted variable - "foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here - "time": "${get_timestamp()}", - }). - Extract(). - WithJmesPath("body.form.time", "varTime"). - Validate(). - AssertEqual("status_code", 200, "check status code"). - AssertLengthEqual("body.form.foo1", 5, "check args foo1"). - AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string - hrp.NewStep("get with timestamp"). - GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}). - Validate(). - AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"), - }, -} - -// debugtalk.go -var demoGoPlugin = `package main - -import ( - "fmt" - - "github.com/httprunner/funplugin/fungo" -) - -func SumTwoInt(a, b int) int { - return a + b -} - -func SumInts(args ...int) int { - var sum int - for _, arg := range args { - sum += arg - } - return sum -} - -func Sum(args ...interface{}) (interface{}, error) { - var sum float64 - for _, arg := range args { - switch v := arg.(type) { - case int: - sum += float64(v) - case float64: - sum += v - default: - return nil, fmt.Errorf("unexpected type: %T", arg) - } - } - return sum, nil -} - -func SetupHookExample(args string) string { - return fmt.Sprintf("step name: %v, setup...", args) -} - -func TeardownHookExample(args string) string { - return fmt.Sprintf("step name: %v, teardown...", args) -} - -func main() { - fungo.Register("sum_ints", SumInts) - fungo.Register("sum_two_int", SumTwoInt) - fungo.Register("sum", Sum) - fungo.Register("setup_hook_example", SetupHookExample) - fungo.Register("teardown_hook_example", TeardownHookExample) - fungo.Serve() -} -` - -// debugtalk.py -var demoPyPlugin = `import logging -from typing import List - -import funppy - - -def sum(*args): - result = 0 - for arg in args: - result += arg - return result - -def sum_ints(*args: List[int]) -> int: - result = 0 - for arg in args: - result += arg - return result - -def sum_two_int(a: int, b: int) -> int: - return a + b - -def sum_two_string(a: str, b: str) -> str: - return a + b - -def sum_strings(*args: List[str]) -> str: - result = "" - for arg in args: - result += arg - return result - -def concatenate(*args: List[str]) -> str: - result = "" - for arg in args: - result += str(arg) - return result - -def setup_hook_example(name): - logging.warning("setup_hook_example") - return f"setup_hook_example: {name}" - -def teardown_hook_example(name): - logging.warning("teardown_hook_example") - return f"teardown_hook_example: {name}" - - -if __name__ == '__main__': - funppy.register("sum", sum) - funppy.register("sum_ints", sum_ints) - funppy.register("concatenate", concatenate) - funppy.register("sum_two_int", sum_two_int) - funppy.register("sum_two_string", sum_two_string) - funppy.register("sum_strings", sum_strings) - funppy.register("setup_hook_example", setup_hook_example) - funppy.register("teardown_hook_example", teardown_hook_example) - funppy.serve() -` - -// .gitignore -var demoIgnoreContent = `.env -reports/ -*.so -.vscode/ -.idea/ -.DS_Store -output/ - -# plugin -debugtalk.bin -debugtalk.so -` - -// .env -var demoEnvContent = `USERNAME=debugtalk -PASSWORD=123456 -` diff --git a/hrp/internal/scaffold/demo_test.go b/hrp/internal/scaffold/demo_test.go deleted file mode 100644 index 4a88ead3..00000000 --- a/hrp/internal/scaffold/demo_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package scaffold - -import ( - "os" - "os/exec" - "testing" - - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/hrp" - "github.com/httprunner/httprunner/hrp/internal/builtin" -) - -var ( - demoTestCaseJSONPath hrp.TestCasePath = "../../../examples/hrp/demo.json" - demoTestCaseYAMLPath hrp.TestCasePath = "../../../examples/hrp/demo.yaml" -) - -func buildHashicorpPlugin() { - log.Info().Msg("[init] build hashicorp go plugin") - cmd := exec.Command("go", "build", - "-o", "../../../examples/hrp/debugtalk.bin", - "../../../examples/hrp/plugin/hashicorp.go", "../../../examples/hrp/plugin/debugtalk.go") - if err := cmd.Run(); err != nil { - panic(err) - } -} - -func removeHashicorpPlugin() { - log.Info().Msg("[teardown] remove hashicorp plugin") - os.Remove("../../../examples/hrp/debugtalk.bin") -} - -func TestGenDemoTestCase(t *testing.T) { - tCase, _ := demoTestCase.ToTCase() - err := builtin.Dump2JSON(tCase, demoTestCaseJSONPath.ToString()) - if err != nil { - t.Fail() - } - err = builtin.Dump2YAML(tCase, demoTestCaseYAMLPath.ToString()) - if err != nil { - t.Fail() - } -} - -func TestExampleDemo(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() - - demoTestCase.Config.Path = "../../../examples/hrp/debugtalk.bin" - err := hrp.NewRunner(nil).Run(demoTestCase) // hrp.Run(demoTestCase) - if err != nil { - t.Fail() - } -} - -func TestJsonDemo(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() - - err := hrp.NewRunner(nil).Run(&demoTestCaseJSONPath) // hrp.Run(testCase) - if err != nil { - t.Fail() - } -} - -func TestYamlDemo(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() - - err := hrp.NewRunner(nil).Run(&demoTestCaseYAMLPath) // hrp.Run(testCase) - if err != nil { - t.Fail() - } -} diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index c567f28e..37f819f1 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -1,16 +1,16 @@ package scaffold import ( + "embed" "fmt" "os" "os/exec" - "path" + "path/filepath" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/funplugin/shared" - "github.com/httprunner/httprunner/hrp" "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/httprunner/httprunner/hrp/internal/sdk" ) @@ -23,6 +23,25 @@ const ( Go PluginType = "go" ) +//go:embed templates/* +var templatesDir embed.FS + +// CopyFile copies a file from templates dir to scaffold project +func CopyFile(templateFile, targetFile string) error { + log.Info().Str("path", targetFile).Msg("create file") + content, err := templatesDir.ReadFile(templateFile) + if err != nil { + return errors.Wrap(err, "template file not found") + } + + err = os.WriteFile(targetFile, content, 0o644) + if err != nil { + log.Error().Err(err).Msg("create file failed") + return err + } + return nil +} + func CreateScaffold(projectName string, pluginType PluginType) error { // report event sdk.SendEvent(sdk.EventTracking{ @@ -46,48 +65,56 @@ func CreateScaffold(projectName string, pluginType PluginType) error { if err := builtin.CreateFolder(projectName); err != nil { return err } - if err := builtin.CreateFolder(path.Join(projectName, "har")); err != nil { + if err := builtin.CreateFolder(filepath.Join(projectName, "har")); err != nil { return err } - if err := builtin.CreateFolder(path.Join(projectName, "testcases")); err != nil { + if err := builtin.CreateFolder(filepath.Join(projectName, "testcases")); err != nil { return err } - if err := builtin.CreateFolder(path.Join(projectName, "reports")); err != nil { - return err - } - - // create demo testcases - var tCase *hrp.TCase - if pluginType == Ignore { - tCase, _ = demoTestCaseWithoutPlugin.ToTCase() - } else { - tCase, _ = demoTestCase.ToTCase() - } - err := builtin.Dump2JSON(tCase, path.Join(projectName, "testcases", "demo.json")) - if err != nil { - log.Error().Err(err).Msg("create demo.json testcase failed") - return err - } - err = builtin.Dump2YAML(tCase, path.Join(projectName, "testcases", "demo.yaml")) - if err != nil { - log.Error().Err(err).Msg("create demo.yml testcase failed") + if err := builtin.CreateFolder(filepath.Join(projectName, "reports")); err != nil { return err } // create .gitignore - if err := builtin.CreateFile(path.Join(projectName, ".gitignore"), demoIgnoreContent); err != nil { + err := CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore")) + if err != nil { return err } // create .env - if err := builtin.CreateFile(path.Join(projectName, ".env"), demoEnvContent); err != nil { + err = CopyFile("templates/env", filepath.Join(projectName, ".env")) + if err != nil { + return err + } + + // create demo testcases + if pluginType == Ignore { + err := CopyFile("templates/testcases/demo_without_plugin.json", + filepath.Join(projectName, "testcases", "demo_without_plugin.json")) + if err != nil { + return err + } + log.Info().Msg("skip creating function plugin") + return nil + } + + err = CopyFile("templates/testcases/demo_with_funplugin.json", + filepath.Join(projectName, "testcases", "demo_with_funplugin.json")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_requests.yml", + filepath.Join(projectName, "testcases", "demo_requests.yml")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_ref_testcase.yml", + filepath.Join(projectName, "testcases", "demo_ref_testcase.yml")) + if err != nil { return err } // create debugtalk function plugin switch pluginType { - case Ignore: - log.Info().Msg("skip creating function plugin") - return nil case Py: return createPythonPlugin(projectName) case Go: @@ -105,12 +132,13 @@ func createGoPlugin(projectName string) error { } // create debugtalk.go - pluginDir := path.Join(projectName, "plugin") + pluginDir := filepath.Join(projectName, "plugin") if err := builtin.CreateFolder(pluginDir); err != nil { return err } - pluginFile := path.Join(pluginDir, "debugtalk.go") - if err := builtin.CreateFile(pluginFile, demoGoPlugin); err != nil { + err := CopyFile("templates/plugin/debugtalk.go", + filepath.Join(projectName, "plugin", "debugtalk.go")) + if err != nil { return err } @@ -125,7 +153,7 @@ func createGoPlugin(projectName string) error { } // build plugin debugtalk.bin - if err := builtin.ExecCommand(exec.Command("go", "build", "-o", path.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil { + if err := builtin.ExecCommand(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil { return err } @@ -136,8 +164,9 @@ func createPythonPlugin(projectName string) error { log.Info().Msg("start to create hashicorp python plugin") // create debugtalk.py - pluginFile := path.Join(projectName, "debugtalk.py") - if err := builtin.CreateFile(pluginFile, demoPyPlugin); err != nil { + pluginFile := filepath.Join(projectName, "debugtalk.py") + err := CopyFile("templates/plugin/debugtalk.py", pluginFile) + if err != nil { return err } diff --git a/hrp/internal/scaffold/templates/env b/hrp/internal/scaffold/templates/env new file mode 100644 index 00000000..9b5dc360 --- /dev/null +++ b/hrp/internal/scaffold/templates/env @@ -0,0 +1,2 @@ +USERNAME=debugtalk +PASSWORD=123456 \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/gitignore b/hrp/internal/scaffold/templates/gitignore new file mode 100644 index 00000000..33401380 --- /dev/null +++ b/hrp/internal/scaffold/templates/gitignore @@ -0,0 +1,15 @@ +.env +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go new file mode 100644 index 00000000..f99a1321 --- /dev/null +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + + "github.com/httprunner/funplugin/fungo" +) + +func SumTwoInt(a, b int) int { + return a + b +} + +func SumInts(args ...int) int { + var sum int + for _, arg := range args { + sum += arg + } + return sum +} + +func Sum(args ...interface{}) (interface{}, error) { + var sum float64 + for _, arg := range args { + switch v := arg.(type) { + case int: + sum += float64(v) + case float64: + sum += v + default: + return nil, fmt.Errorf("unexpected type: %T", arg) + } + } + return sum, nil +} + +func SetupHookExample(args string) string { + return fmt.Sprintf("step name: %v, setup...", args) +} + +func TeardownHookExample(args string) string { + return fmt.Sprintf("step name: %v, teardown...", args) +} + +func GetVersion() string { + return "v4.0.0-alpha" +} + +func main() { + fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("sum_ints", SumInts) + fungo.Register("sum_two_int", SumTwoInt) + fungo.Register("sum_two", SumTwoInt) + fungo.Register("sum", Sum) + fungo.Register("setup_hook_example", SetupHookExample) + fungo.Register("teardown_hook_example", TeardownHookExample) + fungo.Serve() +} diff --git a/examples/hrp/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py similarity index 84% rename from examples/hrp/debugtalk.py rename to hrp/internal/scaffold/templates/plugin/debugtalk.py index 3d2bb5ff..743e7f62 100644 --- a/examples/hrp/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -1,53 +1,71 @@ import logging +import time from typing import List import funppy +def get_httprunner_version(): + return "v4.0.0-alpha" + + +def sleep(n_secs): + time.sleep(n_secs) + + def sum(*args): result = 0 for arg in args: result += arg return result + def sum_ints(*args: List[int]) -> int: result = 0 for arg in args: result += arg return result + def sum_two_int(a: int, b: int) -> int: return a + b + def sum_two_string(a: str, b: str) -> str: return a + b + def sum_strings(*args: List[str]) -> str: result = "" for arg in args: result += arg return result + def concatenate(*args: List[str]) -> str: result = "" for arg in args: result += str(arg) return result + def setup_hook_example(name): logging.warning("setup_hook_example") return f"setup_hook_example: {name}" + def teardown_hook_example(name): logging.warning("teardown_hook_example") return f"teardown_hook_example: {name}" if __name__ == '__main__': + funppy.register("get_httprunner_version", get_httprunner_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) funppy.register("sum_two_int", sum_two_int) + funppy.register("sum_two", sum_two_int) funppy.register("sum_two_string", sum_two_string) funppy.register("sum_strings", sum_strings) funppy.register("setup_hook_example", setup_hook_example) diff --git a/hrp/internal/scaffold/templates/testcases/__init__.py b/hrp/internal/scaffold/templates/testcases/__init__.py new file mode 100644 index 00000000..70cfba53 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/__init__.py @@ -0,0 +1 @@ +# NOTICE: Generated By HttpRunner. DO NOT EDIT! diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml new file mode 100644 index 00000000..7c9bcd19 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -0,0 +1,33 @@ +config: + name: "request methods testcase: reference testcase" + variables: + foo1: testsuite_config_bar1 + expect_foo1: testsuite_config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + +teststeps: +- + name: request with functions + variables: + foo1: testcase_ref_bar1 + expect_foo1: testcase_ref_bar1 + testcase: testcases/demo_requests.yml + export: + - foo3 +- + name: post form data + variables: + foo1: bar1 + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py new file mode 100644 index 00000000..e8707a57 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py @@ -0,0 +1,60 @@ +# NOTE: Generated By HttpRunner v4.0.0-alpha +# FROM: testcases/demo_ref_testcase.yml + + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase + +from testcases.demo_requests_test import TestCaseDemoRequests as DemoRequests + + +class TestCaseDemoRefTestcase(HttpRunner): + + config = ( + Config("request methods testcase: reference testcase") + .variables( + **{ + "foo1": "testsuite_config_bar1", + "expect_foo1": "testsuite_config_bar1", + "expect_foo2": "config_bar2", + } + ) + .base_url("https://postman-echo.com") + .verify(False) + ) + + teststeps = [ + Step( + RunTestCase("request with functions") + .with_variables( + **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"} + ) + .call(DemoRequests) + .export(*["foo3"]) + ), + Step( + RunRequest("post form data") + .with_variables(**{"foo1": "bar1"}) + .post("/post") + .with_headers( + **{ + "User-Agent": "HttpRunner/${get_httprunner_version()}", + "Content-Type": "application/x-www-form-urlencoded", + } + ) + .with_data("foo1=$foo1&foo2=$foo3") + .validate() + .assert_equal("status_code", 200) + .assert_equal("body.form.foo1", "bar1") + .assert_equal("body.form.foo2", "bar21") + ), + ] + + +if __name__ == "__main__": + TestCaseDemoRefTestcase().test_start() diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml new file mode 100644 index 00000000..7c8be928 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -0,0 +1,65 @@ +config: + name: "request methods testcase with functions" + variables: + foo1: config_bar1 + foo2: config_bar2 + expect_foo1: config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + export: ["foo3"] + +teststeps: +- + name: get with params + variables: + foo1: bar11 + foo2: bar21 + sum_v: "${sum_two(1, 2)}" + request: + method: GET + url: /get + params: + foo1: $foo1 + foo2: $foo2 + sum_v: $sum_v + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + extract: + foo3: "body.args.foo2" + validate: + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] +- + name: post raw text + variables: + foo1: "bar12" + foo3: "bar32" + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] +- + name: post form data + variables: + foo2: bar23 + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py new file mode 100644 index 00000000..526961e3 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py @@ -0,0 +1,83 @@ +# NOTE: Generated By HttpRunner v4.0.0-alpha +# FROM: testcases/demo_requests.yml + + +from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase + + +class TestCaseDemoRequests(HttpRunner): + + config = ( + Config("request methods testcase with functions") + .variables( + **{ + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2", + } + ) + .base_url("https://postman-echo.com") + .verify(False) + .export(*["foo3"]) + ) + + teststeps = [ + Step( + RunRequest("get with params") + .with_variables( + **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"} + ) + .get("/get") + .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) + .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) + .extract() + .with_jmespath("body.args.foo2", "foo3") + .validate() + .assert_equal("status_code", 200) + .assert_equal("body.args.foo1", "bar11") + .assert_equal("body.args.sum_v", "3") + .assert_equal("body.args.foo2", "bar21") + ), + Step( + RunRequest("post raw text") + .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) + .post("/post") + .with_headers( + **{ + "User-Agent": "HttpRunner/${get_httprunner_version()}", + "Content-Type": "text/plain", + } + ) + .with_data( + "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + ) + .validate() + .assert_equal("status_code", 200) + .assert_equal( + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", + ) + ), + Step( + RunRequest("post form data") + .with_variables(**{"foo2": "bar23"}) + .post("/post") + .with_headers( + **{ + "User-Agent": "HttpRunner/${get_httprunner_version()}", + "Content-Type": "application/x-www-form-urlencoded", + } + ) + .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") + .validate() + .assert_equal("status_code", 200) + .assert_equal("body.form.foo1", "$expect_foo1") + .assert_equal("body.form.foo2", "bar23") + .assert_equal("body.form.foo3", "bar21") + ), + ] + + +if __name__ == "__main__": + TestCaseDemoRequests().test_start() diff --git a/examples/hrp/demo.json b/hrp/internal/scaffold/templates/testcases/demo_with_funplugin.json similarity index 100% rename from examples/hrp/demo.json rename to hrp/internal/scaffold/templates/testcases/demo_with_funplugin.json diff --git a/examples/hrp/demo.yaml b/hrp/internal/scaffold/templates/testcases/demo_with_funplugin.yaml similarity index 100% rename from examples/hrp/demo.yaml rename to hrp/internal/scaffold/templates/testcases/demo_with_funplugin.yaml diff --git a/hrp/internal/scaffold/templates/testcases/demo_without_funplugin.json b/hrp/internal/scaffold/templates/testcases/demo_without_funplugin.json new file mode 100644 index 00000000..29f311bc --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_without_funplugin.json @@ -0,0 +1,170 @@ +{ + "config": { + "name": "demo without custom function plugin", + "base_url": "https://postman-echo.com", + "variables": { + "a": 12.3, + "b": 3.45, + "n": 5, + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${max($a, $b)}" + }, + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}", + "time": "${get_timestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_without_funplugin.yaml b/hrp/internal/scaffold/templates/testcases/demo_without_funplugin.yaml new file mode 100644 index 00000000..b276c271 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_without_funplugin.yaml @@ -0,0 +1,110 @@ +config: + name: demo without custom function plugin + base_url: https://postman-echo.com + variables: + a: 12.3 + b: 3.45 + "n": 5 + varFoo1: ${gen_random_string($n)} + varFoo2: ${max($a, $b)} +teststeps: + - name: transaction 1 start + transaction: + name: tran1 + type: start + - name: get with params + request: + method: GET + url: /get + params: + foo1: $varFoo1 + foo2: $varFoo2 + headers: + User-Agent: HttpRunnerPlus + variables: + b: 34.5 + "n": 3 + name: get with params + varFoo2: ${max($a, $b)} + extract: + varFoo1: body.args.foo1 + validate: + - check: status_code + assert: equals + expect: 200 + msg: check response status code + - check: headers."Content-Type" + assert: startswith + expect: application/json + - check: body.args.foo1 + assert: length_equals + expect: 5 + msg: check args foo1 + - check: $varFoo1 + assert: length_equals + expect: 5 + msg: check args foo1 + - check: body.args.foo2 + assert: equals + expect: "34.5" + msg: check args foo2 + - name: transaction 1 end + transaction: + name: tran1 + type: end + - name: post json data + request: + method: POST + url: /post + body: + foo1: $varFoo1 + foo2: ${max($a, $b)} + validate: + - check: status_code + assert: equals + expect: 200 + msg: check status code + - check: body.json.foo1 + assert: length_equals + expect: 5 + msg: check args foo1 + - check: body.json.foo2 + assert: equals + expect: 12.3 + msg: check args foo2 + - name: post form data + request: + method: POST + url: /post + headers: + Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + body: + foo1: $varFoo1 + foo2: ${max($a, $b)} + time: ${get_timestamp()} + extract: + varTime: body.form.time + validate: + - check: status_code + assert: equals + expect: 200 + msg: check status code + - check: body.form.foo1 + assert: length_equals + expect: 5 + msg: check args foo1 + - check: body.form.foo2 + assert: equals + expect: "12.3" + msg: check args foo2 + - name: get with timestamp + request: + method: GET + url: /get + params: + time: $varTime + validate: + - check: body.args.time + assert: length_equals + expect: 13 + msg: check extracted var timestamp diff --git a/hrp/plugin_test.go b/hrp/plugin_test.go index 2016ef5b..612e40ae 100644 --- a/hrp/plugin_test.go +++ b/hrp/plugin_test.go @@ -8,32 +8,24 @@ import ( func TestLocateFile(t *testing.T) { // specify target file path - _, err := locateFile("../examples/hrp/plugin/debugtalk.go", "debugtalk.go") + _, err := locateFile(templatesDir+"plugin/debugtalk.go", "debugtalk.go") if !assert.Nil(t, err) { t.Fail() } // specify path with the same dir - _, err = locateFile("../examples/hrp/plugin/hashicorp.go", "debugtalk.go") + _, err = locateFile(templatesDir+"plugin/debugtalk.py", "debugtalk.go") if !assert.Nil(t, err) { t.Fail() } // specify target file path dir - _, err = locateFile("../examples/hrp/plugin/", "debugtalk.go") + _, err = locateFile(templatesDir+"plugin/", "debugtalk.go") if !assert.Nil(t, err) { t.Fail() } // specify wrong path - _, err = locateFile("../examples/hrp", "debugtalk.go") - if !assert.Error(t, err) { - t.Fail() - } - _, err = locateFile("../examples/hrp/demo.json", "debugtalk.go") - if !assert.Error(t, err) { - t.Fail() - } _, err = locateFile(".", "debugtalk.go") if !assert.Error(t, err) { t.Fail() @@ -45,17 +37,17 @@ func TestLocateFile(t *testing.T) { } func TestLocatePythonPlugin(t *testing.T) { - _, err := locatePlugin("../examples/hrp/debugtalk.py") + _, err := locatePlugin(templatesDir + "plugin/debugtalk.py") if !assert.Nil(t, err) { t.Fail() } } func TestLocateGoPlugin(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() - _, err := locatePlugin("../examples/hrp/debugtalk.bin") + _, err := locatePlugin(templatesDir + "debugtalk.bin") if !assert.Nil(t, err) { t.Fail() } diff --git a/hrp/runner_test.go b/hrp/runner_test.go index d6945425..b753ebf9 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -8,31 +8,49 @@ import ( "time" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/hrp/internal/scaffold" ) -func buildHashicorpPlugin() { +func buildHashicorpGoPlugin() { log.Info().Msg("[init] build hashicorp go plugin") cmd := exec.Command("go", "build", - "-o", "../examples/hrp/debugtalk.bin", - "../examples/hrp/plugin/hashicorp.go", "../examples/hrp/plugin/debugtalk.go") + "-o", templatesDir+"debugtalk.bin", templatesDir+"plugin/debugtalk.go") if err := cmd.Run(); err != nil { panic(err) } } -func removeHashicorpPlugin() { - log.Info().Msg("[teardown] remove hashicorp plugin") - os.Remove("../examples/hrp/debugtalk.bin") +func removeHashicorpGoPlugin() { + log.Info().Msg("[teardown] remove hashicorp go plugin") + os.Remove(templatesDir + "debugtalk.bin") +} + +func buildHashicorpPyPlugin() { + log.Info().Msg("[init] prepare hashicorp python plugin") + pluginFile := templatesDir + "debugtalk.py" + err := scaffold.CopyFile("templates/plugin/debugtalk.py", pluginFile) + if err != nil { + panic(err) + } +} + +func removeHashicorpPyPlugin() { + log.Info().Msg("[teardown] remove hashicorp python plugin") + os.Remove(templatesDir + "debugtalk.py") } func TestHttpRunnerWithGoPlugin(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() assertRunTestCases(t) } func TestHttpRunnerWithPythonPlugin(t *testing.T) { + buildHashicorpPyPlugin() + defer removeHashicorpPyPlugin() + assertRunTestCases(t) } @@ -64,7 +82,7 @@ func assertRunTestCases(t *testing.T) { }, ), NewStep("TestCase4").CallRefCase(&demoRefAPIYAMLPath), - NewStep("TestCase5").CallRefCase(&demoTestCaseJSONPath), + NewStep("TestCase5").CallRefCase(&demoTestCaseWithPluginJSONPath), }, } testcase2 := &TestCase{ @@ -153,8 +171,8 @@ func TestInitRendezvous(t *testing.T) { } func TestThinkTime(t *testing.T) { - buildHashicorpPlugin() - defer removeHashicorpPlugin() + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() testcases := []*TestCase{ { diff --git a/httprunner/cli.py b/httprunner/cli.py index 033312c3..d015313e 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -9,7 +9,6 @@ from loguru import logger from httprunner import __description__, __version__ from httprunner.compat import ensure_cli_args from httprunner.make import init_make_parser, main_make -from httprunner.scaffold import init_parser_scaffold, main_scaffold from httprunner.utils import ga_client, init_sentry_sdk init_sentry_sdk() @@ -65,7 +64,6 @@ def main(): subparsers = parser.add_subparsers(help="sub-command help") sub_parser_run = init_parser_run(subparsers) - sub_parser_scaffold = init_parser_scaffold(subparsers) sub_parser_make = init_make_parser(subparsers) if len(sys.argv) == 1: @@ -80,9 +78,6 @@ def main(): elif sys.argv[1] in ["-h", "--help"]: # httprunner -h parser.print_help() - elif sys.argv[1] == "startproject": - # httprunner startproject - sub_parser_scaffold.print_help() elif sys.argv[1] == "run": # httprunner run pytest.main(["-h"]) @@ -109,8 +104,6 @@ def main(): if sys.argv[1] == "run": sys.exit(main_run(extra_args)) - elif sys.argv[1] == "startproject": - main_scaffold(args) elif sys.argv[1] == "make": main_make(args.testcase_path) diff --git a/httprunner/scaffold.py b/httprunner/scaffold.py deleted file mode 100644 index 282bfb08..00000000 --- a/httprunner/scaffold.py +++ /dev/null @@ -1,204 +0,0 @@ -import os.path -import subprocess -import sys - -from loguru import logger - -from httprunner.utils import ga_client - - -def init_parser_scaffold(subparsers): - sub_parser_scaffold = subparsers.add_parser( - "startproject", help="Create a new project with template structure." - ) - sub_parser_scaffold.add_argument( - "project_name", type=str, nargs="?", help="Specify new project name." - ) - return sub_parser_scaffold - - -def create_scaffold(project_name): - """ create scaffold with specified project name. - """ - - def show_tree(prj_name): - try: - print(f"\n$ tree {prj_name} -a") - subprocess.run(["tree", prj_name, "-a"]) - print("") - except FileNotFoundError: - logger.warning("tree command not exists, ignore.") - - if os.path.isdir(project_name): - logger.warning( - f"Project folder {project_name} exists, please specify a new project name." - ) - show_tree(project_name) - return 1 - elif os.path.isfile(project_name): - logger.warning( - f"Project name {project_name} conflicts with existed file, please specify a new one." - ) - return 1 - - logger.info(f"Create new project: {project_name}") - print(f"Project Root Dir: {os.path.join(os.getcwd(), project_name)}\n") - - def create_folder(path): - os.makedirs(path) - msg = f"created folder: {path}" - print(msg) - - def create_file(path, file_content=""): - with open(path, "w", encoding="utf-8") as f: - f.write(file_content) - msg = f"created file: {path}" - print(msg) - - demo_testcase_request_content = """ -config: - name: "request methods testcase with functions" - variables: - foo1: config_bar1 - foo2: config_bar2 - expect_foo1: config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - export: ["foo3"] - -teststeps: -- - name: get with params - variables: - foo1: bar11 - foo2: bar21 - sum_v: "${sum_two(1, 2)}" - request: - method: GET - url: /get - params: - foo1: $foo1 - foo2: $foo2 - sum_v: $sum_v - headers: - User-Agent: HttpRunner/${get_httprunner_version()} - extract: - foo3: "body.args.foo2" - validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] -- - name: post raw text - variables: - foo1: "bar12" - foo3: "bar32" - request: - method: POST - url: /post - headers: - User-Agent: HttpRunner/${get_httprunner_version()} - Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] -- - name: post form data - variables: - foo2: bar23 - request: - method: POST - url: /post - headers: - User-Agent: HttpRunner/${get_httprunner_version()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] -""" - demo_testcase_with_ref_content = """ -config: - name: "request methods testcase: reference testcase" - variables: - foo1: testsuite_config_bar1 - expect_foo1: testsuite_config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - -teststeps: -- - name: request with functions - variables: - foo1: testcase_ref_bar1 - expect_foo1: testcase_ref_bar1 - testcase: testcases/demo_testcase_request.yml - export: - - foo3 -- - name: post form data - variables: - foo1: bar1 - request: - method: POST - url: /post - headers: - User-Agent: HttpRunner/${get_httprunner_version()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] -""" - ignore_content = "\n".join( - [".env", "reports/*", "__pycache__/*", "*.pyc", ".python-version", "logs/*"] - ) - demo_debugtalk_content = """import time - -from httprunner import __version__ - - -def get_httprunner_version(): - return __version__ - - -def sum_two(m, n): - return m + n - - -def sleep(n_secs): - time.sleep(n_secs) -""" - demo_env_content = "\n".join(["USERNAME=leolee", "PASSWORD=123456"]) - - create_folder(project_name) - create_folder(os.path.join(project_name, "har")) - create_folder(os.path.join(project_name, "testcases")) - create_folder(os.path.join(project_name, "reports")) - - create_file( - os.path.join(project_name, "testcases", "demo_testcase_request.yml"), - demo_testcase_request_content, - ) - create_file( - os.path.join(project_name, "testcases", "demo_testcase_ref.yml"), - demo_testcase_with_ref_content, - ) - create_file(os.path.join(project_name, "debugtalk.py"), demo_debugtalk_content) - create_file(os.path.join(project_name, ".env"), demo_env_content) - create_file(os.path.join(project_name, ".gitignore"), ignore_content) - - show_tree(project_name) - return 0 - - -def main_scaffold(args): - ga_client.track_event("Scaffold", "startproject") - sys.exit(create_scaffold(args.project_name)) diff --git a/httprunner/scaffold_test.py b/httprunner/scaffold_test.py deleted file mode 100644 index fbb2ed64..00000000 --- a/httprunner/scaffold_test.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import shutil -import subprocess -import unittest -import platform - -from httprunner.scaffold import create_scaffold - - -class TestScaffold(unittest.TestCase): - def test_create_scaffold(self): - project_name = "projectABC" - create_scaffold(project_name) - self.assertTrue(os.path.isdir(os.path.join(project_name, "har"))) - self.assertTrue(os.path.isdir(os.path.join(project_name, "testcases"))) - self.assertTrue(os.path.isdir(os.path.join(project_name, "reports"))) - self.assertTrue(os.path.isfile(os.path.join(project_name, "debugtalk.py"))) - self.assertTrue(os.path.isfile(os.path.join(project_name, ".env"))) - - # run demo testcases - try: - if platform.system() == "Windows": - subprocess.check_call(["hrun", project_name], shell=True) - else: - subprocess.check_call(["hrun", project_name]) - except subprocess.SubprocessError: - raise - finally: - shutil.rmtree(project_name) From cec3c3177578e8af477c55eba1355de7a623129c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 27 Mar 2022 11:13:03 +0800 Subject: [PATCH 09/13] change: lock funplugin version when creating scaffold project --- docs/CHANGELOG.md | 1 + go.mod | 2 +- go.sum | 4 ++-- hrp/internal/scaffold/main.go | 16 ++++++++++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e38b7a3c..36731be9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,7 @@ - feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json profile file - change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis +- change: lock funplugin version when creating scaffold project - fix: call referenced api/testcase with relative path **python version** diff --git a/go.mod b/go.mod index 431f53b8..e794a328 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/getsentry/sentry-go v0.13.0 github.com/google/uuid v1.3.0 - github.com/httprunner/funplugin v0.4.0 + github.com/httprunner/funplugin v0.4.2 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index 68b20217..3c3270cd 100644 --- a/go.sum +++ b/go.sum @@ -240,8 +240,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.0 h1:jSptZ6Ki0Dh3uvpLDbmxE6kSqVv0FHaQnHs0Qt+6SS8= -github.com/httprunner/funplugin v0.4.0/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.2 h1:iDeg3GVCKdimgZQ40xq0kxHqhL/DQmRxs3DRjzOpUuo= +github.com/httprunner/funplugin v0.4.2/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 37f819f1..a8005fdb 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -148,7 +148,9 @@ func createGoPlugin(projectName string) error { } // download plugin dependency - if err := builtin.ExecCommand(exec.Command("go", "get", "github.com/httprunner/funplugin"), pluginDir); err != nil { + // funplugin version should be locked + funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) + if err := builtin.ExecCommand(exec.Command("go", "get", funplugin), pluginDir); err != nil { return err } @@ -167,12 +169,18 @@ func createPythonPlugin(projectName string) error { pluginFile := filepath.Join(projectName, "debugtalk.py") err := CopyFile("templates/plugin/debugtalk.py", pluginFile) if err != nil { - return err + return errors.Wrap(err, "copy file failed") } // create python venv - if _, err := shared.PreparePython3Venv(pluginFile); err != nil { - return err + home, err := os.UserHomeDir() + if err != nil { + return errors.Wrap(err, "get user home dir failed") + } + venvDir := filepath.Join(home, ".hrp", "venv") + _, err = shared.EnsurePython3Venv(venvDir) + if err != nil { + return errors.Wrap(err, "ensure python venv failed") } return nil From eed9a78bbfcfbe66df30c46765425198316e17d2 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 27 Mar 2022 11:28:46 +0800 Subject: [PATCH 10/13] change: move html template to scaffold --- hrp/internal/{ => scaffold/templates}/report/template.html | 0 hrp/runner.go | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename hrp/internal/{ => scaffold/templates}/report/template.html (100%) diff --git a/hrp/internal/report/template.html b/hrp/internal/scaffold/templates/report/template.html similarity index 100% rename from hrp/internal/report/template.html rename to hrp/internal/scaffold/templates/report/template.html diff --git a/hrp/runner.go b/hrp/runner.go index 7bb0de3d..828cf8d8 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -238,6 +238,7 @@ func (r *caseRunner) reset() *caseRunner { func (r *caseRunner) run() error { config := r.TestCase.Config + log.Info().Str("testcase", config.Name).Msg("run testcase start") // init plugin var err error if r.parser.plugin, err = initPlugin(config.Path, r.hrpRunner.pluginLogOn); err != nil { @@ -251,7 +252,6 @@ func (r *caseRunner) run() error { if err := r.parseConfig(config); err != nil { return err } - log.Info().Str("testcase", config.Name).Msg("run testcase start") r.startTime = time.Now() for index := range r.TestCase.TestSteps { @@ -1048,7 +1048,7 @@ func setBodyBytes(req *http.Request, data []byte) { req.ContentLength = int64(len(data)) } -//go:embed internal/report/template.html +//go:embed internal/scaffold/templates/report/template.html var reportTemplate string func (s *Summary) genHTMLReport() error { From 98d154b654c15991a031d2651f3f6be7d866ecf6 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 27 Mar 2022 11:35:41 +0800 Subject: [PATCH 11/13] refactor: relocate hrp tests --- examples/hrp/demo_test.py | 63 ------------------ examples/hrp/httpbin.json | 51 --------------- examples/hrp/plugin/debugtalk.go | 65 ------------------- examples/hrp/plugin/hashicorp.go | 18 ----- {examples/hrp => hrp/tests}/compat_test.go | 6 +- {examples/hrp => hrp/tests}/extract_test.go | 2 +- {examples/hrp => hrp/tests}/function_test.go | 2 +- .../hrp => hrp/tests}/rendezvous_test.go | 2 +- {examples/hrp => hrp/tests}/request_test.go | 2 +- {examples/hrp => hrp/tests}/validate_test.go | 2 +- {examples/hrp => hrp/tests}/variables_test.go | 2 +- 11 files changed, 9 insertions(+), 206 deletions(-) delete mode 100644 examples/hrp/demo_test.py delete mode 100644 examples/hrp/httpbin.json delete mode 100644 examples/hrp/plugin/debugtalk.go delete mode 100644 examples/hrp/plugin/hashicorp.go rename {examples/hrp => hrp/tests}/compat_test.go (69%) rename {examples/hrp => hrp/tests}/extract_test.go (99%) rename {examples/hrp => hrp/tests}/function_test.go (99%) rename {examples/hrp => hrp/tests}/rendezvous_test.go (98%) rename {examples/hrp => hrp/tests}/request_test.go (99%) rename {examples/hrp => hrp/tests}/validate_test.go (99%) rename {examples/hrp => hrp/tests}/variables_test.go (99%) diff --git a/examples/hrp/demo_test.py b/examples/hrp/demo_test.py deleted file mode 100644 index e2eddc1f..00000000 --- a/examples/hrp/demo_test.py +++ /dev/null @@ -1,63 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.6 -# FROM: hrp/examples/demo.json - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - - -class TestCaseDemo(HttpRunner): - - config = ( - Config("demo with complex mechanisms") - .variables( - **{ - "a": 12.3, - "b": 3.45, - "n": 5, - "varFoo1": "${gen_random_string($n)}", - "varFoo2": "${max($a, $b)}", - } - ) - .base_url("https://postman-echo.com") - ) - - teststeps = [ - Step( - RunRequest("get with params") - .with_variables(**{"b": 34.5, "n": 3, "varFoo2": "${max($a, $b)}"}) - .get("/get") - .with_params(**{"foo1": "$varFoo1", "foo2": "$varFoo2"}) - .with_headers(**{"User-Agent": "HttpRunnerPlus"}) - .extract() - .with_jmespath("body.args.foo1", "varFoo1") - .validate() - .assert_equal("status_code", 200) - .assert_equal('headers."Content-Type"', "application/json") - .assert_equal("body.args.foo1", 5) - .assert_equal("$varFoo1", 5) - .assert_equal("body.args.foo2", "34.5") - ), - Step( - RunRequest("post json data") - .post("/post") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.json.foo1", 5) - .assert_equal("body.json.foo2", 12.3) - ), - Step( - RunRequest("post form data") - .post("/post") - .with_headers( - **{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"} - ) - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", 5) - .assert_equal("body.form.foo2", "12.3") - ), - ] - - -if __name__ == "__main__": - TestCaseDemo().test_start() diff --git a/examples/hrp/httpbin.json b/examples/hrp/httpbin.json deleted file mode 100644 index 2bc11130..00000000 --- a/examples/hrp/httpbin.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "config": { - "name": "testcase description", - "variables": {}, - "verify": false - }, - "teststeps": [ - { - "name": "/get", - "request": { - "url": "http://httpbin.org/get", - "method": "GET", - "headers": { - "Host": "httpbin.org", - "Connection": "keep-alive", - "accept": "application/json", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.50", - "Referer": "http://httpbin.org/", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "assert response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "equals", - "expect": "application/json", - "msg": "assert response header Content-Type" - }, - { - "check": "body.origin", - "assert": "equals", - "expect": "117.176.133.109", - "msg": "assert response body origin" - }, - { - "check": "body.url", - "assert": "equals", - "expect": "http://httpbin.org/get", - "msg": "assert response body url" - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/hrp/plugin/debugtalk.go b/examples/hrp/plugin/debugtalk.go deleted file mode 100644 index 64cdc946..00000000 --- a/examples/hrp/plugin/debugtalk.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "fmt" - "log" -) - -func init() { - log.Println("plugin init function called") -} - -func SumTwoInt(a, b int) int { - return a + b -} - -func SumInts(args ...int) int { - var sum int - for _, arg := range args { - sum += arg - } - return sum -} - -func Sum(args ...interface{}) (interface{}, error) { - var sum float64 - for _, arg := range args { - switch v := arg.(type) { - case int: - sum += float64(v) - case float64: - sum += v - default: - return nil, fmt.Errorf("unexpected type: %T", arg) - } - } - return sum, nil -} - -func SumTwoString(a, b string) string { - return a + b -} - -func SumStrings(s ...string) string { - var sum string - for _, arg := range s { - sum += arg - } - return sum -} - -func Concatenate(args ...interface{}) (interface{}, error) { - var result string - for _, arg := range args { - result += fmt.Sprintf("%v", arg) - } - return result, nil -} - -func SetupHookExample(args string) string { - return fmt.Sprintf("step name: %v, setup...", args) -} - -func TeardownHookExample(args string) string { - return fmt.Sprintf("step name: %v, teardown...", args) -} diff --git a/examples/hrp/plugin/hashicorp.go b/examples/hrp/plugin/hashicorp.go deleted file mode 100644 index 4d09f339..00000000 --- a/examples/hrp/plugin/hashicorp.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "github.com/httprunner/funplugin/fungo" -) - -// register functions and build to plugin binary -func main() { - fungo.Register("sum_ints", SumInts) - fungo.Register("sum_two_int", SumTwoInt) - fungo.Register("sum", Sum) - fungo.Register("sum_two_string", SumTwoString) - fungo.Register("sum_strings", SumStrings) - fungo.Register("concatenate", Concatenate) - fungo.Register("setup_hook_example", SetupHookExample) - fungo.Register("teardown_hook_example", TeardownHookExample) - fungo.Serve() -} diff --git a/examples/hrp/compat_test.go b/hrp/tests/compat_test.go similarity index 69% rename from examples/hrp/compat_test.go rename to hrp/tests/compat_test.go index 336b30d6..74dc5fe0 100644 --- a/examples/hrp/compat_test.go +++ b/hrp/tests/compat_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" @@ -8,8 +8,8 @@ import ( // generated by examples/data/har/demo.har using HttpRunner v3.1.6 var ( - demoHttpRunnerJSONPath hrp.TestCasePath = "demo_httprunner.json" - demoHttpRunnerYAMLPath hrp.TestCasePath = "demo_httprunner.yaml" + demoHttpRunnerJSONPath hrp.TestCasePath = "../../examples/hrp/demo_httprunner.json" + demoHttpRunnerYAMLPath hrp.TestCasePath = "../../examples/hrp/demo_httprunner.yaml" ) func TestCompatTestCase(t *testing.T) { diff --git a/examples/hrp/extract_test.go b/hrp/tests/extract_test.go similarity index 99% rename from examples/hrp/extract_test.go rename to hrp/tests/extract_test.go index ae29b623..e16f2a52 100644 --- a/examples/hrp/extract_test.go +++ b/hrp/tests/extract_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" diff --git a/examples/hrp/function_test.go b/hrp/tests/function_test.go similarity index 99% rename from examples/hrp/function_test.go rename to hrp/tests/function_test.go index 0c2e8106..09dfa2f1 100644 --- a/examples/hrp/function_test.go +++ b/hrp/tests/function_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" diff --git a/examples/hrp/rendezvous_test.go b/hrp/tests/rendezvous_test.go similarity index 98% rename from examples/hrp/rendezvous_test.go rename to hrp/tests/rendezvous_test.go index d8967f31..760e99cf 100644 --- a/examples/hrp/rendezvous_test.go +++ b/hrp/tests/rendezvous_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" diff --git a/examples/hrp/request_test.go b/hrp/tests/request_test.go similarity index 99% rename from examples/hrp/request_test.go rename to hrp/tests/request_test.go index 75a76762..922734a1 100644 --- a/examples/hrp/request_test.go +++ b/hrp/tests/request_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" diff --git a/examples/hrp/validate_test.go b/hrp/tests/validate_test.go similarity index 99% rename from examples/hrp/validate_test.go rename to hrp/tests/validate_test.go index 1a85d410..94922a98 100644 --- a/examples/hrp/validate_test.go +++ b/hrp/tests/validate_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" diff --git a/examples/hrp/variables_test.go b/hrp/tests/variables_test.go similarity index 99% rename from examples/hrp/variables_test.go rename to hrp/tests/variables_test.go index d2a9f834..1bc73d6a 100644 --- a/examples/hrp/variables_test.go +++ b/hrp/tests/variables_test.go @@ -1,4 +1,4 @@ -package examples +package tests import ( "testing" From cdabde4dc8563710a39b88a1ff4dfb67499e9010 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 27 Mar 2022 12:00:09 +0800 Subject: [PATCH 12/13] tests: add examples with scaffold --- .github/workflows/hrp-scaffold.yml | 2 +- examples/demo-with-go-plugin/.gitignore | 15 ++ examples/demo-with-go-plugin/har/.keep | 0 .../demo-with-go-plugin/plugin/debugtalk.go | 57 +++++ examples/demo-with-go-plugin/plugin/go.mod | 5 + examples/demo-with-go-plugin/plugin/go.sum | 196 ++++++++++++++++++ .../testcases/demo_ref_testcase.yml | 33 +++ .../testcases/demo_requests.yml | 65 ++++++ .../testcases/demo_with_funplugin.json | 176 ++++++++++++++++ examples/demo-with-py-plugin/.gitignore | 15 ++ examples/demo-with-py-plugin/debugtalk.py | 73 +++++++ examples/demo-with-py-plugin/har/.keep | 0 .../testcases/demo_ref_testcase.yml | 33 +++ .../testcases/demo_requests.yml | 65 ++++++ .../testcases/demo_with_funplugin.json | 176 ++++++++++++++++ examples/demo-without-plugin/.gitignore | 15 ++ examples/demo-without-plugin/har/.keep | 0 .../testcases/demo_without_funplugin.json | 170 +++++++++++++++ hrp/internal/scaffold/examples_test.go | 29 +++ hrp/internal/scaffold/main.go | 10 +- 20 files changed, 1132 insertions(+), 3 deletions(-) create mode 100644 examples/demo-with-go-plugin/.gitignore create mode 100644 examples/demo-with-go-plugin/har/.keep create mode 100644 examples/demo-with-go-plugin/plugin/debugtalk.go create mode 100644 examples/demo-with-go-plugin/plugin/go.mod create mode 100644 examples/demo-with-go-plugin/plugin/go.sum create mode 100644 examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml create mode 100644 examples/demo-with-go-plugin/testcases/demo_requests.yml create mode 100644 examples/demo-with-go-plugin/testcases/demo_with_funplugin.json create mode 100644 examples/demo-with-py-plugin/.gitignore create mode 100644 examples/demo-with-py-plugin/debugtalk.py create mode 100644 examples/demo-with-py-plugin/har/.keep create mode 100644 examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml create mode 100644 examples/demo-with-py-plugin/testcases/demo_requests.yml create mode 100644 examples/demo-with-py-plugin/testcases/demo_with_funplugin.json create mode 100644 examples/demo-without-plugin/.gitignore create mode 100644 examples/demo-without-plugin/har/.keep create mode 100644 examples/demo-without-plugin/testcases/demo_without_funplugin.json create mode 100644 hrp/internal/scaffold/examples_test.go diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 62b66f1c..de829199 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -70,4 +70,4 @@ jobs: - name: Run start project run: ./output/hrp startproject demo --ignore-plugin - name: Run demo tests - run: ./output/hrp run demo/testcases/demo_without_plugin.json + run: ./output/hrp run demo/testcases/demo_without_funplugin.json diff --git a/examples/demo-with-go-plugin/.gitignore b/examples/demo-with-go-plugin/.gitignore new file mode 100644 index 00000000..33401380 --- /dev/null +++ b/examples/demo-with-go-plugin/.gitignore @@ -0,0 +1,15 @@ +.env +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/examples/demo-with-go-plugin/har/.keep b/examples/demo-with-go-plugin/har/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go new file mode 100644 index 00000000..f99a1321 --- /dev/null +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + + "github.com/httprunner/funplugin/fungo" +) + +func SumTwoInt(a, b int) int { + return a + b +} + +func SumInts(args ...int) int { + var sum int + for _, arg := range args { + sum += arg + } + return sum +} + +func Sum(args ...interface{}) (interface{}, error) { + var sum float64 + for _, arg := range args { + switch v := arg.(type) { + case int: + sum += float64(v) + case float64: + sum += v + default: + return nil, fmt.Errorf("unexpected type: %T", arg) + } + } + return sum, nil +} + +func SetupHookExample(args string) string { + return fmt.Sprintf("step name: %v, setup...", args) +} + +func TeardownHookExample(args string) string { + return fmt.Sprintf("step name: %v, teardown...", args) +} + +func GetVersion() string { + return "v4.0.0-alpha" +} + +func main() { + fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("sum_ints", SumInts) + fungo.Register("sum_two_int", SumTwoInt) + fungo.Register("sum_two", SumTwoInt) + fungo.Register("sum", Sum) + fungo.Register("setup_hook_example", SetupHookExample) + fungo.Register("teardown_hook_example", TeardownHookExample) + fungo.Serve() +} diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod new file mode 100644 index 00000000..a8aafa0f --- /dev/null +++ b/examples/demo-with-go-plugin/plugin/go.mod @@ -0,0 +1,5 @@ +module plugin + +go 1.16 + +require github.com/httprunner/funplugin v0.4.2 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum new file mode 100644 index 00000000..85aa768d --- /dev/null +++ b/examples/demo-with-go-plugin/plugin/go.sum @@ -0,0 +1,196 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= +github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/httprunner/funplugin v0.4.2 h1:iDeg3GVCKdimgZQ40xq0kxHqhL/DQmRxs3DRjzOpUuo= +github.com/httprunner/funplugin v0.4.2/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml new file mode 100644 index 00000000..7c9bcd19 --- /dev/null +++ b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml @@ -0,0 +1,33 @@ +config: + name: "request methods testcase: reference testcase" + variables: + foo1: testsuite_config_bar1 + expect_foo1: testsuite_config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + +teststeps: +- + name: request with functions + variables: + foo1: testcase_ref_bar1 + expect_foo1: testcase_ref_bar1 + testcase: testcases/demo_requests.yml + export: + - foo3 +- + name: post form data + variables: + foo1: bar1 + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] diff --git a/examples/demo-with-go-plugin/testcases/demo_requests.yml b/examples/demo-with-go-plugin/testcases/demo_requests.yml new file mode 100644 index 00000000..7c8be928 --- /dev/null +++ b/examples/demo-with-go-plugin/testcases/demo_requests.yml @@ -0,0 +1,65 @@ +config: + name: "request methods testcase with functions" + variables: + foo1: config_bar1 + foo2: config_bar2 + expect_foo1: config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + export: ["foo3"] + +teststeps: +- + name: get with params + variables: + foo1: bar11 + foo2: bar21 + sum_v: "${sum_two(1, 2)}" + request: + method: GET + url: /get + params: + foo1: $foo1 + foo2: $foo2 + sum_v: $sum_v + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + extract: + foo3: "body.args.foo2" + validate: + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] +- + name: post raw text + variables: + foo1: "bar12" + foo3: "bar32" + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] +- + name: post form data + variables: + foo2: bar23 + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-with-go-plugin/testcases/demo_with_funplugin.json b/examples/demo-with-go-plugin/testcases/demo_with_funplugin.json new file mode 100644 index 00000000..1bb63ed8 --- /dev/null +++ b/examples/demo-with-go-plugin/testcases/demo_with_funplugin.json @@ -0,0 +1,176 @@ +{ + "config": { + "name": "demo with complex mechanisms", + "base_url": "https://postman-echo.com", + "variables": { + "a": "${sum(10, 2.3)}", + "b": 3.45, + "n": "${sum_ints(1, 2, 2)}", + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${max($a, $b)}" + }, + "setup_hooks": [ + "${setup_hook_example($name)}" + ], + "teardown_hooks": [ + "${teardown_hook_example($name)}" + ], + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}", + "time": "${get_timestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-py-plugin/.gitignore b/examples/demo-with-py-plugin/.gitignore new file mode 100644 index 00000000..33401380 --- /dev/null +++ b/examples/demo-with-py-plugin/.gitignore @@ -0,0 +1,15 @@ +.env +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py new file mode 100644 index 00000000..743e7f62 --- /dev/null +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -0,0 +1,73 @@ +import logging +import time +from typing import List + +import funppy + + +def get_httprunner_version(): + return "v4.0.0-alpha" + + +def sleep(n_secs): + time.sleep(n_secs) + + +def sum(*args): + result = 0 + for arg in args: + result += arg + return result + + +def sum_ints(*args: List[int]) -> int: + result = 0 + for arg in args: + result += arg + return result + + +def sum_two_int(a: int, b: int) -> int: + return a + b + + +def sum_two_string(a: str, b: str) -> str: + return a + b + + +def sum_strings(*args: List[str]) -> str: + result = "" + for arg in args: + result += arg + return result + + +def concatenate(*args: List[str]) -> str: + result = "" + for arg in args: + result += str(arg) + return result + + +def setup_hook_example(name): + logging.warning("setup_hook_example") + return f"setup_hook_example: {name}" + + +def teardown_hook_example(name): + logging.warning("teardown_hook_example") + return f"teardown_hook_example: {name}" + + +if __name__ == '__main__': + funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("sum", sum) + funppy.register("sum_ints", sum_ints) + funppy.register("concatenate", concatenate) + funppy.register("sum_two_int", sum_two_int) + funppy.register("sum_two", sum_two_int) + funppy.register("sum_two_string", sum_two_string) + funppy.register("sum_strings", sum_strings) + funppy.register("setup_hook_example", setup_hook_example) + funppy.register("teardown_hook_example", teardown_hook_example) + funppy.serve() diff --git a/examples/demo-with-py-plugin/har/.keep b/examples/demo-with-py-plugin/har/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml new file mode 100644 index 00000000..7c9bcd19 --- /dev/null +++ b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml @@ -0,0 +1,33 @@ +config: + name: "request methods testcase: reference testcase" + variables: + foo1: testsuite_config_bar1 + expect_foo1: testsuite_config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + +teststeps: +- + name: request with functions + variables: + foo1: testcase_ref_bar1 + expect_foo1: testcase_ref_bar1 + testcase: testcases/demo_requests.yml + export: + - foo3 +- + name: post form data + variables: + foo1: bar1 + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] diff --git a/examples/demo-with-py-plugin/testcases/demo_requests.yml b/examples/demo-with-py-plugin/testcases/demo_requests.yml new file mode 100644 index 00000000..7c8be928 --- /dev/null +++ b/examples/demo-with-py-plugin/testcases/demo_requests.yml @@ -0,0 +1,65 @@ +config: + name: "request methods testcase with functions" + variables: + foo1: config_bar1 + foo2: config_bar2 + expect_foo1: config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + export: ["foo3"] + +teststeps: +- + name: get with params + variables: + foo1: bar11 + foo2: bar21 + sum_v: "${sum_two(1, 2)}" + request: + method: GET + url: /get + params: + foo1: $foo1 + foo2: $foo2 + sum_v: $sum_v + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + extract: + foo3: "body.args.foo2" + validate: + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] +- + name: post raw text + variables: + foo1: "bar12" + foo3: "bar32" + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] +- + name: post form data + variables: + foo2: bar23 + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-with-py-plugin/testcases/demo_with_funplugin.json b/examples/demo-with-py-plugin/testcases/demo_with_funplugin.json new file mode 100644 index 00000000..1bb63ed8 --- /dev/null +++ b/examples/demo-with-py-plugin/testcases/demo_with_funplugin.json @@ -0,0 +1,176 @@ +{ + "config": { + "name": "demo with complex mechanisms", + "base_url": "https://postman-echo.com", + "variables": { + "a": "${sum(10, 2.3)}", + "b": 3.45, + "n": "${sum_ints(1, 2, 2)}", + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${max($a, $b)}" + }, + "setup_hooks": [ + "${setup_hook_example($name)}" + ], + "teardown_hooks": [ + "${teardown_hook_example($name)}" + ], + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}", + "time": "${get_timestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-without-plugin/.gitignore b/examples/demo-without-plugin/.gitignore new file mode 100644 index 00000000..33401380 --- /dev/null +++ b/examples/demo-without-plugin/.gitignore @@ -0,0 +1,15 @@ +.env +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/examples/demo-without-plugin/har/.keep b/examples/demo-without-plugin/har/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/demo-without-plugin/testcases/demo_without_funplugin.json b/examples/demo-without-plugin/testcases/demo_without_funplugin.json new file mode 100644 index 00000000..29f311bc --- /dev/null +++ b/examples/demo-without-plugin/testcases/demo_without_funplugin.json @@ -0,0 +1,170 @@ +{ + "config": { + "name": "demo without custom function plugin", + "base_url": "https://postman-echo.com", + "variables": { + "a": 12.3, + "b": 3.45, + "n": 5, + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${max($a, $b)}" + }, + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}", + "time": "${get_timestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/internal/scaffold/examples_test.go b/hrp/internal/scaffold/examples_test.go new file mode 100644 index 00000000..35ec4381 --- /dev/null +++ b/hrp/internal/scaffold/examples_test.go @@ -0,0 +1,29 @@ +package scaffold + +import ( + "os" + "testing" +) + +func TestGenDemoExamples(t *testing.T) { + dir := "../../../examples/demo-with-go-plugin" + os.RemoveAll(dir) + err := CreateScaffold(dir, Go) + if err != nil { + t.Fail() + } + + dir = "../../../examples/demo-with-py-plugin" + os.RemoveAll(dir) + err = CreateScaffold(dir, Py) + if err != nil { + t.Fail() + } + + dir = "../../../examples/demo-without-plugin" + os.RemoveAll(dir) + err = CreateScaffold(dir, Ignore) + if err != nil { + t.Fail() + } +} diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index a8005fdb..b7c1c0ad 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -68,12 +68,18 @@ func CreateScaffold(projectName string, pluginType PluginType) error { if err := builtin.CreateFolder(filepath.Join(projectName, "har")); err != nil { return err } + if err := builtin.CreateFile(filepath.Join(projectName, "har", ".keep"), ""); err != nil { + return err + } if err := builtin.CreateFolder(filepath.Join(projectName, "testcases")); err != nil { return err } if err := builtin.CreateFolder(filepath.Join(projectName, "reports")); err != nil { return err } + if err := builtin.CreateFile(filepath.Join(projectName, "reports", ".keep"), ""); err != nil { + return err + } // create .gitignore err := CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore")) @@ -88,8 +94,8 @@ func CreateScaffold(projectName string, pluginType PluginType) error { // create demo testcases if pluginType == Ignore { - err := CopyFile("templates/testcases/demo_without_plugin.json", - filepath.Join(projectName, "testcases", "demo_without_plugin.json")) + err := CopyFile("templates/testcases/demo_without_funplugin.json", + filepath.Join(projectName, "testcases", "demo_without_funplugin.json")) if err != nil { return err } From 41f60d254fa9ada2387e26334ecd67135b37b7f4 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 27 Mar 2022 13:05:20 +0800 Subject: [PATCH 13/13] fix: call referenced api/testcase with relative path --- .github/workflows/hrp-scaffold.yml | 16 ++- docs/CHANGELOG.md | 2 +- examples/hrp/demo_httprunner.json | 135 ------------------ examples/hrp/demo_httprunner.yaml | 81 ----------- examples/hrp/ref_api_test.yaml | 47 ------ examples/hrp/ref_testcase_test.json | 18 --- examples/hrp/ref_testcase_test.yaml | 11 -- examples/hrp/think_time_test.yaml | 40 ------ hrp/convert.go | 46 +++++- hrp/convert_test.go | 46 ++---- hrp/internal/builtin/utils.go | 12 +- .../internal/scaffold/templates}/api/get.json | 0 .../internal/scaffold/templates}/api/get.yml | 0 .../scaffold/templates}/api/post.json | 0 .../internal/scaffold/templates}/api/post.yml | 0 .../internal/scaffold/templates}/api/put.json | 0 .../internal/scaffold/templates}/api/put.yml | 0 .../templates/testcases/demo_ref_api.json | 0 hrp/models.go | 17 ++- hrp/parser_test.go | 9 +- hrp/runner_test.go | 80 ++++++++--- hrp/step.go | 21 ++- hrp/tests/compat_test.go | 25 ---- 23 files changed, 170 insertions(+), 436 deletions(-) delete mode 100644 examples/hrp/demo_httprunner.json delete mode 100644 examples/hrp/demo_httprunner.yaml delete mode 100644 examples/hrp/ref_api_test.yaml delete mode 100644 examples/hrp/ref_testcase_test.json delete mode 100644 examples/hrp/ref_testcase_test.yaml delete mode 100644 examples/hrp/think_time_test.yaml rename {examples/hrp => hrp/internal/scaffold/templates}/api/get.json (100%) rename {examples/hrp => hrp/internal/scaffold/templates}/api/get.yml (100%) rename {examples/hrp => hrp/internal/scaffold/templates}/api/post.json (100%) rename {examples/hrp => hrp/internal/scaffold/templates}/api/post.yml (100%) rename {examples/hrp => hrp/internal/scaffold/templates}/api/put.json (100%) rename {examples/hrp => hrp/internal/scaffold/templates}/api/put.yml (100%) rename examples/hrp/ref_api_test.json => hrp/internal/scaffold/templates/testcases/demo_ref_api.json (100%) delete mode 100644 hrp/tests/compat_test.go diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index de829199..0bf56c20 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -25,8 +25,11 @@ jobs: run: make build - name: Run start project run: ./output/hrp startproject demo - - name: Run demo tests + - name: Run generated demo tests run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml + - name: Run demo in examples + run: | + ./output/hrp run examples/demo-with-py-plugin/testcases/demo_with_funplugin.json scaffold-with-go-plugin: strategy: @@ -47,8 +50,12 @@ jobs: run: make build - name: Run start project run: ./output/hrp startproject demo --go - - name: Run demo tests + - name: Run generated demo tests run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml + - name: Run demo in examples + run: | + go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go + ./output/hrp run examples/demo-with-go-plugin/testcases/demo_with_funplugin.json scaffold-without-custom-plugin: strategy: @@ -69,5 +76,8 @@ jobs: run: make build - name: Run start project run: ./output/hrp startproject demo --ignore-plugin - - name: Run demo tests + - name: Run generated demo tests run: ./output/hrp run demo/testcases/demo_without_funplugin.json + - name: Run demo in examples + run: | + ./output/hrp run examples/demo-without-plugin/testcases/demo_without_funplugin.json diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 36731be9..c726e78b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 4.0.0 +## v4.0.0-alpha - refactor: merge [hrp] into httprunner v4, which will include golang and python dual engine diff --git a/examples/hrp/demo_httprunner.json b/examples/hrp/demo_httprunner.json deleted file mode 100644 index de017c96..00000000 --- a/examples/hrp/demo_httprunner.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "config": { - "name": "testcase description", - "variables": {}, - "verify": false - }, - "teststeps": [ - { - "name": "/get", - "request": { - "url": "https://postman-echo.com/get", - "params": { - "foo1": "HDnY8", - "foo2": "34.5" - }, - "method": "GET", - "headers": { - "Host": "postman-echo.com", - "User-Agent": "HttpRunnerPlus", - "Accept-Encoding": "gzip" - } - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "headers.Content-Type", - "application/json; charset=utf-8" - ] - }, - { - "eq": [ - "body.url", - "https://postman-echo.com/get?foo1=HDnY8&foo2=34.5" - ] - } - ] - }, - { - "name": "/post", - "request": { - "url": "https://postman-echo.com/post", - "method": "POST", - "cookies": { - "sails.sid": "s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk" - }, - "headers": { - "Host": "postman-echo.com", - "User-Agent": "Go-http-client/1.1", - "Content-Length": "28", - "Content-Type": "application/json; charset=UTF-8", - "Cookie": "sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk", - "Accept-Encoding": "gzip" - }, - "json": { - "foo1": "HDnY8", - "foo2": 12.3 - } - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "headers.Content-Type", - "application/json; charset=utf-8" - ] - }, - { - "eq": [ - "body.url", - "https://postman-echo.com/post" - ] - } - ] - }, - { - "name": "/post", - "request": { - "url": "https://postman-echo.com/post", - "method": "POST", - "cookies": { - "sails.sid": "s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw" - }, - "headers": { - "Host": "postman-echo.com", - "User-Agent": "Go-http-client/1.1", - "Content-Length": "20", - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", - "Cookie": "sails.sid=s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw", - "Accept-Encoding": "gzip" - }, - "data": { - "foo1": "HDnY8", - "foo2": "12.3" - } - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "headers.Content-Type", - "application/json; charset=utf-8" - ] - }, - { - "eq": [ - "body.data", - "" - ] - }, - { - "eq": [ - "body.url", - "https://postman-echo.com/post" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/hrp/demo_httprunner.yaml b/examples/hrp/demo_httprunner.yaml deleted file mode 100644 index 0f39723f..00000000 --- a/examples/hrp/demo_httprunner.yaml +++ /dev/null @@ -1,81 +0,0 @@ -config: - name: testcase description - variables: {} - verify: false -teststeps: -- name: /get - request: - headers: - Accept-Encoding: gzip - Host: postman-echo.com - User-Agent: HttpRunnerPlus - method: GET - params: - foo1: HDnY8 - foo2: '34.5' - url: https://postman-echo.com/get - validate: - - eq: - - status_code - - 200 - - eq: - - headers.Content-Type - - application/json; charset=utf-8 - - eq: - - body.url - - https://postman-echo.com/get?foo1=HDnY8&foo2=34.5 -- name: /post - request: - cookies: - sails.sid: s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk - headers: - Accept-Encoding: gzip - Content-Length: '28' - Content-Type: application/json; charset=UTF-8 - Cookie: sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk - Host: postman-echo.com - User-Agent: Go-http-client/1.1 - json: - foo1: HDnY8 - foo2: 12.3 - method: POST - url: https://postman-echo.com/post - validate: - - eq: - - status_code - - 200 - - eq: - - headers.Content-Type - - application/json; charset=utf-8 - - eq: - - body.url - - https://postman-echo.com/post -- name: /post - request: - cookies: - sails.sid: s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw - data: - foo1: HDnY8 - foo2: '12.3' - headers: - Accept-Encoding: gzip - Content-Length: '20' - Content-Type: application/x-www-form-urlencoded; charset=UTF-8 - Cookie: sails.sid=s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw - Host: postman-echo.com - User-Agent: Go-http-client/1.1 - method: POST - url: https://postman-echo.com/post - validate: - - eq: - - status_code - - 200 - - eq: - - headers.Content-Type - - application/json; charset=utf-8 - - eq: - - body.data - - '' - - eq: - - body.url - - https://postman-echo.com/post diff --git a/examples/hrp/ref_api_test.yaml b/examples/hrp/ref_api_test.yaml deleted file mode 100644 index c920aae0..00000000 --- a/examples/hrp/ref_api_test.yaml +++ /dev/null @@ -1,47 +0,0 @@ -config: - name: 'api test demo' - variables: - user_agent: iOS/10.3 - device_sn: TESTCASE_SETUP_XXX - os_platform: ios - app_version: 2.8.6 - base_url: 'https://postman-echo.com' - herader: - - Accept: '*/*' - Accept-Encoding: 'gzip, deflate, br' - Cache-Control: no-cache - Connection: keep-alive - Host: postman-echo.com - User-Agent: PostmanRuntime/7.28.4 - verify: false - export: - - session_token -teststeps: - - name: 'test api /get' - api: api/get.json - variables: - user_agent: iOS/10.4 - device_sn: $device_sn - os_platform: ios - app_version: 2.8.7 - extract: - session_token: 'body.headers."postman-token"' - - name: 'test api /post' - api: api/post.json - variables: - user_agent: iOS/10.5 - device_sn: $device_sn - os_platform: ios - app_version: 2.8.9 - validate: - - { eq: [ status_code, 200 ] } - - { eq: [ body.headers.postman-token, ea19464c-ddd4-4724-abe9-5e2b254c2723 ] } - - name: 'test api /put' - api: api/put.json - variables: - user_agent: iOS/10.6 - device_sn: $device_sn - os_platform: ios - app_version: 2.8.10 - extract: - session_token: 'body.headers."postman-token"' diff --git a/examples/hrp/ref_testcase_test.json b/examples/hrp/ref_testcase_test.json deleted file mode 100644 index 39bc01d6..00000000 --- a/examples/hrp/ref_testcase_test.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "config": { - "name": "reference testcase test", - "base_url": "https://postman-echo.com", - "variables": { - "os_platform": "ios" - } - }, - "teststeps": [ - { - "name": "run demo_httprunner.json", - "testcase": "demo_httprunner.json", - "variables": { - "os_platform": "$os_platform" - } - } - ] -} diff --git a/examples/hrp/ref_testcase_test.yaml b/examples/hrp/ref_testcase_test.yaml deleted file mode 100644 index f3811e1f..00000000 --- a/examples/hrp/ref_testcase_test.yaml +++ /dev/null @@ -1,11 +0,0 @@ -config: - name: "reference testcase test" - base_url: "https://postman-echo.com" - variables: - os_platform: 'ios' - -teststeps: - - name: run demo_httprunner.yaml - testcase: demo_httprunner.yaml - variables: - os_platform: $os_platform \ No newline at end of file diff --git a/examples/hrp/think_time_test.yaml b/examples/hrp/think_time_test.yaml deleted file mode 100644 index 9f2f5129..00000000 --- a/examples/hrp/think_time_test.yaml +++ /dev/null @@ -1,40 +0,0 @@ -config: - name: "think time test demo" - variables: - app_version: v1 - user_agent: iOS/10.3 - base_url: "https://postman-echo.com" - think_time: - strategy: random_percentage - setting: - min_percentage: 1.0 - max_percentage: 1.5 - limit: 4 - verify: False - -teststeps: - - name: get with params - request: - method: GET - url: /get - headers: - User-Agent: $user_agent,$app_version - validate: - - check: status_code - assert: equals - expect: 200 - msg: check status code - - name: think time 1 - think_time: - time: 3 - - name: post with params - request: - method: POST - url: /post - headers: - User-Agent: $user_agent,$app_version - validate: - - check: status_code - assert: equals - expect: 200 - msg: check status code \ No newline at end of file diff --git a/hrp/convert.go b/hrp/convert.go index 68297e04..3148ab56 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -2,9 +2,11 @@ package hrp import ( "fmt" + "os" "path/filepath" "strings" + "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/hrp/internal/builtin" @@ -92,9 +94,21 @@ func (tc *TCase) ToTestCase() (*TestCase, error) { testCase := &TestCase{ Config: tc.Config, } + + // locate project root dir by plugin path + projectRootDir, err := getProjectRootDirPath(testCase.Config.Path) + if err != nil { + return nil, errors.Wrap(err, "failed to get project root dir") + } + log.Info().Str("dir", projectRootDir).Msg("located project root dir") + for _, step := range tc.TestSteps { if step.APIPath != "" { - path := filepath.Join(filepath.Dir(testCase.Config.Path), step.APIPath) + path := filepath.Join(projectRootDir, step.APIPath) + if !builtin.IsFilePathExists(path) { + return nil, errors.New("referenced api file not found: " + path) + } + refAPI := APIPath(path) step.APIContent = &refAPI apiContent, err := step.APIContent.ToAPI() @@ -106,7 +120,11 @@ func (tc *TCase) ToTestCase() (*TestCase, error) { step: step, }) } else if step.TestCasePath != "" { - path := filepath.Join(filepath.Dir(testCase.Config.Path), step.TestCasePath) + path := filepath.Join(projectRootDir, step.TestCasePath) + if !builtin.IsFilePathExists(path) { + return nil, errors.New("referenced testcase file not found: " + path) + } + refTestCase := TestCasePath(path) step.TestCaseContent = &refTestCase tc, err := step.TestCaseContent.ToTestCase() @@ -140,16 +158,29 @@ func (tc *TCase) ToTestCase() (*TestCase, error) { return testCase, nil } +func getProjectRootDirPath(path string) (rootDir string, err error) { + pluginPath, err := locatePlugin(path) + if err == nil { + rootDir = filepath.Dir(pluginPath) + return + } + + // failed to locate project root dir + // maybe project plugin debugtalk.xx is not exist + // use current dir instead + return os.Getwd() +} + // APIPath implements IAPI interface. type APIPath string -func (path *APIPath) ToString() string { +func (path *APIPath) GetPath() string { return fmt.Sprintf("%v", *path) } func (path *APIPath) ToAPI() (*API, error) { api := &API{} - apiPath := path.ToString() + apiPath := path.GetPath() err := builtin.LoadFile(apiPath, api) if err != nil { return nil, err @@ -161,22 +192,23 @@ func (path *APIPath) ToAPI() (*API, error) { // TestCasePath implements ITestCase interface. type TestCasePath string -func (path *TestCasePath) ToString() string { +func (path *TestCasePath) GetPath() string { return fmt.Sprintf("%v", *path) } func (path *TestCasePath) ToTestCase() (*TestCase, error) { tc := &TCase{} - casePath := path.ToString() + casePath := path.GetPath() err := builtin.LoadFile(casePath, tc) if err != nil { return nil, err } + err = convertCompatTestCase(tc) if err != nil { return nil, err } - tc.Config.Path = path.ToString() + tc.Config.Path = casePath testcase, err := tc.ToTestCase() if err != nil { return nil, err diff --git a/hrp/convert_test.go b/hrp/convert_test.go index 351aedf0..6c2d619b 100644 --- a/hrp/convert_test.go +++ b/hrp/convert_test.go @@ -8,20 +8,22 @@ import ( "github.com/httprunner/httprunner/hrp/internal/builtin" ) -const templatesDir = "internal/scaffold/templates/" +const ( + templatesDir = "internal/scaffold/templates/" + hrpExamplesDir = "../examples/hrp" +) var ( demoTestCaseWithPluginJSONPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.json" demoTestCaseWithPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.yaml" demoTestCaseWithoutPluginJSONPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.json" demoTestCaseWithoutPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.yaml" + demoTestCaseWithRefAPIPath TestCasePath = templatesDir + "testcases/demo_ref_api.json" + demoAPIGETPath APIPath = templatesDir + "/api/get.yml" ) var ( - demoRefAPIYAMLPath TestCasePath = "../examples/hrp/ref_api_test.yaml" - demoRefTestCaseJSONPath TestCasePath = "../examples/hrp/ref_testcase_test.json" - demoThinkTimeJsonPath TestCasePath = "../examples/hrp/think_time_test.json" - demoAPIYAMLPath APIPath = "../examples/hrp/api/put.yml" + demoTestCaseWithThinkTimePath TestCasePath = hrpExamplesDir + "/think_time_test.json" ) var demoTestCaseWithPlugin = &TestCase{ @@ -152,41 +154,21 @@ var demoTestCaseWithoutPlugin = &TestCase{ func TestGenDemoTestCase(t *testing.T) { tCase, _ := demoTestCaseWithPlugin.ToTCase() - err := builtin.Dump2JSON(tCase, demoTestCaseWithPluginJSONPath.ToString()) + err := builtin.Dump2JSON(tCase, demoTestCaseWithPluginJSONPath.GetPath()) if err != nil { t.Fail() } - err = builtin.Dump2YAML(tCase, demoTestCaseWithPluginYAMLPath.ToString()) + err = builtin.Dump2YAML(tCase, demoTestCaseWithPluginYAMLPath.GetPath()) if err != nil { t.Fail() } tCase, _ = demoTestCaseWithoutPlugin.ToTCase() - err = builtin.Dump2JSON(tCase, demoTestCaseWithoutPluginJSONPath.ToString()) + err = builtin.Dump2JSON(tCase, demoTestCaseWithoutPluginJSONPath.GetPath()) if err != nil { t.Fail() } - err = builtin.Dump2YAML(tCase, demoTestCaseWithoutPluginYAMLPath.ToString()) - if err != nil { - t.Fail() - } -} - -func TestJsonDemoWithPlugin(t *testing.T) { - buildHashicorpGoPlugin() - defer removeHashicorpGoPlugin() - - err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase) - if err != nil { - t.Fail() - } -} - -func TestYamlDemoWithPlugin(t *testing.T) { - buildHashicorpGoPlugin() - defer removeHashicorpGoPlugin() - - err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase) + err = builtin.Dump2YAML(tCase, demoTestCaseWithoutPluginYAMLPath.GetPath()) if err != nil { t.Fail() } @@ -195,11 +177,11 @@ func TestYamlDemoWithPlugin(t *testing.T) { func TestLoadCase(t *testing.T) { tcJSON := &TCase{} tcYAML := &TCase{} - err := builtin.LoadFile(demoTestCaseWithPluginJSONPath.ToString(), tcJSON) + err := builtin.LoadFile(demoTestCaseWithPluginJSONPath.GetPath(), tcJSON) if !assert.NoError(t, err) { t.Fail() } - err = builtin.LoadFile(demoTestCaseWithPluginYAMLPath.ToString(), tcYAML) + err = builtin.LoadFile(demoTestCaseWithPluginYAMLPath.GetPath(), tcYAML) if !assert.NoError(t, err) { t.Fail() } @@ -218,7 +200,7 @@ func TestLoadCase(t *testing.T) { } } -func Test_convertCheckExpr(t *testing.T) { +func TestConvertCheckExpr(t *testing.T) { exprs := []struct { before string after string diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 7d9bd4a8..d1701bb2 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -108,16 +108,16 @@ func CreateFile(filePath string, data string) error { return nil } -// isFilePathExists returns true if path exists, whether path is file or dir -func isPathExists(path string) bool { +// IsPathExists returns true if path exists, whether path is file or dir +func IsPathExists(path string) bool { if _, err := os.Stat(path); os.IsNotExist(err) { return false } return true } -// isFilePathExists returns true if path exists and path is file -func isFilePathExists(path string) bool { +// IsFilePathExists returns true if path exists and path is file +func IsFilePathExists(path string) bool { info, err := os.Stat(path) if err != nil { // path not exists @@ -133,10 +133,10 @@ func isFilePathExists(path string) bool { } func EnsureFolderExists(folderPath string) error { - if !isPathExists(folderPath) { + if !IsPathExists(folderPath) { err := CreateFolder(folderPath) return err - } else if isFilePathExists(folderPath) { + } else if IsFilePathExists(folderPath) { return fmt.Errorf("path %v should be directory", folderPath) } return nil diff --git a/examples/hrp/api/get.json b/hrp/internal/scaffold/templates/api/get.json similarity index 100% rename from examples/hrp/api/get.json rename to hrp/internal/scaffold/templates/api/get.json diff --git a/examples/hrp/api/get.yml b/hrp/internal/scaffold/templates/api/get.yml similarity index 100% rename from examples/hrp/api/get.yml rename to hrp/internal/scaffold/templates/api/get.yml diff --git a/examples/hrp/api/post.json b/hrp/internal/scaffold/templates/api/post.json similarity index 100% rename from examples/hrp/api/post.json rename to hrp/internal/scaffold/templates/api/post.json diff --git a/examples/hrp/api/post.yml b/hrp/internal/scaffold/templates/api/post.yml similarity index 100% rename from examples/hrp/api/post.yml rename to hrp/internal/scaffold/templates/api/post.yml diff --git a/examples/hrp/api/put.json b/hrp/internal/scaffold/templates/api/put.json similarity index 100% rename from examples/hrp/api/put.json rename to hrp/internal/scaffold/templates/api/put.json diff --git a/examples/hrp/api/put.yml b/hrp/internal/scaffold/templates/api/put.yml similarity index 100% rename from examples/hrp/api/put.yml rename to hrp/internal/scaffold/templates/api/put.yml diff --git a/examples/hrp/ref_api_test.json b/hrp/internal/scaffold/templates/testcases/demo_ref_api.json similarity index 100% rename from examples/hrp/ref_api_test.json rename to hrp/internal/scaffold/templates/testcases/demo_ref_api.json diff --git a/hrp/models.go b/hrp/models.go index 49398429..4b5068cc 100644 --- a/hrp/models.go +++ b/hrp/models.go @@ -193,6 +193,11 @@ type API struct { Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"` Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"` Export []string `json:"export,omitempty" yaml:"export,omitempty"` + Path string +} + +func (api *API) GetPath() string { + return api.Path } func (api *API) ToAPI() (*API, error) { @@ -210,6 +215,7 @@ type Validator struct { // IAPI represents interface for api, // includes API and APIPath. type IAPI interface { + GetPath() string ToAPI() (*API, error) } @@ -219,8 +225,8 @@ type TStep struct { Name string `json:"name" yaml:"name"` // required Request *Request `json:"request,omitempty" yaml:"request,omitempty"` APIPath string `json:"api,omitempty" yaml:"api,omitempty"` - TestCasePath string `json:"testcase,omitempty" yaml:"testcase,omitempty"` APIContent IAPI `json:"api_content,omitempty" yaml:"api_content,omitempty"` + TestCasePath string `json:"testcase,omitempty" yaml:"testcase,omitempty"` TestCaseContent ITestCase `json:"testcase_content,omitempty" yaml:"testcase_content,omitempty"` Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"` Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"` @@ -287,6 +293,10 @@ type TCase struct { TestSteps []*TStep `json:"teststeps" yaml:"teststeps"` } +func (tc *TCase) Path() string { + return tc.Config.Path +} + // IStep represents interface for all types for teststeps, includes: // StepRequest, StepRequestWithOptionalArgs, StepRequestValidation, StepRequestExtraction, // StepTestCaseWithOptionalArgs, @@ -300,6 +310,7 @@ type IStep interface { // ITestCase represents interface for testcases, // includes TestCase and TestCasePath. type ITestCase interface { + GetPath() string ToTestCase() (*TestCase, error) ToTCase() (*TCase, error) } @@ -311,6 +322,10 @@ type TestCase struct { TestSteps []IStep } +func (tc *TestCase) GetPath() string { + return tc.Config.Path +} + func (tc *TestCase) ToTestCase() (*TestCase, error) { return tc, nil } diff --git a/hrp/parser_test.go b/hrp/parser_test.go index 73a7bbd7..3ad0a2b4 100644 --- a/hrp/parser_test.go +++ b/hrp/parser_test.go @@ -1,6 +1,7 @@ package hrp import ( + "fmt" "sort" "testing" "time" @@ -742,7 +743,7 @@ func TestParseParameters(t *testing.T) { }{ { map[string]interface{}{ - "username-password": "${parameterize(../examples/hrp/account.csv)}", + "username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, 6, }, @@ -782,17 +783,17 @@ func TestParseParametersError(t *testing.T) { }{ { map[string]interface{}{ - "username_password": "${parameterize(../examples/hrp/account.csv)}", + "username_password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, }, { map[string]interface{}{ - "username-password": "${parameterize(../examples/hrp/account.csv)}", + "username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), "user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, }, { map[string]interface{}{ - "username-password": "${param(../examples/hrp/account.csv)}", + "username-password": fmt.Sprintf("${param(%s/account.csv)}", hrpExamplesDir), "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, }, } diff --git a/hrp/runner_test.go b/hrp/runner_test.go index b753ebf9..dcab96c5 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -40,14 +40,14 @@ func removeHashicorpPyPlugin() { os.Remove(templatesDir + "debugtalk.py") } -func TestHttpRunnerWithGoPlugin(t *testing.T) { +func TestRunCaseWithGoPlugin(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() assertRunTestCases(t) } -func TestHttpRunnerWithPythonPlugin(t *testing.T) { +func TestRunCaseWithPythonPlugin(t *testing.T) { buildHashicorpPyPlugin() defer removeHashicorpPyPlugin() @@ -59,19 +59,19 @@ func assertRunTestCases(t *testing.T) { Config: NewConfig("TestCase1"). SetBaseURL("http://httpbin.org"), TestSteps: []IStep{ - NewStep("headers"). + NewStep("testcase1-step1"). GET("/headers"). Validate(). AssertEqual("status_code", 200, "check status code"). AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"), - NewStep("user-agent"). + NewStep("testcase1-step2"). GET("/user-agent"). Validate(). AssertEqual("status_code", 200, "check status code"). AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"), - NewStep("TestCase3").CallRefCase( + NewStep("testcase1-step3").CallRefCase( &TestCase{ - Config: NewConfig("TestCase3").SetBaseURL("http://httpbin.org"), + Config: NewConfig("testcase1-step3-ref-case").SetBaseURL("http://httpbin.org"), TestSteps: []IStep{ NewStep("ip"). GET("/ip"). @@ -81,32 +81,23 @@ func assertRunTestCases(t *testing.T) { }, }, ), - NewStep("TestCase4").CallRefCase(&demoRefAPIYAMLPath), - NewStep("TestCase5").CallRefCase(&demoTestCaseWithPluginJSONPath), + NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithPluginJSONPath), }, } testcase2 := &TestCase{ Config: NewConfig("TestCase2").SetWeight(3), } - testcase3 := &TestCase{ - Config: NewConfig("TestCase1"). - SetBaseURL("https://postman-echo.com"), - TestSteps: []IStep{ - NewStep("TestCase5").CallRefAPI(&demoAPIYAMLPath), - }, - } - testcase4 := &demoRefTestCaseJSONPath r := NewRunner(t) r.SetPluginLogOn() - err := r.Run(testcase1, testcase2, testcase3, testcase4) + err := r.Run(testcase1, testcase2) if err != nil { t.Fatalf("run testcase error: %v", err) } } -func TestInitRendezvous(t *testing.T) { - rendezvousBonudaryTestcase := &TestCase{ +func TestRunCaseWithRendezvous(t *testing.T) { + rendezvousBoundaryTestcase := &TestCase{ Config: NewConfig("run request with functions"). SetBaseURL("https://postman-echo.com"). WithVariables(map[string]interface{}{ @@ -155,7 +146,7 @@ func TestInitRendezvous(t *testing.T) { {number: 100, percent: 1, timeout: 5000}, } - rendezvousList := initRendezvous(rendezvousBonudaryTestcase, 100) + rendezvousList := initRendezvous(rendezvousBoundaryTestcase, 100) for i, r := range rendezvousList { if r.Number != expectedRendezvousParams[i].number { @@ -170,7 +161,7 @@ func TestInitRendezvous(t *testing.T) { } } -func TestThinkTime(t *testing.T) { +func TestRunCaseWithThinkTime(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() @@ -205,7 +196,8 @@ func TestThinkTime(t *testing.T) { { Config: NewConfig("TestCase5"), TestSteps: []IStep{ - NewStep("thinkTime").CallRefCase(&demoThinkTimeJsonPath), // think time: 3s, random pct: {"min_percentage":1, "max_percentage":1.5}, limit: 4s + // think time: 3s, random pct: {"min_percentage":1, "max_percentage":1.5}, limit: 4s + NewStep("thinkTime").CallRefCase(&demoTestCaseWithThinkTimePath), }, }, } @@ -247,3 +239,47 @@ func TestGenHTMLReport(t *testing.T) { t.Error(err) } } + +func TestRunCaseWithPluginJSON(t *testing.T) { + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() + + err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase) + if err != nil { + t.Fail() + } +} + +func TestRunCaseWithPluginYAML(t *testing.T) { + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() + + err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase) + if err != nil { + t.Fail() + } +} + +func TestRunCaseWithRefAPI(t *testing.T) { + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() + + err := NewRunner(nil).Run(&demoTestCaseWithRefAPIPath) + if err != nil { + t.Fail() + } + + testcase := &TestCase{ + Config: NewConfig("TestCase"). + SetBaseURL("https://postman-echo.com"), + TestSteps: []IStep{ + NewStep("run referenced api").CallRefAPI(&demoAPIGETPath), + }, + } + + r := NewRunner(t) + err = r.Run(testcase) + if err != nil { + t.Fail() + } +} diff --git a/hrp/step.go b/hrp/step.go index 829e9aad..f0dd63f9 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -1,6 +1,11 @@ package hrp -import "fmt" +import ( + "fmt" + "os" + + "github.com/rs/zerolog/log" +) // NewConfig returns a new constructed testcase config with specified testcase name. func NewConfig(name string) *TConfig { @@ -163,7 +168,12 @@ func (s *StepRequest) PATCH(url string) *StepRequestWithOptionalArgs { // CallRefCase calls a referenced testcase. func (s *StepRequest) CallRefCase(tc ITestCase) *StepTestCaseWithOptionalArgs { - s.step.TestCaseContent, _ = tc.ToTestCase() + var err error + s.step.TestCaseContent, err = tc.ToTestCase() + if err != nil { + log.Error().Err(err).Msg("failed to load testcase") + os.Exit(1) + } return &StepTestCaseWithOptionalArgs{ step: s.step, } @@ -171,7 +181,12 @@ func (s *StepRequest) CallRefCase(tc ITestCase) *StepTestCaseWithOptionalArgs { // CallRefAPI calls a referenced api. func (s *StepRequest) CallRefAPI(api IAPI) *StepAPIWithOptionalArgs { - s.step.APIContent, _ = api.ToAPI() + var err error + s.step.APIContent, err = api.ToAPI() + if err != nil { + log.Error().Err(err).Msg("failed to load api") + os.Exit(1) + } return &StepAPIWithOptionalArgs{ step: s.step, } diff --git a/hrp/tests/compat_test.go b/hrp/tests/compat_test.go deleted file mode 100644 index 74dc5fe0..00000000 --- a/hrp/tests/compat_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package tests - -import ( - "testing" - - "github.com/httprunner/httprunner/hrp" -) - -// generated by examples/data/har/demo.har using HttpRunner v3.1.6 -var ( - demoHttpRunnerJSONPath hrp.TestCasePath = "../../examples/hrp/demo_httprunner.json" - demoHttpRunnerYAMLPath hrp.TestCasePath = "../../examples/hrp/demo_httprunner.yaml" -) - -func TestCompatTestCase(t *testing.T) { - err := hrp.NewRunner(t).Run(&demoHttpRunnerJSONPath) - if err != nil { - t.Fatalf("run testcase error: %v", err) - } - - err = hrp.NewRunner(t).Run(&demoHttpRunnerYAMLPath) - if err != nil { - t.Fatalf("run testcase error: %v", err) - } -}