mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
refactor: plugin structure
This commit is contained in:
24
parser.go
24
parser.go
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package shared
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package shared
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user