diff --git a/internal/server/scripts.go b/internal/server/scripts.go index acbb3310..ed267e76 100644 --- a/internal/server/scripts.go +++ b/internal/server/scripts.go @@ -640,7 +640,7 @@ func (s *Server) commandInScript(msg *Message) ( case "test": res, err = s.cmdTest(msg) case "server": - res, err = s.cmdServer(msg) + res, err = s.cmdSERVER(msg) } s.sendMonitor(err, msg, nil, true) return diff --git a/internal/server/server.go b/internal/server/server.go index deb764ee..d7c6a399 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1082,7 +1082,7 @@ func (s *Server) command(msg *Message, client *Client) ( case "stats": res, err = s.cmdSTATS(msg) case "server": - res, err = s.cmdServer(msg) + res, err = s.cmdSERVER(msg) case "healthz": res, err = s.cmdHEALTHZ(msg) case "info": diff --git a/internal/server/stats.go b/internal/server/stats.go index 0feb361d..12187016 100644 --- a/internal/server/stats.go +++ b/internal/server/stats.go @@ -125,35 +125,41 @@ func (s *Server) cmdHEALTHZ(msg *Message) (resp.Value, error) { return resp.SimpleStringValue("OK"), nil } -func (s *Server) cmdServer(msg *Message) (res resp.Value, err error) { +// SERVER [ext] +func (s *Server) cmdSERVER(msg *Message) (resp.Value, error) { start := time.Now() + + // >> Args + + args := msg.Args + var ext bool + for i := 1; i < len(args); i++ { + switch strings.ToLower(args[i]) { + case "ext": + ext = true + default: + return retrerr(errInvalidArgument(args[i])) + } + } + + // >> Operation + m := make(map[string]interface{}) - args := msg.Args[1:] - // Switch on the type of stats requested - switch len(args) { - case 0: + if ext { + s.extStats(m) + } else { s.basicStats(m) - case 1: - if strings.ToLower(args[0]) == "ext" { - s.extStats(m) - } - default: - return NOMessage, errInvalidNumberOfArguments } - switch msg.OutputType { - case JSON: - data, err := json.Marshal(m) - if err != nil { - return NOMessage, err - } - res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Since(start).String() + "\"}") - case RESP: - vals := respValuesSimpleMap(m) - res = resp.ArrayValue(vals) + // >> Response + + if msg.OutputType == JSON { + data, _ := json.Marshal(m) + return resp.StringValue(`{"ok":true,"stats":` + string(data) + + `,"elapsed":"` + time.Since(start).String() + "\"}"), nil } - return res, nil + return resp.ArrayValue(respValuesSimpleMap(m)), nil } // basicStats populates the passed map with basic system/go/tile38 statistics diff --git a/tests/keys_test.go b/tests/keys_test.go index 0b3217a6..2bfec979 100644 --- a/tests/keys_test.go +++ b/tests/keys_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math/rand" + "strings" "testing" "time" @@ -33,6 +34,7 @@ func subTestKeys(t *testing.T, mc *mockServer) { runStep(t, mc, "TYPE", keys_TYPE_test) runStep(t, mc, "FLUSHDB", keys_FLUSHDB_test) runStep(t, mc, "HEALTHZ", keys_HEALTHZ_test) + runStep(t, mc, "SERVER", keys_SERVER_test) } @@ -480,33 +482,32 @@ func keys_PDEL_test(mc *mockServer) error { } func keys_WHEREIN_test(mc *mockServer) error { - return mc.DoBatch([][]interface{}{ - {"SET", "mykey", "myid_a1", "FIELD", "a", 1, "POINT", 33, -115}, {"OK"}, - {"WITHIN", "mykey", "WHEREIN", "a", 3, 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, - {"WITHIN", "mykey", "WHEREIN", "a", "a", 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {"ERR invalid argument 'a'"}, - {"WITHIN", "mykey", "WHEREIN", "a", 1, 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {"ERR invalid argument '1'"}, - {"WITHIN", "mykey", "WHEREIN", "a", 3, 0, "a", 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {"[0 []]"}, - {"WITHIN", "mykey", "WHEREIN", "a", 4, 0, "a", 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, - {"SET", "mykey", "myid_a2", "FIELD", "a", 2, "POINT", 32.99, -115}, {"OK"}, - {"SET", "mykey", "myid_a3", "FIELD", "a", 3, "POINT", 33, -115.02}, {"OK"}, - {"WITHIN", "mykey", "WHEREIN", "a", 3, 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, { - `[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, + return mc.DoBatch( + Do("SET", "mykey", "myid_a1", "FIELD", "a", 1, "POINT", 33, -115).OK(), + Do("WITHIN", "mykey", "WHEREIN", "a", 3, 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str(`[0 [[myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`), + Do("WITHIN", "mykey", "WHEREIN", "a", "a", 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Err("invalid argument 'a'"), + Do("WITHIN", "mykey", "WHEREIN", "a", 1, 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Err("invalid argument '1'"), + Do("WITHIN", "mykey", "WHEREIN", "a", 3, 0, "a", 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str("[0 []]"), + Do("WITHIN", "mykey", "WHEREIN", "a", 4, 0, "a", 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str(`[0 [[myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`), + Do("SET", "mykey", "myid_a2", "FIELD", "a", 2, "POINT", 32.99, -115).OK(), + Do("SET", "mykey", "myid_a3", "FIELD", "a", 3, "POINT", 33, -115.02).OK(), + Do("WITHIN", "mykey", "WHEREIN", "a", 3, 0, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str(`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`), // zero value should not match 1 and 2 - {"SET", "mykey", "myid_a0", "FIELD", "a", 0, "POINT", 33, -115.02}, {"OK"}, - {"WITHIN", "mykey", "WHEREIN", "a", 2, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, - }) + Do("SET", "mykey", "myid_a0", "FIELD", "a", 0, "POINT", 33, -115.02).OK(), + Do("WITHIN", "mykey", "WHEREIN", "a", 2, 1, 2, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str(`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`), + ) } func keys_WHEREEVAL_test(mc *mockServer) error { - return mc.DoBatch([][]interface{}{ - {"SET", "mykey", "myid_a1", "FIELD", "a", 1, "POINT", 33, -115}, {"OK"}, - {"WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1])", 1, 0.5, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, - {"WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1])", "a", 0.5, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {"ERR invalid argument 'a'"}, - {"WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1])", 1, 0.5, 4, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {"ERR invalid argument '4'"}, - {"SET", "mykey", "myid_a2", "FIELD", "a", 2, "POINT", 32.99, -115}, {"OK"}, - {"SET", "mykey", "myid_a3", "FIELD", "a", 3, "POINT", 33, -115.02}, {"OK"}, - {"WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1]) and FIELDS.a ~= tonumber(ARGV[2])", 2, 0.5, 3, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, - }) + return mc.DoBatch( + Do("SET", "mykey", "myid_a1", "FIELD", "a", 1, "POINT", 33, -115).OK(), + Do("WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1])", 1, 0.5, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str(`[0 [[myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`), + Do("WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1])", "a", 0.5, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Err("invalid argument 'a'"), + Do("WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1])", 1, 0.5, 4, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Err("invalid argument '4'"), + Do("SET", "mykey", "myid_a2", "FIELD", "a", 2, "POINT", 32.99, -115).OK(), + Do("SET", "mykey", "myid_a3", "FIELD", "a", 3, "POINT", 33, -115.02).OK(), + Do("WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1]) and FIELDS.a ~= tonumber(ARGV[2])", 2, 0.5, 3, "BOUNDS", 32.8, -115.2, 33.2, -114.8).Str(`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`), + ) } func keys_TYPE_test(mc *mockServer) error { @@ -573,3 +574,50 @@ func keys_HEALTHZ_test(mc *mockServer) error { Do("HEALTHZ", "arg").Err(`wrong number of arguments for 'healthz' command`), ) } + +func keys_SERVER_test(mc *mockServer) error { + return mc.DoBatch( + Do("SERVER").Func(func(s string) error { + valid := strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") && + strings.Contains(s, "cpus") && strings.Contains(s, "mem_alloc") + if !valid { + return errors.New("looks invalid") + } + return nil + }), + Do("SERVER").JSON().Func(func(s string) error { + if !gjson.Get(s, "ok").Bool() { + return errors.New("not ok") + } + valid := gjson.Get(s, "stats.cpus").Exists() && + gjson.Get(s, "stats.mem_alloc").Exists() + if !valid { + return errors.New("looks invalid") + } + return nil + }), + Do("SERVER", "ext").Func(func(s string) error { + valid := strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") && + strings.Contains(s, "sys_cpus") && + strings.Contains(s, "tile38_connected_clients") + + if !valid { + return errors.New("looks invalid") + } + return nil + }), + Do("SERVER", "ext").JSON().Func(func(s string) error { + if !gjson.Get(s, "ok").Bool() { + return errors.New("not ok") + } + valid := gjson.Get(s, "stats.sys_cpus").Exists() && + gjson.Get(s, "stats.tile38_connected_clients").Exists() + if !valid { + return errors.New("looks invalid") + } + return nil + }), + Do("SERVER", "ett").Err(`invalid argument 'ett'`), + Do("SERVER", "ett").JSON().Err(`invalid argument 'ett'`), + ) +}