diff --git a/README.md b/README.md index 8216d0c..927c0b5 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,7 @@ Commands which will probably not be implemented: ## &c. -Integration tests are run against Redis 7.2.4. The [./integration](./integration/) subdir +Integration tests are run against Redis 8.4.0. The [./integration](./integration/) subdir compares miniredis against a real redis instance. The Redis 6 RESP3 protocol is supported. If there are problems, please open diff --git a/cmd_connection.go b/cmd_connection.go index e403c94..b4ec55d 100644 --- a/cmd_connection.go +++ b/cmd_connection.go @@ -177,7 +177,7 @@ func (m *Miniredis) cmdHello(c *server.Peer, cmd string, args []string) { c.WriteBulk("server") c.WriteBulk("miniredis") c.WriteBulk("version") - c.WriteBulk("6.0.5") + c.WriteBulk("8.4.0") c.WriteBulk("proto") c.WriteInt(opts.version) c.WriteBulk("id") @@ -186,8 +186,17 @@ func (m *Miniredis) cmdHello(c *server.Peer, cmd string, args []string) { c.WriteBulk("standalone") c.WriteBulk("role") c.WriteBulk("master") - c.WriteBulk("modules") - c.WriteLen(0) + c.WriteBulk("modules") // "modules": [ + c.WriteLen(1) // we have 1: "vectorset" + c.WriteMapLen(4) // { + c.WriteBulk("name") // + c.WriteBulk("vectorset") // + c.WriteBulk("ver") // + c.WriteInt(1) // + c.WriteBulk("path") // + c.WriteBulk("") // + c.WriteBulk("args") // + c.WriteLen(0) // ]} end modules } // ECHO diff --git a/cmd_connection_test.go b/cmd_connection_test.go index dd89468..f82ae4d 100644 --- a/cmd_connection_test.go +++ b/cmd_connection_test.go @@ -236,15 +236,21 @@ func TestSetError(t *testing.T) { func TestHello(t *testing.T) { t.Run("default user", func(t *testing.T) { s, c := runWithClient(t) - payl := proto.Map( proto.String("server"), proto.String("miniredis"), - proto.String("version"), proto.String("6.0.5"), + proto.String("version"), proto.String("8.4.0"), proto.String("proto"), proto.Int(3), proto.String("id"), proto.Int(42), proto.String("mode"), proto.String("standalone"), proto.String("role"), proto.String("master"), - proto.String("modules"), proto.Array(), + proto.String("modules"), proto.Array( + proto.Map( + proto.String("name"), proto.String("vectorset"), + proto.String("ver"), proto.Int(1), + proto.String("path"), proto.String(""), + proto.String("args"), proto.Array(), + ), + ), ) mustDo(t, c, diff --git a/cmd_scripting.go b/cmd_scripting.go index 02ca038..32705b8 100644 --- a/cmd_scripting.go +++ b/cmd_scripting.go @@ -97,6 +97,7 @@ func (m *Miniredis) runLuaScript(c *server.Peer, sha, script string, readOnly bo l.Push(mod) return 1 })) + l.RegisterModule("os", mkLuaOS()) _ = doScript(l, protectGlobals) diff --git a/cmd_scripting_test.go b/cmd_scripting_test.go index 005c284..6f063cc 100644 --- a/cmd_scripting_test.go +++ b/cmd_scripting_test.go @@ -169,40 +169,45 @@ func TestScript(t *testing.T) { "SCRIPT", "FOO", proto.Error("ERR unknown subcommand 'FOO'. Try SCRIPT HELP."), ) -} -func TestCJSON(t *testing.T) { - _, c := runWithClient(t) + t.Run("CJSON", func(t *testing.T) { + mustDo(t, c, + "EVAL", `return cjson.decode('{"id":"foo"}')['id']`, "0", + proto.String("foo"), + ) + mustDo(t, c, + "EVAL", `return cjson.encode({foo=42})`, "0", + proto.String(`{"foo":42}`), + ) - mustDo(t, c, - "EVAL", `return cjson.decode('{"id":"foo"}')['id']`, "0", - proto.String("foo"), - ) - mustDo(t, c, - "EVAL", `return cjson.encode({foo=42})`, "0", - proto.String(`{"foo":42}`), - ) + mustContain(t, c, + "EVAL", `redis.encode()`, "0", + "Error compiling script", + ) + mustContain(t, c, + "EVAL", `redis.encode("1", "2")`, "0", + "Error compiling script", + ) + mustContain(t, c, + "EVAL", `redis.decode()`, "0", + "Error compiling script", + ) + mustContain(t, c, + "EVAL", `redis.decode("{")`, "0", + "Error compiling script", + ) + mustContain(t, c, + "EVAL", `redis.decode("1", "2")`, "0", + "Error compiling script", + ) + }) - mustContain(t, c, - "EVAL", `redis.encode()`, "0", - "Error compiling script", - ) - mustContain(t, c, - "EVAL", `redis.encode("1", "2")`, "0", - "Error compiling script", - ) - mustContain(t, c, - "EVAL", `redis.decode()`, "0", - "Error compiling script", - ) - mustContain(t, c, - "EVAL", `redis.decode("{")`, "0", - "Error compiling script", - ) - mustContain(t, c, - "EVAL", `redis.decode("1", "2")`, "0", - "Error compiling script", - ) + t.Run("os.", func(t *testing.T) { + mustDo(t, c, + "EVAL", `return os.clock()`, "0", + proto.Int(42), + ) + }) } func TestLog(t *testing.T) { diff --git a/cmd_stream.go b/cmd_stream.go index aafc014..f2cccbb 100644 --- a/cmd_stream.go +++ b/cmd_stream.go @@ -753,12 +753,6 @@ parsing: c, opts.blockTimeout, func(c *server.Peer, ctx *connCtx) bool { - if ctx.nested { - setDirty(c) - c.WriteError("ERR XREADGROUP command is not allowed with BLOCK option from scripts") - return false - } - db := m.db(ctx.selectedDB) res, err := xreadgroup( db, @@ -969,12 +963,6 @@ parsing: c, opts.blockTimeout, func(c *server.Peer, ctx *connCtx) bool { - if ctx.nested { - setDirty(c) - c.WriteError("ERR XREAD command is not allowed with BLOCK option from scripts") - return false - } - db := m.db(ctx.selectedDB) res := xread(db, opts.streams, opts.ids, opts.count) if len(res) == 0 { diff --git a/cmd_string.go b/cmd_string.go index 19c3543..217de8f 100644 --- a/cmd_string.go +++ b/cmd_string.go @@ -804,6 +804,10 @@ func (m *Miniredis) cmdBitcount(c *server.Peer, cmd string, args []string) { } args = args[2:] } + if len(args) != 0 { + c.WriteError(msgSyntaxError) + return + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) diff --git a/integration/get_redis.sh b/integration/get_redis.sh index 3a6e5d4..b8e514c 100755 --- a/integration/get_redis.sh +++ b/integration/get_redis.sh @@ -2,7 +2,7 @@ set -eu -VERSION=7.2.4 +VERSION=8.4.0 cd "$(dirname "$0")" rm -rf ./redis_src/ diff --git a/integration/script_test.go b/integration/script_test.go index 51cf13f..07de158 100644 --- a/integration/script_test.go +++ b/integration/script_test.go @@ -110,16 +110,6 @@ func TestScript(t *testing.T) { }) }) - t.Run("blocking", func(t *testing.T) { - testRaw(t, func(c *client) { - c.Do("XADD", "pl", "0-1", "name", "Mercury") - c.Do("EVAL", `redis.call("XINFO", "STREAM", "pl")`, "0") - c.Do("EVAL", `redis.call("XREAD", "STREAMS", "pl", "$")`, "0") - c.Error("not allowed with BLOCK option", "EVAL", `redis.call("XREAD", "BLOCK", "10", "STREAMS", "pl", "$")`, "0") - c.Error("not allowed with BLOCK option", "EVAL", `redis.call("XREADGROUP", "GROUP", "group", "consumer", "BLOCK", 1000, "STREAMS", "pl", ">")`, "0") - }) - }) - t.Run("setresp", func(t *testing.T) { testRaw(t, func(c *client) { c.Do("EVAL", `redis.setresp(3); redis.call("SET", "foo", 12); return redis.call("GET", "foo")`, "0") @@ -203,8 +193,9 @@ func TestLua(t *testing.T) { testRaw(t, func(c *client) { // c.Do("EVAL", "print(1)", "0") c.Do("EVAL", `return string.format('%q', "pretty string")`, "0") - c.Error("Script attempted to access nonexistent global variable", "EVAL", "os.clock()", "0") - c.Error("Script attempted to access nonexistent global variable", "EVAL", "os.exit(42)", "0") + c.Error("Script attempted to access nonexistent global variable", "EVAL", "foob.clock()", "0") + c.DoLoosely("EVAL", "os.clock()", "0") + c.Error("attempt to call", "EVAL", "os.exit(42)", "0") c.Do("EVAL", "return table.concat({1,2,3})", "0") c.Do("EVAL", "return math.abs(-42)", "0") c.Error("Script attempted to access nonexistent global variable", "EVAL", `return utf8.len("hello world")`, "0") diff --git a/integration/string_test.go b/integration/string_test.go index 6082840..dc99b2c 100644 --- a/integration/string_test.go +++ b/integration/string_test.go @@ -453,7 +453,7 @@ func TestBitcount(t *testing.T) { c.Error("out of range", "BITCOUNT", "A", "0", "9223372036854775808") c.Error("wrong number", "BITCOUNT") - c.Do("BITCOUNT", "wrong", "arguments") + c.Error("syntax error", "BITCOUNT", "wrong", "arguments") c.Error("syntax error", "BITCOUNT", "str", "4", "2", "2", "2", "2") c.Error("not an integer", "BITCOUNT", "str", "foo", "2") c.Do("HSET", "aap", "noot", "mies") diff --git a/lua.go b/lua.go index 2ad34a5..29f3aea 100644 --- a/lua.go +++ b/lua.go @@ -299,3 +299,14 @@ func luaStatusReply(msg string) *lua.LTable { tab.RawSetString("ok", lua.LString(msg)) return tab } + +// Our very minimal "os." lua lib. +func mkLuaOS() map[string]lua.LGFunction { + return map[string]lua.LGFunction{ + // > Returns an approximation of the amount in seconds of CPU time used by the program + "clock": func(l *lua.LState) int { + l.Push(lua.LNumber(42)) + return 1 + }, + } +}