From a7ac97803fafd30b1a05bd9db2ea086c805c40fc Mon Sep 17 00:00:00 2001 From: Harmen Date: Wed, 31 Dec 2025 10:24:33 +0100 Subject: [PATCH 1/5] update redis to 8.4.0 --- README.md | 2 +- integration/get_redis.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8216d0cd..927c0b56 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/integration/get_redis.sh b/integration/get_redis.sh index 3a6e5d4c..b8e514c6 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/ From 559b62d349fae2b124745d7aa424813c5313dce7 Mon Sep 17 00:00:00 2001 From: Harmen Date: Wed, 31 Dec 2025 10:54:16 +0100 Subject: [PATCH 2/5] update HELLO --- cmd_connection.go | 15 ++++++++++++--- cmd_connection_test.go | 12 +++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/cmd_connection.go b/cmd_connection.go index e403c949..b4ec55d7 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 dd89468a..f82ae4d4 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, From df024c82fcee76c360fe967a80e5066166af5bb8 Mon Sep 17 00:00:00 2001 From: Harmen Date: Wed, 7 Jan 2026 10:19:56 +0100 Subject: [PATCH 3/5] looks like nested is allowed now --- cmd_stream.go | 12 ------------ integration/script_test.go | 10 ---------- 2 files changed, 22 deletions(-) diff --git a/cmd_stream.go b/cmd_stream.go index aafc014c..f2cccbb5 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/integration/script_test.go b/integration/script_test.go index 51cf13fa..ec65174f 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") From ce80759f11ed0aa0892b0214116097a57d7383e4 Mon Sep 17 00:00:00 2001 From: Harmen Date: Wed, 14 Jan 2026 09:19:33 +0100 Subject: [PATCH 4/5] update bitcount test --- cmd_string.go | 4 ++++ integration/string_test.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd_string.go b/cmd_string.go index 19c35435..217de8f7 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/string_test.go b/integration/string_test.go index 60828404..dc99b2c7 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") From 4f267aceb563fed36cd4d41fe37d1772039aea14 Mon Sep 17 00:00:00 2001 From: Harmen Date: Wed, 14 Jan 2026 09:34:21 +0100 Subject: [PATCH 5/5] tiny os.* module --- cmd_scripting.go | 1 + cmd_scripting_test.go | 67 ++++++++++++++++++++------------------ integration/script_test.go | 5 +-- lua.go | 11 +++++++ 4 files changed, 51 insertions(+), 33 deletions(-) diff --git a/cmd_scripting.go b/cmd_scripting.go index 02ca0384..32705b85 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 005c2848..6f063ccb 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/integration/script_test.go b/integration/script_test.go index ec65174f..07de158d 100644 --- a/integration/script_test.go +++ b/integration/script_test.go @@ -193,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/lua.go b/lua.go index 2ad34a53..29f3aeae 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 + }, + } +}