mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 07:26:55 +08:00
refactor: go plugin
This commit is contained in:
+3
-3
@@ -29,8 +29,8 @@ Copyright 2021 debugtalk
|
|||||||
### SEE ALSO
|
### SEE ALSO
|
||||||
|
|
||||||
* [hrp boom](hrp_boom.md) - run load test with boomer
|
* [hrp boom](hrp_boom.md) - run load test with boomer
|
||||||
* [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files
|
* [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files
|
||||||
* [hrp run](hrp_run.md) - run API test
|
* [hrp run](hrp_run.md) - run API test
|
||||||
* [hrp startproject](hrp_startproject.md) - Create a scaffold project
|
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 8-Jan-2022
|
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ hrp boom [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 8-Jan-2022
|
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
## hrp har2case
|
## hrp har2case
|
||||||
|
|
||||||
Convert HAR to json/yaml testcase files
|
convert HAR to json/yaml testcase files
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
Convert HAR to json/yaml testcase files
|
convert HAR to json/yaml testcase files
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp har2case $har_path... [flags]
|
hrp har2case $har_path... [flags]
|
||||||
@@ -23,4 +23,4 @@ hrp har2case $har_path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 8-Jan-2022
|
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||||
|
|||||||
+1
-1
@@ -31,4 +31,4 @@ hrp run $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 8-Jan-2022
|
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## hrp startproject
|
## hrp startproject
|
||||||
|
|
||||||
Create a scaffold project
|
create a scaffold project
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp startproject $project_name [flags]
|
hrp startproject $project_name [flags]
|
||||||
@@ -16,4 +16,4 @@ hrp startproject $project_name [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 8-Jan-2022
|
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
fmt.Println("[TestMain] build go plugin")
|
fmt.Println("[TestMain] build go plugin")
|
||||||
// flag -race is necessary in order to be consistent with go test
|
// 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 {
|
if err := cmd.Run(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -24,42 +24,42 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestLocatePlugin(t *testing.T) {
|
func TestLocatePlugin(t *testing.T) {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
_, err := locatePlugin(cwd)
|
_, err := locatePlugin(cwd, goPluginFile)
|
||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = locatePlugin("")
|
_, err = locatePlugin("", goPluginFile)
|
||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
startPath := "examples/debugtalk.so"
|
startPath := "examples/debugtalk.so"
|
||||||
_, err = locatePlugin(startPath)
|
_, err = locatePlugin(startPath, goPluginFile)
|
||||||
if !assert.Nil(t, err) {
|
if !assert.Nil(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
startPath = "examples/demo.json"
|
startPath = "examples/demo.json"
|
||||||
_, err = locatePlugin(startPath)
|
_, err = locatePlugin(startPath, goPluginFile)
|
||||||
if !assert.Nil(t, err) {
|
if !assert.Nil(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
startPath = "examples/"
|
startPath = "examples/"
|
||||||
_, err = locatePlugin(startPath)
|
_, err = locatePlugin(startPath, goPluginFile)
|
||||||
if !assert.Nil(t, err) {
|
if !assert.Nil(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
startPath = "examples/plugin/debugtalk.go"
|
startPath = "examples/plugin/debugtalk.go"
|
||||||
_, err = locatePlugin(startPath)
|
_, err = locatePlugin(startPath, goPluginFile)
|
||||||
if !assert.Nil(t, err) {
|
if !assert.Nil(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
startPath = "/abc"
|
startPath = "/abc"
|
||||||
_, err = locatePlugin(startPath)
|
_, err = locatePlugin(startPath, goPluginFile)
|
||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ func TestLocatePlugin(t *testing.T) {
|
|||||||
|
|
||||||
func TestCallPluginFunction(t *testing.T) {
|
func TestCallPluginFunction(t *testing.T) {
|
||||||
parser := newParser()
|
parser := newParser()
|
||||||
parser.loadPlugin("examples/debugtalk.so")
|
parser.initPlugin("examples/debugtalk.so")
|
||||||
|
|
||||||
// call function without arguments
|
// call function without arguments
|
||||||
result, err := parser.callFunc("Concatenate", 1, "2", 3.14)
|
result, err := parser.callFunc("Concatenate", 1, "2", 3.14)
|
||||||
@@ -4,9 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"plugin"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -21,46 +18,7 @@ func newParser() *parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
// pluginLoader stores loaded go plugins.
|
plugin hrpPlugin // plugin is used to call functions
|
||||||
pluginLoader *plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// locatePlugin searches debugtalk.so upward recursively until current
|
|
||||||
// working directory or system root dir.
|
|
||||||
func locatePlugin(startPath string) (string, error) {
|
|
||||||
stat, err := os.Stat(startPath)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var startDir string
|
|
||||||
if stat.IsDir() {
|
|
||||||
startDir = startPath
|
|
||||||
} else {
|
|
||||||
startDir = filepath.Dir(startPath)
|
|
||||||
}
|
|
||||||
startDir, _ = filepath.Abs(startDir)
|
|
||||||
|
|
||||||
// convention over configuration
|
|
||||||
// target plugin file name is always debugtalk.so
|
|
||||||
pluginPath := filepath.Join(startDir, "debugtalk.so")
|
|
||||||
if _, err := os.Stat(pluginPath); err == nil {
|
|
||||||
return pluginPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// current working directory
|
|
||||||
cwd, _ := os.Getwd()
|
|
||||||
if startDir == cwd {
|
|
||||||
return "", fmt.Errorf("searched to CWD, plugin file not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// system root dir
|
|
||||||
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
|
|
||||||
if parentDir == startDir {
|
|
||||||
return "", fmt.Errorf("searched to system root dir, plugin file not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return locatePlugin(parentDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildURL(baseURL, stepURL string) string {
|
func buildURL(baseURL, stepURL string) string {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package hrp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"plugin"
|
"plugin"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -12,28 +14,33 @@ import (
|
|||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *parser) loadPlugin(path string) error {
|
type pluginFile string
|
||||||
|
|
||||||
|
const (
|
||||||
|
goPluginFile pluginFile = "debugtalk.so" // built from go plugin
|
||||||
|
hashicorpGoPluginFile pluginFile = "debugtalk" // built from hashicorp go plugin
|
||||||
|
hashicorpPyPluginFile pluginFile = "debugtalk.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hrpPlugin interface {
|
||||||
|
init(path string) error
|
||||||
|
lookup(funcName string) (reflect.Value, error) // lookup function
|
||||||
|
// call(funcName string, args ...interface{}) (interface{}, error)
|
||||||
|
quit() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// goPlugin implements golang official plugin
|
||||||
|
type goPlugin struct {
|
||||||
|
*plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *goPlugin) init(path string) error {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
log.Warn().Msg("go plugin does not support windows")
|
log.Warn().Msg("go plugin does not support windows")
|
||||||
return nil
|
return fmt.Errorf("go plugin does not support windows")
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
// report event for loading go plugin
|
// report event for loading go plugin
|
||||||
defer func() {
|
defer func() {
|
||||||
event := ga.EventTracking{
|
event := ga.EventTracking{
|
||||||
@@ -46,36 +53,137 @@ func (p *parser) loadPlugin(path string) error {
|
|||||||
go ga.SendEvent(event)
|
go ga.SendEvent(event)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// load plugin
|
p.Plugin, err = plugin.Open(path)
|
||||||
plugins, err := plugin.Open(pluginPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("path", path).Msg("load go plugin failed")
|
log.Error().Err(err).Str("path", path).Msg("load go plugin failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.pluginLoader = plugins
|
|
||||||
|
|
||||||
log.Info().Str("path", path).Msg("load go plugin success")
|
log.Info().Str("path", path).Msg("load go plugin success")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMappingFunction(funcName string, pluginLoader *plugin.Plugin) (reflect.Value, error) {
|
func (p *goPlugin) lookup(funcName string) (reflect.Value, error) {
|
||||||
|
if p.Plugin == nil {
|
||||||
|
return reflect.Value{}, fmt.Errorf("go plugin is not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
sym, err := p.Plugin.Lookup(funcName)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Value{}, fmt.Errorf("function %s is not found", funcName)
|
||||||
|
}
|
||||||
|
fn := reflect.ValueOf(sym)
|
||||||
|
|
||||||
|
// check function type
|
||||||
|
if fn.Kind() != reflect.Func {
|
||||||
|
return reflect.Value{}, fmt.Errorf("function %s is invalid", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||||
|
if p.Plugin == nil {
|
||||||
|
return nil, fmt.Errorf("go plugin is not loaded")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *goPlugin) quit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashicorpPlugin implements hashicorp/go-plugin
|
||||||
|
type hashicorpPlugin struct {
|
||||||
|
cachedFunctions map[string]reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashicorpPlugin) init(path string) error {
|
||||||
|
log.Info().Str("path", path).Msg("load hashicorp go plugin success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashicorpPlugin) lookup(funcName string) (reflect.Value, error) {
|
||||||
|
return reflect.Value{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashicorpPlugin) quit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) initPlugin(path string) error {
|
||||||
|
if path == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate go plugin file
|
||||||
|
pluginPath, err := locatePlugin(path, goPluginFile)
|
||||||
|
if err == nil {
|
||||||
|
// found go plugin file
|
||||||
|
p.plugin = &goPlugin{}
|
||||||
|
return p.plugin.init(pluginPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate hashicorp plugin file
|
||||||
|
pluginPath, err = locatePlugin(path, hashicorpGoPluginFile)
|
||||||
|
if err == nil {
|
||||||
|
// found hashicorp go plugin file
|
||||||
|
p.plugin = &hashicorpPlugin{}
|
||||||
|
return p.plugin.init(pluginPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugin not found
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// locatePlugin searches destPluginFile upward recursively until current
|
||||||
|
// working directory or system root dir.
|
||||||
|
func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
|
||||||
|
stat, err := os.Stat(startPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var startDir string
|
||||||
|
if stat.IsDir() {
|
||||||
|
startDir = startPath
|
||||||
|
} else {
|
||||||
|
startDir = filepath.Dir(startPath)
|
||||||
|
}
|
||||||
|
startDir, _ = filepath.Abs(startDir)
|
||||||
|
|
||||||
|
// convention over configuration
|
||||||
|
pluginPath := filepath.Join(startDir, string(destPluginFile))
|
||||||
|
if _, err := os.Stat(pluginPath); err == nil {
|
||||||
|
return pluginPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// current working directory
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
if startDir == cwd {
|
||||||
|
return "", fmt.Errorf("searched to CWD, plugin file not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// system root dir
|
||||||
|
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
|
||||||
|
if parentDir == startDir {
|
||||||
|
return "", fmt.Errorf("searched to system root dir, plugin file not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return locatePlugin(parentDir, destPluginFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) getMappingFunction(funcName string) (reflect.Value, error) {
|
||||||
var fn reflect.Value
|
var fn reflect.Value
|
||||||
var err error
|
|
||||||
|
|
||||||
defer func() {
|
// get function from plugin
|
||||||
// check function type
|
if p.plugin != nil {
|
||||||
if err == nil && fn.Kind() != reflect.Func {
|
fn, err := p.plugin.lookup(funcName)
|
||||||
// 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 {
|
if err == nil {
|
||||||
fn = reflect.ValueOf(sym)
|
|
||||||
return fn, nil
|
return fn, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +201,7 @@ func getMappingFunction(funcName string, pluginLoader *plugin.Plugin) (reflect.V
|
|||||||
// callFunc calls function with arguments
|
// callFunc calls function with arguments
|
||||||
// only support return at most one result value
|
// only support return at most one result value
|
||||||
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
|
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
|
||||||
fn, err := getMappingFunction(funcName, p.pluginLoader)
|
fn, err := p.getMappingFunction(funcName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ func (r *caseRunner) parseConfig(config IConfig) error {
|
|||||||
cfg.Variables = parsedVariables
|
cfg.Variables = parsedVariables
|
||||||
|
|
||||||
// load plugin variables and functions
|
// load plugin variables and functions
|
||||||
err = r.parser.loadPlugin(cfg.Path)
|
err = r.parser.initPlugin(cfg.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user