From 7c052770e576cd26c37e64dd1591e8cf5cfaf4a7 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 3 May 2022 11:02:17 +0800 Subject: [PATCH 1/9] bump version to v4.0.0 --- README.md | 2 +- docs/CHANGELOG.md | 4 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- examples/data/a_b_c/T1_test.py | 2 +- examples/data/a_b_c/T2_3_test.py | 2 +- .../demo-with-go-plugin/plugin/debugtalk.go | 4 +- .../testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_requests.yml | 6 +- examples/demo-with-py-plugin/debugtalk.py | 6 +- .../demo-with-py-plugin/testcases/__init__.py | 1 - .../testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 60 -------------- .../testcases/demo_requests.yml | 6 +- .../testcases/demo_requests_test.py | 83 ------------------- examples/httpbin/basic_test.py | 2 +- examples/httpbin/hooks_test.py | 2 +- examples/httpbin/load_image_test.py | 2 +- examples/httpbin/upload_test.py | 2 +- examples/httpbin/validate_test.py | 2 +- .../cookie_manipulation/hardcode_test.py | 2 +- .../set_delete_cookies_test.py | 2 +- .../request_methods/hardcode_test.py | 2 +- .../request_with_functions_test.py | 2 +- .../request_with_parameters_test.py | 2 +- .../request_with_testcase_reference_test.py | 2 +- .../request_with_variables_test.py | 2 +- .../validate_with_functions_test.py | 2 +- .../validate_with_variables_test.py | 2 +- .../scaffold/templates/plugin/debugtalk.go | 4 +- .../scaffold/templates/plugin/debugtalk.py | 6 +- .../templates/testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 4 +- .../templates/testcases/demo_requests.yml | 6 +- .../templates/testcases/demo_requests_test.py | 8 +- hrp/internal/version/VERSION | 2 +- httprunner/__init__.py | 2 +- httprunner/make.py | 2 +- pyproject.toml | 2 +- 44 files changed, 59 insertions(+), 201 deletions(-) delete mode 100644 examples/demo-with-py-plugin/testcases/__init__.py delete mode 100644 examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py delete mode 100644 examples/demo-with-py-plugin/testcases/demo_requests_test.py diff --git a/README.md b/README.md index 5d7c2632..c63561ce 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ ## 核心特性 -- 网络协议:完整支持 HTTP(S)/1.1 和 HTTP/2,可扩展支持 WebSocket/TCP/RPC 等更多协议 +- 网络协议:完整支持 HTTP(S)/HTTP2/WebSocket,可扩展支持 TCP/UDP/RPC 等更多协议 - 多格式可选:测试用例支持 YAML/JSON/go test/pytest 格式,并且支持格式互相转换 - 双执行引擎:同时支持 golang/python 两个执行引擎,兼具 go 的高性能和 [pytest] 的丰富生态 - 录制 & 生成:可使用 [HAR]/Postman/Swagger/curl 等生成测试用例;基于链式调用的方法提示也可快速编写测试用例 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 17ab8c0d..51fb4192 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,13 +1,15 @@ # Release History -## v4.0.0-beta2 (2022-04-25) +## v4.0.0 (2022-05-03) **go version** - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility - fix #1240: losing host port in har2case +- fix: concurrent map write in parameterize - change: get hrp version from aliyun OSS file when installing +- change: report more load testing metrics to prometheus ## v4.0.0-beta (2022-04-24) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 4a0c55d0..ef528f9d 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [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 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index ebc6bf4c..fe95ddab 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 6ec6b49e..880bce0b 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 0d15ec54..d7b6e925 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index bc0c2437..5c59d217 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index d7581b4c..6d2f8486 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -34,4 +34,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 3402b1c9..a8706619 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/examples/data/a_b_c/T1_test.py b/examples/data/a_b_c/T1_test.py index d3273df7..504b3f6d 100644 --- a/examples/data/a_b_c/T1_test.py +++ b/examples/data/a_b_c/T1_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: a-b.c/1.yml diff --git a/examples/data/a_b_c/T2_3_test.py b/examples/data/a_b_c/T2_3_test.py index a225b4cb..452927a9 100644 --- a/examples/data/a_b_c/T2_3_test.py +++ b/examples/data/a_b_c/T2_3_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: a-b.c/2 3.yml diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go index dbb37554..b3b39400 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -42,11 +42,11 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-beta" + return fungo.Version } func main() { - fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("get_version", GetVersion) fungo.Register("sum_ints", SumInts) fungo.Register("sum_two_int", SumTwoInt) fungo.Register("sum_two", SumTwoInt) diff --git a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-go-plugin/testcases/demo_requests.yml b/examples/demo-with-go-plugin/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/examples/demo-with-go-plugin/testcases/demo_requests.yml +++ b/examples/demo-with-go-plugin/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py index 180725e0..9fd41120 100644 --- a/examples/demo-with-py-plugin/debugtalk.py +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -5,8 +5,8 @@ from typing import List import funppy -def get_httprunner_version(): - return "v4.0.0-beta" +def get_version(): + return funppy.__version__ def sleep(n_secs): @@ -60,7 +60,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("get_version", get_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) diff --git a/examples/demo-with-py-plugin/testcases/__init__.py b/examples/demo-with-py-plugin/testcases/__init__.py deleted file mode 100644 index 70cfba53..00000000 --- a/examples/demo-with-py-plugin/testcases/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# NOTICE: Generated By HttpRunner. DO NOT EDIT! diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py b/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py deleted file mode 100644 index e88b8d08..00000000 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py +++ /dev/null @@ -1,60 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.11 -# FROM: testcases/demo_ref_testcase.yml - - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - -from testcases.demo_requests_test import TestCaseDemoRequests as DemoRequests - - -class TestCaseDemoRefTestcase(HttpRunner): - - config = ( - Config("request methods testcase: reference testcase") - .variables( - **{ - "foo1": "testsuite_config_bar1", - "expect_foo1": "testsuite_config_bar1", - "expect_foo2": "config_bar2", - } - ) - .base_url("https://postman-echo.com") - .verify(False) - ) - - teststeps = [ - Step( - RunTestCase("request with functions") - .with_variables( - **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"} - ) - .call(DemoRequests) - .export(*["foo3"]) - ), - Step( - RunRequest("post form data") - .with_variables(**{"foo1": "bar1"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded", - } - ) - .with_data("foo1=$foo1&foo2=$foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", "bar1") - .assert_equal("body.form.foo2", "bar21") - ), - ] - - -if __name__ == "__main__": - TestCaseDemoRefTestcase().test_start() diff --git a/examples/demo-with-py-plugin/testcases/demo_requests.yml b/examples/demo-with-py-plugin/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/examples/demo-with-py-plugin/testcases/demo_requests.yml +++ b/examples/demo-with-py-plugin/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/demo_requests_test.py b/examples/demo-with-py-plugin/testcases/demo_requests_test.py deleted file mode 100644 index b5c34bc2..00000000 --- a/examples/demo-with-py-plugin/testcases/demo_requests_test.py +++ /dev/null @@ -1,83 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.11 -# FROM: testcases/demo_requests.yml - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - - -class TestCaseDemoRequests(HttpRunner): - - config = ( - Config("request methods testcase with functions") - .variables( - **{ - "foo1": "config_bar1", - "foo2": "config_bar2", - "expect_foo1": "config_bar1", - "expect_foo2": "config_bar2", - } - ) - .base_url("https://postman-echo.com") - .verify(False) - .export(*["foo3"]) - ) - - teststeps = [ - Step( - RunRequest("get with params") - .with_variables( - **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two_int(1, 2)}"} - ) - .get("/get") - .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) - .extract() - .with_jmespath("body.args.foo2", "foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.args.foo1", "bar11") - .assert_equal("body.args.sum_v", "3") - .assert_equal("body.args.foo2", "bar21") - ), - Step( - RunRequest("post raw text") - .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "text/plain", - } - ) - .with_data( - "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - ) - .validate() - .assert_equal("status_code", 200) - .assert_equal( - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", - ) - ), - Step( - RunRequest("post form data") - .with_variables(**{"foo2": "bar23"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded", - } - ) - .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", "$expect_foo1") - .assert_equal("body.form.foo2", "bar23") - .assert_equal("body.form.foo3", "bar21") - ), - ] - - -if __name__ == "__main__": - TestCaseDemoRequests().test_start() diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py index d13c87ac..42733b08 100644 --- a/examples/httpbin/basic_test.py +++ b/examples/httpbin/basic_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: basic.yml diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py index 96302d35..cb249adb 100644 --- a/examples/httpbin/hooks_test.py +++ b/examples/httpbin/hooks_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: hooks.yml diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py index a43f5e75..9c56466c 100644 --- a/examples/httpbin/load_image_test.py +++ b/examples/httpbin/load_image_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: load_image.yml diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py index 394ecbb6..56d2c1c8 100644 --- a/examples/httpbin/upload_test.py +++ b/examples/httpbin/upload_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: upload.yml diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py index ae948113..2a3e3e0f 100644 --- a/examples/httpbin/validate_test.py +++ b/examples/httpbin/validate_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: validate.yml diff --git a/examples/postman_echo/cookie_manipulation/hardcode_test.py b/examples/postman_echo/cookie_manipulation/hardcode_test.py index f8d58a4d..efc33f10 100644 --- a/examples/postman_echo/cookie_manipulation/hardcode_test.py +++ b/examples/postman_echo/cookie_manipulation/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: cookie_manipulation/hardcode.yml diff --git a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py index 371a5372..73d9928d 100644 --- a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py +++ b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: cookie_manipulation/set_delete_cookies.yml diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index a2b49836..a7fa8d14 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/hardcode.yml diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index 8688765f..a6f8ce0b 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_functions.yml diff --git a/examples/postman_echo/request_methods/request_with_parameters_test.py b/examples/postman_echo/request_methods/request_with_parameters_test.py index 5c6271a8..56e7c249 100644 --- a/examples/postman_echo/request_methods/request_with_parameters_test.py +++ b/examples/postman_echo/request_methods/request_with_parameters_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_parameters.yml diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index 6a55b63a..2ed2b6e9 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_testcase_reference.yml diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index 0c7c0f77..788a0a85 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_variables.yml diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 2ad1b58f..0b1f8966 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/validate_with_functions.yml diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index 26fa76bc..36b500a7 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/validate_with_variables.yml diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go index dbb37554..b3b39400 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -42,11 +42,11 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-beta" + return fungo.Version } func main() { - fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("get_version", GetVersion) fungo.Register("sum_ints", SumInts) fungo.Register("sum_two_int", SumTwoInt) fungo.Register("sum_two", SumTwoInt) diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py index 180725e0..9fd41120 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -5,8 +5,8 @@ from typing import List import funppy -def get_httprunner_version(): - return "v4.0.0-beta" +def get_version(): + return funppy.__version__ def sleep(n_secs): @@ -60,7 +60,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("get_version", get_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py index deae0a81..714030cd 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: testcases/demo_ref_testcase.yml @@ -43,7 +43,7 @@ class TestCaseDemoRefTestcase(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py index e54a7faa..fc2ad5bb 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: testcases/demo_requests.yml @@ -30,7 +30,7 @@ class TestCaseDemoRequests(HttpRunner): ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) + .with_headers(**{"User-Agent": "funplugin/${get_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() @@ -45,7 +45,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain", } ) @@ -65,7 +65,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 46390341..f684230d 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.0.0-beta \ No newline at end of file +v4.0.0 \ No newline at end of file diff --git a/httprunner/__init__.py b/httprunner/__init__.py index cbc40932..7d424fcb 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.0.0-beta" +__version__ = "v4.0.0" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.config import Config diff --git a/httprunner/make.py b/httprunner/make.py index c2b3d3b2..75a4e783 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -33,7 +33,7 @@ pytest_files_made_cache_mapping: Dict[Text, Text] = {} pytest_files_run_set: Set = set() __TEMPLATE__ = jinja2.Template( - """# NOTE: Generated By HttpRunner v{{ version }} + """# NOTE: Generated By HttpRunner {{ version }} # FROM: {{ testcase_path }} {% if imports_list and diff_levels > 0 %} diff --git a/pyproject.toml b/pyproject.toml index c50b5036..c5943ad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.0.0-beta" +version = "v4.0.0" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From 5bdbcdc2e510f17a465be74bd734c836cf10091d Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 3 May 2022 23:37:23 +0800 Subject: [PATCH 2/9] feat: stat HTTP request latencies (DNSLookup, TCP Connection and so on) --- docs/CHANGELOG.md | 3 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 3 +- docs/cmd/hrp_startproject.md | 2 +- hrp/cmd/run.go | 5 + hrp/internal/httpstat/main.go | 169 ++++++++++++++++++++++++++++++++++ hrp/runner.go | 8 ++ hrp/session.go | 4 + hrp/step.go | 19 ++-- hrp/step_request.go | 16 ++++ 14 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 hrp/internal/httpstat/main.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 51fb4192..e67c879e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,9 +1,10 @@ # Release History -## v4.0.0 (2022-05-03) +## v4.0.0 (2022-05-04) **go version** +- feat: stat HTTP request latencies (DNSLookup, TCP Connection and so on) - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility - fix #1240: losing host port in har2case diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index ef528f9d..ce94ba75 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [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 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index fe95ddab..4344710a 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 880bce0b..2010b694 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index d7b6e925..3fd38c9d 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 5c59d217..29678585 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 6d2f8486..86c7018d 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -24,6 +24,7 @@ hrp run $path... [flags] -c, --continue-on-failure continue running next step when failure occurs -g, --gen-html-report generate html report -h, --help help for run + --http-stat turn on HTTP latency stat (DNSLookup, TCP Connection, etc.) --log-plugin turn on plugin logging --log-requests-off turn off request & response details logging -p, --proxy-url string set proxy url @@ -34,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index a8706619..95fa28e6 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/hrp/cmd/run.go b/hrp/cmd/run.go index 17f54014..891563fd 100644 --- a/hrp/cmd/run.go +++ b/hrp/cmd/run.go @@ -35,6 +35,9 @@ var runCmd = &cobra.Command{ if !requestsLogOff { runner.SetRequestsLogOn() } + if httpStatOn { + runner.SetHTTPStatOn() + } if pluginLogOn { runner.SetPluginLogOn() } @@ -51,6 +54,7 @@ var runCmd = &cobra.Command{ var ( continueOnFailure bool requestsLogOff bool + httpStatOn bool pluginLogOn bool proxyUrl string saveTests bool @@ -61,6 +65,7 @@ func init() { rootCmd.AddCommand(runCmd) runCmd.Flags().BoolVarP(&continueOnFailure, "continue-on-failure", "c", false, "continue running next step when failure occurs") runCmd.Flags().BoolVar(&requestsLogOff, "log-requests-off", false, "turn off request & response details logging") + runCmd.Flags().BoolVar(&httpStatOn, "http-stat", false, "turn on HTTP latency stat (DNSLookup, TCP Connection, etc.)") runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", false, "turn on plugin logging") runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url") runCmd.Flags().BoolVarP(&saveTests, "save-tests", "s", false, "save tests summary") diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go new file mode 100644 index 00000000..1d9a1a5e --- /dev/null +++ b/hrp/internal/httpstat/main.go @@ -0,0 +1,169 @@ +// Package httpstat traces HTTP latency infomation (DNSLookup, TCP Connection and so on) on any golang HTTP request. +// It uses `httptrace` package. +// Forked from https://github.com/tcnksm/go-httpstat +package httpstat + +import ( + "context" + "crypto/tls" + "net/http/httptrace" + "time" +) + +// Stat stores httpstat info. +type Stat struct { + // The following are duration for each phase + // DNSLookup => TCPConnection => TLSHandshake => ServerProcessing => ContentTransfer + DNSLookup time.Duration + TCPConnection time.Duration + TLSHandshake time.Duration + ServerProcessing time.Duration + ContentTransfer time.Duration // from the first response byte to tansfer done. + + // The followings are timeline of request + NameLookup time.Duration // = DNSLookup + Connect time.Duration // = DNSLookup + TCPConnection + Pretransfer time.Duration // = DNSLookup + TCPConnection + TLSHandshake + StartTransfer time.Duration // = DNSLookup + TCPConnection + TLSHandshake + ServerProcessing + Total time.Duration // = DNSLookup + TCPConnection + TLSHandshake + ServerProcessing + ContentTransfer + + // internal timelines, including start and finish timestamps of each phase + dnsStart time.Time + dnsDone time.Time + tcpStart time.Time + tcpDone time.Time + tlsStart time.Time + tlsDone time.Time + serverStart time.Time + serverDone time.Time + transferStart time.Time + transferDone time.Time // need to be provided from outside + + // isTLS is true when connection seems to use TLS + isTLS bool + + // isReused is true when connection is reused (keep-alive) + isReused bool +} + +// Finish sets the time when reading response is done. +// This must be called after reading response body. +func (s *Stat) Finish() { + s.transferDone = time.Now() + + // This means result is empty (it does nothing). + // Skip setting value (contentTransfer and total will be zero). + if s.dnsStart.IsZero() { + return + } + + s.ContentTransfer = s.transferDone.Sub(s.transferStart) + s.Total = s.transferDone.Sub(s.dnsStart) +} + +// Durations returns all durations and timelines of request latencies +func (s *Stat) Durations() map[string]time.Duration { + return map[string]time.Duration{ + "DNSLookup": s.DNSLookup / time.Millisecond, + "TCPConnection": s.TCPConnection / time.Millisecond, + "TLSHandshake": s.TLSHandshake / time.Millisecond, + "ServerProcessing": s.ServerProcessing / time.Millisecond, + "ContentTransfer": s.ContentTransfer / time.Millisecond, + "NameLookup": s.NameLookup / time.Millisecond, + "Connect": s.Connect / time.Millisecond, + "Pretransfer": s.Connect / time.Millisecond, + "StartTransfer": s.StartTransfer / time.Millisecond, + "Total": s.Total / time.Millisecond, + } +} + +// WithHTTPStat is a wrapper of httptrace.WithClientTrace. +// It records the time of each httptrace hooks. +func WithHTTPStat(ctx context.Context, s *Stat) context.Context { + return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + DNSStart: func(i httptrace.DNSStartInfo) { + s.dnsStart = time.Now() + }, + + DNSDone: func(i httptrace.DNSDoneInfo) { + s.dnsDone = time.Now() + + s.DNSLookup = s.dnsDone.Sub(s.dnsStart) + s.NameLookup = s.dnsDone.Sub(s.dnsStart) + }, + + ConnectStart: func(_, _ string) { + s.tcpStart = time.Now() + + // When connecting to IP (When no DNS lookup) + if s.dnsStart.IsZero() { + s.dnsStart = s.tcpStart + s.dnsDone = s.tcpStart + } + }, + + ConnectDone: func(network, addr string, err error) { + s.tcpDone = time.Now() + s.TCPConnection = s.tcpDone.Sub(s.tcpStart) + s.Connect = s.tcpDone.Sub(s.dnsStart) + }, + + TLSHandshakeStart: func() { + s.isTLS = true + s.tlsStart = time.Now() + }, + + TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { + s.tlsDone = time.Now() + s.TLSHandshake = s.tlsDone.Sub(s.tlsStart) + s.Pretransfer = s.tlsDone.Sub(s.dnsStart) + }, + + GotConn: func(i httptrace.GotConnInfo) { + // Handle when keep alive is used and connection is reused. + // DNSStart(Done) and ConnectStart(Done) is skipped + if i.Reused { + s.isReused = true + } + }, + + WroteRequest: func(info httptrace.WroteRequestInfo) { + s.serverStart = time.Now() + + // When client doesn't use DialContext or using old (before go1.7) `net` + // package, DNS/TCP/TLS hook is not called. + if s.dnsStart.IsZero() && s.tcpStart.IsZero() { + now := s.serverStart + s.dnsStart = now + s.dnsDone = now + s.tcpStart = now + s.tcpDone = now + } + + // When connection is re-used, DNS/TCP/TLS hook is not called. + if s.isReused { + now := s.serverStart + s.dnsStart = now + s.dnsDone = now + s.tcpStart = now + s.tcpDone = now + s.tlsStart = now + s.tlsDone = now + } + + if s.isTLS { + return + } + + s.TLSHandshake = s.tcpDone.Sub(s.tcpDone) + s.Pretransfer = s.Connect + }, + + GotFirstResponseByte: func() { + s.serverDone = time.Now() + s.ServerProcessing = s.serverDone.Sub(s.serverStart) + s.StartTransfer = s.serverDone.Sub(s.dnsStart) + s.transferStart = s.serverDone + }, + }) +} diff --git a/hrp/runner.go b/hrp/runner.go index d21b8beb..74c463cd 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -54,6 +54,7 @@ func NewRunner(t *testing.T) *HRPRunner { type HRPRunner struct { t *testing.T failfast bool + httpStatOn bool requestsLogOn bool pluginLogOn bool saveTests bool @@ -100,6 +101,13 @@ func (r *HRPRunner) SetRequestsLogOn() *HRPRunner { return r } +// SetHTTPStatOn turns on HTTP latency stat. +func (r *HRPRunner) SetHTTPStatOn() *HRPRunner { + log.Info().Msg("[init] SetHTTPStatOn") + r.httpStatOn = true + return r +} + // SetPluginLogOn turns on plugin logging. func (r *HRPRunner) SetPluginLogOn() *HRPRunner { log.Info().Msg("[init] SetPluginLogOn") diff --git a/hrp/session.go b/hrp/session.go index 96710284..eae3b41a 100644 --- a/hrp/session.go +++ b/hrp/session.go @@ -42,6 +42,10 @@ func (r *SessionRunner) GetConfig() *TConfig { return r.parsedConfig } +func (r *SessionRunner) HTTPStatOn() bool { + return r.hrpRunner.httpStatOn +} + func (r *SessionRunner) LogOn() bool { return r.hrpRunner.requestsLogOn } diff --git a/hrp/step.go b/hrp/step.go index 242f9294..4ee91355 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -1,5 +1,7 @@ package hrp +import "time" + type StepType string const ( @@ -13,14 +15,15 @@ const ( ) type StepResult struct { - Name string `json:"name" yaml:"name"` // step name - StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous - Success bool `json:"success" yaml:"success"` // step execution result - Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) - Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data - ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length - ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables - Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information + Name string `json:"name" yaml:"name"` // step name + StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous + Success bool `json:"success" yaml:"success"` // step execution result + Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) + HttpStat map[string]time.Duration `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data + ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length + ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables + Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information } // TStep represents teststep data structure. diff --git a/hrp/step_request.go b/hrp/step_request.go index 64994657..d1b9e7a8 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -19,6 +19,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/hrp/internal/httpstat" "github.com/httprunner/httprunner/hrp/internal/json" ) @@ -311,6 +312,13 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } } + // stat HTTP request + var httpStat httpstat.Stat + if r.HTTPStatOn() { + ctx := httpstat.WithHTTPStat(rb.req.Context(), &httpStat) + rb.req = rb.req.WithContext(ctx) + } + // do request action start := time.Now() var resp *http.Response @@ -339,6 +347,14 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } } + if r.HTTPStatOn() { + httpStat.Finish() + stepResult.HttpStat = httpStat.Durations() + log.Info(). + Interface("httpstat(ms)", httpStat.Durations()). + Msg("HTTP latency statistics") + } + // new response object respObj, err := newHttpResponseObject(r.hrpRunner.t, parser, resp) if err != nil { From 9f5736260ca83f9bfb24f9843ec3885475b74b2d Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 00:29:14 +0800 Subject: [PATCH 3/9] test: add unittest for httpstat --- hrp/internal/httpstat/main.go | 24 +++++----- hrp/step.go | 20 ++++---- hrp/step_request_test.go | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 23 deletions(-) diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 1d9a1a5e..765e7171 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -62,18 +62,18 @@ func (s *Stat) Finish() { } // Durations returns all durations and timelines of request latencies -func (s *Stat) Durations() map[string]time.Duration { - return map[string]time.Duration{ - "DNSLookup": s.DNSLookup / time.Millisecond, - "TCPConnection": s.TCPConnection / time.Millisecond, - "TLSHandshake": s.TLSHandshake / time.Millisecond, - "ServerProcessing": s.ServerProcessing / time.Millisecond, - "ContentTransfer": s.ContentTransfer / time.Millisecond, - "NameLookup": s.NameLookup / time.Millisecond, - "Connect": s.Connect / time.Millisecond, - "Pretransfer": s.Connect / time.Millisecond, - "StartTransfer": s.StartTransfer / time.Millisecond, - "Total": s.Total / time.Millisecond, +func (s *Stat) Durations() map[string]int64 { + return map[string]int64{ + "DNSLookup": s.DNSLookup.Milliseconds(), + "TCPConnection": s.TCPConnection.Milliseconds(), + "TLSHandshake": s.TLSHandshake.Milliseconds(), + "ServerProcessing": s.ServerProcessing.Milliseconds(), + "ContentTransfer": s.ContentTransfer.Milliseconds(), + "NameLookup": s.NameLookup.Milliseconds(), + "Connect": s.Connect.Milliseconds(), + "Pretransfer": s.Connect.Milliseconds(), + "StartTransfer": s.StartTransfer.Milliseconds(), + "Total": s.Total.Milliseconds(), } } diff --git a/hrp/step.go b/hrp/step.go index 4ee91355..8a9d0031 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -1,7 +1,5 @@ package hrp -import "time" - type StepType string const ( @@ -15,15 +13,15 @@ const ( ) type StepResult struct { - Name string `json:"name" yaml:"name"` // step name - StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous - Success bool `json:"success" yaml:"success"` // step execution result - Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) - HttpStat map[string]time.Duration `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) - Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data - ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length - ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables - Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information + Name string `json:"name" yaml:"name"` // step name + StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous + Success bool `json:"success" yaml:"success"` // step execution result + Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) + HttpStat map[string]int64 `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data + ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length + ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables + Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information } // TStep represents teststep data structure. diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 234119f4..795595b1 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -2,6 +2,8 @@ package hrp import ( "testing" + + "github.com/stretchr/testify/assert" ) var ( @@ -89,3 +91,87 @@ func TestRunRequestRun(t *testing.T) { t.Fatalf("stepPOSTData.Run() error: %v", err) } } + +func TestRunRequestStatOn(t *testing.T) { + testcase := &TestCase{ + Config: NewConfig("test").SetBaseURL("https://postman-echo.com"), + TestSteps: []IStep{stepGET, stepPOSTData}, + } + runner := NewRunner(t).SetHTTPStatOn() + sessionRunner, _ := runner.NewSessionRunner(testcase) + if err := sessionRunner.Start(nil); err != nil { + t.Fatal() + } + summary := sessionRunner.GetSummary() + + stat := summary.Records[0].HttpStat + if !assert.Greater(t, stat["DNSLookup"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["TCPConnection"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["TLSHandshake"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + t.Fatal() + } + if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { + t.Fatal() + } + if !assert.Greater(t, stat["NameLookup"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["Connect"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["Pretransfer"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["StartTransfer"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["Total"], int64(10)) { + t.Fatal() + } + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + t.Fatal() + } + + // reuse connection + stat = summary.Records[1].HttpStat + if !assert.Equal(t, stat["DNSLookup"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["TCPConnection"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { + t.Fatal() + } + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + t.Fatal() + } + if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["NameLookup"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["Connect"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["Pretransfer"], int64(0)) { + t.Fatal() + } + if !assert.Greater(t, stat["StartTransfer"], int64(10)) { + t.Fatal() + } + if !assert.Greater(t, stat["Total"], int64(10)) { + t.Fatal() + } + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + t.Fatal() + } +} From 0bd8cf39eafc022c43b72dbb3aee8d564ac80239 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 14:24:48 +0800 Subject: [PATCH 4/9] feat: print http stat with color --- go.mod | 1 + go.sum | 4 +- hrp/internal/httpstat/main.go | 93 +++++++++++++++++++++++++++++++++-- hrp/step.go | 2 +- hrp/step_request.go | 4 +- hrp/step_request_test.go | 20 ++++---- 6 files changed, 106 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 510515e6..cdba9350 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/andybalholm/brotli v1.0.4 github.com/denisbrodbeck/machineid v1.0.1 + github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.13.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 diff --git a/go.sum b/go.sum index 24c26644..e72fb518 100644 --- a/go.sum +++ b/go.sum @@ -106,8 +106,9 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -303,6 +304,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 765e7171..8d8ccc0d 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -1,15 +1,64 @@ // Package httpstat traces HTTP latency infomation (DNSLookup, TCP Connection and so on) on any golang HTTP request. // It uses `httptrace` package. -// Forked from https://github.com/tcnksm/go-httpstat +// Inspired by https://github.com/tcnksm/go-httpstat and https://github.com/davecheney/httpstat package httpstat import ( "context" "crypto/tls" + "fmt" "net/http/httptrace" + "strconv" + "strings" "time" + + "github.com/fatih/color" + "github.com/rs/zerolog/log" ) +const ( + httpsTemplate = "\n" + + ` DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer` + "\n" + + `[%s | %s | %s | %s | %s ]` + "\n" + + ` | | | | |` + "\n" + + ` namelookup:%s | | | |` + "\n" + + ` connect:%s | | |` + "\n" + + ` pretransfer:%s | |` + "\n" + + ` starttransfer:%s |` + "\n" + + ` total:%s` + "\n\n" + + httpTemplate = "\n" + + ` DNS Lookup TCP Connection Server Processing Content Transfer` + "\n" + + `[ %s | %s | %s | %s ]` + "\n" + + ` | | | |` + "\n" + + ` namelookup:%s | | |` + "\n" + + ` connect:%s | |` + "\n" + + ` starttransfer:%s |` + "\n" + + ` total:%s` + "\n\n" +) + +func fmta(d time.Duration) string { + return color.BlueString("%7dms", int(d.Milliseconds())) +} + +func fmtb(d time.Duration) string { + return color.MagentaString("%-9s", strconv.Itoa(int(d.Milliseconds()))+"ms") +} + +func grayscale(code color.Attribute) func(string, ...interface{}) string { + return color.New(code + 232).SprintfFunc() +} + +func colorize(s string) string { + v := strings.Split(s, "\n") + v[0] = grayscale(16)(v[0]) + return strings.Join(v, "\n") +} + +func printf(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(color.Output, format, a...) +} + // Stat stores httpstat info. type Stat struct { // The following are duration for each phase @@ -61,6 +110,11 @@ func (s *Stat) Finish() { s.Total = s.transferDone.Sub(s.dnsStart) } +func (s *Stat) assertSchemaHTTP() bool { + // HTTP other than HTTPS + return s.dnsStart.IsZero() && s.tcpStart.IsZero() +} + // Durations returns all durations and timelines of request latencies func (s *Stat) Durations() map[string]int64 { return map[string]int64{ @@ -71,12 +125,45 @@ func (s *Stat) Durations() map[string]int64 { "ContentTransfer": s.ContentTransfer.Milliseconds(), "NameLookup": s.NameLookup.Milliseconds(), "Connect": s.Connect.Milliseconds(), - "Pretransfer": s.Connect.Milliseconds(), + "Pretransfer": s.Pretransfer.Milliseconds(), "StartTransfer": s.StartTransfer.Milliseconds(), "Total": s.Total.Milliseconds(), } } +func (s *Stat) Print() { + if s.assertSchemaHTTP() { + // http + printf(colorize(httpTemplate), + fmta(s.DNSLookup), // dns lookup + fmta(s.TCPConnection), // tcp connection + fmta(s.ServerProcessing), // server processing + fmta(s.ContentTransfer), // content transfer + fmtb(s.NameLookup), // namelookup + fmtb(s.Connect), // connect + fmtb(s.StartTransfer), // starttransfer + fmtb(s.Total), // total + ) + } else { + // https + printf(colorize(httpsTemplate), + fmta(s.DNSLookup), // dns lookup + fmta(s.TCPConnection), // tcp connection + fmta(s.TLSHandshake), // tls handshake + fmta(s.ServerProcessing), // server processing + fmta(s.ContentTransfer), // content transfer + fmtb(s.NameLookup), // namelookup + fmtb(s.Connect), // connect + fmtb(s.Pretransfer), // pretransfer + fmtb(s.StartTransfer), // starttransfer + fmtb(s.Total), // total + ) + } + log.Info(). + Interface("httpstat(ms)", s.Durations()). + Msg("HTTP latency statistics") +} + // WithHTTPStat is a wrapper of httptrace.WithClientTrace. // It records the time of each httptrace hooks. func WithHTTPStat(ctx context.Context, s *Stat) context.Context { @@ -132,7 +219,7 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { // When client doesn't use DialContext or using old (before go1.7) `net` // package, DNS/TCP/TLS hook is not called. - if s.dnsStart.IsZero() && s.tcpStart.IsZero() { + if s.assertSchemaHTTP() { now := s.serverStart s.dnsStart = now s.dnsDone = now diff --git a/hrp/step.go b/hrp/step.go index 8a9d0031..b4583852 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -17,7 +17,7 @@ type StepResult struct { StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous Success bool `json:"success" yaml:"success"` // step execution result Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) - HttpStat map[string]int64 `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) + HttpStat map[string]int64 `json:"httpstat,omitempty" yaml:"httpstat,omitempty"` // httpstat in millisecond(ms) Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables diff --git a/hrp/step_request.go b/hrp/step_request.go index d1b9e7a8..9dbae164 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -350,9 +350,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err if r.HTTPStatOn() { httpStat.Finish() stepResult.HttpStat = httpStat.Durations() - log.Info(). - Interface("httpstat(ms)", httpStat.Durations()). - Msg("HTTP latency statistics") + httpStat.Print() } // new response object diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 795595b1..8a88e3bb 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -105,31 +105,31 @@ func TestRunRequestStatOn(t *testing.T) { summary := sessionRunner.GetSummary() stat := summary.Records[0].HttpStat - if !assert.Greater(t, stat["DNSLookup"], int64(1)) { + if !assert.Greater(t, stat["DNSLookup"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["TCPConnection"], int64(1)) { + if !assert.Greater(t, stat["TCPConnection"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["TLSHandshake"], int64(1)) { + if !assert.Greater(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { t.Fatal() } if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["NameLookup"], int64(1)) { + if !assert.Greater(t, stat["NameLookup"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Connect"], int64(1)) { + if !assert.Greater(t, stat["Connect"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Pretransfer"], int64(1)) { + if !assert.Greater(t, stat["Pretransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["StartTransfer"], int64(1)) { + if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["Total"], int64(10)) { @@ -150,7 +150,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { t.Fatal() } if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { @@ -165,7 +165,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Equal(t, stat["Pretransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["StartTransfer"], int64(10)) { + if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["Total"], int64(10)) { From fb229c89c478e1594453f2d62112d8565463cc4c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 14:33:07 +0800 Subject: [PATCH 5/9] change: skip funppy version requirements --- hrp/internal/scaffold/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 98f5ffed..d655384d 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -185,7 +185,7 @@ func createPythonPlugin(projectName string) error { return errors.Wrap(err, "copy file failed") } - _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy>=%s", shared.Version)) + _, err = builtin.EnsurePython3Venv("funppy") if err != nil { return err } From 1b83b17a14fbf870a875643a232f6805d5938af9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 14:44:20 +0800 Subject: [PATCH 6/9] fix: unittest --- hrp/step_request_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 8a88e3bb..254cdee1 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -105,7 +105,7 @@ func TestRunRequestStatOn(t *testing.T) { summary := sessionRunner.GetSummary() stat := summary.Records[0].HttpStat - if !assert.Greater(t, stat["DNSLookup"], int64(0)) { + if !assert.GreaterOrEqual(t, stat["DNSLookup"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["TCPConnection"], int64(0)) { @@ -114,7 +114,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { t.Fatal() } if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { @@ -150,7 +150,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { t.Fatal() } if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { From 8d425aa696e93cbb72ae60e14c16482382caba44 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 15:56:02 +0800 Subject: [PATCH 7/9] change: upgrade funplugin to v0.4.5 --- docs/CHANGELOG.md | 2 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- examples/demo-with-go-plugin/plugin/go.mod | 2 +- examples/demo-with-go-plugin/plugin/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e67c879e..adee1c8c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v4.0.0 (2022-05-04) +## v4.0.0 (2022-05-05) **go version** diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index ce94ba75..1b512dba 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [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 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 4344710a..dc049340 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 2010b694..5366a607 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 3fd38c9d..852c2bc2 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 29678585..6bc851e2 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 86c7018d..d0125914 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 95fa28e6..d459b838 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod index 941628d8..8dabb414 100644 --- a/examples/demo-with-go-plugin/plugin/go.mod +++ b/examples/demo-with-go-plugin/plugin/go.mod @@ -2,4 +2,4 @@ module plugin go 1.16 -require github.com/httprunner/funplugin v0.4.3 // indirect +require github.com/httprunner/funplugin v0.4.5 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum index 93a4421a..33c01bfb 100644 --- a/examples/demo-with-go-plugin/plugin/go.sum +++ b/examples/demo-with-go-plugin/plugin/go.sum @@ -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.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg= -github.com/httprunner/funplugin v0.4.3/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.5 h1:2KCj5AZZA22OER6TN5P/PSBYFMiKpgTmCRbDmHB1tos= +github.com/httprunner/funplugin v0.4.5/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= diff --git a/go.mod b/go.mod index cdba9350..dbe79c4c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,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.4 + github.com/httprunner/funplugin v0.4.5 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index e72fb518..62502254 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,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.4 h1:IVt603Y57WfSbn6DZ0R4iLeGQJ1yj944gmYwEOSBzGo= -github.com/httprunner/funplugin v0.4.4/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.5 h1:2KCj5AZZA22OER6TN5P/PSBYFMiKpgTmCRbDmHB1tos= +github.com/httprunner/funplugin v0.4.5/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= From 47557a403bab95f32c3107b01f18d786256d9229 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 16:03:30 +0800 Subject: [PATCH 8/9] fix: unittest --- hrp/internal/httpstat/main.go | 58 +++++++++++++++++------------------ hrp/step_request.go | 15 ++++----- hrp/step_request_test.go | 6 ++-- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 8d8ccc0d..e5731898 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -7,6 +7,7 @@ import ( "context" "crypto/tls" "fmt" + "net/http" "net/http/httptrace" "strconv" "strings" @@ -93,6 +94,9 @@ type Stat struct { // isReused is true when connection is reused (keep-alive) isReused bool + + // https or http + schema string } // Finish sets the time when reading response is done. @@ -110,11 +114,6 @@ func (s *Stat) Finish() { s.Total = s.transferDone.Sub(s.dnsStart) } -func (s *Stat) assertSchemaHTTP() bool { - // HTTP other than HTTPS - return s.dnsStart.IsZero() && s.tcpStart.IsZero() -} - // Durations returns all durations and timelines of request latencies func (s *Stat) Durations() map[string]int64 { return map[string]int64{ @@ -132,20 +131,8 @@ func (s *Stat) Durations() map[string]int64 { } func (s *Stat) Print() { - if s.assertSchemaHTTP() { - // http - printf(colorize(httpTemplate), - fmta(s.DNSLookup), // dns lookup - fmta(s.TCPConnection), // tcp connection - fmta(s.ServerProcessing), // server processing - fmta(s.ContentTransfer), // content transfer - fmtb(s.NameLookup), // namelookup - fmtb(s.Connect), // connect - fmtb(s.StartTransfer), // starttransfer - fmtb(s.Total), // total - ) - } else { - // https + switch s.schema { + case "https": printf(colorize(httpsTemplate), fmta(s.DNSLookup), // dns lookup fmta(s.TCPConnection), // tcp connection @@ -158,6 +145,17 @@ func (s *Stat) Print() { fmtb(s.StartTransfer), // starttransfer fmtb(s.Total), // total ) + case "http": + printf(colorize(httpTemplate), + fmta(s.DNSLookup), // dns lookup + fmta(s.TCPConnection), // tcp connection + fmta(s.ServerProcessing), // server processing + fmta(s.ContentTransfer), // content transfer + fmtb(s.NameLookup), // namelookup + fmtb(s.Connect), // connect + fmtb(s.StartTransfer), // starttransfer + fmtb(s.Total), // total + ) } log.Info(). Interface("httpstat(ms)", s.Durations()). @@ -166,8 +164,9 @@ func (s *Stat) Print() { // WithHTTPStat is a wrapper of httptrace.WithClientTrace. // It records the time of each httptrace hooks. -func WithHTTPStat(ctx context.Context, s *Stat) context.Context { - return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ +func WithHTTPStat(req *http.Request, s *Stat) context.Context { + s.schema = req.URL.Scheme + return httptrace.WithClientTrace(req.Context(), &httptrace.ClientTrace{ DNSStart: func(i httptrace.DNSStartInfo) { s.dnsStart = time.Now() }, @@ -176,7 +175,7 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { s.dnsDone = time.Now() s.DNSLookup = s.dnsDone.Sub(s.dnsStart) - s.NameLookup = s.dnsDone.Sub(s.dnsStart) + s.NameLookup = s.DNSLookup }, ConnectStart: func(_, _ string) { @@ -215,12 +214,11 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { }, WroteRequest: func(info httptrace.WroteRequestInfo) { - s.serverStart = time.Now() + now := time.Now() + s.serverStart = now - // When client doesn't use DialContext or using old (before go1.7) `net` - // package, DNS/TCP/TLS hook is not called. - if s.assertSchemaHTTP() { - now := s.serverStart + // When client doesn't use DialContext, DNS/TCP/TLS hook is not called. + if s.dnsStart.IsZero() && s.tcpStart.IsZero() { s.dnsStart = now s.dnsDone = now s.tcpStart = now @@ -229,7 +227,6 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { // When connection is re-used, DNS/TCP/TLS hook is not called. if s.isReused { - now := s.serverStart s.dnsStart = now s.dnsDone = now s.tcpStart = now @@ -238,11 +235,12 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { s.tlsDone = now } - if s.isTLS { + if s.isTLS { // https return } - s.TLSHandshake = s.tcpDone.Sub(s.tcpDone) + // http + s.TLSHandshake = 0 s.Pretransfer = s.Connect }, diff --git a/hrp/step_request.go b/hrp/step_request.go index 9dbae164..41e5d022 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -315,7 +315,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err // stat HTTP request var httpStat httpstat.Stat if r.HTTPStatOn() { - ctx := httpstat.WithHTTPStat(rb.req.Context(), &httpStat) + ctx := httpstat.WithHTTPStat(rb.req, &httpStat) rb.req = rb.req.WithContext(ctx) } @@ -347,12 +347,6 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } } - if r.HTTPStatOn() { - httpStat.Finish() - stepResult.HttpStat = httpStat.Durations() - httpStat.Print() - } - // new response object respObj, err := newHttpResponseObject(r.hrpRunner.t, parser, resp) if err != nil { @@ -360,6 +354,13 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err return } + if r.HTTPStatOn() { + // resp.Body has been ReadAll + httpStat.Finish() + stepResult.HttpStat = httpStat.Durations() + httpStat.Print() + } + // add response object to step variables, could be used in teardown hooks stepVariables["hrp_step_response"] = respObj.respObjMeta diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 254cdee1..46c62cae 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -120,7 +120,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["NameLookup"], int64(0)) { + if !assert.GreaterOrEqual(t, stat["NameLookup"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["Connect"], int64(0)) { @@ -132,7 +132,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Total"], int64(10)) { + if !assert.Greater(t, stat["Total"], int64(5)) { t.Fatal() } if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { @@ -168,7 +168,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Total"], int64(10)) { + if !assert.Greater(t, stat["Total"], int64(1)) { t.Fatal() } if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { From f2dbd241245a19882da7419b0bb539c3a73f4711 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 May 2022 21:20:20 +0800 Subject: [PATCH 9/9] feat: print connected network info --- hrp/internal/httpstat/main.go | 16 +++++++++++++++- hrp/step_request.go | 16 ++++++++++++++++ hrp/step_request_test.go | 19 ++++++++----------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index e5731898..29bf464d 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -97,6 +97,9 @@ type Stat struct { // https or http schema string + + // connected network info + network, addr string } // Finish sets the time when reading response is done. @@ -131,6 +134,14 @@ func (s *Stat) Durations() map[string]int64 { } func (s *Stat) Print() { + if s.network != "" && s.addr != "" { + printf("\n%s %s: %s\n", + color.CyanString("Connected to"), + color.YellowString(s.network), + color.BlueString(s.addr), + ) + } + switch s.schema { case "https": printf(colorize(httpsTemplate), @@ -178,7 +189,10 @@ func WithHTTPStat(req *http.Request, s *Stat) context.Context { s.NameLookup = s.DNSLookup }, - ConnectStart: func(_, _ string) { + ConnectStart: func(network, addr string) { + s.network = network + s.addr = addr + s.tcpStart = time.Now() // When connecting to IP (When no DNS lookup) diff --git a/hrp/step_request.go b/hrp/step_request.go index 41e5d022..30c5988e 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/gzip" "compress/zlib" + "crypto/tls" "fmt" "io" "net/http" @@ -15,6 +16,7 @@ import ( "time" "github.com/andybalholm/brotli" + "github.com/fatih/color" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -423,8 +425,22 @@ func printRequest(req *http.Request) error { return nil } +func printf(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(color.Output, format, a...) +} + func printResponse(resp *http.Response) error { fmt.Println("==================== response ====================") + connectedVia := "plaintext" + if resp.TLS != nil { + switch resp.TLS.Version { + case tls.VersionTLS12: + connectedVia = "TLSv1.2" + case tls.VersionTLS13: + connectedVia = "TLSv1.3" + } + } + printf("%s %s\n", color.CyanString("Connected via"), color.BlueString("%s", connectedVia)) respContentType := resp.Header.Get("Content-Type") printBody := shouldPrintBody(respContentType) respDump, err := httputil.DumpResponse(resp, printBody) diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 46c62cae..1076b31d 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -135,34 +135,31 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["Total"], int64(5)) { t.Fatal() } - if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(3)) { t.Fatal() } // reuse connection stat = summary.Records[1].HttpStat - if !assert.Equal(t, stat["DNSLookup"], int64(0)) { + if !assert.Equal(t, int64(0), stat["DNSLookup"]) { t.Fatal() } - if !assert.Equal(t, stat["TCPConnection"], int64(0)) { + if !assert.Equal(t, int64(0), stat["TCPConnection"]) { t.Fatal() } - if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { + if !assert.Equal(t, int64(0), stat["TLSHandshake"]) { t.Fatal() } if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { t.Fatal() } - if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { + if !assert.Equal(t, int64(0), stat["NameLookup"]) { t.Fatal() } - if !assert.Equal(t, stat["NameLookup"], int64(0)) { + if !assert.Equal(t, int64(0), stat["Connect"]) { t.Fatal() } - if !assert.Equal(t, stat["Connect"], int64(0)) { - t.Fatal() - } - if !assert.Equal(t, stat["Pretransfer"], int64(0)) { + if !assert.Equal(t, int64(0), stat["Pretransfer"]) { t.Fatal() } if !assert.Greater(t, stat["StartTransfer"], int64(0)) { @@ -171,7 +168,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["Total"], int64(1)) { t.Fatal() } - if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(3)) { t.Fatal() } }