refactor: plugin structure

This commit is contained in:
debugtalk
2022-01-17 15:18:30 +08:00
parent 24546e98e6
commit 675ded099d
8 changed files with 97 additions and 96 deletions

View File

@@ -11,6 +11,9 @@ import (
"github.com/maja42/goval"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/hrp/internal/builtin"
"github.com/httprunner/hrp/plugin/common"
)
func newParser() *parser {
@@ -18,7 +21,7 @@ func newParser() *parser {
}
type parser struct {
plugin hrpPlugin // plugin is used to call functions
plugin common.Plugin // plugin is used to call functions
}
func buildURL(baseURL, stepURL string) string {
@@ -233,6 +236,25 @@ func (p *parser) parseString(raw string, variablesMapping map[string]interface{}
return parsedString, nil
}
// callFunc calls function with arguments
// only support return at most one result value
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
// call with plugin function
if p.plugin != nil && p.plugin.Has(funcName) {
return p.plugin.Call(funcName, arguments...)
}
// get builtin function
function, ok := builtin.Functions[funcName]
if !ok {
return nil, fmt.Errorf("function %s is not found", funcName)
}
fn := reflect.ValueOf(function)
// call with builtin function
return common.CallFunc(fn, arguments...)
}
// merge two variables mapping, the first variables have higher priority
func mergeVariables(variables, overriddenVariables map[string]interface{}) map[string]interface{} {
if overriddenVariables == nil {

View File

@@ -1,4 +1,4 @@
package shared
package common
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package shared
package common
import (
"errors"

View File

@@ -1,7 +1,7 @@
// +build linux freebsd darwin
// go plugin doesn't support windows
package hrp
package common
import (
"fmt"
@@ -16,7 +16,7 @@ func buildGoPlugin() {
fmt.Println("[setup] build go plugin")
// flag -race is necessary in order to be consistent with go test
cmd := exec.Command("go", "build", "-buildmode=plugin", "-race",
"-o=examples/debugtalk.so", "examples/plugin/debugtalk.go")
"-o=debugtalk.so", "../../examples/plugin/debugtalk.go")
if err := cmd.Run(); err != nil {
panic(err)
}
@@ -24,50 +24,43 @@ func buildGoPlugin() {
func removeGoPlugin() {
fmt.Println("[teardown] remove go plugin")
os.Remove("examples/debugtalk.so")
os.Remove("debugtalk.so")
}
func TestLocatePlugin(t *testing.T) {
buildGoPlugin()
defer removeGoPlugin()
cwd, _ := os.Getwd()
_, err := locatePlugin(cwd, goPluginFile)
_, err := locateFile("../", goPluginFile)
if !assert.Error(t, err) {
t.Fail()
}
_, err = locatePlugin("", goPluginFile)
_, err = locateFile("", goPluginFile)
if !assert.Error(t, err) {
t.Fail()
}
startPath := "examples/debugtalk.so"
_, err = locatePlugin(startPath, goPluginFile)
startPath := "debugtalk.so"
_, err = locateFile(startPath, goPluginFile)
if !assert.Nil(t, err) {
t.Fail()
}
startPath = "examples/demo.json"
_, err = locatePlugin(startPath, goPluginFile)
startPath = "call.go"
_, err = locateFile(startPath, goPluginFile)
if !assert.Nil(t, err) {
t.Fail()
}
startPath = "examples/"
_, err = locatePlugin(startPath, goPluginFile)
if !assert.Nil(t, err) {
t.Fail()
}
startPath = "examples/plugin/debugtalk.go"
_, err = locatePlugin(startPath, goPluginFile)
startPath = "."
_, err = locateFile(startPath, goPluginFile)
if !assert.Nil(t, err) {
t.Fail()
}
startPath = "/abc"
_, err = locatePlugin(startPath, goPluginFile)
_, err = locateFile(startPath, goPluginFile)
if !assert.Error(t, err) {
t.Fail()
}
@@ -75,17 +68,19 @@ func TestLocatePlugin(t *testing.T) {
func TestCallPluginFunction(t *testing.T) {
buildGoPlugin()
removeHashicorpPlugin()
defer removeGoPlugin()
parser := newParser()
err := parser.initPlugin("examples/debugtalk.so")
plugin, err := Init("debugtalk.so")
if err != nil {
t.Fatal(err)
}
if !assert.True(t, plugin.Has("Concatenate")) {
t.Fail()
}
// call function without arguments
result, err := parser.callFunc("Concatenate", "1", 2, "3.14")
result, err := plugin.Call("Concatenate", "1", 2, "3.14")
if !assert.NoError(t, err) {
t.Fail()
}

View File

@@ -1,4 +1,4 @@
package hrp
package common
import (
"fmt"
@@ -6,15 +6,14 @@ import (
"os/exec"
"testing"
"github.com/httprunner/hrp/plugin/host"
"github.com/stretchr/testify/assert"
)
func buildHashicorpPlugin() {
fmt.Println("[init] build hashicorp go plugin")
cmd := exec.Command("go", "build",
"-o=examples/debugtalk.bin",
"examples/plugin/hashicorp.go", "examples/plugin/debugtalk.go")
"-o=debugtalk.bin",
"../../examples/plugin/hashicorp.go", "../../examples/plugin/debugtalk.go")
if err := cmd.Run(); err != nil {
panic(err)
}
@@ -22,46 +21,42 @@ func buildHashicorpPlugin() {
func removeHashicorpPlugin() {
fmt.Println("[teardown] remove hashicorp plugin")
os.Remove("examples/debugtalk.bin")
os.Remove("debugtalk.bin")
}
func TestInitHashicorpPlugin(t *testing.T) {
buildHashicorpPlugin()
defer removeHashicorpPlugin()
f, err := host.Init("examples/debugtalk.bin")
plugin, err := Init("debugtalk.bin")
if err != nil {
t.Fatal(err)
}
defer host.Quit()
defer plugin.Quit()
v1, err := f.GetNames()
if err != nil {
if !assert.True(t, plugin.Has("sum_ints")) {
t.Fatal(err)
}
if !assert.Contains(t, v1, "sum_ints") {
t.Fatal(err)
}
if !assert.Contains(t, v1, "concatenate") {
if !assert.True(t, plugin.Has("concatenate")) {
t.Fatal(err)
}
var v2 interface{}
v2, err = f.Call("sum_ints", 1, 2, 3, 4)
v2, err = plugin.Call("sum_ints", 1, 2, 3, 4)
if err != nil {
t.Fatal(err)
}
if !assert.Equal(t, 10, v2) {
t.Fail()
}
v2, err = f.Call("sum_two_int", 1, 2)
v2, err = plugin.Call("sum_two_int", 1, 2)
if err != nil {
t.Fatal(err)
}
if !assert.Equal(t, 3, v2) {
t.Fail()
}
v2, err = f.Call("sum", 1, 2, 3.4, 5)
v2, err = plugin.Call("sum", 1, 2, 3.4, 5)
if err != nil {
t.Fatal(err)
}
@@ -70,14 +65,14 @@ func TestInitHashicorpPlugin(t *testing.T) {
}
var v3 interface{}
v3, err = f.Call("sum_two_string", "a", "b")
v3, err = plugin.Call("sum_two_string", "a", "b")
if err != nil {
t.Fatal(err)
}
if !assert.Equal(t, "ab", v3) {
t.Fail()
}
v3, err = f.Call("sum_strings", "a", "b", "c")
v3, err = plugin.Call("sum_strings", "a", "b", "c")
if err != nil {
t.Fatal(err)
}
@@ -85,7 +80,7 @@ func TestInitHashicorpPlugin(t *testing.T) {
t.Fail()
}
v3, err = f.Call("concatenate", "a", 2, "c", 3.4)
v3, err = plugin.Call("concatenate", "a", 2, "c", 3.4)
if err != nil {
t.Fatal(err)
}

View File

@@ -1,4 +1,4 @@
package hrp
package common
import (
"fmt"
@@ -10,7 +10,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/httprunner/hrp/internal/builtin"
"github.com/httprunner/hrp/internal/ga"
pluginHost "github.com/httprunner/hrp/plugin/host"
pluginShared "github.com/httprunner/hrp/plugin/shared"
@@ -24,11 +23,11 @@ const (
hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py"
)
type hrpPlugin interface {
init(path string) error // init plugin
has(funcName string) bool // check if plugin has function
call(funcName string, args ...interface{}) (interface{}, error) // call function
quit() error // quit plugin
type Plugin interface {
Init(path string) error // init plugin
Has(funcName string) bool // check if plugin has function
Call(funcName string, args ...interface{}) (interface{}, error) // call function
Quit() error // quit plugin
}
// goPlugin implements golang official plugin
@@ -37,7 +36,7 @@ type goPlugin struct {
cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance
}
func (p *goPlugin) init(path string) error {
func (p *goPlugin) Init(path string) error {
if runtime.GOOS == "windows" {
log.Warn().Msg("go plugin does not support windows")
return fmt.Errorf("go plugin does not support windows")
@@ -67,7 +66,7 @@ func (p *goPlugin) init(path string) error {
return nil
}
func (p *goPlugin) has(funcName string) bool {
func (p *goPlugin) Has(funcName string) bool {
fn, ok := p.cachedFunctions[funcName]
if ok {
return fn.IsValid()
@@ -90,12 +89,15 @@ func (p *goPlugin) has(funcName string) bool {
return true
}
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
func (p *goPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
if !p.Has(funcName) {
return nil, fmt.Errorf("function %s not found", funcName)
}
fn := p.cachedFunctions[funcName]
return pluginShared.CallFunc(fn, args...)
return CallFunc(fn, args...)
}
func (p *goPlugin) quit() error {
func (p *goPlugin) Quit() error {
// no need to quit for go plugin
return nil
}
@@ -106,7 +108,7 @@ type hashicorpPlugin struct {
cachedFunctions map[string]bool // cache loaded functions to improve performance
}
func (p *hashicorpPlugin) init(path string) error {
func (p *hashicorpPlugin) Init(path string) error {
f, err := pluginHost.Init(path)
if err != nil {
@@ -120,7 +122,7 @@ func (p *hashicorpPlugin) init(path string) error {
return nil
}
func (p *hashicorpPlugin) has(funcName string) bool {
func (p *hashicorpPlugin) Has(funcName string) bool {
flag, ok := p.cachedFunctions[funcName]
if ok {
return flag
@@ -142,45 +144,48 @@ func (p *hashicorpPlugin) has(funcName string) bool {
return false
}
func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
func (p *hashicorpPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
return p.FuncCaller.Call(funcName, args...)
}
func (p *hashicorpPlugin) quit() error {
func (p *hashicorpPlugin) Quit() error {
// kill hashicorp plugin process
pluginHost.Quit()
return nil
}
func (p *parser) initPlugin(path string) error {
func Init(path string) (Plugin, error) {
if path == "" {
return nil
return nil, nil
}
var plugin Plugin
// priority: hashicorp plugin > go plugin > builtin functions
// locate hashicorp plugin file
pluginPath, err := locatePlugin(path, hashicorpGoPluginFile)
pluginPath, err := locateFile(path, hashicorpGoPluginFile)
if err == nil {
// found hashicorp go plugin file
p.plugin = &hashicorpPlugin{}
return p.plugin.init(pluginPath)
plugin = &hashicorpPlugin{}
err = plugin.Init(pluginPath)
return plugin, err
}
// locate go plugin file
pluginPath, err = locatePlugin(path, goPluginFile)
pluginPath, err = locateFile(path, goPluginFile)
if err == nil {
// found go plugin file
p.plugin = &goPlugin{}
return p.plugin.init(pluginPath)
plugin = &goPlugin{}
err = plugin.Init(pluginPath)
return plugin, err
}
// plugin not found
return nil
return nil, nil
}
// locatePlugin searches destPluginFile upward recursively until current
// locateFile searches destFile upward recursively until current
// working directory or system root dir.
func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
func locateFile(startPath string, destFile pluginFile) (string, error) {
stat, err := os.Stat(startPath)
if os.IsNotExist(err) {
return "", err
@@ -195,7 +200,7 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
startDir, _ = filepath.Abs(startDir)
// convention over configuration
pluginPath := filepath.Join(startDir, string(destPluginFile))
pluginPath := filepath.Join(startDir, string(destFile))
if _, err := os.Stat(pluginPath); err == nil {
return pluginPath, nil
}
@@ -212,24 +217,5 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
return "", fmt.Errorf("searched to system root dir, plugin file not found")
}
return locatePlugin(parentDir, destPluginFile)
}
// callFunc calls function with arguments
// only support return at most one result value
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
// call with plugin function
if p.plugin != nil && p.plugin.has(funcName) {
return p.plugin.call(funcName, arguments...)
}
// get builtin function
function, ok := builtin.Functions[funcName]
if !ok {
return nil, fmt.Errorf("function %s is not found", funcName)
}
fn := reflect.ValueOf(function)
// call with builtin function
return pluginShared.CallFunc(fn, arguments...)
return locateFile(parentDir, destFile)
}

View File

@@ -8,6 +8,7 @@ import (
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/httprunner/hrp/plugin/common"
pluginShared "github.com/httprunner/hrp/plugin/shared"
)
@@ -36,7 +37,7 @@ func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}
return nil, fmt.Errorf("function %s not found", funcName)
}
return pluginShared.CallFunc(fn, args...)
return common.CallFunc(fn, args...)
}
var functions = make(functionsMap)

View File

@@ -19,6 +19,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/httprunner/hrp/internal/ga"
"github.com/httprunner/hrp/plugin/common"
)
// Run starts to run API test with default configs.
@@ -157,7 +158,7 @@ func (r *caseRunner) reset() *caseRunner {
func (r *caseRunner) run() error {
defer func() {
if r.parser.plugin != nil {
r.parser.plugin.quit()
r.parser.plugin.Quit()
}
}()
config := r.TestCase.Config
@@ -504,7 +505,8 @@ func (r *caseRunner) parseConfig(config IConfig) error {
cfg := config.ToStruct()
// init plugin
err := r.parser.initPlugin(cfg.Path)
var err error
r.parser.plugin, err = common.Init(cfg.Path)
if err != nil {
return err
}