mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +08:00
Merge pull request #1314 from xucong053/add-build-command-for-plugin
feat: add build command for plugin
This commit is contained in:
+2
-1
@@ -30,6 +30,7 @@ Copyright 2017 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 build](hrp_build.md) - build plugin for testing
|
||||||
* [hrp convert](hrp_convert.md) - convert to JSON/YAML/gotest/pytest testcases
|
* [hrp convert](hrp_convert.md) - convert to JSON/YAML/gotest/pytest testcases
|
||||||
* [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 pytest](hrp_pytest.md) - run API test with pytest
|
* [hrp pytest](hrp_pytest.md) - run API test with pytest
|
||||||
@@ -37,4 +38,4 @@ Copyright 2017 debugtalk
|
|||||||
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
||||||
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
|
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -42,4 +42,4 @@ hrp boom [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
## hrp build
|
||||||
|
|
||||||
|
build plugin for testing
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
build python/go plugin for testing
|
||||||
|
|
||||||
|
```
|
||||||
|
hrp build $path ... [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
$ hrp build plugin/debugtalk.go
|
||||||
|
$ hrp build plugin/debugtalk.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for build
|
||||||
|
-o, --output string funplugin product output path, default: cwd
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
@@ -22,4 +22,4 @@ hrp convert $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ hrp har2case $har_path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ hrp pytest $path ... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
+1
-1
@@ -35,4 +35,4 @@ hrp run $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ hrp startproject $project_name [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ hrp wiki [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 27-May-2022
|
###### Auto generated by spf13/cobra on 28-May-2022
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ module plugin
|
|||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require github.com/httprunner/funplugin v0.4.6 // indirect
|
require github.com/httprunner/funplugin v0.4.7 // indirect
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v
|
|||||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
github.com/httprunner/funplugin v0.4.6 h1:wwpjzo3G9a5BCXBkHs845w4ifKaCtVa/yQjREQjQOgo=
|
github.com/httprunner/funplugin v0.4.7 h1:bmk84BL8oPGE/rgxCuHgPcwJtBnwDzm/ocmFY/cKcos=
|
||||||
github.com/httprunner/funplugin v0.4.6/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
|
github.com/httprunner/funplugin v0.4.7/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
|
||||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-with-go-plugin",
|
"project_name": "demo-with-go-plugin",
|
||||||
"project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin",
|
"project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin",
|
||||||
"create_time": "2022-05-27T11:34:23.903959+08:00",
|
"create_time": "2022-05-28T02:00:18.084185+08:00",
|
||||||
"hrp_version": "v4.1.0-beta"
|
"hrp_version": "v4.1.0-beta"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-with-py-plugin",
|
"project_name": "demo-with-py-plugin",
|
||||||
"project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin",
|
"project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin",
|
||||||
"create_time": "2022-05-27T11:34:31.852589+08:00",
|
"create_time": "2022-05-28T02:00:28.517914+08:00",
|
||||||
"hrp_version": "v4.1.0-beta"
|
"hrp_version": "v4.1.0-beta"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-without-plugin",
|
"project_name": "demo-without-plugin",
|
||||||
"project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin",
|
"project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin",
|
||||||
"create_time": "2022-05-27T11:34:32.548637+08:00",
|
"create_time": "2022-05-28T02:00:29.191678+08:00",
|
||||||
"hrp_version": "v4.1.0-beta"
|
"hrp_version": "v4.1.0-beta"
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/go-openapi/spec v0.20.6
|
github.com/go-openapi/spec v0.20.6
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/websocket v1.4.1
|
github.com/gorilla/websocket v1.4.1
|
||||||
github.com/httprunner/funplugin v0.4.6
|
github.com/httprunner/funplugin v0.4.7
|
||||||
github.com/jinzhu/copier v0.3.2
|
github.com/jinzhu/copier v0.3.2
|
||||||
github.com/jmespath/go-jmespath v0.4.0
|
github.com/jmespath/go-jmespath v0.4.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
|
|||||||
@@ -253,8 +253,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
|||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
github.com/httprunner/funplugin v0.4.6 h1:wwpjzo3G9a5BCXBkHs845w4ifKaCtVa/yQjREQjQOgo=
|
github.com/httprunner/funplugin v0.4.7 h1:bmk84BL8oPGE/rgxCuHgPcwJtBnwDzm/ocmFY/cKcos=
|
||||||
github.com/httprunner/funplugin v0.4.6/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
|
github.com/httprunner/funplugin v0.4.7/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
var buildCmd = &cobra.Command{
|
||||||
|
Use: "build $path ...",
|
||||||
|
Short: "build plugin for testing",
|
||||||
|
Long: `build python/go plugin for testing`,
|
||||||
|
Example: ` $ hrp build plugin/debugtalk.go
|
||||||
|
$ hrp build plugin/debugtalk.py`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
setLogLevel(logLevel)
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return build.Run(args[0], output)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var output string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(buildCmd)
|
||||||
|
|
||||||
|
buildCmd.Flags().StringVarP(&output, "output", "o", "", "funplugin product output path, default: cwd")
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin/shared"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
funppy = `import funppy`
|
||||||
|
fungo = `"github.com/httprunner/funplugin/fungo"`
|
||||||
|
regexPythonFunctionName = `def ([a-zA-Z_]\w*)\(.*\)`
|
||||||
|
regexGoImports = `import \(([\s\S]*?)\)`
|
||||||
|
regexGoImport = `import (\"[\s\S]*\")`
|
||||||
|
regexGoFunctionName = `func ([A-Z][a-zA-Z_]\w*)\(.*\)`
|
||||||
|
regexGoFunctionContent = `func [\s\S]*?\n}`
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates/debugtalkPythonTemplate
|
||||||
|
var pyTemplate string
|
||||||
|
|
||||||
|
//go:embed templates/debugtalkGoTemplate
|
||||||
|
var goTemplate string
|
||||||
|
|
||||||
|
type TemplateContent struct {
|
||||||
|
Fun string // funplugin package
|
||||||
|
Regexps *Regexps // match import/function
|
||||||
|
Imports []string // python/go import
|
||||||
|
FromImports []string // python from...import...
|
||||||
|
Functions []string // python/go function
|
||||||
|
FunctionNames []string // function name set by user
|
||||||
|
}
|
||||||
|
|
||||||
|
type Regexps struct {
|
||||||
|
Import *regexp.Regexp
|
||||||
|
Imports *regexp.Regexp
|
||||||
|
FunctionName *regexp.Regexp
|
||||||
|
FunctionContent *regexp.Regexp // including function define and body
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateContent) parseGoContent(path string) error {
|
||||||
|
log.Info().Msg(fmt.Sprintf("start to parse %v", path))
|
||||||
|
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to read file")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
originalContent := string(content)
|
||||||
|
|
||||||
|
// parse imports
|
||||||
|
importSlice := t.Regexps.Imports.FindAllStringSubmatch(originalContent, -1)
|
||||||
|
if len(importSlice) != 0 {
|
||||||
|
imports := strings.Replace(importSlice[0][1], "\t", "", -1)
|
||||||
|
for _, elem := range strings.Split(imports, "\n") {
|
||||||
|
t.Imports = append(t.Imports, strings.TrimSpace(elem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// parse import
|
||||||
|
importSlice = t.Regexps.Import.FindAllStringSubmatch(originalContent, -1)
|
||||||
|
if len(importSlice) != 0 {
|
||||||
|
for _, elem := range importSlice {
|
||||||
|
t.Imports = append(t.Imports, strings.TrimSpace(elem[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// import fungo package
|
||||||
|
if !builtin.Contains(t.Imports, fungo) {
|
||||||
|
t.Imports = append(t.Imports, t.Fun)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse function name
|
||||||
|
functionNameSlice := t.Regexps.FunctionName.FindAllStringSubmatch(originalContent, -1)
|
||||||
|
for _, elem := range functionNameSlice {
|
||||||
|
name := strings.Trim(elem[1], " ")
|
||||||
|
if name == "main" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.FunctionNames = append(t.FunctionNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse function content
|
||||||
|
functionContentSlice := t.Regexps.FunctionContent.FindAllStringSubmatch(originalContent, -1)
|
||||||
|
for _, f := range functionContentSlice {
|
||||||
|
if strings.Contains(f[0], "func main") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Functions = append(t.Functions, strings.Trim(f[0], "\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateContent) parsePyContent(path string) error {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %s\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
r := bufio.NewReader(file)
|
||||||
|
|
||||||
|
// record content excluding import and main
|
||||||
|
content := ""
|
||||||
|
|
||||||
|
// parse python content line by line
|
||||||
|
for {
|
||||||
|
l, _, err := r.ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
line := string(l)
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "import") {
|
||||||
|
t.Imports = append(t.Imports, strings.Trim(line, " "))
|
||||||
|
} else if strings.HasPrefix(line, "from") {
|
||||||
|
t.FromImports = append(t.FromImports, strings.Trim(line, " "))
|
||||||
|
} else {
|
||||||
|
// no parse content at under of `if __name__ == "__main__"`
|
||||||
|
if strings.HasPrefix(line, "if __name__") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "def") {
|
||||||
|
functionNameSlice := t.Regexps.FunctionName.FindAllStringSubmatch(line, -1)
|
||||||
|
if len(functionNameSlice) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.FunctionNames = append(t.FunctionNames, functionNameSlice[0][1])
|
||||||
|
}
|
||||||
|
content += line + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// function content
|
||||||
|
t.Functions = append(t.Functions, strings.Trim(content, "\n"))
|
||||||
|
|
||||||
|
// import funppy
|
||||||
|
if !builtin.Contains(t.Imports, t.Fun) {
|
||||||
|
t.Imports = append(t.Imports, t.Fun)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateContent) genDebugTalk(path string, templ string) error {
|
||||||
|
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("open file failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
writer := bufio.NewWriter(file)
|
||||||
|
tmpl := template.Must(template.New("debugtalk").Parse(templ))
|
||||||
|
err = tmpl.Execute(writer, t)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("execute applies a parsed template to the specified data object failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writer.Flush()
|
||||||
|
if err == nil {
|
||||||
|
log.Info().Str("path", path).Msg("generate debugtalk success")
|
||||||
|
} else {
|
||||||
|
log.Error().Str("path", path).Msg("generate debugtalk failed")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildGo builds debugtalk.go to debugtalk.bin
|
||||||
|
func buildGo(path string, output string) error {
|
||||||
|
templateContent := &TemplateContent{
|
||||||
|
Fun: fungo,
|
||||||
|
Regexps: &Regexps{
|
||||||
|
Import: regexp.MustCompile(regexGoImport),
|
||||||
|
Imports: regexp.MustCompile(regexGoImports),
|
||||||
|
FunctionName: regexp.MustCompile(regexGoFunctionName),
|
||||||
|
FunctionContent: regexp.MustCompile(regexGoFunctionContent),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// create temp dir for building
|
||||||
|
tempDir, err := ioutil.TempDir("", "hrp_build")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check go sdk in tempDir
|
||||||
|
if err := builtin.ExecCommandInDir(exec.Command("go", "version"), tempDir); err != nil {
|
||||||
|
return errors.Wrap(err, "go sdk not installed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create pluginDir
|
||||||
|
pluginDir := filepath.Join(tempDir, "plugin")
|
||||||
|
if err := builtin.CreateFolder(pluginDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// parse debugtalk.go in pluginDir
|
||||||
|
err = templateContent.parseGoContent(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// generate debugtalk.go in pluginDir
|
||||||
|
err = templateContent.genDebugTalk(filepath.Join(pluginDir, "debugtalk.go"), goTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create go mod
|
||||||
|
if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// download plugin dependency
|
||||||
|
// funplugin version should be locked
|
||||||
|
funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version)
|
||||||
|
if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if output == "" {
|
||||||
|
dir, _ := os.Getwd()
|
||||||
|
output = filepath.Join(dir, "debugtalk.bin")
|
||||||
|
} else if builtin.IsFolderPathExists(output) {
|
||||||
|
output = filepath.Join(output, "debugtalk.bin")
|
||||||
|
}
|
||||||
|
outputPath, err := filepath.Abs(output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// build plugin debugtalk.bin
|
||||||
|
if err := builtin.ExecCommandInDir(exec.Command("go", "build", "-o", outputPath, "debugtalk.go"), pluginDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info().Msg(fmt.Sprintf("build %s to %s successfully", path, outputPath))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPy completes funppy information in debugtalk.py
|
||||||
|
func buildPy(path string, output string) error {
|
||||||
|
templateContent := &TemplateContent{
|
||||||
|
Fun: funppy,
|
||||||
|
Regexps: &Regexps{
|
||||||
|
FunctionName: regexp.MustCompile(regexPythonFunctionName),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := templateContent.parsePyContent(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate debugtalk.py
|
||||||
|
if output == "" {
|
||||||
|
dir, _ := os.Getwd()
|
||||||
|
output = filepath.Join(dir, "debugtalk_gen.py")
|
||||||
|
} else if builtin.IsFolderPathExists(output) {
|
||||||
|
output = filepath.Join(output, "debugtalk_gen.py")
|
||||||
|
}
|
||||||
|
err = templateContent.genDebugTalk(output, pyTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure funppy in .env
|
||||||
|
_, err = builtin.EnsurePython3Venv("funppy")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(arg string, output string) (err error) {
|
||||||
|
ext := filepath.Ext(arg)
|
||||||
|
switch ext {
|
||||||
|
case ".py":
|
||||||
|
err = buildPy(arg, output)
|
||||||
|
case ".go":
|
||||||
|
err = buildGo(arg, output)
|
||||||
|
default:
|
||||||
|
return errors.New("type error, expected .py or .go")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg(fmt.Sprintf("failed to build %s", arg))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
err := Run("plugin/debugtalk.go", "./debugtalk_gen.bin")
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Run("plugin/debugtalk.py", "./debugtalk_gen.py")
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBytes, err := builtin.ReadFile("./debugtalk_gen.py")
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(contentBytes)
|
||||||
|
if !assert.Contains(t, content, "import funppy") {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Contains(t, content, "funppy.register") {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, _ := regexp.Compile(`funppy\.register`)
|
||||||
|
matchedSlice := reg.FindAllStringSubmatch(content, -1)
|
||||||
|
if !assert.Len(t, matchedSlice, 10) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package noplugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SumTwoInt(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
func SumInts(args ...int) int {
|
||||||
|
var sum int
|
||||||
|
for _, arg := range args {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sum(args ...interface{}) (interface{}, error) {
|
||||||
|
var sum float64
|
||||||
|
for _, arg := range args {
|
||||||
|
switch v := arg.(type) {
|
||||||
|
case int:
|
||||||
|
sum += float64(v)
|
||||||
|
case float64:
|
||||||
|
sum += v
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupHookExample(args string) string {
|
||||||
|
return fmt.Sprintf("step name: %v, setup...", args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeardownHookExample(args string) string {
|
||||||
|
return fmt.Sprintf("step name: %v, teardown...", args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVersion() string {
|
||||||
|
return "v0.4"
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
return "v0.4"
|
||||||
|
|
||||||
|
|
||||||
|
def sleep(n_secs):
|
||||||
|
time.sleep(n_secs)
|
||||||
|
|
||||||
|
|
||||||
|
def sum(*args):
|
||||||
|
result = 0
|
||||||
|
for arg in args:
|
||||||
|
result += arg
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def sum_ints(*args: List[int]) -> int:
|
||||||
|
result = 0
|
||||||
|
for arg in args:
|
||||||
|
result += arg
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def sum_two_int(a: int, b: int) -> int:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
def sum_two_string(a: str, b: str) -> str:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
def sum_strings(*args: List[str]) -> str:
|
||||||
|
result = ""
|
||||||
|
for arg in args:
|
||||||
|
result += arg
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def concatenate(*args: List[str]) -> str:
|
||||||
|
result = ""
|
||||||
|
for arg in args:
|
||||||
|
result += str(arg)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def setup_hook_example(name):
|
||||||
|
logging.warning("setup_hook_example")
|
||||||
|
return f"setup_hook_example: {name}"
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_hook_example(name):
|
||||||
|
logging.warning("teardown_hook_example")
|
||||||
|
return f"teardown_hook_example: {name}"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
{{- range $import := .Imports }}
|
||||||
|
{{ $import -}}
|
||||||
|
{{ end }}
|
||||||
|
)
|
||||||
|
|
||||||
|
{{ range $function := .Functions }}
|
||||||
|
{{ $function }}
|
||||||
|
{{ end }}
|
||||||
|
func main() {
|
||||||
|
{{- range $idx, $functionName := .FunctionNames }}
|
||||||
|
fungo.Register("{{ $functionName }}", {{ $functionName }})
|
||||||
|
{{- end }}
|
||||||
|
fungo.Serve()
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{{- range $import := .Imports }}
|
||||||
|
{{- $import}}
|
||||||
|
{{ end }}
|
||||||
|
{{ range $fromImport := .FromImports }}
|
||||||
|
{{- $fromImport}}
|
||||||
|
{{ end }}
|
||||||
|
{{ range $function := .Functions }}
|
||||||
|
{{ $function }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
if __name__ == "__main__":
|
||||||
|
{{- range $functionName := .FunctionNames }}
|
||||||
|
funppy.register("{{ $functionName }}", {{ $functionName }})
|
||||||
|
{{- end }}
|
||||||
|
funppy.serve()
|
||||||
@@ -281,7 +281,7 @@ var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension")
|
|||||||
// LoadFile loads file content with file extension and assigns to structObj
|
// LoadFile loads file content with file extension and assigns to structObj
|
||||||
func LoadFile(path string, structObj interface{}) (err error) {
|
func LoadFile(path string, structObj interface{}) (err error) {
|
||||||
log.Info().Str("path", path).Msg("load file")
|
log.Info().Str("path", path).Msg("load file")
|
||||||
file, err := readFile(path)
|
file, err := ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "read file failed")
|
return errors.Wrap(err, "read file failed")
|
||||||
}
|
}
|
||||||
@@ -335,7 +335,7 @@ func parseEnvContent(file []byte, obj interface{}) error {
|
|||||||
|
|
||||||
func loadFromCSV(path string) []map[string]interface{} {
|
func loadFromCSV(path string) []map[string]interface{} {
|
||||||
log.Info().Str("path", path).Msg("load csv file")
|
log.Info().Str("path", path).Msg("load csv file")
|
||||||
file, err := readFile(path)
|
file, err := ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("read csv file failed")
|
log.Error().Err(err).Msg("read csv file failed")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -361,7 +361,7 @@ func loadFromCSV(path string) []map[string]interface{} {
|
|||||||
|
|
||||||
func loadMessage(path string) []byte {
|
func loadMessage(path string) []byte {
|
||||||
log.Info().Str("path", path).Msg("load message file")
|
log.Info().Str("path", path).Msg("load message file")
|
||||||
file, err := readFile(path)
|
file, err := ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("read message file failed")
|
log.Error().Err(err).Msg("read message file failed")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -369,7 +369,7 @@ func loadMessage(path string) []byte {
|
|||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFile(path string) ([]byte, error) {
|
func ReadFile(path string) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
path, err = filepath.Abs(path)
|
path, err = filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+7
-1
@@ -252,9 +252,15 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}
|
|||||||
// 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) {
|
||||||
// call with plugin function
|
// call with plugin function
|
||||||
if p.plugin != nil && p.plugin.Has(funcName) {
|
if p.plugin != nil {
|
||||||
|
if p.plugin.Has(funcName) {
|
||||||
return p.plugin.Call(funcName, arguments...)
|
return p.plugin.Call(funcName, arguments...)
|
||||||
}
|
}
|
||||||
|
commonName := shared.ConvertCommonName(funcName)
|
||||||
|
if p.plugin.Has(commonName) {
|
||||||
|
return p.plugin.Call(commonName, arguments...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get builtin function
|
// get builtin function
|
||||||
function, ok := builtin.Functions[funcName]
|
function, ok := builtin.Functions[funcName]
|
||||||
|
|||||||
+25
-2
@@ -5,18 +5,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
"github.com/httprunner/funplugin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/build"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goPluginFile = "debugtalk.so" // built from go plugin
|
goPluginFile = "debugtalk.so" // built from go plugin
|
||||||
hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin
|
hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin
|
||||||
hashicorpPyPluginFile = "debugtalk.py" // used for hashicorp python plugin
|
hashicorpPyPluginFile = "debugtalk_gen.py" // used for hashicorp python plugin, automatically generated by HRP
|
||||||
|
debugtalkPyFile = "debugtalk.py" // write by user
|
||||||
projectInfoFile = "proj.json" // used for ensuring root project
|
projectInfoFile = "proj.json" // used for ensuring root project
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,6 +35,21 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st
|
|||||||
// TODO: move pluginDir to funplugin
|
// TODO: move pluginDir to funplugin
|
||||||
pluginDir = filepath.Dir(pluginPath)
|
pluginDir = filepath.Dir(pluginPath)
|
||||||
|
|
||||||
|
// compatible the format of debugtalk.py with v2/v3
|
||||||
|
ext := filepath.Ext(pluginPath)
|
||||||
|
if ext == ".py" {
|
||||||
|
// skip if only debugtalk_gen.py exists
|
||||||
|
if !strings.HasSuffix(pluginPath, "debugtalk_gen.py") {
|
||||||
|
genPyPluginPath := filepath.Join(pluginDir, "debugtalk_gen.py")
|
||||||
|
err = build.Run(pluginPath, genPyPluginPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf(fmt.Sprintf("failed to build %s", pluginPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pluginPath = genPyPluginPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// found plugin file
|
// found plugin file
|
||||||
plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn))
|
plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,13 +80,18 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func locatePlugin(path string) (pluginPath string, err error) {
|
func locatePlugin(path string) (pluginPath string, err error) {
|
||||||
// priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so)
|
// priority: hashicorp plugin (debugtalk.bin > debugtalk.py > debugtalk_gen.py) > go plugin (debugtalk.so)
|
||||||
|
|
||||||
pluginPath, err = locateFile(path, hashicorpGoPluginFile)
|
pluginPath, err = locateFile(path, hashicorpGoPluginFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginPath, err = locateFile(path, debugtalkPyFile)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pluginPath, err = locateFile(path, hashicorpPyPluginFile)
|
pluginPath, err = locateFile(path, hashicorpPyPluginFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
+4
-4
@@ -8,14 +8,13 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/build"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/scaffold"
|
"github.com/httprunner/httprunner/v4/hrp/internal/scaffold"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildHashicorpGoPlugin() {
|
func buildHashicorpGoPlugin() {
|
||||||
log.Info().Msg("[init] build hashicorp go plugin")
|
log.Info().Msg("[init] build hashicorp go plugin")
|
||||||
err := builtin.ExecCommand("go", "build",
|
err := build.Run(templatesDir+"plugin/debugtalk.go", templatesDir+"debugtalk.bin")
|
||||||
"-o", templatesDir+"debugtalk.bin", templatesDir+"plugin/debugtalk.go")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("build hashicorp go plugin failed")
|
log.Error().Err(err).Msg("build hashicorp go plugin failed")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -39,7 +38,8 @@ func buildHashicorpPyPlugin() {
|
|||||||
|
|
||||||
func removeHashicorpPyPlugin() {
|
func removeHashicorpPyPlugin() {
|
||||||
log.Info().Msg("[teardown] remove hashicorp python plugin")
|
log.Info().Msg("[teardown] remove hashicorp python plugin")
|
||||||
os.Remove(templatesDir + "debugtalk.py")
|
// on v4.1^, running case will generate debugtalk_gen.py used by python plugin
|
||||||
|
os.Remove(templatesDir + "debugtalk_gen.py")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunCaseWithGoPlugin(t *testing.T) {
|
func TestRunCaseWithGoPlugin(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user