193 lines
5.4 KiB
Go
193 lines
5.4 KiB
Go
package hrp
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/httprunner/funplugin"
|
|
"github.com/httprunner/funplugin/myexec"
|
|
"github.com/httprunner/httprunner/v5/code"
|
|
"github.com/httprunner/httprunner/v5/internal/config"
|
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
|
)
|
|
|
|
const (
|
|
PluginGoBuiltFile = "debugtalk.so" // built from go official plugin
|
|
PluginHashicorpGoBuiltFile = "debugtalk.bin" // built from hashicorp go plugin
|
|
PluginGoSourceFile = "debugtalk.go" // golang function plugin source file
|
|
PluginGoSourceGenFile = "debugtalk_gen.go" // generated for hashicorp go plugin
|
|
PluginPySourceFile = "debugtalk.py" // python function plugin source file
|
|
PluginPySourceGenFile = ".debugtalk_gen.py" // generated for hashicorp python plugin
|
|
)
|
|
|
|
const projectInfoFile = "proj.json" // used for ensuring root project
|
|
|
|
var (
|
|
pluginMap sync.Map // used for reusing plugin instance
|
|
pluginMutex sync.RWMutex
|
|
)
|
|
|
|
func initPlugin(path, venv string, logOn bool) (plugin funplugin.IPlugin, err error) {
|
|
log.Info().Str("path", path).Str("venv", venv).
|
|
Bool("logOn", logOn).Msg("init plugin")
|
|
// plugin file not found
|
|
if path == "" {
|
|
return nil, errors.New("testcase path not specified")
|
|
}
|
|
pluginPath, err := LocatePlugin(path)
|
|
if err != nil {
|
|
log.Warn().Str("path", path).Msg("locate plugin failed")
|
|
return nil, errors.Wrap(err, "locate plugin failed")
|
|
}
|
|
|
|
pluginMutex.Lock()
|
|
defer pluginMutex.Unlock()
|
|
|
|
// reuse plugin instance if it already initialized
|
|
if p, ok := pluginMap.Load(pluginPath); ok {
|
|
return p.(funplugin.IPlugin), nil
|
|
}
|
|
|
|
pluginOptions := []funplugin.Option{funplugin.WithDebugLogger(logOn)}
|
|
|
|
if strings.HasSuffix(pluginPath, ".py") {
|
|
// register funppy plugin
|
|
genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), PluginPySourceGenFile)
|
|
err = BuildPlugin(pluginPath, genPyPluginPath)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("path", pluginPath).Msg("build plugin failed")
|
|
return nil, err
|
|
}
|
|
pluginPath = genPyPluginPath
|
|
|
|
packages := []string{"funppy"}
|
|
python3, err := myexec.EnsurePython3Venv(venv, packages...)
|
|
if err != nil {
|
|
log.Error().Err(err).
|
|
Interface("packages", packages).
|
|
Msg("python3 venv is not ready")
|
|
return nil, errors.Wrap(code.InvalidPython3Venv, err.Error())
|
|
}
|
|
pluginOptions = append(pluginOptions, funplugin.WithPython3(python3))
|
|
}
|
|
|
|
// found plugin file
|
|
plugin, err = funplugin.Init(pluginPath, pluginOptions...)
|
|
if err != nil {
|
|
log.Error().Str("path", pluginPath).Msg("init plugin failed")
|
|
err = errors.Wrap(code.InitPluginFailed, err.Error())
|
|
return
|
|
}
|
|
|
|
// add plugin instance to plugin map
|
|
pluginMap.Store(pluginPath, plugin)
|
|
|
|
// report event for initializing plugin
|
|
params := map[string]interface{}{
|
|
"type": plugin.Type(),
|
|
"result": "success",
|
|
}
|
|
if err != nil {
|
|
params["result"] = "failed"
|
|
}
|
|
go sdk.SendGA4Event("init_plugin", params)
|
|
|
|
return
|
|
}
|
|
|
|
func LocatePlugin(path string) (pluginPath string, err error) {
|
|
log.Info().Str("path", path).Msg("locate plugin")
|
|
// priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so)
|
|
|
|
pluginPath, err = LocateFile(path, PluginHashicorpGoBuiltFile)
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
pluginPath, err = LocateFile(path, PluginPySourceFile)
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
pluginPath, err = LocateFile(path, PluginGoBuiltFile)
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
return "", errors.New("plugin file not found")
|
|
}
|
|
|
|
// LocateFile searches destFile upward recursively until system root dir
|
|
// if not found, then searches in hrp executable dir
|
|
func LocateFile(startPath string, destFile string) (pluginPath string, err error) {
|
|
stat, err := os.Stat(startPath)
|
|
if os.IsNotExist(err) {
|
|
return "", errors.Wrap(err, "start path not exists")
|
|
}
|
|
|
|
var startDir string
|
|
if stat.IsDir() {
|
|
startDir = startPath
|
|
} else {
|
|
startDir = filepath.Dir(startPath)
|
|
}
|
|
startDir, _ = filepath.Abs(startDir)
|
|
|
|
// convention over configuration
|
|
pluginPath = filepath.Join(startDir, destFile)
|
|
if _, err := os.Stat(pluginPath); err == nil {
|
|
return pluginPath, nil
|
|
}
|
|
|
|
// system root dir
|
|
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
|
|
if parentDir == startDir {
|
|
if pluginPath, err = locateExecutable(destFile); err == nil {
|
|
return
|
|
}
|
|
return "", errors.New("searched to system root dir, plugin file not found")
|
|
}
|
|
|
|
return LocateFile(parentDir, destFile)
|
|
}
|
|
|
|
// locateExecutable finds destFile in hrp executable dir
|
|
func locateExecutable(destFile string) (string, error) {
|
|
exePath, err := os.Executable()
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "get hrp executable failed")
|
|
}
|
|
|
|
exeDir := filepath.Dir(exePath)
|
|
pluginPath := filepath.Join(exeDir, destFile)
|
|
if _, err := os.Stat(pluginPath); err == nil {
|
|
return pluginPath, nil
|
|
}
|
|
|
|
return "", errors.New("plugin file not found in hrp executable dir")
|
|
}
|
|
|
|
func GetProjectRootDirPath(path string) (rootDir string, err error) {
|
|
pluginPath, err := LocatePlugin(path)
|
|
if err == nil {
|
|
rootDir = filepath.Dir(pluginPath)
|
|
return
|
|
}
|
|
// fix: no debugtalk file in project but having proj.json created by startproject
|
|
projPath, err := LocateFile(path, projectInfoFile)
|
|
if err == nil {
|
|
rootDir = filepath.Dir(projPath)
|
|
return
|
|
}
|
|
|
|
// failed to locate project root dir
|
|
// maybe project plugin debugtalk.xx and proj.json are not exist
|
|
// use current dir instead
|
|
return config.GetConfig().RootDir, nil
|
|
}
|