diff --git a/.gitignore b/.gitignore index 5d528d38..9684a85b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,10 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Dependency directories (remove the comment below to include it) -# vendor/ - +# system or IDE generated files __debug_bin .vscode/ .idea/ -__pycache__ .DS_Store *.bak @@ -27,3 +24,9 @@ output/ # built plugins debugtalk.bin debugtalk.so + +# python files +.venv +__pycache__ +.pyc +dist diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 632789ae..e2f73a2a 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -33,4 +33,4 @@ Copyright 2021 debugtalk * [hrp run](hrp_run.md) - run API test * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 15-Mar-2022 +###### Auto generated by spf13/cobra on 17-Mar-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 577deac7..aeae7f46 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Mar-2022 +###### Auto generated by spf13/cobra on 17-Mar-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 05d36e44..34ff04ee 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -23,4 +23,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Mar-2022 +###### Auto generated by spf13/cobra on 17-Mar-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 5748b4eb..f827c16e 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -34,4 +34,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Mar-2022 +###### Auto generated by spf13/cobra on 17-Mar-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 20b3b0cf..3c538379 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -16,4 +16,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Mar-2022 +###### Auto generated by spf13/cobra on 17-Mar-2022 diff --git a/go.mod b/go.mod index 7a262f5c..a693006e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/andybalholm/brotli v1.0.4 github.com/denisbrodbeck/machineid v1.0.1 github.com/google/uuid v1.3.0 - github.com/httprunner/funplugin v0.3.0 + github.com/httprunner/funplugin v0.3.1 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 d835192c..749e8595 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,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.3.0 h1:qCf8uwO6BGctq1e95KH4gXSJkUkq+q1oRnBJnQy9Fr4= -github.com/httprunner/funplugin v0.3.0/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.3.1 h1:XnaAg1Cpunzo9Q9ZADuf/t5JS7qP42a/o/A74P2VQNk= +github.com/httprunner/funplugin v0.3.1/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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= diff --git a/plugin.go b/plugin.go new file mode 100644 index 00000000..32ddf4b3 --- /dev/null +++ b/plugin.go @@ -0,0 +1,116 @@ +package hrp + +import ( + "fmt" + "os" + "os/signal" + "path/filepath" + "syscall" + + "github.com/httprunner/funplugin" + "github.com/httprunner/hrp/internal/ga" + "github.com/rs/zerolog/log" +) + +const ( + goPluginFile = "debugtalk.so" // built from go plugin + hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin + hashicorpPyPluginFile = "debugtalk.py" // used for hashicorp python plugin +) + +func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, err error) { + // plugin file not found + if path == "" { + return nil, nil + } + pluginPath, err := locatePlugin(path) + if err != nil { + return nil, nil + } + + // found plugin file + plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn)) + if err != nil { + log.Error().Err(err).Msgf("init plugin failed: %s", pluginPath) + return + } + + // catch Interrupt and SIGTERM signals to ensure plugin quitted + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + plugin.Quit() + os.Exit(0) + }() + + // report event for initializing plugin + event := ga.EventTracking{ + Category: "InitPlugin", + Action: fmt.Sprintf("Init %s plugin", plugin.Type()), + } + if err != nil { + event.Value = 1 // failed + } + go ga.SendEvent(event) + + return +} + +func locatePlugin(path string) (pluginPath string, err error) { + // priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so) + + pluginPath, err = locateFile(path, hashicorpGoPluginFile) + if err == nil { + return + } + + pluginPath, err = locateFile(path, hashicorpPyPluginFile) + if err == nil { + return + } + + pluginPath, err = locateFile(path, goPluginFile) + if err == nil { + return + } + + return "", fmt.Errorf("plugin file not found") +} + +// locateFile searches destFile upward recursively until current +// working directory or system root dir. +func locateFile(startPath string, destFile string) (string, error) { + stat, err := os.Stat(startPath) + if os.IsNotExist(err) { + return "", err + } + + var startDir string + if stat.IsDir() { + startDir = startPath + } else { + startDir = filepath.Dir(startPath) + } + startDir, _ = filepath.Abs(startDir) + + // convention over configuration + pluginPath := filepath.Join(startDir, destFile) + if _, err := os.Stat(pluginPath); err == nil { + return pluginPath, nil + } + + // current working directory + cwd, _ := os.Getwd() + if startDir == cwd { + return "", fmt.Errorf("searched to CWD, plugin file not found") + } + + // system root dir + parentDir, _ := filepath.Abs(filepath.Dir(startDir)) + if parentDir == startDir { + return "", fmt.Errorf("searched to system root dir, plugin file not found") + } + + return locateFile(parentDir, destFile) +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 00000000..ce68152d --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,53 @@ +package hrp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocateFile(t *testing.T) { + // specify target file path + _, err := locateFile("examples/plugin/debugtalk.go", "debugtalk.go") + if !assert.Nil(t, err) { + t.Fail() + } + + // specify path with the same dir + _, err = locateFile("examples/plugin/hashicorp.go", "debugtalk.go") + if !assert.Nil(t, err) { + t.Fail() + } + + // specify target file path dir + _, err = locateFile("examples/plugin/", "debugtalk.go") + if !assert.Nil(t, err) { + t.Fail() + } + + // specify wrong path + _, err = locateFile("examples", "debugtalk.go") + if !assert.Error(t, err) { + t.Fail() + } + _, err = locateFile("examples/demo.json", "debugtalk.go") + if !assert.Error(t, err) { + t.Fail() + } + _, err = locateFile(".", "debugtalk.go") + if !assert.Error(t, err) { + t.Fail() + } + _, err = locateFile("/abc", "debugtalk.go") + if !assert.Error(t, err) { + t.Fail() + } +} + +func TestLocatePlugin(t *testing.T) { + // specify target plugin path + _, err := locatePlugin("examples/plugin/debugtalk.py") + if !assert.Nil(t, err) { + t.Fail() + } +} diff --git a/runner.go b/runner.go index 3619f849..fadd503c 100644 --- a/runner.go +++ b/runner.go @@ -15,13 +15,11 @@ import ( "net/http/httputil" "net/url" "os" - "os/signal" "path/filepath" "strconv" "strings" "sync" "sync/atomic" - "syscall" "testing" "time" @@ -30,7 +28,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin" "github.com/httprunner/hrp/internal/builtin" "github.com/httprunner/hrp/internal/ga" "github.com/httprunner/hrp/internal/json" @@ -297,34 +294,6 @@ func (r *caseRunner) run() error { return nil } -func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, err error) { - plugin, err = funplugin.Init(path, logOn) - if plugin == nil { - return - } - - // catch Interrupt and SIGTERM signals to ensure plugin quitted - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - plugin.Quit() - os.Exit(0) - }() - - // report event for initializing plugin - event := ga.EventTracking{ - Category: "InitPlugin", - Action: fmt.Sprintf("Init %s plugin", plugin.Type()), - } - if err != nil { - event.Value = 1 // failed - } - go ga.SendEvent(event) - - return -} - func (r *caseRunner) runStep(index int, caseConfig *TConfig) (stepResult *stepData, err error) { step := r.TestCase.TestSteps[index]