mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-08 01:09:44 +08:00
Merge pull request #124 from httprunner/refactor-plugin
refactor plugin
This commit is contained in:
18
boomer.go
18
boomer.go
@@ -9,29 +9,21 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/hrp/internal/boomer"
|
"github.com/httprunner/hrp/internal/boomer"
|
||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
"github.com/httprunner/hrp/plugin/common"
|
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBoomer(spawnCount int, spawnRate float64) *HRPBoomer {
|
func NewBoomer(spawnCount int, spawnRate float64) *HRPBoomer {
|
||||||
b := &HRPBoomer{
|
b := &HRPBoomer{
|
||||||
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
|
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
|
||||||
pluginsMutex: new(sync.RWMutex),
|
pluginsMutex: new(sync.RWMutex),
|
||||||
debug: false,
|
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
type HRPBoomer struct {
|
type HRPBoomer struct {
|
||||||
*boomer.Boomer
|
*boomer.Boomer
|
||||||
plugins []common.Plugin // each task has its own plugin process
|
plugins []pluginInternal.IPlugin // each task has its own plugin process
|
||||||
pluginsMutex *sync.RWMutex // avoid data race
|
pluginsMutex *sync.RWMutex // avoid data race
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDebug configures whether to log HTTP request and response content.
|
|
||||||
func (b *HRPBoomer) SetDebug(debug bool) *HRPBoomer {
|
|
||||||
b.debug = debug
|
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts to run load test for one or multiple testcases.
|
// Run starts to run load test for one or multiple testcases.
|
||||||
@@ -75,11 +67,11 @@ func (b *HRPBoomer) Quit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
|
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
|
||||||
hrpRunner := NewRunner(nil).SetDebug(b.debug)
|
hrpRunner := NewRunner(nil)
|
||||||
config := testcase.Config
|
config := testcase.Config
|
||||||
|
|
||||||
// each testcase has its own plugin process
|
// each testcase has its own plugin process
|
||||||
plugin, _ := initPlugin(config.Path)
|
plugin, _ := initPlugin(config.Path, false)
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
b.pluginsMutex.Lock()
|
b.pluginsMutex.Lock()
|
||||||
b.plugins = append(b.plugins, plugin)
|
b.plugins = append(b.plugins, plugin)
|
||||||
|
|||||||
@@ -26,12 +26,17 @@ var runCmd = &cobra.Command{
|
|||||||
paths = append(paths, &hrp.TestCasePath{Path: arg})
|
paths = append(paths, &hrp.TestCasePath{Path: arg})
|
||||||
}
|
}
|
||||||
runner := hrp.NewRunner(nil).
|
runner := hrp.NewRunner(nil).
|
||||||
SetDebug(!silentFlag).
|
|
||||||
SetFailfast(!continueOnFailure).
|
SetFailfast(!continueOnFailure).
|
||||||
SetSaveTests(saveTests)
|
SetSaveTests(saveTests)
|
||||||
if genHTMLReport {
|
if genHTMLReport {
|
||||||
runner.GenHTMLReport()
|
runner.GenHTMLReport()
|
||||||
}
|
}
|
||||||
|
if !requestsLogOff {
|
||||||
|
runner.SetRequestsLogOn()
|
||||||
|
}
|
||||||
|
if pluginLogOn {
|
||||||
|
runner.SetPluginLogOn()
|
||||||
|
}
|
||||||
if proxyUrl != "" {
|
if proxyUrl != "" {
|
||||||
runner.SetProxyUrl(proxyUrl)
|
runner.SetProxyUrl(proxyUrl)
|
||||||
}
|
}
|
||||||
@@ -44,7 +49,8 @@ var runCmd = &cobra.Command{
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
continueOnFailure bool
|
continueOnFailure bool
|
||||||
silentFlag bool
|
requestsLogOff bool
|
||||||
|
pluginLogOn bool
|
||||||
proxyUrl string
|
proxyUrl string
|
||||||
saveTests bool
|
saveTests bool
|
||||||
genHTMLReport bool
|
genHTMLReport bool
|
||||||
@@ -52,9 +58,10 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(runCmd)
|
rootCmd.AddCommand(runCmd)
|
||||||
runCmd.Flags().BoolVar(&continueOnFailure, "continue-on-failure", false, "continue running next step when failure occurs")
|
runCmd.Flags().BoolVarP(&continueOnFailure, "continue-on-failure", "c", false, "continue running next step when failure occurs")
|
||||||
runCmd.Flags().BoolVarP(&silentFlag, "silent", "s", false, "disable logging request & response details")
|
runCmd.Flags().BoolVar(&requestsLogOff, "log-requests-off", false, "turn off request & response details logging")
|
||||||
|
runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", false, "turn on plugin logging")
|
||||||
runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url")
|
runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url")
|
||||||
runCmd.Flags().BoolVar(&saveTests, "save-tests", false, "save tests summary")
|
runCmd.Flags().BoolVarP(&saveTests, "save-tests", "s", false, "save tests summary")
|
||||||
runCmd.Flags().BoolVarP(&genHTMLReport, "gen-html-report", "r", false, "generate html report")
|
runCmd.Flags().BoolVarP(&genHTMLReport, "gen-html-report", "g", false, "generate html report")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ Copyright 2021 debugtalk
|
|||||||
* [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 22-Feb-2022
|
###### Auto generated by spf13/cobra on 25-Feb-2022
|
||||||
|
|||||||
@@ -39,4 +39,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 22-Feb-2022
|
###### Auto generated by spf13/cobra on 25-Feb-2022
|
||||||
|
|||||||
@@ -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 22-Feb-2022
|
###### Auto generated by spf13/cobra on 25-Feb-2022
|
||||||
|
|||||||
@@ -21,16 +21,17 @@ hrp run $path... [flags]
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
```
|
```
|
||||||
--continue-on-failure continue running next step when failure occurs
|
-c, --continue-on-failure continue running next step when failure occurs
|
||||||
-r, --gen-html-report generate html report
|
-g, --gen-html-report generate html report
|
||||||
-h, --help help for run
|
-h, --help help for run
|
||||||
|
--log-plugin turn on plugin logging
|
||||||
|
--log-requests-off turn off request & response details logging
|
||||||
-p, --proxy-url string set proxy url
|
-p, --proxy-url string set proxy url
|
||||||
--save-tests save tests summary
|
-s, --save-tests save tests summary
|
||||||
-s, --silent disable logging request & response details
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### SEE ALSO
|
### SEE ALSO
|
||||||
|
|
||||||
* [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 22-Feb-2022
|
###### Auto generated by spf13/cobra on 25-Feb-2022
|
||||||
|
|||||||
@@ -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 22-Feb-2022
|
###### Auto generated by spf13/cobra on 25-Feb-2022
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/hrp/internal/builtin"
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
"github.com/httprunner/hrp/plugin/common"
|
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newParser() *parser {
|
func newParser() *parser {
|
||||||
@@ -21,7 +21,7 @@ func newParser() *parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
plugin common.Plugin // plugin is used to call functions
|
plugin pluginInternal.IPlugin // plugin is used to call functions
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildURL(baseURL, stepURL string) string {
|
func buildURL(baseURL, stepURL string) string {
|
||||||
@@ -252,7 +252,7 @@ func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{
|
|||||||
fn := reflect.ValueOf(function)
|
fn := reflect.ValueOf(function)
|
||||||
|
|
||||||
// call with builtin function
|
// call with builtin function
|
||||||
return common.CallFunc(fn, arguments...)
|
return pluginInternal.CallFunc(fn, arguments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge two variables mapping, the first variables have higher priority
|
// merge two variables mapping, the first variables have higher priority
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"plugin"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
pluginHost "github.com/httprunner/hrp/plugin/host"
|
|
||||||
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pluginFile string
|
|
||||||
|
|
||||||
const (
|
|
||||||
goPluginFile pluginFile = pluginShared.Name + ".so" // built from go plugin
|
|
||||||
hashicorpGoPluginFile pluginFile = pluginShared.Name + ".bin" // built from hashicorp go plugin
|
|
||||||
hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
type GoPlugin struct {
|
|
||||||
*plugin.Plugin
|
|
||||||
cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
p.Plugin, err = plugin.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Str("path", path).Msg("load go plugin failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cachedFunctions = make(map[string]reflect.Value)
|
|
||||||
log.Info().Str("path", path).Msg("load go plugin success")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GoPlugin) Has(funcName string) bool {
|
|
||||||
fn, ok := p.cachedFunctions[funcName]
|
|
||||||
if ok {
|
|
||||||
return fn.IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
sym, err := p.Plugin.Lookup(funcName)
|
|
||||||
if err != nil {
|
|
||||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fn = reflect.ValueOf(sym)
|
|
||||||
|
|
||||||
// check function type
|
|
||||||
if fn.Kind() != reflect.Func {
|
|
||||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cachedFunctions[funcName] = fn
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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 CallFunc(fn, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GoPlugin) Quit() error {
|
|
||||||
// no need to quit for go plugin
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashicorpPlugin implements hashicorp/go-plugin
|
|
||||||
type HashicorpPlugin struct {
|
|
||||||
pluginShared.FuncCaller
|
|
||||||
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HashicorpPlugin) Init(path string) error {
|
|
||||||
|
|
||||||
f, err := pluginHost.Init(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.FuncCaller = f
|
|
||||||
|
|
||||||
p.cachedFunctions = make(map[string]bool)
|
|
||||||
log.Info().Str("path", path).Msg("load hashicorp go plugin success")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HashicorpPlugin) Has(funcName string) bool {
|
|
||||||
flag, ok := p.cachedFunctions[funcName]
|
|
||||||
if ok {
|
|
||||||
return flag
|
|
||||||
}
|
|
||||||
|
|
||||||
funcNames, err := p.GetNames()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range funcNames {
|
|
||||||
if name == funcName {
|
|
||||||
p.cachedFunctions[funcName] = true // cache as exists
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cachedFunctions[funcName] = false // cache as not exists
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HashicorpPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
|
|
||||||
return p.FuncCaller.Call(funcName, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HashicorpPlugin) Quit() error {
|
|
||||||
// kill hashicorp plugin process
|
|
||||||
log.Info().Msg("quit hashicorp plugin process")
|
|
||||||
pluginHost.Quit()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(path string) (Plugin, error) {
|
|
||||||
if path == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var plugin Plugin
|
|
||||||
|
|
||||||
// priority: hashicorp plugin > go plugin
|
|
||||||
// locate hashicorp plugin file
|
|
||||||
pluginPath, err := locateFile(path, hashicorpGoPluginFile)
|
|
||||||
if err == nil {
|
|
||||||
// found hashicorp go plugin file
|
|
||||||
plugin = &HashicorpPlugin{}
|
|
||||||
err = plugin.Init(pluginPath)
|
|
||||||
return plugin, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// locate go plugin file
|
|
||||||
pluginPath, err = locateFile(path, goPluginFile)
|
|
||||||
if err == nil {
|
|
||||||
// found go plugin file
|
|
||||||
plugin = &GoPlugin{}
|
|
||||||
err = plugin.Init(pluginPath)
|
|
||||||
return plugin, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// plugin not found
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// locateFile searches destFile upward recursively until current
|
|
||||||
// working directory or system root dir.
|
|
||||||
func locateFile(startPath string, destFile 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(destFile))
|
|
||||||
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 locateFile(parentDir, destFile)
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package host
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
hclog "github.com/hashicorp/go-hclog"
|
|
||||||
"github.com/hashicorp/go-plugin"
|
|
||||||
|
|
||||||
"github.com/httprunner/hrp/plugin/shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
var client *plugin.Client
|
|
||||||
|
|
||||||
func Init(path string) (shared.FuncCaller, error) {
|
|
||||||
// launch the plugin process
|
|
||||||
client = plugin.NewClient(&plugin.ClientConfig{
|
|
||||||
HandshakeConfig: shared.HandshakeConfig,
|
|
||||||
Plugins: map[string]plugin.Plugin{
|
|
||||||
shared.Name: &shared.HashicorpPlugin{},
|
|
||||||
},
|
|
||||||
Cmd: exec.Command(path),
|
|
||||||
Logger: hclog.New(&hclog.LoggerOptions{
|
|
||||||
Name: shared.Name,
|
|
||||||
Output: os.Stdout,
|
|
||||||
Level: hclog.Info,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Connect via RPC
|
|
||||||
rpcClient, err := client.Client()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request the plugin
|
|
||||||
raw, err := rpcClient.Dispense(shared.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should have a Function now! This feels like a normal interface
|
|
||||||
// implementation but is in fact over an RPC connection.
|
|
||||||
function := raw.(shared.FuncCaller)
|
|
||||||
return function, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Quit() {
|
|
||||||
client.Kill()
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package common
|
package pluginInternal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package common
|
package pluginInternal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package shared
|
package pluginInternal
|
||||||
|
|
||||||
import "github.com/hashicorp/go-plugin"
|
import "github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
const Name = "debugtalk"
|
const PluginName = "debugtalk"
|
||||||
|
|
||||||
// handshakeConfigs are used to just do a basic handshake between
|
// handshakeConfigs are used to just do a basic handshake between
|
||||||
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
||||||
@@ -11,5 +11,5 @@ const Name = "debugtalk"
|
|||||||
var HandshakeConfig = plugin.HandshakeConfig{
|
var HandshakeConfig = plugin.HandshakeConfig{
|
||||||
ProtocolVersion: 1,
|
ProtocolVersion: 1,
|
||||||
MagicCookieKey: "HttpRunnerPlus",
|
MagicCookieKey: "HttpRunnerPlus",
|
||||||
MagicCookieValue: Name,
|
MagicCookieValue: PluginName,
|
||||||
}
|
}
|
||||||
70
plugin/inner/go_plugin.go
Normal file
70
plugin/inner/go_plugin.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package pluginInternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"plugin"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoPlugin implements golang official plugin
|
||||||
|
type GoPlugin struct {
|
||||||
|
*plugin.Plugin
|
||||||
|
cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.Plugin, err = plugin.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("path", path).Msg("load go plugin failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.cachedFunctions = make(map[string]reflect.Value)
|
||||||
|
log.Info().Str("path", path).Msg("load go plugin success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GoPlugin) Has(funcName string) bool {
|
||||||
|
fn, ok := p.cachedFunctions[funcName]
|
||||||
|
if ok {
|
||||||
|
return fn.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
sym, err := p.Plugin.Lookup(funcName)
|
||||||
|
if err != nil {
|
||||||
|
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fn = reflect.ValueOf(sym)
|
||||||
|
|
||||||
|
// check function type
|
||||||
|
if fn.Kind() != reflect.Func {
|
||||||
|
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.cachedFunctions[funcName] = fn
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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 CallFunc(fn, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GoPlugin) Quit() error {
|
||||||
|
// no need to quit for go plugin
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// +build linux freebsd darwin
|
// +build linux freebsd darwin
|
||||||
// go plugin doesn't support windows
|
// go plugin doesn't support windows
|
||||||
|
|
||||||
package common
|
package pluginInternal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -70,7 +70,7 @@ func TestCallPluginFunction(t *testing.T) {
|
|||||||
buildGoPlugin()
|
buildGoPlugin()
|
||||||
defer removeGoPlugin()
|
defer removeGoPlugin()
|
||||||
|
|
||||||
plugin, err := Init("debugtalk.so")
|
plugin, err := Init("debugtalk.so", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
95
plugin/inner/hashicorp_plugin.go
Normal file
95
plugin/inner/hashicorp_plugin.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package pluginInternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
|
"github.com/hashicorp/go-plugin"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var client *plugin.Client
|
||||||
|
|
||||||
|
// HashicorpPlugin implements hashicorp/go-plugin
|
||||||
|
type HashicorpPlugin struct {
|
||||||
|
logOn bool // turn on plugin log
|
||||||
|
FuncCaller
|
||||||
|
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HashicorpPlugin) Init(path string) error {
|
||||||
|
loggerOptions := &hclog.LoggerOptions{
|
||||||
|
Name: PluginName,
|
||||||
|
Output: os.Stdout,
|
||||||
|
}
|
||||||
|
if p.logOn {
|
||||||
|
loggerOptions.Level = hclog.Debug
|
||||||
|
} else {
|
||||||
|
loggerOptions.Level = hclog.Info
|
||||||
|
}
|
||||||
|
// launch the plugin process
|
||||||
|
client = plugin.NewClient(&plugin.ClientConfig{
|
||||||
|
HandshakeConfig: HandshakeConfig,
|
||||||
|
Plugins: map[string]plugin.Plugin{
|
||||||
|
PluginName: &HRPPlugin{},
|
||||||
|
},
|
||||||
|
Cmd: exec.Command(path),
|
||||||
|
Logger: hclog.New(loggerOptions),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Connect via RPC
|
||||||
|
rpcClient, err := client.Client()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("connect plugin via RPC failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request the plugin
|
||||||
|
raw, err := rpcClient.Dispense(PluginName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("request plugin failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have a Function now! This feels like a normal interface
|
||||||
|
// implementation but is in fact over an RPC connection.
|
||||||
|
p.FuncCaller = raw.(FuncCaller)
|
||||||
|
|
||||||
|
p.cachedFunctions = make(map[string]bool)
|
||||||
|
log.Info().Str("path", path).Msg("load hashicorp go plugin success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HashicorpPlugin) Has(funcName string) bool {
|
||||||
|
flag, ok := p.cachedFunctions[funcName]
|
||||||
|
if ok {
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
|
||||||
|
funcNames, err := p.GetNames()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range funcNames {
|
||||||
|
if name == funcName {
|
||||||
|
p.cachedFunctions[funcName] = true // cache as exists
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.cachedFunctions[funcName] = false // cache as not exists
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HashicorpPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
|
||||||
|
return p.FuncCaller.Call(funcName, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HashicorpPlugin) Quit() error {
|
||||||
|
// kill hashicorp plugin process
|
||||||
|
log.Info().Msg("quit hashicorp plugin process")
|
||||||
|
client.Kill()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package common
|
package pluginInternal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -28,7 +28,7 @@ func TestInitHashicorpPlugin(t *testing.T) {
|
|||||||
buildHashicorpPlugin()
|
buildHashicorpPlugin()
|
||||||
defer removeHashicorpPlugin()
|
defer removeHashicorpPlugin()
|
||||||
|
|
||||||
plugin, err := Init("../../examples/debugtalk.bin")
|
plugin, err := Init("../../examples/debugtalk.bin", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package shared
|
package pluginInternal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
@@ -89,15 +89,22 @@ func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashicorpPlugin implements hashicorp's plugin.Plugin.
|
// HRPPlugin implements hashicorp's plugin.Plugin.
|
||||||
type HashicorpPlugin struct {
|
type HRPPlugin struct {
|
||||||
Impl FuncCaller
|
Impl FuncCaller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
func (p *HRPPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||||
return &functionRPCServer{Impl: p.Impl}, nil
|
return &functionRPCServer{Impl: p.Impl}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (HashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
func (HRPPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||||
return &functionRPC{client: c}, nil
|
return &functionRPC{client: c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPlugin 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
|
||||||
|
}
|
||||||
83
plugin/inner/init.go
Normal file
83
plugin/inner/init.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package pluginInternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pluginFile string
|
||||||
|
|
||||||
|
const (
|
||||||
|
goPluginFile pluginFile = PluginName + ".so" // built from go plugin
|
||||||
|
hashicorpGoPluginFile pluginFile = PluginName + ".bin" // built from hashicorp go plugin
|
||||||
|
hashicorpPyPluginFile pluginFile = PluginName + ".py"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init(path string, logOn bool) (IPlugin, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var plugin IPlugin
|
||||||
|
|
||||||
|
// priority: hashicorp plugin > go plugin
|
||||||
|
// locate hashicorp plugin file
|
||||||
|
pluginPath, err := locateFile(path, hashicorpGoPluginFile)
|
||||||
|
if err == nil {
|
||||||
|
// found hashicorp go plugin file
|
||||||
|
plugin = &HashicorpPlugin{
|
||||||
|
logOn: logOn,
|
||||||
|
}
|
||||||
|
err = plugin.Init(pluginPath)
|
||||||
|
return plugin, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate go plugin file
|
||||||
|
pluginPath, err = locateFile(path, goPluginFile)
|
||||||
|
if err == nil {
|
||||||
|
// found go plugin file
|
||||||
|
plugin = &GoPlugin{}
|
||||||
|
err = plugin.Init(pluginPath)
|
||||||
|
return plugin, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugin not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// locateFile searches destFile upward recursively until current
|
||||||
|
// working directory or system root dir.
|
||||||
|
func locateFile(startPath string, destFile 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(destFile))
|
||||||
|
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 locateFile(parentDir, destFile)
|
||||||
|
}
|
||||||
@@ -8,8 +8,7 @@ import (
|
|||||||
hclog "github.com/hashicorp/go-hclog"
|
hclog "github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"github.com/httprunner/hrp/plugin/common"
|
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
||||||
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// functionsMap stores plugin functions
|
// functionsMap stores plugin functions
|
||||||
@@ -37,7 +36,7 @@ func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}
|
|||||||
return nil, fmt.Errorf("function %s not found", funcName)
|
return nil, fmt.Errorf("function %s not found", funcName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.CallFunc(fn, args...)
|
return pluginInternal.CallFunc(fn, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var functions = make(functionsMap)
|
var functions = make(functionsMap)
|
||||||
@@ -55,17 +54,17 @@ func Register(funcName string, fn interface{}) {
|
|||||||
func Serve() {
|
func Serve() {
|
||||||
funcPlugin := &functionPlugin{
|
funcPlugin := &functionPlugin{
|
||||||
logger: hclog.New(&hclog.LoggerOptions{
|
logger: hclog.New(&hclog.LoggerOptions{
|
||||||
Name: pluginShared.Name,
|
Name: pluginInternal.PluginName,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
Level: hclog.Info,
|
Level: hclog.Info,
|
||||||
}),
|
}),
|
||||||
functions: functions,
|
functions: functions,
|
||||||
}
|
}
|
||||||
var pluginMap = map[string]plugin.Plugin{
|
var pluginMap = map[string]plugin.Plugin{
|
||||||
pluginShared.Name: &pluginShared.HashicorpPlugin{Impl: funcPlugin},
|
pluginInternal.PluginName: &pluginInternal.HRPPlugin{Impl: funcPlugin},
|
||||||
}
|
}
|
||||||
plugin.Serve(&plugin.ServeConfig{
|
plugin.Serve(&plugin.ServeConfig{
|
||||||
HandshakeConfig: pluginShared.HandshakeConfig,
|
HandshakeConfig: pluginInternal.HandshakeConfig,
|
||||||
Plugins: pluginMap,
|
Plugins: pluginMap,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
82
runner.go
82
runner.go
@@ -32,7 +32,7 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/hrp/internal/builtin"
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
"github.com/httprunner/hrp/plugin/common"
|
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -43,7 +43,7 @@ const (
|
|||||||
// Run starts to run API test with default configs.
|
// Run starts to run API test with default configs.
|
||||||
func Run(testcases ...ITestCase) error {
|
func Run(testcases ...ITestCase) error {
|
||||||
t := &testing.T{}
|
t := &testing.T{}
|
||||||
return NewRunner(t).SetDebug(true).Run(testcases...)
|
return NewRunner(t).SetRequestsLogOn().Run(testcases...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRunner constructs a new runner instance.
|
// NewRunner constructs a new runner instance.
|
||||||
@@ -53,8 +53,7 @@ func NewRunner(t *testing.T) *HRPRunner {
|
|||||||
}
|
}
|
||||||
return &HRPRunner{
|
return &HRPRunner{
|
||||||
t: t,
|
t: t,
|
||||||
failfast: true, // default to failfast
|
failfast: true, // default to failfast
|
||||||
debug: false, // default to turn off debug
|
|
||||||
genHTMLReport: false,
|
genHTMLReport: false,
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
@@ -68,7 +67,8 @@ func NewRunner(t *testing.T) *HRPRunner {
|
|||||||
type HRPRunner struct {
|
type HRPRunner struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
failfast bool
|
failfast bool
|
||||||
debug bool
|
requestsLogOn bool
|
||||||
|
pluginLogOn bool
|
||||||
saveTests bool
|
saveTests bool
|
||||||
genHTMLReport bool
|
genHTMLReport bool
|
||||||
client *http.Client
|
client *http.Client
|
||||||
@@ -81,10 +81,17 @@ func (r *HRPRunner) SetFailfast(failfast bool) *HRPRunner {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDebug configures whether to log HTTP request and response content.
|
// SetRequestsLogOn turns on request & response details logging.
|
||||||
func (r *HRPRunner) SetDebug(debug bool) *HRPRunner {
|
func (r *HRPRunner) SetRequestsLogOn() *HRPRunner {
|
||||||
log.Info().Bool("debug", debug).Msg("[init] SetDebug")
|
log.Info().Msg("[init] SetRequestsLogOn")
|
||||||
r.debug = debug
|
r.requestsLogOn = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPluginLogOn turns on plugin logging.
|
||||||
|
func (r *HRPRunner) SetPluginLogOn() *HRPRunner {
|
||||||
|
log.Info().Msg("[init] SetPluginLogOn")
|
||||||
|
r.pluginLogOn = true
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +228,7 @@ func (r *caseRunner) run() error {
|
|||||||
config := r.TestCase.Config
|
config := r.TestCase.Config
|
||||||
// init plugin
|
// init plugin
|
||||||
var err error
|
var err error
|
||||||
if r.parser.plugin, err = initPlugin(config.Path); err != nil {
|
if r.parser.plugin, err = initPlugin(config.Path, r.hrpRunner.pluginLogOn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -247,9 +254,7 @@ func (r *caseRunner) run() error {
|
|||||||
// merge test case if the step is test case
|
// merge test case if the step is test case
|
||||||
summary, ok := stepDataObj.Data.(*testCaseSummary)
|
summary, ok := stepDataObj.Data.(*testCaseSummary)
|
||||||
if ok {
|
if ok {
|
||||||
for _, rc := range summary.Records {
|
r.summary.Records = append(r.summary.Records, summary.Records...)
|
||||||
r.summary.Records = append(r.summary.Records, rc)
|
|
||||||
}
|
|
||||||
r.summary.Stat.Total += summary.Stat.Total
|
r.summary.Stat.Total += summary.Stat.Total
|
||||||
r.summary.Stat.Successes += summary.Stat.Successes
|
r.summary.Stat.Successes += summary.Stat.Successes
|
||||||
r.summary.Stat.Failures += summary.Stat.Failures
|
r.summary.Stat.Failures += summary.Stat.Failures
|
||||||
@@ -277,8 +282,8 @@ func (r *caseRunner) run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initPlugin(path string) (plugin common.Plugin, err error) {
|
func initPlugin(path string, logOn bool) (plugin pluginInternal.IPlugin, err error) {
|
||||||
plugin, err = common.Init(path)
|
plugin, err = pluginInternal.Init(path, logOn)
|
||||||
if plugin == nil {
|
if plugin == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -294,7 +299,7 @@ func initPlugin(path string) (plugin common.Plugin, err error) {
|
|||||||
|
|
||||||
// report event for initializing plugin
|
// report event for initializing plugin
|
||||||
var pluginType string
|
var pluginType string
|
||||||
if _, ok := plugin.(*common.GoPlugin); ok {
|
if _, ok := plugin.(*pluginInternal.GoPlugin); ok {
|
||||||
pluginType = "go"
|
pluginType = "go"
|
||||||
} else {
|
} else {
|
||||||
pluginType = "hashicorp"
|
pluginType = "hashicorp"
|
||||||
@@ -616,14 +621,6 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
}
|
}
|
||||||
sessionData := newSessionData()
|
sessionData := newSessionData()
|
||||||
|
|
||||||
// deal with setup hooks
|
|
||||||
for _, setupHook := range step.SetupHooks {
|
|
||||||
_, err = r.parser.parseData(setupHook, step.Variables)
|
|
||||||
if err != nil {
|
|
||||||
return stepResult, errors.Wrap(err, "run setup hooks failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert request struct to map
|
// convert request struct to map
|
||||||
jsonRequest, _ := json.Marshal(&step.Request)
|
jsonRequest, _ := json.Marshal(&step.Request)
|
||||||
var requestMap map[string]interface{}
|
var requestMap map[string]interface{}
|
||||||
@@ -764,6 +761,18 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
req.URL = u
|
req.URL = u
|
||||||
req.Host = u.Host
|
req.Host = u.Host
|
||||||
|
|
||||||
|
// add request object to step variables, could be used in setup hooks
|
||||||
|
step.Variables["hrp_step_name"] = step.Name
|
||||||
|
step.Variables["hrp_step_request"] = requestMap
|
||||||
|
|
||||||
|
// deal with setup hooks
|
||||||
|
for _, setupHook := range step.SetupHooks {
|
||||||
|
_, err = r.parser.parseData(setupHook, step.Variables)
|
||||||
|
if err != nil {
|
||||||
|
return stepResult, errors.Wrap(err, "run setup hooks failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// log & print request
|
// log & print request
|
||||||
if err := r.printRequest(req); err != nil {
|
if err := r.printRequest(req); err != nil {
|
||||||
return stepResult, err
|
return stepResult, err
|
||||||
@@ -795,6 +804,18 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
err = errors.Wrap(err, "init ResponseObject error")
|
err = errors.Wrap(err, "init ResponseObject error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add response object to step variables, could be used in teardown hooks
|
||||||
|
step.Variables["hrp_step_response"] = respObj.respObjMeta
|
||||||
|
|
||||||
|
// deal with teardown hooks
|
||||||
|
for _, teardownHook := range step.TeardownHooks {
|
||||||
|
_, err = r.parser.parseData(teardownHook, step.Variables)
|
||||||
|
if err != nil {
|
||||||
|
return stepResult, errors.Wrap(err, "run teardown hooks failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sessionData.ReqResps.Request = requestMap
|
sessionData.ReqResps.Request = requestMap
|
||||||
sessionData.ReqResps.Response = builtin.FormatResponse(respObj.respObjMeta)
|
sessionData.ReqResps.Response = builtin.FormatResponse(respObj.respObjMeta)
|
||||||
|
|
||||||
@@ -816,18 +837,11 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
stepResult.ContentSize = resp.ContentLength
|
stepResult.ContentSize = resp.ContentLength
|
||||||
stepResult.Data = sessionData
|
stepResult.Data = sessionData
|
||||||
|
|
||||||
// deal with teardown hooks
|
|
||||||
for _, teardownHook := range step.TeardownHooks {
|
|
||||||
_, err = r.parser.parseData(teardownHook, step.Variables)
|
|
||||||
if err != nil {
|
|
||||||
return stepResult, errors.Wrap(err, "run teardown hooks failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stepResult, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *caseRunner) printRequest(req *http.Request) error {
|
func (r *caseRunner) printRequest(req *http.Request) error {
|
||||||
if !r.hrpRunner.debug {
|
if !r.hrpRunner.requestsLogOn {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
reqContentType := req.Header.Get("Content-Type")
|
reqContentType := req.Header.Get("Content-Type")
|
||||||
@@ -846,7 +860,7 @@ func (r *caseRunner) printRequest(req *http.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *caseRunner) printResponse(resp *http.Response) error {
|
func (r *caseRunner) printResponse(resp *http.Response) error {
|
||||||
if !r.hrpRunner.debug {
|
if !r.hrpRunner.requestsLogOn {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fmt.Println("==================== response ===================")
|
fmt.Println("==================== response ===================")
|
||||||
@@ -877,7 +891,7 @@ func shouldPrintBody(contentType string) bool {
|
|||||||
if strings.HasPrefix(contentType, "application/xml") {
|
if strings.HasPrefix(contentType, "application/xml") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(contentType, "application/www-form-urlencoded") {
|
if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func TestRunRequestRun(t *testing.T) {
|
|||||||
Config: NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
Config: NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{stepGET, stepPOSTData},
|
TestSteps: []IStep{stepGET, stepPOSTData},
|
||||||
}
|
}
|
||||||
runner := NewRunner(t).SetDebug(true).newCaseRunner(testcase)
|
runner := NewRunner(t).SetRequestsLogOn().newCaseRunner(testcase)
|
||||||
if _, err := runner.runStep(0, testcase.Config); err != nil {
|
if _, err := runner.runStep(0, testcase.Config); err != nil {
|
||||||
t.Fatalf("tStep.Run() error: %s", err)
|
t.Fatalf("tStep.Run() error: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user