From 90ca598772b9356e5bfb13a7e363510de01384a1 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 21 Apr 2022 13:08:38 +0800 Subject: [PATCH 01/19] change: update project info --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 745deb07..0f7c8e75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ license = "Apache-2.0" readme = "README.md" authors = ["debugtalk "] -homepage = "https://github.com/httprunner/httprunner" +homepage = "https://httprunner.com" repository = "https://github.com/httprunner/httprunner" documentation = "https://httprunner.com/docs" From 2681d4c8157536d8ffdb3ec9719a351b6957c35d Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 11:56:31 +0800 Subject: [PATCH 02/19] change: remove hrun command --- hrp/cmd/scaffold.go | 2 +- hrp/runner.go | 1 + httprunner/cli.py | 20 -------------------- pyproject.toml | 1 - 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index 0d065839..a9e7aac9 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -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 diff --git a/hrp/runner.go b/hrp/runner.go index e73ae616..057bdabd 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -153,6 +153,7 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { // load all testcases testCases, err := loadTestCases(testcases...) if err != nil { + log.Error().Err(err).Msg("failed to load testcases") return err } diff --git a/httprunner/cli.py b/httprunner/cli.py index 1aa29eac..2257ba97 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -108,26 +108,6 @@ def main(): main_make(args.testcase_path) -def main_hrun_alias(): - """ command alias - hrun = httprunner run - """ - if len(sys.argv) == 2: - if sys.argv[1] in ["-V", "--version"]: - # hrun -V - sys.argv = ["httprunner", "-V"] - elif sys.argv[1] in ["-h", "--help"]: - pytest.main(["-h"]) - sys.exit(0) - else: - # hrun /path/to/testcase - sys.argv.insert(1, "run") - else: - sys.argv.insert(1, "run") - - main() - - def main_make_alias(): """ command alias hmake = httprunner make diff --git a/pyproject.toml b/pyproject.toml index 0f7c8e75..bdd45f2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,6 @@ coverage = "^4.5.4" [tool.poetry.scripts] httprunner = "httprunner.cli:main" -hrun = "httprunner.cli:main_hrun_alias" hmake = "httprunner.cli:main_make_alias" [build-system] From 8f659effa74241cd8cbda9eaf3ab51765f8b324c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 19:28:16 +0800 Subject: [PATCH 03/19] refactor: run tests with pytest --- docs/cmd/hrp.md | 3 ++- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 21 +++++++++++++++++++++ docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- hrp/internal/builtin/utils.go | 16 ++++++++++++++++ hrp/internal/pytest/main.go | 10 +++++++++- hrp/internal/scaffold/main.go | 8 +------- 12 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 docs/cmd/hrp_convert.md diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 1a3960a4..ebdeb949 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -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 22-Apr-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 7be66d74..506f675e 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 17-Apr-2022 +###### Auto generated by spf13/cobra on 22-Apr-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md new file mode 100644 index 00000000..b62216bb --- /dev/null +++ b/docs/cmd/hrp_convert.md @@ -0,0 +1,21 @@ +## hrp convert + +convert JSON/YAML testcases to pytest/gotest scripts + +``` +hrp convert $path... [flags] +``` + +### Options + +``` + --gotest convert to gotest scripts + -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 22-Apr-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 729b1226..7183b3a3 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 17-Apr-2022 +###### Auto generated by spf13/cobra on 22-Apr-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 5a2363be..a07f1867 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 17-Apr-2022 +###### Auto generated by spf13/cobra on 22-Apr-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 7ad8d4d4..bf7543a1 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 17-Apr-2022 +###### Auto generated by spf13/cobra on 22-Apr-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index e33d46d6..a06d5ee5 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -19,4 +19,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 17-Apr-2022 +###### Auto generated by spf13/cobra on 22-Apr-2022 diff --git a/go.mod b/go.mod index 52540f0c..49c7f16b 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index ad484835..9647213c 100644 --- a/go.sum +++ b/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= diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 7bf7a943..f328f1c4 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -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,6 +77,21 @@ func FormatResponse(raw interface{}) interface{} { return formattedResponse } +func EnsurePython3Venv(packages ...string) (string, error) { + // create python venv + home, err := os.UserHomeDir() + if err != nil { + 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 ExecCommand(cmd *exec.Cmd, cwd string) error { log.Info().Str("cmd", cmd.String()).Str("cwd", cwd).Msg("exec command") cmd.Dir = cwd diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go index 7304de99..667213d0 100644 --- a/hrp/internal/pytest/main.go +++ b/hrp/internal/pytest/main.go @@ -6,10 +6,18 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/hrp/internal/builtin" ) func RunPytest(args []string) error { - cmd := exec.Command("pytest", args...) + python3, err := builtin.EnsurePython3Venv("httprunner") + if err != nil { + return errors.Wrap(err, "ensure python venv failed") + } + + args = append([]string{"-m", "httprunner", "run"}, args...) + cmd := exec.Command(python3, args...) log.Info().Str("cmd", cmd.String()).Msg("run pytest") output, err := cmd.CombinedOutput() diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index b7c1c0ad..808f3440 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -178,13 +178,7 @@ func createPythonPlugin(projectName string) error { return errors.Wrap(err, "copy file failed") } - // create python venv - home, err := os.UserHomeDir() - if err != nil { - return errors.Wrap(err, "get user home dir failed") - } - venvDir := filepath.Join(home, ".hrp", "venv") - _, err = shared.EnsurePython3Venv(venvDir) + _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy==%s", shared.Version)) if err != nil { return errors.Wrap(err, "ensure python venv failed") } From a5a4ecaf9d070a62c567eb297ab869c1d3d00602 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 19:30:38 +0800 Subject: [PATCH 04/19] feat: hrp convert testcases to pytest scripts --- hrp/cmd/convert.go | 48 +++++++++++++++++++++++++++++++++++ hrp/convert.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++ httprunner/cli.py | 8 ------ pyproject.toml | 1 - 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 hrp/cmd/convert.go create mode 100644 hrp/convert.go diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go new file mode 100644 index 00000000..4c16b8ef --- /dev/null +++ b/hrp/cmd/convert.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "errors" + "os" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/httprunner/httprunner/hrp" +) + +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 = hrp.Convert2TestScripts("gotest", args...) + } else { + err = hrp.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)") +} diff --git a/hrp/convert.go b/hrp/convert.go new file mode 100644 index 00000000..2194dd51 --- /dev/null +++ b/hrp/convert.go @@ -0,0 +1,62 @@ +package hrp + +import ( + "os/exec" + "strings" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/hrp/internal/builtin" +) + +func Convert2TestScripts(destType string, paths ...string) error { + if destType == "gotest" { + return convert2GoTestScripts(paths...) + } else { + return convert2PyTestScripts(paths...) + } +} + +func convert2PyTestScripts(paths ...string) error { + python3, err := builtin.EnsurePython3Venv("httprunner") + if err != nil { + return errors.Wrap(err, "ensure python venv failed") + } + + args := append([]string{"-m", "httprunner", "make"}, paths...) + cmd := exec.Command(python3, args...) + log.Info().Str("cmd", cmd.String()).Msg("convert to pytest scripts") + + output, err := cmd.CombinedOutput() + if err != nil { + return errors.Wrap(err, "pytest running failed") + } + out := strings.TrimSpace(string(output)) + println(out) + + log.Info().Msg("convert to pytest scripts successfully") + return nil +} + +func convert2GoTestScripts(paths ...string) error { + log.Warn().Msg("convert to gotest scripts is not supported yet") + // report event + // sdk.SendEvent(sdk.EventTracking{ + // Category: "Convert", + // Action: fmt.Sprintf("hrp convert to %s", destType), + // }) + + // var testCasePaths []ITestCase + // for _, path := range paths { + // testCasePath := TestCasePath(path) + // testCasePaths = append(testCasePaths, &testCasePath) + // } + + // _, err := loadTestCases(testCasePaths...) + // if err != nil { + // log.Error().Err(err).Msg("failed to load testcases") + // return err + // } + return nil +} diff --git a/httprunner/cli.py b/httprunner/cli.py index 2257ba97..87b16229 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -108,13 +108,5 @@ def main(): main_make(args.testcase_path) -def main_make_alias(): - """ command alias - hmake = httprunner make - """ - sys.argv.insert(1, "make") - main() - - if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml index bdd45f2b..9f3f74a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,6 @@ coverage = "^4.5.4" [tool.poetry.scripts] httprunner = "httprunner.cli:main" -hmake = "httprunner.cli:main_make_alias" [build-system] requires = ["poetry>=1.0.0"] From 8c071f57c69a0b9c46b212a4622c9fdbac864f70 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 20:18:01 +0800 Subject: [PATCH 05/19] feat: print output with colors --- hrp/convert.go | 16 +-------------- hrp/internal/builtin/utils.go | 37 +++++++++++++++++++++++++++-------- hrp/internal/pytest/main.go | 16 +-------------- hrp/internal/scaffold/main.go | 8 ++++---- hrp/runner_test.go | 9 +++++---- 5 files changed, 40 insertions(+), 46 deletions(-) diff --git a/hrp/convert.go b/hrp/convert.go index 2194dd51..828d3794 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -1,9 +1,6 @@ package hrp import ( - "os/exec" - "strings" - "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -25,18 +22,7 @@ func convert2PyTestScripts(paths ...string) error { } args := append([]string{"-m", "httprunner", "make"}, paths...) - cmd := exec.Command(python3, args...) - log.Info().Str("cmd", cmd.String()).Msg("convert to pytest scripts") - - output, err := cmd.CombinedOutput() - if err != nil { - return errors.Wrap(err, "pytest running failed") - } - out := strings.TrimSpace(string(output)) - println(out) - - log.Info().Msg("convert to pytest scripts successfully") - return nil + return builtin.ExecCommand(python3, args...) } func convert2GoTestScripts(paths ...string) error { diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index f328f1c4..a7ab624d 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -92,16 +92,37 @@ func EnsurePython3Venv(packages ...string) (string, error) { return python3, nil } -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 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).Str("output", out).Msg("exec command failed") - } else if len(out) != 0 { - log.Info().Str("output", out).Msg("exec command success") + 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 + + err := cmd.Run() + if err != nil { + log.Error().Err(err).Msg("exec command failed") + return err + } + return err } diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go index 667213d0..5b77eeef 100644 --- a/hrp/internal/pytest/main.go +++ b/hrp/internal/pytest/main.go @@ -1,11 +1,7 @@ package pytest import ( - "os/exec" - "strings" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/hrp/internal/builtin" ) @@ -17,15 +13,5 @@ func RunPytest(args []string) error { } args = append([]string{"-m", "httprunner", "run"}, args...) - cmd := exec.Command(python3, args...) - log.Info().Str("cmd", cmd.String()).Msg("run pytest") - - output, err := cmd.CombinedOutput() - if err != nil { - return errors.Wrap(err, "pytest running failed") - } - out := strings.TrimSpace(string(output)) - println(out) - - return nil + return builtin.ExecCommand(python3, args...) } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 808f3440..cb05cbe0 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -133,7 +133,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 +149,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 } diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 9510a975..54c82f05 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -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) } From be0b0cc263813488fb881872805b2c1cc5542a3c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 20:28:11 +0800 Subject: [PATCH 06/19] change: update docs --- README.en.md | 2 +- README.md | 2 +- docs/CHANGELOG.md | 2 ++ docs/cmd/hrp.md | 2 +- docs/cmd/hrp_convert.md | 2 +- hrp/cmd/root.go | 2 +- httprunner/compat.py | 4 ++-- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.en.md b/README.en.md index 47eab79d..511a02cd 100644 --- a/README.en.md +++ b/README.en.md @@ -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] diff --git a/README.md b/README.md index c623efa0..b701e953 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 504da451..e5781be5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ - 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 diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index ebdeb949..935d1e7a 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -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 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index b62216bb..5b4d2b89 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -9,7 +9,7 @@ hrp convert $path... [flags] ### Options ``` - --gotest convert to gotest scripts + --gotest convert to gotest scripts (TODO) -h, --help help for convert --pytest convert to pytest scripts (default true) ``` diff --git a/hrp/cmd/root.go b/hrp/cmd/root.go index 9128d37e..c19775d9 100644 --- a/hrp/cmd/root.go +++ b/hrp/cmd/root.go @@ -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" { diff --git a/httprunner/compat.py b/httprunner/compat.py index fb78b39b..457f1686 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -301,13 +301,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, From 1b6c7c9a93d8a086fdffd23179c3392fc79c15b0 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 20:45:19 +0800 Subject: [PATCH 07/19] change: update GA events --- hrp/convert.go | 12 ++++++++++++ hrp/internal/pytest/main.go | 6 ++++++ httprunner/cli.py | 28 ++++++++++++++++++++++++++++ pyproject.toml | 2 ++ 4 files changed, 48 insertions(+) diff --git a/hrp/convert.go b/hrp/convert.go index 828d3794..fd30ac1e 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -5,6 +5,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/hrp/internal/sdk" ) func Convert2TestScripts(destType string, paths ...string) error { @@ -16,6 +17,11 @@ func Convert2TestScripts(destType string, paths ...string) error { } func convert2PyTestScripts(paths ...string) error { + sdk.SendEvent(sdk.EventTracking{ + Category: "ConvertTests", + Action: "hrp convert --pytest", + }) + python3, err := builtin.EnsurePython3Venv("httprunner") if err != nil { return errors.Wrap(err, "ensure python venv failed") @@ -27,6 +33,12 @@ func convert2PyTestScripts(paths ...string) error { func convert2GoTestScripts(paths ...string) error { log.Warn().Msg("convert to gotest scripts is not supported yet") + + sdk.SendEvent(sdk.EventTracking{ + Category: "ConvertTests", + Action: "hrp convert --gotest", + }) + // report event // sdk.SendEvent(sdk.EventTracking{ // Category: "Convert", diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go index 5b77eeef..5bbc0555 100644 --- a/hrp/internal/pytest/main.go +++ b/hrp/internal/pytest/main.go @@ -4,9 +4,15 @@ import ( "github.com/pkg/errors" "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/hrp/internal/sdk" ) func RunPytest(args []string) error { + sdk.SendEvent(sdk.EventTracking{ + Category: "RunAPITests", + Action: "hrp pytest", + }) + python3, err := builtin.EnsurePython3Venv("httprunner") if err != nil { return errors.Wrap(err, "ensure python venv failed") diff --git a/httprunner/cli.py b/httprunner/cli.py index 87b16229..1aa29eac 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -108,5 +108,33 @@ def main(): main_make(args.testcase_path) +def main_hrun_alias(): + """ command alias + hrun = httprunner run + """ + if len(sys.argv) == 2: + if sys.argv[1] in ["-V", "--version"]: + # hrun -V + sys.argv = ["httprunner", "-V"] + elif sys.argv[1] in ["-h", "--help"]: + pytest.main(["-h"]) + sys.exit(0) + else: + # hrun /path/to/testcase + sys.argv.insert(1, "run") + else: + sys.argv.insert(1, "run") + + main() + + +def main_make_alias(): + """ command alias + hmake = httprunner make + """ + sys.argv.insert(1, "make") + main() + + if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml index 9f3f74a7..0f7c8e75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,8 @@ coverage = "^4.5.4" [tool.poetry.scripts] httprunner = "httprunner.cli:main" +hrun = "httprunner.cli:main_hrun_alias" +hmake = "httprunner.cli:main_make_alias" [build-system] requires = ["poetry>=1.0.0"] From 4bed3af166e8b446842b6afeb527f5d46ffb1098 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 20:53:14 +0800 Subject: [PATCH 08/19] change: update logs --- hrp/convert.go | 3 +-- hrp/internal/pytest/main.go | 4 +--- hrp/internal/scaffold/main.go | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/hrp/convert.go b/hrp/convert.go index fd30ac1e..92f424f8 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -1,7 +1,6 @@ package hrp import ( - "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/hrp/internal/builtin" @@ -24,7 +23,7 @@ func convert2PyTestScripts(paths ...string) error { python3, err := builtin.EnsurePython3Venv("httprunner") if err != nil { - return errors.Wrap(err, "ensure python venv failed") + return err } args := append([]string{"-m", "httprunner", "make"}, paths...) diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go index 5bbc0555..a70ead1e 100644 --- a/hrp/internal/pytest/main.go +++ b/hrp/internal/pytest/main.go @@ -1,8 +1,6 @@ package pytest import ( - "github.com/pkg/errors" - "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/httprunner/httprunner/hrp/internal/sdk" ) @@ -15,7 +13,7 @@ func RunPytest(args []string) error { python3, err := builtin.EnsurePython3Venv("httprunner") if err != nil { - return errors.Wrap(err, "ensure python venv failed") + return err } args = append([]string{"-m", "httprunner", "run"}, args...) diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index cb05cbe0..9ff2fe79 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -180,7 +180,7 @@ func createPythonPlugin(projectName string) error { _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy==%s", shared.Version)) if err != nil { - return errors.Wrap(err, "ensure python venv failed") + return err } return nil From 1600cf83645750908da3640dc8d7a2bdb3902642 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 21:35:38 +0800 Subject: [PATCH 09/19] change: init logger with INFO level --- .github/workflows/smoketest.yml | 4 ++++ httprunner/cli.py | 4 +++- httprunner/runner.py | 8 +++++--- httprunner/utils.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index 1d629d59..3dd0afb3 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -73,3 +73,7 @@ jobs: - name: Run smoketest - 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/ diff --git a/httprunner/cli.py b/httprunner/cli.py index 1aa29eac..c803dd0f 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -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() @@ -57,6 +57,8 @@ def main_run(extra_args) -> enum.IntEnum: def main(): """ 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" diff --git a/httprunner/runner.py b/httprunner/runner.py index 53d4786c..fe535098 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -12,6 +12,7 @@ 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 @@ -19,7 +20,7 @@ from httprunner.loader import load_project_meta 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 +44,7 @@ class SessionRunner(object): __log_path: Text = "" def __init(self): + init_logger() self.__config = self.config.struct() self.__session_variables = {} self.__start_at = 0 @@ -188,6 +190,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 +203,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 diff --git a/httprunner/utils.py b/httprunner/utils.py index 1acfca25..9c0306f6 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -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 @@ -320,3 +321,12 @@ def gen_cartesian_product(*args: List[Dict]) -> List[Dict]: product_list.append(product_item_dict) return product_list + + +LOGGER_FORMAT = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {message}" + + +def init_logger(): + # set log level to INFO + logger.remove() + logger.add(sys.stderr, format=LOGGER_FORMAT, level="INFO") From 2a14da0e583174dcf215118522561710e123eaa1 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 22:54:04 +0800 Subject: [PATCH 10/19] fix: assertion equals --- hrp/internal/builtin/assertion.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/hrp/internal/builtin/assertion.go b/hrp/internal/builtin/assertion.go index 01eb3157..96fd4310 100644 --- a/hrp/internal/builtin/assertion.go +++ b/hrp/internal/builtin/assertion.go @@ -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)) { From 19772076a2ae1ca800a60f23c58d3ada406e4919 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 23:06:01 +0800 Subject: [PATCH 11/19] change: upgrade black --- poetry.lock | 194 +++++++++++++++++++------------------------------ pyproject.toml | 3 +- 2 files changed, 77 insertions(+), 120 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7b9eea4b..41ec159b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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"}, diff --git a/pyproject.toml b/pyproject.toml index 0f7c8e75..b1a5bf99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 From 86d78cd1fa329215996c090be50cbc913e86dde9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 23:32:32 +0800 Subject: [PATCH 12/19] fix: smoketest --- .github/workflows/smoketest.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index 3dd0afb3..e08ea6e5 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -66,11 +66,11 @@ 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 From 12f92c6108e42ab516e0b3092cd72c46045bf724 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 22 Apr 2022 23:45:16 +0800 Subject: [PATCH 13/19] change: remove unused sentry exception --- httprunner/make.py | 2 -- httprunner/parser.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/httprunner/make.py b/httprunner/make.py index e540e450..744b61bd 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -6,7 +6,6 @@ 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, @@ -169,7 +168,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: diff --git a/httprunner/parser.py b/httprunner/parser.py index 7bf6e9ef..b575cd42 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -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 @@ -144,7 +143,7 @@ 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 [] From 7eaa8a4ac0242f9c5690a7e2aa7a973f2612104a Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 23 Apr 2022 15:04:35 +0800 Subject: [PATCH 14/19] change: remove testsuite --- httprunner/loader.py | 45 +++++--------- httprunner/make.py | 129 +++++++++++----------------------------- httprunner/make_test.py | 59 ++++-------------- httprunner/models.py | 29 +++------ 4 files changed, 71 insertions(+), 191 deletions(-) diff --git a/httprunner/loader.py b/httprunner/loader.py index 6c81fd82..49dc849a 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -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 :] diff --git a/httprunner/make.py b/httprunner/make.py index 744b61bd..962bb30a 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -8,14 +8,21 @@ import jinja2 from loguru import logger 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 """ @@ -71,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[len("./") :] elif path.startswith(".\\"): # Windows, hrun .\\test.yml - path = path[len(".\\"):] + path = path[len(".\\") :] path = ensure_path_sep(path) project_meta = load_project_meta(path) @@ -92,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 @@ -133,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 @@ -431,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: @@ -526,13 +479,13 @@ def __make(tests_path: Text): if "config" not in test_content: logger.warning( - f"Invalid testcase/testsuite file: {test_file}\n" + f"Invalid testcase file: {test_file}\n" f"reason: 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 @@ -540,33 +493,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]: @@ -594,10 +533,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" diff --git a/httprunner/make_test.py b/httprunner/make_test.py index b6a40f20..f3a80325 100644 --- a/httprunner/make_test.py +++ b/httprunner/make_test.py @@ -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", diff --git a/httprunner/models.py b/httprunner/models.py index 8c8ced42..d43a3a94 100644 --- a/httprunner/models.py +++ b/httprunner/models.py @@ -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 From 418a16fcee7b8a64ae933fca86e6e64ba42913d0 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 23 Apr 2022 15:05:21 +0800 Subject: [PATCH 15/19] refactor: reformat all code with black --- examples/demo-with-py-plugin/debugtalk.py | 2 +- examples/httpbin/debugtalk.py | 10 ++- examples/postman_echo/conftest.py | 3 +- .../request_methods/demo_testsuite.yml | 22 ------- .../scaffold/templates/plugin/debugtalk.py | 2 +- httprunner/builtin/functions.py | 12 ++-- httprunner/cli.py | 11 ++-- httprunner/client.py | 3 +- httprunner/client_test.py | 12 ++-- httprunner/compat.py | 23 ++----- httprunner/compat_test.py | 7 +-- httprunner/config.py | 7 +-- httprunner/exceptions.py | 3 +- httprunner/ext/uploader/__init__.py | 10 ++- httprunner/loader_test.py | 6 +- httprunner/make.py | 7 +-- httprunner/parser.py | 44 +++++++------ httprunner/parser_test.py | 1 - httprunner/response.py | 26 ++++---- httprunner/response_test.py | 16 ++--- httprunner/runner.py | 25 +++++--- httprunner/step.py | 7 ++- httprunner/step_request.py | 34 +++++----- httprunner/step_request_test.py | 5 +- httprunner/step_testcase.py | 8 +-- httprunner/step_testcase_test.py | 13 ++-- httprunner/utils.py | 63 +++++++++---------- 27 files changed, 169 insertions(+), 213 deletions(-) delete mode 100644 examples/postman_echo/request_methods/demo_testsuite.yml diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py index 743e7f62..d30e6e63 100644 --- a/examples/demo-with-py-plugin/debugtalk.py +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -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) diff --git a/examples/httpbin/debugtalk.py b/examples/httpbin/debugtalk.py index ef78aee0..f949e84b 100644 --- a/examples/httpbin/debugtalk.py +++ b/examples/httpbin/debugtalk.py @@ -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: diff --git a/examples/postman_echo/conftest.py b/examples/postman_echo/conftest.py index 88a30859..c894700f 100644 --- a/examples/postman_echo/conftest.py +++ b/examples/postman_echo/conftest.py @@ -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) diff --git a/examples/postman_echo/request_methods/demo_testsuite.yml b/examples/postman_echo/request_methods/demo_testsuite.yml deleted file mode 100644 index 4c367a6b..00000000 --- a/examples/postman_echo/request_methods/demo_testsuite.yml +++ /dev/null @@ -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 diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py index 743e7f62..d30e6e63 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -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) diff --git a/httprunner/builtin/functions.py b/httprunner/builtin/functions.py index 2a7c68ca..00f964fd 100644 --- a/httprunner/builtin/functions.py +++ b/httprunner/builtin/functions.py @@ -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) diff --git a/httprunner/cli.py b/httprunner/cli.py index c803dd0f..b26dc5be 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -55,8 +55,7 @@ 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__) @@ -111,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"]: @@ -131,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() diff --git a/httprunner/client.py b/httprunner/client.py index 8ac38f0e..8e45425c 100644 --- a/httprunner/client.py +++ b/httprunner/client.py @@ -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" diff --git a/httprunner/client_test.py b/httprunner/client_test.py index 2ea5a6b1..467d4246 100644 --- a/httprunner/client_test.py +++ b/httprunner/client_test.py @@ -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) diff --git a/httprunner/compat.py b/httprunner/compat.py index 457f1686..c7352f6a 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -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") @@ -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("/")) diff --git a/httprunner/compat_test.py b/httprunner/compat_test.py index 661d4ad8..391133a1 100644 --- a/httprunner/compat_test.py +++ b/httprunner/compat_test.py @@ -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"' diff --git a/httprunner/config.py b/httprunner/config.py index 0f372975..ea594b5f 100644 --- a/httprunner/config.py +++ b/httprunner/config.py @@ -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: diff --git a/httprunner/exceptions.py b/httprunner/exceptions.py index 6559fd87..495922d7 100644 --- a/httprunner/exceptions.py +++ b/httprunner/exceptions.py @@ -85,5 +85,4 @@ class TestcaseNotFound(NotFoundError): class SummaryEmpty(MyBaseError): - """ test result summary data is empty - """ + """test result summary data is empty""" diff --git a/httprunner/ext/uploader/__init__.py b/httprunner/ext/uploader/__init__.py index 95aad33a..c284f7b3 100644 --- a/httprunner/ext/uploader/__init__.py +++ b/httprunner/ext/uploader/__init__.py @@ -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 diff --git a/httprunner/loader_test.py b/httprunner/loader_test.py index 95449f43..7b09d87b 100644 --- a/httprunner/loader_test.py +++ b/httprunner/loader_test.py @@ -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, {}) diff --git a/httprunner/make.py b/httprunner/make.py index 962bb30a..c2b3d3b2 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -78,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) @@ -479,8 +479,7 @@ def __make(tests_path: Text): if "config" not in test_content: logger.warning( - f"Invalid testcase 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): diff --git a/httprunner/parser.py b/httprunner/parser.py index b575cd42..e1adb7be 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -20,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" @@ -36,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 @@ -49,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 @@ -115,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 @@ -148,8 +150,7 @@ def regex_findall_functions(content: Text) -> List[Text]: 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: @@ -169,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 @@ -220,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 @@ -245,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: @@ -295,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. @@ -402,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 @@ -475,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 @@ -583,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: diff --git a/httprunner/parser_test.py b/httprunner/parser_test.py index eaed0f78..2ac75422 100644 --- a/httprunner/parser_test.py +++ b/httprunner/parser_test.py @@ -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") diff --git a/httprunner/response.py b/httprunner/response.py index 7ee2006f..bac5632b 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -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): diff --git a/httprunner/response_test.py b/httprunner/response_test.py index 8b292f46..7ab7a6fb 100644 --- a/httprunner/response_test.py +++ b/httprunner/response_test.py @@ -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, ) diff --git a/httprunner/runner.py b/httprunner/runner.py index fe535098..af69ec61 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -17,8 +17,15 @@ 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 LOGGER_FORMAT, init_logger, merge_variables @@ -55,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() @@ -87,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( @@ -176,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) diff --git a/httprunner/step.py b/httprunner/step.py index c349de3e..a5221f16 100644 --- a/httprunner/step.py +++ b/httprunner/step.py @@ -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[ diff --git a/httprunner/step_request.py b/httprunner/step_request.py index a681e448..9299cba2 100644 --- a/httprunner/step_request.py +++ b/httprunner/step_request.py @@ -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) diff --git a/httprunner/step_request_test.py b/httprunner/step_request_test.py index 7a54e494..58164acc 100644 --- a/httprunner/step_request_test.py +++ b/httprunner/step_request_test.py @@ -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() diff --git a/httprunner/step_testcase.py b/httprunner/step_testcase.py index b61285c5..c011168f 100644 --- a/httprunner/step_testcase.py +++ b/httprunner/step_testcase.py @@ -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: diff --git a/httprunner/step_testcase_test.py b/httprunner/step_testcase_test.py index 27a7301c..9f64e0c0 100644 --- a/httprunner/step_testcase_test.py +++ b/httprunner/step_testcase_test.py @@ -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) diff --git a/httprunner/utils.py b/httprunner/utils.py index 9c0306f6..3a42f3b3 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -32,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" @@ -53,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): @@ -70,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 @@ -87,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 @@ -122,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 @@ -157,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. @@ -202,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 @@ -244,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: @@ -257,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: @@ -283,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 From c4426be6ddc8b8e515d0050b1d6f6c3b5e58ca0c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 23 Apr 2022 23:56:47 +0800 Subject: [PATCH 16/19] refactor: relocate convert --- hrp/boomer.go | 2 +- hrp/cmd/convert.go | 6 +- hrp/convert.go | 59 --------------- hrp/internal/convert/main.go | 118 +++++++++++++++++++++++++++++ hrp/internal/convert/testcase.tmpl | 38 ++++++++++ hrp/runner.go | 2 +- hrp/runner_test.go | 8 +- hrp/testcase.go | 2 +- 8 files changed, 166 insertions(+), 69 deletions(-) delete mode 100644 hrp/convert.go create mode 100644 hrp/internal/convert/main.go create mode 100644 hrp/internal/convert/testcase.tmpl diff --git a/hrp/boomer.go b/hrp/boomer.go index d639f981..271f00d7 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -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) diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 4c16b8ef..7ec89187 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -7,7 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/hrp/internal/convert" ) var convertCmd = &cobra.Command{ @@ -24,9 +24,9 @@ var convertCmd = &cobra.Command{ var err error if gotestFlag { - err = hrp.Convert2TestScripts("gotest", args...) + err = convert.Convert2TestScripts("gotest", args...) } else { - err = hrp.Convert2TestScripts("pytest", args...) + err = convert.Convert2TestScripts("pytest", args...) } if err != nil { log.Error().Err(err).Msg("convert test scripts failed") diff --git a/hrp/convert.go b/hrp/convert.go deleted file mode 100644 index 92f424f8..00000000 --- a/hrp/convert.go +++ /dev/null @@ -1,59 +0,0 @@ -package hrp - -import ( - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/sdk" -) - -func Convert2TestScripts(destType string, paths ...string) error { - if destType == "gotest" { - return convert2GoTestScripts(paths...) - } else { - return convert2PyTestScripts(paths...) - } -} - -func convert2PyTestScripts(paths ...string) error { - sdk.SendEvent(sdk.EventTracking{ - Category: "ConvertTests", - Action: "hrp convert --pytest", - }) - - 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") - - sdk.SendEvent(sdk.EventTracking{ - Category: "ConvertTests", - Action: "hrp convert --gotest", - }) - - // report event - // sdk.SendEvent(sdk.EventTracking{ - // Category: "Convert", - // Action: fmt.Sprintf("hrp convert to %s", destType), - // }) - - // var testCasePaths []ITestCase - // for _, path := range paths { - // testCasePath := TestCasePath(path) - // testCasePaths = append(testCasePaths, &testCasePath) - // } - - // _, err := loadTestCases(testCasePaths...) - // if err != nil { - // log.Error().Err(err).Msg("failed to load testcases") - // return err - // } - return nil -} diff --git a/hrp/internal/convert/main.go b/hrp/internal/convert/main.go new file mode 100644 index 00000000..a52a0fab --- /dev/null +++ b/hrp/internal/convert/main.go @@ -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 +} diff --git a/hrp/internal/convert/testcase.tmpl b/hrp/internal/convert/testcase.tmpl new file mode 100644 index 00000000..783a1bc3 --- /dev/null +++ b/hrp/internal/convert/testcase.tmpl @@ -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() diff --git a/hrp/runner.go b/hrp/runner.go index 057bdabd..d21b8beb 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -151,7 +151,7 @@ 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 diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 54c82f05..25eaca68 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -203,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() } @@ -213,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() } @@ -223,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() } @@ -235,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() } diff --git a/hrp/testcase.go b/hrp/testcase.go index 815480d7..ce6a8106 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -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 { From b2d07d7f34bac2ebe6d6f444691ca783559e3e02 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 24 Apr 2022 18:51:28 +0800 Subject: [PATCH 17/19] fix: add cmd dir path to PATH --- hrp/internal/builtin/utils.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index a7ab624d..de2486a8 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -117,6 +117,13 @@ func ExecCommand(cmdName string, args ...string) error { 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") From 85476f27f2bda8767bb4904237b3f5a3d78b4ae5 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 24 Apr 2022 20:55:43 +0800 Subject: [PATCH 18/19] bump version to v4.0.0-beta --- docs/CHANGELOG.md | 6 +- 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 | 2 +- examples/demo-with-py-plugin/debugtalk.py | 2 +- 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 +- .../demo_testsuite_yml/__init__.py | 1 - .../request_with_functions_test.py | 86 ------------------- .../request_with_testcase_reference_test.py | 65 -------------- .../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 | 2 +- .../scaffold/templates/plugin/debugtalk.py | 2 +- .../testcases/demo_ref_testcase_test.py | 2 +- .../templates/testcases/demo_requests_test.py | 2 +- hrp/internal/version/init.go | 2 +- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- scripts/install.sh | 2 +- 30 files changed, 31 insertions(+), 179 deletions(-) delete mode 100644 examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py delete mode 100644 examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py delete mode 100644 examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e5781be5..d6f3369e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # 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 @@ -165,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 diff --git a/examples/data/a_b_c/T1_test.py b/examples/data/a_b_c/T1_test.py index db722b78..d3273df7 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 eec4401b..a225b4cb 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 f99a1321..dbb37554 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -42,7 +42,7 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-alpha" + return "v4.0.0-beta" } func main() { diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py index d30e6e63..180725e0 100644 --- a/examples/demo-with-py-plugin/debugtalk.py +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -6,7 +6,7 @@ import funppy def get_httprunner_version(): - return "v4.0.0-alpha" + return "v4.0.0-beta" def sleep(n_secs): diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py index eec02f1f..d13c87ac 100644 --- a/examples/httpbin/basic_test.py +++ b/examples/httpbin/basic_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: basic.yml diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py index 0a696d8f..96302d35 100644 --- a/examples/httpbin/hooks_test.py +++ b/examples/httpbin/hooks_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: hooks.yml diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py index d00e0e22..a43f5e75 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: load_image.yml diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py index f9ef8406..394ecbb6 100644 --- a/examples/httpbin/upload_test.py +++ b/examples/httpbin/upload_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: upload.yml diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py index fe1c2b76..ae948113 100644 --- a/examples/httpbin/validate_test.py +++ b/examples/httpbin/validate_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: validate.yml diff --git a/examples/postman_echo/cookie_manipulation/hardcode_test.py b/examples/postman_echo/cookie_manipulation/hardcode_test.py index b69b0e40..f8d58a4d 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 v3.1.7 +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 77347c7a..371a5372 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 v3.1.7 +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: cookie_manipulation/set_delete_cookies.yml diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py b/examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py deleted file mode 100644 index 70cfba53..00000000 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# NOTICE: Generated By HttpRunner. DO NOT EDIT! diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py deleted file mode 100644 index 97593703..00000000 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py +++ /dev/null @@ -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() diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py deleted file mode 100644 index 54d76e41..00000000 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py +++ /dev/null @@ -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() diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index ba492426..a2b49836 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 90f14f36..8688765f 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 5a538af5..5c6271a8 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 71305153..6a55b63a 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 61d9382b..0c7c0f77 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 d2f58894..2ad1b58f 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 eaa20726..26fa76bc 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # 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 f99a1321..dbb37554 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -42,7 +42,7 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-alpha" + return "v4.0.0-beta" } func main() { diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py index d30e6e63..180725e0 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -6,7 +6,7 @@ import funppy def get_httprunner_version(): - return "v4.0.0-alpha" + return "v4.0.0-beta" def sleep(n_secs): 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 e8707a57..deae0a81 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: testcases/demo_ref_testcase.yml diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py index 526961e3..5ce8a6d8 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-alpha +# NOTE: Generated By HttpRunner v4.0.0-beta # FROM: testcases/demo_requests.yml diff --git a/hrp/internal/version/init.go b/hrp/internal/version/init.go index 720fe5c6..ca433250 100644 --- a/hrp/internal/version/init.go +++ b/hrp/internal/version/init.go @@ -1,3 +1,3 @@ package version -const VERSION = "v4.0.0-alpha" +const VERSION = "v4.0.0-beta" diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 5c6dcfe5..1329604f 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -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 diff --git a/pyproject.toml b/pyproject.toml index b1a5bf99..9c098cf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [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" diff --git a/scripts/install.sh b/scripts/install.sh index d26b93b6..dfc11491 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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 From ce60dba5f9fe03897f93a383688f7e504e66e56f Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 24 Apr 2022 21:19:48 +0800 Subject: [PATCH 19/19] feat: support force to overwrite existing project with --force/-f flag in hrp startproject --- 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 | 3 ++- examples/demo-with-go-plugin/plugin/go.mod | 2 +- examples/demo-with-go-plugin/plugin/go.sum | 4 ++-- hrp/cmd/scaffold.go | 4 +++- hrp/internal/scaffold/examples_test.go | 10 +++------- hrp/internal/scaffold/main.go | 23 ++++++++++++++-------- 12 files changed, 32 insertions(+), 26 deletions(-) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 935d1e7a..4a0c55d0 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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 506f675e..ebc6bf4c 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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 5b4d2b89..6ec6b49e 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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 7183b3a3..0d15ec54 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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index a07f1867..bc0c2437 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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index bf7543a1..d7581b4c 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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index a06d5ee5..3402b1c9 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -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 22-Apr-2022 +###### Auto generated by spf13/cobra on 24-Apr-2022 diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod index a8aafa0f..941628d8 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.2 // indirect +require github.com/httprunner/funplugin v0.4.3 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum index 85aa768d..93a4421a 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.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= diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index a9e7aac9..5f9eb945 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -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") diff --git a/hrp/internal/scaffold/examples_test.go b/hrp/internal/scaffold/examples_test.go index 22daa768..3ec85cb5 100644 --- a/hrp/internal/scaffold/examples_test.go +++ b/hrp/internal/scaffold/examples_test.go @@ -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() } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 9ff2fe79..92ff9ddf 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -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