mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
Merge pull request #57 from httprunner/go-plugin
integrate hashicorp 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-14)
|
||||
|
||||
- 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
|
||||
|
||||
@@ -33,4 +33,4 @@ Copyright 2021 debugtalk
|
||||
* [hrp run](hrp_run.md) - run API test
|
||||
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
||||
|
||||
###### Auto generated by spf13/cobra on 12-Jan-2022
|
||||
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||
|
||||
@@ -25,6 +25,7 @@ hrp boom [flags]
|
||||
--cpu-profile-duration duration CPU profile duration. (default 30s)
|
||||
--disable-console-output Disable console output.
|
||||
-h, --help help for boom
|
||||
--loop-count int The specify running cycles for load testing (default -1)
|
||||
--max-rps int Max RPS that boomer can generate, disabled by default.
|
||||
--mem-profile string Enable memory profiling.
|
||||
--mem-profile-duration duration Memory profile duration. (default 30s)
|
||||
@@ -38,4 +39,4 @@ hrp boom [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 12-Jan-2022
|
||||
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||
|
||||
@@ -23,4 +23,4 @@ hrp har2case $har_path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 12-Jan-2022
|
||||
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||
|
||||
@@ -31,4 +31,4 @@ hrp run $path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 12-Jan-2022
|
||||
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||
|
||||
@@ -16,4 +16,4 @@ hrp startproject $project_name [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 12-Jan-2022
|
||||
###### Auto generated by spf13/cobra on 13-Jan-2022
|
||||
|
||||
@@ -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,69 +12,80 @@ 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")
|
||||
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)
|
||||
_, err := locatePlugin(cwd, goPluginFile)
|
||||
if !assert.Error(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_, err = locatePlugin("")
|
||||
_, err = locatePlugin("", goPluginFile)
|
||||
if !assert.Error(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
startPath := "examples/debugtalk.so"
|
||||
_, err = locatePlugin(startPath)
|
||||
_, err = locatePlugin(startPath, goPluginFile)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
startPath = "examples/demo.json"
|
||||
_, err = locatePlugin(startPath)
|
||||
_, err = locatePlugin(startPath, goPluginFile)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
startPath = "examples/"
|
||||
_, err = locatePlugin(startPath)
|
||||
_, err = locatePlugin(startPath, goPluginFile)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
startPath = "examples/plugin/debugtalk.go"
|
||||
_, err = locatePlugin(startPath)
|
||||
_, err = locatePlugin(startPath, goPluginFile)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
startPath = "/abc"
|
||||
_, err = locatePlugin(startPath)
|
||||
_, err = locatePlugin(startPath, goPluginFile)
|
||||
if !assert.Error(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallPluginFunction(t *testing.T) {
|
||||
buildGoPlugin()
|
||||
removeHashicorpPlugin()
|
||||
defer removeGoPlugin()
|
||||
|
||||
parser := newParser()
|
||||
parser.loadPlugin("examples/debugtalk.so")
|
||||
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"
|
||||
|
||||
44
parser.go
44
parser.go
@@ -4,9 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -21,46 +18,7 @@ func newParser() *parser {
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
// pluginLoader stores loaded go plugins.
|
||||
pluginLoader *plugin.Plugin
|
||||
}
|
||||
|
||||
// locatePlugin searches debugtalk.so upward recursively until current
|
||||
// working directory or system root dir.
|
||||
func locatePlugin(startPath string) (string, error) {
|
||||
stat, err := os.Stat(startPath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var startDir string
|
||||
if stat.IsDir() {
|
||||
startDir = startPath
|
||||
} else {
|
||||
startDir = filepath.Dir(startPath)
|
||||
}
|
||||
startDir, _ = filepath.Abs(startDir)
|
||||
|
||||
// convention over configuration
|
||||
// target plugin file name is always debugtalk.so
|
||||
pluginPath := filepath.Join(startDir, "debugtalk.so")
|
||||
if _, err := os.Stat(pluginPath); err == nil {
|
||||
return pluginPath, nil
|
||||
}
|
||||
|
||||
// current working directory
|
||||
cwd, _ := os.Getwd()
|
||||
if startDir == cwd {
|
||||
return "", fmt.Errorf("searched to CWD, plugin file not found")
|
||||
}
|
||||
|
||||
// system root dir
|
||||
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
|
||||
if parentDir == startDir {
|
||||
return "", fmt.Errorf("searched to system root dir, plugin file not found")
|
||||
}
|
||||
|
||||
return locatePlugin(parentDir)
|
||||
plugin hrpPlugin // plugin is used to call functions
|
||||
}
|
||||
|
||||
func buildURL(baseURL, stepURL string) string {
|
||||
|
||||
268
plugin.go
268
plugin.go
@@ -2,6 +2,8 @@ package hrp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -10,30 +12,38 @@ import (
|
||||
|
||||
"github.com/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/hrp/internal/ga"
|
||||
pluginHost "github.com/httprunner/hrp/plugin/host"
|
||||
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
||||
)
|
||||
|
||||
func (p *parser) loadPlugin(path string) error {
|
||||
type pluginFile string
|
||||
|
||||
const (
|
||||
goPluginFile pluginFile = pluginShared.Name + ".so" // built from go plugin
|
||||
hashicorpGoPluginFile pluginFile = pluginShared.Name + ".bin" // built from hashicorp go plugin
|
||||
hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py"
|
||||
)
|
||||
|
||||
type hrpPlugin 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if runtime.GOOS == "windows" {
|
||||
log.Warn().Msg("go plugin does not support windows")
|
||||
return nil
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if loaded before
|
||||
if p.pluginLoader != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// locate plugin file
|
||||
pluginPath, err := locatePlugin(path)
|
||||
if err != nil {
|
||||
// plugin not found
|
||||
return nil
|
||||
return fmt.Errorf("go plugin does not support windows")
|
||||
}
|
||||
|
||||
var err error
|
||||
// report event for loading go plugin
|
||||
defer func() {
|
||||
event := ga.EventTracking{
|
||||
@@ -46,100 +56,180 @@ func (p *parser) loadPlugin(path string) error {
|
||||
go ga.SendEvent(event)
|
||||
}()
|
||||
|
||||
// load plugin
|
||||
plugins, err := plugin.Open(pluginPath)
|
||||
p.Plugin, err = plugin.Open(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", path).Msg("load go plugin failed")
|
||||
return err
|
||||
}
|
||||
p.pluginLoader = plugins
|
||||
|
||||
p.cachedFunctions = make(map[string]reflect.Value)
|
||||
log.Info().Str("path", path).Msg("load go plugin success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMappingFunction(funcName string, pluginLoader *plugin.Plugin) (reflect.Value, error) {
|
||||
var fn reflect.Value
|
||||
var err error
|
||||
func (p *goPlugin) has(funcName string) bool {
|
||||
fn, ok := p.cachedFunctions[funcName]
|
||||
if ok {
|
||||
return fn.IsValid()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// check function type
|
||||
if err == nil && fn.Kind() != reflect.Func {
|
||||
// function not valid
|
||||
err = fmt.Errorf("function %s is invalid", funcName)
|
||||
return
|
||||
}
|
||||
}()
|
||||
sym, err := p.Plugin.Lookup(funcName)
|
||||
if err != nil {
|
||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||
return false
|
||||
}
|
||||
fn = reflect.ValueOf(sym)
|
||||
|
||||
// get function from plugin loader
|
||||
if pluginLoader != nil {
|
||||
sym, err := pluginLoader.Lookup(funcName)
|
||||
if err == nil {
|
||||
fn = reflect.ValueOf(sym)
|
||||
return fn, nil
|
||||
// check function type
|
||||
if fn.Kind() != reflect.Func {
|
||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||
return false
|
||||
}
|
||||
|
||||
p.cachedFunctions[funcName] = fn
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
fn := p.cachedFunctions[funcName]
|
||||
return pluginShared.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 {
|
||||
pluginShared.FuncCaller
|
||||
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) init(path string) error {
|
||||
|
||||
f, err := pluginHost.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) 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
|
||||
}
|
||||
}
|
||||
|
||||
// get builtin function
|
||||
if function, ok := builtin.Functions[funcName]; ok {
|
||||
fn = reflect.ValueOf(function)
|
||||
return fn, nil
|
||||
p.cachedFunctions[funcName] = false // cache as not exists
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
return p.FuncCaller.Call(funcName, args...)
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) quit() error {
|
||||
// kill hashicorp plugin process
|
||||
pluginHost.Quit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) initPlugin(path string) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// function not found
|
||||
return reflect.Value{}, fmt.Errorf("function %s is not found", funcName)
|
||||
// priority: hashicorp plugin > go plugin > builtin functions
|
||||
// locate hashicorp plugin file
|
||||
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
|
||||
}
|
||||
|
||||
// locatePlugin searches destPluginFile upward recursively until current
|
||||
// working directory or system root dir.
|
||||
func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
|
||||
stat, err := os.Stat(startPath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var startDir string
|
||||
if stat.IsDir() {
|
||||
startDir = startPath
|
||||
} else {
|
||||
startDir = filepath.Dir(startPath)
|
||||
}
|
||||
startDir, _ = filepath.Abs(startDir)
|
||||
|
||||
// convention over configuration
|
||||
pluginPath := filepath.Join(startDir, string(destPluginFile))
|
||||
if _, err := os.Stat(pluginPath); err == nil {
|
||||
return pluginPath, nil
|
||||
}
|
||||
|
||||
// current working directory
|
||||
cwd, _ := os.Getwd()
|
||||
if startDir == cwd {
|
||||
return "", fmt.Errorf("searched to CWD, plugin file not found")
|
||||
}
|
||||
|
||||
// system root dir
|
||||
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
|
||||
if parentDir == startDir {
|
||||
return "", fmt.Errorf("searched to system root dir, plugin file not found")
|
||||
}
|
||||
|
||||
return locatePlugin(parentDir, destPluginFile)
|
||||
}
|
||||
|
||||
// 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 := getMappingFunction(funcName, p.pluginLoader)
|
||||
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) {
|
||||
// function arguments not match
|
||||
return nil, fmt.Errorf("function arguments number not match")
|
||||
// get builtin function
|
||||
function, ok := builtin.Functions[funcName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("function %s is not found", funcName)
|
||||
}
|
||||
fn := reflect.ValueOf(function)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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")
|
||||
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
|
||||
// call with builtin function
|
||||
return pluginShared.CallFunc(fn, arguments...)
|
||||
}
|
||||
|
||||
65
plugin/examples/debugtalk.go
Normal file
65
plugin/examples/debugtalk.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
plugin "github.com/httprunner/hrp/plugin"
|
||||
)
|
||||
|
||||
func SumTwoInt(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func SumInts(args ...int) int {
|
||||
var sum int
|
||||
for _, arg := range args {
|
||||
sum += arg
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func Sum(args ...interface{}) (interface{}, error) {
|
||||
var sum float64
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case int:
|
||||
sum += float64(v)
|
||||
case float64:
|
||||
sum += v
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func SumTwoString(a, b string) string {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func SumStrings(s ...string) string {
|
||||
var sum string
|
||||
for _, arg := range s {
|
||||
sum += arg
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func Concatenate(args ...interface{}) (interface{}, error) {
|
||||
var result string
|
||||
for _, arg := range args {
|
||||
result += fmt.Sprintf("%v", arg)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// register functions and build to plugin binary
|
||||
func main() {
|
||||
plugin.Register("sum_ints", SumInts)
|
||||
plugin.Register("sum_two_int", SumTwoInt)
|
||||
plugin.Register("sum", Sum)
|
||||
plugin.Register("sum_two_string", SumTwoString)
|
||||
plugin.Register("sum_strings", SumStrings)
|
||||
plugin.Register("concatenate", Concatenate)
|
||||
plugin.Serve()
|
||||
}
|
||||
87
plugin/examples/plugin_test.go
Normal file
87
plugin/examples/plugin_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/httprunner/hrp/plugin/host"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("[TestMain] build go plugin")
|
||||
cmd := exec.Command("go", "build", "-o=debugtalk.bin", "./debugtalk.go")
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestInitHashicorpPlugin(t *testing.T) {
|
||||
f, err := host.Init("./debugtalk.bin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer host.Quit()
|
||||
|
||||
v1, err := f.GetNames()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Contains(t, v1, "sum_ints") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Contains(t, v1, "concatenate") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var v2 interface{}
|
||||
v2, err = f.Call("sum_ints", 1, 2, 3, 4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, 10, v2) {
|
||||
t.Fail()
|
||||
}
|
||||
v2, err = f.Call("sum_two_int", 1, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, 3, v2) {
|
||||
t.Fail()
|
||||
}
|
||||
v2, err = f.Call("sum", 1, 2, 3.4, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, 11.4, v2) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
var v3 interface{}
|
||||
v3, err = f.Call("sum_two_string", "a", "b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, "ab", v3) {
|
||||
t.Fail()
|
||||
}
|
||||
v3, err = f.Call("sum_strings", "a", "b", "c")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, "abc", v3) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
v3, err = f.Call("concatenate", "a", 2, "c", 3.4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t, "a2c3.4", v3) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
10
plugin/go.mod
Normal file
10
plugin/go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module github.com/httprunner/hrp/plugin
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/hashicorp/go-hclog v1.1.0
|
||||
github.com/hashicorp/go-plugin v1.4.3
|
||||
github.com/rs/zerolog v1.26.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
118
plugin/go.sum
Normal file
118
plugin/go.sum
Normal file
@@ -0,0 +1,118 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
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-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||
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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
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-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
50
plugin/host/host.go
Normal file
50
plugin/host/host.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
|
||||
"github.com/httprunner/hrp/plugin/shared"
|
||||
)
|
||||
|
||||
var client *plugin.Client
|
||||
|
||||
func Init(path string) (shared.FuncCaller, error) {
|
||||
// launch the plugin process
|
||||
client = plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: shared.HandshakeConfig,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
shared.Name: &shared.HashicorpPlugin{},
|
||||
},
|
||||
Cmd: exec.Command(path),
|
||||
Logger: hclog.New(&hclog.LoggerOptions{
|
||||
Name: shared.Name,
|
||||
Output: os.Stdout,
|
||||
Level: hclog.Info,
|
||||
}),
|
||||
})
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense(shared.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.(shared.FuncCaller)
|
||||
return function, nil
|
||||
}
|
||||
|
||||
func Quit() {
|
||||
client.Kill()
|
||||
}
|
||||
67
plugin/plugin.go
Normal file
67
plugin/plugin.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
|
||||
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
||||
)
|
||||
|
||||
// functionsMap stores plugin functions
|
||||
type functionsMap map[string]reflect.Value
|
||||
|
||||
// functionPlugin implements the FuncCaller interface
|
||||
type functionPlugin struct {
|
||||
logger hclog.Logger
|
||||
functions functionsMap
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
fn, ok := p.functions[funcName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("function %s not found", funcName)
|
||||
}
|
||||
|
||||
return pluginShared.CallFunc(fn, args...)
|
||||
}
|
||||
|
||||
var functions = make(functionsMap)
|
||||
|
||||
func Register(funcName string, fn interface{}) {
|
||||
if _, ok := functions[funcName]; ok {
|
||||
return
|
||||
}
|
||||
functions[funcName] = reflect.ValueOf(fn)
|
||||
}
|
||||
|
||||
func Serve() {
|
||||
funcPlugin := &functionPlugin{
|
||||
logger: hclog.New(&hclog.LoggerOptions{
|
||||
Name: pluginShared.Name,
|
||||
Output: os.Stdout,
|
||||
Level: hclog.Info,
|
||||
}),
|
||||
functions: functions,
|
||||
}
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
pluginShared.Name: &pluginShared.HashicorpPlugin{Impl: funcPlugin},
|
||||
}
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: pluginShared.HandshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
})
|
||||
}
|
||||
15
plugin/shared/config.go
Normal file
15
plugin/shared/config.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package shared
|
||||
|
||||
import "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,
|
||||
}
|
||||
103
plugin/shared/interface.go
Normal file
103
plugin/shared/interface.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package shared
|
||||
|
||||
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
|
||||
}
|
||||
50
plugin/shared/utils.go
Normal file
50
plugin/shared/utils.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// 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, expect %d, got %d", fnArgsNum, len(args))
|
||||
}
|
||||
// arguments do not have slice, and arguments number matched
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
resultValues := fn.Call(argumentsValue)
|
||||
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
|
||||
}
|
||||
}
|
||||
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.loadPlugin(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