mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 08:59:44 +08:00
Merge pull request #1269 from httprunner/convert-pytest
hrp convert pytest scripts - feat: prepare python3 venv in `~/.hrp/venv` before running #1262 - feat: convert YAML/JSON testcases to pytest scripts with `hrp convert` #1261 - refactor: reformat all python code with black
This commit is contained in:
12
.github/workflows/smoketest.yml
vendored
12
.github/workflows/smoketest.yml
vendored
@@ -66,10 +66,14 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: Build hrp binary
|
||||
run: make build
|
||||
- name: Run smoketest - postman echo
|
||||
run: ./output/hrp boom examples/hrp/postman-echo.json --spawn-count 10 --spawn-rate 10 --loop-count 10
|
||||
- name: Run smoketest - data driven with parameterize mechanism
|
||||
- name: Run smoketest - run with parameters
|
||||
run: ./output/hrp run examples/hrp/parameters_test.json
|
||||
- name: Run smoketest - boom with parameters
|
||||
run: ./output/hrp boom examples/hrp/parameters_test.json --spawn-count 10 --spawn-rate 10 --loop-count 10
|
||||
- name: Run smoketest - rendezvous
|
||||
- name: Run smoketest - boom with rendezvous
|
||||
run: |
|
||||
./output/hrp boom examples/hrp/rendezvous_test.json --spawn-count 10 --spawn-rate 10 --loop-count 10
|
||||
- name: Run hrp convert --pytest
|
||||
run: ./output/hrp convert examples/postman_echo/request_methods/
|
||||
- name: Run hrp pytest
|
||||
run: ./output/hrp pytest examples/postman_echo/request_methods/
|
||||
|
||||
@@ -83,7 +83,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
|
||||
License: Apache-2.0
|
||||
Website: https://httprunner.com
|
||||
Github: https://github.com/httprunner/httprunner
|
||||
Copyright 2021 debugtalk
|
||||
Copyright 2017 debugtalk
|
||||
|
||||
Usage:
|
||||
hrp [command]
|
||||
|
||||
@@ -76,7 +76,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
|
||||
License: Apache-2.0
|
||||
Website: https://httprunner.com
|
||||
Github: https://github.com/httprunner/httprunner
|
||||
Copyright 2021 debugtalk
|
||||
Copyright 2017 debugtalk
|
||||
|
||||
Usage:
|
||||
hrp [command]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Release History
|
||||
|
||||
## v4.0.0-alpha
|
||||
## v4.0.0-beta (2022-04-24)
|
||||
|
||||
- refactor: merge [hrp] into httprunner v4, which will include golang and python dual engine
|
||||
- refactor: redesign `IStep` to make step extensible to support implementing new protocols and test types
|
||||
- feat: disable GA events report by setting environment `DISABLE_GA=true`
|
||||
- feat: disable sentry reports by setting environment `DISABLE_SENTRY=true`
|
||||
- feat: prepare python3 venv in `~/.hrp/venv` before running
|
||||
|
||||
**go version**
|
||||
|
||||
@@ -13,6 +14,7 @@
|
||||
- feat: support run testcases in specified folder path, including testcases in sub folders
|
||||
- feat: support HTTP/2 protocol
|
||||
- feat: support WebSocket protocol
|
||||
- feat: convert YAML/JSON testcases to pytest scripts with `hrp convert`
|
||||
- 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
|
||||
@@ -163,6 +165,10 @@
|
||||
- test: add CI test with [github actions][github-actions]
|
||||
- test: integrate [sentry sdk][sentry sdk] for event reporting and analysis
|
||||
|
||||
## 3.1.11 (2022-04-24)
|
||||
|
||||
- fix #1273: ImportError by cannot import name '_unicodefun' from 'click'
|
||||
|
||||
## 3.1.10 (2022-04-18)
|
||||
|
||||
- fix #1249: catch exceptions when requesting with disabling allow_redirects
|
||||
|
||||
@@ -19,7 +19,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
|
||||
License: Apache-2.0
|
||||
Website: https://httprunner.com
|
||||
Github: https://github.com/httprunner/httprunner
|
||||
Copyright 2021 debugtalk
|
||||
Copyright 2017 debugtalk
|
||||
|
||||
### Options
|
||||
|
||||
@@ -30,9 +30,10 @@ Copyright 2021 debugtalk
|
||||
### SEE ALSO
|
||||
|
||||
* [hrp boom](hrp_boom.md) - run load test with boomer
|
||||
* [hrp convert](hrp_convert.md) - convert JSON/YAML testcases to pytest/gotest scripts
|
||||
* [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files
|
||||
* [hrp pytest](hrp_pytest.md) - run API test with pytest
|
||||
* [hrp run](hrp_run.md) - run API test with go engine
|
||||
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-Apr-2022
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
|
||||
@@ -41,4 +41,4 @@ hrp boom [flags]
|
||||
|
||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-Apr-2022
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
|
||||
21
docs/cmd/hrp_convert.md
Normal file
21
docs/cmd/hrp_convert.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## hrp convert
|
||||
|
||||
convert JSON/YAML testcases to pytest/gotest scripts
|
||||
|
||||
```
|
||||
hrp convert $path... [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--gotest convert to gotest scripts (TODO)
|
||||
-h, --help help for convert
|
||||
--pytest convert to pytest scripts (default true)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
@@ -24,4 +24,4 @@ hrp har2case $har_path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-Apr-2022
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
|
||||
@@ -16,4 +16,4 @@ hrp pytest $path ... [flags]
|
||||
|
||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-Apr-2022
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
|
||||
@@ -34,4 +34,4 @@ hrp run $path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-Apr-2022
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
|
||||
@@ -9,6 +9,7 @@ hrp startproject $project_name [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-f, --force force to overwrite existing project
|
||||
--go generate hashicorp go plugin
|
||||
-h, --help help for startproject
|
||||
--ignore-plugin ignore function plugin
|
||||
@@ -19,4 +20,4 @@ hrp startproject $project_name [flags]
|
||||
|
||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-Apr-2022
|
||||
###### Auto generated by spf13/cobra on 24-Apr-2022
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: a-b.c/1.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: a-b.c/2 3.yml
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func TeardownHookExample(args string) string {
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return "v4.0.0-alpha"
|
||||
return "v4.0.0-beta"
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -2,4 +2,4 @@ module plugin
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/httprunner/funplugin v0.4.2 // indirect
|
||||
require github.com/httprunner/funplugin v0.4.3 // indirect
|
||||
|
||||
@@ -58,8 +58,8 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v
|
||||
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/httprunner/funplugin v0.4.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg=
|
||||
github.com/httprunner/funplugin v0.4.3/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=
|
||||
|
||||
@@ -6,7 +6,7 @@ import funppy
|
||||
|
||||
|
||||
def get_httprunner_version():
|
||||
return "v4.0.0-alpha"
|
||||
return "v4.0.0-beta"
|
||||
|
||||
|
||||
def sleep(n_secs):
|
||||
@@ -59,7 +59,7 @@ def teardown_hook_example(name):
|
||||
return f"teardown_hook_example: {name}"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
funppy.register("get_httprunner_version", get_httprunner_version)
|
||||
funppy.register("sum", sum)
|
||||
funppy.register("sum_ints", sum_ints)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: basic.yml
|
||||
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ def sum_two(m, n):
|
||||
|
||||
|
||||
def sum_status_code(status_code, expect_sum):
|
||||
""" sum status code digits
|
||||
e.g. 400 => 4, 201 => 3
|
||||
"""sum status code digits
|
||||
e.g. 400 => 4, 201 => 3
|
||||
"""
|
||||
sum_value = 0
|
||||
for digit in str(status_code):
|
||||
@@ -54,8 +54,7 @@ os.environ["TEST_ENV"] = "PRODUCTION"
|
||||
|
||||
|
||||
def skip_test_in_production_env():
|
||||
""" skip this test in production environment
|
||||
"""
|
||||
"""skip this test in production environment"""
|
||||
return os.environ["TEST_ENV"] == "PRODUCTION"
|
||||
|
||||
|
||||
@@ -97,8 +96,7 @@ def setup_hook_remove_kwargs(request):
|
||||
|
||||
|
||||
def teardown_hook_sleep_N_secs(response, n_secs):
|
||||
""" sleep n seconds after request
|
||||
"""
|
||||
"""sleep n seconds after request"""
|
||||
if response.status_code == 200:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: hooks.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: load_image.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: upload.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: validate.yml
|
||||
|
||||
|
||||
|
||||
@@ -54,8 +54,7 @@ def session_fixture(request):
|
||||
summary["details"].append(testcase_summary_json)
|
||||
|
||||
summary_path = os.path.join(
|
||||
os.getcwd(),
|
||||
"examples/postman_echo/logs/request_methods/hardcode.summary.json"
|
||||
os.getcwd(), "examples/postman_echo/logs/request_methods/hardcode.summary.json"
|
||||
)
|
||||
summary_dir = os.path.dirname(summary_path)
|
||||
os.makedirs(summary_dir, exist_ok=True)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v3.1.7
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: cookie_manipulation/hardcode.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v3.1.7
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: cookie_manipulation/set_delete_cookies.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
config:
|
||||
name: "demo testsuite"
|
||||
variables: ${get_testsuite_config_variables()}
|
||||
|
||||
testcases:
|
||||
-
|
||||
name: request with functions
|
||||
testcase: request_methods/request_with_functions.yml
|
||||
weight: 2
|
||||
variables:
|
||||
foo1: testcase_ref_bar11
|
||||
expect_foo1: testcase_ref_bar11
|
||||
expect_foo2: testsuite_config_bar2
|
||||
-
|
||||
name: request with referenced testcase
|
||||
testcase: request_methods/request_with_testcase_reference.yml
|
||||
weight: 3
|
||||
variables:
|
||||
foo1: testcase_ref_bar12
|
||||
expect_foo1: testcase_ref_bar12
|
||||
foo2: testcase_ref_bar22
|
||||
expect_foo2: testcase_ref_bar22
|
||||
@@ -1 +0,0 @@
|
||||
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
|
||||
@@ -1,86 +0,0 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# FROM: request_methods/request_with_functions.yml
|
||||
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
|
||||
class TestCaseRequestWithFunctions(HttpRunner):
|
||||
|
||||
config = (
|
||||
Config("request with functions")
|
||||
.variables(
|
||||
**{
|
||||
"foo1": "testcase_ref_bar11",
|
||||
"foo2": "testsuite_config_bar2",
|
||||
"expect_foo1": "testcase_ref_bar11",
|
||||
"expect_foo2": "testsuite_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.",
|
||||
)
|
||||
.assert_type_match("body.json", "None")
|
||||
.assert_type_match("body.json", "NoneType")
|
||||
.assert_type_match("body.json", None)
|
||||
),
|
||||
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, "response status code should be 200")
|
||||
.assert_equal("body.form.foo1", "$expect_foo1")
|
||||
.assert_equal("body.form.foo2", "bar23")
|
||||
.assert_equal("body.form.foo3", "bar21")
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestCaseRequestWithFunctions().test_start()
|
||||
@@ -1,65 +0,0 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# FROM: request_methods/request_with_testcase_reference.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 request_methods.request_with_functions_test import (
|
||||
TestCaseRequestWithFunctions as RequestWithFunctions,
|
||||
)
|
||||
|
||||
|
||||
class TestCaseRequestWithTestcaseReference(HttpRunner):
|
||||
|
||||
config = (
|
||||
Config("request with referenced testcase")
|
||||
.variables(
|
||||
**{
|
||||
"foo1": "testcase_ref_bar12",
|
||||
"expect_foo1": "testcase_ref_bar12",
|
||||
"expect_foo2": "testcase_ref_bar22",
|
||||
"foo2": "testcase_ref_bar22",
|
||||
}
|
||||
)
|
||||
.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"}
|
||||
)
|
||||
.setup_hook("${sleep(0.1)}")
|
||||
.call(RequestWithFunctions)
|
||||
.teardown_hook("${sleep(0.2)}")
|
||||
.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__":
|
||||
TestCaseRequestWithTestcaseReference().test_start()
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/hardcode.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/request_with_functions.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/request_with_parameters.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/request_with_testcase_reference.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/request_with_variables.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/validate_with_functions.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: request_methods/validate_with_variables.yml
|
||||
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -8,7 +8,7 @@ require (
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/httprunner/funplugin v0.4.2
|
||||
github.com/httprunner/funplugin v0.4.3
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
|
||||
4
go.sum
4
go.sum
@@ -241,8 +241,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.2 h1:iDeg3GVCKdimgZQ40xq0kxHqhL/DQmRxs3DRjzOpUuo=
|
||||
github.com/httprunner/funplugin v0.4.2/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
|
||||
github.com/httprunner/funplugin v0.4.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg=
|
||||
github.com/httprunner/funplugin v0.4.3/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=
|
||||
|
||||
@@ -46,7 +46,7 @@ func (b *HRPBoomer) Run(testcases ...ITestCase) {
|
||||
var taskSlice []*boomer.Task
|
||||
|
||||
// load all testcases
|
||||
testCases, err := loadTestCases(testcases...)
|
||||
testCases, err := LoadTestCases(testcases...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load testcases")
|
||||
os.Exit(1)
|
||||
|
||||
48
hrp/cmd/convert.go
Normal file
48
hrp/cmd/convert.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/convert"
|
||||
)
|
||||
|
||||
var convertCmd = &cobra.Command{
|
||||
Use: "convert $path...",
|
||||
Short: "convert JSON/YAML testcases to pytest/gotest scripts",
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !pytestFlag && !gotestFlag {
|
||||
return errors.New("please specify convertion type")
|
||||
}
|
||||
|
||||
var err error
|
||||
if gotestFlag {
|
||||
err = convert.Convert2TestScripts("gotest", args...)
|
||||
} else {
|
||||
err = convert.Convert2TestScripts("pytest", args...)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert test scripts failed")
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
pytestFlag bool
|
||||
gotestFlag bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(convertCmd)
|
||||
convertCmd.Flags().BoolVar(&pytestFlag, "pytest", true, "convert to pytest scripts")
|
||||
convertCmd.Flags().BoolVar(&gotestFlag, "gotest", false, "convert to gotest scripts (TODO)")
|
||||
}
|
||||
@@ -31,7 +31,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
|
||||
License: Apache-2.0
|
||||
Website: https://httprunner.com
|
||||
Github: https://github.com/httprunner/httprunner
|
||||
Copyright 2021 debugtalk`,
|
||||
Copyright 2017 debugtalk`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
var noColor = false
|
||||
if runtime.GOOS == "windows" {
|
||||
|
||||
@@ -19,7 +19,7 @@ var scaffoldCmd = &cobra.Command{
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !ignorePlugin && !genPythonPlugin && !genGoPlugin {
|
||||
return errors.New("please select function plugin type")
|
||||
return errors.New("please specify function plugin type")
|
||||
}
|
||||
|
||||
var pluginType scaffold.PluginType
|
||||
@@ -31,7 +31,7 @@ var scaffoldCmd = &cobra.Command{
|
||||
pluginType = scaffold.Py // default
|
||||
}
|
||||
|
||||
err := scaffold.CreateScaffold(args[0], pluginType)
|
||||
err := scaffold.CreateScaffold(args[0], pluginType, force)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create scaffold project failed")
|
||||
os.Exit(1)
|
||||
@@ -45,10 +45,12 @@ var (
|
||||
ignorePlugin bool
|
||||
genPythonPlugin bool
|
||||
genGoPlugin bool
|
||||
force bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(scaffoldCmd)
|
||||
scaffoldCmd.Flags().BoolVarP(&force, "force", "f", false, "force to overwrite existing project")
|
||||
scaffoldCmd.Flags().BoolVar(&genPythonPlugin, "py", true, "generate hashicorp python plugin")
|
||||
scaffoldCmd.Flags().BoolVar(&genGoPlugin, "go", false, "generate hashicorp go plugin")
|
||||
scaffoldCmd.Flags().BoolVar(&ignorePlugin, "ignore-plugin", false, "ignore function plugin")
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
)
|
||||
|
||||
var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected interface{}, msgAndArgs ...interface{}) bool{
|
||||
"eq": assert.EqualValues,
|
||||
"equals": assert.EqualValues,
|
||||
"equal": assert.EqualValues,
|
||||
"eq": EqualValues,
|
||||
"equals": EqualValues,
|
||||
"equal": EqualValues,
|
||||
"lt": assert.Less,
|
||||
"less_than": assert.Less,
|
||||
"le": assert.LessOrEqual,
|
||||
@@ -20,8 +20,8 @@ var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected
|
||||
"greater_than": assert.Greater,
|
||||
"ge": assert.GreaterOrEqual,
|
||||
"greater_or_equals": assert.GreaterOrEqual,
|
||||
"ne": assert.NotEqual,
|
||||
"not_equal": assert.NotEqual,
|
||||
"ne": NotEqual,
|
||||
"not_equal": NotEqual,
|
||||
"contains": assert.Contains,
|
||||
"type_match": assert.IsType,
|
||||
// custom assertions
|
||||
@@ -48,6 +48,14 @@ var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected
|
||||
"regex_match": RegexMatch,
|
||||
}
|
||||
|
||||
func EqualValues(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||
return assert.EqualValues(t, expected, actual, msgAndArgs)
|
||||
}
|
||||
|
||||
func NotEqual(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||
return assert.NotEqual(t, expected, actual, msgAndArgs)
|
||||
}
|
||||
|
||||
// StartsWith check if string starts with substring
|
||||
func StartsWith(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
|
||||
if !assert.IsType(t, "string", actual, fmt.Sprintf("actual is %v", actual)) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/httprunner/funplugin/shared"
|
||||
"github.com/httprunner/httprunner/hrp/internal/json"
|
||||
)
|
||||
|
||||
@@ -76,16 +77,59 @@ func FormatResponse(raw interface{}) interface{} {
|
||||
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))
|
||||
func EnsurePython3Venv(packages ...string) (string, error) {
|
||||
// create python venv
|
||||
home, err := os.UserHomeDir()
|
||||
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 "", errors.Wrap(err, "get user home dir failed")
|
||||
}
|
||||
venvDir := filepath.Join(home, ".hrp", "venv")
|
||||
python3, err := shared.EnsurePython3Venv(venvDir, packages...)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "ensure python venv failed")
|
||||
}
|
||||
|
||||
return python3, nil
|
||||
}
|
||||
|
||||
func ExecCommandInDir(cmd *exec.Cmd, dir string) error {
|
||||
log.Info().Str("cmd", cmd.String()).Str("dir", dir).Msg("exec command")
|
||||
cmd.Dir = dir
|
||||
|
||||
// print output with colors
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("exec command failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExecCommand(cmdName string, args ...string) error {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
log.Info().Str("cmd", cmd.String()).Msg("exec command")
|
||||
|
||||
// print output with colors
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// add cmd dir path to PATH
|
||||
PATH := fmt.Sprintf("%s:%s", filepath.Dir(cmdName), os.Getenv("PATH"))
|
||||
if err := os.Setenv("PATH", PATH); err != nil {
|
||||
log.Error().Err(err).Msg("failed to add cmd dir path to $PATH")
|
||||
return err
|
||||
}
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("exec command failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
118
hrp/internal/convert/main.go
Normal file
118
hrp/internal/convert/main.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp"
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/hrp/internal/sdk"
|
||||
)
|
||||
|
||||
func Convert2TestScripts(destType string, paths ...string) error {
|
||||
// report event
|
||||
sdk.SendEvent(sdk.EventTracking{
|
||||
Category: "ConvertTests",
|
||||
Action: fmt.Sprintf("hrp convert --%s", destType),
|
||||
})
|
||||
|
||||
if destType == "gotest" {
|
||||
return convert2GoTestScripts(paths...)
|
||||
} else {
|
||||
// default to pytest
|
||||
return convert2PyTestScripts(paths...)
|
||||
}
|
||||
}
|
||||
|
||||
func convert2PyTestScripts(paths ...string) error {
|
||||
python3, err := builtin.EnsurePython3Venv("httprunner")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := append([]string{"-m", "httprunner", "make"}, paths...)
|
||||
return builtin.ExecCommand(python3, args...)
|
||||
}
|
||||
|
||||
func convert2GoTestScripts(paths ...string) error {
|
||||
log.Warn().Msg("convert to gotest scripts is not supported yet")
|
||||
os.Exit(1)
|
||||
|
||||
// TODO
|
||||
var testCasePaths []hrp.ITestCase
|
||||
for _, path := range paths {
|
||||
testCasePath := hrp.TestCasePath(path)
|
||||
testCasePaths = append(testCasePaths, &testCasePath)
|
||||
}
|
||||
|
||||
testCases, err := hrp.LoadTestCases(testCasePaths...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load testcases")
|
||||
return err
|
||||
}
|
||||
|
||||
var pytestPaths []string
|
||||
for _, testCase := range testCases {
|
||||
tc := testCase.ToTCase()
|
||||
converter := CaseConverter{
|
||||
TCase: tc,
|
||||
}
|
||||
pytestPath, err := converter.ToPyTest()
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("originPath", tc.Config.Path).
|
||||
Msg("convert to pytest failed")
|
||||
continue
|
||||
}
|
||||
log.Info().
|
||||
Str("pytestPath", pytestPath).
|
||||
Str("originPath", tc.Config.Path).
|
||||
Msg("convert to pytest success")
|
||||
pytestPaths = append(pytestPaths, pytestPath)
|
||||
}
|
||||
|
||||
// format pytest scripts with black
|
||||
python3, err := builtin.EnsurePython3Venv("black")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := append([]string{"-m", "black"}, pytestPaths...)
|
||||
return builtin.ExecCommand(python3, args...)
|
||||
}
|
||||
|
||||
//go:embed testcase.tmpl
|
||||
var testcaseTemplate string
|
||||
|
||||
type CaseConverter struct {
|
||||
*hrp.TCase
|
||||
}
|
||||
|
||||
func (c *CaseConverter) ToPyTest() (string, error) {
|
||||
script := convertConfig(c.TCase.Config)
|
||||
println(script)
|
||||
return script, nil
|
||||
}
|
||||
|
||||
func (c *CaseConverter) ToGoTest() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func convertConfig(config *hrp.TConfig) string {
|
||||
script := fmt.Sprintf("Config('%s')", config.Name)
|
||||
|
||||
if config.Variables != nil {
|
||||
script += fmt.Sprintf(".variables(**{%v})", config.Variables)
|
||||
}
|
||||
if config.BaseURL != "" {
|
||||
script += fmt.Sprintf(".base_url('%s')", config.BaseURL)
|
||||
}
|
||||
if config.Export != nil {
|
||||
script += fmt.Sprintf(".export(*%v)", config.Export)
|
||||
}
|
||||
script += fmt.Sprintf(".verify(%v)", config.Verify)
|
||||
|
||||
return script
|
||||
}
|
||||
38
hrp/internal/convert/testcase.tmpl
Normal file
38
hrp/internal/convert/testcase.tmpl
Normal file
@@ -0,0 +1,38 @@
|
||||
# NOTE: Generated By HttpRunner v{{ version }}
|
||||
# FROM: {{ testcase_path }}
|
||||
|
||||
{% if imports_list and diff_levels > 0 %}
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__){% for _ in range(diff_levels) %}.parent{% endfor %}))
|
||||
{% endif %}
|
||||
|
||||
{% if parameters %}
|
||||
import pytest
|
||||
from httprunner import Parameters
|
||||
{% endif %}
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
{% for import_str in imports_list %}
|
||||
{{ import_str }}
|
||||
{% endfor %}
|
||||
|
||||
class {{ class_name }}(HttpRunner):
|
||||
|
||||
{% if parameters %}
|
||||
@pytest.mark.parametrize("param", Parameters({{parameters}}))
|
||||
def test_start(self, param):
|
||||
super().test_start(param)
|
||||
{% endif %}
|
||||
|
||||
config = {{ config_chain_style }}
|
||||
|
||||
teststeps = [
|
||||
{% for step_chain_style in teststeps_chain_style %}
|
||||
{{ step_chain_style }},
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
{{ class_name }}().test_start()
|
||||
@@ -1,23 +1,21 @@
|
||||
package pytest
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/hrp/internal/sdk"
|
||||
)
|
||||
|
||||
func RunPytest(args []string) error {
|
||||
cmd := exec.Command("pytest", args...)
|
||||
log.Info().Str("cmd", cmd.String()).Msg("run pytest")
|
||||
sdk.SendEvent(sdk.EventTracking{
|
||||
Category: "RunAPITests",
|
||||
Action: "hrp pytest",
|
||||
})
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
python3, err := builtin.EnsurePython3Venv("httprunner")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "pytest running failed")
|
||||
return err
|
||||
}
|
||||
out := strings.TrimSpace(string(output))
|
||||
println(out)
|
||||
|
||||
return nil
|
||||
args = append([]string{"-m", "httprunner", "run"}, args...)
|
||||
return builtin.ExecCommand(python3, args...)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
package scaffold
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenDemoExamples(t *testing.T) {
|
||||
dir := "../../../examples/demo-with-go-plugin"
|
||||
os.RemoveAll(dir)
|
||||
err := CreateScaffold(dir, Go)
|
||||
err := CreateScaffold(dir, Go, true)
|
||||
if err != nil {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
dir = "../../../examples/demo-with-py-plugin"
|
||||
os.RemoveAll(dir)
|
||||
err = CreateScaffold(dir, Py)
|
||||
err = CreateScaffold(dir, Py, true)
|
||||
if err != nil {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
dir = "../../../examples/demo-without-plugin"
|
||||
os.RemoveAll(dir)
|
||||
err = CreateScaffold(dir, Ignore)
|
||||
err = CreateScaffold(dir, Ignore, true)
|
||||
if err != nil {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
@@ -42,25 +42,32 @@ func CopyFile(templateFile, targetFile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateScaffold(projectName string, pluginType PluginType) error {
|
||||
func CreateScaffold(projectName string, pluginType PluginType, force bool) error {
|
||||
// report event
|
||||
sdk.SendEvent(sdk.EventTracking{
|
||||
Category: "Scaffold",
|
||||
Action: "hrp startproject",
|
||||
})
|
||||
|
||||
// check if projectName exists
|
||||
if _, err := os.Stat(projectName); err == nil {
|
||||
log.Warn().Str("projectName", projectName).
|
||||
Msg("project name already exists, please specify a new one.")
|
||||
return fmt.Errorf("project name already exists")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("projectName", projectName).
|
||||
Str("pluginType", string(pluginType)).
|
||||
Bool("force", force).
|
||||
Msg("create new scaffold project")
|
||||
|
||||
// check if projectName exists
|
||||
if _, err := os.Stat(projectName); err == nil {
|
||||
if !force {
|
||||
log.Warn().Str("projectName", projectName).
|
||||
Msg("project name already exists, please specify a new one.")
|
||||
return fmt.Errorf("project name already exists")
|
||||
}
|
||||
|
||||
log.Warn().Str("projectName", projectName).
|
||||
Msg("project name already exists, remove first !!!")
|
||||
os.RemoveAll(projectName)
|
||||
}
|
||||
|
||||
// create project folders
|
||||
if err := builtin.CreateFolder(projectName); err != nil {
|
||||
return err
|
||||
@@ -133,7 +140,7 @@ func CreateScaffold(projectName string, pluginType PluginType) error {
|
||||
func createGoPlugin(projectName string) error {
|
||||
log.Info().Msg("start to create hashicorp go plugin")
|
||||
// check go sdk
|
||||
if err := builtin.ExecCommand(exec.Command("go", "version"), projectName); err != nil {
|
||||
if err := builtin.ExecCommandInDir(exec.Command("go", "version"), projectName); err != nil {
|
||||
return errors.Wrap(err, "go sdk not installed")
|
||||
}
|
||||
|
||||
@@ -149,19 +156,19 @@ func createGoPlugin(projectName string) error {
|
||||
}
|
||||
|
||||
// create go mod
|
||||
if err := builtin.ExecCommand(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil {
|
||||
if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// download plugin dependency
|
||||
// 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 {
|
||||
if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build plugin debugtalk.bin
|
||||
if err := builtin.ExecCommand(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil {
|
||||
if err := builtin.ExecCommandInDir(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -178,15 +185,9 @@ func createPythonPlugin(projectName string) error {
|
||||
return errors.Wrap(err, "copy file failed")
|
||||
}
|
||||
|
||||
// create python venv
|
||||
home, err := os.UserHomeDir()
|
||||
_, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy==%s", shared.Version))
|
||||
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 err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -42,7 +42,7 @@ func TeardownHookExample(args string) string {
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return "v4.0.0-alpha"
|
||||
return "v4.0.0-beta"
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import funppy
|
||||
|
||||
|
||||
def get_httprunner_version():
|
||||
return "v4.0.0-alpha"
|
||||
return "v4.0.0-beta"
|
||||
|
||||
|
||||
def sleep(n_secs):
|
||||
@@ -59,7 +59,7 @@ def teardown_hook_example(name):
|
||||
return f"teardown_hook_example: {name}"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
funppy.register("get_httprunner_version", get_httprunner_version)
|
||||
funppy.register("sum", sum)
|
||||
funppy.register("sum_ints", sum_ints)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: testcases/demo_ref_testcase.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# NOTE: Generated By HttpRunner v4.0.0-beta
|
||||
# FROM: testcases/demo_requests.yml
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package version
|
||||
|
||||
const VERSION = "v4.0.0-alpha"
|
||||
const VERSION = "v4.0.0-beta"
|
||||
|
||||
@@ -151,8 +151,9 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
||||
s := newOutSummary()
|
||||
|
||||
// load all testcases
|
||||
testCases, err := loadTestCases(testcases...)
|
||||
testCases, err := LoadTestCases(testcases...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load testcases")
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@ package hrp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/scaffold"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/hrp/internal/scaffold"
|
||||
)
|
||||
|
||||
func buildHashicorpGoPlugin() {
|
||||
log.Info().Msg("[init] build hashicorp go plugin")
|
||||
cmd := exec.Command("go", "build",
|
||||
err := builtin.ExecCommand("go", "build",
|
||||
"-o", templatesDir+"debugtalk.bin", templatesDir+"plugin/debugtalk.go")
|
||||
if err := cmd.Run(); err != nil {
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("build hashicorp go plugin failed")
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -202,7 +203,7 @@ func TestRunCaseWithRefAPI(t *testing.T) {
|
||||
func TestLoadTestCases(t *testing.T) {
|
||||
// load test cases from folder path
|
||||
tc := TestCasePath("../examples/demo-with-py-plugin/testcases/")
|
||||
testCases, err := loadTestCases(&tc)
|
||||
testCases, err := LoadTestCases(&tc)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
@@ -212,7 +213,7 @@ func TestLoadTestCases(t *testing.T) {
|
||||
|
||||
// load test cases from folder path, including sub folders
|
||||
tc = TestCasePath("../examples/demo-with-py-plugin/")
|
||||
testCases, err = loadTestCases(&tc)
|
||||
testCases, err = LoadTestCases(&tc)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
@@ -222,7 +223,7 @@ func TestLoadTestCases(t *testing.T) {
|
||||
|
||||
// load test cases from single file path
|
||||
tc = demoTestCaseWithPluginJSONPath
|
||||
testCases, err = loadTestCases(&tc)
|
||||
testCases, err = LoadTestCases(&tc)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
@@ -234,7 +235,7 @@ func TestLoadTestCases(t *testing.T) {
|
||||
testcase := &TestCase{
|
||||
Config: NewConfig("TestCase").SetWeight(3),
|
||||
}
|
||||
testCases, err = loadTestCases(testcase)
|
||||
testCases, err = LoadTestCases(testcase)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ func convertCheckExpr(checkExpr string) string {
|
||||
return strings.Join(checkItems, ".")
|
||||
}
|
||||
|
||||
func loadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
|
||||
func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
|
||||
testCases := make([]*TestCase, 0)
|
||||
|
||||
for _, iTestCase := range iTestCases {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = "4.0.0-alpha"
|
||||
__version__ = "4.0.0-beta"
|
||||
__description__ = "One-stop solution for HTTP(S) testing."
|
||||
|
||||
from httprunner.config import Config
|
||||
|
||||
@@ -11,16 +11,14 @@ from httprunner.exceptions import ParamsError
|
||||
|
||||
|
||||
def gen_random_string(str_len):
|
||||
""" generate random string with specified length
|
||||
"""
|
||||
"""generate random string with specified length"""
|
||||
return "".join(
|
||||
random.choice(string.ascii_letters + string.digits) for _ in range(str_len)
|
||||
)
|
||||
|
||||
|
||||
def get_timestamp(str_len=13):
|
||||
""" get timestamp string, length can only between 0 and 16
|
||||
"""
|
||||
"""get timestamp string, length can only between 0 and 16"""
|
||||
if isinstance(str_len, int) and 0 < str_len < 17:
|
||||
return str(time.time()).replace(".", "")[:str_len]
|
||||
|
||||
@@ -28,12 +26,10 @@ def get_timestamp(str_len=13):
|
||||
|
||||
|
||||
def get_current_date(fmt="%Y-%m-%d"):
|
||||
""" get current date, default format is %Y-%m-%d
|
||||
"""
|
||||
"""get current date, default format is %Y-%m-%d"""
|
||||
return datetime.datetime.now().strftime(fmt)
|
||||
|
||||
|
||||
def sleep(n_secs):
|
||||
""" sleep n seconds
|
||||
"""
|
||||
"""sleep n seconds"""
|
||||
time.sleep(n_secs)
|
||||
|
||||
@@ -9,7 +9,7 @@ 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.utils import ga_client, init_sentry_sdk
|
||||
from httprunner.utils import ga_client, init_logger, init_sentry_sdk
|
||||
|
||||
init_sentry_sdk()
|
||||
|
||||
@@ -55,8 +55,9 @@ def main_run(extra_args) -> enum.IntEnum:
|
||||
|
||||
|
||||
def main():
|
||||
""" API test: parse command line options and run commands.
|
||||
"""
|
||||
"""API test: parse command line options and run commands."""
|
||||
init_logger()
|
||||
|
||||
parser = argparse.ArgumentParser(description=__description__)
|
||||
parser.add_argument(
|
||||
"-V", "--version", dest="version", action="store_true", help="show version"
|
||||
@@ -109,8 +110,8 @@ def main():
|
||||
|
||||
|
||||
def main_hrun_alias():
|
||||
""" command alias
|
||||
hrun = httprunner run
|
||||
"""command alias
|
||||
hrun = httprunner run
|
||||
"""
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1] in ["-V", "--version"]:
|
||||
@@ -129,8 +130,8 @@ def main_hrun_alias():
|
||||
|
||||
|
||||
def main_make_alias():
|
||||
""" command alias
|
||||
hmake = httprunner make
|
||||
"""command alias
|
||||
hmake = httprunner make
|
||||
"""
|
||||
sys.argv.insert(1, "make")
|
||||
main()
|
||||
|
||||
@@ -27,8 +27,7 @@ class ApiResponse(Response):
|
||||
|
||||
|
||||
def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
||||
""" get request and response info from Response() object.
|
||||
"""
|
||||
"""get request and response info from Response() object."""
|
||||
|
||||
def log_print(req_or_resp, r_type):
|
||||
msg = f"\n================== {r_type} details ==================\n"
|
||||
|
||||
@@ -27,7 +27,8 @@ class TestHttpSession(unittest.TestCase):
|
||||
self.session.request(
|
||||
"get",
|
||||
"http://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||
allow_redirects=True)
|
||||
allow_redirects=True,
|
||||
)
|
||||
address = self.session.data.address
|
||||
self.assertNotEqual(address.server_ip, "N/A")
|
||||
self.assertEqual(address.server_port, 443)
|
||||
@@ -38,7 +39,8 @@ class TestHttpSession(unittest.TestCase):
|
||||
self.session.request(
|
||||
"get",
|
||||
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||
allow_redirects=True)
|
||||
allow_redirects=True,
|
||||
)
|
||||
address = self.session.data.address
|
||||
self.assertNotEqual(address.server_ip, "N/A")
|
||||
self.assertEqual(address.server_port, 443)
|
||||
@@ -49,7 +51,8 @@ class TestHttpSession(unittest.TestCase):
|
||||
self.session.request(
|
||||
"get",
|
||||
"http://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||
allow_redirects=False)
|
||||
allow_redirects=False,
|
||||
)
|
||||
address = self.session.data.address
|
||||
self.assertEqual(address.server_ip, "N/A")
|
||||
self.assertEqual(address.server_port, 0)
|
||||
@@ -60,7 +63,8 @@ class TestHttpSession(unittest.TestCase):
|
||||
self.session.request(
|
||||
"get",
|
||||
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
|
||||
allow_redirects=False)
|
||||
allow_redirects=False,
|
||||
)
|
||||
address = self.session.data.address
|
||||
self.assertEqual(address.server_ip, "N/A")
|
||||
self.assertEqual(address.server_port, 0)
|
||||
|
||||
@@ -14,25 +14,12 @@ from httprunner.utils import sort_dict_by_custom_order
|
||||
|
||||
|
||||
def convert_variables(
|
||||
raw_variables: Union[Dict, List, Text], test_path: Text
|
||||
raw_variables: Union[Dict, Text], test_path: Text
|
||||
) -> Dict[Text, Any]:
|
||||
|
||||
if isinstance(raw_variables, Dict):
|
||||
return raw_variables
|
||||
|
||||
if isinstance(raw_variables, List):
|
||||
# [{"var1": 1}, {"var2": 2}]
|
||||
variables: Dict[Text, Any] = {}
|
||||
for var_item in raw_variables:
|
||||
if not isinstance(var_item, Dict) or len(var_item) != 1:
|
||||
raise exceptions.TestCaseFormatError(
|
||||
f"Invalid variables format: {raw_variables}"
|
||||
)
|
||||
|
||||
variables.update(var_item)
|
||||
|
||||
return variables
|
||||
|
||||
elif isinstance(raw_variables, Text):
|
||||
# get variables by function, e.g. ${get_variables()}
|
||||
project_meta = load_project_meta(test_path)
|
||||
@@ -79,7 +66,7 @@ def _convert_jmespath(raw: Text) -> Text:
|
||||
|
||||
|
||||
def _convert_extractors(extractors: Union[List, Dict]) -> Dict:
|
||||
""" convert extract list(v2) to dict(v3)
|
||||
"""convert extract list(v2) to dict(v3)
|
||||
|
||||
Args:
|
||||
extractors: [{"varA": "content.varA"}, {"varB": "json.varB"}]
|
||||
@@ -251,8 +238,7 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
|
||||
|
||||
|
||||
def ensure_cli_args(args: List) -> List:
|
||||
""" ensure compatibility with deprecated cli args in v2
|
||||
"""
|
||||
"""ensure compatibility with deprecated cli args in v2"""
|
||||
# remove deprecated --failfast
|
||||
if "--failfast" in args:
|
||||
logger.warning("remove deprecated argument: --failfast")
|
||||
@@ -301,13 +287,13 @@ from httprunner.utils import get_platform, ExtendJSONEncoder
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def session_fixture(request):
|
||||
"""setup and teardown each task"""
|
||||
logger.info(f"start running testcases ...")
|
||||
logger.info("start running testcases ...")
|
||||
|
||||
start_at = time.time()
|
||||
|
||||
yield
|
||||
|
||||
logger.info(f"task finished, generate task summary for --save-tests")
|
||||
logger.info("task finished, generate task summary for --save-tests")
|
||||
|
||||
summary = {
|
||||
"success": True,
|
||||
@@ -386,8 +372,7 @@ def session_fixture(request):
|
||||
|
||||
|
||||
def ensure_path_sep(path: Text) -> Text:
|
||||
""" ensure compatibility with different path separators of Linux and Windows
|
||||
"""
|
||||
"""ensure compatibility with different path separators of Linux and Windows"""
|
||||
if "/" in path:
|
||||
path = os.sep.join(path.split("/"))
|
||||
|
||||
|
||||
@@ -9,11 +9,6 @@ class TestCompat(unittest.TestCase):
|
||||
loader.project_meta = None
|
||||
|
||||
def test_convert_variables(self):
|
||||
raw_variables = [{"var1": 1}, {"var2": "val2"}]
|
||||
self.assertEqual(
|
||||
compat.convert_variables(raw_variables, "examples/data/a-b.c/1.yml"),
|
||||
{"var1": 1, "var2": "val2"},
|
||||
)
|
||||
raw_variables = {"var1": 1, "var2": "val2"}
|
||||
self.assertEqual(
|
||||
compat.convert_variables(raw_variables, "examples/data/a-b.c/1.yml"),
|
||||
@@ -38,7 +33,7 @@ class TestCompat(unittest.TestCase):
|
||||
compat._convert_jmespath("headers.Content-Type"), 'headers."Content-Type"'
|
||||
)
|
||||
self.assertEqual(
|
||||
compat._convert_jmespath('headers.User-Agent'), 'headers."User-Agent"'
|
||||
compat._convert_jmespath("headers.User-Agent"), 'headers."User-Agent"'
|
||||
)
|
||||
self.assertEqual(
|
||||
compat._convert_jmespath('headers."Content-Type"'), 'headers."Content-Type"'
|
||||
|
||||
@@ -5,7 +5,6 @@ from httprunner.models import TConfig, TConfigThrift
|
||||
|
||||
|
||||
class ConfigThrift(object):
|
||||
|
||||
def __init__(self, config: TConfig) -> None:
|
||||
self.__config = config
|
||||
self.__config.thrift = TConfigThrift()
|
||||
@@ -31,13 +30,9 @@ class ConfigThrift(object):
|
||||
|
||||
|
||||
class Config(object):
|
||||
|
||||
def __init__(self, name: Text) -> None:
|
||||
caller_frame = inspect.stack()[1]
|
||||
self.__config = TConfig(
|
||||
name=name,
|
||||
path=caller_frame.filename
|
||||
)
|
||||
self.__config = TConfig(name=name, path=caller_frame.filename)
|
||||
|
||||
@property
|
||||
def name(self) -> Text:
|
||||
|
||||
@@ -85,5 +85,4 @@ class TestcaseNotFound(NotFoundError):
|
||||
|
||||
|
||||
class SummaryEmpty(MyBaseError):
|
||||
""" test result summary data is empty
|
||||
"""
|
||||
"""test result summary data is empty"""
|
||||
|
||||
@@ -76,7 +76,7 @@ def ensure_upload_ready():
|
||||
|
||||
|
||||
def prepare_upload_step(step: TStep, functions: FunctionsMapping):
|
||||
""" preprocess for upload test
|
||||
"""preprocess for upload test
|
||||
replace `upload` info with MultipartEncoder
|
||||
|
||||
Args:
|
||||
@@ -102,9 +102,7 @@ def prepare_upload_step(step: TStep, functions: FunctionsMapping):
|
||||
return
|
||||
|
||||
# parse upload info
|
||||
step.request.upload = parse_data(
|
||||
step.request.upload, step.variables, functions
|
||||
)
|
||||
step.request.upload = parse_data(step.request.upload, step.variables, functions)
|
||||
|
||||
ensure_upload_ready()
|
||||
params_list = []
|
||||
@@ -124,7 +122,7 @@ def prepare_upload_step(step: TStep, functions: FunctionsMapping):
|
||||
|
||||
|
||||
def multipart_encoder(**kwargs):
|
||||
""" initialize MultipartEncoder with uploading fields.
|
||||
"""initialize MultipartEncoder with uploading fields.
|
||||
|
||||
Returns:
|
||||
MultipartEncoder: initialized MultipartEncoder object
|
||||
@@ -169,7 +167,7 @@ def multipart_encoder(**kwargs):
|
||||
|
||||
|
||||
def multipart_content_type(m_encoder) -> Text:
|
||||
""" prepare Content-Type for request headers
|
||||
"""prepare Content-Type for request headers
|
||||
|
||||
Args:
|
||||
m_encoder: MultipartEncoder object
|
||||
|
||||
@@ -11,14 +11,13 @@ from loguru import logger
|
||||
from pydantic import ValidationError
|
||||
|
||||
from httprunner import builtin, exceptions, utils
|
||||
from httprunner.models import ProjectMeta, TestCase, TestSuite
|
||||
from httprunner.models import ProjectMeta, TestCase
|
||||
|
||||
project_meta: Union[ProjectMeta, None] = None
|
||||
|
||||
|
||||
def _load_yaml_file(yaml_file: Text) -> Dict:
|
||||
""" load yaml file and check file content format
|
||||
"""
|
||||
"""load yaml file and check file content format"""
|
||||
with open(yaml_file, mode="rb") as stream:
|
||||
try:
|
||||
yaml_content = yaml.load(stream, Loader=yaml.FullLoader)
|
||||
@@ -31,8 +30,7 @@ def _load_yaml_file(yaml_file: Text) -> Dict:
|
||||
|
||||
|
||||
def _load_json_file(json_file: Text) -> Dict:
|
||||
""" load json file and check file content format
|
||||
"""
|
||||
"""load json file and check file content format"""
|
||||
with open(json_file, mode="rb") as data_file:
|
||||
try:
|
||||
json_content = json.load(data_file)
|
||||
@@ -81,20 +79,8 @@ def load_testcase_file(testcase_file: Text) -> TestCase:
|
||||
return testcase_obj
|
||||
|
||||
|
||||
def load_testsuite(testsuite: Dict) -> TestSuite:
|
||||
path = testsuite["config"]["path"]
|
||||
try:
|
||||
# validate with pydantic TestCase model
|
||||
testsuite_obj = TestSuite.parse_obj(testsuite)
|
||||
except ValidationError as ex:
|
||||
err_msg = f"TestSuite ValidationError:\nfile: {path}\nerror: {ex}"
|
||||
raise exceptions.TestSuiteFormatError(err_msg)
|
||||
|
||||
return testsuite_obj
|
||||
|
||||
|
||||
def load_dot_env_file(dot_env_path: Text) -> Dict:
|
||||
""" load .env file.
|
||||
"""load .env file.
|
||||
|
||||
Args:
|
||||
dot_env_path (str): .env file path
|
||||
@@ -140,7 +126,7 @@ def load_dot_env_file(dot_env_path: Text) -> Dict:
|
||||
|
||||
|
||||
def load_csv_file(csv_file: Text) -> List[Dict]:
|
||||
""" load csv file and check file content format
|
||||
"""load csv file and check file content format
|
||||
|
||||
Args:
|
||||
csv_file (str): csv file path, csv file content is like below:
|
||||
@@ -186,7 +172,7 @@ def load_csv_file(csv_file: Text) -> List[Dict]:
|
||||
|
||||
|
||||
def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
|
||||
""" load folder path, return all files endswith .yml/.yaml/.json/_test.py in list.
|
||||
"""load folder path, return all files endswith .yml/.yaml/.json/_test.py in list.
|
||||
|
||||
Args:
|
||||
folder_path (str): specified folder path to load
|
||||
@@ -227,7 +213,7 @@ def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
|
||||
|
||||
|
||||
def load_module_functions(module) -> Dict[Text, Callable]:
|
||||
""" load python module functions.
|
||||
"""load python module functions.
|
||||
|
||||
Args:
|
||||
module: python module
|
||||
@@ -251,13 +237,12 @@ def load_module_functions(module) -> Dict[Text, Callable]:
|
||||
|
||||
|
||||
def load_builtin_functions() -> Dict[Text, Callable]:
|
||||
""" load builtin module functions
|
||||
"""
|
||||
"""load builtin module functions"""
|
||||
return load_module_functions(builtin)
|
||||
|
||||
|
||||
def locate_file(start_path: Text, file_name: Text) -> Text:
|
||||
""" locate filename and return absolute file path.
|
||||
"""locate filename and return absolute file path.
|
||||
searching will be recursive upward until system root dir.
|
||||
|
||||
Args:
|
||||
@@ -295,7 +280,7 @@ def locate_file(start_path: Text, file_name: Text) -> Text:
|
||||
|
||||
|
||||
def locate_debugtalk_py(start_path: Text) -> Text:
|
||||
""" locate debugtalk.py file
|
||||
"""locate debugtalk.py file
|
||||
|
||||
Args:
|
||||
start_path (str): start locating path,
|
||||
@@ -315,7 +300,7 @@ def locate_debugtalk_py(start_path: Text) -> Text:
|
||||
|
||||
|
||||
def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]:
|
||||
""" locate debugtalk.py path as project root directory
|
||||
"""locate debugtalk.py path as project root directory
|
||||
|
||||
Args:
|
||||
test_path: specified testfile path
|
||||
@@ -352,7 +337,7 @@ def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]:
|
||||
|
||||
|
||||
def load_debugtalk_functions() -> Dict[Text, Callable]:
|
||||
""" load project debugtalk.py module functions
|
||||
"""load project debugtalk.py module functions
|
||||
debugtalk.py should be located in project root directory.
|
||||
|
||||
Returns:
|
||||
@@ -376,7 +361,7 @@ def load_debugtalk_functions() -> Dict[Text, Callable]:
|
||||
|
||||
|
||||
def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
|
||||
""" load testcases, .env, debugtalk.py functions.
|
||||
"""load testcases, .env, debugtalk.py functions.
|
||||
testcases folder is relative to project_root_directory
|
||||
by default, project_meta will be loaded only once, unless set reload to true.
|
||||
|
||||
@@ -428,7 +413,7 @@ def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
|
||||
|
||||
|
||||
def convert_relative_project_root_dir(abs_path: Text) -> Text:
|
||||
""" convert absolute path to relative path, based on project_meta.RootDir
|
||||
"""convert absolute path to relative path, based on project_meta.RootDir
|
||||
|
||||
Args:
|
||||
abs_path: absolute path
|
||||
@@ -444,4 +429,4 @@ def convert_relative_project_root_dir(abs_path: Text) -> Text:
|
||||
f"project_meta.RootDir: {_project_meta.RootDir}"
|
||||
)
|
||||
|
||||
return abs_path[len(_project_meta.RootDir) + 1:]
|
||||
return abs_path[len(_project_meta.RootDir) + 1 :]
|
||||
|
||||
@@ -97,7 +97,11 @@ class TestLoader(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_load_env_path_not_exist(self):
|
||||
dot_env_path = os.path.join(os.getcwd(), "tests", "data",)
|
||||
dot_env_path = os.path.join(
|
||||
os.getcwd(),
|
||||
"tests",
|
||||
"data",
|
||||
)
|
||||
env_variables_mapping = loader.load_dot_env_file(dot_env_path)
|
||||
self.assertEqual(env_variables_mapping, {})
|
||||
|
||||
|
||||
@@ -6,17 +6,23 @@ from typing import Dict, List, Set, Text, Tuple
|
||||
|
||||
import jinja2
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner import __version__, exceptions
|
||||
from httprunner.compat import (convert_variables, ensure_path_sep,
|
||||
ensure_testcase_v3, ensure_testcase_v3_api)
|
||||
from httprunner.loader import (convert_relative_project_root_dir,
|
||||
load_folder_files, load_project_meta,
|
||||
load_test_file, load_testcase, load_testsuite)
|
||||
from httprunner.compat import (
|
||||
convert_variables,
|
||||
ensure_path_sep,
|
||||
ensure_testcase_v3,
|
||||
ensure_testcase_v3_api,
|
||||
)
|
||||
from httprunner.loader import (
|
||||
convert_relative_project_root_dir,
|
||||
load_folder_files,
|
||||
load_project_meta,
|
||||
load_test_file,
|
||||
load_testcase,
|
||||
)
|
||||
from httprunner.response import uniform_validator
|
||||
from httprunner.utils import (ga_client, is_support_multiprocessing,
|
||||
merge_variables)
|
||||
from httprunner.utils import ga_client, is_support_multiprocessing
|
||||
|
||||
""" cache converted pytest files, avoid duplicate making
|
||||
"""
|
||||
@@ -72,10 +78,10 @@ if __name__ == "__main__":
|
||||
def __ensure_absolute(path: Text) -> Text:
|
||||
if path.startswith("./"):
|
||||
# Linux/Darwin, hrun ./test.yml
|
||||
path = path[len("./"):]
|
||||
path = path[2:]
|
||||
elif path.startswith(".\\"):
|
||||
# Windows, hrun .\\test.yml
|
||||
path = path[len(".\\"):]
|
||||
path = path[3:]
|
||||
|
||||
path = ensure_path_sep(path)
|
||||
project_meta = load_project_meta(path)
|
||||
@@ -93,7 +99,7 @@ def __ensure_absolute(path: Text) -> Text:
|
||||
|
||||
|
||||
def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
|
||||
""" ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
|
||||
"""ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
|
||||
|
||||
Args:
|
||||
file_abs_path: absolute file path
|
||||
@@ -134,8 +140,7 @@ def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
|
||||
|
||||
|
||||
def __ensure_testcase_module(path: Text):
|
||||
""" ensure pytest files are in python module, generate __init__.py on demand
|
||||
"""
|
||||
"""ensure pytest files are in python module, generate __init__.py on demand"""
|
||||
init_file = os.path.join(os.path.dirname(path), "__init__.py")
|
||||
if os.path.isfile(init_file):
|
||||
return
|
||||
@@ -169,7 +174,6 @@ def format_pytest_with_black(*python_paths: Text):
|
||||
)
|
||||
[subprocess.run(["black", path]) for path in python_paths]
|
||||
except subprocess.CalledProcessError as ex:
|
||||
capture_exception(ex)
|
||||
logger.error(ex)
|
||||
sys.exit(1)
|
||||
except OSError:
|
||||
@@ -433,61 +437,8 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
|
||||
return testcase_python_abs_path
|
||||
|
||||
|
||||
def make_testsuite(testsuite: Dict):
|
||||
"""convert valid testsuite dict to pytest folder with testcases"""
|
||||
# validate testsuite format
|
||||
load_testsuite(testsuite)
|
||||
|
||||
testsuite_config = testsuite["config"]
|
||||
testsuite_path = testsuite_config["path"]
|
||||
testsuite_variables = convert_variables(
|
||||
testsuite_config.get("variables", {}), testsuite_path
|
||||
)
|
||||
|
||||
logger.info(f"start to make testsuite: {testsuite_path}")
|
||||
|
||||
# create directory with testsuite file name, put its testcases under this directory
|
||||
testsuite_path = ensure_file_abs_path_valid(testsuite_path)
|
||||
testsuite_dir, file_suffix = os.path.splitext(testsuite_path)
|
||||
# demo_testsuite.yml => demo_testsuite_yml
|
||||
testsuite_dir = f"{testsuite_dir}_{file_suffix.lstrip('.')}"
|
||||
|
||||
for testcase in testsuite["testcases"]:
|
||||
# get referenced testcase content
|
||||
testcase_file = testcase["testcase"]
|
||||
testcase_path = __ensure_absolute(testcase_file)
|
||||
testcase_dict = load_test_file(testcase_path)
|
||||
testcase_dict.setdefault("config", {})
|
||||
testcase_dict["config"]["path"] = testcase_path
|
||||
|
||||
# override testcase name
|
||||
testcase_dict["config"]["name"] = testcase["name"]
|
||||
# override base_url
|
||||
base_url = testsuite_config.get("base_url") or testcase.get("base_url")
|
||||
if base_url:
|
||||
testcase_dict["config"]["base_url"] = base_url
|
||||
# override verify
|
||||
if "verify" in testsuite_config:
|
||||
testcase_dict["config"]["verify"] = testsuite_config["verify"]
|
||||
# override variables
|
||||
# testsuite testcase variables > testsuite config variables
|
||||
testcase_variables = convert_variables(
|
||||
testcase.get("variables", {}), testcase_path
|
||||
)
|
||||
testcase_variables = merge_variables(testcase_variables, testsuite_variables)
|
||||
# testsuite testcase variables > testcase config variables
|
||||
testcase_dict["config"]["variables"] = convert_variables(
|
||||
testcase_dict["config"].get("variables", {}), testcase_path
|
||||
)
|
||||
testcase_dict["config"]["variables"].update(testcase_variables)
|
||||
|
||||
# make testcase
|
||||
testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir)
|
||||
pytest_files_run_set.add(testcase_pytest_path)
|
||||
|
||||
|
||||
def __make(tests_path: Text):
|
||||
""" make testcase(s) with testcase/testsuite/folder absolute path
|
||||
"""make testcase(s) with testcase/folder absolute path
|
||||
generated pytest file path will be cached in pytest_files_made_cache_mapping
|
||||
|
||||
Args:
|
||||
@@ -528,13 +479,12 @@ def __make(tests_path: Text):
|
||||
|
||||
if "config" not in test_content:
|
||||
logger.warning(
|
||||
f"Invalid testcase/testsuite file: {test_file}\n"
|
||||
f"reason: missing config part."
|
||||
f"Invalid testcase file: {test_file}\nreason: missing config part."
|
||||
)
|
||||
continue
|
||||
elif not isinstance(test_content["config"], Dict):
|
||||
logger.warning(
|
||||
f"Invalid testcase/testsuite file: {test_file}\n"
|
||||
f"Invalid testcase file: {test_file}\n"
|
||||
f"reason: config should be dict type, got {test_content['config']}"
|
||||
)
|
||||
continue
|
||||
@@ -542,33 +492,19 @@ def __make(tests_path: Text):
|
||||
# ensure path absolute
|
||||
test_content.setdefault("config", {})["path"] = test_file
|
||||
|
||||
# testcase
|
||||
if "teststeps" in test_content:
|
||||
try:
|
||||
testcase_pytest_path = make_testcase(test_content)
|
||||
pytest_files_run_set.add(testcase_pytest_path)
|
||||
except exceptions.TestCaseFormatError as ex:
|
||||
logger.warning(
|
||||
f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
|
||||
)
|
||||
continue
|
||||
|
||||
# testsuite
|
||||
elif "testcases" in test_content:
|
||||
try:
|
||||
make_testsuite(test_content)
|
||||
except exceptions.TestSuiteFormatError as ex:
|
||||
logger.warning(
|
||||
f"Invalid testsuite file: {test_file}\n{type(ex).__name__}: {ex}"
|
||||
)
|
||||
continue
|
||||
|
||||
# invalid format
|
||||
else:
|
||||
if "teststeps" not in test_content:
|
||||
logger.warning(f"Invalid testcase file: {test_file}")
|
||||
|
||||
# testcase
|
||||
try:
|
||||
testcase_pytest_path = make_testcase(test_content)
|
||||
pytest_files_run_set.add(testcase_pytest_path)
|
||||
except exceptions.TestCaseFormatError as ex:
|
||||
logger.warning(
|
||||
f"Invalid test file: {test_file}\n"
|
||||
f"reason: file content is neither testcase nor testsuite"
|
||||
f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
def main_make(tests_paths: List[Text]) -> List[Text]:
|
||||
@@ -596,10 +532,10 @@ def main_make(tests_paths: List[Text]) -> List[Text]:
|
||||
|
||||
|
||||
def init_make_parser(subparsers):
|
||||
""" make testcases: parse command line options and run commands.
|
||||
"""
|
||||
"""make testcases: parse command line options and run commands."""
|
||||
parser = subparsers.add_parser(
|
||||
"make", help="Convert YAML/JSON testcases to pytest cases.",
|
||||
"make",
|
||||
help="Convert YAML/JSON testcases to pytest cases.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path"
|
||||
|
||||
@@ -73,7 +73,8 @@ from request_methods.request_with_functions_test import (
|
||||
content,
|
||||
)
|
||||
self.assertIn(
|
||||
".call(RequestWithFunctions)", content,
|
||||
".call(RequestWithFunctions)",
|
||||
content,
|
||||
)
|
||||
|
||||
def test_make_testcase_folder(self):
|
||||
@@ -94,9 +95,7 @@ from request_methods.request_with_functions_test import (
|
||||
|
||||
def test_ensure_file_path_valid(self):
|
||||
self.assertEqual(
|
||||
ensure_file_abs_path_valid(
|
||||
os.path.join(self.data_dir, "a-b.c", "2 3.yml")
|
||||
),
|
||||
ensure_file_abs_path_valid(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
|
||||
os.path.join(self.data_dir, "a_b_c", "T2_3.yml"),
|
||||
)
|
||||
loader.project_meta = None
|
||||
@@ -113,67 +112,31 @@ from request_methods.request_with_functions_test import (
|
||||
)
|
||||
loader.project_meta = None
|
||||
self.assertEqual(
|
||||
ensure_file_abs_path_valid(os.getcwd()), os.getcwd(),
|
||||
ensure_file_abs_path_valid(os.getcwd()),
|
||||
os.getcwd(),
|
||||
)
|
||||
loader.project_meta = None
|
||||
self.assertEqual(
|
||||
ensure_file_abs_path_valid(
|
||||
os.path.join(self.data_dir, ".csv")
|
||||
),
|
||||
ensure_file_abs_path_valid(os.path.join(self.data_dir, ".csv")),
|
||||
os.path.join(self.data_dir, ".csv"),
|
||||
)
|
||||
|
||||
def test_convert_testcase_path(self):
|
||||
self.assertEqual(
|
||||
convert_testcase_path(
|
||||
os.path.join(self.data_dir, "a-b.c", "2 3.yml")
|
||||
),
|
||||
convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
|
||||
(
|
||||
os.path.join(self.data_dir, "a_b_c", "T2_3_test.py"),
|
||||
"T23",
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
convert_testcase_path(
|
||||
os.path.join(self.data_dir, "a-b.c", "中文case.yml")
|
||||
),
|
||||
convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "中文case.yml")),
|
||||
(
|
||||
os.path.join(self.data_dir, "a_b_c", "中文case_test.py"),
|
||||
"中文Case",
|
||||
),
|
||||
)
|
||||
|
||||
def test_make_testsuite(self):
|
||||
path = ["examples/postman_echo/request_methods/demo_testsuite.yml"]
|
||||
testcase_python_list = main_make(path)
|
||||
self.assertEqual(len(testcase_python_list), 2)
|
||||
self.assertIn(
|
||||
os.path.join(
|
||||
os.getcwd(),
|
||||
os.path.join(
|
||||
"examples",
|
||||
"postman_echo",
|
||||
"request_methods",
|
||||
"demo_testsuite_yml",
|
||||
"request_with_functions_test.py",
|
||||
),
|
||||
),
|
||||
testcase_python_list,
|
||||
)
|
||||
self.assertIn(
|
||||
os.path.join(
|
||||
os.getcwd(),
|
||||
os.path.join(
|
||||
"examples",
|
||||
"postman_echo",
|
||||
"request_methods",
|
||||
"demo_testsuite_yml",
|
||||
"request_with_testcase_reference_test.py",
|
||||
),
|
||||
),
|
||||
testcase_python_list,
|
||||
)
|
||||
|
||||
def test_make_config_chain_style(self):
|
||||
config = {
|
||||
"name": "request methods testcase: validate with functions",
|
||||
@@ -190,7 +153,11 @@ from request_methods.request_with_functions_test import (
|
||||
def test_make_teststep_chain_style(self):
|
||||
step = {
|
||||
"name": "get with params",
|
||||
"variables": {"foo1": "bar1", "foo2": 123, "sum_v": "${sum_two(1, 2)}",},
|
||||
"variables": {
|
||||
"foo1": "bar1",
|
||||
"foo2": 123,
|
||||
"sum_v": "${sum_two(1, 2)}",
|
||||
},
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
|
||||
@@ -97,7 +97,9 @@ class ProjectMeta(BaseModel):
|
||||
dot_env_path: Text = "" # .env file path
|
||||
functions: FunctionsMapping = {} # functions defined in debugtalk.py
|
||||
env: Env = {}
|
||||
RootDir: Text = os.getcwd() # project root directory (ensure absolute), the path debugtalk.py located
|
||||
RootDir: Text = (
|
||||
os.getcwd()
|
||||
) # project root directory (ensure absolute), the path debugtalk.py located
|
||||
|
||||
|
||||
class TestsMapping(BaseModel):
|
||||
@@ -166,21 +168,20 @@ class SessionData(BaseModel):
|
||||
class StepResult(BaseModel):
|
||||
"""teststep data, each step maybe corresponding to one request or one testcase"""
|
||||
|
||||
name: Text = "" # teststep name
|
||||
step_type: Text = "" # teststep type, request or testcase
|
||||
name: Text = "" # teststep name
|
||||
step_type: Text = "" # teststep type, request or testcase
|
||||
success: bool = False
|
||||
data: Union[SessionData, List['StepResult']] = None
|
||||
elapsed: float = 0.0 # teststep elapsed time
|
||||
content_size: float = 0 # response content size
|
||||
data: Union[SessionData, List["StepResult"]] = None
|
||||
elapsed: float = 0.0 # teststep elapsed time
|
||||
content_size: float = 0 # response content size
|
||||
export_vars: VariablesMapping = {}
|
||||
attachment: Text = "" # teststep attachment
|
||||
attachment: Text = "" # teststep attachment
|
||||
|
||||
|
||||
StepResult.update_forward_refs()
|
||||
|
||||
|
||||
class IStep(object):
|
||||
|
||||
def name(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -211,18 +212,6 @@ class PlatformInfo(BaseModel):
|
||||
platform: Text
|
||||
|
||||
|
||||
class TestCaseRef(BaseModel):
|
||||
name: Text
|
||||
base_url: Text = ""
|
||||
testcase: Text
|
||||
variables: VariablesMapping = {}
|
||||
|
||||
|
||||
class TestSuite(BaseModel):
|
||||
config: TConfig
|
||||
testcases: List[TestCaseRef]
|
||||
|
||||
|
||||
class Stat(BaseModel):
|
||||
total: int = 0
|
||||
success: int = 0
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import Any, Callable, Dict, List, Set, Text
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner import exceptions, loader, utils
|
||||
from httprunner.models import FunctionsMapping, VariablesMapping
|
||||
@@ -21,7 +20,7 @@ function_regex_compile = re.compile(r"\$\{([a-zA-Z_]\w*)\(([\$\w\.\-/\s=,]*)\)\}
|
||||
|
||||
|
||||
def parse_string_value(str_value: Text) -> Any:
|
||||
""" parse string to number if possible
|
||||
"""parse string to number if possible
|
||||
e.g. "123" => 123
|
||||
"12.2" => 12.3
|
||||
"abc" => "abc"
|
||||
@@ -37,7 +36,7 @@ def parse_string_value(str_value: Text) -> Any:
|
||||
|
||||
|
||||
def build_url(base_url, step_url):
|
||||
""" prepend url with base_url unless it's already an absolute URL """
|
||||
"""prepend url with base_url unless it's already an absolute URL"""
|
||||
o_step_url = urlparse(step_url)
|
||||
if o_step_url.netloc != "":
|
||||
# step url is absolute url
|
||||
@@ -50,14 +49,16 @@ def build_url(base_url, step_url):
|
||||
raise exceptions.ParamsError("base url missed!")
|
||||
|
||||
path = o_base_url.path.rstrip("/") + "/" + o_step_url.path.lstrip("/")
|
||||
o_step_url = o_step_url._replace(scheme=o_base_url.scheme) \
|
||||
._replace(netloc=o_base_url.netloc) \
|
||||
o_step_url = (
|
||||
o_step_url._replace(scheme=o_base_url.scheme)
|
||||
._replace(netloc=o_base_url.netloc)
|
||||
._replace(path=path)
|
||||
)
|
||||
return o_step_url.geturl()
|
||||
|
||||
|
||||
def regex_findall_variables(raw_string: Text) -> List[Text]:
|
||||
""" extract all variable names from content, which is in format $variable
|
||||
"""extract all variable names from content, which is in format $variable
|
||||
|
||||
Args:
|
||||
raw_string (str): string content
|
||||
@@ -116,7 +117,7 @@ def regex_findall_variables(raw_string: Text) -> List[Text]:
|
||||
|
||||
|
||||
def regex_findall_functions(content: Text) -> List[Text]:
|
||||
""" extract all functions from string content, which are in format ${fun()}
|
||||
"""extract all functions from string content, which are in format ${fun()}
|
||||
|
||||
Args:
|
||||
content (str): string content
|
||||
@@ -144,13 +145,12 @@ def regex_findall_functions(content: Text) -> List[Text]:
|
||||
try:
|
||||
return function_regex_compile.findall(content)
|
||||
except TypeError as ex:
|
||||
capture_exception(ex)
|
||||
logger.error(f"regex findall functions error: {ex}")
|
||||
return []
|
||||
|
||||
|
||||
def extract_variables(content: Any) -> Set:
|
||||
""" extract all variables in content recursively.
|
||||
"""
|
||||
"""extract all variables in content recursively."""
|
||||
if isinstance(content, (list, set, tuple)):
|
||||
variables = set()
|
||||
for item in content:
|
||||
@@ -170,7 +170,7 @@ def extract_variables(content: Any) -> Set:
|
||||
|
||||
|
||||
def parse_function_params(params: Text) -> Dict:
|
||||
""" parse function params to args and kwargs.
|
||||
"""parse function params to args and kwargs.
|
||||
|
||||
Args:
|
||||
params (str): function param in string
|
||||
@@ -221,7 +221,7 @@ def parse_function_params(params: Text) -> Dict:
|
||||
def get_mapping_variable(
|
||||
variable_name: Text, variables_mapping: VariablesMapping
|
||||
) -> Any:
|
||||
""" get variable from variables_mapping.
|
||||
"""get variable from variables_mapping.
|
||||
|
||||
Args:
|
||||
variable_name (str): variable name
|
||||
@@ -246,7 +246,7 @@ def get_mapping_variable(
|
||||
def get_mapping_function(
|
||||
function_name: Text, functions_mapping: FunctionsMapping
|
||||
) -> Callable:
|
||||
""" get function from functions_mapping,
|
||||
"""get function from functions_mapping,
|
||||
if not found, then try to check if builtin function.
|
||||
|
||||
Args:
|
||||
@@ -296,7 +296,7 @@ def parse_string(
|
||||
variables_mapping: VariablesMapping,
|
||||
functions_mapping: FunctionsMapping,
|
||||
) -> Any:
|
||||
""" parse string content with variables and functions mapping.
|
||||
"""parse string content with variables and functions mapping.
|
||||
|
||||
Args:
|
||||
raw_string: raw string content to be parsed.
|
||||
@@ -403,8 +403,8 @@ def parse_data(
|
||||
variables_mapping: VariablesMapping = None,
|
||||
functions_mapping: FunctionsMapping = None,
|
||||
) -> Any:
|
||||
""" parse raw data with evaluated variables mapping.
|
||||
Notice: variables_mapping should not contain any variable or function.
|
||||
"""parse raw data with evaluated variables mapping.
|
||||
Notice: variables_mapping should not contain any variable or function.
|
||||
"""
|
||||
if isinstance(raw_data, str):
|
||||
# content in string format may contains variables and functions
|
||||
@@ -476,8 +476,10 @@ def parse_variables_mapping(
|
||||
return parsed_variables
|
||||
|
||||
|
||||
def parse_parameters(parameters: Dict,) -> List[Dict]:
|
||||
""" parse parameters and generate cartesian product.
|
||||
def parse_parameters(
|
||||
parameters: Dict,
|
||||
) -> List[Dict]:
|
||||
"""parse parameters and generate cartesian product.
|
||||
|
||||
Args:
|
||||
parameters (Dict) parameters: parameter name and value mapping
|
||||
@@ -584,17 +586,20 @@ def parse_parameters(parameters: Dict,) -> List[Dict]:
|
||||
|
||||
|
||||
class Parser(object):
|
||||
|
||||
def __init__(self, functions_mapping: FunctionsMapping = None) -> None:
|
||||
self.functions_mapping = functions_mapping
|
||||
|
||||
def parse_string(self, raw_string: Text, variables_mapping: VariablesMapping) -> Any:
|
||||
def parse_string(
|
||||
self, raw_string: Text, variables_mapping: VariablesMapping
|
||||
) -> Any:
|
||||
return parse_string(raw_string, variables_mapping, self.functions_mapping)
|
||||
|
||||
def parse_variables(self, variables_mapping: VariablesMapping) -> VariablesMapping:
|
||||
return parse_variables_mapping(variables_mapping, self.functions_mapping)
|
||||
|
||||
def parse_data(self, raw_data: Any, variables_mapping: VariablesMapping = None) -> Any:
|
||||
def parse_data(
|
||||
self, raw_data: Any, variables_mapping: VariablesMapping = None
|
||||
) -> Any:
|
||||
return parse_data(raw_data, variables_mapping, self.functions_mapping)
|
||||
|
||||
def get_mapping_function(self, func_name: Text) -> Callable:
|
||||
|
||||
@@ -8,7 +8,6 @@ from httprunner.loader import load_project_meta
|
||||
|
||||
|
||||
class TestParserBasic(unittest.TestCase):
|
||||
|
||||
def test_build_url(self):
|
||||
url = parser.build_url("https://postman-echo.com", "/get")
|
||||
self.assertEqual(url, "https://postman-echo.com/get")
|
||||
|
||||
@@ -12,8 +12,7 @@ from httprunner.parser import Parser, parse_string_value
|
||||
|
||||
|
||||
def get_uniform_comparator(comparator: Text):
|
||||
""" convert comparator alias to uniform name
|
||||
"""
|
||||
"""convert comparator alias to uniform name"""
|
||||
if comparator in ["eq", "equals", "equal"]:
|
||||
return "equal"
|
||||
elif comparator in ["lt", "less_than"]:
|
||||
@@ -52,7 +51,7 @@ def get_uniform_comparator(comparator: Text):
|
||||
|
||||
|
||||
def uniform_validator(validator):
|
||||
""" unify validator
|
||||
"""unify validator
|
||||
|
||||
Args:
|
||||
validator (dict): validator maybe in two formats:
|
||||
@@ -116,7 +115,7 @@ def uniform_validator(validator):
|
||||
|
||||
class ResponseObject(object):
|
||||
def __init__(self, resp_obj: requests.Response, parser: Parser):
|
||||
""" initialize with a requests.Response object
|
||||
"""initialize with a requests.Response object
|
||||
|
||||
Args:
|
||||
resp_obj (instance): requests.Response instance
|
||||
@@ -168,20 +167,19 @@ class ResponseObject(object):
|
||||
|
||||
return check_value
|
||||
|
||||
def extract(self,
|
||||
extractors: Dict[Text, Text],
|
||||
variables_mapping: VariablesMapping = None,
|
||||
) -> Dict[Text, Any]:
|
||||
def extract(
|
||||
self,
|
||||
extractors: Dict[Text, Text],
|
||||
variables_mapping: VariablesMapping = None,
|
||||
) -> Dict[Text, Any]:
|
||||
if not extractors:
|
||||
return {}
|
||||
|
||||
extract_mapping = {}
|
||||
for key, field in extractors.items():
|
||||
if '$' in field:
|
||||
if "$" in field:
|
||||
# field contains variable or function
|
||||
field = self.parser.parse_data(
|
||||
field, variables_mapping
|
||||
)
|
||||
field = self.parser.parse_data(field, variables_mapping)
|
||||
field_value = self._search_jmespath(field)
|
||||
extract_mapping[key] = field_value
|
||||
|
||||
@@ -214,9 +212,7 @@ class ResponseObject(object):
|
||||
check_item = u_validator["check"]
|
||||
if "$" in check_item:
|
||||
# check_item is variable or function
|
||||
check_item = self.parser.parse_data(
|
||||
check_item, variables_mapping
|
||||
)
|
||||
check_item = self.parser.parse_data(check_item, variables_mapping)
|
||||
check_item = parse_string_value(check_item)
|
||||
|
||||
if check_item and isinstance(check_item, Text):
|
||||
|
||||
@@ -19,16 +19,13 @@ class TestResponse(unittest.TestCase):
|
||||
]
|
||||
},
|
||||
)
|
||||
parser = Parser(functions_mapping={
|
||||
'get_name': lambda: 'name',
|
||||
"get_num": lambda x: x
|
||||
})
|
||||
parser = Parser(
|
||||
functions_mapping={"get_name": lambda: "name", "get_num": lambda x: x}
|
||||
)
|
||||
self.resp_obj = ResponseObject(resp, parser)
|
||||
|
||||
def test_extract(self):
|
||||
variables_mapping = {
|
||||
'body': 'body'
|
||||
}
|
||||
variables_mapping = {"body": "body"}
|
||||
extract_mapping = self.resp_obj.extract(
|
||||
{
|
||||
"var_1": "body.json.locations[0]",
|
||||
@@ -64,6 +61,9 @@ class TestResponse(unittest.TestCase):
|
||||
def test_validate_functions(self):
|
||||
variables_mapping = {"index": 1}
|
||||
self.resp_obj.validate(
|
||||
[{"eq": ["${get_num(0)}", 0]}, {"eq": ["${get_num($index)}", 1]},],
|
||||
[
|
||||
{"eq": ["${get_num(0)}", 0]},
|
||||
{"eq": ["${get_num($index)}", 1]},
|
||||
],
|
||||
variables_mapping=variables_mapping,
|
||||
)
|
||||
|
||||
@@ -12,14 +12,22 @@ except ModuleNotFoundError:
|
||||
USE_ALLURE = False
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from httprunner.client import HttpSession
|
||||
from httprunner.config import Config
|
||||
from httprunner.exceptions import ParamsError, ValidationFailure
|
||||
from httprunner.loader import load_project_meta
|
||||
from httprunner.models import (ProjectMeta, StepResult, TConfig, TestCaseInOut,
|
||||
TestCaseSummary, TestCaseTime, VariablesMapping)
|
||||
from httprunner.models import (
|
||||
ProjectMeta,
|
||||
StepResult,
|
||||
TConfig,
|
||||
TestCaseInOut,
|
||||
TestCaseSummary,
|
||||
TestCaseTime,
|
||||
VariablesMapping,
|
||||
)
|
||||
from httprunner.parser import Parser
|
||||
from httprunner.utils import merge_variables
|
||||
from httprunner.utils import LOGGER_FORMAT, init_logger, merge_variables
|
||||
|
||||
|
||||
class SessionRunner(object):
|
||||
@@ -43,6 +51,7 @@ class SessionRunner(object):
|
||||
__log_path: Text = ""
|
||||
|
||||
def __init(self):
|
||||
init_logger()
|
||||
self.__config = self.config.struct()
|
||||
self.__session_variables = {}
|
||||
self.__start_at = 0
|
||||
@@ -53,9 +62,7 @@ class SessionRunner(object):
|
||||
)
|
||||
self.case_id = self.case_id or str(uuid.uuid4())
|
||||
self.root_dir = self.root_dir or self.__project_meta.RootDir
|
||||
self.__log_path = os.path.join(
|
||||
self.root_dir, "logs", f"{self.case_id}.run.log"
|
||||
)
|
||||
self.__log_path = os.path.join(self.root_dir, "logs", f"{self.case_id}.run.log")
|
||||
|
||||
self.__step_results.clear()
|
||||
self.session = self.session or HttpSession()
|
||||
@@ -85,9 +92,7 @@ class SessionRunner(object):
|
||||
self.__config.variables.update(self.__session_variables)
|
||||
if param:
|
||||
self.__config.variables.update(param)
|
||||
self.__config.variables = self.parser.parse_variables(
|
||||
self.__config.variables
|
||||
)
|
||||
self.__config.variables = self.parser.parse_variables(self.__config.variables)
|
||||
|
||||
# parse config name
|
||||
self.__config.name = self.parser.parse_data(
|
||||
@@ -174,10 +179,12 @@ class SessionRunner(object):
|
||||
raise
|
||||
else:
|
||||
logger.warning(
|
||||
f"run step {step.name()} validation failed,wait {step.retry_interval} sec and try again")
|
||||
f"run step {step.name()} validation failed,wait {step.retry_interval} sec and try again"
|
||||
)
|
||||
time.sleep(step.retry_interval)
|
||||
logger.info(
|
||||
f"run step retry ({i+1}/{step.retry_times} time): {step.name()} >>>>>>")
|
||||
f"run step retry ({i+1}/{step.retry_times} time): {step.name()} >>>>>>"
|
||||
)
|
||||
|
||||
# save extracted variables to session variables
|
||||
self.__session_variables.update(step_result.export_vars)
|
||||
@@ -188,6 +195,7 @@ class SessionRunner(object):
|
||||
|
||||
def test_start(self, param: Dict = None) -> "SessionRunner":
|
||||
"""main entrance, discovered by pytest"""
|
||||
print("\n")
|
||||
self.__init()
|
||||
self.__parse_config(param)
|
||||
|
||||
@@ -200,14 +208,13 @@ class SessionRunner(object):
|
||||
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.case_id}"
|
||||
)
|
||||
|
||||
log_handler = logger.add(self.__log_path, level="DEBUG")
|
||||
logger.add(self.__log_path, format=LOGGER_FORMAT, level="DEBUG")
|
||||
self.__start_at = time.time()
|
||||
try:
|
||||
# run step in sequential order
|
||||
for step in self.teststeps:
|
||||
self.__run_step(step)
|
||||
finally:
|
||||
logger.remove(log_handler)
|
||||
logger.info(f"generate testcase log: {self.__log_path}")
|
||||
|
||||
self.__duration = time.time() - self.__start_at
|
||||
|
||||
@@ -2,12 +2,15 @@ from typing import Union
|
||||
|
||||
from httprunner.models import StepResult, TRequest, TStep, TestCase
|
||||
from httprunner.runner import HttpRunner
|
||||
from httprunner.step_request import RequestWithOptionalArgs, StepRequestExtraction, StepRequestValidation
|
||||
from httprunner.step_request import (
|
||||
RequestWithOptionalArgs,
|
||||
StepRequestExtraction,
|
||||
StepRequestValidation,
|
||||
)
|
||||
from httprunner.step_testcase import StepRefCase
|
||||
|
||||
|
||||
class Step(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
step: Union[
|
||||
|
||||
@@ -6,15 +6,24 @@ from loguru import logger
|
||||
from httprunner import utils
|
||||
from httprunner.exceptions import ValidationFailure
|
||||
from httprunner.ext.uploader import prepare_upload_step
|
||||
from httprunner.models import (Hooks, IStep, MethodEnum, StepResult, TRequest,
|
||||
TStep, VariablesMapping)
|
||||
from httprunner.models import (
|
||||
Hooks,
|
||||
IStep,
|
||||
MethodEnum,
|
||||
StepResult,
|
||||
TRequest,
|
||||
TStep,
|
||||
VariablesMapping,
|
||||
)
|
||||
from httprunner.parser import build_url
|
||||
from httprunner.response import ResponseObject
|
||||
from httprunner.runner import HttpRunner
|
||||
|
||||
|
||||
def call_hooks(runner: HttpRunner, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text):
|
||||
""" call hook actions.
|
||||
def call_hooks(
|
||||
runner: HttpRunner, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text
|
||||
):
|
||||
"""call hook actions.
|
||||
|
||||
Args:
|
||||
hooks (list): each hook in hooks list maybe in two format.
|
||||
@@ -46,9 +55,7 @@ def call_hooks(runner: HttpRunner, hooks: Hooks, step_variables: VariablesMappin
|
||||
elif isinstance(hook, Dict) and len(hook) == 1:
|
||||
# format 2: {"var": "${func()}"}
|
||||
var_name, hook_content = list(hook.items())[0]
|
||||
hook_content_eval = runner.parser.parse_data(
|
||||
hook_content, step_variables
|
||||
)
|
||||
hook_content_eval = runner.parser.parse_data(hook_content, step_variables)
|
||||
logger.debug(
|
||||
f"call hook function: {hook_content}, got value: {hook_content_eval}"
|
||||
)
|
||||
@@ -73,9 +80,7 @@ def run_step_request(runner: HttpRunner, step: TStep) -> StepResult:
|
||||
prepare_upload_step(step, functions)
|
||||
request_dict = step.request.dict()
|
||||
request_dict.pop("upload", None)
|
||||
parsed_request_dict = runner.parser.parse_data(
|
||||
request_dict, step.variables
|
||||
)
|
||||
parsed_request_dict = runner.parser.parse_data(request_dict, step.variables)
|
||||
parsed_request_dict["headers"].setdefault(
|
||||
"HRUN-Request-ID",
|
||||
f"HRUN-{runner.case_id}-{str(int(time.time() * 1000))[-6:]}",
|
||||
@@ -136,9 +141,7 @@ def run_step_request(runner: HttpRunner, step: TStep) -> StepResult:
|
||||
# validate
|
||||
validators = step.validators
|
||||
try:
|
||||
resp_obj.validate(
|
||||
validators, variables_mapping
|
||||
)
|
||||
resp_obj.validate(validators, variables_mapping)
|
||||
step_result.success = True
|
||||
except ValidationFailure:
|
||||
log_req_resp_details()
|
||||
@@ -162,9 +165,7 @@ class StepRequestValidation(IStep):
|
||||
def assert_equal(
|
||||
self, jmes_path: Text, expected_value: Any, message: Text = ""
|
||||
) -> "StepRequestValidation":
|
||||
self.__step.validators.append(
|
||||
{"equal": [jmes_path, expected_value, message]}
|
||||
)
|
||||
self.__step.validators.append({"equal": [jmes_path, expected_value, message]})
|
||||
return self
|
||||
|
||||
def assert_not_equal(
|
||||
@@ -418,7 +419,6 @@ class RequestWithOptionalArgs(IStep):
|
||||
|
||||
|
||||
class RunRequest(object):
|
||||
|
||||
def __init__(self, name: Text):
|
||||
self.__step = TStep(name=name)
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import unittest
|
||||
|
||||
from examples.postman_echo.request_methods.request_with_functions_test import TestCaseRequestWithFunctions
|
||||
from examples.postman_echo.request_methods.request_with_functions_test import (
|
||||
TestCaseRequestWithFunctions,
|
||||
)
|
||||
|
||||
|
||||
class TestRunRequest(unittest.TestCase):
|
||||
|
||||
def test_run_request(self):
|
||||
runner = TestCaseRequestWithFunctions().test_start()
|
||||
summary = runner.get_summary()
|
||||
|
||||
@@ -22,11 +22,9 @@ def run_step_testcase(runner: HttpRunner, step: TStep) -> StepResult:
|
||||
|
||||
# step.testcase is a referenced testcase, e.g. RequestWithFunctions
|
||||
ref_case_runner = step.testcase()
|
||||
ref_case_runner.with_session(runner.session) \
|
||||
.with_case_id(runner.case_id) \
|
||||
.with_variables(step_variables) \
|
||||
.with_export(step_export) \
|
||||
.test_start()
|
||||
ref_case_runner.with_session(runner.session).with_case_id(
|
||||
runner.case_id
|
||||
).with_variables(step_variables).with_export(step_export).test_start()
|
||||
|
||||
# teardown hooks
|
||||
if step.teardown_hooks:
|
||||
|
||||
@@ -2,19 +2,22 @@ import unittest
|
||||
|
||||
from httprunner.runner import HttpRunner
|
||||
from httprunner.step_testcase import RunTestCase
|
||||
from examples.postman_echo.request_methods.request_with_functions_test import TestCaseRequestWithFunctions
|
||||
from examples.postman_echo.request_methods.request_with_functions_test import (
|
||||
TestCaseRequestWithFunctions,
|
||||
)
|
||||
|
||||
|
||||
class TestRunTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.runner = HttpRunner()
|
||||
|
||||
def test_run_testcase_by_path(self):
|
||||
|
||||
step_result = RunTestCase("run referenced testcase").call(
|
||||
TestCaseRequestWithFunctions
|
||||
).run(self.runner)
|
||||
step_result = (
|
||||
RunTestCase("run referenced testcase")
|
||||
.call(TestCaseRequestWithFunctions)
|
||||
.run(self.runner)
|
||||
)
|
||||
self.assertTrue(step_result.success)
|
||||
self.assertEqual(step_result.name, "run referenced testcase")
|
||||
self.assertEqual(len(step_result.data), 3)
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import os
|
||||
import os.path
|
||||
import platform
|
||||
import sys
|
||||
import uuid
|
||||
from multiprocessing import Queue
|
||||
from typing import Any, Dict, List, Text
|
||||
@@ -31,18 +32,20 @@ def init_sentry_sdk():
|
||||
|
||||
class GAClient(object):
|
||||
|
||||
version = '1' # GA API Version
|
||||
report_url = 'https://www.google-analytics.com/collect'
|
||||
report_debug_url = 'https://www.google-analytics.com/debug/collect' # used for debug
|
||||
version = "1" # GA API Version
|
||||
report_url = "https://www.google-analytics.com/collect"
|
||||
report_debug_url = (
|
||||
"https://www.google-analytics.com/debug/collect" # used for debug
|
||||
)
|
||||
|
||||
def __init__(self, tracking_id: Text):
|
||||
self.http_client = requests.Session()
|
||||
self.label = f"v{__version__}"
|
||||
self.common_params = {
|
||||
'v': self.version,
|
||||
'tid': tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
|
||||
'cid': uuid.getnode(), # Anonymous Client ID
|
||||
'ua': f'HttpRunner/{__version__}',
|
||||
"v": self.version,
|
||||
"tid": tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
|
||||
"cid": uuid.getnode(), # Anonymous Client ID
|
||||
"ua": f"HttpRunner/{__version__}",
|
||||
}
|
||||
# do not send GA events in CI environment
|
||||
self.__is_ci = os.getenv("DISABLE_GA") == "true"
|
||||
@@ -52,16 +55,16 @@ class GAClient(object):
|
||||
return
|
||||
|
||||
data = {
|
||||
't': 'event', # Event hit type = event
|
||||
'ec': category, # Required. Event Category.
|
||||
'ea': action, # Required. Event Action.
|
||||
'el': self.label, # Optional. Event label, used as version.
|
||||
'ev': value, # Optional. Event value, must be non-negative integer
|
||||
"t": "event", # Event hit type = event
|
||||
"ec": category, # Required. Event Category.
|
||||
"ea": action, # Required. Event Action.
|
||||
"el": self.label, # Optional. Event label, used as version.
|
||||
"ev": value, # Optional. Event value, must be non-negative integer
|
||||
}
|
||||
data.update(self.common_params)
|
||||
try:
|
||||
self.http_client.post(self.report_url, data=data, timeout=5)
|
||||
except Exception: # ProxyError, SSLError, ConnectionError
|
||||
except Exception: # ProxyError, SSLError, ConnectionError
|
||||
pass
|
||||
|
||||
def track_user_timing(self, category: Text, variable: Text, duration: int):
|
||||
@@ -69,16 +72,16 @@ class GAClient(object):
|
||||
return
|
||||
|
||||
data = {
|
||||
't': 'timing', # Event hit type = timing
|
||||
'utc': category, # Required. user timing category. e.g. jsonLoader
|
||||
'utv': variable, # Required. timing variable. e.g. load
|
||||
'utt': duration, # Required. time took duration.
|
||||
'utl': self.label, # Optional. user timing label, used as version.
|
||||
"t": "timing", # Event hit type = timing
|
||||
"utc": category, # Required. user timing category. e.g. jsonLoader
|
||||
"utv": variable, # Required. timing variable. e.g. load
|
||||
"utt": duration, # Required. time took duration.
|
||||
"utl": self.label, # Optional. user timing label, used as version.
|
||||
}
|
||||
data.update(self.common_params)
|
||||
try:
|
||||
self.http_client.post(self.report_url, data=data, timeout=5)
|
||||
except Exception: # ProxyError, SSLError, ConnectionError
|
||||
except Exception: # ProxyError, SSLError, ConnectionError
|
||||
pass
|
||||
|
||||
|
||||
@@ -86,23 +89,21 @@ ga_client = GAClient("UA-114587036-1")
|
||||
|
||||
|
||||
def set_os_environ(variables_mapping):
|
||||
""" set variables mapping to os.environ
|
||||
"""
|
||||
"""set variables mapping to os.environ"""
|
||||
for variable in variables_mapping:
|
||||
os.environ[variable] = variables_mapping[variable]
|
||||
logger.debug(f"Set OS environment variable: {variable}")
|
||||
|
||||
|
||||
def unset_os_environ(variables_mapping):
|
||||
""" unset variables mapping to os.environ
|
||||
"""
|
||||
"""unset variables mapping to os.environ"""
|
||||
for variable in variables_mapping:
|
||||
os.environ.pop(variable)
|
||||
logger.debug(f"Unset OS environment variable: {variable}")
|
||||
|
||||
|
||||
def get_os_environ(variable_name):
|
||||
""" get value of environment variable.
|
||||
"""get value of environment variable.
|
||||
|
||||
Args:
|
||||
variable_name(str): variable name
|
||||
@@ -121,7 +122,7 @@ def get_os_environ(variable_name):
|
||||
|
||||
|
||||
def lower_dict_keys(origin_dict):
|
||||
""" convert keys in dict to lower case
|
||||
"""convert keys in dict to lower case
|
||||
|
||||
Args:
|
||||
origin_dict (dict): mapping data structure
|
||||
@@ -156,7 +157,7 @@ def lower_dict_keys(origin_dict):
|
||||
|
||||
|
||||
def print_info(info_mapping):
|
||||
""" print info in mapping.
|
||||
"""print info in mapping.
|
||||
|
||||
Args:
|
||||
info_mapping (dict): input(variables) or output mapping.
|
||||
@@ -201,8 +202,7 @@ def print_info(info_mapping):
|
||||
|
||||
|
||||
def omit_long_data(body, omit_len=512):
|
||||
""" omit too long str/bytes
|
||||
"""
|
||||
"""omit too long str/bytes"""
|
||||
if not isinstance(body, (str, bytes)):
|
||||
return body
|
||||
|
||||
@@ -243,8 +243,7 @@ def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List):
|
||||
|
||||
|
||||
class ExtendJSONEncoder(json.JSONEncoder):
|
||||
""" especially used to safely dump json data with python object, such as MultipartEncoder
|
||||
"""
|
||||
"""especially used to safely dump json data with python object, such as MultipartEncoder"""
|
||||
|
||||
def default(self, obj):
|
||||
try:
|
||||
@@ -256,8 +255,7 @@ class ExtendJSONEncoder(json.JSONEncoder):
|
||||
def merge_variables(
|
||||
variables: VariablesMapping, variables_to_be_overridden: VariablesMapping
|
||||
) -> VariablesMapping:
|
||||
""" merge two variables mapping, the first variables have higher priority
|
||||
"""
|
||||
"""merge two variables mapping, the first variables have higher priority"""
|
||||
step_new_variables = {}
|
||||
for key, value in variables.items():
|
||||
if f"${key}" == value or "${" + key + "}" == value:
|
||||
@@ -282,7 +280,7 @@ def is_support_multiprocessing() -> bool:
|
||||
|
||||
|
||||
def gen_cartesian_product(*args: List[Dict]) -> List[Dict]:
|
||||
""" generate cartesian product for lists
|
||||
"""generate cartesian product for lists
|
||||
|
||||
Args:
|
||||
args (list of list): lists to be generated with cartesian product
|
||||
@@ -320,3 +318,12 @@ def gen_cartesian_product(*args: List[Dict]) -> List[Dict]:
|
||||
product_list.append(product_item_dict)
|
||||
|
||||
return product_list
|
||||
|
||||
|
||||
LOGGER_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level}</level> | <level>{message}</level>"
|
||||
|
||||
|
||||
def init_logger():
|
||||
# set log level to INFO
|
||||
logger.remove()
|
||||
logger.add(sys.stderr, format=LOGGER_FORMAT, level="INFO")
|
||||
|
||||
194
poetry.lock
generated
194
poetry.lock
generated
@@ -34,19 +34,6 @@ type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "appdirs"
|
||||
version = "1.4.4"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.0"
|
||||
@@ -81,23 +68,26 @@ reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "19.10b0"
|
||||
version = "22.3.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
appdirs = "*"
|
||||
attrs = ">=18.1.0"
|
||||
click = ">=6.5"
|
||||
pathspec = ">=0.6,<1"
|
||||
regex = "*"
|
||||
toml = ">=0.9.4"
|
||||
typed-ast = ">=1.4.0"
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
@@ -315,6 +305,19 @@ type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
@@ -344,6 +347,23 @@ type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
@@ -486,19 +506,6 @@ type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2022.3.15"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.27.1"
|
||||
@@ -692,7 +699,7 @@ upload = ["requests-toolbelt", "filetype"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "ce51964d1daf419593be8b6d6f003069bf8626d922b950432e2c6f9a8093d9e6"
|
||||
content-hash = "8dc444117b1b9a55f00d25b86e69a26d9ceaac2d331731b676415fd4a019f57a"
|
||||
|
||||
[metadata.files]
|
||||
allure-pytest = [
|
||||
@@ -703,10 +710,6 @@ 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"},
|
||||
]
|
||||
appdirs = [
|
||||
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
||||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
@@ -716,8 +719,29 @@ attrs = [
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
|
||||
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
|
||||
{file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
|
||||
{file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
|
||||
{file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
|
||||
{file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
|
||||
{file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
|
||||
{file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
|
||||
{file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
|
||||
{file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
|
||||
{file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
|
||||
{file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
|
||||
{file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
|
||||
{file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
|
||||
{file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
|
||||
{file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
|
||||
{file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
|
||||
{file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
|
||||
{file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
|
||||
{file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
|
||||
{file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
|
||||
{file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
|
||||
{file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
|
||||
{file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
|
||||
{file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
|
||||
]
|
||||
brotli = [
|
||||
{file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"},
|
||||
@@ -888,6 +912,10 @@ markupsafe = [
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
@@ -896,6 +924,10 @@ pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
@@ -975,82 +1007,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"},
|
||||
]
|
||||
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"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0a5a1fdc9f148a8827d55b05425801acebeeefc9e86065c7ac8b8cc740a91ff"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb374a2a4dba7c4be0b19dc7b1adc50e6c2c26c3369ac629f50f3c198f3743a4"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c33ce0c665dd325200209340a88438ba7a470bd5f09f7424e520e1a3ff835b52"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04c09b9651fa814eeeb38e029dc1ae83149203e4eeb94e52bb868fadf64852bc"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab5d89cfaf71807da93c131bb7a19c3e19eaefd613d14f3bce4e97de830b15df"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e2630ae470d6a9f8e4967388c1eda4762706f5750ecf387785e0df63a4cc5af"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:df037c01d68d1958dad3463e2881d3638a0d6693483f58ad41001aa53a83fcea"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:940570c1a305bac10e8b2bc934b85a7709c649317dd16520471e85660275083a"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7f63877c87552992894ea1444378b9c3a1d80819880ae226bb30b04789c0828c"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3e265b388cc80c7c9c01bb4f26c9e536c40b2c05b7231fbb347381a2e1c8bf43"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:058054c7a54428d5c3e3739ac1e363dc9347d15e64833817797dc4f01fb94bb8"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-win32.whl", hash = "sha256:76435a92e444e5b8f346aed76801db1c1e5176c4c7e17daba074fbb46cb8d783"},
|
||||
{file = "regex-2022.3.15-cp310-cp310-win_amd64.whl", hash = "sha256:174d964bc683b1e8b0970e1325f75e6242786a92a22cedb2a6ec3e4ae25358bd"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e1d8ed9e61f37881c8db383a124829a6e8114a69bd3377a25aecaeb9b3538f8"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b52771f05cff7517f7067fef19ffe545b1f05959e440d42247a17cd9bddae11b"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:673f5a393d603c34477dbad70db30025ccd23996a2d0916e942aac91cc42b31a"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8923e1c5231549fee78ff9b2914fad25f2e3517572bb34bfaa3aea682a758683"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764e66a0e382829f6ad3bbce0987153080a511c19eb3d2f8ead3f766d14433ac"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd00859291658fe1fda48a99559fb34da891c50385b0bfb35b808f98956ef1e7"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa2ce79f3889720b46e0aaba338148a1069aea55fda2c29e0626b4db20d9fcb7"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:34bb30c095342797608727baf5c8aa122406aa5edfa12107b8e08eb432d4c5d7"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:25ecb1dffc5e409ca42f01a2b2437f93024ff1612c1e7983bad9ee191a5e8828"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:aa5eedfc2461c16a092a2fabc5895f159915f25731740c9152a1b00f4bcf629a"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7d1a6e403ac8f1d91d8f51c441c3f99367488ed822bda2b40836690d5d0059f5"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3e4d710ff6539026e49f15a3797c6b1053573c2b65210373ef0eec24480b900b"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-win32.whl", hash = "sha256:0100f0ded953b6b17f18207907159ba9be3159649ad2d9b15535a74de70359d3"},
|
||||
{file = "regex-2022.3.15-cp36-cp36m-win_amd64.whl", hash = "sha256:f320c070dea3f20c11213e56dbbd7294c05743417cde01392148964b7bc2d31a"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fc8c7958d14e8270171b3d72792b609c057ec0fa17d507729835b5cff6b7f69a"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ca6dcd17f537e9f3793cdde20ac6076af51b2bd8ad5fe69fa54373b17b48d3c"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0214ff6dff1b5a4b4740cfe6e47f2c4c92ba2938fca7abbea1359036305c132f"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a98ae493e4e80b3ded6503ff087a8492db058e9c68de371ac3df78e88360b374"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b1cc70e31aacc152a12b39245974c8fccf313187eead559ee5966d50e1b5817"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4829db3737480a9d5bfb1c0320c4ee13736f555f53a056aacc874f140e98f64"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:303b15a3d32bf5fe5a73288c316bac5807587f193ceee4eb6d96ee38663789fa"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:dc7b7c16a519d924c50876fb152af661a20749dcbf653c8759e715c1a7a95b18"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ce3057777a14a9a1399b81eca6a6bfc9612047811234398b84c54aeff6d536ea"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:48081b6bff550fe10bcc20c01cf6c83dbca2ccf74eeacbfac240264775fd7ecf"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dcbb7665a9db9f8d7642171152c45da60e16c4f706191d66a1dc47ec9f820aed"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c155a1a80c5e7a8fa1d9bb1bf3c8a953532b53ab1196092749bafb9d3a7cbb60"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-win32.whl", hash = "sha256:04b5ee2b6d29b4a99d38a6469aa1db65bb79d283186e8460542c517da195a8f6"},
|
||||
{file = "regex-2022.3.15-cp37-cp37m-win_amd64.whl", hash = "sha256:797437e6024dc1589163675ae82f303103063a0a580c6fd8d0b9a0a6708da29e"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8afcd1c2297bc989dceaa0379ba15a6df16da69493635e53431d2d0c30356086"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0066a6631c92774391f2ea0f90268f0d82fffe39cb946f0f9c6b382a1c61a5e5"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8248f19a878c72d8c0a785a2cd45d69432e443c9f10ab924c29adda77b324ae"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d1f3ea0d1924feb4cf6afb2699259f658a08ac6f8f3a4a806661c2dfcd66db1"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:794a6bc66c43db8ed06698fc32aaeaac5c4812d9f825e9589e56f311da7becd9"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d1445824944e642ffa54c4f512da17a953699c563a356d8b8cbdad26d3b7598"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f553a1190ae6cd26e553a79f6b6cfba7b8f304da2071052fa33469da075ea625"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:75a5e6ce18982f0713c4bac0704bf3f65eed9b277edd3fb9d2b0ff1815943327"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f16cf7e4e1bf88fecf7f41da4061f181a6170e179d956420f84e700fb8a3fd6b"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dad3991f0678facca1a0831ec1ddece2eb4d1dd0f5150acb9440f73a3b863907"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:491fc754428514750ab21c2d294486223ce7385446f2c2f5df87ddbed32979ae"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:6504c22c173bb74075d7479852356bb7ca80e28c8e548d4d630a104f231e04fb"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01c913cf573d1da0b34c9001a94977273b5ee2fe4cb222a5d5b320f3a9d1a835"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-win32.whl", hash = "sha256:029e9e7e0d4d7c3446aa92474cbb07dafb0b2ef1d5ca8365f059998c010600e6"},
|
||||
{file = "regex-2022.3.15-cp38-cp38-win_amd64.whl", hash = "sha256:947a8525c0a95ba8dc873191f9017d1b1e3024d4dc757f694e0af3026e34044a"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:591d4fba554f24bfa0421ba040cd199210a24301f923ed4b628e1e15a1001ff4"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9809404528a999cf02a400ee5677c81959bc5cb938fdc696b62eb40214e3632"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f08a7e4d62ea2a45557f561eea87c907222575ca2134180b6974f8ac81e24f06"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a86cac984da35377ca9ac5e2e0589bd11b3aebb61801204bd99c41fac516f0d"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:286908cbe86b1a0240a867aecfe26a439b16a1f585d2de133540549831f8e774"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b7494df3fdcc95a1f76cf134d00b54962dd83189520fd35b8fcd474c0aa616d"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b1ceede92400b3acfebc1425937454aaf2c62cd5261a3fabd560c61e74f6da3"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0317eb6331146c524751354ebef76a7a531853d7207a4d760dfb5f553137a2a4"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c144405220c5ad3f5deab4c77f3e80d52e83804a6b48b6bed3d81a9a0238e4c"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b2e24f3ae03af3d8e8e6d824c891fea0ca9035c5d06ac194a2700373861a15c"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f2c53f3af011393ab5ed9ab640fa0876757498aac188f782a0c620e33faa2a3d"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:060f9066d2177905203516c62c8ea0066c16c7342971d54204d4e51b13dfbe2e"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:530a3a16e57bd3ea0dff5ec2695c09632c9d6c549f5869d6cf639f5f7153fb9c"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-win32.whl", hash = "sha256:78ce90c50d0ec970bd0002462430e00d1ecfd1255218d52d08b3a143fe4bde18"},
|
||||
{file = "regex-2022.3.15-cp39-cp39-win_amd64.whl", hash = "sha256:c5adc854764732dbd95a713f2e6c3e914e17f2ccdc331b9ecb777484c31f73b6"},
|
||||
{file = "regex-2022.3.15.tar.gz", hash = "sha256:0a7b75cc7bb4cc0334380053e4671c560e31272c9d2d5a6c4b8e9ae2c9bd0f82"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[tool.poetry]
|
||||
name = "httprunner"
|
||||
version = "4.0.0-alpha"
|
||||
version = "4.0.0-beta"
|
||||
description = "One-stop solution for HTTP(S) testing."
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["debugtalk <debugtalk@gmail.com>"]
|
||||
|
||||
homepage = "https://github.com/httprunner/httprunner"
|
||||
homepage = "https://httprunner.com"
|
||||
repository = "https://github.com/httprunner/httprunner"
|
||||
documentation = "https://httprunner.com/docs"
|
||||
|
||||
@@ -35,7 +35,7 @@ pyyaml = "^5.4.1"
|
||||
pydantic = "~1.8" # >=1.8.0 <1.9.0
|
||||
loguru = "^0.4.1"
|
||||
jmespath = "^0.9.5"
|
||||
black = "^19.10b0"
|
||||
black = "^22.3.0"
|
||||
pytest = "^7.1.1"
|
||||
pytest-html = "^3.1.1"
|
||||
sentry-sdk = "^0.14.4"
|
||||
@@ -44,6 +44,7 @@ requests-toolbelt = {version = "^0.9.1", optional = true}
|
||||
filetype = {version = "^1.0.7", optional = true}
|
||||
Brotli = "^1.0.9"
|
||||
jinja2 = "^3.0.3"
|
||||
toml = "^0.10.2"
|
||||
|
||||
[tool.poetry.extras]
|
||||
allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# install hrp with one shell command
|
||||
# bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)"
|
||||
|
||||
LATEST_VERSION="v4.0.0-alpha"
|
||||
LATEST_VERSION="v4.0.0-beta"
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
Reference in New Issue
Block a user