From 1c7f2f98343c592a745b34e64f86afc7379db4c5 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 13 Jan 2022 14:30:07 +0800 Subject: [PATCH 01/13] refactor: go plugin --- docs/cmd/hrp.md | 6 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_har2case.md | 6 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 4 +- plugin_test.go => go_plugin_test.go | 18 +-- parser.go | 44 +------ plugin.go | 182 ++++++++++++++++++++++------ runner.go | 2 +- 9 files changed, 166 insertions(+), 100 deletions(-) rename plugin_test.go => go_plugin_test.go (73%) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 93352005..8ccee28e 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -29,8 +29,8 @@ Copyright 2021 debugtalk ### SEE ALSO * [hrp boom](hrp_boom.md) - run load test with boomer -* [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files +* [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files * [hrp run](hrp_run.md) - run API test -* [hrp startproject](hrp_startproject.md) - Create a scaffold project +* [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 8-Jan-2022 +###### Auto generated by spf13/cobra on 13-Jan-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 36e4851c..f421c9d9 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -38,4 +38,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 8-Jan-2022 +###### Auto generated by spf13/cobra on 13-Jan-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 79315570..6f57a509 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -1,10 +1,10 @@ ## hrp har2case -Convert HAR to json/yaml testcase files +convert HAR to json/yaml testcase files ### Synopsis -Convert HAR to json/yaml testcase files +convert HAR to json/yaml testcase files ``` hrp har2case $har_path... [flags] @@ -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 8-Jan-2022 +###### Auto generated by spf13/cobra on 13-Jan-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 20523b8d..9cce4aaf 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -31,4 +31,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 8-Jan-2022 +###### Auto generated by spf13/cobra on 13-Jan-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index c22b9f47..736d31d6 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -1,6 +1,6 @@ ## hrp startproject -Create a scaffold project +create a scaffold project ``` hrp startproject $project_name [flags] @@ -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 8-Jan-2022 +###### Auto generated by spf13/cobra on 13-Jan-2022 diff --git a/plugin_test.go b/go_plugin_test.go similarity index 73% rename from plugin_test.go rename to go_plugin_test.go index b811c6e3..a08dd0ce 100644 --- a/plugin_test.go +++ b/go_plugin_test.go @@ -15,7 +15,7 @@ import ( func TestMain(m *testing.M) { fmt.Println("[TestMain] 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") + cmd := exec.Command("go", "build", "-buildmode=plugin", "-race", "-o=examples/debugtalk.so", "examples/plugin/debugtalk.go") if err := cmd.Run(); err != nil { panic(err) } @@ -24,42 +24,42 @@ func TestMain(m *testing.M) { func TestLocatePlugin(t *testing.T) { cwd, _ := os.Getwd() - _, err := locatePlugin(cwd) + _, err := locatePlugin(cwd, goPluginFile) if !assert.Error(t, err) { t.Fail() } - _, err = locatePlugin("") + _, err = locatePlugin("", goPluginFile) if !assert.Error(t, err) { t.Fail() } startPath := "examples/debugtalk.so" - _, err = locatePlugin(startPath) + _, err = locatePlugin(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } startPath = "examples/demo.json" - _, err = locatePlugin(startPath) + _, err = locatePlugin(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } startPath = "examples/" - _, err = locatePlugin(startPath) + _, err = locatePlugin(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } startPath = "examples/plugin/debugtalk.go" - _, err = locatePlugin(startPath) + _, err = locatePlugin(startPath, goPluginFile) if !assert.Nil(t, err) { t.Fail() } startPath = "/abc" - _, err = locatePlugin(startPath) + _, err = locatePlugin(startPath, goPluginFile) if !assert.Error(t, err) { t.Fail() } @@ -67,7 +67,7 @@ func TestLocatePlugin(t *testing.T) { func TestCallPluginFunction(t *testing.T) { parser := newParser() - parser.loadPlugin("examples/debugtalk.so") + parser.initPlugin("examples/debugtalk.so") // call function without arguments result, err := parser.callFunc("Concatenate", 1, "2", 3.14) diff --git a/parser.go b/parser.go index 49062ae2..b456f680 100644 --- a/parser.go +++ b/parser.go @@ -4,9 +4,6 @@ import ( "encoding/json" "fmt" "net/url" - "os" - "path/filepath" - "plugin" "reflect" "regexp" "strings" @@ -21,46 +18,7 @@ func newParser() *parser { } type parser struct { - // pluginLoader stores loaded go plugins. - pluginLoader *plugin.Plugin -} - -// locatePlugin searches debugtalk.so upward recursively until current -// working directory or system root dir. -func locatePlugin(startPath 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 - // target plugin file name is always debugtalk.so - pluginPath := filepath.Join(startDir, "debugtalk.so") - 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 locatePlugin(parentDir) + plugin hrpPlugin // plugin is used to call functions } func buildURL(baseURL, stepURL string) string { diff --git a/plugin.go b/plugin.go index 7748e207..8bc29af9 100644 --- a/plugin.go +++ b/plugin.go @@ -2,6 +2,8 @@ package hrp import ( "fmt" + "os" + "path/filepath" "plugin" "reflect" "runtime" @@ -12,28 +14,33 @@ import ( "github.com/httprunner/hrp/internal/ga" ) -func (p *parser) loadPlugin(path string) error { +type pluginFile string + +const ( + goPluginFile pluginFile = "debugtalk.so" // built from go plugin + hashicorpGoPluginFile pluginFile = "debugtalk" // built from hashicorp go plugin + hashicorpPyPluginFile pluginFile = "debugtalk.py" +) + +type hrpPlugin interface { + init(path string) error + lookup(funcName string) (reflect.Value, error) // lookup function + // call(funcName string, args ...interface{}) (interface{}, error) + quit() error +} + +// goPlugin implements golang official plugin +type goPlugin struct { + *plugin.Plugin +} + +func (p *goPlugin) init(path string) error { if runtime.GOOS == "windows" { log.Warn().Msg("go plugin does not support windows") - return nil - } - - if path == "" { - return nil - } - - // check if loaded before - if p.pluginLoader != nil { - return nil - } - - // locate plugin file - pluginPath, err := locatePlugin(path) - if err != nil { - // plugin not found - return nil + return fmt.Errorf("go plugin does not support windows") } + var err error // report event for loading go plugin defer func() { event := ga.EventTracking{ @@ -46,36 +53,137 @@ func (p *parser) loadPlugin(path string) error { go ga.SendEvent(event) }() - // load plugin - plugins, err := plugin.Open(pluginPath) + p.Plugin, err = plugin.Open(path) if err != nil { log.Error().Err(err).Str("path", path).Msg("load go plugin failed") return err } - p.pluginLoader = plugins log.Info().Str("path", path).Msg("load go plugin success") return nil } -func getMappingFunction(funcName string, pluginLoader *plugin.Plugin) (reflect.Value, error) { +func (p *goPlugin) lookup(funcName string) (reflect.Value, error) { + if p.Plugin == nil { + return reflect.Value{}, fmt.Errorf("go plugin is not loaded") + } + + sym, err := p.Plugin.Lookup(funcName) + if err != nil { + return reflect.Value{}, fmt.Errorf("function %s is not found", funcName) + } + fn := reflect.ValueOf(sym) + + // check function type + if fn.Kind() != reflect.Func { + return reflect.Value{}, fmt.Errorf("function %s is invalid", funcName) + } + + return fn, nil +} + +func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) { + if p.Plugin == nil { + return nil, fmt.Errorf("go plugin is not loaded") + } + return nil, nil +} + +func (p *goPlugin) quit() error { + return nil +} + +// hashicorpPlugin implements hashicorp/go-plugin +type hashicorpPlugin struct { + cachedFunctions map[string]reflect.Value +} + +func (p *hashicorpPlugin) init(path string) error { + log.Info().Str("path", path).Msg("load hashicorp go plugin success") + return nil +} + +func (p *hashicorpPlugin) lookup(funcName string) (reflect.Value, error) { + return reflect.Value{}, nil +} + +func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) { + return nil, nil +} + +func (p *hashicorpPlugin) quit() error { + return nil +} + +func (p *parser) initPlugin(path string) error { + if path == "" { + return nil + } + + // locate go plugin file + pluginPath, err := locatePlugin(path, goPluginFile) + if err == nil { + // found go plugin file + p.plugin = &goPlugin{} + return p.plugin.init(pluginPath) + } + + // locate hashicorp plugin file + pluginPath, err = locatePlugin(path, hashicorpGoPluginFile) + if err == nil { + // found hashicorp go plugin file + p.plugin = &hashicorpPlugin{} + return p.plugin.init(pluginPath) + } + + // plugin not found + return nil +} + +// locatePlugin searches destPluginFile upward recursively until current +// working directory or system root dir. +func locatePlugin(startPath string, destPluginFile pluginFile) (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, string(destPluginFile)) + 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 locatePlugin(parentDir, destPluginFile) +} + +func (p *parser) getMappingFunction(funcName string) (reflect.Value, error) { var fn reflect.Value - var err error - defer func() { - // check function type - if err == nil && fn.Kind() != reflect.Func { - // function not valid - err = fmt.Errorf("function %s is invalid", funcName) - return - } - }() - - // get function from plugin loader - if pluginLoader != nil { - sym, err := pluginLoader.Lookup(funcName) + // get function from plugin + if p.plugin != nil { + fn, err := p.plugin.lookup(funcName) if err == nil { - fn = reflect.ValueOf(sym) return fn, nil } } @@ -93,7 +201,7 @@ func getMappingFunction(funcName string, pluginLoader *plugin.Plugin) (reflect.V // callFunc calls function with arguments // only support return at most one result value func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) { - fn, err := getMappingFunction(funcName, p.pluginLoader) + fn, err := p.getMappingFunction(funcName) if err != nil { return nil, err } diff --git a/runner.go b/runner.go index c59e1fec..4ba6eaa2 100644 --- a/runner.go +++ b/runner.go @@ -506,7 +506,7 @@ func (r *caseRunner) parseConfig(config IConfig) error { cfg.Variables = parsedVariables // load plugin variables and functions - err = r.parser.loadPlugin(cfg.Path) + err = r.parser.initPlugin(cfg.Path) if err != nil { return err } From c31d76b5577707dbdffb1ef7854c632bbb0a2f70 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 13 Jan 2022 15:07:10 +0800 Subject: [PATCH 02/13] change: cache loaded functions to improve performance --- parser.go | 3 ++- plugin.go | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index b456f680..760b7748 100644 --- a/parser.go +++ b/parser.go @@ -18,7 +18,8 @@ func newParser() *parser { } type parser struct { - plugin hrpPlugin // plugin is used to call functions + plugin hrpPlugin // plugin is used to call functions + cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance } func buildURL(baseURL, stepURL string) string { diff --git a/plugin.go b/plugin.go index 8bc29af9..b64f8a1f 100644 --- a/plugin.go +++ b/plugin.go @@ -95,7 +95,6 @@ func (p *goPlugin) quit() error { // hashicorpPlugin implements hashicorp/go-plugin type hashicorpPlugin struct { - cachedFunctions map[string]reflect.Value } func (p *hashicorpPlugin) init(path string) error { @@ -178,12 +177,17 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) { } func (p *parser) getMappingFunction(funcName string) (reflect.Value, error) { + if function, ok := p.cachedFunctions[funcName]; ok { + return function, nil + } + var fn reflect.Value // get function from plugin if p.plugin != nil { fn, err := p.plugin.lookup(funcName) if err == nil { + p.cachedFunctions[funcName] = fn return fn, nil } } @@ -191,6 +195,7 @@ func (p *parser) getMappingFunction(funcName string) (reflect.Value, error) { // get builtin function if function, ok := builtin.Functions[funcName]; ok { fn = reflect.ValueOf(function) + p.cachedFunctions[funcName] = fn return fn, nil } From 5a943ff7216d4faa142d9b5e0a7c3fe954673444 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 13 Jan 2022 21:27:24 +0800 Subject: [PATCH 03/13] feat: support creating and calling custom functions with hashicorp/go-plugin --- .gitignore | 4 + docs/CHANGELOG.md | 4 + examples/plugin/debugtalk.go | 24 +++- go.mod | 2 + go.sum | 26 ++++ go_plugin_test.go | 21 +++- internal/version/init.go | 2 +- parser.go | 3 +- plugin-gosdk/build.go | 55 ++++++++ plugin-gosdk/host_test.go | 42 +++++++ plugin-gosdk/interface.go | 105 ++++++++++++++++ plugin-gosdk/main.go | 63 ++++++++++ plugin-gosdk/plugin/debugtalk.go | 38 ++++++ plugin.go | 208 +++++++++++++++++-------------- runner.go | 18 ++- 15 files changed, 505 insertions(+), 110 deletions(-) create mode 100644 plugin-gosdk/build.go create mode 100644 plugin-gosdk/host_test.go create mode 100644 plugin-gosdk/interface.go create mode 100644 plugin-gosdk/main.go create mode 100644 plugin-gosdk/plugin/debugtalk.go diff --git a/.gitignore b/.gitignore index 82168d34..e5528b08 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ __pycache__ site/ output/ + +# built plugins +debugtalk.bin +debugtalk.so diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ab72d19f..ba2df60c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## v0.5.2 (2022-01-13) + +- feat: support creating and calling custom functions with [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin) + ## v0.5.1 (2022-01-13) - feat: support specifying running cycles for load testing diff --git a/examples/plugin/debugtalk.go b/examples/plugin/debugtalk.go index ce98e20c..dbe3bf7a 100644 --- a/examples/plugin/debugtalk.go +++ b/examples/plugin/debugtalk.go @@ -9,6 +9,26 @@ func init() { log.Println("plugin init function called") } -func Concatenate(a int, b string, c float64) string { - return fmt.Sprintf("%v_%v_%v", a, b, c) +func SumInt(args ...interface{}) (interface{}, error) { + var sum int + for _, arg := range args { + v, ok := arg.(int) + if !ok { + return nil, fmt.Errorf("unexpected type: %T", arg) + } + sum += v + } + return sum, nil +} + +func ConcatenateString(args ...interface{}) (interface{}, error) { + var result string + for _, arg := range args { + v, ok := arg.(string) + if !ok { + return nil, fmt.Errorf("unexpected type: %T", arg) + } + result += v + } + return result, nil } diff --git a/go.mod b/go.mod index 6bfe7528..ba72d455 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/getsentry/sentry-go v0.11.0 github.com/google/uuid v1.3.0 + github.com/hashicorp/go-hclog v1.1.0 + github.com/hashicorp/go-plugin v1.4.3 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/maja42/goval v1.2.1 diff --git a/go.sum b/go.sum index c5a10d67..cbd318a5 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -205,9 +206,14 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= +github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -222,6 +228,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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= @@ -233,6 +241,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w= github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -277,10 +287,14 @@ github.com/maja42/goval v1.2.1 h1:fyEgzddqPgCZsKcFLk4C6SdCHyEaAHYvtZG4mGzQOHU= github.com/maja42/goval v1.2.1/go.mod h1:42LU+BQXL/veE9jnTTUOSj38GRmOTSThYSXRVodI5J4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -293,6 +307,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -310,6 +326,8 @@ github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5Vgl github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -476,6 +494,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -518,6 +537,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -563,6 +583,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -608,6 +629,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -700,6 +722,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -740,7 +763,9 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -760,6 +785,7 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/go_plugin_test.go b/go_plugin_test.go index a08dd0ce..c0384242 100644 --- a/go_plugin_test.go +++ b/go_plugin_test.go @@ -12,17 +12,24 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMain(m *testing.M) { - fmt.Println("[TestMain] build go plugin") +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") if err := cmd.Run(); err != nil { panic(err) } - os.Exit(m.Run()) +} + +func removeGoPlugin() { + fmt.Println("[teardown] remove go plugin") + os.Remove("examples/debugtalk.so") } func TestLocatePlugin(t *testing.T) { + buildGoPlugin() + defer removeGoPlugin() + cwd, _ := os.Getwd() _, err := locatePlugin(cwd, goPluginFile) if !assert.Error(t, err) { @@ -66,15 +73,19 @@ func TestLocatePlugin(t *testing.T) { } func TestCallPluginFunction(t *testing.T) { + buildGoPlugin() + removeHashicorpPlugin() + defer removeGoPlugin() + parser := newParser() parser.initPlugin("examples/debugtalk.so") // call function without arguments - result, err := parser.callFunc("Concatenate", 1, "2", 3.14) + result, err := parser.callFunc("ConcatenateString", "1", "2", "3.14") if !assert.NoError(t, err) { t.Fail() } - if !assert.Equal(t, result, "1_2_3.14") { + if !assert.Equal(t, "123.14", result) { t.Fail() } } diff --git a/internal/version/init.go b/internal/version/init.go index 53bdb1fa..77c25720 100644 --- a/internal/version/init.go +++ b/internal/version/init.go @@ -1,3 +1,3 @@ package version -const VERSION = "v0.5.1" +const VERSION = "v0.5.2" diff --git a/parser.go b/parser.go index 760b7748..b456f680 100644 --- a/parser.go +++ b/parser.go @@ -18,8 +18,7 @@ func newParser() *parser { } type parser struct { - plugin hrpPlugin // plugin is used to call functions - cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance + plugin hrpPlugin // plugin is used to call functions } func buildURL(baseURL, stepURL string) string { diff --git a/plugin-gosdk/build.go b/plugin-gosdk/build.go new file mode 100644 index 00000000..53be86d9 --- /dev/null +++ b/plugin-gosdk/build.go @@ -0,0 +1,55 @@ +package plugin + +import ( + "fmt" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +// Here is a real implementation of funcCaller +type functionPlugin struct { + logger hclog.Logger + functions map[string]func(args ...interface{}) (interface{}, error) +} + +func (p *functionPlugin) GetNames() ([]string, error) { + var names []string + for name := range p.functions { + names = append(names, name) + } + return names, nil +} + +func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}, error) { + p.logger.Info("Call function", "funcName", funcName, "args", args) + + f, ok := p.functions[funcName] + if !ok { + return nil, fmt.Errorf("function %s not found", funcName) + } + + return f(args...) +} + +var functions = make(map[string]func(args ...interface{}) (interface{}, error)) + +func Register(funcName string, fn func(args ...interface{}) (interface{}, error)) { + functions[funcName] = fn +} + +func Serve() { + funcPlugin := &functionPlugin{ + logger: logger, + functions: functions, + } + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]plugin.Plugin{ + Name: &hashicorpPlugin{Impl: funcPlugin}, + } + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} diff --git a/plugin-gosdk/host_test.go b/plugin-gosdk/host_test.go new file mode 100644 index 00000000..8927a698 --- /dev/null +++ b/plugin-gosdk/host_test.go @@ -0,0 +1,42 @@ +package plugin + +import ( + "fmt" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + fmt.Println("[TestMain] build go plugin") + cmd := exec.Command("go", "build", "-o=debugtalk", "plugin/debugtalk.go") + if err := cmd.Run(); err != nil { + panic(err) + } + os.Exit(m.Run()) +} + +func TestInitHashicorpPlugin(t *testing.T) { + f, err := Init("./debugtalk") + if err != nil { + t.Fatal(err) + } + defer Quit() + + v, err := f.Call("sum_int", 1, 2, 3, 4) + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, 10, v) { + t.Fail() + } + v, err = f.Call("concatenate_string", "a", "b", "c") + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, "abc", v) { + t.Fail() + } +} diff --git a/plugin-gosdk/interface.go b/plugin-gosdk/interface.go new file mode 100644 index 00000000..5853e397 --- /dev/null +++ b/plugin-gosdk/interface.go @@ -0,0 +1,105 @@ +package plugin + +import ( + "encoding/gob" + "net/rpc" + + "github.com/hashicorp/go-plugin" + "github.com/rs/zerolog/log" +) + +func init() { + gob.Register(new(funcData)) +} + +// funcData is used to transfer between plugin and host via RPC. +type funcData struct { + Name string // function name + Args []interface{} // function arguments +} + +// FuncCaller is the interface that we're exposing as a plugin. +type FuncCaller interface { + GetNames() ([]string, error) // get all plugin function names list + Call(funcName string, args ...interface{}) (interface{}, error) // call plugin function +} + +// functionRPC runs on the host side. +type functionRPC struct { + client *rpc.Client +} + +func (g *functionRPC) GetNames() ([]string, error) { + var resp []string + err := g.client.Call("Plugin.GetNames", new(interface{}), &resp) + if err != nil { + log.Error().Err(err).Msg("rpc call GetNames() failed") + return nil, err + } + return resp, nil +} + +// host -> plugin +func (g *functionRPC) Call(funcName string, funcArgs ...interface{}) (interface{}, error) { + log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via RPC") + f := funcData{ + Name: funcName, + Args: funcArgs, + } + + var args interface{} = f + var resp interface{} + err := g.client.Call("Plugin.Call", &args, &resp) + if err != nil { + log.Error().Err(err). + Str("funcName", funcName).Interface("funcArgs", funcArgs). + Msg("rpc call Call() failed") + return nil, err + } + return resp, nil +} + +// functionRPCServer runs on the plugin side, executing the user custom function. +type functionRPCServer struct { + Impl FuncCaller +} + +// plugin execution +func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error { + log.Info().Interface("args", args).Msg("GetNames called on plugin side") + var err error + *resp, err = s.Impl.GetNames() + if err != nil { + log.Error().Err(err).Msg("GetNames execution failed") + return err + } + return nil +} + +// plugin execution +func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error { + log.Info().Interface("args", args).Msg("function called on plugin side") + f := args.(*funcData) + var err error + *resp, err = s.Impl.Call(f.Name, f.Args...) + if err != nil { + log.Error().Err(err). + Interface("args", args). + Msg("function execution failed") + return err + } + return nil +} + +// hashicorpPlugin implements hashicorp's plugin.Plugin. +type hashicorpPlugin struct { + Impl FuncCaller +} + +func (p *hashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &functionRPCServer{Impl: p.Impl}, nil +} + +func (hashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &functionRPC{client: c}, nil +} diff --git a/plugin-gosdk/main.go b/plugin-gosdk/main.go new file mode 100644 index 00000000..73b7f8d3 --- /dev/null +++ b/plugin-gosdk/main.go @@ -0,0 +1,63 @@ +package plugin + +import ( + "os" + "os/exec" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +const Name = "debugtalk" + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. If the handshake fails, a user friendly error is shown. +// This prevents users from executing bad plugins or executing a plugin +// directory. It is a UX feature, not a security feature. +var handshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "HttpRunnerPlus", + MagicCookieValue: Name, +} + +// Create an hclog.Logger +var logger = hclog.New(&hclog.LoggerOptions{ + Name: Name, + Output: os.Stdout, + Level: hclog.Debug, +}) + +var client *plugin.Client + +func Init(path string) (FuncCaller, error) { + // launch the plugin process + client = plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: map[string]plugin.Plugin{ + Name: &hashicorpPlugin{}, + }, + Cmd: exec.Command(path), + Logger: logger, + }) + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + return nil, err + } + + // Request the plugin + raw, err := rpcClient.Dispense(Name) + if err != nil { + return nil, err + } + + // We should have a Function now! This feels like a normal interface + // implementation but is in fact over an RPC connection. + function := raw.(FuncCaller) + return function, nil +} + +func Quit() { + client.Kill() +} diff --git a/plugin-gosdk/plugin/debugtalk.go b/plugin-gosdk/plugin/debugtalk.go new file mode 100644 index 00000000..acf09ec2 --- /dev/null +++ b/plugin-gosdk/plugin/debugtalk.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + + plugin "github.com/httprunner/hrp/plugin-gosdk" +) + +func SumInt(args ...interface{}) (interface{}, error) { + var sum int + for _, arg := range args { + v, ok := arg.(int) + if !ok { + return nil, fmt.Errorf("unexpected type: %T", arg) + } + sum += v + } + return sum, nil +} + +func ConcatenateString(args ...interface{}) (interface{}, error) { + var result string + for _, arg := range args { + v, ok := arg.(string) + if !ok { + return nil, fmt.Errorf("unexpected type: %T", arg) + } + result += v + } + return result, nil +} + +// register functions and build to plugin binary +func main() { + plugin.Register("sum_int", SumInt) + plugin.Register("concatenate_string", ConcatenateString) + plugin.Serve() +} diff --git a/plugin.go b/plugin.go index b64f8a1f..8de40078 100644 --- a/plugin.go +++ b/plugin.go @@ -12,26 +12,28 @@ import ( "github.com/httprunner/hrp/internal/builtin" "github.com/httprunner/hrp/internal/ga" + pluginSDK "github.com/httprunner/hrp/plugin-gosdk" ) type pluginFile string const ( - goPluginFile pluginFile = "debugtalk.so" // built from go plugin - hashicorpGoPluginFile pluginFile = "debugtalk" // built from hashicorp go plugin - hashicorpPyPluginFile pluginFile = "debugtalk.py" + goPluginFile pluginFile = pluginSDK.Name + ".so" // built from go plugin + hashicorpGoPluginFile pluginFile = pluginSDK.Name + ".bin" // built from hashicorp go plugin + hashicorpPyPluginFile pluginFile = pluginSDK.Name + ".py" ) type hrpPlugin interface { - init(path string) error - lookup(funcName string) (reflect.Value, error) // lookup function - // call(funcName string, args ...interface{}) (interface{}, error) - quit() error + 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 type goPlugin struct { *plugin.Plugin + cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance } func (p *goPlugin) init(path string) error { @@ -59,58 +61,93 @@ func (p *goPlugin) init(path string) error { return err } + p.cachedFunctions = make(map[string]reflect.Value) log.Info().Str("path", path).Msg("load go plugin success") return nil } -func (p *goPlugin) lookup(funcName string) (reflect.Value, error) { - if p.Plugin == nil { - return reflect.Value{}, fmt.Errorf("go plugin is not loaded") +func (p *goPlugin) has(funcName string) bool { + fn, ok := p.cachedFunctions[funcName] + if ok { + return fn.IsValid() } sym, err := p.Plugin.Lookup(funcName) if err != nil { - return reflect.Value{}, fmt.Errorf("function %s is not found", funcName) + p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid + return false } - fn := reflect.ValueOf(sym) + fn = reflect.ValueOf(sym) // check function type if fn.Kind() != reflect.Func { - return reflect.Value{}, fmt.Errorf("function %s is invalid", funcName) + p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid + return false } - return fn, nil + p.cachedFunctions[funcName] = fn + return true } func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) { - if p.Plugin == nil { - return nil, fmt.Errorf("go plugin is not loaded") - } - return nil, nil + fn := p.cachedFunctions[funcName] + return callFunc(fn, args...) } func (p *goPlugin) quit() error { + // no need to quit for go plugin return nil } // hashicorpPlugin implements hashicorp/go-plugin type hashicorpPlugin struct { + pluginSDK.FuncCaller + cachedFunctions map[string]bool // cache loaded functions to improve performance } func (p *hashicorpPlugin) init(path string) error { + + f, err := pluginSDK.Init(path) + if err != nil { + log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed") + return err + } + p.FuncCaller = f + + p.cachedFunctions = make(map[string]bool) log.Info().Str("path", path).Msg("load hashicorp go plugin success") return nil } -func (p *hashicorpPlugin) lookup(funcName string) (reflect.Value, error) { - return reflect.Value{}, nil +func (p *hashicorpPlugin) has(funcName string) bool { + flag, ok := p.cachedFunctions[funcName] + if ok { + return flag + } + + funcNames, err := p.GetNames() + if err != nil { + return false + } + + for _, name := range funcNames { + if name == funcName { + p.cachedFunctions[funcName] = true // cache as exists + return true + } + } + + p.cachedFunctions[funcName] = false // cache as not exists + return false } func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) { - return nil, nil + return p.FuncCaller.Call(funcName, args...) } func (p *hashicorpPlugin) quit() error { + // kill hashicorp plugin process + pluginSDK.Quit() return nil } @@ -119,22 +156,23 @@ func (p *parser) initPlugin(path string) error { return nil } - // locate go plugin file - pluginPath, err := locatePlugin(path, goPluginFile) - if err == nil { - // found go plugin file - p.plugin = &goPlugin{} - return p.plugin.init(pluginPath) - } - + // priority: hashicorp plugin > go plugin > builtin functions // locate hashicorp plugin file - pluginPath, err = locatePlugin(path, hashicorpGoPluginFile) + pluginPath, err := locatePlugin(path, hashicorpGoPluginFile) if err == nil { // found hashicorp go plugin file p.plugin = &hashicorpPlugin{} return p.plugin.init(pluginPath) } + // locate go plugin file + pluginPath, err = locatePlugin(path, goPluginFile) + if err == nil { + // found go plugin file + p.plugin = &goPlugin{} + return p.plugin.init(pluginPath) + } + // plugin not found return nil } @@ -176,83 +214,65 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) { return locatePlugin(parentDir, destPluginFile) } -func (p *parser) getMappingFunction(funcName string) (reflect.Value, error) { - if function, ok := p.cachedFunctions[funcName]; ok { - return function, nil - } - - var fn reflect.Value - - // get function from plugin - if p.plugin != nil { - fn, err := p.plugin.lookup(funcName) - if err == nil { - p.cachedFunctions[funcName] = fn - return fn, nil - } - } - - // get builtin function - if function, ok := builtin.Functions[funcName]; ok { - fn = reflect.ValueOf(function) - p.cachedFunctions[funcName] = fn - return fn, nil - } - - // function not found - return reflect.Value{}, fmt.Errorf("function %s is not found", funcName) -} - // callFunc calls function with arguments // only support return at most one result value func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) { - fn, err := p.getMappingFunction(funcName) - if err != nil { - return nil, err + // call with plugin function + if p.plugin != nil && p.plugin.has(funcName) { + return p.plugin.call(funcName, arguments...) } - if fn.Type().NumIn() != len(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 callFunc(fn, arguments...) +} + +// callFunc calls function with arguments +// it is used when calling go plugin or builtin functions +func callFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { + fnArgsNum := fn.Type().NumIn() + if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice { + // last argument is slice, do not check arguments number + // e.g. ...interface{} + // e.g. a, b string, c ...interface{} + } else if fnArgsNum != len(args) { // function arguments not match return nil, fmt.Errorf("function arguments number not match") } + // arguments do not have slice, and arguments number matched - argumentsValue := make([]reflect.Value, len(arguments)) - for index, argument := range arguments { - argumentValue := reflect.ValueOf(argument) - expectArgumentType := fn.Type().In(index) - actualArgumentType := reflect.TypeOf(argument) - - // type match - if expectArgumentType == actualArgumentType { - argumentsValue[index] = argumentValue - continue + argumentsValue := make([]reflect.Value, len(args)) + for index, argument := range args { + if argument == nil { + argumentsValue[index] = reflect.Zero(fn.Type().In(index)) + } else { + argumentsValue[index] = reflect.ValueOf(args[index]) } - - // type not match, check if convertible - if !actualArgumentType.ConvertibleTo(expectArgumentType) { - // function argument type not match and not convertible - err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", - index, expectArgumentType, actualArgumentType) - return nil, err - } - // convert argument to expect type - argumentsValue[index] = argumentValue.Convert(expectArgumentType) } resultValues := fn.Call(argumentsValue) - if len(resultValues) > 1 { - // function should return at most one value - err := fmt.Errorf("function should return at most one value") + if resultValues == nil { + // no returns + return nil, nil + } else if len(resultValues) == 2 { + // return two arguments: interface{}, error + if resultValues[1].Interface() != nil { + return resultValues[0].Interface(), resultValues[1].Interface().(error) + } else { + return resultValues[0].Interface(), nil + } + } else if len(resultValues) == 1 { + // return one arguments: interface{} + return resultValues[0].Interface(), nil + } else { + // return more than 2 arguments, unexpected + err := fmt.Errorf("function should return at most 2 arguments") return nil, err } - - // no return value - if len(resultValues) == 0 { - return nil, nil - } - - // return one value - // convert reflect.Value to interface{} - result := resultValues[0].Interface() - return result, nil } diff --git a/runner.go b/runner.go index 4ba6eaa2..66844d23 100644 --- a/runner.go +++ b/runner.go @@ -155,6 +155,11 @@ func (r *caseRunner) reset() *caseRunner { } func (r *caseRunner) run() error { + defer func() { + if r.parser.plugin != nil { + r.parser.plugin.quit() + } + }() config := r.TestCase.Config if err := r.parseConfig(config); err != nil { return err @@ -497,6 +502,13 @@ func (r *caseRunner) runStepTestCase(step *TStep) (stepResult *stepData, err err func (r *caseRunner) parseConfig(config IConfig) error { cfg := config.ToStruct() + + // init plugin + err := r.parser.initPlugin(cfg.Path) + if err != nil { + return err + } + // parse config variables parsedVariables, err := r.parser.parseVariables(cfg.Variables) if err != nil { @@ -505,12 +517,6 @@ func (r *caseRunner) parseConfig(config IConfig) error { } cfg.Variables = parsedVariables - // load plugin variables and functions - err = r.parser.initPlugin(cfg.Path) - if err != nil { - return err - } - // parse config name parsedName, err := r.parser.parseString(cfg.Name, cfg.Variables) if err != nil { From b8d6192a34cae9f2963d0035cceb1287d5526c5e Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 14 Jan 2022 16:37:36 +0800 Subject: [PATCH 04/13] refactor: plugin functions can be defined in any format --- plugin-gosdk/build.go | 55 ------------- plugin-gosdk/host_test.go | 42 ---------- plugin-gosdk/main.go | 63 -------------- plugin-gosdk/plugin/debugtalk.go | 38 --------- plugin.go | 63 +++----------- plugin/examples/debugtalk.go | 65 +++++++++++++++ plugin/examples/plugin_test.go | 87 ++++++++++++++++++++ plugin/host/host.go | 50 +++++++++++ plugin/plugin.go | 67 +++++++++++++++ plugin/shared/config.go | 15 ++++ {plugin-gosdk => plugin/shared}/interface.go | 14 ++-- plugin/shared/utils.go | 50 +++++++++++ 12 files changed, 350 insertions(+), 259 deletions(-) delete mode 100644 plugin-gosdk/build.go delete mode 100644 plugin-gosdk/host_test.go delete mode 100644 plugin-gosdk/main.go delete mode 100644 plugin-gosdk/plugin/debugtalk.go create mode 100644 plugin/examples/debugtalk.go create mode 100644 plugin/examples/plugin_test.go create mode 100644 plugin/host/host.go create mode 100644 plugin/plugin.go create mode 100644 plugin/shared/config.go rename {plugin-gosdk => plugin/shared}/interface.go (88%) create mode 100644 plugin/shared/utils.go diff --git a/plugin-gosdk/build.go b/plugin-gosdk/build.go deleted file mode 100644 index 53be86d9..00000000 --- a/plugin-gosdk/build.go +++ /dev/null @@ -1,55 +0,0 @@ -package plugin - -import ( - "fmt" - - hclog "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-plugin" -) - -// Here is a real implementation of funcCaller -type functionPlugin struct { - logger hclog.Logger - functions map[string]func(args ...interface{}) (interface{}, error) -} - -func (p *functionPlugin) GetNames() ([]string, error) { - var names []string - for name := range p.functions { - names = append(names, name) - } - return names, nil -} - -func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}, error) { - p.logger.Info("Call function", "funcName", funcName, "args", args) - - f, ok := p.functions[funcName] - if !ok { - return nil, fmt.Errorf("function %s not found", funcName) - } - - return f(args...) -} - -var functions = make(map[string]func(args ...interface{}) (interface{}, error)) - -func Register(funcName string, fn func(args ...interface{}) (interface{}, error)) { - functions[funcName] = fn -} - -func Serve() { - funcPlugin := &functionPlugin{ - logger: logger, - functions: functions, - } - // pluginMap is the map of plugins we can dispense. - var pluginMap = map[string]plugin.Plugin{ - Name: &hashicorpPlugin{Impl: funcPlugin}, - } - - plugin.Serve(&plugin.ServeConfig{ - HandshakeConfig: handshakeConfig, - Plugins: pluginMap, - }) -} diff --git a/plugin-gosdk/host_test.go b/plugin-gosdk/host_test.go deleted file mode 100644 index 8927a698..00000000 --- a/plugin-gosdk/host_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package plugin - -import ( - "fmt" - "os" - "os/exec" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMain(m *testing.M) { - fmt.Println("[TestMain] build go plugin") - cmd := exec.Command("go", "build", "-o=debugtalk", "plugin/debugtalk.go") - if err := cmd.Run(); err != nil { - panic(err) - } - os.Exit(m.Run()) -} - -func TestInitHashicorpPlugin(t *testing.T) { - f, err := Init("./debugtalk") - if err != nil { - t.Fatal(err) - } - defer Quit() - - v, err := f.Call("sum_int", 1, 2, 3, 4) - if err != nil { - t.Fatal(err) - } - if !assert.Equal(t, 10, v) { - t.Fail() - } - v, err = f.Call("concatenate_string", "a", "b", "c") - if err != nil { - t.Fatal(err) - } - if !assert.Equal(t, "abc", v) { - t.Fail() - } -} diff --git a/plugin-gosdk/main.go b/plugin-gosdk/main.go deleted file mode 100644 index 73b7f8d3..00000000 --- a/plugin-gosdk/main.go +++ /dev/null @@ -1,63 +0,0 @@ -package plugin - -import ( - "os" - "os/exec" - - hclog "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-plugin" -) - -const Name = "debugtalk" - -// handshakeConfigs are used to just do a basic handshake between -// a plugin and host. If the handshake fails, a user friendly error is shown. -// This prevents users from executing bad plugins or executing a plugin -// directory. It is a UX feature, not a security feature. -var handshakeConfig = plugin.HandshakeConfig{ - ProtocolVersion: 1, - MagicCookieKey: "HttpRunnerPlus", - MagicCookieValue: Name, -} - -// Create an hclog.Logger -var logger = hclog.New(&hclog.LoggerOptions{ - Name: Name, - Output: os.Stdout, - Level: hclog.Debug, -}) - -var client *plugin.Client - -func Init(path string) (FuncCaller, error) { - // launch the plugin process - client = plugin.NewClient(&plugin.ClientConfig{ - HandshakeConfig: handshakeConfig, - Plugins: map[string]plugin.Plugin{ - Name: &hashicorpPlugin{}, - }, - Cmd: exec.Command(path), - Logger: logger, - }) - - // Connect via RPC - rpcClient, err := client.Client() - if err != nil { - return nil, err - } - - // Request the plugin - raw, err := rpcClient.Dispense(Name) - if err != nil { - return nil, err - } - - // We should have a Function now! This feels like a normal interface - // implementation but is in fact over an RPC connection. - function := raw.(FuncCaller) - return function, nil -} - -func Quit() { - client.Kill() -} diff --git a/plugin-gosdk/plugin/debugtalk.go b/plugin-gosdk/plugin/debugtalk.go deleted file mode 100644 index acf09ec2..00000000 --- a/plugin-gosdk/plugin/debugtalk.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "fmt" - - plugin "github.com/httprunner/hrp/plugin-gosdk" -) - -func SumInt(args ...interface{}) (interface{}, error) { - var sum int - for _, arg := range args { - v, ok := arg.(int) - if !ok { - return nil, fmt.Errorf("unexpected type: %T", arg) - } - sum += v - } - return sum, nil -} - -func ConcatenateString(args ...interface{}) (interface{}, error) { - var result string - for _, arg := range args { - v, ok := arg.(string) - if !ok { - return nil, fmt.Errorf("unexpected type: %T", arg) - } - result += v - } - return result, nil -} - -// register functions and build to plugin binary -func main() { - plugin.Register("sum_int", SumInt) - plugin.Register("concatenate_string", ConcatenateString) - plugin.Serve() -} diff --git a/plugin.go b/plugin.go index 8de40078..389d58e3 100644 --- a/plugin.go +++ b/plugin.go @@ -12,15 +12,16 @@ import ( "github.com/httprunner/hrp/internal/builtin" "github.com/httprunner/hrp/internal/ga" - pluginSDK "github.com/httprunner/hrp/plugin-gosdk" + pluginHost "github.com/httprunner/hrp/plugin/host" + pluginShared "github.com/httprunner/hrp/plugin/shared" ) type pluginFile string const ( - goPluginFile pluginFile = pluginSDK.Name + ".so" // built from go plugin - hashicorpGoPluginFile pluginFile = pluginSDK.Name + ".bin" // built from hashicorp go plugin - hashicorpPyPluginFile pluginFile = pluginSDK.Name + ".py" + goPluginFile pluginFile = pluginShared.Name + ".so" // built from go plugin + hashicorpGoPluginFile pluginFile = pluginShared.Name + ".bin" // built from hashicorp go plugin + hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py" ) type hrpPlugin interface { @@ -91,7 +92,7 @@ func (p *goPlugin) has(funcName string) bool { func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) { fn := p.cachedFunctions[funcName] - return callFunc(fn, args...) + return pluginShared.CallFunc(fn, args...) } func (p *goPlugin) quit() error { @@ -101,13 +102,13 @@ func (p *goPlugin) quit() error { // hashicorpPlugin implements hashicorp/go-plugin type hashicorpPlugin struct { - pluginSDK.FuncCaller + pluginShared.FuncCaller cachedFunctions map[string]bool // cache loaded functions to improve performance } func (p *hashicorpPlugin) init(path string) error { - f, err := pluginSDK.Init(path) + f, err := pluginHost.Init(path) if err != nil { log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed") return err @@ -147,7 +148,7 @@ func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{ func (p *hashicorpPlugin) quit() error { // kill hashicorp plugin process - pluginSDK.Quit() + pluginHost.Quit() return nil } @@ -230,49 +231,5 @@ func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{ fn := reflect.ValueOf(function) // call with builtin function - return callFunc(fn, arguments...) -} - -// callFunc calls function with arguments -// it is used when calling go plugin or builtin functions -func callFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { - fnArgsNum := fn.Type().NumIn() - if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice { - // last argument is slice, do not check arguments number - // e.g. ...interface{} - // e.g. a, b string, c ...interface{} - } else if fnArgsNum != len(args) { - // function arguments not match - return nil, fmt.Errorf("function arguments number not match") - } - // arguments do not have slice, and arguments number matched - - argumentsValue := make([]reflect.Value, len(args)) - for index, argument := range args { - if argument == nil { - argumentsValue[index] = reflect.Zero(fn.Type().In(index)) - } else { - argumentsValue[index] = reflect.ValueOf(args[index]) - } - } - - resultValues := fn.Call(argumentsValue) - if resultValues == nil { - // no returns - return nil, nil - } else if len(resultValues) == 2 { - // return two arguments: interface{}, error - if resultValues[1].Interface() != nil { - return resultValues[0].Interface(), resultValues[1].Interface().(error) - } else { - return resultValues[0].Interface(), nil - } - } else if len(resultValues) == 1 { - // return one arguments: interface{} - return resultValues[0].Interface(), nil - } else { - // return more than 2 arguments, unexpected - err := fmt.Errorf("function should return at most 2 arguments") - return nil, err - } + return pluginShared.CallFunc(fn, arguments...) } diff --git a/plugin/examples/debugtalk.go b/plugin/examples/debugtalk.go new file mode 100644 index 00000000..d9d52d6d --- /dev/null +++ b/plugin/examples/debugtalk.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + + plugin "github.com/httprunner/hrp/plugin" +) + +func SumTwoInt(a, b int) int { + return a + b +} + +func SumInts(args ...int) int { + var sum int + for _, arg := range args { + sum += arg + } + return sum +} + +func Sum(args ...interface{}) (interface{}, error) { + var sum float64 + for _, arg := range args { + switch v := arg.(type) { + case int: + sum += float64(v) + case float64: + sum += v + default: + return nil, fmt.Errorf("unexpected type: %T", arg) + } + } + return sum, nil +} + +func SumTwoString(a, b string) string { + return a + b +} + +func SumStrings(s ...string) string { + var sum string + for _, arg := range s { + sum += arg + } + return sum +} + +func Concatenate(args ...interface{}) (interface{}, error) { + var result string + for _, arg := range args { + result += fmt.Sprintf("%v", arg) + } + return result, nil +} + +// register functions and build to plugin binary +func main() { + plugin.Register("sum_ints", SumInts) + plugin.Register("sum_two_int", SumTwoInt) + plugin.Register("sum", Sum) + plugin.Register("sum_two_string", SumTwoString) + plugin.Register("sum_strings", SumStrings) + plugin.Register("concatenate", Concatenate) + plugin.Serve() +} diff --git a/plugin/examples/plugin_test.go b/plugin/examples/plugin_test.go new file mode 100644 index 00000000..2164ceee --- /dev/null +++ b/plugin/examples/plugin_test.go @@ -0,0 +1,87 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/httprunner/hrp/plugin/host" +) + +func TestMain(m *testing.M) { + fmt.Println("[TestMain] build go plugin") + cmd := exec.Command("go", "build", "-o=debugtalk.bin", "./debugtalk.go") + if err := cmd.Run(); err != nil { + panic(err) + } + os.Exit(m.Run()) +} + +func TestInitHashicorpPlugin(t *testing.T) { + f, err := host.Init("./debugtalk.bin") + if err != nil { + t.Fatal(err) + } + defer host.Quit() + + v1, err := f.GetNames() + if err != nil { + t.Fatal(err) + } + if !assert.Contains(t, v1, "sum_ints") { + t.Fatal(err) + } + if !assert.Contains(t, v1, "concatenate") { + t.Fatal(err) + } + + var v2 interface{} + v2, err = f.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) + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, 3, v2) { + t.Fail() + } + v2, err = f.Call("sum", 1, 2, 3.4, 5) + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, 11.4, v2) { + t.Fail() + } + + var v3 interface{} + v3, err = f.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") + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, "abc", v3) { + t.Fail() + } + + v3, err = f.Call("concatenate", "a", 2, "c", 3.4) + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, "a2c3.4", v3) { + t.Fail() + } +} diff --git a/plugin/host/host.go b/plugin/host/host.go new file mode 100644 index 00000000..e815c7e5 --- /dev/null +++ b/plugin/host/host.go @@ -0,0 +1,50 @@ +package host + +import ( + "os" + "os/exec" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + + "github.com/httprunner/hrp/plugin/shared" +) + +var client *plugin.Client + +func Init(path string) (shared.FuncCaller, error) { + // launch the plugin process + client = plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: shared.HandshakeConfig, + Plugins: map[string]plugin.Plugin{ + shared.Name: &shared.HashicorpPlugin{}, + }, + Cmd: exec.Command(path), + Logger: hclog.New(&hclog.LoggerOptions{ + Name: shared.Name, + Output: os.Stdout, + Level: hclog.Info, + }), + }) + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + return nil, err + } + + // Request the plugin + raw, err := rpcClient.Dispense(shared.Name) + if err != nil { + return nil, err + } + + // We should have a Function now! This feels like a normal interface + // implementation but is in fact over an RPC connection. + function := raw.(shared.FuncCaller) + return function, nil +} + +func Quit() { + client.Kill() +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 00000000..c9301795 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,67 @@ +package plugin + +import ( + "fmt" + "os" + "reflect" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + + pluginShared "github.com/httprunner/hrp/plugin/shared" +) + +// functionsMap stores plugin functions +type functionsMap map[string]reflect.Value + +// functionPlugin implements the FuncCaller interface +type functionPlugin struct { + logger hclog.Logger + functions functionsMap +} + +func (p *functionPlugin) GetNames() ([]string, error) { + var names []string + for name := range p.functions { + names = append(names, name) + } + return names, nil +} + +func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}, error) { + p.logger.Info("call function", "funcName", funcName, "args", args) + + fn, ok := p.functions[funcName] + if !ok { + return nil, fmt.Errorf("function %s not found", funcName) + } + + return pluginShared.CallFunc(fn, args...) +} + +var functions = make(functionsMap) + +func Register(funcName string, fn interface{}) { + if _, ok := functions[funcName]; ok { + return + } + functions[funcName] = reflect.ValueOf(fn) +} + +func Serve() { + funcPlugin := &functionPlugin{ + logger: hclog.New(&hclog.LoggerOptions{ + Name: pluginShared.Name, + Output: os.Stdout, + Level: hclog.Info, + }), + functions: functions, + } + var pluginMap = map[string]plugin.Plugin{ + pluginShared.Name: &pluginShared.HashicorpPlugin{Impl: funcPlugin}, + } + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: pluginShared.HandshakeConfig, + Plugins: pluginMap, + }) +} diff --git a/plugin/shared/config.go b/plugin/shared/config.go new file mode 100644 index 00000000..423688b1 --- /dev/null +++ b/plugin/shared/config.go @@ -0,0 +1,15 @@ +package shared + +import "github.com/hashicorp/go-plugin" + +const Name = "debugtalk" + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. If the handshake fails, a user friendly error is shown. +// This prevents users from executing bad plugins or executing a plugin +// directory. It is a UX feature, not a security feature. +var HandshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "HttpRunnerPlus", + MagicCookieValue: Name, +} diff --git a/plugin-gosdk/interface.go b/plugin/shared/interface.go similarity index 88% rename from plugin-gosdk/interface.go rename to plugin/shared/interface.go index 5853e397..9f334808 100644 --- a/plugin-gosdk/interface.go +++ b/plugin/shared/interface.go @@ -1,4 +1,4 @@ -package plugin +package shared import ( "encoding/gob" @@ -83,23 +83,21 @@ func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error { var err error *resp, err = s.Impl.Call(f.Name, f.Args...) if err != nil { - log.Error().Err(err). - Interface("args", args). - Msg("function execution failed") + log.Error().Err(err).Interface("args", args).Msg("function execution failed") return err } return nil } -// hashicorpPlugin implements hashicorp's plugin.Plugin. -type hashicorpPlugin struct { +// HashicorpPlugin implements hashicorp's plugin.Plugin. +type HashicorpPlugin struct { Impl FuncCaller } -func (p *hashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) { +func (p *HashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) { return &functionRPCServer{Impl: p.Impl}, nil } -func (hashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { +func (HashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { return &functionRPC{client: c}, nil } diff --git a/plugin/shared/utils.go b/plugin/shared/utils.go new file mode 100644 index 00000000..a33a060b --- /dev/null +++ b/plugin/shared/utils.go @@ -0,0 +1,50 @@ +package shared + +import ( + "fmt" + "reflect" +) + +// CallFunc calls function with arguments +// it is used when calling go plugin or builtin functions +func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { + fnArgsNum := fn.Type().NumIn() + if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice { + // last argument is slice, do not check arguments number + // e.g. ...interface{} + // e.g. a, b string, c ...interface{} + } else if fnArgsNum != len(args) { + // function arguments not match + return nil, fmt.Errorf("function arguments number not match, expect %d, got %d", fnArgsNum, len(args)) + } + // arguments do not have slice, and arguments number matched + + argumentsValue := make([]reflect.Value, len(args)) + for index, argument := range args { + if argument == nil { + argumentsValue[index] = reflect.Zero(fn.Type().In(index)) + } else { + argumentsValue[index] = reflect.ValueOf(args[index]) + } + } + + resultValues := fn.Call(argumentsValue) + if resultValues == nil { + // no returns + return nil, nil + } else if len(resultValues) == 2 { + // return two arguments: interface{}, error + if resultValues[1].Interface() != nil { + return resultValues[0].Interface(), resultValues[1].Interface().(error) + } else { + return resultValues[0].Interface(), nil + } + } else if len(resultValues) == 1 { + // return one arguments: interface{} + return resultValues[0].Interface(), nil + } else { + // return more than 2 arguments, unexpected + err := fmt.Errorf("function should return at most 2 arguments") + return nil, err + } +} From 7af6b8419f7a00349825f1dd91b4cf5bd82536fc Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 14 Jan 2022 17:28:03 +0800 Subject: [PATCH 05/13] change: init module github.com/httprunner/hrp/plugin --- docs/CHANGELOG.md | 2 +- plugin/go.mod | 10 ++++ plugin/go.sum | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 plugin/go.mod create mode 100644 plugin/go.sum diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ba2df60c..dfeec493 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v0.5.2 (2022-01-13) +## v0.5.2 (2022-01-14) - feat: support creating and calling custom functions with [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin) diff --git a/plugin/go.mod b/plugin/go.mod new file mode 100644 index 00000000..f5af4505 --- /dev/null +++ b/plugin/go.mod @@ -0,0 +1,10 @@ +module github.com/httprunner/hrp/plugin + +go 1.16 + +require ( + github.com/hashicorp/go-hclog v1.1.0 + github.com/hashicorp/go-plugin v1.4.3 + github.com/rs/zerolog v1.26.1 + github.com/stretchr/testify v1.7.0 +) diff --git a/plugin/go.sum b/plugin/go.sum new file mode 100644 index 00000000..0e146002 --- /dev/null +++ b/plugin/go.sum @@ -0,0 +1,118 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= +github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +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/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 5c647c2435c102deec39d1799f0f634e1045a025 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 14 Jan 2022 17:44:41 +0800 Subject: [PATCH 06/13] change: update sub module --- go.mod | 5 ++--- go.sum | 7 +++++-- plugin/examples/debugtalk.go | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ba72d455..3652c03a 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,7 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/getsentry/sentry-go v0.11.0 github.com/google/uuid v1.3.0 - github.com/hashicorp/go-hclog v1.1.0 - github.com/hashicorp/go-plugin v1.4.3 + github.com/httprunner/hrp/plugin v0.0.0-20220114094051-8dedd4883f5c github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/maja42/goval v1.2.1 @@ -15,7 +14,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.11.0 - github.com/rs/zerolog v1.26.0 + github.com/rs/zerolog v1.26.1 github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect diff --git a/go.sum b/go.sum index cbd318a5..63f2f64c 100644 --- a/go.sum +++ b/go.sum @@ -231,6 +231,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/httprunner/hrp/plugin v0.0.0-20220114094051-8dedd4883f5c h1:A1e68FJBaVtIcT1YlJtWqs0nXEwLYKJam1Er/pdg7BA= +github.com/httprunner/hrp/plugin v0.0.0-20220114094051-8dedd4883f5c/go.mod h1:64amelrDUjb+V53VgGU716+x7W6MHIU19g8pBurrXTs= 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= @@ -370,8 +372,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= -github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -459,6 +461,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/plugin/examples/debugtalk.go b/plugin/examples/debugtalk.go index d9d52d6d..16a3cc2e 100644 --- a/plugin/examples/debugtalk.go +++ b/plugin/examples/debugtalk.go @@ -3,7 +3,7 @@ package main import ( "fmt" - plugin "github.com/httprunner/hrp/plugin" + "github.com/httprunner/hrp/plugin" ) func SumTwoInt(a, b int) int { From a0de8106d7ea2ecb841a76e6db37f91d09c1eb04 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 14 Jan 2022 17:52:23 +0800 Subject: [PATCH 07/13] change: update docs --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 8ccee28e..b531e439 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 13-Jan-2022 +###### Auto generated by spf13/cobra on 14-Jan-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 1d0a6263..dcd60c35 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -39,4 +39,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 13-Jan-2022 +###### Auto generated by spf13/cobra on 14-Jan-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 6f57a509..7dedd529 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 13-Jan-2022 +###### Auto generated by spf13/cobra on 14-Jan-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 9cce4aaf..df670831 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -31,4 +31,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 13-Jan-2022 +###### Auto generated by spf13/cobra on 14-Jan-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 736d31d6..213cdacb 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 13-Jan-2022 +###### Auto generated by spf13/cobra on 14-Jan-2022 From 875f2fadd36d3ef9e3b9b51bf858f71d168c62f8 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 14 Jan 2022 17:53:06 +0800 Subject: [PATCH 08/13] refactor: hashicorp tests --- examples/plugin/debugtalk.go | 43 +++++++++--- examples/plugin/hashicorp.go | 14 ++++ go_plugin_test.go | 10 ++- ...plugin_test.go => hashicorp_plugin_test.go | 24 ++++--- plugin/examples/debugtalk.go | 65 ------------------- plugin/shared/{utils.go => call.go} | 33 ++++++++-- plugin/shared/call_test.go | 62 ++++++++++++++++++ 7 files changed, 161 insertions(+), 90 deletions(-) create mode 100644 examples/plugin/hashicorp.go rename plugin/examples/plugin_test.go => hashicorp_plugin_test.go (73%) delete mode 100644 plugin/examples/debugtalk.go rename plugin/shared/{utils.go => call.go} (60%) create mode 100644 plugin/shared/call_test.go diff --git a/examples/plugin/debugtalk.go b/examples/plugin/debugtalk.go index dbe3bf7a..ff8d115f 100644 --- a/examples/plugin/debugtalk.go +++ b/examples/plugin/debugtalk.go @@ -9,26 +9,49 @@ func init() { log.Println("plugin init function called") } -func SumInt(args ...interface{}) (interface{}, error) { +func SumTwoInt(a, b int) int { + return a + b +} + +func SumInts(args ...int) int { var sum int for _, arg := range args { - v, ok := arg.(int) - if !ok { + sum += arg + } + return sum +} + +func Sum(args ...interface{}) (interface{}, error) { + var sum float64 + for _, arg := range args { + switch v := arg.(type) { + case int: + sum += float64(v) + case float64: + sum += v + default: return nil, fmt.Errorf("unexpected type: %T", arg) } - sum += v } return sum, nil } -func ConcatenateString(args ...interface{}) (interface{}, error) { +func SumTwoString(a, b string) string { + return a + b +} + +func SumStrings(s ...string) string { + var sum string + for _, arg := range s { + sum += arg + } + return sum +} + +func Concatenate(args ...interface{}) (interface{}, error) { var result string for _, arg := range args { - v, ok := arg.(string) - if !ok { - return nil, fmt.Errorf("unexpected type: %T", arg) - } - result += v + result += fmt.Sprintf("%v", arg) } return result, nil } diff --git a/examples/plugin/hashicorp.go b/examples/plugin/hashicorp.go new file mode 100644 index 00000000..8bae45a3 --- /dev/null +++ b/examples/plugin/hashicorp.go @@ -0,0 +1,14 @@ +package main + +import "github.com/httprunner/hrp/plugin" + +// register functions and build to plugin binary +func main() { + plugin.Register("sum_ints", SumInts) + plugin.Register("sum_two_int", SumTwoInt) + plugin.Register("sum", Sum) + plugin.Register("sum_two_string", SumTwoString) + plugin.Register("sum_strings", SumStrings) + plugin.Register("concatenate", Concatenate) + plugin.Serve() +} diff --git a/go_plugin_test.go b/go_plugin_test.go index c0384242..6183c7ea 100644 --- a/go_plugin_test.go +++ b/go_plugin_test.go @@ -15,7 +15,8 @@ import ( 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") + cmd := exec.Command("go", "build", "-buildmode=plugin", "-race", + "-o=examples/debugtalk.so", "examples/plugin/debugtalk.go") if err := cmd.Run(); err != nil { panic(err) } @@ -78,10 +79,13 @@ func TestCallPluginFunction(t *testing.T) { defer removeGoPlugin() parser := newParser() - parser.initPlugin("examples/debugtalk.so") + err := parser.initPlugin("examples/debugtalk.so") + if err != nil { + t.Fatal(err) + } // call function without arguments - result, err := parser.callFunc("ConcatenateString", "1", "2", "3.14") + result, err := parser.callFunc("Concatenate", "1", 2, "3.14") if !assert.NoError(t, err) { t.Fail() } diff --git a/plugin/examples/plugin_test.go b/hashicorp_plugin_test.go similarity index 73% rename from plugin/examples/plugin_test.go rename to hashicorp_plugin_test.go index 2164ceee..c9edf994 100644 --- a/plugin/examples/plugin_test.go +++ b/hashicorp_plugin_test.go @@ -1,4 +1,4 @@ -package main +package hrp import ( "fmt" @@ -6,22 +6,30 @@ import ( "os/exec" "testing" - "github.com/stretchr/testify/assert" - "github.com/httprunner/hrp/plugin/host" + "github.com/stretchr/testify/assert" ) -func TestMain(m *testing.M) { - fmt.Println("[TestMain] build go plugin") - cmd := exec.Command("go", "build", "-o=debugtalk.bin", "./debugtalk.go") +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") if err := cmd.Run(); err != nil { panic(err) } - os.Exit(m.Run()) +} + +func removeHashicorpPlugin() { + fmt.Println("[teardown] remove hashicorp plugin") + os.Remove("examples/debugtalk.bin") } func TestInitHashicorpPlugin(t *testing.T) { - f, err := host.Init("./debugtalk.bin") + buildHashicorpPlugin() + defer removeHashicorpPlugin() + + f, err := host.Init("examples/debugtalk.bin") if err != nil { t.Fatal(err) } diff --git a/plugin/examples/debugtalk.go b/plugin/examples/debugtalk.go deleted file mode 100644 index 16a3cc2e..00000000 --- a/plugin/examples/debugtalk.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/httprunner/hrp/plugin" -) - -func SumTwoInt(a, b int) int { - return a + b -} - -func SumInts(args ...int) int { - var sum int - for _, arg := range args { - sum += arg - } - return sum -} - -func Sum(args ...interface{}) (interface{}, error) { - var sum float64 - for _, arg := range args { - switch v := arg.(type) { - case int: - sum += float64(v) - case float64: - sum += v - default: - return nil, fmt.Errorf("unexpected type: %T", arg) - } - } - return sum, nil -} - -func SumTwoString(a, b string) string { - return a + b -} - -func SumStrings(s ...string) string { - var sum string - for _, arg := range s { - sum += arg - } - return sum -} - -func Concatenate(args ...interface{}) (interface{}, error) { - var result string - for _, arg := range args { - result += fmt.Sprintf("%v", arg) - } - return result, nil -} - -// register functions and build to plugin binary -func main() { - plugin.Register("sum_ints", SumInts) - plugin.Register("sum_two_int", SumTwoInt) - plugin.Register("sum", Sum) - plugin.Register("sum_two_string", SumTwoString) - plugin.Register("sum_strings", SumStrings) - plugin.Register("concatenate", Concatenate) - plugin.Serve() -} diff --git a/plugin/shared/utils.go b/plugin/shared/call.go similarity index 60% rename from plugin/shared/utils.go rename to plugin/shared/call.go index a33a060b..b0c58ee1 100644 --- a/plugin/shared/utils.go +++ b/plugin/shared/call.go @@ -23,9 +23,28 @@ func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { for index, argument := range args { if argument == nil { argumentsValue[index] = reflect.Zero(fn.Type().In(index)) - } else { - argumentsValue[index] = reflect.ValueOf(args[index]) + continue } + + argumentValue := reflect.ValueOf(argument) + expectArgumentType := fn.Type().In(index) + actualArgumentType := reflect.TypeOf(argument) + + // type match + if expectArgumentType == actualArgumentType { + argumentsValue[index] = argumentValue + continue + } + + // type not match, check if convertible + if !actualArgumentType.ConvertibleTo(expectArgumentType) { + // function argument type not match and not convertible + err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", + index, expectArgumentType, actualArgumentType) + return nil, err + } + // convert argument to expect type + argumentsValue[index] = argumentValue.Convert(expectArgumentType) } resultValues := fn.Call(argumentsValue) @@ -40,8 +59,14 @@ func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { return resultValues[0].Interface(), nil } } else if len(resultValues) == 1 { - // return one arguments: interface{} - return resultValues[0].Interface(), nil + // return one argument + if err, ok := resultValues[0].Interface().(error); ok { + // return error + return nil, err + } else { + // return interface{} + return resultValues[0].Interface(), nil + } } else { // return more than 2 arguments, unexpected err := fmt.Errorf("function should return at most 2 arguments") diff --git a/plugin/shared/call_test.go b/plugin/shared/call_test.go new file mode 100644 index 00000000..9b19933a --- /dev/null +++ b/plugin/shared/call_test.go @@ -0,0 +1,62 @@ +package shared + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCallFunc(t *testing.T) { + type data struct { + f interface{} + args []interface{} + expVal interface{} + expErr error + } + params := []data{ + // zero argument, zero return + {f: func() {}, args: []interface{}{}, expVal: nil, expErr: nil}, + // zero argument, return one value + {f: func() int { return 1 }, args: []interface{}{}, expVal: 1, expErr: nil}, + {f: func() string { return "a" }, args: []interface{}{}, expVal: "a", expErr: nil}, + {f: func() interface{} { return 1.23 }, args: []interface{}{}, expVal: 1.23, expErr: nil}, + // zero argument, return error + {f: func() error { return errors.New("xxx") }, args: []interface{}{}, expVal: nil, expErr: errors.New("xxx")}, + // zero argument, return one value and error + {f: func() (int, error) { return 1, errors.New("xxx") }, args: []interface{}{}, expVal: 1, expErr: errors.New("xxx")}, + {f: func() (interface{}, error) { return 1.23, errors.New("xxx") }, args: []interface{}{}, expVal: 1.23, expErr: errors.New("xxx")}, + // one argument, return one value + {f: func(n int) int { return n * n }, args: []interface{}{2}, expVal: 4}, + {f: func(c string) string { return c + c }, args: []interface{}{"p"}, expVal: "pp"}, + {f: func(arg interface{}) interface{} { return fmt.Sprintf("%v", arg) }, args: []interface{}{1.23}, expVal: "1.23"}, + // two arguments in same type + {f: func(a, b int) int { return a * b }, args: []interface{}{2, 3}, expVal: 6}, + // two arguments in different type + { + f: func(n int, c string) string { + var s string + for i := 0; i < n; i++ { + s += c + } + return s + }, + args: []interface{}{3, "p"}, + expVal: "ppp", + }, + } + + for _, p := range params { + fn := reflect.ValueOf(p.f) + val, err := CallFunc(fn, p.args...) + if !assert.Equal(t, p.expErr, err) { + t.Fatal(err) + } + if !assert.Equal(t, p.expVal, val) { + t.Fatal() + } + } + +} From f41c4896990d757c27bcbd15054d1f049dc3b324 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 15 Jan 2022 21:39:48 +0800 Subject: [PATCH 09/13] fix: unittests for CallFunc --- plugin/shared/call.go | 103 +++++++++++++++++++++++---- plugin/shared/call_test.go | 138 +++++++++++++++++++++++++++++++++++-- 2 files changed, 221 insertions(+), 20 deletions(-) diff --git a/plugin/shared/call.go b/plugin/shared/call.go index b0c58ee1..49cafad4 100644 --- a/plugin/shared/call.go +++ b/plugin/shared/call.go @@ -3,24 +3,97 @@ package shared import ( "fmt" "reflect" + + "github.com/rs/zerolog/log" ) // CallFunc calls function with arguments // it is used when calling go plugin or builtin functions func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { fnArgsNum := fn.Type().NumIn() - if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice { - // last argument is slice, do not check arguments number - // e.g. ...interface{} - // e.g. a, b string, c ...interface{} - } else if fnArgsNum != len(args) { - // function arguments not match - return nil, fmt.Errorf("function arguments number not match, expect %d, got %d", fnArgsNum, len(args)) - } - // arguments do not have slice, and arguments number matched - argumentsValue := make([]reflect.Value, len(args)) - for index, argument := range args { + // function expect 0 argument + if fnArgsNum == 0 { + if len(args) > 0 { + return nil, fmt.Errorf("function expect 0 argument, but got %d", len(args)) + } + return call(fn, nil) + } + + // fnArgsNum > 0: function expect more than 0 arguments + // function last argument is not slice + if fn.Type().In(fnArgsNum-1).Kind() != reflect.Slice { + // function arguments should match exactly + if fnArgsNum != len(args) { + return nil, fmt.Errorf("function expect %d arguments, but got %d", fnArgsNum, len(args)) + } + // prepare arguments + argumentsValue, err := convertFirstNArgs(fn, fnArgsNum, args...) + if err != nil { + return nil, err + } + return call(fn, argumentsValue) + } + + // function last argument is slice + // e.g. + // ...interface{} + // a, b string, c ...interface{} + // []int + // a, b string, c []int + + // arguments number should >= fnArgsNum-1 + if len(args) < fnArgsNum-1 { + return nil, fmt.Errorf("function expect at least %d arguments, but got %d", fnArgsNum-1, len(args)) + } + + // prepare first n-1 arguments + argumentsValue, err := convertFirstNArgs(fn, fnArgsNum-1, args...) + if err != nil { + return nil, err + } + + // prepare last slice argument + fnLastSliceElemType := fn.Type().In(fnArgsNum - 1).Elem() // slice element type + for i := fnArgsNum - 1; i < len(args); i++ { // loop last len(args)-fnArgsNum+1 arguments + argValue := reflect.ValueOf(args[i]) + argValueType := reflect.TypeOf(args[i]) + + // check if slice element type match + if fnLastSliceElemType == argValueType { + argumentsValue = append(argumentsValue, argValue) + continue + } + + // last argument is also slice, e.g. []int + if argValueType.Kind() == reflect.Slice { + if argValueType.Elem() != fnLastSliceElemType { + err := fmt.Errorf("function argument %d's slice element type is not match, expect %v, actual %v", + i, fnLastSliceElemType, argValueType) + return nil, err + } + argumentsValue = append(argumentsValue, argValue) + continue + } + + // type not match, check if convertible + if !argValueType.ConvertibleTo(fnLastSliceElemType) { + // function argument type not match and not convertible + err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", + i, fnLastSliceElemType, argValueType) + return nil, err + } + // convert argument to expect type + argumentsValue = append(argumentsValue, argValue.Convert(fnLastSliceElemType)) + } + + return call(fn, argumentsValue) +} + +func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect.Value, error) { + argumentsValue := make([]reflect.Value, n) + for index := 0; index < n; index++ { + argument := args[index] if argument == nil { argumentsValue[index] = reflect.Zero(fn.Type().In(index)) continue @@ -41,13 +114,17 @@ func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { // function argument type not match and not convertible err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", index, expectArgumentType, actualArgumentType) + log.Error().Err(err).Msg("convert arguments failed") return nil, err } // convert argument to expect type argumentsValue[index] = argumentValue.Convert(expectArgumentType) } + return argumentsValue, nil +} - resultValues := fn.Call(argumentsValue) +func call(fn reflect.Value, args []reflect.Value) (interface{}, error) { + resultValues := fn.Call(args) if resultValues == nil { // no returns return nil, nil @@ -69,7 +146,7 @@ func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { } } else { // return more than 2 arguments, unexpected - err := fmt.Errorf("function should return at most 2 arguments") + err := fmt.Errorf("function should return at most 2 values") return nil, err } } diff --git a/plugin/shared/call_test.go b/plugin/shared/call_test.go index 9b19933a..4f45fa70 100644 --- a/plugin/shared/call_test.go +++ b/plugin/shared/call_test.go @@ -9,13 +9,14 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCallFunc(t *testing.T) { - type data struct { - f interface{} - args []interface{} - expVal interface{} - expErr error - } +type data struct { + f interface{} + args []interface{} + expVal interface{} + expErr error +} + +func TestCallFuncBasic(t *testing.T) { params := []data{ // zero argument, zero return {f: func() {}, args: []interface{}{}, expVal: nil, expErr: nil}, @@ -28,10 +29,16 @@ func TestCallFunc(t *testing.T) { // zero argument, return one value and error {f: func() (int, error) { return 1, errors.New("xxx") }, args: []interface{}{}, expVal: 1, expErr: errors.New("xxx")}, {f: func() (interface{}, error) { return 1.23, errors.New("xxx") }, args: []interface{}{}, expVal: 1.23, expErr: errors.New("xxx")}, + + // one argument, zero return + {f: func(n int) {}, args: []interface{}{1}, expVal: nil, expErr: nil}, // one argument, return one value {f: func(n int) int { return n * n }, args: []interface{}{2}, expVal: 4}, {f: func(c string) string { return c + c }, args: []interface{}{"p"}, expVal: "pp"}, {f: func(arg interface{}) interface{} { return fmt.Sprintf("%v", arg) }, args: []interface{}{1.23}, expVal: "1.23"}, + // one argument, return one value and error + {f: func(arg interface{}) (interface{}, error) { return 1.23, errors.New("xxx") }, args: []interface{}{"a"}, expVal: 1.23, expErr: errors.New("xxx")}, + // two arguments in same type {f: func(a, b int) int { return a * b }, args: []interface{}{2, 3}, expVal: 6}, // two arguments in different type @@ -46,6 +53,123 @@ func TestCallFunc(t *testing.T) { args: []interface{}{3, "p"}, expVal: "ppp", }, + + // variable arguments list: ...int, ...interface{} + { + f: func(n ...int) int { + var sum int + for _, arg := range n { + sum += arg + } + return sum + }, + args: []interface{}{1, 2, 3}, + expVal: 6, + }, + { + f: func(args ...interface{}) (interface{}, error) { + var result string + for _, arg := range args { + result += fmt.Sprintf("%v", arg) + } + return result, nil + }, + args: []interface{}{1, 2.3, "4.5", "p"}, + expVal: "12.34.5p", + }, + { + f: func(a, b int8, n ...int) int { + var sum int + for _, arg := range n { + sum += arg + } + sum += int(a) + int(b) + return sum + }, + args: []interface{}{1, 2, 3, 4.5}, + expVal: 10, + }, + { + f: func(a, b int8, n ...int) int { + sum := int(a) + int(b) + for _, arg := range n { + sum += arg + } + return sum + }, + args: []interface{}{1, 2}, + expVal: 3, + }, + + { + f: func(a []int, n ...int) int { + var sum int + for _, arg := range a { + sum += arg + } + for _, arg := range n { + sum += arg + } + return sum + }, + args: []interface{}{[]int{1, 2}, 3, 4}, + expVal: 10, + }, + } + + for _, p := range params { + fn := reflect.ValueOf(p.f) + val, err := CallFunc(fn, p.args...) + if !assert.Equal(t, p.expErr, err) { + t.Fatal(err) + } + if !assert.Equal(t, p.expVal, val) { + t.Fatal() + } + } + +} + +func TestCallFuncComplex(t *testing.T) { + params := []data{ + // arguments include slice + { + f: func(a int, n []int, b int) int { + sum := a + for _, arg := range n { + sum += arg + } + sum += b + return sum + }, + args: []interface{}{1, []int{2, 3}, 4}, + expVal: 10, + }, + // last argument is slice + { + f: func(n []int) int { + var sum int + for _, arg := range n { + sum += arg + } + return sum + }, + args: []interface{}{[]int{1, 2, 3}}, + expVal: 6, + }, + { + f: func(a, b int, n []int) int { + sum := a + b + for _, arg := range n { + sum += arg + } + return sum + }, + args: []interface{}{1, 2, []int{3, 4}}, + expVal: 10, + }, + + // FIXME: return more than 2 values } for _, p := range params { From c1b9daeb5a83c4ae1cde79621f149df0ccf65246 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 16 Jan 2022 00:13:49 +0800 Subject: [PATCH 10/13] change: remove plugin sub-module --- docs/CHANGELOG.md | 2 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- go.mod | 3 +- go.sum | 2 - plugin/go.mod | 10 --- plugin/go.sum | 118 ----------------------------------- 10 files changed, 8 insertions(+), 137 deletions(-) delete mode 100644 plugin/go.mod delete mode 100644 plugin/go.sum diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index dfeec493..ebe15942 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v0.5.2 (2022-01-14) +## v0.5.2 (2022-01-16) - feat: support creating and calling custom functions with [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index b531e439..456f0a5d 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 14-Jan-2022 +###### Auto generated by spf13/cobra on 16-Jan-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index dcd60c35..d6deb490 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -39,4 +39,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 14-Jan-2022 +###### Auto generated by spf13/cobra on 16-Jan-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 7dedd529..f370fc06 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 14-Jan-2022 +###### Auto generated by spf13/cobra on 16-Jan-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index df670831..2541720e 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -31,4 +31,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 14-Jan-2022 +###### Auto generated by spf13/cobra on 16-Jan-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 213cdacb..d34097a2 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 14-Jan-2022 +###### Auto generated by spf13/cobra on 16-Jan-2022 diff --git a/go.mod b/go.mod index 3652c03a..e0e88167 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,8 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/getsentry/sentry-go v0.11.0 github.com/google/uuid v1.3.0 - github.com/httprunner/hrp/plugin v0.0.0-20220114094051-8dedd4883f5c + github.com/hashicorp/go-hclog v1.1.0 + github.com/hashicorp/go-plugin v1.4.3 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/maja42/goval v1.2.1 diff --git a/go.sum b/go.sum index 63f2f64c..9a93daa1 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,6 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/httprunner/hrp/plugin v0.0.0-20220114094051-8dedd4883f5c h1:A1e68FJBaVtIcT1YlJtWqs0nXEwLYKJam1Er/pdg7BA= -github.com/httprunner/hrp/plugin v0.0.0-20220114094051-8dedd4883f5c/go.mod h1:64amelrDUjb+V53VgGU716+x7W6MHIU19g8pBurrXTs= 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/plugin/go.mod b/plugin/go.mod deleted file mode 100644 index f5af4505..00000000 --- a/plugin/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/httprunner/hrp/plugin - -go 1.16 - -require ( - github.com/hashicorp/go-hclog v1.1.0 - github.com/hashicorp/go-plugin v1.4.3 - github.com/rs/zerolog v1.26.1 - github.com/stretchr/testify v1.7.0 -) diff --git a/plugin/go.sum b/plugin/go.sum deleted file mode 100644 index 0e146002..00000000 --- a/plugin/go.sum +++ /dev/null @@ -1,118 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= -github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -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/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 39994501df0e1b4963213ffd15329089962041b4 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 16 Jan 2022 00:15:15 +0800 Subject: [PATCH 11/13] change: update github action --- .github/workflows/unittest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 6a1558e2..ece0b0fb 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -1,6 +1,7 @@ name: Run unittests on: + push: pull_request: types: [synchronize] schedule: From 48c0f63812164f2e715c733f005e263f0152b602 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 16 Jan 2022 23:47:56 +0800 Subject: [PATCH 12/13] refactor: convertArgs --- plugin/shared/call.go | 111 +++++++++++-------------------------- plugin/shared/call_test.go | 26 ++++++++- 2 files changed, 55 insertions(+), 82 deletions(-) diff --git a/plugin/shared/call.go b/plugin/shared/call.go index 49cafad4..8d30516d 100644 --- a/plugin/shared/call.go +++ b/plugin/shared/call.go @@ -8,91 +8,25 @@ import ( ) // CallFunc calls function with arguments -// it is used when calling go plugin or builtin functions func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { - fnArgsNum := fn.Type().NumIn() - - // function expect 0 argument - if fnArgsNum == 0 { - if len(args) > 0 { - return nil, fmt.Errorf("function expect 0 argument, but got %d", len(args)) - } - return call(fn, nil) - } - - // fnArgsNum > 0: function expect more than 0 arguments - // function last argument is not slice - if fn.Type().In(fnArgsNum-1).Kind() != reflect.Slice { - // function arguments should match exactly - if fnArgsNum != len(args) { - return nil, fmt.Errorf("function expect %d arguments, but got %d", fnArgsNum, len(args)) - } - // prepare arguments - argumentsValue, err := convertFirstNArgs(fn, fnArgsNum, args...) - if err != nil { - return nil, err - } - return call(fn, argumentsValue) - } - - // function last argument is slice - // e.g. - // ...interface{} - // a, b string, c ...interface{} - // []int - // a, b string, c []int - - // arguments number should >= fnArgsNum-1 - if len(args) < fnArgsNum-1 { - return nil, fmt.Errorf("function expect at least %d arguments, but got %d", fnArgsNum-1, len(args)) - } - - // prepare first n-1 arguments - argumentsValue, err := convertFirstNArgs(fn, fnArgsNum-1, args...) + argumentsValue, err := convertArgs(fn, args...) if err != nil { + log.Error().Err(err).Msg("convert arguments failed") return nil, err } - - // prepare last slice argument - fnLastSliceElemType := fn.Type().In(fnArgsNum - 1).Elem() // slice element type - for i := fnArgsNum - 1; i < len(args); i++ { // loop last len(args)-fnArgsNum+1 arguments - argValue := reflect.ValueOf(args[i]) - argValueType := reflect.TypeOf(args[i]) - - // check if slice element type match - if fnLastSliceElemType == argValueType { - argumentsValue = append(argumentsValue, argValue) - continue - } - - // last argument is also slice, e.g. []int - if argValueType.Kind() == reflect.Slice { - if argValueType.Elem() != fnLastSliceElemType { - err := fmt.Errorf("function argument %d's slice element type is not match, expect %v, actual %v", - i, fnLastSliceElemType, argValueType) - return nil, err - } - argumentsValue = append(argumentsValue, argValue) - continue - } - - // type not match, check if convertible - if !argValueType.ConvertibleTo(fnLastSliceElemType) { - // function argument type not match and not convertible - err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", - i, fnLastSliceElemType, argValueType) - return nil, err - } - // convert argument to expect type - argumentsValue = append(argumentsValue, argValue.Convert(fnLastSliceElemType)) - } - return call(fn, argumentsValue) } -func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect.Value, error) { - argumentsValue := make([]reflect.Value, n) - for index := 0; index < n; index++ { +func convertArgs(fn reflect.Value, args ...interface{}) ([]reflect.Value, error) { + fnArgsNum := fn.Type().NumIn() + + // function arguments should match exactly if function's last argument is not slice + if len(args) != fnArgsNum && (fnArgsNum == 0 || fn.Type().In(fnArgsNum-1).Kind() != reflect.Slice) { + return nil, fmt.Errorf("function expect %d arguments, but got %d", fnArgsNum, len(args)) + } + + argumentsValue := make([]reflect.Value, len(args)) + for index := 0; index < len(args); index++ { argument := args[index] if argument == nil { argumentsValue[index] = reflect.Zero(fn.Type().In(index)) @@ -100,9 +34,27 @@ func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect. } argumentValue := reflect.ValueOf(argument) - expectArgumentType := fn.Type().In(index) actualArgumentType := reflect.TypeOf(argument) + var expectArgumentType reflect.Type + if (index == fnArgsNum-1 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice) || index > fnArgsNum-1 { + // last fn argument is slice + expectArgumentType = fn.Type().In(fnArgsNum - 1).Elem() // slice element type + + // last argument is also slice, e.g. []int + if actualArgumentType.Kind() == reflect.Slice { + if actualArgumentType.Elem() != expectArgumentType { + err := fmt.Errorf("function argument %d's slice element type is not match, expect %v, actual %v", + index, expectArgumentType, actualArgumentType) + return nil, err + } + argumentsValue[index] = argumentValue + continue + } + } else { + expectArgumentType = fn.Type().In(index) + } + // type match if expectArgumentType == actualArgumentType { argumentsValue[index] = argumentValue @@ -114,7 +66,6 @@ func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect. // function argument type not match and not convertible err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", index, expectArgumentType, actualArgumentType) - log.Error().Err(err).Msg("convert arguments failed") return nil, err } // convert argument to expect type diff --git a/plugin/shared/call_test.go b/plugin/shared/call_test.go index 4f45fa70..d886f2e0 100644 --- a/plugin/shared/call_test.go +++ b/plugin/shared/call_test.go @@ -168,8 +168,30 @@ func TestCallFuncComplex(t *testing.T) { args: []interface{}{1, 2, []int{3, 4}}, expVal: 10, }, - - // FIXME: return more than 2 values + } + + for _, p := range params { + fn := reflect.ValueOf(p.f) + val, err := CallFunc(fn, p.args...) + if !assert.Equal(t, p.expErr, err) { + t.Fatal(err) + } + if !assert.Equal(t, p.expVal, val) { + t.Fatal() + } + } + +} + +func TestCallFuncAbnormal(t *testing.T) { + params := []data{ + // return more than 2 values + { + f: func() (int, int, error) { return 1, 2, nil }, + args: []interface{}{}, + expVal: nil, + expErr: fmt.Errorf("function should return at most 2 values"), + }, } for _, p := range params { From 60de95167310f062ea52891a005b5baa637fe6cd Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 17 Jan 2022 00:07:18 +0800 Subject: [PATCH 13/13] change: update docs --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 456f0a5d..14de4bca 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 16-Jan-2022 +###### Auto generated by spf13/cobra on 17-Jan-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index d6deb490..9b274446 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -39,4 +39,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 16-Jan-2022 +###### Auto generated by spf13/cobra on 17-Jan-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index f370fc06..2fe2e4ae 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 16-Jan-2022 +###### Auto generated by spf13/cobra on 17-Jan-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 2541720e..2abd2c76 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -31,4 +31,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 16-Jan-2022 +###### Auto generated by spf13/cobra on 17-Jan-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index d34097a2..40b7807b 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 16-Jan-2022 +###### Auto generated by spf13/cobra on 17-Jan-2022