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: 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 8ccee28e..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 13-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 1d0a6263..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 13-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 6f57a509..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 13-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 9cce4aaf..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 13-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 736d31d6..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 13-Jan-2022 +###### Auto generated by spf13/cobra on 17-Jan-2022 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.mod b/go.mod index ba72d455..e0e88167 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,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..9a93daa1 100644 --- a/go.sum +++ b/go.sum @@ -370,8 +370,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 +459,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/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 d9d52d6d..00000000 --- a/plugin/examples/debugtalk.go +++ /dev/null @@ -1,65 +0,0 @@ -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/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= diff --git a/plugin/shared/call.go b/plugin/shared/call.go new file mode 100644 index 00000000..8d30516d --- /dev/null +++ b/plugin/shared/call.go @@ -0,0 +1,103 @@ +package shared + +import ( + "fmt" + "reflect" + + "github.com/rs/zerolog/log" +) + +// CallFunc calls function with arguments +func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { + argumentsValue, err := convertArgs(fn, args...) + if err != nil { + log.Error().Err(err).Msg("convert arguments failed") + return nil, err + } + return call(fn, argumentsValue) +} + +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)) + continue + } + + argumentValue := reflect.ValueOf(argument) + 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 + 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) + } + return argumentsValue, nil +} + +func call(fn reflect.Value, args []reflect.Value) (interface{}, error) { + resultValues := fn.Call(args) + 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 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 values") + return nil, err + } +} diff --git a/plugin/shared/call_test.go b/plugin/shared/call_test.go new file mode 100644 index 00000000..d886f2e0 --- /dev/null +++ b/plugin/shared/call_test.go @@ -0,0 +1,208 @@ +package shared + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +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}, + // 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, 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 + { + 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", + }, + + // 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, + }, + } + + 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 { + 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() + } + } + +} diff --git a/plugin/shared/utils.go b/plugin/shared/utils.go deleted file mode 100644 index a33a060b..00000000 --- a/plugin/shared/utils.go +++ /dev/null @@ -1,50 +0,0 @@ -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 - } -}