From 675ded099ddf5e1663c557a0403e68e09e9c7497 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 17 Jan 2022 15:18:30 +0800 Subject: [PATCH] refactor: plugin structure --- parser.go | 24 +++++- plugin/{shared => common}/call.go | 2 +- plugin/{shared => common}/call_test.go | 2 +- .../common/go_plugin_test.go | 41 ++++------ .../common/hashicorp_plugin_test.go | 33 ++++---- plugin.go => plugin/common/init.go | 82 ++++++++----------- plugin/plugin.go | 3 +- runner.go | 6 +- 8 files changed, 97 insertions(+), 96 deletions(-) rename plugin/{shared => common}/call.go (99%) rename plugin/{shared => common}/call_test.go (99%) rename go_plugin_test.go => plugin/common/go_plugin_test.go (58%) rename hashicorp_plugin_test.go => plugin/common/hashicorp_plugin_test.go (61%) rename plugin.go => plugin/common/init.go (67%) diff --git a/parser.go b/parser.go index b456f680..ce2a8fe3 100644 --- a/parser.go +++ b/parser.go @@ -11,6 +11,9 @@ import ( "github.com/maja42/goval" "github.com/pkg/errors" "github.com/rs/zerolog/log" + + "github.com/httprunner/hrp/internal/builtin" + "github.com/httprunner/hrp/plugin/common" ) func newParser() *parser { @@ -18,7 +21,7 @@ func newParser() *parser { } type parser struct { - plugin hrpPlugin // plugin is used to call functions + plugin common.Plugin // plugin is used to call functions } func buildURL(baseURL, stepURL string) string { @@ -233,6 +236,25 @@ func (p *parser) parseString(raw string, variablesMapping map[string]interface{} return parsedString, nil } +// callFunc calls function with arguments +// only support return at most one result value +func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) { + // call with plugin function + if p.plugin != nil && p.plugin.Has(funcName) { + return p.plugin.Call(funcName, arguments...) + } + + // get builtin function + function, ok := builtin.Functions[funcName] + if !ok { + return nil, fmt.Errorf("function %s is not found", funcName) + } + fn := reflect.ValueOf(function) + + // call with builtin function + return common.CallFunc(fn, arguments...) +} + // merge two variables mapping, the first variables have higher priority func mergeVariables(variables, overriddenVariables map[string]interface{}) map[string]interface{} { if overriddenVariables == nil { diff --git a/plugin/shared/call.go b/plugin/common/call.go similarity index 99% rename from plugin/shared/call.go rename to plugin/common/call.go index 8d30516d..02238cc9 100644 --- a/plugin/shared/call.go +++ b/plugin/common/call.go @@ -1,4 +1,4 @@ -package shared +package common import ( "fmt" diff --git a/plugin/shared/call_test.go b/plugin/common/call_test.go similarity index 99% rename from plugin/shared/call_test.go rename to plugin/common/call_test.go index d886f2e0..03b33ba6 100644 --- a/plugin/shared/call_test.go +++ b/plugin/common/call_test.go @@ -1,4 +1,4 @@ -package shared +package common import ( "errors" diff --git a/go_plugin_test.go b/plugin/common/go_plugin_test.go similarity index 58% rename from go_plugin_test.go rename to plugin/common/go_plugin_test.go index 6183c7ea..fec89180 100644 --- a/go_plugin_test.go +++ b/plugin/common/go_plugin_test.go @@ -1,7 +1,7 @@ // +build linux freebsd darwin // go plugin doesn't support windows -package hrp +package common import ( "fmt" @@ -16,7 +16,7 @@ func buildGoPlugin() { fmt.Println("[setup] build go plugin") // flag -race is necessary in order to be consistent with go test cmd := exec.Command("go", "build", "-buildmode=plugin", "-race", - "-o=examples/debugtalk.so", "examples/plugin/debugtalk.go") + "-o=debugtalk.so", "../../examples/plugin/debugtalk.go") if err := cmd.Run(); err != nil { panic(err) } @@ -24,50 +24,43 @@ func buildGoPlugin() { func removeGoPlugin() { fmt.Println("[teardown] remove go plugin") - os.Remove("examples/debugtalk.so") + os.Remove("debugtalk.so") } func TestLocatePlugin(t *testing.T) { buildGoPlugin() defer removeGoPlugin() - cwd, _ := os.Getwd() - _, err := locatePlugin(cwd, goPluginFile) + _, err := locateFile("../", goPluginFile) if !assert.Error(t, err) { t.Fail() } - _, err = locatePlugin("", goPluginFile) + _, err = locateFile("", goPluginFile) if !assert.Error(t, err) { t.Fail() } - startPath := "examples/debugtalk.so" - _, err = locatePlugin(startPath, goPluginFile) + startPath := "debugtalk.so" + _, err = locateFile(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } - startPath = "examples/demo.json" - _, err = locatePlugin(startPath, goPluginFile) + startPath = "call.go" + _, err = locateFile(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } - startPath = "examples/" - _, err = locatePlugin(startPath, goPluginFile) - if !assert.Nil(t, err) { - t.Fail() - } - - startPath = "examples/plugin/debugtalk.go" - _, err = locatePlugin(startPath, goPluginFile) + startPath = "." + _, err = locateFile(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } startPath = "/abc" - _, err = locatePlugin(startPath, goPluginFile) + _, err = locateFile(startPath, goPluginFile) if !assert.Error(t, err) { t.Fail() } @@ -75,17 +68,19 @@ func TestLocatePlugin(t *testing.T) { func TestCallPluginFunction(t *testing.T) { buildGoPlugin() - removeHashicorpPlugin() defer removeGoPlugin() - parser := newParser() - err := parser.initPlugin("examples/debugtalk.so") + plugin, err := Init("debugtalk.so") if err != nil { t.Fatal(err) } + if !assert.True(t, plugin.Has("Concatenate")) { + t.Fail() + } + // call function without arguments - result, err := parser.callFunc("Concatenate", "1", 2, "3.14") + result, err := plugin.Call("Concatenate", "1", 2, "3.14") if !assert.NoError(t, err) { t.Fail() } diff --git a/hashicorp_plugin_test.go b/plugin/common/hashicorp_plugin_test.go similarity index 61% rename from hashicorp_plugin_test.go rename to plugin/common/hashicorp_plugin_test.go index c9edf994..06713926 100644 --- a/hashicorp_plugin_test.go +++ b/plugin/common/hashicorp_plugin_test.go @@ -1,4 +1,4 @@ -package hrp +package common import ( "fmt" @@ -6,15 +6,14 @@ import ( "os/exec" "testing" - "github.com/httprunner/hrp/plugin/host" "github.com/stretchr/testify/assert" ) func buildHashicorpPlugin() { fmt.Println("[init] build hashicorp go plugin") cmd := exec.Command("go", "build", - "-o=examples/debugtalk.bin", - "examples/plugin/hashicorp.go", "examples/plugin/debugtalk.go") + "-o=debugtalk.bin", + "../../examples/plugin/hashicorp.go", "../../examples/plugin/debugtalk.go") if err := cmd.Run(); err != nil { panic(err) } @@ -22,46 +21,42 @@ func buildHashicorpPlugin() { func removeHashicorpPlugin() { fmt.Println("[teardown] remove hashicorp plugin") - os.Remove("examples/debugtalk.bin") + os.Remove("debugtalk.bin") } func TestInitHashicorpPlugin(t *testing.T) { buildHashicorpPlugin() defer removeHashicorpPlugin() - f, err := host.Init("examples/debugtalk.bin") + plugin, err := Init("debugtalk.bin") if err != nil { t.Fatal(err) } - defer host.Quit() + defer plugin.Quit() - v1, err := f.GetNames() - if err != nil { + if !assert.True(t, plugin.Has("sum_ints")) { t.Fatal(err) } - if !assert.Contains(t, v1, "sum_ints") { - t.Fatal(err) - } - if !assert.Contains(t, v1, "concatenate") { + if !assert.True(t, plugin.Has("concatenate")) { t.Fatal(err) } var v2 interface{} - v2, err = f.Call("sum_ints", 1, 2, 3, 4) + v2, err = plugin.Call("sum_ints", 1, 2, 3, 4) if err != nil { t.Fatal(err) } if !assert.Equal(t, 10, v2) { t.Fail() } - v2, err = f.Call("sum_two_int", 1, 2) + v2, err = plugin.Call("sum_two_int", 1, 2) if err != nil { t.Fatal(err) } if !assert.Equal(t, 3, v2) { t.Fail() } - v2, err = f.Call("sum", 1, 2, 3.4, 5) + v2, err = plugin.Call("sum", 1, 2, 3.4, 5) if err != nil { t.Fatal(err) } @@ -70,14 +65,14 @@ func TestInitHashicorpPlugin(t *testing.T) { } var v3 interface{} - v3, err = f.Call("sum_two_string", "a", "b") + v3, err = plugin.Call("sum_two_string", "a", "b") if err != nil { t.Fatal(err) } if !assert.Equal(t, "ab", v3) { t.Fail() } - v3, err = f.Call("sum_strings", "a", "b", "c") + v3, err = plugin.Call("sum_strings", "a", "b", "c") if err != nil { t.Fatal(err) } @@ -85,7 +80,7 @@ func TestInitHashicorpPlugin(t *testing.T) { t.Fail() } - v3, err = f.Call("concatenate", "a", 2, "c", 3.4) + v3, err = plugin.Call("concatenate", "a", 2, "c", 3.4) if err != nil { t.Fatal(err) } diff --git a/plugin.go b/plugin/common/init.go similarity index 67% rename from plugin.go rename to plugin/common/init.go index 389d58e3..0ff13a0d 100644 --- a/plugin.go +++ b/plugin/common/init.go @@ -1,4 +1,4 @@ -package hrp +package common import ( "fmt" @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog/log" - "github.com/httprunner/hrp/internal/builtin" "github.com/httprunner/hrp/internal/ga" pluginHost "github.com/httprunner/hrp/plugin/host" pluginShared "github.com/httprunner/hrp/plugin/shared" @@ -24,11 +23,11 @@ const ( hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py" ) -type hrpPlugin interface { - init(path string) error // init plugin - has(funcName string) bool // check if plugin has function - call(funcName string, args ...interface{}) (interface{}, error) // call function - quit() error // quit plugin +type Plugin interface { + Init(path string) error // init plugin + Has(funcName string) bool // check if plugin has function + Call(funcName string, args ...interface{}) (interface{}, error) // call function + Quit() error // quit plugin } // goPlugin implements golang official plugin @@ -37,7 +36,7 @@ type goPlugin struct { cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance } -func (p *goPlugin) init(path string) error { +func (p *goPlugin) Init(path string) error { if runtime.GOOS == "windows" { log.Warn().Msg("go plugin does not support windows") return fmt.Errorf("go plugin does not support windows") @@ -67,7 +66,7 @@ func (p *goPlugin) init(path string) error { return nil } -func (p *goPlugin) has(funcName string) bool { +func (p *goPlugin) Has(funcName string) bool { fn, ok := p.cachedFunctions[funcName] if ok { return fn.IsValid() @@ -90,12 +89,15 @@ func (p *goPlugin) has(funcName string) bool { return true } -func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) { +func (p *goPlugin) Call(funcName string, args ...interface{}) (interface{}, error) { + if !p.Has(funcName) { + return nil, fmt.Errorf("function %s not found", funcName) + } fn := p.cachedFunctions[funcName] - return pluginShared.CallFunc(fn, args...) + return CallFunc(fn, args...) } -func (p *goPlugin) quit() error { +func (p *goPlugin) Quit() error { // no need to quit for go plugin return nil } @@ -106,7 +108,7 @@ type hashicorpPlugin struct { cachedFunctions map[string]bool // cache loaded functions to improve performance } -func (p *hashicorpPlugin) init(path string) error { +func (p *hashicorpPlugin) Init(path string) error { f, err := pluginHost.Init(path) if err != nil { @@ -120,7 +122,7 @@ func (p *hashicorpPlugin) init(path string) error { return nil } -func (p *hashicorpPlugin) has(funcName string) bool { +func (p *hashicorpPlugin) Has(funcName string) bool { flag, ok := p.cachedFunctions[funcName] if ok { return flag @@ -142,45 +144,48 @@ func (p *hashicorpPlugin) has(funcName string) bool { return false } -func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) { +func (p *hashicorpPlugin) Call(funcName string, args ...interface{}) (interface{}, error) { return p.FuncCaller.Call(funcName, args...) } -func (p *hashicorpPlugin) quit() error { +func (p *hashicorpPlugin) Quit() error { // kill hashicorp plugin process pluginHost.Quit() return nil } -func (p *parser) initPlugin(path string) error { +func Init(path string) (Plugin, error) { if path == "" { - return nil + return nil, nil } + var plugin Plugin // priority: hashicorp plugin > go plugin > builtin functions // locate hashicorp plugin file - pluginPath, err := locatePlugin(path, hashicorpGoPluginFile) + pluginPath, err := locateFile(path, hashicorpGoPluginFile) if err == nil { // found hashicorp go plugin file - p.plugin = &hashicorpPlugin{} - return p.plugin.init(pluginPath) + plugin = &hashicorpPlugin{} + err = plugin.Init(pluginPath) + return plugin, err } // locate go plugin file - pluginPath, err = locatePlugin(path, goPluginFile) + pluginPath, err = locateFile(path, goPluginFile) if err == nil { // found go plugin file - p.plugin = &goPlugin{} - return p.plugin.init(pluginPath) + plugin = &goPlugin{} + err = plugin.Init(pluginPath) + return plugin, err } // plugin not found - return nil + return nil, nil } -// locatePlugin searches destPluginFile upward recursively until current +// locateFile searches destFile upward recursively until current // working directory or system root dir. -func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) { +func locateFile(startPath string, destFile pluginFile) (string, error) { stat, err := os.Stat(startPath) if os.IsNotExist(err) { return "", err @@ -195,7 +200,7 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) { startDir, _ = filepath.Abs(startDir) // convention over configuration - pluginPath := filepath.Join(startDir, string(destPluginFile)) + pluginPath := filepath.Join(startDir, string(destFile)) if _, err := os.Stat(pluginPath); err == nil { return pluginPath, nil } @@ -212,24 +217,5 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) { return "", fmt.Errorf("searched to system root dir, plugin file not found") } - return locatePlugin(parentDir, destPluginFile) -} - -// callFunc calls function with arguments -// only support return at most one result value -func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) { - // call with plugin function - if p.plugin != nil && p.plugin.has(funcName) { - return p.plugin.call(funcName, arguments...) - } - - // get builtin function - function, ok := builtin.Functions[funcName] - if !ok { - return nil, fmt.Errorf("function %s is not found", funcName) - } - fn := reflect.ValueOf(function) - - // call with builtin function - return pluginShared.CallFunc(fn, arguments...) + return locateFile(parentDir, destFile) } diff --git a/plugin/plugin.go b/plugin/plugin.go index c9301795..6a75dedb 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -8,6 +8,7 @@ import ( hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" + "github.com/httprunner/hrp/plugin/common" pluginShared "github.com/httprunner/hrp/plugin/shared" ) @@ -36,7 +37,7 @@ func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{} return nil, fmt.Errorf("function %s not found", funcName) } - return pluginShared.CallFunc(fn, args...) + return common.CallFunc(fn, args...) } var functions = make(functionsMap) diff --git a/runner.go b/runner.go index 66844d23..b0e9b6d4 100644 --- a/runner.go +++ b/runner.go @@ -19,6 +19,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/hrp/internal/ga" + "github.com/httprunner/hrp/plugin/common" ) // Run starts to run API test with default configs. @@ -157,7 +158,7 @@ func (r *caseRunner) reset() *caseRunner { func (r *caseRunner) run() error { defer func() { if r.parser.plugin != nil { - r.parser.plugin.quit() + r.parser.plugin.Quit() } }() config := r.TestCase.Config @@ -504,7 +505,8 @@ func (r *caseRunner) parseConfig(config IConfig) error { cfg := config.ToStruct() // init plugin - err := r.parser.initPlugin(cfg.Path) + var err error + r.parser.plugin, err = common.Init(cfg.Path) if err != nil { return err }