From 014e53cef4b3a313ea753da09bc0a871eb85207e Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 6 Jan 2022 18:01:23 +0800 Subject: [PATCH] refactor: call function --- parser.go | 125 +--------------------------------------------- parser_test.go | 14 +++--- plugin.go | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ plugin_test.go | 10 ++-- 4 files changed, 142 insertions(+), 139 deletions(-) create mode 100644 plugin.go diff --git a/parser.go b/parser.go index 03dc0140..49062ae2 100644 --- a/parser.go +++ b/parser.go @@ -9,14 +9,11 @@ import ( "plugin" "reflect" "regexp" - "runtime" "strings" "github.com/maja42/goval" "github.com/pkg/errors" "github.com/rs/zerolog/log" - - "github.com/httprunner/hrp/internal/builtin" ) func newParser() *parser { @@ -28,40 +25,6 @@ type parser struct { pluginLoader *plugin.Plugin } -func (p *parser) loadPlugin(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 - } - - // load plugin - plugins, err := plugin.Open(pluginPath) - 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 -} - // locatePlugin searches debugtalk.so upward recursively until current // working directory or system root dir. func locatePlugin(startPath string) (string, error) { @@ -251,12 +214,7 @@ func (p *parser) parseString(raw string, variablesMapping map[string]interface{} return raw, err } - fn, err := getMappingFunction(funcName, p.pluginLoader) - if err != nil { - return raw, err - } - - result, err := callFunc(fn, parsedArgs.([]interface{})...) + result, err := p.callFunc(funcName, parsedArgs.([]interface{})...) if err != nil { log.Error().Str("funcName", funcName).Interface("arguments", arguments). Err(err).Msg("call function failed") @@ -342,87 +300,6 @@ func mergeVariables(variables, overriddenVariables map[string]interface{}) map[s return mergedVariables } -func getMappingFunction(funcName string, pluginLoader *plugin.Plugin) (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) - if err == nil { - fn = reflect.ValueOf(sym) - return fn, nil - } - } - - // get builtin function - if function, ok := builtin.Functions[funcName]; ok { - fn = reflect.ValueOf(function) - return fn, nil - } - - // function not found - return reflect.Value{}, fmt.Errorf("function %s is not found", funcName) -} - -// callFunc call function with arguments -// only support return at most one result value -func callFunc(fn reflect.Value, arguments ...interface{}) (interface{}, error) { - if fn.Type().NumIn() != len(arguments) { - // function arguments not match - return nil, fmt.Errorf("function arguments number not match") - } - - 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 - } - - // 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") - 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 -} - var eval = goval.NewEvaluator() // literalEval parse string to number if possible diff --git a/parser_test.go b/parser_test.go index 70ffe899..b79f1c2b 100644 --- a/parser_test.go +++ b/parser_test.go @@ -378,17 +378,17 @@ func TestMergeVariables(t *testing.T) { } func TestCallBuiltinFunction(t *testing.T) { + parser := newParser() + // call function without arguments - f1, _ := getMappingFunction("get_timestamp", nil) - _, err := callFunc(f1) + _, err := parser.callFunc("get_timestamp") if !assert.NoError(t, err) { t.Fail() } // call function with one argument timeStart := time.Now() - f2, _ := getMappingFunction("sleep", nil) - _, err = callFunc(f2, 1) + _, err = parser.callFunc("sleep", 1) if !assert.NoError(t, err) { t.Fail() } @@ -397,8 +397,7 @@ func TestCallBuiltinFunction(t *testing.T) { } // call function with one argument - f3, _ := getMappingFunction("gen_random_string", nil) - result, err := callFunc(f3, 10) + result, err := parser.callFunc("gen_random_string", 10) if !assert.NoError(t, err) { t.Fail() } @@ -407,8 +406,7 @@ func TestCallBuiltinFunction(t *testing.T) { } // call function with two argument - f4, _ := getMappingFunction("max", nil) - result, err = callFunc(f4, float64(10), 9.99) + result, err = parser.callFunc("max", float64(10), 9.99) if !assert.NoError(t, err) { t.Fail() } diff --git a/plugin.go b/plugin.go new file mode 100644 index 00000000..005dbda8 --- /dev/null +++ b/plugin.go @@ -0,0 +1,132 @@ +package hrp + +import ( + "fmt" + "plugin" + "reflect" + "runtime" + + "github.com/rs/zerolog/log" + + "github.com/httprunner/hrp/internal/builtin" +) + +func (p *parser) loadPlugin(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 + } + + // load plugin + plugins, err := plugin.Open(pluginPath) + 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) { + 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) + if err == nil { + fn = reflect.ValueOf(sym) + return fn, nil + } + } + + // get builtin function + if function, ok := builtin.Functions[funcName]; ok { + fn = reflect.ValueOf(function) + 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 := getMappingFunction(funcName, p.pluginLoader) + if err != nil { + return nil, err + } + + if fn.Type().NumIn() != len(arguments) { + // function arguments not match + return nil, fmt.Errorf("function arguments number not match") + } + + 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 + } + + // 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") + 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/plugin_test.go b/plugin_test.go index 75fc271d..7e4ff8ac 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "os/exec" - "plugin" "testing" "github.com/stretchr/testify/assert" @@ -27,14 +26,11 @@ func TestMain(m *testing.M) { } func TestCallPluginFunction(t *testing.T) { - pluginLoader, err := plugin.Open("examples/debugtalk.so") - if err != nil { - t.Fatalf(err.Error()) - } + parser := newParser() + parser.loadPlugin("examples/debugtalk.so") // call function without arguments - f1, _ := getMappingFunction("Concatenate", pluginLoader) - result, err := callFunc(f1, 1, "2", 3.14) + result, err := parser.callFunc("Concatenate", 1, "2", 3.14) if !assert.NoError(t, err) { t.Fail() }