Better SET/PERSIST/TTL/STATS tests

This commit is contained in:
tidwall 2022-09-23 15:29:46 -07:00
parent 7fa2dc4419
commit 295a9c45a8
6 changed files with 206 additions and 161 deletions

View File

@ -2,7 +2,7 @@ package server
import ( import (
"bytes" "bytes"
"errors" "math"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -706,23 +706,22 @@ func (s *Server) cmdSET(msg *Message) (resp.Value, commandDetails, error) {
return retwerr(errInvalidArgument(args[i])) return retwerr(errInvalidArgument(args[i]))
} }
} }
if oobj == nil {
return retwerr(errInvalidNumberOfArguments)
}
// >> Operation // >> Operation
nada := func() (resp.Value, commandDetails, error) { nada := func() (resp.Value, commandDetails, error) {
// exclude operation due to 'xx' or 'nx' match // exclude operation due to 'xx' or 'nx' match
switch msg.OutputType { if msg.OutputType == JSON {
default:
case JSON:
if nx { if nx {
return retwerr(errIDAlreadyExists) return retwerr(errIDAlreadyExists)
} else { } else {
return retwerr(errIDNotFound) return retwerr(errIDNotFound)
} }
case RESP:
return resp.NullValue(), commandDetails{}, nil
} }
return retwerr(errors.New("nada unknown output")) return resp.NullValue(), commandDetails{}, nil
} }
col, ok := s.cols.Get(key) col, ok := s.cols.Get(key)
@ -931,11 +930,17 @@ func (s *Server) cmdEXPIRE(msg *Message) (resp.Value, commandDetails, error) {
// PERSIST key id // PERSIST key id
func (s *Server) cmdPERSIST(msg *Message) (resp.Value, commandDetails, error) { func (s *Server) cmdPERSIST(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now() start := time.Now()
// >> Args
args := msg.Args args := msg.Args
if len(args) != 3 { if len(args) != 3 {
return retwerr(errInvalidNumberOfArguments) return retwerr(errInvalidNumberOfArguments)
} }
key, id := args[1], args[2] key, id := args[1], args[2]
// >> Operation
col, _ := s.cols.Get(key) col, _ := s.cols.Get(key)
if col == nil { if col == nil {
if msg.OutputType == RESP { if msg.OutputType == RESP {
@ -959,6 +964,8 @@ func (s *Server) cmdPERSIST(msg *Message) (resp.Value, commandDetails, error) {
cleared = true cleared = true
} }
// >> Response
var res resp.Value var res resp.Value
var d commandDetails var d commandDetails
@ -985,62 +992,47 @@ func (s *Server) cmdPERSIST(msg *Message) (resp.Value, commandDetails, error) {
// TTL key id // TTL key id
func (s *Server) cmdTTL(msg *Message) (resp.Value, error) { func (s *Server) cmdTTL(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
// >> Args
args := msg.Args args := msg.Args
if len(args) != 3 { if len(args) != 3 {
return retrerr(errInvalidNumberOfArguments) return retrerr(errInvalidNumberOfArguments)
} }
key, id := args[1], args[2] key, id := args[1], args[2]
var v float64
var ok bool // >> Operation
var ok2 bool
col, _ := s.cols.Get(key) col, _ := s.cols.Get(key)
if col != nil { if col == nil {
o := col.Get(id) if msg.OutputType == JSON {
ok = o != nil return retrerr(errKeyNotFound)
if ok {
if o.Expires() != 0 {
now := start.UnixNano()
if now > o.Expires() {
ok2 = false
} else {
v = float64(o.Expires()-now) / float64(time.Second)
if v < 0 {
v = 0
}
ok2 = true
}
}
} }
return resp.IntegerValue(-2), nil
} }
var res resp.Value
switch msg.OutputType { o := col.Get(id)
case JSON: if o == nil {
if ok { if msg.OutputType == JSON {
var ttl string
if ok2 {
ttl = strconv.FormatFloat(v, 'f', -1, 64)
} else {
ttl = "-1"
}
res = resp.SimpleStringValue(
`{"ok":true,"ttl":` + ttl + `,"elapsed":"` +
time.Since(start).String() + "\"}")
} else {
if col == nil {
return retrerr(errKeyNotFound)
}
return retrerr(errIDNotFound) return retrerr(errIDNotFound)
} }
case RESP: return resp.IntegerValue(-2), nil
if ok {
if ok2 {
res = resp.IntegerValue(int(v))
} else {
res = resp.IntegerValue(-1)
}
} else {
res = resp.IntegerValue(-2)
}
} }
return res, nil
var ttl float64
if o.Expires() == 0 {
ttl = -1
} else {
now := start.UnixNano()
ttl = math.Max(float64(o.Expires()-now)/float64(time.Second), 0)
}
// >> Response
if msg.OutputType == JSON {
return resp.SimpleStringValue(
`{"ok":true,"ttl":` + strconv.Itoa(int(ttl)) + `,"elapsed":"` +
time.Since(start).String() + "\"}"), nil
}
return resp.IntegerValue(int(ttl)), nil
} }

View File

@ -612,7 +612,7 @@ func (s *Server) commandInScript(msg *Message) (
case "ttl": case "ttl":
res, err = s.cmdTTL(msg) res, err = s.cmdTTL(msg)
case "stats": case "stats":
res, err = s.cmdStats(msg) res, err = s.cmdSTATS(msg)
case "scan": case "scan":
res, err = s.cmdScan(msg) res, err = s.cmdScan(msg)
case "nearby": case "nearby":

View File

@ -1080,7 +1080,7 @@ func (s *Server) command(msg *Message, client *Client) (
case "readonly": case "readonly":
res, err = s.cmdReadOnly(msg) res, err = s.cmdReadOnly(msg)
case "stats": case "stats":
res, err = s.cmdStats(msg) res, err = s.cmdSTATS(msg)
case "server": case "server":
res, err = s.cmdServer(msg) res, err = s.cmdServer(msg)
case "healthz": case "healthz":

View File

@ -45,22 +45,23 @@ func readMemStats() runtime.MemStats {
return ms return ms
} }
func (s *Server) cmdStats(msg *Message) (res resp.Value, err error) { // STATS key [key...]
func (s *Server) cmdSTATS(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Args[1:]
var ms = []map[string]interface{}{}
if len(vs) == 0 { // >> Args
return NOMessage, errInvalidNumberOfArguments
args := msg.Args
if len(args) < 2 {
return retrerr(errInvalidNumberOfArguments)
} }
// >> Operation
var vals []resp.Value var vals []resp.Value
var key string var ms = []map[string]interface{}{}
var ok bool for i := 1; i < len(args); i++ {
for { key := args[i]
vs, key, ok = tokenval(vs)
if !ok {
break
}
col, _ := s.cols.Get(key) col, _ := s.cols.Get(key)
if col != nil { if col != nil {
m := make(map[string]interface{}) m := make(map[string]interface{})
@ -83,18 +84,15 @@ func (s *Server) cmdStats(msg *Message) (res resp.Value, err error) {
} }
} }
} }
switch msg.OutputType {
case JSON:
data, err := json.Marshal(ms) // >> Response
if err != nil {
return NOMessage, err if msg.OutputType == JSON {
} data, _ := json.Marshal(ms)
res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Since(start).String() + "\"}") return resp.StringValue(`{"ok":true,"stats":` + string(data) +
case RESP: `,"elapsed":"` + time.Since(start).String() + "\"}"), nil
res = resp.ArrayValue(vals)
} }
return res, nil return resp.ArrayValue(vals), nil
} }
func (s *Server) cmdHealthz(msg *Message) (res resp.Value, err error) { func (s *Server) cmdHealthz(msg *Message) (res resp.Value, err error) {

View File

@ -249,94 +249,149 @@ func keys_KEYS_test(mc *mockServer) error {
}) })
} }
func keys_PERSIST_test(mc *mockServer) error { func keys_PERSIST_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"}, Do("SET", "mykey", "myid", "STRING", "value").OK(),
{"EXPIRE", "mykey", "myid", 2}, {1}, Do("EXPIRE", "mykey", "myid", 2).Str("1"),
{"PERSIST", "mykey", "myid"}, {1}, Do("PERSIST", "mykey", "myid").Str("1"),
{"PERSIST", "mykey", "myid"}, {0}, Do("PERSIST", "mykey", "myid").Str("0"),
}) Do("PERSIST", "mykey").Err("wrong number of arguments for 'persist' command"),
Do("PERSIST", "mykey2", "myid").Str("0"),
Do("PERSIST", "mykey2", "myid").JSON().Err("key not found"),
Do("PERSIST", "mykey", "myid2").Str("0"),
Do("PERSIST", "mykey", "myid2").JSON().Err("id not found"),
Do("EXPIRE", "mykey", "myid", 2).Str("1"),
Do("PERSIST", "mykey", "myid").JSON().OK(),
)
} }
func keys_SET_test(mc *mockServer) error { func keys_SET_test(mc *mockServer) error {
return mc.DoBatch( return mc.DoBatch(
"point", [][]interface{}{ // Section: point
{"SET", "mykey", "myid", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid", "POINT", 33, -115).OK(),
{"GET", "mykey", "myid", "POINT"}, {"[33 -115]"}, Do("GET", "mykey", "myid", "POINT").Str("[33 -115]"),
{"GET", "mykey", "myid", "BOUNDS"}, {"[[33 -115] [33 -115]]"}, Do("GET", "mykey", "myid", "BOUNDS").Str("[[33 -115] [33 -115]]"),
{"GET", "mykey", "myid", "OBJECT"}, {`{"type":"Point","coordinates":[-115,33]}`}, Do("GET", "mykey", "myid", "OBJECT").Str(`{"type":"Point","coordinates":[-115,33]}`),
{"GET", "mykey", "myid", "HASH", 7}, {"9my5xp7"}, Do("GET", "mykey", "myid", "HASH", 7).Str("9my5xp7"),
{"DEL", "mykey", "myid"}, {"1"}, Do("DEL", "mykey", "myid").Str("1"),
{"GET", "mykey", "myid"}, {nil}, Do("GET", "mykey", "myid").Str("<nil>"),
}, Do("SET", "mykey", "myid", "point", "33", "-112", "99").OK(),
"object", [][]interface{}{
{"SET", "mykey", "myid", "OBJECT", `{"type":"Point","coordinates":[-115,33]}`}, {"OK"}, // Section: object
{"GET", "mykey", "myid", "POINT"}, {"[33 -115]"}, Do("SET", "mykey", "myid", "OBJECT", `{"type":"Point","coordinates":[-115,33]}`).OK(),
{"GET", "mykey", "myid", "BOUNDS"}, {"[[33 -115] [33 -115]]"}, Do("GET", "mykey", "myid", "POINT").Str("[33 -115]"),
{"GET", "mykey", "myid", "OBJECT"}, {`{"type":"Point","coordinates":[-115,33]}`}, Do("GET", "mykey", "myid", "BOUNDS").Str("[[33 -115] [33 -115]]"),
{"GET", "mykey", "myid", "HASH", 7}, {"9my5xp7"}, Do("GET", "mykey", "myid", "OBJECT").Str(`{"type":"Point","coordinates":[-115,33]}`),
{"DEL", "mykey", "myid"}, {"1"}, Do("GET", "mykey", "myid", "HASH", 7).Str("9my5xp7"),
{"GET", "mykey", "myid"}, {nil}, Do("DEL", "mykey", "myid").Str("1"),
}, Do("GET", "mykey", "myid").Str("<nil>"),
"bounds", [][]interface{}{
{"SET", "mykey", "myid", "BOUNDS", 33, -115, 33, -115}, {"OK"}, // Section: bounds
{"GET", "mykey", "myid", "POINT"}, {"[33 -115]"}, Do("SET", "mykey", "myid", "BOUNDS", 33, -115, 33, -115).OK(),
{"GET", "mykey", "myid", "BOUNDS"}, {"[[33 -115] [33 -115]]"}, Do("GET", "mykey", "myid", "POINT").Str("[33 -115]"),
{"GET", "mykey", "myid", "OBJECT"}, {`{"type":"Point","coordinates":[-115,33]}`}, Do("GET", "mykey", "myid", "BOUNDS").Str("[[33 -115] [33 -115]]"),
{"GET", "mykey", "myid", "HASH", 7}, {"9my5xp7"}, Do("GET", "mykey", "myid", "OBJECT").Str(`{"type":"Point","coordinates":[-115,33]}`),
{"DEL", "mykey", "myid"}, {"1"}, Do("GET", "mykey", "myid", "HASH", 7).Str("9my5xp7"),
{"GET", "mykey", "myid"}, {nil}, Do("DEL", "mykey", "myid").Str("1"),
}, Do("GET", "mykey", "myid").Str("<nil>"),
"hash", [][]interface{}{
{"SET", "mykey", "myid", "HASH", "9my5xp7"}, {"OK"}, // Section: hash
{"GET", "mykey", "myid", "HASH", 7}, {"9my5xp7"}, Do("SET", "mykey", "myid", "HASH", "9my5xp7").OK(),
{"DEL", "mykey", "myid"}, {"1"}, Do("GET", "mykey", "myid", "HASH", 7).Str("9my5xp7"),
{"GET", "mykey", "myid"}, {nil}, Do("DEL", "mykey", "myid").Str("1"),
}, Do("GET", "mykey", "myid").Str("<nil>"),
"field", [][]interface{}{ Do("SET", "mykey", "myid", "HASH", "9my5xp7").JSON().OK(),
{"SET", "mykey", "myid", "FIELD", "f1", 33, "FIELD", "a2", 44.5, "HASH", "9my5xp7"}, {"OK"},
{"GET", "mykey", "myid", "WITHFIELDS", "HASH", 7}, {"[9my5xp7 [a2 44.5 f1 33]]"}, // Section: field
{"FSET", "mykey", "myid", "f1", 0}, {1}, Do("SET", "mykey", "myid", "FIELD", "f1", 33, "FIELD", "a2", 44.5, "HASH", "9my5xp7").OK(),
{"FSET", "mykey", "myid", "f1", 0}, {0}, Do("GET", "mykey", "myid", "WITHFIELDS", "HASH", 7).Str("[9my5xp7 [a2 44.5 f1 33]]"),
{"GET", "mykey", "myid", "WITHFIELDS", "HASH", 7}, {"[9my5xp7 [a2 44.5]]"}, Do("FSET", "mykey", "myid", "f1", 0).Str("1"),
{"DEL", "mykey", "myid"}, {"1"}, Do("FSET", "mykey", "myid", "f1", 0).Str("0"),
{"GET", "mykey", "myid"}, {nil}, Do("GET", "mykey", "myid", "WITHFIELDS", "HASH", 7).Str("[9my5xp7 [a2 44.5]]"),
}, Do("DEL", "mykey", "myid").Str("1"),
"string", [][]interface{}{ Do("GET", "mykey", "myid").Str("<nil>"),
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"},
{"GET", "mykey", "myid"}, {"value"}, // Section: string
{"SET", "mykey", "myid", "STRING", "value2"}, {"OK"}, Do("SET", "mykey", "myid", "STRING", "value").OK(),
{"GET", "mykey", "myid"}, {"value2"}, Do("GET", "mykey", "myid").Str("value"),
{"DEL", "mykey", "myid"}, {"1"}, Do("SET", "mykey", "myid", "STRING", "value2").OK(),
{"GET", "mykey", "myid"}, {nil}, Do("GET", "mykey", "myid").Str("value2"),
}, Do("DEL", "mykey", "myid").Str("1"),
Do("GET", "mykey", "myid").Str("<nil>"),
// Test error conditions
Do("CONFIG", "SET", "maxmemory", "1").OK(),
Do("SET", "mykey", "myid", "STRING", "value2").Err("OOM command not allowed when used memory > 'maxmemory'"),
Do("CONFIG", "SET", "maxmemory", "0").OK(),
Do("SET").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "FIELD", "f1").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "FIELD", "z", "1").Err("invalid argument 'z'"),
Do("SET", "mykey", "myid", "EX").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "EX", "yyy").Err("invalid argument 'yyy'"),
Do("SET", "mykey", "myid", "EX", "123").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "nx").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "nx", "xx").Err("invalid argument 'xx'"),
Do("SET", "mykey", "myid", "xx", "nx").Err("invalid argument 'nx'"),
Do("SET", "mykey", "myid", "string").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "point").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "point", "33").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "point", "33f", "-112").Err("invalid argument '33f'"),
Do("SET", "mykey", "myid", "point", "33", "-112f").Err("invalid argument '-112f'"),
Do("SET", "mykey", "myid", "point", "33", "-112f", "99").Err("invalid argument '-112f'"),
Do("SET", "mykey", "myid", "bounds").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "bounds", "fff", "1", "2", "3").Err("invalid argument 'fff'"),
Do("SET", "mykey", "myid", "hash").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "object").Err("wrong number of arguments for 'set' command"),
Do("SET", "mykey", "myid", "object", "asd").Err("invalid data"),
Do("SET", "mykey", "myid", "joint").Err("invalid argument 'joint'"),
Do("SET", "mykey", "myid", "XX", "HASH", "9my5xp7").Err("<nil>"),
Do("SET", "mykey", "myid", "XX", "HASH", "9my5xp7").JSON().Err("id not found"),
Do("SET", "mykey", "myid1", "HASH", "9my5xp7").OK(),
Do("SET", "mykey", "myid", "XX", "HASH", "9my5xp7").Err("<nil>"),
Do("SET", "mykey", "myid", "NX", "HASH", "9my5xp7").OK(),
Do("SET", "mykey", "myid", "XX", "HASH", "9my5xp7").OK(),
Do("SET", "mykey", "myid", "NX", "HASH", "9my5xp7").Err("<nil>"),
Do("SET", "mykey", "myid", "NX", "HASH", "9my5xp7").JSON().Err("id already exists"),
) )
} }
func keys_STATS_test(mc *mockServer) error { func keys_STATS_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"STATS", "mykey"}, {"[nil]"}, Do("STATS", "mykey").Str("[nil]"),
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"}, Do("SET", "mykey", "myid", "STRING", "value").OK(),
{"STATS", "mykey"}, {"[[in_memory_size 9 num_objects 1 num_points 0 num_strings 1]]"}, Do("STATS", "mykey").Str("[[in_memory_size 9 num_objects 1 num_points 0 num_strings 1]]"),
{"SET", "mykey", "myid2", "STRING", "value"}, {"OK"}, Do("STATS", "mykey", "hello").JSON().Str(`{"ok":true,"stats":[{"in_memory_size":9,"num_objects":1,"num_points":0,"num_strings":1},null]}`),
{"STATS", "mykey"}, {"[[in_memory_size 19 num_objects 2 num_points 0 num_strings 2]]"}, Do("SET", "mykey", "myid2", "STRING", "value").OK(),
{"SET", "mykey", "myid3", "OBJECT", `{"type":"Point","coordinates":[-115,33]}`}, {"OK"}, Do("STATS", "mykey").Str("[[in_memory_size 19 num_objects 2 num_points 0 num_strings 2]]"),
{"STATS", "mykey"}, {"[[in_memory_size 40 num_objects 3 num_points 1 num_strings 2]]"}, Do("SET", "mykey", "myid3", "OBJECT", `{"type":"Point","coordinates":[-115,33]}`).OK(),
{"DEL", "mykey", "myid"}, {1}, Do("STATS", "mykey").Str("[[in_memory_size 40 num_objects 3 num_points 1 num_strings 2]]"),
{"STATS", "mykey"}, {"[[in_memory_size 31 num_objects 2 num_points 1 num_strings 1]]"}, Do("DEL", "mykey", "myid").Str("1"),
{"DEL", "mykey", "myid3"}, {1}, Do("STATS", "mykey").Str("[[in_memory_size 31 num_objects 2 num_points 1 num_strings 1]]"),
{"STATS", "mykey"}, {"[[in_memory_size 10 num_objects 1 num_points 0 num_strings 1]]"}, Do("DEL", "mykey", "myid3").Str("1"),
{"STATS", "mykey", "mykey2"}, {"[[in_memory_size 10 num_objects 1 num_points 0 num_strings 1] nil]"}, Do("STATS", "mykey").Str("[[in_memory_size 10 num_objects 1 num_points 0 num_strings 1]]"),
{"DEL", "mykey", "myid2"}, {1}, Do("STATS", "mykey", "mykey2").Str("[[in_memory_size 10 num_objects 1 num_points 0 num_strings 1] nil]"),
{"STATS", "mykey"}, {"[nil]"}, Do("DEL", "mykey", "myid2").Str("1"),
{"STATS", "mykey", "mykey2"}, {"[nil nil]"}, Do("STATS", "mykey").Str("[nil]"),
}) Do("STATS", "mykey", "mykey2").Str("[nil nil]"),
)
} }
func keys_TTL_test(mc *mockServer) error { func keys_TTL_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"}, Do("SET", "mykey", "myid", "STRING", "value").OK(),
{"EXPIRE", "mykey", "myid", 2}, {1}, Do("EXPIRE", "mykey", "myid", 2).Str("1"),
{time.Second / 4}, {}, // sleep Do("EXPIRE", "mykey", "myid", 2).JSON().OK(),
{"TTL", "mykey", "myid"}, {1}, Sleep(time.Millisecond*10),
}) Do("TTL", "mykey", "myid").Str("1"),
Do("EXPIRE", "mykey", "myid", 1).Str("1"),
Sleep(time.Millisecond*10),
Do("TTL", "mykey", "myid").Str("0"),
Do("TTL", "mykey", "myid").JSON().Str(`{"ok":true,"ttl":0}`),
Do("TTL", "mykey2", "myid").Str("-2"),
Do("TTL", "mykey", "myid2").Str("-2"),
Do("TTL", "mykey").Err("wrong number of arguments for 'ttl' command"),
Do("SET", "mykey", "myid", "STRING", "value").OK(),
Do("TTL", "mykey", "myid").Str("-1"),
Do("TTL", "mykey2", "myid").JSON().Err("key not found"),
Do("TTL", "mykey", "myid2").JSON().Err("id not found"),
)
} }
func keys_SET_EX_test(mc *mockServer) (err error) { func keys_SET_EX_test(mc *mockServer) (err error) {
@ -465,7 +520,7 @@ func keys_FLUSHDB_test(mc *mockServer) error {
Do("SET", "mykey2", "myid1", "POINT", 33, -115).OK(), Do("SET", "mykey2", "myid1", "POINT", 33, -115).OK(),
Do("SETCHAN", "mychan", "INTERSECTS", "mykey1", "BOUNDS", 10, 10, 10, 10).Str("1"), Do("SETCHAN", "mychan", "INTERSECTS", "mykey1", "BOUNDS", 10, 10, 10, 10).Str("1"),
Do("KEYS", "*").Str("[mykey1 mykey2]"), Do("KEYS", "*").Str("[mykey1 mykey2]"),
Do("CHANS", "*").JSON().Custom(func(s string) error { Do("CHANS", "*").JSON().Func(func(s string) error {
if gjson.Get(s, "chans.#").Int() != 1 { if gjson.Get(s, "chans.#").Int() != 1 {
return fmt.Errorf("expected '%d', got '%d'", 1, gjson.Get(s, "chans.#").Int()) return fmt.Errorf("expected '%d', got '%d'", 1, gjson.Get(s, "chans.#").Int())
} }

View File

@ -34,7 +34,7 @@ func (cmd *IO) Str(s string) *IO {
cmd.out = s cmd.out = s
return cmd return cmd
} }
func (cmd *IO) Custom(fn func(s string) error) *IO { func (cmd *IO) Func(fn func(s string) error) *IO {
cmd.out = func(s string) error { cmd.out = func(s string) error {
if cmd.json { if cmd.json {
if !gjson.Valid(s) { if !gjson.Valid(s) {
@ -47,7 +47,7 @@ func (cmd *IO) Custom(fn func(s string) error) *IO {
} }
func (cmd *IO) OK() *IO { func (cmd *IO) OK() *IO {
return cmd.Custom(func(s string) error { return cmd.Func(func(s string) error {
if cmd.json { if cmd.json {
if gjson.Get(s, "ok").Type != gjson.True { if gjson.Get(s, "ok").Type != gjson.True {
return errors.New("not ok") return errors.New("not ok")
@ -60,7 +60,7 @@ func (cmd *IO) OK() *IO {
} }
func (cmd *IO) Err(msg string) *IO { func (cmd *IO) Err(msg string) *IO {
return cmd.Custom(func(s string) error { return cmd.Func(func(s string) error {
if cmd.json { if cmd.json {
if gjson.Get(s, "ok").Type != gjson.False { if gjson.Get(s, "ok").Type != gjson.False {
return errors.New("ok=true") return errors.New("ok=true")