mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-11 18:11:21 +08:00
feat: support creating and calling custom functions with hashicorp/go-plugin
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,3 +22,7 @@ __pycache__
|
||||
|
||||
site/
|
||||
output/
|
||||
|
||||
# built plugins
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Release History
|
||||
|
||||
## v0.5.2 (2022-01-13)
|
||||
|
||||
- feat: support creating and calling custom functions with [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin)
|
||||
|
||||
## v0.5.1 (2022-01-13)
|
||||
|
||||
- feat: support specifying running cycles for load testing
|
||||
|
||||
@@ -9,6 +9,26 @@ func init() {
|
||||
log.Println("plugin init function called")
|
||||
}
|
||||
|
||||
func Concatenate(a int, b string, c float64) string {
|
||||
return fmt.Sprintf("%v_%v_%v", a, b, c)
|
||||
func SumInt(args ...interface{}) (interface{}, error) {
|
||||
var sum int
|
||||
for _, arg := range args {
|
||||
v, ok := arg.(int)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func ConcatenateString(args ...interface{}) (interface{}, error) {
|
||||
var result string
|
||||
for _, arg := range args {
|
||||
v, ok := arg.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
result += v
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -6,6 +6,8 @@ require (
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/getsentry/sentry-go v0.11.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/go-hclog v1.1.0
|
||||
github.com/hashicorp/go-plugin v1.4.3
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/maja42/goval v1.2.1
|
||||
|
||||
26
go.sum
26
go.sum
@@ -101,6 +101,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -205,9 +206,14 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw=
|
||||
github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
@@ -222,6 +228,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
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/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
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=
|
||||
@@ -233,6 +241,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/
|
||||
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
|
||||
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||
github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w=
|
||||
github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
@@ -277,10 +287,14 @@ github.com/maja42/goval v1.2.1 h1:fyEgzddqPgCZsKcFLk4C6SdCHyEaAHYvtZG4mGzQOHU=
|
||||
github.com/maja42/goval v1.2.1/go.mod h1:42LU+BQXL/veE9jnTTUOSj38GRmOTSThYSXRVodI5J4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
@@ -293,6 +307,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
@@ -310,6 +326,8 @@ github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5Vgl
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -476,6 +494,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -518,6 +537,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -563,6 +583,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -608,6 +629,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -700,6 +722,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -740,7 +763,9 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -760,6 +785,7 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -12,17 +12,24 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("[TestMain] build go plugin")
|
||||
func buildGoPlugin() {
|
||||
fmt.Println("[setup] build go plugin")
|
||||
// flag -race is necessary in order to be consistent with go test
|
||||
cmd := exec.Command("go", "build", "-buildmode=plugin", "-race", "-o=examples/debugtalk.so", "examples/plugin/debugtalk.go")
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func removeGoPlugin() {
|
||||
fmt.Println("[teardown] remove go plugin")
|
||||
os.Remove("examples/debugtalk.so")
|
||||
}
|
||||
|
||||
func TestLocatePlugin(t *testing.T) {
|
||||
buildGoPlugin()
|
||||
defer removeGoPlugin()
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
_, err := locatePlugin(cwd, goPluginFile)
|
||||
if !assert.Error(t, err) {
|
||||
@@ -66,15 +73,19 @@ func TestLocatePlugin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCallPluginFunction(t *testing.T) {
|
||||
buildGoPlugin()
|
||||
removeHashicorpPlugin()
|
||||
defer removeGoPlugin()
|
||||
|
||||
parser := newParser()
|
||||
parser.initPlugin("examples/debugtalk.so")
|
||||
|
||||
// call function without arguments
|
||||
result, err := parser.callFunc("Concatenate", 1, "2", 3.14)
|
||||
result, err := parser.callFunc("ConcatenateString", "1", "2", "3.14")
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t, result, "1_2_3.14") {
|
||||
if !assert.Equal(t, "123.14", result) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package version
|
||||
|
||||
const VERSION = "v0.5.1"
|
||||
const VERSION = "v0.5.2"
|
||||
|
||||
@@ -18,8 +18,7 @@ func newParser() *parser {
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
plugin hrpPlugin // plugin is used to call functions
|
||||
cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance
|
||||
plugin hrpPlugin // plugin is used to call functions
|
||||
}
|
||||
|
||||
func buildURL(baseURL, stepURL string) string {
|
||||
|
||||
55
plugin-gosdk/build.go
Normal file
55
plugin-gosdk/build.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
// Here is a real implementation of funcCaller
|
||||
type functionPlugin struct {
|
||||
logger hclog.Logger
|
||||
functions map[string]func(args ...interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
func (p *functionPlugin) GetNames() ([]string, error) {
|
||||
var names []string
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
p.logger.Info("Call function", "funcName", funcName, "args", args)
|
||||
|
||||
f, ok := p.functions[funcName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("function %s not found", funcName)
|
||||
}
|
||||
|
||||
return f(args...)
|
||||
}
|
||||
|
||||
var functions = make(map[string]func(args ...interface{}) (interface{}, error))
|
||||
|
||||
func Register(funcName string, fn func(args ...interface{}) (interface{}, error)) {
|
||||
functions[funcName] = fn
|
||||
}
|
||||
|
||||
func Serve() {
|
||||
funcPlugin := &functionPlugin{
|
||||
logger: logger,
|
||||
functions: functions,
|
||||
}
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
Name: &hashicorpPlugin{Impl: funcPlugin},
|
||||
}
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: handshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
})
|
||||
}
|
||||
42
plugin-gosdk/host_test.go
Normal file
42
plugin-gosdk/host_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("[TestMain] build go plugin")
|
||||
cmd := exec.Command("go", "build", "-o=debugtalk", "plugin/debugtalk.go")
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestInitHashicorpPlugin(t *testing.T) {
|
||||
f, err := Init("./debugtalk")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer Quit()
|
||||
|
||||
v, err := f.Call("sum_int", 1, 2, 3, 4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, 10, v) {
|
||||
t.Fail()
|
||||
}
|
||||
v, err = f.Call("concatenate_string", "a", "b", "c")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, "abc", v) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
105
plugin-gosdk/interface.go
Normal file
105
plugin-gosdk/interface.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(new(funcData))
|
||||
}
|
||||
|
||||
// funcData is used to transfer between plugin and host via RPC.
|
||||
type funcData struct {
|
||||
Name string // function name
|
||||
Args []interface{} // function arguments
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// functionRPC runs on the host side.
|
||||
type functionRPC struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (g *functionRPC) GetNames() ([]string, error) {
|
||||
var resp []string
|
||||
err := g.client.Call("Plugin.GetNames", new(interface{}), &resp)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("rpc call GetNames() failed")
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// host -> plugin
|
||||
func (g *functionRPC) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
|
||||
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via RPC")
|
||||
f := funcData{
|
||||
Name: funcName,
|
||||
Args: funcArgs,
|
||||
}
|
||||
|
||||
var args interface{} = f
|
||||
var resp interface{}
|
||||
err := g.client.Call("Plugin.Call", &args, &resp)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("funcName", funcName).Interface("funcArgs", funcArgs).
|
||||
Msg("rpc call Call() failed")
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// functionRPCServer runs on the plugin side, executing the user custom function.
|
||||
type functionRPCServer struct {
|
||||
Impl FuncCaller
|
||||
}
|
||||
|
||||
// plugin execution
|
||||
func (s *functionRPCServer) GetNames(args interface{}, resp *[]string) error {
|
||||
log.Info().Interface("args", args).Msg("GetNames called on plugin side")
|
||||
var err error
|
||||
*resp, err = s.Impl.GetNames()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("GetNames execution failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// plugin execution
|
||||
func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error {
|
||||
log.Info().Interface("args", args).Msg("function called on plugin side")
|
||||
f := args.(*funcData)
|
||||
var err error
|
||||
*resp, err = s.Impl.Call(f.Name, f.Args...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Interface("args", args).
|
||||
Msg("function execution failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashicorpPlugin implements hashicorp's plugin.Plugin.
|
||||
type hashicorpPlugin struct {
|
||||
Impl FuncCaller
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
return &functionRPCServer{Impl: p.Impl}, nil
|
||||
}
|
||||
|
||||
func (hashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &functionRPC{client: c}, nil
|
||||
}
|
||||
63
plugin-gosdk/main.go
Normal file
63
plugin-gosdk/main.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
const Name = "debugtalk"
|
||||
|
||||
// handshakeConfigs are used to just do a basic handshake between
|
||||
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
||||
// This prevents users from executing bad plugins or executing a plugin
|
||||
// directory. It is a UX feature, not a security feature.
|
||||
var handshakeConfig = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "HttpRunnerPlus",
|
||||
MagicCookieValue: Name,
|
||||
}
|
||||
|
||||
// Create an hclog.Logger
|
||||
var logger = hclog.New(&hclog.LoggerOptions{
|
||||
Name: Name,
|
||||
Output: os.Stdout,
|
||||
Level: hclog.Debug,
|
||||
})
|
||||
|
||||
var client *plugin.Client
|
||||
|
||||
func Init(path string) (FuncCaller, error) {
|
||||
// launch the plugin process
|
||||
client = plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: handshakeConfig,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
Name: &hashicorpPlugin{},
|
||||
},
|
||||
Cmd: exec.Command(path),
|
||||
Logger: logger,
|
||||
})
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense(Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We should have a Function now! This feels like a normal interface
|
||||
// implementation but is in fact over an RPC connection.
|
||||
function := raw.(FuncCaller)
|
||||
return function, nil
|
||||
}
|
||||
|
||||
func Quit() {
|
||||
client.Kill()
|
||||
}
|
||||
38
plugin-gosdk/plugin/debugtalk.go
Normal file
38
plugin-gosdk/plugin/debugtalk.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
plugin "github.com/httprunner/hrp/plugin-gosdk"
|
||||
)
|
||||
|
||||
func SumInt(args ...interface{}) (interface{}, error) {
|
||||
var sum int
|
||||
for _, arg := range args {
|
||||
v, ok := arg.(int)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func ConcatenateString(args ...interface{}) (interface{}, error) {
|
||||
var result string
|
||||
for _, arg := range args {
|
||||
v, ok := arg.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
result += v
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// register functions and build to plugin binary
|
||||
func main() {
|
||||
plugin.Register("sum_int", SumInt)
|
||||
plugin.Register("concatenate_string", ConcatenateString)
|
||||
plugin.Serve()
|
||||
}
|
||||
208
plugin.go
208
plugin.go
@@ -12,26 +12,28 @@ import (
|
||||
|
||||
"github.com/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/hrp/internal/ga"
|
||||
pluginSDK "github.com/httprunner/hrp/plugin-gosdk"
|
||||
)
|
||||
|
||||
type pluginFile string
|
||||
|
||||
const (
|
||||
goPluginFile pluginFile = "debugtalk.so" // built from go plugin
|
||||
hashicorpGoPluginFile pluginFile = "debugtalk" // built from hashicorp go plugin
|
||||
hashicorpPyPluginFile pluginFile = "debugtalk.py"
|
||||
goPluginFile pluginFile = pluginSDK.Name + ".so" // built from go plugin
|
||||
hashicorpGoPluginFile pluginFile = pluginSDK.Name + ".bin" // built from hashicorp go plugin
|
||||
hashicorpPyPluginFile pluginFile = pluginSDK.Name + ".py"
|
||||
)
|
||||
|
||||
type hrpPlugin interface {
|
||||
init(path string) error
|
||||
lookup(funcName string) (reflect.Value, error) // lookup function
|
||||
// call(funcName string, args ...interface{}) (interface{}, error)
|
||||
quit() error
|
||||
init(path string) error // init plugin
|
||||
has(funcName string) bool // check if plugin has function
|
||||
call(funcName string, args ...interface{}) (interface{}, error) // call function
|
||||
quit() error // quit plugin
|
||||
}
|
||||
|
||||
// goPlugin implements golang official plugin
|
||||
type goPlugin struct {
|
||||
*plugin.Plugin
|
||||
cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance
|
||||
}
|
||||
|
||||
func (p *goPlugin) init(path string) error {
|
||||
@@ -59,58 +61,93 @@ func (p *goPlugin) init(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.cachedFunctions = make(map[string]reflect.Value)
|
||||
log.Info().Str("path", path).Msg("load go plugin success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *goPlugin) lookup(funcName string) (reflect.Value, error) {
|
||||
if p.Plugin == nil {
|
||||
return reflect.Value{}, fmt.Errorf("go plugin is not loaded")
|
||||
func (p *goPlugin) has(funcName string) bool {
|
||||
fn, ok := p.cachedFunctions[funcName]
|
||||
if ok {
|
||||
return fn.IsValid()
|
||||
}
|
||||
|
||||
sym, err := p.Plugin.Lookup(funcName)
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("function %s is not found", funcName)
|
||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||
return false
|
||||
}
|
||||
fn := reflect.ValueOf(sym)
|
||||
fn = reflect.ValueOf(sym)
|
||||
|
||||
// check function type
|
||||
if fn.Kind() != reflect.Func {
|
||||
return reflect.Value{}, fmt.Errorf("function %s is invalid", funcName)
|
||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||
return false
|
||||
}
|
||||
|
||||
return fn, nil
|
||||
p.cachedFunctions[funcName] = fn
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
if p.Plugin == nil {
|
||||
return nil, fmt.Errorf("go plugin is not loaded")
|
||||
}
|
||||
return nil, nil
|
||||
fn := p.cachedFunctions[funcName]
|
||||
return callFunc(fn, args...)
|
||||
}
|
||||
|
||||
func (p *goPlugin) quit() error {
|
||||
// no need to quit for go plugin
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashicorpPlugin implements hashicorp/go-plugin
|
||||
type hashicorpPlugin struct {
|
||||
pluginSDK.FuncCaller
|
||||
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) init(path string) error {
|
||||
|
||||
f, err := pluginSDK.Init(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed")
|
||||
return err
|
||||
}
|
||||
p.FuncCaller = f
|
||||
|
||||
p.cachedFunctions = make(map[string]bool)
|
||||
log.Info().Str("path", path).Msg("load hashicorp go plugin success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) lookup(funcName string) (reflect.Value, error) {
|
||||
return reflect.Value{}, nil
|
||||
func (p *hashicorpPlugin) has(funcName string) bool {
|
||||
flag, ok := p.cachedFunctions[funcName]
|
||||
if ok {
|
||||
return flag
|
||||
}
|
||||
|
||||
funcNames, err := p.GetNames()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, name := range funcNames {
|
||||
if name == funcName {
|
||||
p.cachedFunctions[funcName] = true // cache as exists
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
p.cachedFunctions[funcName] = false // cache as not exists
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
return p.FuncCaller.Call(funcName, args...)
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) quit() error {
|
||||
// kill hashicorp plugin process
|
||||
pluginSDK.Quit()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -119,22 +156,23 @@ func (p *parser) initPlugin(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// locate go plugin file
|
||||
pluginPath, err := locatePlugin(path, goPluginFile)
|
||||
if err == nil {
|
||||
// found go plugin file
|
||||
p.plugin = &goPlugin{}
|
||||
return p.plugin.init(pluginPath)
|
||||
}
|
||||
|
||||
// priority: hashicorp plugin > go plugin > builtin functions
|
||||
// locate hashicorp plugin file
|
||||
pluginPath, err = locatePlugin(path, hashicorpGoPluginFile)
|
||||
pluginPath, err := locatePlugin(path, hashicorpGoPluginFile)
|
||||
if err == nil {
|
||||
// found hashicorp go plugin file
|
||||
p.plugin = &hashicorpPlugin{}
|
||||
return p.plugin.init(pluginPath)
|
||||
}
|
||||
|
||||
// locate go plugin file
|
||||
pluginPath, err = locatePlugin(path, goPluginFile)
|
||||
if err == nil {
|
||||
// found go plugin file
|
||||
p.plugin = &goPlugin{}
|
||||
return p.plugin.init(pluginPath)
|
||||
}
|
||||
|
||||
// plugin not found
|
||||
return nil
|
||||
}
|
||||
@@ -176,83 +214,65 @@ func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
|
||||
return locatePlugin(parentDir, destPluginFile)
|
||||
}
|
||||
|
||||
func (p *parser) getMappingFunction(funcName string) (reflect.Value, error) {
|
||||
if function, ok := p.cachedFunctions[funcName]; ok {
|
||||
return function, nil
|
||||
}
|
||||
|
||||
var fn reflect.Value
|
||||
|
||||
// get function from plugin
|
||||
if p.plugin != nil {
|
||||
fn, err := p.plugin.lookup(funcName)
|
||||
if err == nil {
|
||||
p.cachedFunctions[funcName] = fn
|
||||
return fn, nil
|
||||
}
|
||||
}
|
||||
|
||||
// get builtin function
|
||||
if function, ok := builtin.Functions[funcName]; ok {
|
||||
fn = reflect.ValueOf(function)
|
||||
p.cachedFunctions[funcName] = fn
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
// function not found
|
||||
return reflect.Value{}, fmt.Errorf("function %s is not found", funcName)
|
||||
}
|
||||
|
||||
// callFunc calls function with arguments
|
||||
// only support return at most one result value
|
||||
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
|
||||
fn, err := p.getMappingFunction(funcName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// call with plugin function
|
||||
if p.plugin != nil && p.plugin.has(funcName) {
|
||||
return p.plugin.call(funcName, arguments...)
|
||||
}
|
||||
|
||||
if fn.Type().NumIn() != len(arguments) {
|
||||
// get builtin function
|
||||
function, ok := builtin.Functions[funcName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("function %s is not found", funcName)
|
||||
}
|
||||
fn := reflect.ValueOf(function)
|
||||
|
||||
// call with builtin function
|
||||
return callFunc(fn, arguments...)
|
||||
}
|
||||
|
||||
// callFunc calls function with arguments
|
||||
// it is used when calling go plugin or builtin functions
|
||||
func callFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
||||
fnArgsNum := fn.Type().NumIn()
|
||||
if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice {
|
||||
// last argument is slice, do not check arguments number
|
||||
// e.g. ...interface{}
|
||||
// e.g. a, b string, c ...interface{}
|
||||
} else if fnArgsNum != len(args) {
|
||||
// function arguments not match
|
||||
return nil, fmt.Errorf("function arguments number not match")
|
||||
}
|
||||
// arguments do not have slice, and arguments number matched
|
||||
|
||||
argumentsValue := make([]reflect.Value, len(arguments))
|
||||
for index, argument := range arguments {
|
||||
argumentValue := reflect.ValueOf(argument)
|
||||
expectArgumentType := fn.Type().In(index)
|
||||
actualArgumentType := reflect.TypeOf(argument)
|
||||
|
||||
// type match
|
||||
if expectArgumentType == actualArgumentType {
|
||||
argumentsValue[index] = argumentValue
|
||||
continue
|
||||
argumentsValue := make([]reflect.Value, len(args))
|
||||
for index, argument := range args {
|
||||
if argument == nil {
|
||||
argumentsValue[index] = reflect.Zero(fn.Type().In(index))
|
||||
} else {
|
||||
argumentsValue[index] = reflect.ValueOf(args[index])
|
||||
}
|
||||
|
||||
// type not match, check if convertible
|
||||
if !actualArgumentType.ConvertibleTo(expectArgumentType) {
|
||||
// function argument type not match and not convertible
|
||||
err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v",
|
||||
index, expectArgumentType, actualArgumentType)
|
||||
return nil, err
|
||||
}
|
||||
// convert argument to expect type
|
||||
argumentsValue[index] = argumentValue.Convert(expectArgumentType)
|
||||
}
|
||||
|
||||
resultValues := fn.Call(argumentsValue)
|
||||
if len(resultValues) > 1 {
|
||||
// function should return at most one value
|
||||
err := fmt.Errorf("function should return at most one value")
|
||||
if resultValues == nil {
|
||||
// no returns
|
||||
return nil, nil
|
||||
} else if len(resultValues) == 2 {
|
||||
// return two arguments: interface{}, error
|
||||
if resultValues[1].Interface() != nil {
|
||||
return resultValues[0].Interface(), resultValues[1].Interface().(error)
|
||||
} else {
|
||||
return resultValues[0].Interface(), nil
|
||||
}
|
||||
} else if len(resultValues) == 1 {
|
||||
// return one arguments: interface{}
|
||||
return resultValues[0].Interface(), nil
|
||||
} else {
|
||||
// return more than 2 arguments, unexpected
|
||||
err := fmt.Errorf("function should return at most 2 arguments")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// no return value
|
||||
if len(resultValues) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// return one value
|
||||
// convert reflect.Value to interface{}
|
||||
result := resultValues[0].Interface()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
18
runner.go
18
runner.go
@@ -155,6 +155,11 @@ func (r *caseRunner) reset() *caseRunner {
|
||||
}
|
||||
|
||||
func (r *caseRunner) run() error {
|
||||
defer func() {
|
||||
if r.parser.plugin != nil {
|
||||
r.parser.plugin.quit()
|
||||
}
|
||||
}()
|
||||
config := r.TestCase.Config
|
||||
if err := r.parseConfig(config); err != nil {
|
||||
return err
|
||||
@@ -497,6 +502,13 @@ func (r *caseRunner) runStepTestCase(step *TStep) (stepResult *stepData, err err
|
||||
|
||||
func (r *caseRunner) parseConfig(config IConfig) error {
|
||||
cfg := config.ToStruct()
|
||||
|
||||
// init plugin
|
||||
err := r.parser.initPlugin(cfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse config variables
|
||||
parsedVariables, err := r.parser.parseVariables(cfg.Variables)
|
||||
if err != nil {
|
||||
@@ -505,12 +517,6 @@ func (r *caseRunner) parseConfig(config IConfig) error {
|
||||
}
|
||||
cfg.Variables = parsedVariables
|
||||
|
||||
// load plugin variables and functions
|
||||
err = r.parser.initPlugin(cfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse config name
|
||||
parsedName, err := r.parser.parseString(cfg.Name, cfg.Variables)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user