From f691d91a85a16671d63e56694118b9d7bb605b9c Mon Sep 17 00:00:00 2001 From: vearne Date: Tue, 14 May 2024 15:55:32 +0800 Subject: [PATCH 1/2] support lua --- go.mod | 2 + go.sum | 4 ++ internal/resource/resource.go | 5 ++ internal/rule/http_lua_rule.go | 80 +++++++++++++++++++++++++++++ internal/rule/http_lua_rule_test.go | 1 + 5 files changed, 92 insertions(+) create mode 100644 internal/rule/http_lua_rule.go create mode 100644 internal/rule/http_lua_rule_test.go diff --git a/go.mod b/go.mod index 8221640..9c5c185 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/vearne/executor v0.0.3 github.com/vearne/simplelog v0.0.2 github.com/vearne/zaplog v0.0.2 + github.com/yuin/gopher-lua v1.1.1 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,4 +40,5 @@ require ( golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf // indirect ) diff --git a/go.sum b/go.sum index 53790d4..a66b9be 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/vearne/zaplog v0.0.2/go.mod h1:p8k2zw85seZZHEUfz5D6JWtbaf41oqQav8ibm4 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= @@ -136,3 +138,5 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0= +layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= diff --git a/internal/resource/resource.go b/internal/resource/resource.go index 262421a..edb7d36 100644 --- a/internal/resource/resource.go +++ b/internal/resource/resource.go @@ -14,6 +14,7 @@ import ( "strings" "sync" "sync/atomic" + "time" ) var GlobalConfig config.AutoTestConfig @@ -55,6 +56,10 @@ func ParseConfigFile(filePath string) error { return err } + if GlobalConfig.Global.RequestTimeout < time.Second { + GlobalConfig.Global.RequestTimeout = time.Second + } + slog.Info("2) parse http rule files") // read testcase // 1) http testcase diff --git a/internal/rule/http_lua_rule.go b/internal/rule/http_lua_rule.go new file mode 100644 index 0000000..348bfc2 --- /dev/null +++ b/internal/rule/http_lua_rule.go @@ -0,0 +1,80 @@ +package rule + +import ( + "github.com/yuin/gopher-lua" + luajson "layeh.com/gopher-json" +) + +var L *lua.LState + +func init() { + L = lua.NewState() + //defer L.Close() + // 注册 JSON 库 + luajson.Preload(L) + registerHttpRespType(L) +} + +type HttpResp struct { + Code string + Body string +} + +const luaHttpRespTypeName = "HttpResp" + +var httpRespMethods = map[string]lua.LGFunction{ + "code": getSetCode, + "body": getSetBody, +} + +// Getter and setter for the HttpResp#Code +func getSetCode(L *lua.LState) int { + p := checkHttpResp(L) + if L.GetTop() == 3 { + p.Code = L.CheckString(2) + return 0 + } + L.Push(lua.LString(p.Code)) + return 1 +} + +// Getter and setter for the HttpResp#Body +func getSetBody(L *lua.LState) int { + p := checkHttpResp(L) + if L.GetTop() == 3 { + p.Body = L.CheckString(3) + return 0 + } + L.Push(lua.LString(p.Body)) + return 1 +} + +// Checks whether the first lua argument is a *LUserData with *Person and returns this *Person. +func checkHttpResp(L *lua.LState) *HttpResp { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*HttpResp); ok { + return v + } + L.ArgError(1, "HttpResp expected") + return nil +} + +// Constructor +func newHttpResp(L *lua.LState) int { + resp := &HttpResp{L.CheckString(1), L.CheckString(2)} + ud := L.NewUserData() + ud.Value = resp + L.SetMetatable(ud, L.GetTypeMetatable(luaHttpRespTypeName)) + L.Push(ud) + return 1 +} + +// Registers my person type to given L. +func registerHttpRespType(L *lua.LState) { + mt := L.NewTypeMetatable(luaHttpRespTypeName) + L.SetGlobal("HttpResp", mt) + // static attributes + L.SetField(mt, "new", L.NewFunction(newHttpResp)) + // methods + L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), httpRespMethods)) +} diff --git a/internal/rule/http_lua_rule_test.go b/internal/rule/http_lua_rule_test.go new file mode 100644 index 0000000..63ed38e --- /dev/null +++ b/internal/rule/http_lua_rule_test.go @@ -0,0 +1 @@ +package rule From c3d664e9261c3fcaa7d7fe4c9c3a0a1b7b9cbdc8 Mon Sep 17 00:00:00 2001 From: vearne Date: Tue, 14 May 2024 18:52:14 +0800 Subject: [PATCH 2/2] support HttpLuaRule --- config_files/my_http_api.yml | 9 +++++- doc/lua.md | 0 internal/rule/http_lua_rule.go | 49 ++++++++++++++++++++++++++++- internal/rule/http_lua_rule_test.go | 42 +++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 doc/lua.md diff --git a/config_files/my_http_api.yml b/config_files/my_http_api.yml index 07cb197..89f3bff 100644 --- a/config_files/my_http_api.yml +++ b/config_files/my_http_api.yml @@ -60,7 +60,14 @@ - name: "HttpBodyEqualRule" xpath: "/author" Expected: "book3_author-2" - + - name: "HttpLuaRule" + lua: | + function verify(r) + local json = require "json"; + local book = json.decode(r:body()); + return book.title == "book3_title" and book.author == "book3_author-2"; + end + - id: 4 desc: "delete the book1" request: diff --git a/doc/lua.md b/doc/lua.md new file mode 100644 index 0000000..e69de29 diff --git a/internal/rule/http_lua_rule.go b/internal/rule/http_lua_rule.go index 348bfc2..92694b3 100644 --- a/internal/rule/http_lua_rule.go +++ b/internal/rule/http_lua_rule.go @@ -1,16 +1,27 @@ package rule import ( + "github.com/go-resty/resty/v2" + "github.com/vearne/zaplog" "github.com/yuin/gopher-lua" + "go.uber.org/zap" luajson "layeh.com/gopher-json" + "strconv" + "sync" ) var L *lua.LState +/* +Lua virtual machine is not thread-safe +so we need LuaVMLock to protect L +*/ +var LuaVMLock sync.Mutex + func init() { L = lua.NewState() //defer L.Close() - // 注册 JSON 库 + // register json lib luajson.Preload(L) registerHttpRespType(L) } @@ -78,3 +89,39 @@ func registerHttpRespType(L *lua.LState) { // methods L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), httpRespMethods)) } + +type HttpLuaRule struct { + LuaStr string `json:"lua"` +} + +func (r *HttpLuaRule) Name() string { + return "HttpLuaRule" +} + +func (r *HttpLuaRule) Verify(resp *resty.Response) bool { + L.SetGlobal("codeStr", lua.LString(strconv.Itoa(resp.StatusCode()))) + L.SetGlobal("bodyStr", lua.LString(resp.String())) + + source := r.LuaStr + + ` +r = HttpResp.new(codeStr, bodyStr); +return verify(r); +` + if err := runLuaStr(source); err != nil { + zaplog.Error("HttpLuaRule-Verify", + zap.Int("status", resp.StatusCode()), + zap.String("body", resp.String()), + zap.String("LuaStr", r.LuaStr), + zap.Error(err)) + return false + } + lv := L.Get(-1) + return lv == lua.LTrue +} + +func runLuaStr(source string) error { + LuaVMLock.Lock() + defer LuaVMLock.Unlock() + + return L.DoString(source) +} diff --git a/internal/rule/http_lua_rule_test.go b/internal/rule/http_lua_rule_test.go index 63ed38e..322fe59 100644 --- a/internal/rule/http_lua_rule_test.go +++ b/internal/rule/http_lua_rule_test.go @@ -1 +1,43 @@ package rule + +import ( + "github.com/go-resty/resty/v2" + "github.com/stretchr/testify/assert" + lua "github.com/yuin/gopher-lua" + "testing" +) + +func TestLua(t *testing.T) { + if err := runLuaStr(` + function verify(r) + local json = require "json"; + local car = json.decode(r:body()); + return r:code() == "200" and car.age == 10 and car.name == "buick"; + end + r = HttpResp.new("200", "{\"age\": 10,\"name\": \"buick\"}") + return verify(r) + `); err != nil { + panic(err) + } + + result := false + lv := L.Get(-1) + if lv == lua.LTrue { + result = true + } + assert.True(t, result) +} + +func TestHttpLuaRule(t *testing.T) { + luaStr := ` +function verify(r) + local json = require "json"; + local person = json.decode(r:body()); + return person.age == 10 and person.name == "John"; +end +` + var resp resty.Response + resp.SetBody([]byte(`{"age": 10, "name": "John"}`)) + rule := HttpLuaRule{LuaStr: luaStr} + assert.True(t, rule.Verify(&resp)) +}