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() + } + } + +}