mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-28 03:30:01 +08:00
Merge pull request #131 from httprunner/refactor-plugin
refactor hashicorp plugin
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
# install hrp with one shell command
|
# install hrp with one shell command
|
||||||
# bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)"
|
# bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)"
|
||||||
|
|
||||||
LATEST_VERSION="v0.6.3"
|
LATEST_VERSION="v0.7.0-beta"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,6 @@ package hrp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -10,6 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadFromJSON(path string) (*TCase, error) {
|
func loadFromJSON(path string) (*TCase, error) {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
|
## v0.7.0 (2022-03-04)
|
||||||
|
|
||||||
|
- feat: both support gPRC(default) and net/rpc mode in hashicorp plugin, switch with environment `HRP_PLUGIN_TYPE`
|
||||||
|
- refactor: replace builtin json library with json-iterator/go to improve performance
|
||||||
|
|
||||||
## v0.6.3 (2022-03-04)
|
## v0.6.3 (2022-03-04)
|
||||||
|
|
||||||
- feat: support customized setup/teardown hooks (variable assignment not supported)
|
- feat: support customized setup/teardown hooks (variable assignment not supported)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/httprunner/hrp/plugin v0.0.0
|
github.com/httprunner/hrp/plugin v0.0.0
|
||||||
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/maja42/goval v1.2.1
|
github.com/maja42/goval v1.2.1
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
|
|||||||
@@ -209,6 +209,8 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
|
|||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
@@ -252,9 +254,12 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
|
|||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package boomer
|
package boomer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/push"
|
"github.com/prometheus/client_golang/prometheus/push"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Output is primarily responsible for printing test results to different destinations
|
// Output is primarily responsible for printing test results to different destinations
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package boomer
|
package boomer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type transaction struct {
|
type transaction struct {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@@ -15,9 +14,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/httprunner/hrp/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Functions = map[string]interface{}{
|
var Functions = map[string]interface{}{
|
||||||
@@ -96,7 +96,7 @@ func Dump2JSON(data interface{}, path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info().Str("path", path).Msg("dump data to json")
|
log.Info().Str("path", path).Msg("dump data to json")
|
||||||
file, _ := json.MarshalIndent(data, "", "\t")
|
file, _ := json.MarshalIndent(data, "", " ")
|
||||||
err = os.WriteFile(path, file, 0644)
|
err = os.WriteFile(path, file, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("dump json path failed")
|
log.Error().Err(err).Msg("dump json path failed")
|
||||||
@@ -137,7 +137,7 @@ func FormatResponse(raw interface{}) interface{} {
|
|||||||
for key, value := range raw.(map[string]interface{}) {
|
for key, value := range raw.(map[string]interface{}) {
|
||||||
// convert value to json
|
// convert value to json
|
||||||
if key == "body" {
|
if key == "body" {
|
||||||
b, _ := json.MarshalIndent(&value, "", "\t")
|
b, _ := json.MarshalIndent(&value, "", " ")
|
||||||
value = string(b)
|
value = string(b)
|
||||||
}
|
}
|
||||||
formattedResponse[key] = value
|
formattedResponse[key] = value
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package har2case
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -17,6 +16,7 @@ import (
|
|||||||
"github.com/httprunner/hrp"
|
"github.com/httprunner/hrp"
|
||||||
"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/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// replace with third-party json library to improve performance
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
|
||||||
|
var (
|
||||||
|
Marshal = json.Marshal
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
)
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
package version
|
package version
|
||||||
|
|
||||||
const VERSION = "v0.6.3"
|
const VERSION = "v0.7.0-beta"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
builtinJSON "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -68,7 +68,7 @@ func (p *parser) parseData(raw interface{}, variablesMapping map[string]interfac
|
|||||||
switch rawValue.Kind() {
|
switch rawValue.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
// json.Number
|
// json.Number
|
||||||
if rawValue, ok := raw.(json.Number); ok {
|
if rawValue, ok := raw.(builtinJSON.Number); ok {
|
||||||
return parseJSONNumber(rawValue)
|
return parseJSONNumber(rawValue)
|
||||||
}
|
}
|
||||||
// other string
|
// other string
|
||||||
@@ -108,7 +108,7 @@ func (p *parser) parseData(raw interface{}, variablesMapping map[string]interfac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseJSONNumber(raw json.Number) (interface{}, error) {
|
func parseJSONNumber(raw builtinJSON.Number) (interface{}, error) {
|
||||||
if strings.Contains(raw.String(), ".") {
|
if strings.Contains(raw.String(), ".") {
|
||||||
// float64
|
// float64
|
||||||
return raw.Float64()
|
return raw.Float64()
|
||||||
|
|||||||
@@ -99,6 +99,18 @@ Then, you can call your defined plugin function in your `YAML/JSON` testcase at
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### rpc vs. gRPC
|
||||||
|
|
||||||
|
HttpRunner+ has both supported `net/rpc` and `gRPC` in [hashicorp/plugin]. It is recommended to use `gRPC` and this is the default choice.
|
||||||
|
|
||||||
|
If you want to run plugin in `net/rpc` mode, you can set an environment variable `HRP_PLUGIN_TYPE=rpc`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ export HRP_PLUGIN_TYPE=rpc
|
||||||
|
$ hrp run examples/demo.json
|
||||||
|
$ hrp boom examples/demo.json
|
||||||
|
```
|
||||||
|
|
||||||
## go plugin
|
## go plugin
|
||||||
|
|
||||||
The golang official plugin is only supported on Linux, FreeBSD, and macOS. And this solution also has many drawbacks.
|
The golang official plugin is only supported on Linux, FreeBSD, and macOS. And this solution also has many drawbacks.
|
||||||
|
|||||||
+23
-1
@@ -1,8 +1,15 @@
|
|||||||
package pluginInternal
|
package pluginInternal
|
||||||
|
|
||||||
import "github.com/hashicorp/go-plugin"
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-plugin"
|
||||||
|
)
|
||||||
|
|
||||||
const PluginName = "debugtalk"
|
const PluginName = "debugtalk"
|
||||||
|
const RPCPluginName = PluginName + "_rpc"
|
||||||
|
const GRPCPluginName = PluginName + "_grpc"
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -13,3 +20,18 @@ var HandshakeConfig = plugin.HandshakeConfig{
|
|||||||
MagicCookieKey: "HttpRunnerPlus",
|
MagicCookieKey: "HttpRunnerPlus",
|
||||||
MagicCookieValue: PluginName,
|
MagicCookieValue: PluginName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hrpPluginTypeEnvName = "HRP_PLUGIN_TYPE"
|
||||||
|
|
||||||
|
var hrpPluginType string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
hrpPluginType = strings.ToLower(os.Getenv(hrpPluginTypeEnvName))
|
||||||
|
if hrpPluginType == "" {
|
||||||
|
hrpPluginType = "grpc" // default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRPCPluginType() bool {
|
||||||
|
return hrpPluginType == "rpc"
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package pluginInternal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-plugin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/json"
|
||||||
|
"github.com/httprunner/hrp/plugin/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// functionGRPCClient runs on the host side, it implements FuncCaller interface
|
||||||
|
type functionGRPCClient struct {
|
||||||
|
client proto.DebugTalkClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *functionGRPCClient) GetNames() ([]string, error) {
|
||||||
|
log.Info().Msg("function GetNames called on host side")
|
||||||
|
resp, err := m.client.GetNames(context.Background(), &proto.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("gRPC call GetNames() failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.Names, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *functionGRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
||||||
|
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via gRPC")
|
||||||
|
|
||||||
|
funcArgBytes, err := json.Marshal(funcArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to marshal Call() funcArgs")
|
||||||
|
}
|
||||||
|
req := &proto.CallRequest{
|
||||||
|
Name: funcName,
|
||||||
|
Args: funcArgBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := m.client.Call(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).
|
||||||
|
Str("funcName", funcName).Interface("funcArgs", funcArgs).
|
||||||
|
Msg("gRPC Call() failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp interface{}
|
||||||
|
err = json.Unmarshal(response.Value, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal Call() response")
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here is the gRPC server that functionGRPCClient talks to.
|
||||||
|
type functionGRPCServer struct {
|
||||||
|
proto.UnimplementedDebugTalkServer
|
||||||
|
Impl FuncCaller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *functionGRPCServer) GetNames(ctx context.Context, req *proto.Empty) (*proto.GetNamesResponse, error) {
|
||||||
|
log.Info().Interface("req", req).Msg("gRPC GetNames() called on plugin side")
|
||||||
|
v, err := m.Impl.GetNames()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("gRPC GetNames() execution failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &proto.GetNamesResponse{Names: v}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *functionGRPCServer) Call(ctx context.Context, req *proto.CallRequest) (*proto.CallResponse, error) {
|
||||||
|
var funcArgs []interface{}
|
||||||
|
if err := json.Unmarshal(req.Args, &funcArgs); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal Call() funcArgs")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Interface("req", req).Msg("gRPC Call() called on plugin side")
|
||||||
|
|
||||||
|
v, err := m.Impl.Call(req.Name, funcArgs...)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Interface("req", req).Msg("gRPC Call() execution failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to marshal Call() response")
|
||||||
|
}
|
||||||
|
return &proto.CallResponse{Value: value}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HRPPlugin implements hashicorp's plugin.GRPCPlugin.
|
||||||
|
type GRPCPlugin struct {
|
||||||
|
plugin.Plugin
|
||||||
|
Impl FuncCaller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
|
||||||
|
proto.RegisterDebugTalkServer(s, &functionGRPCServer{Impl: p.Impl})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
|
||||||
|
return &functionGRPCClient{client: proto.NewDebugTalkClient(c)}, nil
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package pluginInternal
|
package pluginInternal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,8 +21,16 @@ type HashicorpPlugin struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *HashicorpPlugin) Init(path string) error {
|
func (p *HashicorpPlugin) Init(path string) error {
|
||||||
|
var pluginName string
|
||||||
|
if IsRPCPluginType() {
|
||||||
|
pluginName = RPCPluginName
|
||||||
|
} else {
|
||||||
|
pluginName = GRPCPluginName
|
||||||
|
}
|
||||||
|
|
||||||
|
// logger
|
||||||
loggerOptions := &hclog.LoggerOptions{
|
loggerOptions := &hclog.LoggerOptions{
|
||||||
Name: PluginName,
|
Name: pluginName,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
if p.logOn {
|
if p.logOn {
|
||||||
@@ -28,28 +38,36 @@ func (p *HashicorpPlugin) Init(path string) error {
|
|||||||
} else {
|
} else {
|
||||||
loggerOptions.Level = hclog.Info
|
loggerOptions.Level = hclog.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cmd
|
||||||
|
cmd := exec.Command(path)
|
||||||
|
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", hrpPluginTypeEnvName, hrpPluginType))
|
||||||
|
|
||||||
// launch the plugin process
|
// launch the plugin process
|
||||||
client = plugin.NewClient(&plugin.ClientConfig{
|
client = plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: HandshakeConfig,
|
HandshakeConfig: HandshakeConfig,
|
||||||
Plugins: map[string]plugin.Plugin{
|
Plugins: map[string]plugin.Plugin{
|
||||||
PluginName: &HRPPlugin{},
|
RPCPluginName: &RPCPlugin{},
|
||||||
|
GRPCPluginName: &GRPCPlugin{},
|
||||||
},
|
},
|
||||||
Cmd: exec.Command(path),
|
Cmd: cmd,
|
||||||
Logger: hclog.New(loggerOptions),
|
Logger: hclog.New(loggerOptions),
|
||||||
|
AllowedProtocols: []plugin.Protocol{
|
||||||
|
plugin.ProtocolNetRPC,
|
||||||
|
plugin.ProtocolGRPC,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Connect via RPC
|
// Connect via RPC/gRPC
|
||||||
rpcClient, err := client.Client()
|
rpcClient, err := client.Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("connect plugin via RPC failed")
|
return errors.Wrap(err, fmt.Sprintf("connect %s plugin failed", hrpPluginType))
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the plugin
|
// Request the plugin
|
||||||
raw, err := rpcClient.Dispense(PluginName)
|
raw, err := rpcClient.Dispense(pluginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("request plugin failed")
|
return errors.Wrap(err, fmt.Sprintf("request %s plugin failed", hrpPluginType))
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should have a Function now! This feels like a normal interface
|
// We should have a Function now! This feels like a normal interface
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package pluginInternal
|
||||||
|
|
||||||
|
// FuncCaller is the interface that we're exposing as a plugin.
|
||||||
|
type FuncCaller interface {
|
||||||
|
GetNames() ([]string, error) // get all plugin function names list
|
||||||
|
Call(funcName string, args ...interface{}) (interface{}, error) // call plugin function
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -18,18 +18,12 @@ type funcData struct {
|
|||||||
Args []interface{} // function arguments
|
Args []interface{} // function arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
// FuncCaller is the interface that we're exposing as a plugin.
|
// functionRPCClient runs on the host side, it implements FuncCaller interface
|
||||||
type FuncCaller interface {
|
type functionRPCClient struct {
|
||||||
GetNames() ([]string, error) // get all plugin function names list
|
|
||||||
Call(funcName string, args ...interface{}) (interface{}, error) // call plugin function
|
|
||||||
}
|
|
||||||
|
|
||||||
// functionRPC runs on the host side.
|
|
||||||
type functionRPC struct {
|
|
||||||
client *rpc.Client
|
client *rpc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *functionRPC) GetNames() ([]string, error) {
|
func (g *functionRPCClient) GetNames() ([]string, error) {
|
||||||
var resp []string
|
var resp []string
|
||||||
err := g.client.Call("Plugin.GetNames", new(interface{}), &resp)
|
err := g.client.Call("Plugin.GetNames", new(interface{}), &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,7 +34,7 @@ func (g *functionRPC) GetNames() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// host -> plugin
|
// host -> plugin
|
||||||
func (g *functionRPC) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
func (g *functionRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
||||||
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via RPC")
|
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via RPC")
|
||||||
f := funcData{
|
f := funcData{
|
||||||
Name: funcName,
|
Name: funcName,
|
||||||
@@ -53,7 +47,7 @@ func (g *functionRPC) Call(funcName string, funcArgs ...interface{}) (interface{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).
|
log.Error().Err(err).
|
||||||
Str("funcName", funcName).Interface("funcArgs", funcArgs).
|
Str("funcName", funcName).Interface("funcArgs", funcArgs).
|
||||||
Msg("rpc call Call() failed")
|
Msg("rpc Call() failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
@@ -66,11 +60,11 @@ type functionRPCServer struct {
|
|||||||
|
|
||||||
// plugin execution
|
// plugin execution
|
||||||
func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error {
|
func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error {
|
||||||
log.Info().Interface("args", args).Msg("GetNames called on plugin side")
|
log.Info().Interface("args", args).Msg("rpc GetNames() called on plugin side")
|
||||||
var err error
|
var err error
|
||||||
*resp, err = s.Impl.GetNames()
|
*resp, err = s.Impl.GetNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("GetNames execution failed")
|
log.Error().Err(err).Msg("rpc GetNames() execution failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -78,33 +72,26 @@ func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error {
|
|||||||
|
|
||||||
// plugin execution
|
// plugin execution
|
||||||
func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error {
|
func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error {
|
||||||
log.Info().Interface("args", args).Msg("function called on plugin side")
|
log.Info().Interface("args", args).Msg("rpc Call() called on plugin side")
|
||||||
f := args.(*funcData)
|
f := args.(*funcData)
|
||||||
var err error
|
var err error
|
||||||
*resp, err = s.Impl.Call(f.Name, f.Args...)
|
*resp, err = s.Impl.Call(f.Name, f.Args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Interface("args", args).Msg("function execution failed")
|
log.Error().Err(err).Interface("args", args).Msg("rpc Call() execution failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HRPPlugin implements hashicorp's plugin.Plugin.
|
// RPCPlugin implements hashicorp's plugin.Plugin.
|
||||||
type HRPPlugin struct {
|
type RPCPlugin struct {
|
||||||
Impl FuncCaller
|
Impl FuncCaller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HRPPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
func (p *RPCPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||||
return &functionRPCServer{Impl: p.Impl}, nil
|
return &functionRPCServer{Impl: p.Impl}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (HRPPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
func (RPCPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||||
return &functionRPC{client: c}, nil
|
return &functionRPCClient{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
|
|
||||||
}
|
}
|
||||||
+39
-4
@@ -7,6 +7,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/rs/zerolog/log"
|
||||||
|
|
||||||
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
||||||
)
|
)
|
||||||
@@ -50,21 +51,55 @@ func Register(funcName string, fn interface{}) {
|
|||||||
functions[funcName] = reflect.ValueOf(fn)
|
functions[funcName] = reflect.ValueOf(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve starts a plugin server process.
|
// serveRPC starts a plugin server process in RPC mode.
|
||||||
func Serve() {
|
func serveRPC() {
|
||||||
|
log.Info().Msg("start plugin server in RPC mode")
|
||||||
funcPlugin := &functionPlugin{
|
funcPlugin := &functionPlugin{
|
||||||
logger: hclog.New(&hclog.LoggerOptions{
|
logger: hclog.New(&hclog.LoggerOptions{
|
||||||
Name: pluginInternal.PluginName,
|
Name: pluginInternal.RPCPluginName,
|
||||||
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{
|
||||||
pluginInternal.PluginName: &pluginInternal.HRPPlugin{Impl: funcPlugin},
|
pluginInternal.RPCPluginName: &pluginInternal.RPCPlugin{Impl: funcPlugin},
|
||||||
}
|
}
|
||||||
|
// start RPC server
|
||||||
plugin.Serve(&plugin.ServeConfig{
|
plugin.Serve(&plugin.ServeConfig{
|
||||||
HandshakeConfig: pluginInternal.HandshakeConfig,
|
HandshakeConfig: pluginInternal.HandshakeConfig,
|
||||||
Plugins: pluginMap,
|
Plugins: pluginMap,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serveGRPC starts a plugin server process in gRPC mode.
|
||||||
|
func serveGRPC() {
|
||||||
|
log.Info().Msg("start plugin server in gRPC mode")
|
||||||
|
funcPlugin := &functionPlugin{
|
||||||
|
logger: hclog.New(&hclog.LoggerOptions{
|
||||||
|
Name: pluginInternal.GRPCPluginName,
|
||||||
|
Output: os.Stdout,
|
||||||
|
Level: hclog.Info,
|
||||||
|
}),
|
||||||
|
functions: functions,
|
||||||
|
}
|
||||||
|
var pluginMap = map[string]plugin.Plugin{
|
||||||
|
pluginInternal.GRPCPluginName: &pluginInternal.GRPCPlugin{Impl: funcPlugin},
|
||||||
|
}
|
||||||
|
// start gRPC server
|
||||||
|
plugin.Serve(&plugin.ServeConfig{
|
||||||
|
HandshakeConfig: pluginInternal.HandshakeConfig,
|
||||||
|
Plugins: pluginMap,
|
||||||
|
GRPCServer: plugin.DefaultGRPCServer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to run plugin in gRPC mode
|
||||||
|
func Serve() {
|
||||||
|
if pluginInternal.IsRPCPluginType() {
|
||||||
|
serveRPC()
|
||||||
|
} else {
|
||||||
|
// default
|
||||||
|
serveGRPC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
## Updating the Protocol
|
||||||
|
|
||||||
|
If you update the protocol buffers file, you can regenerate the file using the following command from the project root directory. You do not need to run this if you're just using the plugin.
|
||||||
|
|
||||||
|
For Go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative plugin/proto/debugtalk.proto
|
||||||
|
```
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.26.0
|
||||||
|
// protoc v3.19.4
|
||||||
|
// source: plugin/proto/debugtalk.proto
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Empty struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) Reset() {
|
||||||
|
*x = Empty{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Empty) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Empty) Descriptor() ([]byte, []int) {
|
||||||
|
return file_plugin_proto_debugtalk_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNamesResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamesResponse) Reset() {
|
||||||
|
*x = GetNamesResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetNamesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetNamesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetNamesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetNamesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_plugin_proto_debugtalk_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamesResponse) GetNames() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Names
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Args []byte `protobuf:"bytes,2,opt,name=args,proto3" json:"args,omitempty"` // []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallRequest) Reset() {
|
||||||
|
*x = CallRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CallRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CallRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CallRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CallRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_plugin_proto_debugtalk_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallRequest) GetArgs() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Args
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` // interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallResponse) Reset() {
|
||||||
|
*x = CallResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CallResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CallResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_plugin_proto_debugtalk_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CallResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CallResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_plugin_proto_debugtalk_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallResponse) GetValue() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_plugin_proto_debugtalk_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_plugin_proto_debugtalk_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1c, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64,
|
||||||
|
0x65, 0x62, 0x75, 0x67, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05,
|
||||||
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x28,
|
||||||
|
0x0a, 0x10, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||||
|
0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x35, 0x0a, 0x0b, 0x43, 0x61, 0x6c, 0x6c,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61,
|
||||||
|
0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x22,
|
||||||
|
0x24, 0x0a, 0x0c, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
|
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
|
||||||
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x6f, 0x0a, 0x09, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x61,
|
||||||
|
0x6c, 0x6b, 0x12, 0x31, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x0c,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x12, 0x2e,
|
||||||
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||||
|
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2f,
|
||||||
|
0x68, 0x72, 0x70, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_plugin_proto_debugtalk_proto_rawDescOnce sync.Once
|
||||||
|
file_plugin_proto_debugtalk_proto_rawDescData = file_plugin_proto_debugtalk_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_plugin_proto_debugtalk_proto_rawDescGZIP() []byte {
|
||||||
|
file_plugin_proto_debugtalk_proto_rawDescOnce.Do(func() {
|
||||||
|
file_plugin_proto_debugtalk_proto_rawDescData = protoimpl.X.CompressGZIP(file_plugin_proto_debugtalk_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_plugin_proto_debugtalk_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_plugin_proto_debugtalk_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||||
|
var file_plugin_proto_debugtalk_proto_goTypes = []interface{}{
|
||||||
|
(*Empty)(nil), // 0: proto.Empty
|
||||||
|
(*GetNamesResponse)(nil), // 1: proto.GetNamesResponse
|
||||||
|
(*CallRequest)(nil), // 2: proto.CallRequest
|
||||||
|
(*CallResponse)(nil), // 3: proto.CallResponse
|
||||||
|
}
|
||||||
|
var file_plugin_proto_debugtalk_proto_depIdxs = []int32{
|
||||||
|
0, // 0: proto.DebugTalk.GetNames:input_type -> proto.Empty
|
||||||
|
2, // 1: proto.DebugTalk.Call:input_type -> proto.CallRequest
|
||||||
|
1, // 2: proto.DebugTalk.GetNames:output_type -> proto.GetNamesResponse
|
||||||
|
3, // 3: proto.DebugTalk.Call:output_type -> proto.CallResponse
|
||||||
|
2, // [2:4] is the sub-list for method output_type
|
||||||
|
0, // [0:2] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_plugin_proto_debugtalk_proto_init() }
|
||||||
|
func file_plugin_proto_debugtalk_proto_init() {
|
||||||
|
if File_plugin_proto_debugtalk_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_plugin_proto_debugtalk_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Empty); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_plugin_proto_debugtalk_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GetNamesResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_plugin_proto_debugtalk_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CallRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_plugin_proto_debugtalk_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CallResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_plugin_proto_debugtalk_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 4,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_plugin_proto_debugtalk_proto_goTypes,
|
||||||
|
DependencyIndexes: file_plugin_proto_debugtalk_proto_depIdxs,
|
||||||
|
MessageInfos: file_plugin_proto_debugtalk_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_plugin_proto_debugtalk_proto = out.File
|
||||||
|
file_plugin_proto_debugtalk_proto_rawDesc = nil
|
||||||
|
file_plugin_proto_debugtalk_proto_goTypes = nil
|
||||||
|
file_plugin_proto_debugtalk_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package proto;
|
||||||
|
|
||||||
|
option go_package = "github.com/httprunner/hrp/plugin/proto";
|
||||||
|
|
||||||
|
message Empty {}
|
||||||
|
|
||||||
|
message GetNamesResponse {
|
||||||
|
repeated string names = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CallRequest {
|
||||||
|
string name = 1;
|
||||||
|
bytes args = 2; // []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CallResponse {
|
||||||
|
bytes value = 1; // interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
service DebugTalk {
|
||||||
|
rpc GetNames(Empty) returns (GetNamesResponse);
|
||||||
|
rpc Call(CallRequest) returns (CallResponse);
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.32.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion7
|
||||||
|
|
||||||
|
// DebugTalkClient is the client API for DebugTalk service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type DebugTalkClient interface {
|
||||||
|
GetNames(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetNamesResponse, error)
|
||||||
|
Call(ctx context.Context, in *CallRequest, opts ...grpc.CallOption) (*CallResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type debugTalkClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDebugTalkClient(cc grpc.ClientConnInterface) DebugTalkClient {
|
||||||
|
return &debugTalkClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *debugTalkClient) GetNames(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetNamesResponse, error) {
|
||||||
|
out := new(GetNamesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/proto.DebugTalk/GetNames", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *debugTalkClient) Call(ctx context.Context, in *CallRequest, opts ...grpc.CallOption) (*CallResponse, error) {
|
||||||
|
out := new(CallResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/proto.DebugTalk/Call", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugTalkServer is the server API for DebugTalk service.
|
||||||
|
// All implementations must embed UnimplementedDebugTalkServer
|
||||||
|
// for forward compatibility
|
||||||
|
type DebugTalkServer interface {
|
||||||
|
GetNames(context.Context, *Empty) (*GetNamesResponse, error)
|
||||||
|
Call(context.Context, *CallRequest) (*CallResponse, error)
|
||||||
|
mustEmbedUnimplementedDebugTalkServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedDebugTalkServer must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedDebugTalkServer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedDebugTalkServer) GetNames(context.Context, *Empty) (*GetNamesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetNames not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDebugTalkServer) Call(context.Context, *CallRequest) (*CallResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Call not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDebugTalkServer) mustEmbedUnimplementedDebugTalkServer() {}
|
||||||
|
|
||||||
|
// UnsafeDebugTalkServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to DebugTalkServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeDebugTalkServer interface {
|
||||||
|
mustEmbedUnimplementedDebugTalkServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterDebugTalkServer(s grpc.ServiceRegistrar, srv DebugTalkServer) {
|
||||||
|
s.RegisterService(&DebugTalk_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DebugTalk_GetNames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DebugTalkServer).GetNames(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/proto.DebugTalk/GetNames",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DebugTalkServer).GetNames(ctx, req.(*Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DebugTalk_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CallRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DebugTalkServer).Call(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/proto.DebugTalk/Call",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DebugTalkServer).Call(ctx, req.(*CallRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugTalk_ServiceDesc is the grpc.ServiceDesc for DebugTalk service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var DebugTalk_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "proto.DebugTalk",
|
||||||
|
HandlerType: (*DebugTalkServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "GetNames",
|
||||||
|
Handler: _DebugTalk_GetNames_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Call",
|
||||||
|
Handler: _DebugTalk_Call_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "plugin/proto/debugtalk.proto",
|
||||||
|
}
|
||||||
+3
-2
@@ -2,7 +2,7 @@ package hrp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
builtinJSON "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -15,6 +15,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/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newResponseObject(t *testing.T, parser *parser, resp *http.Response) (*responseObject, error) {
|
func newResponseObject(t *testing.T, parser *parser, resp *http.Response) (*responseObject, error) {
|
||||||
@@ -190,7 +191,7 @@ func (v *responseObject) searchJmespath(expr string) interface{} {
|
|||||||
log.Error().Str("expr", expr).Err(err).Msg("search jmespath failed")
|
log.Error().Str("expr", expr).Err(err).Msg("search jmespath failed")
|
||||||
return expr // jmespath not found, return the expression
|
return expr // jmespath not found, return the expression
|
||||||
}
|
}
|
||||||
if number, ok := checkValue.(json.Number); ok {
|
if number, ok := checkValue.(builtinJSON.Number); ok {
|
||||||
checkNumber, err := parseJSONNumber(number)
|
checkNumber, err := parseJSONNumber(number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Interface("json number", number).Err(err).Msg("convert json number failed")
|
log.Error().Interface("json number", number).Err(err).Msg("convert json number failed")
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
@@ -32,6 +31,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/internal/json"
|
||||||
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
pluginInternal "github.com/httprunner/hrp/plugin/inner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user