259 lines
7.4 KiB
Go
259 lines
7.4 KiB
Go
package hrp
|
|
|
|
import (
|
|
"bufio"
|
|
_ "embed"
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/httprunner/funplugin/fungo"
|
|
"github.com/httprunner/funplugin/myexec"
|
|
"github.com/httprunner/httprunner/v5/code"
|
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
"github.com/httprunner/httprunner/v5/internal/config"
|
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
|
"github.com/httprunner/httprunner/v5/internal/version"
|
|
)
|
|
|
|
//go:embed internal/scaffold/templates/plugin/debugtalkPythonTemplate
|
|
var pyTemplate string
|
|
|
|
//go:embed internal/scaffold/templates/plugin/debugtalkGoTemplate
|
|
var goTemplate string
|
|
|
|
// regex for finding all function names
|
|
type regexFunctions struct {
|
|
*regexp.Regexp
|
|
}
|
|
|
|
var (
|
|
regexPyFunctionName = regexFunctions{regexp.MustCompile(`(?m)^def ([a-zA-Z_]\w*)\(.*\)`)}
|
|
regexGoFunctionName = regexFunctions{regexp.MustCompile(`(?m)^func ([a-zA-Z_]\w*)\(.*\)`)}
|
|
)
|
|
|
|
func (r *regexFunctions) findAllFunctionNames(content string) ([]string, error) {
|
|
var functionNames []string
|
|
// find all function names
|
|
functionNameSlice := r.FindAllStringSubmatch(content, -1)
|
|
for _, elem := range functionNameSlice {
|
|
name := strings.Trim(elem[1], " ")
|
|
functionNames = append(functionNames, name)
|
|
}
|
|
|
|
var filteredFunctionNames []string
|
|
if r == ®exPyFunctionName {
|
|
// filter private functions
|
|
for _, name := range functionNames {
|
|
if strings.HasPrefix(name, "__") {
|
|
continue
|
|
}
|
|
filteredFunctionNames = append(filteredFunctionNames, name)
|
|
}
|
|
} else if r == ®exGoFunctionName {
|
|
// filter main and init function
|
|
for _, name := range functionNames {
|
|
if name == "main" {
|
|
log.Warn().Msg("plugin debugtalk.go should not define main() function !!!")
|
|
return nil, errors.New("debugtalk.go should not contain main() function")
|
|
}
|
|
if name == "init" {
|
|
continue
|
|
}
|
|
filteredFunctionNames = append(filteredFunctionNames, name)
|
|
}
|
|
}
|
|
|
|
log.Info().Strs("functionNames", filteredFunctionNames).Msg("find all function names")
|
|
return filteredFunctionNames, nil
|
|
}
|
|
|
|
type pluginTemplate struct {
|
|
path string // file path
|
|
Version string // hrp version
|
|
FunctionNames []string // function names
|
|
}
|
|
|
|
func (pt *pluginTemplate) generate(tmpl, output string) error {
|
|
file, err := os.Create(output)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("open output file failed")
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
writer := bufio.NewWriter(file)
|
|
err = template.Must(template.New("debugtalk").Parse(tmpl)).Execute(writer, pt)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("execute template parsing failed")
|
|
return err
|
|
}
|
|
|
|
err = writer.Flush()
|
|
if err == nil {
|
|
log.Info().Str("output", output).Msg("generate debugtalk success")
|
|
} else {
|
|
log.Error().Str("output", output).Msg("generate debugtalk failed")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (pt *pluginTemplate) generatePy(output string) error {
|
|
// specify output file path
|
|
if output == "" {
|
|
output = filepath.Join(config.GetConfig().RootDir, PluginPySourceGenFile)
|
|
} else if builtin.IsFolderPathExists(output) {
|
|
output = filepath.Join(output, PluginPySourceGenFile)
|
|
}
|
|
|
|
// generate .debugtalk_gen.py
|
|
err := pt.generate(pyTemplate, output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Info().Str("output", output).Str("plugin", pt.path).Msg("build python plugin successfully")
|
|
return nil
|
|
}
|
|
|
|
func (pt *pluginTemplate) generateGo(output string) error {
|
|
pluginDir := filepath.Dir(pt.path)
|
|
err := pt.generate(goTemplate, filepath.Join(pluginDir, PluginGoSourceGenFile))
|
|
if err != nil {
|
|
return errors.Wrap(err, "generate hashicorp plugin failed")
|
|
}
|
|
|
|
// check go sdk in tempDir
|
|
if err := myexec.RunCommand("go", "version"); err != nil {
|
|
return errors.Wrap(err, "go sdk not installed")
|
|
}
|
|
|
|
if !builtin.IsFilePathExists(filepath.Join(pluginDir, "go.mod")) {
|
|
// create go mod
|
|
if err := myexec.ExecCommandInDir(myexec.Command("go", "mod", "init", "main"), pluginDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
// download plugin dependency
|
|
// funplugin version should be locked
|
|
funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", fungo.Version)
|
|
if err := myexec.ExecCommandInDir(myexec.Command("go", "get", funplugin), pluginDir); err != nil {
|
|
return errors.Wrap(err, "go get funplugin failed")
|
|
}
|
|
}
|
|
|
|
// add missing and remove unused modules
|
|
if err := myexec.ExecCommandInDir(myexec.Command("go", "mod", "tidy"), pluginDir); err != nil {
|
|
return errors.Wrap(err, "go mod tidy failed")
|
|
}
|
|
|
|
// specify output file path
|
|
if output == "" {
|
|
output = filepath.Join(config.GetConfig().RootDir, PluginHashicorpGoBuiltFile)
|
|
} else if builtin.IsFolderPathExists(output) {
|
|
output = filepath.Join(output, PluginHashicorpGoBuiltFile)
|
|
}
|
|
outputPath, _ := filepath.Abs(output)
|
|
|
|
// build go plugin to debugtalk.bin
|
|
cmd := myexec.Command("go", "build", "-o", outputPath, PluginGoSourceGenFile, filepath.Base(pt.path))
|
|
if err := myexec.ExecCommandInDir(cmd, pluginDir); err != nil {
|
|
return errors.Wrap(err, "go build plugin failed")
|
|
}
|
|
log.Info().Str("output", outputPath).Str("plugin", pt.path).Msg("build go plugin successfully")
|
|
return nil
|
|
}
|
|
|
|
// buildGo builds debugtalk.go to debugtalk.bin
|
|
func buildGo(path string, output string) error {
|
|
log.Info().Str("path", path).Str("output", output).Msg("start to build go plugin")
|
|
|
|
// report GA event
|
|
sdk.SendGA4Event("hrp_build_plugin", map[string]interface{}{
|
|
"pluginType": "go",
|
|
})
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to read file")
|
|
return errors.Wrap(code.LoadFileError, err.Error())
|
|
}
|
|
functionNames, err := regexGoFunctionName.findAllFunctionNames(string(content))
|
|
if err != nil {
|
|
return errors.Wrap(code.InvalidPluginFile, err.Error())
|
|
}
|
|
|
|
templateContent := &pluginTemplate{
|
|
path: path,
|
|
Version: version.VERSION,
|
|
FunctionNames: functionNames,
|
|
}
|
|
err = templateContent.generateGo(output)
|
|
if err != nil {
|
|
return errors.Wrap(code.BuildGoPluginFailed, err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// buildPy completes funppy information in debugtalk.py
|
|
func buildPy(path string, output string) error {
|
|
log.Info().Str("path", path).Str("output", output).Msg("start to prepare python plugin")
|
|
|
|
// report GA event
|
|
sdk.SendGA4Event("hrp_build_plugin", map[string]interface{}{
|
|
"pluginType": "python",
|
|
})
|
|
|
|
// check the syntax of debugtalk.py
|
|
err := myexec.ExecPython3Command("py_compile", path)
|
|
if err != nil {
|
|
return errors.Wrap(code.InvalidPluginFile,
|
|
fmt.Sprintf("python plugin syntax invalid: %s", err.Error()))
|
|
}
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to read file")
|
|
return errors.Wrap(code.LoadFileError, err.Error())
|
|
}
|
|
functionNames, err := regexPyFunctionName.findAllFunctionNames(string(content))
|
|
if err != nil {
|
|
return errors.Wrap(code.InvalidPluginFile, err.Error())
|
|
}
|
|
|
|
templateContent := &pluginTemplate{
|
|
path: path,
|
|
Version: version.VERSION,
|
|
FunctionNames: functionNames,
|
|
}
|
|
err = templateContent.generatePy(output)
|
|
if err != nil {
|
|
return errors.Wrap(code.BuildPyPluginFailed, err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func BuildPlugin(path string, output string) (err error) {
|
|
ext := filepath.Ext(path)
|
|
switch ext {
|
|
case ".py":
|
|
err = buildPy(path, output)
|
|
case ".go":
|
|
err = buildGo(path, output)
|
|
default:
|
|
return errors.Wrap(code.UnsupportedFileExtension,
|
|
"type error, expected .py or .go")
|
|
}
|
|
if err != nil {
|
|
log.Error().Err(err).Str("path", path).Msg("build plugin failed")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|