From dcacc00e3750300617ba6e16eb346713f91a783a Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Wed, 21 Jan 2026 22:52:54 +0100 Subject: Remove testing data --- examples/redis-unstable/tests/unit/acl-v2.tcl | 551 ---- examples/redis-unstable/tests/unit/acl.tcl | 1311 -------- examples/redis-unstable/tests/unit/aofrw.tcl | 232 -- examples/redis-unstable/tests/unit/auth.tcl | 126 - examples/redis-unstable/tests/unit/bitfield.tcl | 263 -- examples/redis-unstable/tests/unit/bitops.tcl | 706 ----- .../redis-unstable/tests/unit/client-eviction.tcl | 623 ---- .../tests/unit/cluster/announced-endpoints.tcl | 75 - .../tests/unit/cluster/atomic-slot-migration.tcl | 3063 ------------------ examples/redis-unstable/tests/unit/cluster/cli.tcl | 415 --- .../tests/unit/cluster/cluster-response-tls.tcl | 110 - .../tests/unit/cluster/failure-marking.tcl | 53 - .../tests/unit/cluster/hostnames.tcl | 230 -- .../unit/cluster/human-announced-nodename.tcl | 29 - .../tests/unit/cluster/internal-secret.tcl | 71 - .../redis-unstable/tests/unit/cluster/links.tcl | 292 -- .../redis-unstable/tests/unit/cluster/misc.tcl | 36 - .../tests/unit/cluster/multi-slot-operations.tcl | 182 -- .../tests/unit/cluster/scripting.tcl | 91 - .../tests/unit/cluster/sharded-pubsub.tcl | 67 - .../tests/unit/cluster/slot-ownership.tcl | 61 - .../tests/unit/cluster/slot-stats.tcl | 1169 ------- examples/redis-unstable/tests/unit/dump.tcl | 440 --- examples/redis-unstable/tests/unit/expire.tcl | 972 ------ examples/redis-unstable/tests/unit/functions.tcl | 1259 -------- examples/redis-unstable/tests/unit/geo.tcl | 768 ----- examples/redis-unstable/tests/unit/hotkeys.tcl | 449 --- examples/redis-unstable/tests/unit/hyperloglog.tcl | 374 --- .../redis-unstable/tests/unit/info-command.tcl | 62 - .../redis-unstable/tests/unit/info-keysizes.tcl | 764 ----- examples/redis-unstable/tests/unit/info.tcl | 604 ---- .../redis-unstable/tests/unit/introspection-2.tcl | 281 -- .../redis-unstable/tests/unit/introspection.tcl | 1101 ------- examples/redis-unstable/tests/unit/keyspace.tcl | 557 ---- .../redis-unstable/tests/unit/latency-monitor.tcl | 189 -- examples/redis-unstable/tests/unit/lazyfree.tcl | 200 -- examples/redis-unstable/tests/unit/limits.tcl | 21 - examples/redis-unstable/tests/unit/maxmemory.tcl | 709 ----- .../redis-unstable/tests/unit/memefficiency.tcl | 1176 ------- .../tests/unit/moduleapi/aclcheck.tcl | 207 -- .../tests/unit/moduleapi/async_rm_call.tcl | 437 --- .../redis-unstable/tests/unit/moduleapi/auth.tcl | 90 - .../redis-unstable/tests/unit/moduleapi/basics.tcl | 70 - .../tests/unit/moduleapi/blockedclient.tcl | 310 -- .../tests/unit/moduleapi/blockonbackground.tcl | 124 - .../tests/unit/moduleapi/blockonkeys.tcl | 366 --- .../tests/unit/moduleapi/cluster.tcl | 227 -- .../tests/unit/moduleapi/cmdintrospection.tcl | 50 - .../tests/unit/moduleapi/commandfilter.tcl | 175 -- .../tests/unit/moduleapi/configaccess.tcl | 227 -- .../redis-unstable/tests/unit/moduleapi/crash.tcl | 129 - .../tests/unit/moduleapi/datatype.tcl | 238 -- .../tests/unit/moduleapi/datatype2.tcl | 232 -- .../redis-unstable/tests/unit/moduleapi/defrag.tcl | 101 - .../tests/unit/moduleapi/eventloop.tcl | 28 - .../redis-unstable/tests/unit/moduleapi/fork.tcl | 41 - .../tests/unit/moduleapi/getchannels.tcl | 40 - .../tests/unit/moduleapi/getkeys.tcl | 89 - .../redis-unstable/tests/unit/moduleapi/hash.tcl | 176 -- .../redis-unstable/tests/unit/moduleapi/hooks.tcl | 321 -- .../tests/unit/moduleapi/infotest.tcl | 131 - .../redis-unstable/tests/unit/moduleapi/infra.tcl | 25 - .../tests/unit/moduleapi/internalsecret.tcl | 287 -- .../tests/unit/moduleapi/keymeta.tcl | 910 ------ .../tests/unit/moduleapi/keyspace_events.tcl | 140 - .../tests/unit/moduleapi/keyspecs.tcl | 160 - .../redis-unstable/tests/unit/moduleapi/list.tcl | 225 -- .../tests/unit/moduleapi/mallocsize.tcl | 21 - .../redis-unstable/tests/unit/moduleapi/misc.tcl | 617 ---- .../tests/unit/moduleapi/moduleauth.tcl | 405 --- .../tests/unit/moduleapi/moduleconfigs.tcl | 386 --- .../tests/unit/moduleapi/postnotifications.tcl | 219 -- .../tests/unit/moduleapi/propagate.tcl | 806 ----- .../tests/unit/moduleapi/publish.tcl | 34 - .../tests/unit/moduleapi/rdbloadsave.tcl | 203 -- .../redis-unstable/tests/unit/moduleapi/reply.tcl | 152 - .../redis-unstable/tests/unit/moduleapi/scan.tcl | 103 - .../redis-unstable/tests/unit/moduleapi/stream.tcl | 176 -- .../tests/unit/moduleapi/subcommands.tcl | 61 - .../tests/unit/moduleapi/test_lazyfree.tcl | 32 - .../tests/unit/moduleapi/testrdb.tcl | 311 -- .../redis-unstable/tests/unit/moduleapi/timer.tcl | 99 - .../tests/unit/moduleapi/usercall.tcl | 186 -- .../redis-unstable/tests/unit/moduleapi/zset.tcl | 121 - examples/redis-unstable/tests/unit/multi.tcl | 925 ------ examples/redis-unstable/tests/unit/networking.tcl | 446 --- examples/redis-unstable/tests/unit/obuf-limits.tcl | 240 -- .../redis-unstable/tests/unit/oom-score-adj.tcl | 144 - examples/redis-unstable/tests/unit/other.tcl | 733 ----- examples/redis-unstable/tests/unit/pause.tcl | 431 --- examples/redis-unstable/tests/unit/printver.tcl | 6 - examples/redis-unstable/tests/unit/protocol.tcl | 309 -- examples/redis-unstable/tests/unit/pubsub.tcl | 1016 ------ examples/redis-unstable/tests/unit/pubsubshard.tcl | 169 - examples/redis-unstable/tests/unit/querybuf.tcl | 179 -- examples/redis-unstable/tests/unit/quit.tcl | 33 - .../redis-unstable/tests/unit/replybufsize.tcl | 48 - examples/redis-unstable/tests/unit/scan.tcl | 505 --- examples/redis-unstable/tests/unit/scripting.tcl | 2688 ---------------- examples/redis-unstable/tests/unit/shutdown.tcl | 133 - examples/redis-unstable/tests/unit/slowlog.tcl | 251 -- examples/redis-unstable/tests/unit/sort.tcl | 417 --- examples/redis-unstable/tests/unit/tls.tcl | 219 -- examples/redis-unstable/tests/unit/tracking.tcl | 902 ------ .../tests/unit/type/hash-field-expire.tcl | 2508 --------------- examples/redis-unstable/tests/unit/type/hash.tcl | 962 ------ examples/redis-unstable/tests/unit/type/incr.tcl | 225 -- examples/redis-unstable/tests/unit/type/list-2.tcl | 47 - examples/redis-unstable/tests/unit/type/list-3.tcl | 232 -- examples/redis-unstable/tests/unit/type/list.tcl | 2500 --------------- examples/redis-unstable/tests/unit/type/set.tcl | 1314 -------- .../tests/unit/type/stream-cgroups.tcl | 3293 -------------------- examples/redis-unstable/tests/unit/type/stream.tcl | 3283 ------------------- examples/redis-unstable/tests/unit/type/string.tcl | 1556 --------- examples/redis-unstable/tests/unit/type/zset.tcl | 2761 ---------------- examples/redis-unstable/tests/unit/violations.tcl | 103 - examples/redis-unstable/tests/unit/wait.tcl | 528 ---- 117 files changed, 58276 deletions(-) delete mode 100644 examples/redis-unstable/tests/unit/acl-v2.tcl delete mode 100644 examples/redis-unstable/tests/unit/acl.tcl delete mode 100644 examples/redis-unstable/tests/unit/aofrw.tcl delete mode 100644 examples/redis-unstable/tests/unit/auth.tcl delete mode 100644 examples/redis-unstable/tests/unit/bitfield.tcl delete mode 100644 examples/redis-unstable/tests/unit/bitops.tcl delete mode 100644 examples/redis-unstable/tests/unit/client-eviction.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/announced-endpoints.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/atomic-slot-migration.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/cli.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/cluster-response-tls.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/failure-marking.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/hostnames.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/human-announced-nodename.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/internal-secret.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/links.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/misc.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/multi-slot-operations.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/scripting.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/sharded-pubsub.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/slot-ownership.tcl delete mode 100644 examples/redis-unstable/tests/unit/cluster/slot-stats.tcl delete mode 100644 examples/redis-unstable/tests/unit/dump.tcl delete mode 100644 examples/redis-unstable/tests/unit/expire.tcl delete mode 100644 examples/redis-unstable/tests/unit/functions.tcl delete mode 100644 examples/redis-unstable/tests/unit/geo.tcl delete mode 100644 examples/redis-unstable/tests/unit/hotkeys.tcl delete mode 100644 examples/redis-unstable/tests/unit/hyperloglog.tcl delete mode 100644 examples/redis-unstable/tests/unit/info-command.tcl delete mode 100644 examples/redis-unstable/tests/unit/info-keysizes.tcl delete mode 100644 examples/redis-unstable/tests/unit/info.tcl delete mode 100644 examples/redis-unstable/tests/unit/introspection-2.tcl delete mode 100644 examples/redis-unstable/tests/unit/introspection.tcl delete mode 100644 examples/redis-unstable/tests/unit/keyspace.tcl delete mode 100644 examples/redis-unstable/tests/unit/latency-monitor.tcl delete mode 100644 examples/redis-unstable/tests/unit/lazyfree.tcl delete mode 100644 examples/redis-unstable/tests/unit/limits.tcl delete mode 100644 examples/redis-unstable/tests/unit/maxmemory.tcl delete mode 100644 examples/redis-unstable/tests/unit/memefficiency.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/auth.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/basics.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/cluster.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/crash.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/datatype.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/defrag.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/fork.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/hash.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/hooks.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/infotest.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/infra.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/list.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/misc.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/propagate.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/publish.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/reply.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/scan.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/stream.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/timer.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/usercall.tcl delete mode 100644 examples/redis-unstable/tests/unit/moduleapi/zset.tcl delete mode 100644 examples/redis-unstable/tests/unit/multi.tcl delete mode 100644 examples/redis-unstable/tests/unit/networking.tcl delete mode 100644 examples/redis-unstable/tests/unit/obuf-limits.tcl delete mode 100644 examples/redis-unstable/tests/unit/oom-score-adj.tcl delete mode 100644 examples/redis-unstable/tests/unit/other.tcl delete mode 100644 examples/redis-unstable/tests/unit/pause.tcl delete mode 100644 examples/redis-unstable/tests/unit/printver.tcl delete mode 100644 examples/redis-unstable/tests/unit/protocol.tcl delete mode 100644 examples/redis-unstable/tests/unit/pubsub.tcl delete mode 100644 examples/redis-unstable/tests/unit/pubsubshard.tcl delete mode 100644 examples/redis-unstable/tests/unit/querybuf.tcl delete mode 100644 examples/redis-unstable/tests/unit/quit.tcl delete mode 100644 examples/redis-unstable/tests/unit/replybufsize.tcl delete mode 100644 examples/redis-unstable/tests/unit/scan.tcl delete mode 100644 examples/redis-unstable/tests/unit/scripting.tcl delete mode 100644 examples/redis-unstable/tests/unit/shutdown.tcl delete mode 100644 examples/redis-unstable/tests/unit/slowlog.tcl delete mode 100644 examples/redis-unstable/tests/unit/sort.tcl delete mode 100644 examples/redis-unstable/tests/unit/tls.tcl delete mode 100644 examples/redis-unstable/tests/unit/tracking.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/hash-field-expire.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/hash.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/incr.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/list-2.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/list-3.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/list.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/set.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/stream-cgroups.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/stream.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/string.tcl delete mode 100644 examples/redis-unstable/tests/unit/type/zset.tcl delete mode 100644 examples/redis-unstable/tests/unit/violations.tcl delete mode 100644 examples/redis-unstable/tests/unit/wait.tcl (limited to 'examples/redis-unstable/tests/unit') diff --git a/examples/redis-unstable/tests/unit/acl-v2.tcl b/examples/redis-unstable/tests/unit/acl-v2.tcl deleted file mode 100644 index b233118..0000000 --- a/examples/redis-unstable/tests/unit/acl-v2.tcl +++ /dev/null @@ -1,551 +0,0 @@ -start_server {tags {"acl external:skip"}} { - set r2 [redis_client] - test {Test basic multiple selectors} { - r ACL SETUSER selector-1 on -@all resetkeys nopass - $r2 auth selector-1 password - catch {$r2 ping} err - assert_match "*NOPERM*command*" $err - catch {$r2 set write::foo bar} err - assert_match "*NOPERM*command*" $err - catch {$r2 get read::foo} err - assert_match "*NOPERM*command*" $err - - r ACL SETUSER selector-1 (+@write ~write::*) (+@read ~read::*) - catch {$r2 ping} err - assert_equal "OK" [$r2 set write::foo bar] - assert_equal "" [$r2 get read::foo] - catch {$r2 get write::foo} err - assert_match "*NOPERM*key*" $err - catch {$r2 set read::foo bar} err - assert_match "*NOPERM*key*" $err - } - - test {Test ACL selectors by default have no permissions} { - r ACL SETUSER selector-default reset () - set user [r ACL GETUSER "selector-default"] - assert_equal 1 [llength [dict get $user selectors]] - assert_equal "" [dict get [lindex [dict get $user selectors] 0] keys] - assert_equal "" [dict get [lindex [dict get $user selectors] 0] channels] - assert_equal "-@all" [dict get [lindex [dict get $user selectors] 0] commands] - } - - test {Test deleting selectors} { - r ACL SETUSER selector-del on "(~added-selector)" - set user [r ACL GETUSER "selector-del"] - assert_equal "~added-selector" [dict get [lindex [dict get $user selectors] 0] keys] - assert_equal [llength [dict get $user selectors]] 1 - - r ACL SETUSER selector-del clearselectors - set user [r ACL GETUSER "selector-del"] - assert_equal [llength [dict get $user selectors]] 0 - } - - test {Test selector syntax error reports the error in the selector context} { - catch {r ACL SETUSER selector-syntax on (this-is-invalid)} e - assert_match "*ERR Error in ACL SETUSER modifier '(*)*Syntax*" $e - - catch {r ACL SETUSER selector-syntax on (&* &fail)} e - assert_match "*ERR Error in ACL SETUSER modifier '(*)*Adding a pattern after the*" $e - - catch {r ACL SETUSER selector-syntax on (+PING (+SELECT (+DEL} e - assert_match "*ERR Unmatched parenthesis in acl selector*" $e - - catch {r ACL SETUSER selector-syntax on (+PING (+SELECT (+DEL ) ) ) } e - assert_match "*ERR Error in ACL SETUSER modifier*" $e - - catch {r ACL SETUSER selector-syntax on (+PING (+SELECT (+DEL ) } e - assert_match "*ERR Error in ACL SETUSER modifier*" $e - - assert_equal "" [r ACL GETUSER selector-syntax] - } - - test {Test flexible selector definition} { - # Test valid selectors - r ACL SETUSER selector-2 "(~key1 +get )" "( ~key2 +get )" "( ~key3 +get)" "(~key4 +get)" - r ACL SETUSER selector-2 (~key5 +get ) ( ~key6 +get ) ( ~key7 +get) (~key8 +get) - set user [r ACL GETUSER "selector-2"] - assert_equal "~key1" [dict get [lindex [dict get $user selectors] 0] keys] - assert_equal "~key2" [dict get [lindex [dict get $user selectors] 1] keys] - assert_equal "~key3" [dict get [lindex [dict get $user selectors] 2] keys] - assert_equal "~key4" [dict get [lindex [dict get $user selectors] 3] keys] - assert_equal "~key5" [dict get [lindex [dict get $user selectors] 4] keys] - assert_equal "~key6" [dict get [lindex [dict get $user selectors] 5] keys] - assert_equal "~key7" [dict get [lindex [dict get $user selectors] 6] keys] - assert_equal "~key8" [dict get [lindex [dict get $user selectors] 7] keys] - - # Test invalid selector syntax - catch {r ACL SETUSER invalid-selector " () "} err - assert_match "*ERR*Syntax error*" $err - catch {r ACL SETUSER invalid-selector (} err - assert_match "*Unmatched parenthesis*" $err - catch {r ACL SETUSER invalid-selector )} err - assert_match "*ERR*Syntax error" $err - } - - test {Test separate read permission} { - r ACL SETUSER key-permission-R on nopass %R~read* +@all - $r2 auth key-permission-R password - assert_equal PONG [$r2 PING] - r set readstr bar - assert_equal bar [$r2 get readstr] - catch {$r2 set readstr bar} err - assert_match "*NOPERM*key*" $err - catch {$r2 get notread} err - assert_match "*NOPERM*key*" $err - } - - test {Test separate write permission} { - r ACL SETUSER key-permission-W on nopass %W~write* +@all - $r2 auth key-permission-W password - assert_equal PONG [$r2 PING] - # Note, SET is a RW command, so it's not used for testing - $r2 LPUSH writelist 10 - catch {$r2 GET writestr} err - assert_match "*NOPERM*key*" $err - catch {$r2 LPUSH notwrite 10} err - assert_match "*NOPERM*key*" $err - } - - test {Test separate read and write permissions} { - r ACL SETUSER key-permission-RW on nopass %R~read* %W~write* +@all - $r2 auth key-permission-RW password - assert_equal PONG [$r2 PING] - r set read bar - $r2 copy read write - catch {$r2 copy write read} err - assert_match "*NOPERM*key*" $err - } - - test {Validate read and write permissions format - empty permission} { - catch {r ACL SETUSER key-permission-RW %~} err - set err - } {ERR Error in ACL SETUSER modifier '%~': Syntax error} - - test {Validate read and write permissions format - empty selector} { - catch {r ACL SETUSER key-permission-RW %} err - set err - } {ERR Error in ACL SETUSER modifier '%': Syntax error} - - test {Validate read and write permissions format - empty pattern} { - # Empty pattern results with R/W access to no key - r ACL SETUSER key-permission-RW on nopass %RW~ +@all - $r2 auth key-permission-RW password - catch {$r2 SET x 5} err - set err - } {NOPERM No permissions to access a key} - - test {Validate read and write permissions format - no pattern} { - # No pattern results with R/W access to no key (currently we accept this syntax error) - r ACL SETUSER key-permission-RW on nopass %RW +@all - $r2 auth key-permission-RW password - catch {$r2 SET x 5} err - set err - } {NOPERM No permissions to access a key} - - test {Test separate read and write permissions on different selectors are not additive} { - r ACL SETUSER key-permission-RW-selector on nopass "(%R~read* +@all)" "(%W~write* +@all)" - $r2 auth key-permission-RW-selector password - assert_equal PONG [$r2 PING] - - # Verify write selector - $r2 LPUSH writelist 10 - catch {$r2 GET writestr} err - assert_match "*NOPERM*key*" $err - catch {$r2 LPUSH notwrite 10} err - assert_match "*NOPERM*key*" $err - - # Verify read selector - r set readstr bar - assert_equal bar [$r2 get readstr] - catch {$r2 set readstr bar} err - assert_match "*NOPERM*key*" $err - catch {$r2 get notread} err - assert_match "*NOPERM*key*" $err - - # Verify they don't combine - catch {$r2 copy read write} err - assert_match "*NOPERM*key*" $err - catch {$r2 copy write read} err - assert_match "*NOPERM*key*" $err - } - - test {Test SET with separate read permission} { - r del readstr - r ACL SETUSER set-key-permission-R on nopass %R~read* +@all - $r2 auth set-key-permission-R password - assert_equal PONG [$r2 PING] - assert_equal {} [$r2 get readstr] - - # We don't have the permission to WRITE key. - assert_error {*NOPERM*key*} {$r2 set readstr bar} - assert_error {*NOPERM*key*} {$r2 set readstr bar get} - assert_error {*NOPERM*key*} {$r2 set readstr bar ex 100} - assert_error {*NOPERM*key*} {$r2 set readstr bar keepttl nx} - } - - test {Test SET with separate write permission} { - r del writestr - r ACL SETUSER set-key-permission-W on nopass %W~write* +@all - $r2 auth set-key-permission-W password - assert_equal PONG [$r2 PING] - assert_equal {OK} [$r2 set writestr bar] - assert_equal {OK} [$r2 set writestr get] - - # We don't have the permission to READ key. - assert_error {*NOPERM*key*} {$r2 set get writestr} - assert_error {*NOPERM*key*} {$r2 set writestr bar get} - assert_error {*NOPERM*key*} {$r2 set writestr bar get ex 100} - assert_error {*NOPERM*key*} {$r2 set writestr bar get keepttl nx} - - # this probably should be `ERR value is not an integer or out of range` - assert_error {*NOPERM*key*} {$r2 set writestr bar ex get} - } - - test {Test SET with read and write permissions} { - r del readwrite_str - r ACL SETUSER set-key-permission-RW-selector on nopass %RW~readwrite* +@all - $r2 auth set-key-permission-RW-selector password - assert_equal PONG [$r2 PING] - - assert_equal {} [$r2 get readwrite_str] - assert_error {ERR * not an integer *} {$r2 set readwrite_str bar ex get} - - assert_equal {OK} [$r2 set readwrite_str bar] - assert_equal {bar} [$r2 get readwrite_str] - - assert_equal {bar} [$r2 set readwrite_str bar2 get] - assert_equal {bar2} [$r2 get readwrite_str] - - assert_equal {bar2} [$r2 set readwrite_str bar3 get ex 10] - assert_equal {bar3} [$r2 get readwrite_str] - assert_range [$r2 ttl readwrite_str] 5 10 - } - - test {Test BITFIELD with separate read permission} { - r del readstr - r ACL SETUSER bitfield-key-permission-R on nopass %R~read* +@all - $r2 auth bitfield-key-permission-R password - assert_equal PONG [$r2 PING] - assert_equal {0} [$r2 bitfield readstr get u4 0] - - # We don't have the permission to WRITE key. - assert_error {*NOPERM*key*} {$r2 bitfield readstr set u4 0 1} - assert_error {*NOPERM*key*} {$r2 bitfield readstr get u4 0 set u4 0 1} - assert_error {*NOPERM*key*} {$r2 bitfield readstr incrby u4 0 1} - } - - test {Test BITFIELD with separate write permission} { - r del writestr - r ACL SETUSER bitfield-key-permission-W on nopass %W~write* +@all - $r2 auth bitfield-key-permission-W password - assert_equal PONG [$r2 PING] - - # We don't have the permission to READ key. - assert_error {*NOPERM*key*} {$r2 bitfield writestr get u4 0} - assert_error {*NOPERM*key*} {$r2 bitfield writestr set u4 0 1} - assert_error {*NOPERM*key*} {$r2 bitfield writestr incrby u4 0 1} - } - - test {Test BITFIELD with read and write permissions} { - r del readwrite_str - r ACL SETUSER bitfield-key-permission-RW-selector on nopass %RW~readwrite* +@all - $r2 auth bitfield-key-permission-RW-selector password - assert_equal PONG [$r2 PING] - - assert_equal {0} [$r2 bitfield readwrite_str get u4 0] - assert_equal {0} [$r2 bitfield readwrite_str set u4 0 1] - assert_equal {2} [$r2 bitfield readwrite_str incrby u4 0 1] - assert_equal {2} [$r2 bitfield readwrite_str get u4 0] - } - - test {Test ACL log correctly identifies the relevant item when selectors are used} { - r ACL SETUSER acl-log-test-selector on nopass - r ACL SETUSER acl-log-test-selector +mget ~key (+mget ~key ~otherkey) - $r2 auth acl-log-test-selector password - - # Test that command is shown only if none of the selectors match - r ACL LOG RESET - catch {$r2 GET key} err - assert_match "*NOPERM*command*" $err - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] "acl-log-test-selector" - assert_equal [dict get $entry context] "toplevel" - assert_equal [dict get $entry reason] "command" - assert_equal [dict get $entry object] "get" - - # Test two cases where the first selector matches less than the - # second selector. We should still show the logically first unmatched key. - r ACL LOG RESET - catch {$r2 MGET otherkey someotherkey} err - assert_match "*NOPERM*key*" $err - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] "acl-log-test-selector" - assert_equal [dict get $entry context] "toplevel" - assert_equal [dict get $entry reason] "key" - assert_equal [dict get $entry object] "someotherkey" - - r ACL LOG RESET - catch {$r2 MGET key otherkey someotherkey} err - assert_match "*NOPERM*key*" $err - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] "acl-log-test-selector" - assert_equal [dict get $entry context] "toplevel" - assert_equal [dict get $entry reason] "key" - assert_equal [dict get $entry object] "someotherkey" - } - - test {Test ACL GETUSER response information} { - r ACL setuser selector-info -@all +get resetchannels &channel1 %R~foo1 %W~bar1 ~baz1 - r ACL setuser selector-info (-@all +set resetchannels &channel2 %R~foo2 %W~bar2 ~baz2) - set user [r ACL GETUSER "selector-info"] - - # Root selector - assert_equal "%R~foo1 %W~bar1 ~baz1" [dict get $user keys] - assert_equal "&channel1" [dict get $user channels] - assert_equal "-@all +get" [dict get $user commands] - - # Added selector - set secondary_selector [lindex [dict get $user selectors] 0] - assert_equal "%R~foo2 %W~bar2 ~baz2" [dict get $secondary_selector keys] - assert_equal "&channel2" [dict get $secondary_selector channels] - assert_equal "-@all +set" [dict get $secondary_selector commands] - } - - test {Test ACL list idempotency} { - r ACL SETUSER user-idempotency off -@all +get resetchannels &channel1 %R~foo1 %W~bar1 ~baz1 (-@all +set resetchannels &channel2 %R~foo2 %W~bar2 ~baz2) - set response [lindex [r ACL LIST] [lsearch [r ACL LIST] "user user-idempotency*"]] - - assert_match "*-@all*+get*(*)*" $response - assert_match "*resetchannels*&channel1*(*)*" $response - assert_match "*%R~foo1*%W~bar1*~baz1*(*)*" $response - - assert_match "*(*-@all*+set*)*" $response - assert_match "*(*resetchannels*&channel2*)*" $response - assert_match "*(*%R~foo2*%W~bar2*~baz2*)*" $response - } - - test {Test R+W is the same as all permissions} { - r ACL setuser selector-rw-info %R~foo %W~foo %RW~bar - set user [r ACL GETUSER selector-rw-info] - assert_equal "~foo ~bar" [dict get $user keys] - } - - test {Test basic dry run functionality} { - r ACL setuser command-test +@all %R~read* %W~write* %RW~rw* - assert_equal "OK" [r ACL DRYRUN command-test GET read] - - catch {r ACL DRYRUN not-a-user GET read} e - assert_equal "ERR User 'not-a-user' not found" $e - - catch {r ACL DRYRUN command-test not-a-command read} e - assert_equal "ERR Command 'not-a-command' not found" $e - } - - test {Test various commands for command permissions} { - r ACL setuser command-test -@all - assert_match {*has no permissions to run the 'set' command*} [r ACL DRYRUN command-test set somekey somevalue] - assert_match {*has no permissions to run the 'get' command*} [r ACL DRYRUN command-test get somekey] - } - - test {Test various odd commands for key permissions} { - r ACL setuser command-test +@all %R~read* %W~write* %RW~rw* - - # Test migrate, which is marked with incomplete keys - assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever rw 0 500] - assert_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN command-test MIGRATE whatever whatever read 0 500] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN command-test MIGRATE whatever whatever write 0 500] - assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 KEYS rw] - assert_match "*has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 KEYS read] - assert_match "*has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 KEYS write] - assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH KEYS KEYS rw] - assert_match "*has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH KEYS KEYS read] - assert_match "*has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH KEYS KEYS write] - assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH2 KEYS 123 KEYS rw] - assert_match "*has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH2 KEYS 123 KEYS read] - assert_match "*has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH2 KEYS 123 KEYS write] - assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH2 USER KEYS KEYS rw] - assert_match "*has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH2 USER KEYS KEYS read] - assert_match "*has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 AUTH2 USER KEYS KEYS write] - - # Test SORT, which is marked with incomplete keys - assert_equal "OK" [r ACL DRYRUN command-test SORT read STORE write] - assert_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN command-test SORT read STORE read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN command-test SORT write STORE write] - - # Test EVAL, which uses the numkey keyspec (Also test EVAL_RO) - assert_equal "OK" [r ACL DRYRUN command-test EVAL "" 1 rw1] - assert_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN command-test EVAL "" 1 read] - assert_equal "OK" [r ACL DRYRUN command-test EVAL_RO "" 1 rw1] - assert_equal "OK" [r ACL DRYRUN command-test EVAL_RO "" 1 read] - - # Read is an optional argument and not a key here, make sure we don't treat it as a key - assert_equal "OK" [r ACL DRYRUN command-test EVAL "" 0 read] - - # These are syntax errors, but it's 'OK' from an ACL perspective - assert_equal "OK" [r ACL DRYRUN command-test EVAL "" -1 read] - assert_equal "OK" [r ACL DRYRUN command-test EVAL "" 3 rw rw] - assert_equal "OK" [r ACL DRYRUN command-test EVAL "" 3 rw read] - - # Test GEORADIUS which uses the last type of keyspec, keyword - assert_equal "OK" [r ACL DRYRUN command-test GEORADIUS read longitude latitude radius M STOREDIST write] - assert_equal "OK" [r ACL DRYRUN command-test GEORADIUS read longitude latitude radius M] - assert_match {*has no permissions to access the 'read2' key*} [r ACL DRYRUN command-test GEORADIUS read1 longitude latitude radius M STOREDIST read2] - assert_match {*has no permissions to access the 'write1' key*} [r ACL DRYRUN command-test GEORADIUS write1 longitude latitude radius M STOREDIST write2] - assert_equal "OK" [r ACL DRYRUN command-test GEORADIUS read longitude latitude radius M STORE write] - assert_equal "OK" [r ACL DRYRUN command-test GEORADIUS read longitude latitude radius M] - assert_match {*has no permissions to access the 'read2' key*} [r ACL DRYRUN command-test GEORADIUS read1 longitude latitude radius M STORE read2] - assert_match {*has no permissions to access the 'write1' key*} [r ACL DRYRUN command-test GEORADIUS write1 longitude latitude radius M STORE write2] - } - - # Existence test commands are not marked as access since they are the result - # of a lot of write commands. We therefore make the claim they can be executed - # when either READ or WRITE flags are provided. - test {Existence test commands are not marked as access} { - assert_equal "OK" [r ACL DRYRUN command-test HEXISTS read foo] - assert_equal "OK" [r ACL DRYRUN command-test HEXISTS write foo] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test HEXISTS nothing foo] - - assert_equal "OK" [r ACL DRYRUN command-test HSTRLEN read foo] - assert_equal "OK" [r ACL DRYRUN command-test HSTRLEN write foo] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test HSTRLEN nothing foo] - - assert_equal "OK" [r ACL DRYRUN command-test SISMEMBER read foo] - assert_equal "OK" [r ACL DRYRUN command-test SISMEMBER write foo] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test SISMEMBER nothing foo] - } - - # Unlike existence test commands, intersection cardinality commands process the data - # between keys and return an aggregated cardinality. therefore they have the access - # requirement. - test {Intersection cardinaltiy commands are access commands} { - assert_equal "OK" [r ACL DRYRUN command-test SINTERCARD 2 read read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN command-test SINTERCARD 2 write read] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test SINTERCARD 2 nothing read] - - assert_equal "OK" [r ACL DRYRUN command-test ZCOUNT read 0 1] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN command-test ZCOUNT write 0 1] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test ZCOUNT nothing 0 1] - - assert_equal "OK" [r ACL DRYRUN command-test PFCOUNT read read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN command-test PFCOUNT write read] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test PFCOUNT nothing read] - - assert_equal "OK" [r ACL DRYRUN command-test ZINTERCARD 2 read read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN command-test ZINTERCARD 2 write read] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test ZINTERCARD 2 nothing read] - } - - test {Test general keyspace commands require some type of permission to execute} { - assert_equal "OK" [r ACL DRYRUN command-test touch read] - assert_equal "OK" [r ACL DRYRUN command-test touch write] - assert_equal "OK" [r ACL DRYRUN command-test touch rw] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test touch nothing] - - assert_equal "OK" [r ACL DRYRUN command-test exists read] - assert_equal "OK" [r ACL DRYRUN command-test exists write] - assert_equal "OK" [r ACL DRYRUN command-test exists rw] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test exists nothing] - - assert_equal "OK" [r ACL DRYRUN command-test MEMORY USAGE read] - assert_equal "OK" [r ACL DRYRUN command-test MEMORY USAGE write] - assert_equal "OK" [r ACL DRYRUN command-test MEMORY USAGE rw] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test MEMORY USAGE nothing] - - assert_equal "OK" [r ACL DRYRUN command-test TYPE read] - assert_equal "OK" [r ACL DRYRUN command-test TYPE write] - assert_equal "OK" [r ACL DRYRUN command-test TYPE rw] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test TYPE nothing] - } - - test {Cardinality commands require some type of permission to execute} { - set commands {STRLEN HLEN LLEN SCARD ZCARD XLEN} - foreach command $commands { - assert_equal "OK" [r ACL DRYRUN command-test $command read] - assert_equal "OK" [r ACL DRYRUN command-test $command write] - assert_equal "OK" [r ACL DRYRUN command-test $command rw] - assert_match {*has no permissions to access the 'nothing' key*} [r ACL DRYRUN command-test $command nothing] - } - } - - test {Test sharded channel permissions} { - r ACL setuser test-channels +@all resetchannels &channel - assert_equal "OK" [r ACL DRYRUN test-channels spublish channel foo] - assert_equal "OK" [r ACL DRYRUN test-channels ssubscribe channel] - assert_equal "OK" [r ACL DRYRUN test-channels sunsubscribe] - assert_equal "OK" [r ACL DRYRUN test-channels sunsubscribe channel] - assert_equal "OK" [r ACL DRYRUN test-channels sunsubscribe otherchannel] - - assert_match {*has no permissions to access the 'otherchannel' channel*} [r ACL DRYRUN test-channels spublish otherchannel foo] - assert_match {*has no permissions to access the 'otherchannel' channel*} [r ACL DRYRUN test-channels ssubscribe otherchannel foo] - } - - test {Test sort with ACL permissions} { - r set v1 1 - r lpush mylist 1 - - r ACL setuser test-sort-acl on nopass (+sort ~mylist) - $r2 auth test-sort-acl nopass - - catch {$r2 sort mylist by v*} e - assert_equal "ERR BY option of SORT denied due to insufficient ACL permissions." $e - catch {$r2 sort mylist get v*} e - assert_equal "ERR GET option of SORT denied due to insufficient ACL permissions." $e - - r ACL setuser test-sort-acl (+sort ~mylist ~v*) - catch {$r2 sort mylist by v*} e - assert_equal "ERR BY option of SORT denied due to insufficient ACL permissions." $e - catch {$r2 sort mylist get v*} e - assert_equal "ERR GET option of SORT denied due to insufficient ACL permissions." $e - - r ACL setuser test-sort-acl (+sort ~mylist %W~*) - catch {$r2 sort mylist by v*} e - assert_equal "ERR BY option of SORT denied due to insufficient ACL permissions." $e - catch {$r2 sort mylist get v*} e - assert_equal "ERR GET option of SORT denied due to insufficient ACL permissions." $e - - r ACL setuser test-sort-acl (+sort ~mylist %R~*) - assert_equal "1" [$r2 sort mylist by v*] - - # cleanup - r ACL deluser test-sort-acl - r del v1 mylist - } - - test {Test DRYRUN with wrong number of arguments} { - r ACL setuser test-dry-run +@all ~v* - - assert_equal "OK" [r ACL DRYRUN test-dry-run SET v v] - - catch {r ACL DRYRUN test-dry-run SET v} e - assert_equal "ERR wrong number of arguments for 'set' command" $e - - catch {r ACL DRYRUN test-dry-run SET} e - assert_equal "ERR wrong number of arguments for 'set' command" $e - } - - $r2 close -} - -set server_path [tmpdir "selectors.acl"] -exec cp -f tests/assets/userwithselectors.acl $server_path -exec cp -f tests/assets/default.conf $server_path -start_server [list overrides [list "dir" $server_path "aclfile" "userwithselectors.acl"] tags [list "external:skip"]] { - - test {Test behavior of loading ACLs} { - set selectors [dict get [r ACL getuser alice] selectors] - assert_equal [llength $selectors] 1 - set test_selector [lindex $selectors 0] - assert_equal "-@all +get" [dict get $test_selector "commands"] - assert_equal "~rw*" [dict get $test_selector "keys"] - - set selectors [dict get [r ACL getuser bob] selectors] - assert_equal [llength $selectors] 2 - set test_selector [lindex $selectors 0] - assert_equal "-@all +set" [dict get $test_selector "commands"] - assert_equal "%W~w*" [dict get $test_selector "keys"] - - set test_selector [lindex $selectors 1] - assert_equal "-@all +get" [dict get $test_selector "commands"] - assert_equal "%R~r*" [dict get $test_selector "keys"] - } -} diff --git a/examples/redis-unstable/tests/unit/acl.tcl b/examples/redis-unstable/tests/unit/acl.tcl deleted file mode 100644 index 8e3fb20..0000000 --- a/examples/redis-unstable/tests/unit/acl.tcl +++ /dev/null @@ -1,1311 +0,0 @@ -start_server {tags {"acl external:skip"}} { - test {Connections start with the default user} { - r ACL WHOAMI - } {default} - - test {It is possible to create new users} { - r ACL setuser newuser - } - - test {Coverage: ACL USERS} { - r ACL USERS - } {default newuser} - - test {Usernames can not contain spaces or null characters} { - catch {r ACL setuser "a a"} err - set err - } {*Usernames can't contain spaces or null characters*} - - test {New users start disabled} { - r ACL setuser newuser >passwd1 - catch {r AUTH newuser passwd1} err - set err - } {*WRONGPASS*} - - test {Enabling the user allows the login} { - r ACL setuser newuser on +acl - r AUTH newuser passwd1 - r ACL WHOAMI - } {newuser} - - test {Only the set of correct passwords work} { - r ACL setuser newuser >passwd2 - catch {r AUTH newuser passwd1} e - assert {$e eq "OK"} - catch {r AUTH newuser passwd2} e - assert {$e eq "OK"} - catch {r AUTH newuser passwd3} e - set e - } {*WRONGPASS*} - - test {It is possible to remove passwords from the set of valid ones} { - r ACL setuser newuser pspass +acl +client +@pubsub - r AUTH psuser pspass - catch {r PUBLISH foo bar} e - set e - } {*NOPERM*channel*} - - test {By default, only default user is not able to publish to any shard channel} { - r AUTH default pwd - r SPUBLISH foo bar - r AUTH psuser pspass - catch {r SPUBLISH foo bar} e - set e - } {*NOPERM*channel*} - - test {By default, only default user is able to subscribe to any channel} { - set rd [redis_deferring_client] - $rd AUTH default pwd - $rd read - $rd SUBSCRIBE foo - assert_match {subscribe foo 1} [$rd read] - $rd UNSUBSCRIBE - $rd read - $rd AUTH psuser pspass - $rd read - $rd SUBSCRIBE foo - catch {$rd read} e - $rd close - set e - } {*NOPERM*channel*} - - test {By default, only default user is able to subscribe to any shard channel} { - set rd [redis_deferring_client] - $rd AUTH default pwd - $rd read - $rd SSUBSCRIBE foo - assert_match {ssubscribe foo 1} [$rd read] - $rd SUNSUBSCRIBE - $rd read - $rd AUTH psuser pspass - $rd read - $rd SSUBSCRIBE foo - catch {$rd read} e - $rd close - set e - } {*NOPERM*channel*} - - test {By default, only default user is able to subscribe to any pattern} { - set rd [redis_deferring_client] - $rd AUTH default pwd - $rd read - $rd PSUBSCRIBE bar* - assert_match {psubscribe bar\* 1} [$rd read] - $rd PUNSUBSCRIBE - $rd read - $rd AUTH psuser pspass - $rd read - $rd PSUBSCRIBE bar* - catch {$rd read} e - $rd close - set e - } {*NOPERM*channel*} - - test {It's possible to allow publishing to a subset of channels} { - r ACL setuser psuser resetchannels &foo:1 &bar:* - assert_equal {0} [r PUBLISH foo:1 somemessage] - assert_equal {0} [r PUBLISH bar:2 anothermessage] - catch {r PUBLISH zap:3 nosuchmessage} e - set e - } {*NOPERM*channel*} - - test {It's possible to allow publishing to a subset of shard channels} { - r ACL setuser psuser resetchannels &foo:1 &bar:* - assert_equal {0} [r SPUBLISH foo:1 somemessage] - assert_equal {0} [r SPUBLISH bar:2 anothermessage] - catch {r SPUBLISH zap:3 nosuchmessage} e - set e - } {*NOPERM*channel*} - - test {Validate subset of channels is prefixed with resetchannels flag} { - r ACL setuser hpuser on nopass resetchannels &foo +@all - - # Verify resetchannels flag is prefixed before the channel name(s) - set users [r ACL LIST] - set curruser "hpuser" - foreach user [lshuffle $users] { - if {[string first $curruser $user] != -1} { - assert_equal {user hpuser on nopass sanitize-payload resetchannels &foo +@all} $user - } - } - - # authenticate as hpuser - r AUTH hpuser pass - - assert_equal {0} [r PUBLISH foo bar] - catch {r PUBLISH bar game} e - - # Falling back to psuser for the below tests - r AUTH psuser pspass - r ACL deluser hpuser - set e - } {*NOPERM*channel*} - - test {In transaction queue publish/subscribe/psubscribe to unauthorized channel will fail} { - r ACL setuser psuser +multi +discard - r MULTI - assert_error {*NOPERM*channel*} {r PUBLISH notexits helloworld} - r DISCARD - r MULTI - assert_error {*NOPERM*channel*} {r SUBSCRIBE notexits foo:1} - r DISCARD - r MULTI - assert_error {*NOPERM*channel*} {r PSUBSCRIBE notexits:* bar:*} - r DISCARD - } - - test {It's possible to allow subscribing to a subset of channels} { - set rd [redis_deferring_client] - $rd AUTH psuser pspass - $rd read - $rd SUBSCRIBE foo:1 - assert_match {subscribe foo:1 1} [$rd read] - $rd SUBSCRIBE bar:2 - assert_match {subscribe bar:2 2} [$rd read] - $rd SUBSCRIBE zap:3 - catch {$rd read} e - set e - } {*NOPERM*channel*} - - test {It's possible to allow subscribing to a subset of shard channels} { - set rd [redis_deferring_client] - $rd AUTH psuser pspass - $rd read - $rd SSUBSCRIBE foo:1 - assert_match {ssubscribe foo:1 1} [$rd read] - $rd SSUBSCRIBE bar:2 - assert_match {ssubscribe bar:2 2} [$rd read] - $rd SSUBSCRIBE zap:3 - catch {$rd read} e - set e - } {*NOPERM*channel*} - - test {It's possible to allow subscribing to a subset of channel patterns} { - set rd [redis_deferring_client] - $rd AUTH psuser pspass - $rd read - $rd PSUBSCRIBE foo:1 - assert_match {psubscribe foo:1 1} [$rd read] - $rd PSUBSCRIBE bar:* - assert_match {psubscribe bar:\* 2} [$rd read] - $rd PSUBSCRIBE bar:baz - catch {$rd read} e - set e - } {*NOPERM*channel*} - - test {Subscribers are killed when revoked of channel permission} { - # This test covers the case that the SETUSER is requested over the subscriber - set rd [redis_deferring_client] - r ACL setuser psuser resetchannels &foo:1 - # we must use RESP 3 since AUTH command is not supported over a subscribed client with RESP2 - $rd HELLO 3 AUTH psuser pspass - $rd read - $rd CLIENT SETNAME deathrow - $rd read - $rd SUBSCRIBE foo:1 - assert_match {subscribe foo:1 1} [$rd read] - $rd ACL setuser psuser resetchannels - assert_match {OK} [$rd read] - # 'psuser' no longer has access to "foo:1" channel, so they should get disconnected - catch {$rd read} e - assert_match {*I/O error*} $e - assert_no_match {*deathrow*} [r CLIENT LIST] - $rd close - } {0} - - test {Subscribers are killed when revoked of channel permission} { - set rd [redis_deferring_client] - r ACL setuser psuser resetchannels &foo:1 - $rd AUTH psuser pspass - $rd read - $rd CLIENT SETNAME deathrow - $rd read - $rd SUBSCRIBE foo:1 - $rd read - r ACL setuser psuser resetchannels - assert_no_match {*deathrow*} [r CLIENT LIST] - $rd close - } {0} - - test {Subscribers are killed when revoked of channel permission} { - set rd [redis_deferring_client] - r ACL setuser psuser resetchannels &foo:1 - $rd AUTH psuser pspass - $rd read - $rd CLIENT SETNAME deathrow - $rd read - $rd SSUBSCRIBE foo:1 - $rd read - r ACL setuser psuser resetchannels - assert_no_match {*deathrow*} [r CLIENT LIST] - $rd close - } {0} - - test {Subscribers are killed when revoked of pattern permission} { - set rd [redis_deferring_client] - r ACL setuser psuser resetchannels &bar:* - $rd AUTH psuser pspass - $rd read - $rd CLIENT SETNAME deathrow - $rd read - $rd PSUBSCRIBE bar:* - $rd read - r ACL setuser psuser resetchannels - assert_no_match {*deathrow*} [r CLIENT LIST] - $rd close - } {0} - - test {Subscribers are killed when revoked of allchannels permission} { - set rd [redis_deferring_client] - r ACL setuser psuser allchannels - $rd AUTH psuser pspass - $rd read - $rd CLIENT SETNAME deathrow - $rd read - $rd PSUBSCRIBE foo - $rd read - r ACL setuser psuser resetchannels - assert_no_match {*deathrow*} [r CLIENT LIST] - $rd close - } {0} - - test {Subscribers are pardoned if literal permissions are retained and/or gaining allchannels} { - set rd [redis_deferring_client] - r ACL setuser psuser resetchannels &foo:1 &bar:* &orders - $rd AUTH psuser pspass - $rd read - $rd CLIENT SETNAME pardoned - $rd read - $rd SUBSCRIBE foo:1 - $rd read - $rd SSUBSCRIBE orders - $rd read - $rd PSUBSCRIBE bar:* - $rd read - r ACL setuser psuser resetchannels &foo:1 &bar:* &orders &baz:qaz &zoo:* - assert_match {*pardoned*} [r CLIENT LIST] - r ACL setuser psuser allchannels - assert_match {*pardoned*} [r CLIENT LIST] - $rd close - } {0} - - test {blocked command gets rejected when reprocessed after permission change} { - r auth default "" - r config resetstat - set rd [redis_deferring_client] - r ACL setuser psuser reset on nopass +@all allkeys - $rd AUTH psuser pspass - $rd read - $rd BLPOP list1 0 - wait_for_blocked_client - r ACL setuser psuser resetkeys - r LPUSH list1 foo - assert_error {*NOPERM No permissions to access a key*} {$rd read} - $rd ping - $rd close - assert_match {*calls=0,usec=0,*,rejected_calls=1,failed_calls=0} [cmdrstat blpop r] - } - - test {Users can be configured to authenticate with any password} { - r ACL setuser newuser nopass - r AUTH newuser zipzapblabla - } {OK} - - test {ACLs can exclude single commands} { - r ACL setuser newuser -ping - r INCR mycounter ; # Should not raise an error - catch {r PING} e - set e - } {*NOPERM*ping*} - - test {ACLs can include or exclude whole classes of commands} { - r ACL setuser newuser -@all +@set +acl - r SADD myset a b c; # Should not raise an error - r ACL setuser newuser +@all -@string - r SADD myset a b c; # Again should not raise an error - # String commands instead should raise an error - catch {r SET foo bar} e - r ACL setuser newuser allcommands; # Undo commands ACL - set e - } {*NOPERM*set*} - - test {ACLs can include single subcommands} { - r ACL setuser newuser +@all -client - r ACL setuser newuser +client|id +client|setname - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_match {+@all*-client*+client|id*} $cmdstr - assert_match {+@all*-client*+client|setname*} $cmdstr - r CLIENT ID; # Should not fail - r CLIENT SETNAME foo ; # Should not fail - catch {r CLIENT KILL type master} e - set e - } {*NOPERM*client|kill*} - - test {ACLs can exclude single subcommands, case 1} { - r ACL setuser newuser +@all -client|kill - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_equal {+@all -client|kill} $cmdstr - r CLIENT ID; # Should not fail - r CLIENT SETNAME foo ; # Should not fail - catch {r CLIENT KILL type master} e - set e - } {*NOPERM*client|kill*} - - test {ACLs can exclude single subcommands, case 2} { - r ACL setuser newuser -@all +acl +config -config|set - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_match {*+config*} $cmdstr - assert_match {*-config|set*} $cmdstr - r CONFIG GET loglevel; # Should not fail - catch {r CONFIG SET loglevel debug} e - set e - } {*NOPERM*config|set*} - - test {ACLs cannot include a subcommand with a specific arg} { - r ACL setuser newuser +@all -config|get - catch { r ACL setuser newuser +config|get|appendonly} e - set e - } {*Allowing first-arg of a subcommand is not supported*} - - test {ACLs cannot exclude or include a container commands with a specific arg} { - r ACL setuser newuser +@all +config|get - catch { r ACL setuser newuser +@all +config|asdf} e - assert_match "*Unknown command or category name in ACL*" $e - catch { r ACL setuser newuser +@all -config|asdf} e - assert_match "*Unknown command or category name in ACL*" $e - } {} - - test {ACLs cannot exclude or include a container command with two args} { - r ACL setuser newuser +@all +config|get - catch { r ACL setuser newuser +@all +get|key1|key2} e - assert_match "*Unknown command or category name in ACL*" $e - catch { r ACL setuser newuser +@all -get|key1|key2} e - assert_match "*Unknown command or category name in ACL*" $e - } {} - - test {ACLs including of a type includes also subcommands} { - r ACL setuser newuser -@all +del +acl +@stream - r DEL key - r XADD key * field value - r XINFO STREAM key - } - - test {ACLs can block SELECT of all but a specific DB} { - r ACL setuser newuser -@all +acl +select|0 - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_match {*+select|0*} $cmdstr - r SELECT 0 - catch {r SELECT 1} e - set e - } {*NOPERM*select*} {singledb:skip} - - test {ACLs can block all DEBUG subcommands except one} { - r ACL setuser newuser -@all +acl +del +incr +debug|object - r DEL key - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_match {*+debug|object*} $cmdstr - r INCR key - r DEBUG OBJECT key - catch {r DEBUG SEGFAULT} e - set e - } {*NOPERM*debug*} - - test {ACLs set can include subcommands, if already full command exists} { - r ACL setuser bob +memory|doctor - set cmdstr [dict get [r ACL getuser bob] commands] - assert_equal {-@all +memory|doctor} $cmdstr - - # Validate the commands have got engulfed to +memory. - r ACL setuser bob +memory - set cmdstr [dict get [r ACL getuser bob] commands] - assert_equal {-@all +memory} $cmdstr - - # Appending to the existing access string of bob. - r ACL setuser bob +@all +client|id - # Although this does nothing, we retain it anyways so we can reproduce - # the original ACL. - set cmdstr [dict get [r ACL getuser bob] commands] - assert_equal {+@all +client|id} $cmdstr - - r ACL setuser bob >passwd1 on - r AUTH bob passwd1 - r CLIENT ID; # Should not fail - r MEMORY DOCTOR; # Should not fail - } - - test {ACLs set can exclude subcommands, if already full command exists} { - r ACL setuser alice +@all -memory|doctor - set cmdstr [dict get [r ACL getuser alice] commands] - assert_equal {+@all -memory|doctor} $cmdstr - - r ACL setuser alice >passwd1 on - r AUTH alice passwd1 - - assert_error {*NOPERM*memory|doctor*} {r MEMORY DOCTOR} - r MEMORY STATS ;# should work - - # Validate the commands have got engulfed to -memory. - r ACL setuser alice +@all -memory - set cmdstr [dict get [r ACL getuser alice] commands] - assert_equal {+@all -memory} $cmdstr - - assert_error {*NOPERM*memory|doctor*} {r MEMORY DOCTOR} - assert_error {*NOPERM*memory|stats*} {r MEMORY STATS} - - # Appending to the existing access string of alice. - r ACL setuser alice -@all - - # Now, alice can't do anything, we need to auth newuser to execute ACL GETUSER - r AUTH newuser passwd1 - - # Validate the new commands has got engulfed to -@all. - set cmdstr [dict get [r ACL getuser alice] commands] - assert_equal {-@all} $cmdstr - - r AUTH alice passwd1 - - assert_error {*NOPERM*get*} {r GET key} - assert_error {*NOPERM*memory|stats*} {r MEMORY STATS} - - # Auth newuser before the next test - r AUTH newuser passwd1 - } - - test {ACL SETUSER RESET reverting to default newly created user} { - set current_user "example" - r ACL DELUSER $current_user - r ACL SETUSER $current_user - - set users [r ACL LIST] - foreach user [lshuffle $users] { - if {[string first $current_user $user] != -1} { - set current_user_output $user - } - } - - r ACL SETUSER $current_user reset - set users [r ACL LIST] - foreach user [lshuffle $users] { - if {[string first $current_user $user] != -1} { - assert_equal $current_user_output $user - } - } - } - - # Note that the order of the generated ACL rules is not stable in Redis - # so we need to match the different parts and not as a whole string. - test {ACL GETUSER is able to translate back command permissions} { - # Subtractive - r ACL setuser newuser reset +@all ~* -@string +incr -debug +debug|digest - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_match {*+@all*} $cmdstr - assert_match {*-@string*} $cmdstr - assert_match {*+incr*} $cmdstr - assert_match {*-debug +debug|digest**} $cmdstr - - # Additive - r ACL setuser newuser reset +@string -incr +acl +debug|digest +debug|segfault - set cmdstr [dict get [r ACL getuser newuser] commands] - assert_match {*-@all*} $cmdstr - assert_match {*+@string*} $cmdstr - assert_match {*-incr*} $cmdstr - assert_match {*+debug|digest*} $cmdstr - assert_match {*+debug|segfault*} $cmdstr - assert_match {*+acl*} $cmdstr - } - - # A regression test make sure that as long as there is a simple - # category defining the commands, that it will be used as is. - test {ACL GETUSER provides reasonable results} { - set categories [r ACL CAT] - - # Test that adding each single category will - # result in just that category with both +@all and -@all - foreach category $categories { - # Test for future commands where allowed - r ACL setuser additive reset +@all "-@$category" - set cmdstr [dict get [r ACL getuser additive] commands] - assert_equal "+@all -@$category" $cmdstr - - # Test for future commands where disallowed - r ACL setuser restrictive reset -@all "+@$category" - set cmdstr [dict get [r ACL getuser restrictive] commands] - assert_equal "-@all +@$category" $cmdstr - } - } - - # Test that only lossless compaction of ACLs occur. - test {ACL GETUSER provides correct results} { - r ACL SETUSER adv-test - r ACL SETUSER adv-test +@all -@hash -@slow +hget - assert_equal "+@all -@hash -@slow +hget" [dict get [r ACL getuser adv-test] commands] - - # Categories are re-ordered if re-added - r ACL SETUSER adv-test -@hash - assert_equal "+@all -@slow +hget -@hash" [dict get [r ACL getuser adv-test] commands] - - # Inverting categories removes existing categories - r ACL SETUSER adv-test +@hash - assert_equal "+@all -@slow +hget +@hash" [dict get [r ACL getuser adv-test] commands] - - # Inverting the all category compacts everything - r ACL SETUSER adv-test -@all - assert_equal "-@all" [dict get [r ACL getuser adv-test] commands] - r ACL SETUSER adv-test -@string -@slow +@all - assert_equal "+@all" [dict get [r ACL getuser adv-test] commands] - - # Make sure categories are case insensitive - r ACL SETUSER adv-test -@all +@HASH +@hash +@HaSh - assert_equal "-@all +@hash" [dict get [r ACL getuser adv-test] commands] - - # Make sure commands are case insensitive - r ACL SETUSER adv-test -@all +HGET +hget +hGeT - assert_equal "-@all +hget" [dict get [r ACL getuser adv-test] commands] - - # Arbitrary category additions and removals are handled - r ACL SETUSER adv-test -@all +@hash +@slow +@set +@set +@slow +@hash - assert_equal "-@all +@set +@slow +@hash" [dict get [r ACL getuser adv-test] commands] - - # Arbitrary command additions and removals are handled - r ACL SETUSER adv-test -@all +hget -hset +hset -hget - assert_equal "-@all +hset -hget" [dict get [r ACL getuser adv-test] commands] - - # Arbitrary subcommands are compacted - r ACL SETUSER adv-test -@all +client|list +client|list +config|get +config +acl|list -acl - assert_equal "-@all +client|list +config -acl" [dict get [r ACL getuser adv-test] commands] - - # Deprecated subcommand usage is handled - r ACL SETUSER adv-test -@all +select|0 +select|0 +debug|segfault +debug - assert_equal "-@all +select|0 +debug" [dict get [r ACL getuser adv-test] commands] - - # Unnecessary categories are retained for potentional future compatibility - r ACL SETUSER adv-test -@all -@dangerous - assert_equal "-@all -@dangerous" [dict get [r ACL getuser adv-test] commands] - - # Duplicate categories are compressed, regression test for #12470 - r ACL SETUSER adv-test -@all +config +config|get -config|set +config - assert_equal "-@all +config" [dict get [r ACL getuser adv-test] commands] - } - - test "ACL CAT with illegal arguments" { - assert_error {*Unknown category 'NON_EXISTS'} {r ACL CAT NON_EXISTS} - assert_error {*unknown subcommand or wrong number of arguments for 'CAT'*} {r ACL CAT NON_EXISTS NON_EXISTS2} - } - - test "ACL CAT without category - list all categories" { - set categories [r acl cat] - assert_not_equal [lsearch $categories "keyspace"] -1 - assert_not_equal [lsearch $categories "connection"] -1 - } - - test "ACL CAT category - list all commands/subcommands that belong to category" { - assert_not_equal [lsearch [r acl cat transaction] "multi"] -1 - assert_not_equal [lsearch [r acl cat scripting] "function|list"] -1 - - # Negative check to make sure it doesn't actually return all commands. - assert_equal [lsearch [r acl cat keyspace] "set"] -1 - assert_equal [lsearch [r acl cat stream] "get"] -1 - } - - test "ACL requires explicit permission for scripting for EVAL_RO, EVALSHA_RO and FCALL_RO" { - r ACL SETUSER scripter on nopass +readonly - assert_match {*has no permissions to run the 'eval_ro' command*} [r ACL DRYRUN scripter EVAL_RO "" 0] - assert_match {*has no permissions to run the 'evalsha_ro' command*} [r ACL DRYRUN scripter EVALSHA_RO "" 0] - assert_match {*has no permissions to run the 'fcall_ro' command*} [r ACL DRYRUN scripter FCALL_RO "" 0] - } - - test {ACL #5998 regression: memory leaks adding / removing subcommands} { - r AUTH default "" - r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c - r ACL setuser newuser -debug - # The test framework will detect a leak if any. - } - - test {ACL LOG aggregates similar errors together and assigns unique entry-id to new errors} { - r ACL LOG RESET - r ACL setuser user1 >foo - assert_error "*WRONGPASS*" {r AUTH user1 doo} - set entry_id_initial_error [dict get [lindex [r ACL LOG] 0] entry-id] - set timestamp_created_original [dict get [lindex [r ACL LOG] 0] timestamp-created] - set timestamp_last_update_original [dict get [lindex [r ACL LOG] 0] timestamp-last-updated] - after 1 - for {set j 0} {$j < 10} {incr j} { - assert_error "*WRONGPASS*" {r AUTH user1 doo} - } - set entry_id_lastest_error [dict get [lindex [r ACL LOG] 0] entry-id] - set timestamp_created_updated [dict get [lindex [r ACL LOG] 0] timestamp-created] - set timestamp_last_updated_after_update [dict get [lindex [r ACL LOG] 0] timestamp-last-updated] - assert {$entry_id_lastest_error eq $entry_id_initial_error} - assert {$timestamp_last_update_original < $timestamp_last_updated_after_update} - assert {$timestamp_created_original eq $timestamp_created_updated} - r ACL setuser user2 >doo - assert_error "*WRONGPASS*" {r AUTH user2 foo} - set new_error_entry_id [dict get [lindex [r ACL LOG] 0] entry-id] - assert {$new_error_entry_id eq $entry_id_lastest_error + 1 } - } - - test {ACL LOG shows failed command executions at toplevel} { - r ACL LOG RESET - r ACL setuser antirez >foo on +set ~object:1234 - r ACL setuser antirez +eval +multi +exec - r ACL setuser antirez resetchannels +publish - r AUTH antirez foo - assert_error "*NOPERM*get*" {r GET foo} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry username] eq {antirez}} - assert {[dict get $entry context] eq {toplevel}} - assert {[dict get $entry reason] eq {command}} - assert {[dict get $entry object] eq {get}} - assert_match {*cmd=get*} [dict get $entry client-info] - } - - test "ACL LOG shows failed subcommand executions at toplevel" { - r ACL LOG RESET - r ACL DELUSER demo - r ACL SETUSER demo on nopass - r AUTH demo "" - assert_error "*NOPERM*script|help*" {r SCRIPT HELP} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] {demo} - assert_equal [dict get $entry context] {toplevel} - assert_equal [dict get $entry reason] {command} - assert_equal [dict get $entry object] {script|help} - } - - test {ACL LOG is able to test similar events} { - r ACL LOG RESET - r AUTH antirez foo - catch {r GET foo} - catch {r GET foo} - catch {r GET foo} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry count] == 3} - } - - test {ACL LOG is able to log keys access violations and key name} { - r AUTH antirez foo - catch {r SET somekeynotallowed 1234} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry reason] eq {key}} - assert {[dict get $entry object] eq {somekeynotallowed}} - } - - test {ACL LOG is able to log channel access violations and channel name} { - r AUTH antirez foo - catch {r PUBLISH somechannelnotallowed nullmsg} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry reason] eq {channel}} - assert {[dict get $entry object] eq {somechannelnotallowed}} - } - - test {ACL LOG RESET is able to flush the entries in the log} { - r ACL LOG RESET - assert {[llength [r ACL LOG]] == 0} - } - - test {ACL LOG can distinguish the transaction context (1)} { - r AUTH antirez foo - r MULTI - catch {r INCR foo} - catch {r EXEC} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry context] eq {multi}} - assert {[dict get $entry object] eq {incr}} - } - - test {ACL LOG can distinguish the transaction context (2)} { - set rd1 [redis_deferring_client] - r ACL SETUSER antirez +incr - - r AUTH antirez foo - r MULTI - r INCR object:1234 - $rd1 ACL SETUSER antirez -incr - $rd1 read - catch {r EXEC} - $rd1 close - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry context] eq {multi}} - assert {[dict get $entry object] eq {incr}} - assert_match {*cmd=exec*} [dict get $entry client-info] - r ACL SETUSER antirez -incr - } - - test {ACL can log errors in the context of Lua scripting} { - r AUTH antirez foo - catch {r EVAL {redis.call('incr','foo')} 0} - r AUTH default "" - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry context] eq {lua}} - assert {[dict get $entry object] eq {incr}} - assert_match {*cmd=eval*} [dict get $entry client-info] - } - - test {ACL LOG can accept a numerical argument to show less entries} { - r AUTH antirez foo - catch {r INCR foo} - catch {r INCR foo} - catch {r INCR foo} - catch {r INCR foo} - r AUTH default "" - assert {[llength [r ACL LOG]] > 1} - assert {[llength [r ACL LOG 2]] == 2} - } - - test {ACL LOG can log failed auth attempts} { - catch {r AUTH antirez wrong-password} - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry context] eq {toplevel}} - assert {[dict get $entry reason] eq {auth}} - assert {[dict get $entry object] eq {AUTH}} - assert {[dict get $entry username] eq {antirez}} - } - - test {ACLLOG - zero max length is correctly handled} { - r ACL LOG RESET - r CONFIG SET acllog-max-len 0 - for {set j 0} {$j < 10} {incr j} { - catch {r SET obj:$j 123} - } - r AUTH default "" - assert {[llength [r ACL LOG]] == 0} - } - - test {ACL LOG entries are limited to a maximum amount} { - r ACL LOG RESET - r CONFIG SET acllog-max-len 5 - r AUTH antirez foo - for {set j 0} {$j < 10} {incr j} { - catch {r SET obj:$j 123} - } - r AUTH default "" - assert {[llength [r ACL LOG]] == 5} - } - - test {ACL LOG entries are still present on update of max len config} { - r CONFIG SET acllog-max-len 0 - assert {[llength [r ACL LOG]] == 5} - } - - test {When default user is off, new connections are not authenticated} { - r ACL setuser default off - catch {set rd1 [redis_deferring_client]} e - r ACL setuser default on - set e - } {*NOAUTH*} - - test {When default user has no command permission, hello command still works for other users} { - r ACL setuser secure-user >supass on +@all - r ACL setuser default -@all - r HELLO 2 AUTH secure-user supass - r ACL setuser default nopass +@all - r AUTH default "" - } - - test {When an authentication chain is used in the HELLO cmd, the last auth cmd has precedence} { - r ACL setuser secure-user1 >supass on +@all - r ACL setuser secure-user2 >supass on +@all - r HELLO 2 AUTH secure-user pass AUTH secure-user2 supass AUTH secure-user1 supass - assert {[r ACL whoami] eq {secure-user1}} - catch {r HELLO 2 AUTH secure-user supass AUTH secure-user2 supass AUTH secure-user pass} e - assert_match "WRONGPASS invalid username-password pair or user is disabled." $e - assert {[r ACL whoami] eq {secure-user1}} - } - - test {When a setname chain is used in the HELLO cmd, the last setname cmd has precedence} { - r HELLO 2 setname client1 setname client2 setname client3 setname client4 - assert {[r client getname] eq {client4}} - catch {r HELLO 2 setname client5 setname client6 setname "client name"} e - assert_match "ERR Client names cannot contain spaces, newlines or special characters." $e - assert {[r client getname] eq {client4}} - } - - test {When authentication fails in the HELLO cmd, the client setname should not be applied} { - r client setname client0 - catch {r HELLO 2 AUTH user pass setname client1} e - assert_match "WRONGPASS invalid username-password pair or user is disabled." $e - assert {[r client getname] eq {client0}} - } - - test {ACL HELP should not have unexpected options} { - catch {r ACL help xxx} e - assert_match "*wrong number of arguments for 'acl|help' command" $e - } - - test {Delete a user that the client doesn't use} { - r ACL setuser not_used on >passwd - assert {[r ACL deluser not_used] == 1} - # The client is not closed - assert {[r ping] eq {PONG}} - } - - test {Delete a user that the client is using} { - r ACL setuser using on +acl >passwd - r AUTH using passwd - # The client will receive reply normally - assert {[r ACL deluser using] == 1} - # The client is closed - catch {[r ping]} e - assert_match "*I/O error*" $e - } - - test {ACL GENPASS command failed test} { - catch {r ACL genpass -236} err1 - catch {r ACL genpass 5000} err2 - assert_match "*ACL GENPASS argument must be the number*" $err1 - assert_match "*ACL GENPASS argument must be the number*" $err2 - } - - test {Default user can not be removed} { - catch {r ACL deluser default} err - set err - } {ERR The 'default' user cannot be removed} - - test {ACL load non-existing configured ACL file} { - catch {r ACL load} err - set err - } {*Redis instance is not configured to use an ACL file*} - - # If there is an AUTH failure the metric increases - test {ACL-Metrics user AUTH failure} { - set current_auth_failures [s acl_access_denied_auth] - set current_invalid_cmd_accesses [s acl_access_denied_cmd] - set current_invalid_key_accesses [s acl_access_denied_key] - set current_invalid_channel_accesses [s acl_access_denied_channel] - assert_error "*WRONGPASS*" {r AUTH notrealuser 1233456} - assert {[s acl_access_denied_auth] eq [expr $current_auth_failures + 1]} - assert_error "*WRONGPASS*" {r HELLO 3 AUTH notrealuser 1233456} - assert {[s acl_access_denied_auth] eq [expr $current_auth_failures + 2]} - assert_error "*WRONGPASS*" {r HELLO 2 AUTH notrealuser 1233456} - assert {[s acl_access_denied_auth] eq [expr $current_auth_failures + 3]} - assert {[s acl_access_denied_cmd] eq $current_invalid_cmd_accesses} - assert {[s acl_access_denied_key] eq $current_invalid_key_accesses} - assert {[s acl_access_denied_channel] eq $current_invalid_channel_accesses} - } - - # If a user try to access an unauthorized command the metric increases - test {ACL-Metrics invalid command accesses} { - set current_auth_failures [s acl_access_denied_auth] - set current_invalid_cmd_accesses [s acl_access_denied_cmd] - set current_invalid_key_accesses [s acl_access_denied_key] - set current_invalid_channel_accesses [s acl_access_denied_channel] - r ACL setuser invalidcmduser on >passwd nocommands - r AUTH invalidcmduser passwd - assert_error "*no permissions to run the * command*" {r acl list} - r AUTH default "" - assert {[s acl_access_denied_auth] eq $current_auth_failures} - assert {[s acl_access_denied_cmd] eq [expr $current_invalid_cmd_accesses + 1]} - assert {[s acl_access_denied_key] eq $current_invalid_key_accesses} - assert {[s acl_access_denied_channel] eq $current_invalid_channel_accesses} - } - - # If a user try to access an unauthorized key the metric increases - test {ACL-Metrics invalid key accesses} { - set current_auth_failures [s acl_access_denied_auth] - set current_invalid_cmd_accesses [s acl_access_denied_cmd] - set current_invalid_key_accesses [s acl_access_denied_key] - set current_invalid_channel_accesses [s acl_access_denied_channel] - r ACL setuser invalidkeyuser on >passwd resetkeys allcommands - r AUTH invalidkeyuser passwd - assert_error "*NOPERM*key*" {r get x} - r AUTH default "" - assert {[s acl_access_denied_auth] eq $current_auth_failures} - assert {[s acl_access_denied_cmd] eq $current_invalid_cmd_accesses} - assert {[s acl_access_denied_key] eq [expr $current_invalid_key_accesses + 1]} - assert {[s acl_access_denied_channel] eq $current_invalid_channel_accesses} - } - - # If a user try to access an unauthorized channel the metric increases - test {ACL-Metrics invalid channels accesses} { - set current_auth_failures [s acl_access_denied_auth] - set current_invalid_cmd_accesses [s acl_access_denied_cmd] - set current_invalid_key_accesses [s acl_access_denied_key] - set current_invalid_channel_accesses [s acl_access_denied_channel] - r ACL setuser invalidchanneluser on >passwd resetchannels allcommands - r AUTH invalidkeyuser passwd - assert_error "*NOPERM*channel*" {r subscribe x} - r AUTH default "" - assert {[s acl_access_denied_auth] eq $current_auth_failures} - assert {[s acl_access_denied_cmd] eq $current_invalid_cmd_accesses} - assert {[s acl_access_denied_key] eq $current_invalid_key_accesses} - assert {[s acl_access_denied_channel] eq [expr $current_invalid_channel_accesses + 1]} - } -} - -set server_path [tmpdir "server.acl"] -exec cp -f tests/assets/user.acl $server_path -start_server [list overrides [list "dir" $server_path "acl-pubsub-default" "allchannels" "aclfile" "user.acl"] tags [list "external:skip"]] { - # user alice on allcommands allkeys &* >alice - # user bob on -@all +@set +acl ~set* &* >bob - # user default on nopass ~* &* +@all - - test {default: load from include file, can access any channels} { - r SUBSCRIBE foo - r PSUBSCRIBE bar* - r UNSUBSCRIBE - r PUNSUBSCRIBE - r PUBLISH hello world - } - - test {default: with config acl-pubsub-default allchannels after reset, can access any channels} { - r ACL setuser default reset on nopass ~* +@all - r SUBSCRIBE foo - r PSUBSCRIBE bar* - r UNSUBSCRIBE - r PUNSUBSCRIBE - r PUBLISH hello world - } - - test {default: with config acl-pubsub-default resetchannels after reset, can not access any channels} { - r CONFIG SET acl-pubsub-default resetchannels - r ACL setuser default reset on nopass ~* +@all - assert_error {*NOPERM*channel*} {r SUBSCRIBE foo} - assert_error {*NOPERM*channel*} {r PSUBSCRIBE bar*} - assert_error {*NOPERM*channel*} {r PUBLISH hello world} - r CONFIG SET acl-pubsub-default resetchannels - } - - test {Alice: can execute all command} { - r AUTH alice alice - assert_equal "alice" [r acl whoami] - r SET key value - } - - test {Bob: just execute @set and acl command} { - r AUTH bob bob - assert_equal "bob" [r acl whoami] - assert_equal "3" [r sadd set 1 2 3] - catch {r SET key value} e - set e - } {*NOPERM*set*} - - test {ACL LOAD only disconnects affected clients} { - reconnect - r ACL SETUSER doug on nopass resetchannels &test* +@all ~* - - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - $rd1 AUTH alice alice - $rd1 read - $rd1 SUBSCRIBE test1 - $rd1 read - - $rd2 AUTH doug doug - $rd2 read - $rd2 SUBSCRIBE test1 - $rd2 read - - r ACL LOAD - r PUBLISH test1 test-message - - # Permissions for 'alice' haven't changed, so they should still be connected - assert_match {*test-message*} [$rd1 read] - - # 'doug' no longer has access to "test1" channel, so they should get disconnected - catch {$rd2 read} e - assert_match {*I/O error*} $e - - $rd1 close - $rd2 close - } - - test {ACL LOAD disconnects affected subscriber} { - # This test covers the case that the LOAD is requested over the subscriber - reconnect - r ACL SETUSER doug on nopass resetchannels &test* +@all ~* - - set rd1 [redis_deferring_client] - - # we must use RESP 3 since AUTH command is not supported over a subscribed client with RESP2 - $rd1 HELLO 3 AUTH doug doug - $rd1 read - $rd1 SUBSCRIBE test1 - $rd1 read - - $rd1 ACL LOAD - assert_match {OK} [$rd1 read] - - # 'doug' no longer has access to "test1" channel, so they should get disconnected - catch {$rd1 read} e - assert_match {*I/O error*} $e - - $rd1 close - } - - test {ACL LOAD disconnects clients of deleted users} { - reconnect - r ACL SETUSER mortimer on >mortimer ~* &* +@all - - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - $rd1 AUTH alice alice - $rd1 read - $rd1 SUBSCRIBE test - $rd1 read - - $rd2 AUTH mortimer mortimer - $rd2 read - $rd2 SUBSCRIBE test - $rd2 read - - r ACL LOAD - r PUBLISH test test-message - - # Permissions for 'alice' haven't changed, so they should still be connected - assert_match {*test-message*} [$rd1 read] - - # 'mortimer' has been deleted, so their client should get disconnected - catch {$rd2 read} e - assert_match {*I/O error*} $e - - $rd1 close - $rd2 close - } - - test {ACL load and save} { - r ACL setuser eve +get allkeys >eve on - r ACL save - - r ACL load - - # Clients should not be disconnected since permissions haven't changed - - r AUTH alice alice - r SET key value - r AUTH eve eve - r GET key - catch {r SET key value} e - set e - } {*NOPERM*set*} - - test {ACL load and save with restricted channels} { - r AUTH alice alice - r ACL setuser harry on nopass resetchannels &test +@all ~* - r ACL save - - r ACL load - - # Clients should not be disconnected since permissions haven't changed - - r AUTH harry anything - r publish test bar - catch {r publish test1 bar} e - r ACL deluser harry - set e - } {*NOPERM*channel*} - - set server_path [tmpdir "server.acl"] - exec cp -f tests/assets/user.acl $server_path - start_server [list overrides [list "dir" $server_path "acl-pubsub-default" "allchannels" "aclfile" "user.acl"] tags [list "repl" "external:skip"]] { - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - set slave [srv 0 client] - - test {First server should have role slave after SLAVEOF} { - $slave slaveof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - } - - test {ACL load on replica when connected to replica} { - assert_match {OK} [$slave ACL LOAD] - } - } -} - -set server_path [tmpdir "resetchannels.acl"] -exec cp -f tests/assets/nodefaultuser.acl $server_path -exec cp -f tests/assets/default.conf $server_path -start_server [list overrides [list "dir" $server_path "aclfile" "nodefaultuser.acl"] tags [list "external:skip"]] { - - test {Default user has access to all channels irrespective of flag} { - set channelinfo [dict get [r ACL getuser default] channels] - assert_equal "&*" $channelinfo - set channelinfo [dict get [r ACL getuser alice] channels] - assert_equal "" $channelinfo - } - - test {Update acl-pubsub-default, existing users shouldn't get affected} { - set channelinfo [dict get [r ACL getuser default] channels] - assert_equal "&*" $channelinfo - r CONFIG set acl-pubsub-default allchannels - r ACL setuser mydefault - set channelinfo [dict get [r ACL getuser mydefault] channels] - assert_equal "&*" $channelinfo - r CONFIG set acl-pubsub-default resetchannels - set channelinfo [dict get [r ACL getuser mydefault] channels] - assert_equal "&*" $channelinfo - } - - test {Single channel is valid} { - r ACL setuser onechannel &test - set channelinfo [dict get [r ACL getuser onechannel] channels] - assert_equal "&test" $channelinfo - r ACL deluser onechannel - } - - test {Single channel is not valid with allchannels} { - r CONFIG set acl-pubsub-default allchannels - catch {r ACL setuser onechannel &test} err - r CONFIG set acl-pubsub-default resetchannels - set err - } {*start with an empty list of channels*} -} - -set server_path [tmpdir "resetchannels.acl"] -exec cp -f tests/assets/nodefaultuser.acl $server_path -exec cp -f tests/assets/default.conf $server_path -start_server [list overrides [list "dir" $server_path "acl-pubsub-default" "resetchannels" "aclfile" "nodefaultuser.acl"] tags [list "external:skip"]] { - - test {Only default user has access to all channels irrespective of flag} { - set channelinfo [dict get [r ACL getuser default] channels] - assert_equal "&*" $channelinfo - set channelinfo [dict get [r ACL getuser alice] channels] - assert_equal "" $channelinfo - } -} - - -start_server {overrides {user "default on nopass ~* +@all"} tags {"external:skip"}} { - test {default: load from config file, without channel permission default user can't access any channels} { - catch {r SUBSCRIBE foo} e - set e - } {*NOPERM*channel*} -} - -start_server {overrides {user "default on nopass ~* &* +@all"} tags {"external:skip"}} { - test {default: load from config file with all channels permissions} { - r SUBSCRIBE foo - r PSUBSCRIBE bar* - r UNSUBSCRIBE - r PUNSUBSCRIBE - r PUBLISH hello world - } -} - -set server_path [tmpdir "duplicate.acl"] -exec cp -f tests/assets/user.acl $server_path -exec cp -f tests/assets/default.conf $server_path -start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"] tags [list "external:skip"]] { - - test {Test loading an ACL file with duplicate users} { - exec cp -f tests/assets/user.acl $server_path - - # Corrupt the ACL file - set corruption "\nuser alice on nopass ~* -@all" - exec echo $corruption >> $server_path/user.acl - catch {r ACL LOAD} err - assert_match {*Duplicate user 'alice' found*} $err - - # Verify the previous users still exist - # NOTE: A missing user evaluates to an empty - # string. - assert {[r ACL GETUSER alice] != ""} - assert_equal [dict get [r ACL GETUSER alice] commands] "+@all" - assert {[r ACL GETUSER bob] != ""} - assert {[r ACL GETUSER default] != ""} - } - - test {Test loading an ACL file with duplicate default user} { - exec cp -f tests/assets/user.acl $server_path - - # Corrupt the ACL file - set corruption "\nuser default on nopass ~* -@all" - exec echo $corruption >> $server_path/user.acl - catch {r ACL LOAD} err - assert_match {*Duplicate user 'default' found*} $err - - # Verify the previous users still exist - # NOTE: A missing user evaluates to an empty - # string. - assert {[r ACL GETUSER alice] != ""} - assert_equal [dict get [r ACL GETUSER alice] commands] "+@all" - assert {[r ACL GETUSER bob] != ""} - assert {[r ACL GETUSER default] != ""} - } - - test {Test loading duplicate users in config on startup} { - catch {exec src/redis-server --user foo --user foo} err - assert_match {*Duplicate user*} $err - - catch {exec src/redis-server --user default --user default} err - assert_match {*Duplicate user*} $err - } {} {external:skip} -} - -start_server {overrides {user "default on nopass ~* +@all -flushdb"} tags {acl external:skip}} { - test {ACL from config file and config rewrite} { - assert_error {NOPERM *} {r flushdb} - r config rewrite - restart_server 0 true false - assert_error {NOPERM *} {r flushdb} - } -} - diff --git a/examples/redis-unstable/tests/unit/aofrw.tcl b/examples/redis-unstable/tests/unit/aofrw.tcl deleted file mode 100644 index 11324e1..0000000 --- a/examples/redis-unstable/tests/unit/aofrw.tcl +++ /dev/null @@ -1,232 +0,0 @@ -# This unit has the potential to create huge .reqres files, causing log-req-res-validator.py to run for a very long time... -# Since this unit doesn't do anything worth validating, reply_schema-wise, we decided to skip it -start_server {tags {"aofrw external:skip logreqres:skip"} overrides {save {}}} { - # Enable the AOF - r config set appendonly yes - r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. - waitForBgrewriteaof r - - foreach rdbpre {yes no} { - r config set aof-use-rdb-preamble $rdbpre - test "AOF rewrite during write load: RDB preamble=$rdbpre" { - # Start a write load for 10 seconds - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - set load_handle0 [start_write_load $master_host $master_port 10] - set load_handle1 [start_write_load $master_host $master_port 10] - set load_handle2 [start_write_load $master_host $master_port 10] - set load_handle3 [start_write_load $master_host $master_port 10] - set load_handle4 [start_write_load $master_host $master_port 10] - - # Make sure the instance is really receiving data - wait_for_condition 50 100 { - [r dbsize] > 0 - } else { - fail "No write load detected." - } - - # After 3 seconds, start a rewrite, while the write load is still - # active. - after 3000 - r bgrewriteaof - waitForBgrewriteaof r - - # Let it run a bit more so that we'll append some data to the new - # AOF. - after 1000 - - # Stop the processes generating the load if they are still active - stop_write_load $load_handle0 - stop_write_load $load_handle1 - stop_write_load $load_handle2 - stop_write_load $load_handle3 - stop_write_load $load_handle4 - - # Make sure no more commands processed, before taking debug digest - wait_load_handlers_disconnected - - # Get the data set digest - set d1 [debug_digest] - - # Load the AOF - r debug loadaof - set d2 [debug_digest] - - # Make sure they are the same - assert {$d1 eq $d2} - } - } -} - -start_server {tags {"aofrw external:skip debug_defrag:skip"} overrides {aof-use-rdb-preamble no}} { - test {Turning off AOF kills the background writing child if any} { - r config set appendonly yes - waitForBgrewriteaof r - - # start a slow AOFRW - r set k v - r config set rdb-key-save-delay 10000000 - r bgrewriteaof - - # disable AOF and wait for the child to be killed - r config set appendonly no - wait_for_condition 50 100 { - [string match {*Killing*AOF*child*} [exec tail -5 < [srv 0 stdout]]] - } else { - fail "Can't find 'Killing AOF child' into recent logs" - } - r config set rdb-key-save-delay 0 - } - - foreach d {string int} { - foreach e {listpack quicklist} { - test "AOF rewrite of list with $e encoding, $d data" { - r flushall - if {$e eq {listpack}} { - r config set list-max-listpack-size -2 - set len 10 - } else { - r config set list-max-listpack-size 10 - set len 1000 - } - for {set j 0} {$j < $len} {incr j} { - if {$d eq {string}} { - set data [randstring 0 16 alpha] - } else { - set data [randomInt 4000000000] - } - r lpush key $data - } - assert_equal [r object encoding key] $e - set d1 [debug_digest] - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - set d2 [debug_digest] - if {$d1 ne $d2} { - error "assertion:$d1 is not equal to $d2" - } - } - } - } - - foreach d {string int} { - foreach e {intset hashtable} { - test "AOF rewrite of set with $e encoding, $d data" { - r flushall - if {$e eq {intset}} {set len 10} else {set len 1000} - for {set j 0} {$j < $len} {incr j} { - if {$d eq {string}} { - set data [randstring 0 16 alpha] - } else { - set data [randomInt 4000000000] - } - r sadd key $data - } - if {$d ne {string}} { - assert_equal [r object encoding key] $e - } - set d1 [debug_digest] - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - set d2 [debug_digest] - if {$d1 ne $d2} { - error "assertion:$d1 is not equal to $d2" - } - } - } - } - - foreach d {string int} { - foreach e {listpack hashtable} { - test "AOF rewrite of hash with $e encoding, $d data" { - r flushall - if {$e eq {listpack}} {set len 10} else {set len 1000} - for {set j 0} {$j < $len} {incr j} { - if {$d eq {string}} { - set data [randstring 0 16 alpha] - } else { - set data [randomInt 4000000000] - } - r hset key $data $data - } - assert_equal [r object encoding key] $e - set d1 [debug_digest] - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - set d2 [debug_digest] - if {$d1 ne $d2} { - error "assertion:$d1 is not equal to $d2" - } - } - } - } - - foreach d {string int} { - foreach e {listpack skiplist} { - test "AOF rewrite of zset with $e encoding, $d data" { - r flushall - if {$e eq {listpack}} {set len 10} else {set len 1000} - for {set j 0} {$j < $len} {incr j} { - if {$d eq {string}} { - set data [randstring 0 16 alpha] - } else { - set data [randomInt 4000000000] - } - r zadd key [expr rand()] $data - } - assert_equal [r object encoding key] $e - set d1 [debug_digest] - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - set d2 [debug_digest] - if {$d1 ne $d2} { - error "assertion:$d1 is not equal to $d2" - } - } - } - } - - test "AOF rewrite functions" { - r flushall - r FUNCTION LOAD {#!lua name=test - redis.register_function('test', function() return 1 end) - } - r bgrewriteaof - waitForBgrewriteaof r - r function flush - r debug loadaof - assert_equal [r fcall test 0] 1 - r FUNCTION LIST - } {{library_name test engine LUA functions {{name test description {} flags {}}}}} - - test {BGREWRITEAOF is delayed if BGSAVE is in progress} { - r flushall - r set k v - r config set rdb-key-save-delay 10000000 - r bgsave - assert_match {*scheduled*} [r bgrewriteaof] - assert_equal [s aof_rewrite_scheduled] 1 - r config set rdb-key-save-delay 0 - catch {exec kill -9 [get_child_pid 0]} - while {[s aof_rewrite_scheduled] eq 1} { - after 100 - } - } - - test {BGREWRITEAOF is refused if already in progress} { - r config set aof-use-rdb-preamble yes - r config set rdb-key-save-delay 10000000 - catch { - r bgrewriteaof - r bgrewriteaof - } e - assert_match {*ERR*already*} $e - r config set rdb-key-save-delay 0 - catch {exec kill -9 [get_child_pid 0]} - } -} diff --git a/examples/redis-unstable/tests/unit/auth.tcl b/examples/redis-unstable/tests/unit/auth.tcl deleted file mode 100644 index 89fc4e0..0000000 --- a/examples/redis-unstable/tests/unit/auth.tcl +++ /dev/null @@ -1,126 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -start_server {tags {"auth external:skip"}} { - test {AUTH fails if there is no password configured server side} { - catch {r auth foo} err - set _ $err - } {ERR *any password*} - - test {Arity check for auth command} { - catch {r auth a b c} err - set _ $err - } {*syntax error*} -} - -start_server {tags {"auth external:skip"} overrides {requirepass foobar}} { - test {AUTH fails when a wrong password is given} { - catch {r auth wrong!} err - set _ $err - } {WRONGPASS*} - - test {Arbitrary command gives an error when AUTH is required} { - catch {r set foo bar} err - set _ $err - } {NOAUTH*} - - test {AUTH succeeds when the right password is given} { - r auth foobar - } {OK} - - test {Once AUTH succeeded we can actually send commands to the server} { - r set foo 100 - r incr foo - } {101} - - test {For unauthenticated clients multibulk and bulk length are limited} { - set rr [redis [srv "host"] [srv "port"] 0 $::tls] - $rr write "*100\r\n" - $rr flush - catch {[$rr read]} e - assert_match {*unauthenticated multibulk length*} $e - $rr close - - set rr [redis [srv "host"] [srv "port"] 0 $::tls] - $rr write "*1\r\n\$100000000\r\n" - $rr flush - catch {[$rr read]} e - assert_match {*unauthenticated bulk length*} $e - $rr close - } - - test {For unauthenticated clients output buffer is limited} { - set rr [redis [srv "host"] [srv "port"] 1 $::tls] - $rr SET x 5 - catch {[$rr read]} e - assert_match {*NOAUTH Authentication required*} $e - - # Fill the output buffer in a loop without reading it and make - # sure the client disconnected. - # Considering the socket eat some of the replies, we are testing - # that such client can't consume more than few MB's. - catch { - for {set j 0} {$j < 1000000} {incr j} { - $rr SET x 5 - } - } e - assert_match {I/O error reading reply} $e - } -} - -start_server {tags {"auth_binary_password external:skip"}} { - test {AUTH fails when binary password is wrong} { - r config set requirepass "abc\x00def" - catch {r auth abc} err - set _ $err - } {WRONGPASS*} - - test {AUTH succeeds when binary password is correct} { - r config set requirepass "abc\x00def" - r auth "abc\x00def" - } {OK} - - start_server {tags {"masterauth"}} { - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - set slave [srv 0 client] - - foreach rdbchannel {yes no} { - test "MASTERAUTH test with binary password rdbchannel=$rdbchannel" { - $slave slaveof no one - $master config set requirepass "abc\x00def" - $master config set repl-rdb-channel $rdbchannel - - # Configure the replica with masterauth - set loglines [count_log_lines 0] - $slave config set masterauth "abc" - $slave config set repl-rdb-channel $rdbchannel - $slave slaveof $master_host $master_port - - # Verify replica is not able to sync with master - wait_for_log_messages 0 {"*Unable to AUTH to MASTER*"} $loglines 1000 10 - assert_equal {down} [s 0 master_link_status] - - # Test replica with the correct masterauth - $slave config set masterauth "abc\x00def" - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Can't turn the instance into a replica" - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/bitfield.tcl b/examples/redis-unstable/tests/unit/bitfield.tcl deleted file mode 100644 index 21091aa..0000000 --- a/examples/redis-unstable/tests/unit/bitfield.tcl +++ /dev/null @@ -1,263 +0,0 @@ -start_server {tags {"bitops"}} { - test {BITFIELD signed SET and GET basics} { - r del bits - set results {} - lappend results [r bitfield bits set i8 0 -100] - lappend results [r bitfield bits set i8 0 101] - lappend results [r bitfield bits get i8 0] - set results - } {0 -100 101} - - test {BITFIELD unsigned SET and GET basics} { - r del bits - set results {} - lappend results [r bitfield bits set u8 0 255] - lappend results [r bitfield bits set u8 0 100] - lappend results [r bitfield bits get u8 0] - set results - } {0 255 100} - - test {BITFIELD signed SET and GET together} { - r del bits - set results [r bitfield bits set i8 0 255 set i8 0 100 get i8 0] - } {0 -1 100} - - test {BITFIELD unsigned with SET, GET and INCRBY arguments} { - r del bits - set results [r bitfield bits set u8 0 255 incrby u8 0 100 get u8 0] - } {0 99 99} - - test {BITFIELD with only key as argument} { - r del bits - set result [r bitfield bits] - assert {$result eq {}} - } - - test {BITFIELD # form} { - r del bits - set results {} - r bitfield bits set u8 #0 65 - r bitfield bits set u8 #1 66 - r bitfield bits set u8 #2 67 - r get bits - } {ABC} - - test {BITFIELD basic INCRBY form} { - r del bits - set results {} - r bitfield bits set u8 #0 10 - lappend results [r bitfield bits incrby u8 #0 100] - lappend results [r bitfield bits incrby u8 #0 100] - set results - } {110 210} - - test {BITFIELD chaining of multiple commands} { - r del bits - set results {} - r bitfield bits set u8 #0 10 - lappend results [r bitfield bits incrby u8 #0 100 incrby u8 #0 100] - set results - } {{110 210}} - - test {BITFIELD unsigned overflow wrap} { - r del bits - set results {} - r bitfield bits set u8 #0 100 - lappend results [r bitfield bits overflow wrap incrby u8 #0 257] - lappend results [r bitfield bits get u8 #0] - lappend results [r bitfield bits overflow wrap incrby u8 #0 255] - lappend results [r bitfield bits get u8 #0] - } {101 101 100 100} - - test {BITFIELD unsigned overflow sat} { - r del bits - set results {} - r bitfield bits set u8 #0 100 - lappend results [r bitfield bits overflow sat incrby u8 #0 257] - lappend results [r bitfield bits get u8 #0] - lappend results [r bitfield bits overflow sat incrby u8 #0 -255] - lappend results [r bitfield bits get u8 #0] - } {255 255 0 0} - - test {BITFIELD signed overflow wrap} { - r del bits - set results {} - r bitfield bits set i8 #0 100 - lappend results [r bitfield bits overflow wrap incrby i8 #0 257] - lappend results [r bitfield bits get i8 #0] - lappend results [r bitfield bits overflow wrap incrby i8 #0 255] - lappend results [r bitfield bits get i8 #0] - } {101 101 100 100} - - test {BITFIELD signed overflow sat} { - r del bits - set results {} - r bitfield bits set u8 #0 100 - lappend results [r bitfield bits overflow sat incrby i8 #0 257] - lappend results [r bitfield bits get i8 #0] - lappend results [r bitfield bits overflow sat incrby i8 #0 -255] - lappend results [r bitfield bits get i8 #0] - } {127 127 -128 -128} - - test {BITFIELD overflow detection fuzzing} { - for {set j 0} {$j < 1000} {incr j} { - set bits [expr {[randomInt 64]+1}] - set sign [randomInt 2] - set range [expr {2**$bits}] - if {$bits == 64} {set sign 1} ; # u64 is not supported by BITFIELD. - if {$sign} { - set min [expr {-($range/2)}] - set type "i$bits" - } else { - set min 0 - set type "u$bits" - } - set max [expr {$min+$range-1}] - - # Compare Tcl vs Redis - set range2 [expr {$range*2}] - set value [expr {($min*2)+[randomInt $range2]}] - set increment [expr {($min*2)+[randomInt $range2]}] - if {$value > 9223372036854775807} { - set value 9223372036854775807 - } - if {$value < -9223372036854775808} { - set value -9223372036854775808 - } - if {$increment > 9223372036854775807} { - set increment 9223372036854775807 - } - if {$increment < -9223372036854775808} { - set increment -9223372036854775808 - } - - set overflow 0 - if {$value > $max || $value < $min} {set overflow 1} - if {($value + $increment) > $max} {set overflow 1} - if {($value + $increment) < $min} {set overflow 1} - - r del bits - set res1 [r bitfield bits overflow fail set $type 0 $value] - set res2 [r bitfield bits overflow fail incrby $type 0 $increment] - - if {$overflow && [lindex $res1 0] ne {} && - [lindex $res2 0] ne {}} { - fail "OW not detected where needed: $type $value+$increment" - } - if {!$overflow && ([lindex $res1 0] eq {} || - [lindex $res2 0] eq {})} { - fail "OW detected where NOT needed: $type $value+$increment" - } - } - } - - test {BITFIELD overflow wrap fuzzing} { - for {set j 0} {$j < 1000} {incr j} { - set bits [expr {[randomInt 64]+1}] - set sign [randomInt 2] - set range [expr {2**$bits}] - if {$bits == 64} {set sign 1} ; # u64 is not supported by BITFIELD. - if {$sign} { - set min [expr {-($range/2)}] - set type "i$bits" - } else { - set min 0 - set type "u$bits" - } - set max [expr {$min+$range-1}] - - # Compare Tcl vs Redis - set range2 [expr {$range*2}] - set value [expr {($min*2)+[randomInt $range2]}] - set increment [expr {($min*2)+[randomInt $range2]}] - if {$value > 9223372036854775807} { - set value 9223372036854775807 - } - if {$value < -9223372036854775808} { - set value -9223372036854775808 - } - if {$increment > 9223372036854775807} { - set increment 9223372036854775807 - } - if {$increment < -9223372036854775808} { - set increment -9223372036854775808 - } - - r del bits - r bitfield bits overflow wrap set $type 0 $value - r bitfield bits overflow wrap incrby $type 0 $increment - set res [lindex [r bitfield bits get $type 0] 0] - - set expected 0 - if {$sign} {incr expected [expr {$max+1}]} - incr expected $value - incr expected $increment - set expected [expr {$expected % $range}] - if {$sign} {incr expected $min} - - if {$res != $expected} { - fail "WRAP error: $type $value+$increment = $res, should be $expected" - } - } - } - - test {BITFIELD regression for #3221} { - r set bits 1 - r bitfield bits get u1 0 - } {0} - - test {BITFIELD regression for #3564} { - for {set j 0} {$j < 10} {incr j} { - r del mystring - set res [r BITFIELD mystring SET i8 0 10 SET i8 64 10 INCRBY i8 10 99900] - assert {$res eq {0 0 60}} - } - r del mystring - } - - test {BITFIELD_RO with only key as argument} { - set res [r bitfield_ro bits] - assert {$res eq {}} - } - - test {BITFIELD_RO fails when write option is used} { - catch {r bitfield_ro bits set u8 0 100 get u8 0} err - assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err - } -} - -start_server {tags {"repl external:skip"}} { - start_server {} { - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - set slave [srv 0 client] - - test {BITFIELD: setup slave} { - $slave slaveof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - } - - test {BITFIELD: write on master, read on slave} { - $master del bits - assert_equal 0 [$master bitfield bits set u8 0 255] - assert_equal 255 [$master bitfield bits set u8 0 100] - wait_for_ofs_sync $master $slave - assert_equal 100 [$slave bitfield_ro bits get u8 0] - } - - test {BITFIELD_RO with only key as argument on read-only replica} { - set res [$slave bitfield_ro bits] - assert {$res eq {}} - } - - test {BITFIELD_RO fails when write option is used on read-only replica} { - catch {$slave bitfield_ro bits set u8 0 100 get u8 0} err - assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err - } - } -} diff --git a/examples/redis-unstable/tests/unit/bitops.tcl b/examples/redis-unstable/tests/unit/bitops.tcl deleted file mode 100644 index 23b2736..0000000 --- a/examples/redis-unstable/tests/unit/bitops.tcl +++ /dev/null @@ -1,706 +0,0 @@ -# Compare Redis commands against Tcl implementations of the same commands. -proc count_bits s { - binary scan $s b* bits - string length [regsub -all {0} $bits {}] -} - -# start end are bit index -proc count_bits_start_end {s start end} { - binary scan $s B* bits - string length [regsub -all {0} [string range $bits $start $end] {}] -} - -proc simulate_bit_op {op args} { - set maxlen 0 - set j 0 - set count [llength $args] - foreach a $args { - binary scan $a b* bits - set b($j) $bits - if {[string length $bits] > $maxlen} { - set maxlen [string length $bits] - } - incr j - } - for {set j 0} {$j < $count} {incr j} { - if {[string length $b($j)] < $maxlen} { - append b($j) [string repeat 0 [expr $maxlen-[string length $b($j)]]] - } - } - set out {} - for {set x 0} {$x < $maxlen} {incr x} { - set fst_bit [string range $b(0) $x $x] - set bit $fst_bit - if {[expr {$op == {diff} || $op == {diff1} || $op == {andor}}]} { - set bit "0" - } - if {$op eq {not}} {set bit [expr {!$bit}]} - set multi_cnt "0" - for {set j 1} {$j < $count} {incr j} { - set bit2 [string range $b($j) $x $x] - switch $op { - and {set bit [expr {$bit & $bit2}]} - or {set bit [expr {$bit | $bit2}]} - xor {set bit [expr {$bit ^ $bit2}]} - diff {set bit [expr {$bit | $bit2}]} - diff1 {set bit [expr {$bit | $bit2}]} - andor {set bit [expr {$bit | $bit2}]} - one { - set multi_cnt [expr $multi_cnt | {$bit & $bit2}] - set bit [expr {$bit ^ $bit2}] - set bit [expr {$bit & !$multi_cnt}] - } - } - } - switch $op { - diff { set bit [expr {$fst_bit & !$bit}] } - diff1 { set bit [expr {!$fst_bit & $bit}] } - andor { set bit [expr {$fst_bit & $bit}] } - } - - append out $bit - } - binary format b* $out -} - -start_server {tags {"bitops"}} { - test {BITCOUNT against wrong type} { - r del mylist - r lpush mylist a b c - assert_error "*WRONGTYPE*" {r bitcount mylist} - assert_error "*WRONGTYPE*" {r bitcount mylist 0 100} - - # with negative indexes where start > end - assert_error "*WRONGTYPE*" {r bitcount mylist -6 -7} - assert_error "*WRONGTYPE*" {r bitcount mylist -6 -15 bit} - } - - test {BITCOUNT returns 0 against non existing key} { - r del no-key - assert {[r bitcount no-key] == 0} - assert {[r bitcount no-key 0 1000 bit] == 0} - } - - test {BITCOUNT returns 0 with out of range indexes} { - r set str "xxxx" - assert {[r bitcount str 4 10] == 0} - assert {[r bitcount str 32 87 bit] == 0} - } - - test {BITCOUNT returns 0 with negative indexes where start > end} { - r set str "xxxx" - assert {[r bitcount str -6 -7] == 0} - assert {[r bitcount str -6 -15 bit] == 0} - - # against non existing key - r del str - assert {[r bitcount str -6 -7] == 0} - assert {[r bitcount str -6 -15 bit] == 0} - } - - catch {unset num} - foreach vec [list "" "\xaa" "\x00\x00\xff" "foobar" "123"] { - incr num - test "BITCOUNT against test vector #$num" { - r set str $vec - set count [count_bits $vec] - assert {[r bitcount str] == $count} - assert {[r bitcount str 0 -1 bit] == $count} - } - } - - test {BITCOUNT fuzzing without start/end} { - for {set j 0} {$j < 100} {incr j} { - set str [randstring 0 3000] - r set str $str - set count [count_bits $str] - assert {[r bitcount str] == $count} - assert {[r bitcount str 0 -1 bit] == $count} - } - } - - test {BITCOUNT fuzzing with start/end} { - for {set j 0} {$j < 100} {incr j} { - set str [randstring 0 3000] - r set str $str - set l [string length $str] - set start [randomInt $l] - set end [randomInt $l] - if {$start > $end} { - # Swap start and end - lassign [list $end $start] start end - } - assert {[r bitcount str $start $end] == [count_bits [string range $str $start $end]]} - } - - for {set j 0} {$j < 100} {incr j} { - set str [randstring 0 3000] - r set str $str - set l [expr [string length $str] * 8] - set start [randomInt $l] - set end [randomInt $l] - if {$start > $end} { - # Swap start and end - lassign [list $end $start] start end - } - assert {[r bitcount str $start $end bit] == [count_bits_start_end $str $start $end]} - } - } - - test {BITCOUNT with start, end} { - set s "foobar" - r set s $s - assert_equal [r bitcount s 0 -1] [count_bits "foobar"] - assert_equal [r bitcount s 1 -2] [count_bits "ooba"] - assert_equal [r bitcount s -2 1] [count_bits ""] - assert_equal [r bitcount s 0 1000] [count_bits "foobar"] - - assert_equal [r bitcount s 0 -1 bit] [count_bits $s] - assert_equal [r bitcount s 10 14 bit] [count_bits_start_end $s 10 14] - assert_equal [r bitcount s 3 14 bit] [count_bits_start_end $s 3 14] - assert_equal [r bitcount s 3 29 bit] [count_bits_start_end $s 3 29] - assert_equal [r bitcount s 10 -34 bit] [count_bits_start_end $s 10 14] - assert_equal [r bitcount s 3 -34 bit] [count_bits_start_end $s 3 14] - assert_equal [r bitcount s 3 -19 bit] [count_bits_start_end $s 3 29] - assert_equal [r bitcount s -2 1 bit] 0 - assert_equal [r bitcount s 0 1000 bit] [count_bits $s] - } - - test {BITCOUNT with illegal arguments} { - # Used to return 0 for non-existing key instead of errors - r del s - assert_error {ERR *syntax*} {r bitcount s 0} - assert_error {ERR *syntax*} {r bitcount s 0 1 hello} - assert_error {ERR *syntax*} {r bitcount s 0 1 hello hello2} - - r set s 1 - assert_error {ERR *syntax*} {r bitcount s 0} - assert_error {ERR *syntax*} {r bitcount s 0 1 hello} - assert_error {ERR *syntax*} {r bitcount s 0 1 hello hello2} - } - - test {BITCOUNT against non-integer value} { - # against existing key - r set s 1 - assert_error {ERR *not an integer*} {r bitcount s a b} - - # against non existing key - r del s - assert_error {ERR *not an integer*} {r bitcount s a b} - - # against wrong type - r lpush s a b c - assert_error {ERR *not an integer*} {r bitcount s a b} - } - - test {BITCOUNT regression test for github issue #582} { - r del foo - r setbit foo 0 1 - if {[catch {r bitcount foo 0 4294967296} e]} { - assert_match {*ERR*out of range*} $e - set _ 1 - } else { - set e - } - } {1} - - test {BITCOUNT misaligned prefix} { - r del str - r set str ab - r bitcount str 1 -1 - } {3} - - test {BITCOUNT misaligned prefix + full words + remainder} { - r del str - r set str __PPxxxxxxxxxxxxxxxxRR__ - r bitcount str 2 -3 - } {74} - - test {BITOP NOT (empty string)} { - r set s{t} "" - r bitop not dest{t} s{t} - r get dest{t} - } {} - - test {BITOP NOT (known string)} { - r set s{t} "\xaa\x00\xff\x55" - r bitop not dest{t} s{t} - r get dest{t} - } "\x55\xff\x00\xaa" - - test {BITOP NOT with multiple source keys} { - r set s{t} "\xaa\x00\xff\x55" - assert_error "ERR BITOP NOT*" { r bitop not dest{t} s{t} s{t} } - } - - test {BITOP where dest and target are the same key} { - r set s "\xaa\x00\xff\x55" - r bitop not s s - r get s - } "\x55\xff\x00\xaa" - - test {BITOP AND|OR|XOR|ONE don't change the string with single input key} { - r set a{t} "\x01\x02\xff" - r bitop and res1{t} a{t} - r bitop or res2{t} a{t} - r bitop xor res3{t} a{t} - r bitop one res4{t} a{t} - list [r get res1{t}] [r get res2{t}] [r get res3{t}] [r get res4{t}] - } [list "\x01\x02\xff" "\x01\x02\xff" "\x01\x02\xff" "\x01\x02\xff"] - - test {BITOP DIFF|DIFF1|ANDOR with one source key} { - r set s{t} "" - assert_error "ERR BITOP DIFF*" { r bitop diff dest{t} s{t} } - assert_error "ERR BITOP DIFF1*" { r bitop diff1 dest{t} s{t} } - assert_error "ERR BITOP ANDOR*" { r bitop andor dest{t} s{t} } - } - - test {BITOP missing key is considered a stream of zero} { - r set a{t} "\x01\x02\xff" - r bitop and res1{t} no-such-key{t} a{t} - r bitop or res2{t} no-such-key{t} a{t} no-such-key{t} - r bitop xor res3{t} no-such-key{t} a{t} - r bitop diff res4{t} a{t} no-such-key{t} - r bitop diff1 res5{t} a{t} no-such-key{t} - r bitop andor res6{t} a{t} no-such-key{t} - r bitop one res7{t} no-such_key{t} a{t} - list [r get res1{t}] [r get res2{t}] [r get res3{t}] [r get res4{t}] [r get res5{t}] [r get res6{t}] [r get res7{t}] - } [list "\x00\x00\x00" "\x01\x02\xff" "\x01\x02\xff" "\x01\x02\xff" "\x00\x00\x00" "\x00\x00\x00" "\x01\x02\xff"] - - test {BITOP shorter keys are zero-padded to the key with max length} { - r set a{t} "\x01\x02\xff\xff" - r set b{t} "\x01\x02\xff" - r bitop and res1{t} a{t} b{t} - r bitop or res2{t} a{t} b{t} - r bitop xor res3{t} a{t} b{t} - r bitop diff res4{t} a{t} b{t} - r bitop diff1 res5{t} a{t} b{t} - r bitop andor res6{t} a{t} b{t} - r bitop one res7{t} a{t} b{t} - list [r get res1{t}] [r get res2{t}] [r get res3{t}] [r get res4{t}] [r get res5{t}] [r get res6{t}] [r get res7{t}] - } [list "\x01\x02\xff\x00" "\x01\x02\xff\xff" "\x00\x00\x00\xff" "\x00\x00\x00\xff" "\x00\x00\x00\x00" "\x01\x02\xff\x00" "\x00\x00\x00\xff"] - - foreach op {and or xor diff diff1 andor one} { - test "BITOP $op fuzzing" { - set min_args 1 - if {[expr {$op == {diff} || $op == {diff1} || $op == {andor}}]} { - set min_args 2 - } - for {set i 0} {$i < 10} {incr i} { - r flushall - set vec {} - set veckeys {} - set numvec [expr {[randomInt 10]+$min_args}] - for {set j 0} {$j < $numvec} {incr j} { - set str [randstring 0 1000] - lappend vec $str - lappend veckeys vector_$j{t} - r set vector_$j{t} $str - } - r bitop $op target{t} {*}$veckeys - assert_equal [r get target{t}] [simulate_bit_op $op {*}$vec] - } - } - } - - test {BITOP NOT fuzzing} { - for {set i 0} {$i < 10} {incr i} { - r flushall - set str [randstring 0 1000] - r set str{t} $str - r bitop not target{t} str{t} - assert_equal [r get target{t}] [simulate_bit_op not $str] - } - } - - test {BITOP with integer encoded source objects} { - r set a{t} 1 - r set b{t} 2 - r bitop xor dest{t} a{t} b{t} a{t} - r get dest{t} - } {2} - - test {BITOP with non string source key} { - r del c{t} - r set a{t} 1 - r set b{t} 2 - r lpush c{t} foo - catch {r bitop xor dest{t} a{t} b{t} c{t} d{t}} e - set e - } {WRONGTYPE*} - - test {BITOP with empty string after non empty string (issue #529)} { - r flushdb - r set a{t} "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - r bitop or x{t} a{t} b{t} - } {32} - - test {BITPOS against wrong type} { - r del mylist - r lpush mylist a b c - assert_error "*WRONGTYPE*" {r bitpos mylist 0} - assert_error "*WRONGTYPE*" {r bitpos mylist 1 10 100} - } - - test {BITPOS will illegal arguments} { - # Used to return 0 for non-existing key instead of errors - r del s - assert_error {ERR *syntax*} {r bitpos s 0 1 hello hello2} - assert_error {ERR *syntax*} {r bitpos s 0 0 1 hello} - - r set s 1 - assert_error {ERR *syntax*} {r bitpos s 0 1 hello hello2} - assert_error {ERR *syntax*} {r bitpos s 0 0 1 hello} - } - - test {BITPOS against non-integer value} { - # against existing key - r set s 1 - assert_error {ERR *not an integer*} {r bitpos s a} - assert_error {ERR *not an integer*} {r bitpos s 0 a b} - - # against non existing key - r del s - assert_error {ERR *not an integer*} {r bitpos s b} - assert_error {ERR *not an integer*} {r bitpos s 0 a b} - - # against wrong type - r lpush s a b c - assert_error {ERR *not an integer*} {r bitpos s a} - assert_error {ERR *not an integer*} {r bitpos s 1 a b} - } - - test {BITPOS bit=0 with empty key returns 0} { - r del str - assert {[r bitpos str 0] == 0} - assert {[r bitpos str 0 0 -1 bit] == 0} - } - - test {BITPOS bit=1 with empty key returns -1} { - r del str - assert {[r bitpos str 1] == -1} - assert {[r bitpos str 1 0 -1] == -1} - } - - test {BITPOS bit=0 with string less than 1 word works} { - r set str "\xff\xf0\x00" - assert {[r bitpos str 0] == 12} - assert {[r bitpos str 0 0 -1 bit] == 12} - } - - test {BITPOS bit=1 with string less than 1 word works} { - r set str "\x00\x0f\x00" - assert {[r bitpos str 1] == 12} - assert {[r bitpos str 1 0 -1 bit] == 12} - } - - test {BITPOS bit=0 starting at unaligned address} { - r set str "\xff\xf0\x00" - assert {[r bitpos str 0 1] == 12} - assert {[r bitpos str 0 1 -1 bit] == 12} - } - - test {BITPOS bit=1 starting at unaligned address} { - r set str "\x00\x0f\xff" - assert {[r bitpos str 1 1] == 12} - assert {[r bitpos str 1 1 -1 bit] == 12} - } - - test {BITPOS bit=0 unaligned+full word+reminder} { - r del str - r set str "\xff\xff\xff" ; # Prefix - # Followed by two (or four in 32 bit systems) full words - r append str "\xff\xff\xff\xff\xff\xff\xff\xff" - r append str "\xff\xff\xff\xff\xff\xff\xff\xff" - r append str "\xff\xff\xff\xff\xff\xff\xff\xff" - # First zero bit. - r append str "\x0f" - assert {[r bitpos str 0] == 216} - assert {[r bitpos str 0 1] == 216} - assert {[r bitpos str 0 2] == 216} - assert {[r bitpos str 0 3] == 216} - assert {[r bitpos str 0 4] == 216} - assert {[r bitpos str 0 5] == 216} - assert {[r bitpos str 0 6] == 216} - assert {[r bitpos str 0 7] == 216} - assert {[r bitpos str 0 8] == 216} - - assert {[r bitpos str 0 1 -1 bit] == 216} - assert {[r bitpos str 0 9 -1 bit] == 216} - assert {[r bitpos str 0 17 -1 bit] == 216} - assert {[r bitpos str 0 25 -1 bit] == 216} - assert {[r bitpos str 0 33 -1 bit] == 216} - assert {[r bitpos str 0 41 -1 bit] == 216} - assert {[r bitpos str 0 49 -1 bit] == 216} - assert {[r bitpos str 0 57 -1 bit] == 216} - assert {[r bitpos str 0 65 -1 bit] == 216} - } - - test {BITPOS bit=1 unaligned+full word+reminder} { - r del str - r set str "\x00\x00\x00" ; # Prefix - # Followed by two (or four in 32 bit systems) full words - r append str "\x00\x00\x00\x00\x00\x00\x00\x00" - r append str "\x00\x00\x00\x00\x00\x00\x00\x00" - r append str "\x00\x00\x00\x00\x00\x00\x00\x00" - # First zero bit. - r append str "\xf0" - assert {[r bitpos str 1] == 216} - assert {[r bitpos str 1 1] == 216} - assert {[r bitpos str 1 2] == 216} - assert {[r bitpos str 1 3] == 216} - assert {[r bitpos str 1 4] == 216} - assert {[r bitpos str 1 5] == 216} - assert {[r bitpos str 1 6] == 216} - assert {[r bitpos str 1 7] == 216} - assert {[r bitpos str 1 8] == 216} - - assert {[r bitpos str 1 1 -1 bit] == 216} - assert {[r bitpos str 1 9 -1 bit] == 216} - assert {[r bitpos str 1 17 -1 bit] == 216} - assert {[r bitpos str 1 25 -1 bit] == 216} - assert {[r bitpos str 1 33 -1 bit] == 216} - assert {[r bitpos str 1 41 -1 bit] == 216} - assert {[r bitpos str 1 49 -1 bit] == 216} - assert {[r bitpos str 1 57 -1 bit] == 216} - assert {[r bitpos str 1 65 -1 bit] == 216} - } - - test {BITPOS bit=1 returns -1 if string is all 0 bits} { - r set str "" - for {set j 0} {$j < 20} {incr j} { - assert {[r bitpos str 1] == -1} - assert {[r bitpos str 1 0 -1 bit] == -1} - r append str "\x00" - } - } - - test {BITPOS bit=0 works with intervals} { - r set str "\x00\xff\x00" - assert {[r bitpos str 0 0 -1] == 0} - assert {[r bitpos str 0 1 -1] == 16} - assert {[r bitpos str 0 2 -1] == 16} - assert {[r bitpos str 0 2 200] == 16} - assert {[r bitpos str 0 1 1] == -1} - - assert {[r bitpos str 0 0 -1 bit] == 0} - assert {[r bitpos str 0 8 -1 bit] == 16} - assert {[r bitpos str 0 16 -1 bit] == 16} - assert {[r bitpos str 0 16 200 bit] == 16} - assert {[r bitpos str 0 8 8 bit] == -1} - } - - test {BITPOS bit=1 works with intervals} { - r set str "\x00\xff\x00" - assert {[r bitpos str 1 0 -1] == 8} - assert {[r bitpos str 1 1 -1] == 8} - assert {[r bitpos str 1 2 -1] == -1} - assert {[r bitpos str 1 2 200] == -1} - assert {[r bitpos str 1 1 1] == 8} - - assert {[r bitpos str 1 0 -1 bit] == 8} - assert {[r bitpos str 1 8 -1 bit] == 8} - assert {[r bitpos str 1 16 -1 bit] == -1} - assert {[r bitpos str 1 16 200 bit] == -1} - assert {[r bitpos str 1 8 8 bit] == 8} - } - - test {BITPOS bit=0 changes behavior if end is given} { - r set str "\xff\xff\xff" - assert {[r bitpos str 0] == 24} - assert {[r bitpos str 0 0] == 24} - assert {[r bitpos str 0 0 -1] == -1} - assert {[r bitpos str 0 0 -1 bit] == -1} - } - - test {SETBIT/BITFIELD only increase dirty when the value changed} { - r del foo{t} foo2{t} foo3{t} - set dirty [s rdb_changes_since_last_save] - - # Create a new key, always increase the dirty. - r setbit foo{t} 0 0 - r bitfield foo2{t} set i5 0 0 - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 2} - - # No change. - r setbit foo{t} 0 0 - r bitfield foo2{t} set i5 0 0 - set dirty3 [s rdb_changes_since_last_save] - assert {$dirty3 == $dirty2} - - # Do a change and a no change. - r setbit foo{t} 0 1 - r setbit foo{t} 0 1 - r setbit foo{t} 0 0 - r setbit foo{t} 0 0 - r bitfield foo2{t} set i5 0 1 - r bitfield foo2{t} set i5 0 1 - r bitfield foo2{t} set i5 0 0 - r bitfield foo2{t} set i5 0 0 - set dirty4 [s rdb_changes_since_last_save] - assert {$dirty4 == $dirty3 + 4} - - # BITFIELD INCRBY always increase dirty. - r bitfield foo3{t} incrby i5 0 1 - r bitfield foo3{t} incrby i5 0 1 - set dirty5 [s rdb_changes_since_last_save] - assert {$dirty5 == $dirty4 + 2} - - # Change length only - r setbit foo{t} 90 0 - r bitfield foo2{t} set i5 90 0 - set dirty6 [s rdb_changes_since_last_save] - assert {$dirty6 == $dirty5 + 2} - } - - test {BITPOS bit=1 fuzzy testing using SETBIT} { - r del str - set max 524288; # 64k - set first_one_pos -1 - for {set j 0} {$j < 1000} {incr j} { - assert {[r bitpos str 1] == $first_one_pos} - assert {[r bitpos str 1 0 -1 bit] == $first_one_pos} - set pos [randomInt $max] - r setbit str $pos 1 - if {$first_one_pos == -1 || $first_one_pos > $pos} { - # Update the position of the first 1 bit in the array - # if the bit we set is on the left of the previous one. - set first_one_pos $pos - } - } - } - - test {BITPOS bit=0 fuzzy testing using SETBIT} { - set max 524288; # 64k - set first_zero_pos $max - r set str [string repeat "\xff" [expr $max/8]] - for {set j 0} {$j < 1000} {incr j} { - assert {[r bitpos str 0] == $first_zero_pos} - if {$first_zero_pos == $max} { - assert {[r bitpos str 0 0 -1 bit] == -1} - } else { - assert {[r bitpos str 0 0 -1 bit] == $first_zero_pos} - } - set pos [randomInt $max] - r setbit str $pos 0 - if {$first_zero_pos > $pos} { - # Update the position of the first 0 bit in the array - # if the bit we clear is on the left of the previous one. - set first_zero_pos $pos - } - } - } - - # This test creates a string of 10 bytes. It has two iterations. One clears - # all the bits and sets just one bit and another set all the bits and clears - # just one bit. Each iteration loops from bit offset 0 to 79 and uses SETBIT - # to set the bit to 0 or 1, and then use BITPOS and BITCOUNT on a few mutations. - test {BITPOS/BITCOUNT fuzzy testing using SETBIT} { - # We have two start and end ranges, each range used to select a random - # position, one for start position and one for end position. - proc test_one {start1 end1 start2 end2 pos bit pos_type} { - set start [randomRange $start1 $end1] - set end [randomRange $start2 $end2] - if {$start > $end} { - # Swap start and end - lassign [list $end $start] start end - } - set startbit $start - set endbit $end - # For byte index, we need to generate the real bit index - if {[string equal $pos_type byte]} { - set startbit [expr $start << 3] - set endbit [expr ($end << 3) + 7] - } - # This means whether the test bit index is in the range. - set inrange [expr ($pos >= $startbit && $pos <= $endbit) ? 1: 0] - # For bitcount, there are four different results. - # $inrange == 0 && $bit == 0, all bits in the range are set, so $endbit - $startbit + 1 - # $inrange == 0 && $bit == 1, all bits in the range are clear, so 0 - # $inrange == 1 && $bit == 0, all bits in the range are set but one, so $endbit - $startbit - # $inrange == 1 && $bit == 1, all bits in the range are clear but one, so 1 - set res_count [expr ($endbit - $startbit + 1) * (1 - $bit) + $inrange * [expr $bit ? 1 : -1]] - assert {[r bitpos str $bit $start $end $pos_type] == [expr $inrange ? $pos : -1]} - assert {[r bitcount str $start $end $pos_type] == $res_count} - } - - r del str - set max 80; - r setbit str [expr $max - 1] 0 - set bytes [expr $max >> 3] - # First iteration sets all bits to 1, then set bit to 0 from 0 to max - 1 - # Second iteration sets all bits to 0, then set bit to 1 from 0 to max - 1 - for {set bit 0} {$bit < 2} {incr bit} { - r bitop not str str - for {set j 0} {$j < $max} {incr j} { - r setbit str $j $bit - - # First iteration tests byte index and second iteration tests bit index. - foreach {curr end pos_type} [list [expr $j >> 3] $bytes byte $j $max bit] { - # start==end set to bit position - test_one $curr $curr $curr $curr $j $bit $pos_type - # Both start and end are before bit position - if {$curr > 0} { - test_one 0 $curr 0 $curr $j $bit $pos_type - } - # Both start and end are after bit position - if {$curr < [expr $end - 1]} { - test_one [expr $curr + 1] $end [expr $curr + 1] $end $j $bit $pos_type - } - # start is before and end is after bit position - if {$curr > 0 && $curr < [expr $end - 1]} { - test_one 0 $curr [expr $curr +1] $end $j $bit $pos_type - } - } - - # restore bit - r setbit str $j [expr 1 - $bit] - } - } - } -} - -run_solo {bitops-large-memory} { -start_server {tags {"bitops"}} { - test "BIT pos larger than UINT_MAX" { - set bytes [expr (1 << 29) + 1] - set bitpos [expr (1 << 32)] - set oldval [lindex [r config get proto-max-bulk-len] 1] - r config set proto-max-bulk-len $bytes - r setbit mykey $bitpos 1 - assert_equal $bytes [r strlen mykey] - assert_equal 1 [r getbit mykey $bitpos] - assert_equal [list 128 128 -1] [r bitfield mykey get u8 $bitpos set u8 $bitpos 255 get i8 $bitpos] - assert_equal $bitpos [r bitpos mykey 1] - assert_equal $bitpos [r bitpos mykey 1 [expr $bytes - 1]] - if {$::accurate} { - # set all bits to 1 - set mega [expr (1 << 23)] - set part [string repeat "\xFF" $mega] - for {set i 0} {$i < 64} {incr i} { - r setrange mykey [expr $i * $mega] $part - } - r setrange mykey [expr $bytes - 1] "\xFF" - assert_equal [expr $bitpos + 8] [r bitcount mykey] - assert_equal -1 [r bitpos mykey 0 0 [expr $bytes - 1]] - } - r config set proto-max-bulk-len $oldval - r del mykey - } {1} {large-memory} - - test "SETBIT values larger than UINT32_MAX and lzf_compress/lzf_decompress correctly" { - set bytes [expr (1 << 32) + 1] - set bitpos [expr (1 << 35)] - set oldval [lindex [r config get proto-max-bulk-len] 1] - r config set proto-max-bulk-len $bytes - r setbit mykey $bitpos 1 - assert_equal $bytes [r strlen mykey] - assert_equal 1 [r getbit mykey $bitpos] - r debug reload ;# lzf_compress/lzf_decompress when RDB saving/loading. - assert_equal 1 [r getbit mykey $bitpos] - r config set proto-max-bulk-len $oldval - r del mykey - } {1} {large-memory needs:debug} -} -} ;#run_solo diff --git a/examples/redis-unstable/tests/unit/client-eviction.tcl b/examples/redis-unstable/tests/unit/client-eviction.tcl deleted file mode 100644 index ac2860f..0000000 --- a/examples/redis-unstable/tests/unit/client-eviction.tcl +++ /dev/null @@ -1,623 +0,0 @@ -tags {"external:skip logreqres:skip"} { - -# Get info about a redis client connection: -# name - name of client we want to query -# f - field name from "CLIENT LIST" we want to get -proc client_field {name f} { - set clients [split [string trim [r client list]] "\r\n"] - set c [lsearch -inline $clients *name=$name*] - if {![regexp $f=(\[a-zA-Z0-9-\]+) $c - res]} { - error "no client named $name found with field $f" - } - return $res -} - -proc client_exists {name} { - if {[catch { client_field $name tot-mem } e]} { - return false - } - return true -} - -proc gen_client {} { - set rr [redis_client] - set name "tst_[randstring 4 4 simplealpha]" - $rr client setname $name - assert {[client_exists $name]} - return [list $rr $name] -} - -# Sum a value across all redis client connections: -# f - the field name from "CLIENT LIST" we want to sum -proc clients_sum {f} { - set sum 0 - set clients [split [string trim [r client list]] "\r\n"] - foreach c $clients { - if {![regexp $f=(\[a-zA-Z0-9-\]+) $c - res]} { - error "field $f not found in $c" - } - incr sum $res - } - return $sum -} - -proc mb {v} { - return [expr $v * 1024 * 1024] -} - -proc kb {v} { - return [expr $v * 1024] -} - -start_server {} { - set maxmemory_clients 3000000 - r config set maxmemory-clients $maxmemory_clients - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - test "client evicted due to large argv" { - r flushdb - lassign [gen_client] rr cname - # Attempt a large multi-bulk command under eviction limit - $rr mset k v k2 [string repeat v 1000000] - assert_equal [$rr get k] v - # Attempt another command, now causing client eviction - catch { $rr mset k v k2 [string repeat v $maxmemory_clients] } e - assert {![client_exists $cname]} - $rr close - } - - test "client evicted due to large query buf" { - r flushdb - lassign [gen_client] rr cname - # Attempt to fill the query buff without completing the argument above the limit, causing client eviction - catch { - $rr write [join [list "*1\r\n\$$maxmemory_clients\r\n" [string repeat v $maxmemory_clients]] ""] - $rr flush - $rr read - } e - assert {![client_exists $cname]} - $rr close - } - - test "client evicted due to percentage of maxmemory" { - set maxmemory [mb 6] - r config set maxmemory $maxmemory - # Set client eviction threshold to 7% of maxmemory - set maxmemory_clients_p 7 - r config set maxmemory-clients $maxmemory_clients_p% - r flushdb - - set maxmemory_clients_actual [expr $maxmemory * $maxmemory_clients_p / 100] - - lassign [gen_client] rr cname - # Attempt to fill the query buff with only half the percentage threshold verify we're not disconnected - set n [expr $maxmemory_clients_actual / 2] - $rr write [join [list "*1\r\n\$$n\r\n" [string repeat v $n]] ""] - $rr flush - wait_for_condition 100 10 { - [client_field $cname tot-mem] >= $n - } else { - fail "Failed to fill qbuf for test" - } - set tot_mem [client_field $cname tot-mem] - assert {$tot_mem >= $n && $tot_mem < $maxmemory_clients_actual} - - # Attempt to fill the query buff with the percentage threshold of maxmemory and verify we're evicted - $rr close - lassign [gen_client] rr cname - catch { - $rr write [join [list "*1\r\n\$$maxmemory_clients_actual\r\n" [string repeat v $maxmemory_clients_actual]] ""] - $rr flush - } e - wait_for_condition 100 10 { - ![client_exists $cname] - } else { - fail "Failed to evict client" - } - $rr close - - # Restore settings - r config set maxmemory 0 - r config set maxmemory-clients $maxmemory_clients - } - - test "client evicted due to large multi buf" { - r flushdb - lassign [gen_client] rr cname - - # Attempt a multi-exec where sum of commands is less than maxmemory_clients - $rr multi - $rr set k [string repeat v [expr $maxmemory_clients / 4]] - $rr set k [string repeat v [expr $maxmemory_clients / 4]] - assert_equal [$rr exec] {OK OK} - - # Attempt a multi-exec where sum of commands is more than maxmemory_clients, causing client eviction - $rr multi - catch { - for {set j 0} {$j < 5} {incr j} { - $rr set k [string repeat v [expr $maxmemory_clients / 4]] - } - } e - assert {![client_exists $cname]} - $rr close - } - - test "client evicted due to watched key list" { - r flushdb - set rr [redis_client] - - # Since watched key list is a small overhead this test uses a minimal maxmemory-clients config - set temp_maxmemory_clients 200000 - r config set maxmemory-clients $temp_maxmemory_clients - - # Append watched keys until list maxes out maxmemory clients and causes client eviction - catch { - for {set j 0} {$j < $temp_maxmemory_clients} {incr j} { - $rr watch $j - } - } e - assert_match {I/O error reading reply} $e - $rr close - - # Restore config for next tests - r config set maxmemory-clients $maxmemory_clients - } - - test "client evicted due to pubsub subscriptions" { - r flushdb - - # Since pubsub subscriptions cause a small overhead this test uses a minimal maxmemory-clients config - set temp_maxmemory_clients 200000 - r config set maxmemory-clients $temp_maxmemory_clients - - # Test eviction due to pubsub patterns - set rr [redis_client] - # Add patterns until list maxes out maxmemory clients and causes client eviction - catch { - for {set j 0} {$j < $temp_maxmemory_clients} {incr j} { - $rr psubscribe $j - } - } e - assert_match {I/O error reading reply} $e - $rr close - - # Test eviction due to pubsub channels - set rr [redis_client] - # Subscribe to global channels until list maxes out maxmemory clients and causes client eviction - catch { - for {set j 0} {$j < $temp_maxmemory_clients} {incr j} { - $rr subscribe $j - } - } e - assert_match {I/O error reading reply} $e - $rr close - - # Test eviction due to sharded pubsub channels - set rr [redis_client] - # Subscribe to sharded pubsub channels until list maxes out maxmemory clients and causes client eviction - catch { - for {set j 0} {$j < $temp_maxmemory_clients} {incr j} { - $rr ssubscribe $j - } - } e - assert_match {I/O error reading reply} $e - $rr close - - # Restore config for next tests - r config set maxmemory-clients $maxmemory_clients - } - - test "client evicted due to tracking redirection" { - r flushdb - set rr [redis_client] - set redirected_c [redis_client] - $redirected_c client setname redirected_client - set redir_id [$redirected_c client id] - $redirected_c SUBSCRIBE __redis__:invalidate - $rr client tracking on redirect $redir_id bcast - # Use a big key name to fill the redirected tracking client's buffer quickly - set key_length [expr 1024*200] - set long_key [string repeat k $key_length] - # Use a script so we won't need to pass the long key name when dirtying it in the loop - set script_sha [$rr script load "redis.call('incr', '$long_key')"] - - # Pause serverCron so it won't update memory usage since we're testing the update logic when - # writing tracking redirection output - r debug pause-cron 1 - - # Read and write to same (long) key until redirected_client's buffers cause it to be evicted - catch { - while true { - set mem [client_field redirected_client tot-mem] - assert {$mem < $maxmemory_clients} - $rr evalsha $script_sha 0 - } - } e - assert_match {no client named redirected_client found*} $e - - r debug pause-cron 0 - $rr close - $redirected_c close - } {0} {needs:debug} - - test "client evicted due to client tracking prefixes" { - r flushdb - set rr [redis_client] - - # Since tracking prefixes list is a small overhead this test uses a minimal maxmemory-clients config - set temp_maxmemory_clients 200000 - r config set maxmemory-clients $temp_maxmemory_clients - - # Append tracking prefixes until list maxes out maxmemory clients and causes client eviction - # Combine more prefixes in each command to speed up the test. Because we did not actually count - # the memory usage of all prefixes, see getClientMemoryUsage, so we can not use larger prefixes - # to speed up the test here. - catch { - for {set j 0} {$j < $temp_maxmemory_clients} {incr j} { - $rr client tracking on prefix [format a%09s $j] prefix [format b%09s $j] prefix [format c%09s $j] bcast - } - } e - assert_match {I/O error reading reply} $e - $rr close - - # Restore config for next tests - r config set maxmemory-clients $maxmemory_clients - } - - test "client evicted due to output buf" { - r flushdb - r setrange k 200000 v - set rr [redis_deferring_client] - $rr client setname test_client - $rr flush - assert {[$rr read] == "OK"} - # Attempt a large response under eviction limit - $rr get k - $rr flush - assert {[string length [$rr read]] == 200001} - set mem [client_field test_client tot-mem] - assert {$mem < $maxmemory_clients} - - # Fill output buff in loop without reading it and make sure - # we're eventually disconnected, but before reaching maxmemory_clients - while true { - if { [catch { - set mem [client_field test_client tot-mem] - assert {$mem < $maxmemory_clients} - $rr get k - $rr flush - } e]} { - assert {![client_exists test_client]} - break - } - } - $rr close - } - - foreach {no_evict} {on off} { - test "client no-evict $no_evict" { - r flushdb - r client setname control - r client no-evict on ;# Avoid evicting the main connection - lassign [gen_client] rr cname - $rr client no-evict $no_evict - - # Overflow maxmemory-clients - set qbsize [expr {$maxmemory_clients + 1}] - if {[catch { - $rr write [join [list "*1\r\n\$$qbsize\r\n" [string repeat v $qbsize]] ""] - $rr flush - wait_for_condition 200 10 { - [client_field $cname qbuf] == $qbsize - } else { - fail "Failed to fill qbuf for test" - } - } e] && $no_evict == off} { - assert {![client_exists $cname]} - } elseif {$no_evict == on} { - assert {[client_field $cname tot-mem] > $maxmemory_clients} - } - $rr close - } - } -} - -start_server {} { - set server_pid [s process_id] - set maxmemory_clients [mb 10] - set obuf_limit [mb 3] - r config set maxmemory-clients $maxmemory_clients - r config set client-output-buffer-limit "normal $obuf_limit 0 0" - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - test "avoid client eviction when client is freed by output buffer limit" { - r flushdb - set obuf_size [expr {$obuf_limit + [mb 1]}] - r setrange k $obuf_size v - set rr1 [redis_client] - $rr1 client setname "qbuf-client" - set rr2 [redis_deferring_client] - $rr2 client setname "obuf-client1" - assert_equal [$rr2 read] OK - set rr3 [redis_deferring_client] - $rr3 client setname "obuf-client2" - assert_equal [$rr3 read] OK - - # Occupy client's query buff with less than output buffer limit left to exceed maxmemory-clients - set qbsize [expr {$maxmemory_clients - $obuf_size}] - $rr1 write [join [list "*1\r\n\$$qbsize\r\n" [string repeat v $qbsize]] ""] - $rr1 flush - # Wait for qbuff to be as expected - wait_for_condition 200 10 { - [client_field qbuf-client qbuf] == $qbsize - } else { - fail "Failed to fill qbuf for test" - } - - # Make the other two obuf-clients pass obuf limit and also pass maxmemory-clients - # We use two obuf-clients to make sure that even if client eviction is attempted - # between two command processing (with no sleep) we don't perform any client eviction - # because the obuf limit is enforced with precedence. - pause_process $server_pid - $rr2 get k - $rr2 flush - $rr3 get k - $rr3 flush - resume_process $server_pid - r ping ;# make sure a full event loop cycle is processed before issuing CLIENT LIST - - # wait for get commands to be processed - wait_for_condition 100 10 { - [expr {[regexp {calls=(\d+)} [cmdrstat get r] -> calls] ? $calls : 0}] >= 2 - } else { - fail "get did not arrive" - } - - # Validate obuf-clients were disconnected (because of obuf limit) - catch {client_field obuf-client1 name} e - assert_match {no client named obuf-client1 found*} $e - catch {client_field obuf-client2 name} e - assert_match {no client named obuf-client2 found*} $e - - # Validate qbuf-client is still connected and wasn't evicted - if {[lindex [r config get io-threads] 1] == 1} { - assert_equal [client_field qbuf-client name] {qbuf-client} - } - - $rr1 close - $rr2 close - $rr3 close - } -} - -start_server {} { - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - test "decrease maxmemory-clients causes client eviction" { - set maxmemory_clients [mb 4] - set client_count 10 - set qbsize [expr ($maxmemory_clients - [mb 1]) / $client_count] - r config set maxmemory-clients $maxmemory_clients - - - # Make multiple clients consume together roughly 1mb less than maxmemory_clients - set rrs {} - for {set j 0} {$j < $client_count} {incr j} { - set rr [redis_client] - lappend rrs $rr - $rr client setname client$j - $rr write [join [list "*2\r\n\$$qbsize\r\n" [string repeat v $qbsize]] ""] - $rr flush - wait_for_condition 200 10 { - [client_field client$j qbuf] >= $qbsize - } else { - fail "Failed to fill qbuf for test" - } - } - - # Make sure all clients are still connected - set connected_clients [llength [lsearch -all [split [string trim [r client list]] "\r\n"] *name=client*]] - assert {$connected_clients == $client_count} - - # Decrease maxmemory_clients and expect client eviction - r config set maxmemory-clients [expr $maxmemory_clients / 2] - wait_for_condition 200 10 { - [llength [regexp -all -inline {name=client} [r client list]]] < $client_count - } else { - fail "Failed to evict clients" - } - - foreach rr $rrs {$rr close} - } -} - -start_server {} { - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - test "evict clients only until below limit" { - set client_count 10 - set client_mem [mb 1] - r debug replybuffer resizing 0 - r config set maxmemory-clients 0 - r client setname control - r client no-evict on - - # Make multiple clients consume together roughly 1mb less than maxmemory_clients - set total_client_mem 0 - set max_client_mem 0 - set rrs {} - for {set j 0} {$j < $client_count} {incr j} { - set rr [redis_client] - lappend rrs $rr - $rr client setname client$j - $rr write [join [list "*2\r\n\$$client_mem\r\n" [string repeat v $client_mem]] ""] - $rr flush - wait_for_condition 200 10 { - [client_field client$j tot-mem] >= $client_mem - } else { - fail "Failed to fill qbuf for test" - } - # In theory all these clients should use the same amount of memory (~1mb). But in practice - # some allocators (libc) can return different allocation sizes for the same malloc argument causing - # some clients to use slightly more memory than others. We find the largest client and make sure - # all clients are roughly the same size (+-1%). Then we can safely set the client eviction limit and - # expect consistent results in the test. - set cmem [client_field client$j tot-mem] - if {$max_client_mem > 0} { - set size_ratio [expr $max_client_mem.0/$cmem.0] - assert_range $size_ratio 0.99 1.01 - } - if {$cmem > $max_client_mem} { - set max_client_mem $cmem - } - } - - # Make sure all clients are still connected - set connected_clients [llength [lsearch -all [split [string trim [r client list]] "\r\n"] *name=client*]] - assert {$connected_clients == $client_count} - - # Set maxmemory-clients to accommodate half our clients (taking into account the control client) - set maxmemory_clients [expr ($max_client_mem * $client_count) / 2 + [client_field control tot-mem]] - r config set maxmemory-clients $maxmemory_clients - - # Make sure total used memory is below maxmemory_clients - set total_client_mem [clients_sum tot-mem] - assert {$total_client_mem <= $maxmemory_clients} - - # Make sure we have only half of our clients now - wait_for_condition 200 100 { - ([lindex [r config get io-threads] 1] == 1) ? - ([llength [regexp -all -inline {name=client} [r client list]]] == $client_count / 2) : - ([llength [regexp -all -inline {name=client} [r client list]]] <= $client_count / 2) - } else { - fail "Failed to evict clients" - } - - # Restore the reply buffer resize to default - r debug replybuffer resizing 1 - - foreach rr $rrs {$rr close} - } {} {needs:debug} -} - -start_server {} { - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - test "evict clients in right order (large to small)" { - # Note that each size step needs to be at least x2 larger than previous step - # because of how the client-eviction size bucketing works - set sizes [list [kb 128] [mb 1] [mb 3]] - set clients_per_size 3 - r client setname control - r client no-evict on - r config set maxmemory-clients 0 - r debug replybuffer resizing 0 - - # Run over all sizes and create some clients using up that size - set total_client_mem 0 - set rrs {} - for {set i 0} {$i < [llength $sizes]} {incr i} { - set size [lindex $sizes $i] - - for {set j 0} {$j < $clients_per_size} {incr j} { - set rr [redis_client] - lappend rrs $rr - $rr client setname client-$i - $rr write [join [list "*2\r\n\$$size\r\n" [string repeat v $size]] ""] - $rr flush - } - set client_mem [client_field client-$i tot-mem] - - # Update our size list based on actual used up size (this is usually - # slightly more than expected because of allocator bins - assert {$client_mem >= $size} - set sizes [lreplace $sizes $i $i $client_mem] - - # Account total client memory usage - incr total_mem [expr $clients_per_size * $client_mem] - } - - # Make sure all clients are connected - set clients [split [string trim [r client list]] "\r\n"] - for {set i 0} {$i < [llength $sizes]} {incr i} { - assert_equal [llength [lsearch -all $clients "*name=client-$i *"]] $clients_per_size - } - - # For each size reduce maxmemory-clients so relevant clients should be evicted - # do this from largest to smallest - foreach size [lreverse $sizes] { - set control_mem [client_field control tot-mem] - set total_mem [expr $total_mem - $clients_per_size * $size] - # allow some tolerance when using io threads - r config set maxmemory-clients [expr $total_mem + $control_mem + 1000] - set clients [split [string trim [r client list]] "\r\n"] - # Verify only relevant clients were evicted - for {set i 0} {$i < [llength $sizes]} {incr i} { - set verify_size [lindex $sizes $i] - set count [llength [lsearch -all $clients "*name=client-$i *"]] - if {$verify_size < $size} { - assert_equal $count $clients_per_size - } else { - assert_equal $count 0 - } - } - } - - # Restore the reply buffer resize to default - r debug replybuffer resizing 1 - - foreach rr $rrs {$rr close} - } {} {needs:debug} -} - -start_server {} { - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - foreach type {"client no-evict" "maxmemory-clients disabled"} { - r flushall - r client no-evict on - r config set maxmemory-clients 0 - - test "client total memory grows during $type" { - r setrange k [mb 1] v - set rr [redis_client] - $rr client setname test_client - if {$type eq "client no-evict"} { - $rr client no-evict on - r config set maxmemory-clients 1 - } - $rr deferred 1 - - # Fill output buffer in loop without reading it and make sure - # the tot-mem of client has increased (OS buffers didn't swallow it) - # and eviction not occurring. - while {true} { - $rr get k - $rr flush - after 10 - if {[client_field test_client tot-mem] > [mb 10]} { - break - } - } - - # Trigger the client eviction, by flipping the no-evict flag to off - if {$type eq "client no-evict"} { - $rr client no-evict off - } else { - r config set maxmemory-clients 1 - } - - # wait for the client to be disconnected - wait_for_condition 5000 50 { - ![client_exists test_client] - } else { - puts [r client list] - fail "client was not disconnected" - } - $rr close - } - } -} - -} ;# tags - diff --git a/examples/redis-unstable/tests/unit/cluster/announced-endpoints.tcl b/examples/redis-unstable/tests/unit/cluster/announced-endpoints.tcl deleted file mode 100644 index a37ca58..0000000 --- a/examples/redis-unstable/tests/unit/cluster/announced-endpoints.tcl +++ /dev/null @@ -1,75 +0,0 @@ -start_cluster 2 2 {tags {external:skip cluster}} { - - test "Test change cluster-announce-port and cluster-announce-tls-port at runtime" { - if {$::tls} { - set baseport [lindex [R 0 config get tls-port] 1] - } else { - set baseport [lindex [R 0 config get port] 1] - } - set count [expr [llength $::servers] + 1] - set used_port [find_available_port $baseport $count] - - R 0 config set cluster-announce-tls-port $used_port - R 0 config set cluster-announce-port $used_port - - assert_match "*:$used_port@*" [R 0 CLUSTER NODES] - wait_for_condition 50 100 { - [string match "*:$used_port@*" [R 1 CLUSTER NODES]] - } else { - fail "Cluster announced port was not propagated via gossip" - } - - R 0 config set cluster-announce-tls-port 0 - R 0 config set cluster-announce-port 0 - assert_match "*:$baseport@*" [R 0 CLUSTER NODES] - } - - test "Test change cluster-announce-bus-port at runtime" { - if {$::tls} { - set baseport [lindex [R 0 config get tls-port] 1] - } else { - set baseport [lindex [R 0 config get port] 1] - } - set count [expr [llength $::servers] + 1] - set used_port [find_available_port $baseport $count] - - # Verify config set cluster-announce-bus-port - R 0 config set cluster-announce-bus-port $used_port - assert_match "*@$used_port *" [R 0 CLUSTER NODES] - wait_for_condition 50 100 { - [string match "*@$used_port *" [R 1 CLUSTER NODES]] - } else { - fail "Cluster announced port was not propagated via gossip" - } - - # Verify restore default cluster-announce-port - set base_bus_port [expr $baseport + 10000] - R 0 config set cluster-announce-bus-port 0 - assert_match "*@$base_bus_port *" [R 0 CLUSTER NODES] - } - - test "CONFIG SET port updates cluster-announced port" { - set count [expr [llength $::servers] + 1] - # Get the original port and change to new_port - if {$::tls} { - set orig_port [lindex [R 0 config get tls-port] 1] - } else { - set orig_port [lindex [R 0 config get port] 1] - } - assert {$orig_port != ""} - set new_port [find_available_port $orig_port $count] - - if {$::tls} { - R 0 config set tls-port $new_port - } else { - R 0 config set port $new_port - } - - # Verify that the new port appears in the output of cluster slots - wait_for_condition 50 100 { - [string match "*$new_port*" [R 0 cluster slots]] - } else { - fail "Cluster announced port was not updated in cluster slots" - } - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/atomic-slot-migration.tcl b/examples/redis-unstable/tests/unit/cluster/atomic-slot-migration.tcl deleted file mode 100644 index f04257f..0000000 --- a/examples/redis-unstable/tests/unit/cluster/atomic-slot-migration.tcl +++ /dev/null @@ -1,3063 +0,0 @@ -set ::slot_prefixes [dict create \ - 0 "{06S}" \ - 1 "{Qi}" \ - 2 "{5L5}" \ - 3 "{4Iu}" \ - 4 "{4gY}" \ - 5 "{460}" \ - 6 "{1Y7}" \ - 7 "{1LV}" \ - 101 "{1j2}" \ - 102 "{75V}" \ - 103 "{bno}" \ - 5462 "{450}"\ - 5463 "{4dY}"\ - 6000 "{4L7}" \ - 6001 "{4YV}" \ - 6002 "{0bx}" \ - 6003 "{AJ}" \ - 6004 "{of}" \ - 16383 "{6ZJ}" \ -] - -# Helper functions -proc get_port {node_id} { - if {$::tls} { - return [lindex [R $node_id config get tls-port] 1] - } else { - return [lindex [R $node_id config get port] 1] - } -} - -# return the prefix for the given slot -proc slot_prefix {slot} { - return [dict get $::slot_prefixes $slot] -} - -# return a key for the given slot -proc slot_key {slot {suffix ""}} { - return "[slot_prefix $slot]$suffix" -} - -# Populate a slot with keys -# TODO: Consider merging with populate() -proc populate_slot {num args} { - # Default values - set prefix "key:" - set size 3 - set idx 0 - set prints false - set expires 0 - set slot -1 - - # Parse named arguments - foreach {key value} $args { - switch -- $key { - -prefix { set prefix $value } - -size { set size $value } - -idx { set idx $value } - -prints { set prints $value } - -expires { set expires $value } - -slot { set slot $value } - default { error "Unknown option: $key" } - } - } - - # If slot is specified, use slot prefix from table - if {$slot >= 0} { - if {[dict exists $::slot_prefixes $slot]} { - set prefix [dict get $::slot_prefixes $slot] - } else { - error "Slot $slot not supported in slot_prefixes table, add it manually" - } - } - - R $idx deferred 1 - if {$num > 16} {set pipeline 16} else {set pipeline $num} - set val [string repeat A $size] - for {set j 0} {$j < $pipeline} {incr j} { - if {$expires > 0} { - R $idx set $prefix$j $val ex $expires - } else { - R $idx set $prefix$j $val - } - if {$prints} {puts $j} - } - for {} {$j < $num} {incr j} { - if {$expires > 0} { - R $idx set $prefix$j $val ex $expires - } else { - R $idx set $prefix$j $val - } - R $idx read - if {$prints} {puts $j} - } - for {set j 0} {$j < $pipeline} {incr j} { - R $idx read - if {$prints} {puts $j} - } - R $idx deferred 0 -} - -# Return 1 if all instances are idle -proc asm_all_instances_idle {total} { - for {set i 0} {$i < $total} {incr i} { - if {[CI $i cluster_slot_migration_active_tasks] != 0} { return 0 } - if {[CI $i cluster_slot_migration_active_trim_running] != 0} { return 0 } - } - return 1 -} - -# Wait for all ASM tasks to complete in the cluster -proc wait_for_asm_done {} { - set total_instances [expr {$::cluster_master_nodes + $::cluster_replica_nodes}] - - wait_for_condition 1000 10 { - [asm_all_instances_idle $total_instances] == 1 - } else { - # Print the number of active tasks on each instance - for {set i 0} {$i < $total_instances} {incr i} { - set migration_count [CI $i cluster_slot_migration_active_tasks] - set trim_count [CI $i cluster_slot_migration_active_trim_running] - puts "Instance $i: migration_tasks=$migration_count, trim_tasks=$trim_count" - } - fail "ASM tasks did not complete on all instances" - } - # wait all nodes to reach the same cluster config after ASM - wait_for_cluster_propagation -} - -proc failover_and_wait_for_done {node_id {failover_arg ""}} { - set max_attempts 5 - for {set attempt 1} {$attempt <= $max_attempts} {incr attempt} { - if {$failover_arg eq ""} { - R $node_id cluster failover - } else { - R $node_id cluster failover $failover_arg - } - - set completed 1 - wait_for_condition 1000 10 { - [string match "*master*" [R $node_id role]] - } else { - set completed 0 - } - - if {$completed} { - wait_for_cluster_propagation - return - } - } - fail "Failover did not complete after $max_attempts attempts for node $node_id" -} - -proc migration_status {node_id task_id field} { - set status [R $node_id CLUSTER MIGRATION STATUS ID $task_id] - - # STATUS ID returns single task, so get first element - if {[llength $status] == 0} { - return "" - } - - set task_status [lindex $status 0] - set field_value "" - - # Parse the key-value pairs in the task - for {set i 0} {$i < [llength $task_status]} {incr i 2} { - set key [lindex $task_status $i] - set value [lindex $task_status [expr $i + 1]] - - if {$key eq $field} { - set field_value $value - break - } - } - - return $field_value -} - -# Setup slot migration test with keys and delay, then start migration -# Returns the task_id for the migration -proc setup_slot_migration_with_delay {src_node dst_node start_slot end_slot {keys 2} {delay 1000000}} { - # Two keys on the start slot - populate_slot $keys -idx $src_node -slot $start_slot - - # we set a delay to ensure migration takes time for testing, - # with default parameters, two keys cost 2s to save - R $src_node config set rdb-key-save-delay $delay - - # migrate slot range from src_node to dst_node - set task_id [R $dst_node CLUSTER MIGRATION IMPORT $start_slot $end_slot] - wait_for_condition 2000 10 { - [string match {*send-bulk-and-stream*} [migration_status $src_node $task_id state]] - } else { - fail "ASM task did not start" - } - - return $task_id -} - -# Helper function to clear module internal event logs -proc clear_module_event_log {} { - for {set i 0} {$i < $::cluster_master_nodes + $::cluster_replica_nodes} {incr i} { - R $i asm.clear_event_log - } -} - -proc reset_default_trim_method {} { - for {set i 0} {$i < $::cluster_master_nodes + $::cluster_replica_nodes} {incr i} { - R $i debug asm-trim-method default - } -} - -start_cluster 3 3 {tags {external:skip cluster} overrides {cluster-node-timeout 60000 cluster-allow-replica-migration no}} { - foreach trim_method {"active" "bg"} { - test "Simple slot migration (trim method: $trim_method)" { - R 0 debug asm-trim-method $trim_method - R 3 debug asm-trim-method $trim_method - - set slot0_key [slot_key 0 mykey] - R 0 set $slot0_key "a" - set slot1_key [slot_key 1 mykey] - R 0 set $slot1_key "b" - set slot101_key [slot_key 101 mykey] - R 0 set $slot101_key "c" - # 3 keys cost 3s to save - R 0 config set rdb-key-save-delay 1000000 - - # load a function - R 0 function load {#!lua name=test1 - redis.register_function('test1', function() return 'hello1' end) - } - - # migrate slot 0-100 to R 1 - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 100] - # migration is start, and in accumulating buffer stage - wait_for_condition 1000 50 { - [string match {*send-bulk-and-stream*} [migration_status 0 $task_id state]] && - [string match {*accumulate-buffer*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not start" - } - - # append 99 times during migration - for {set i 0} {$i < 99} {incr i} { - R 0 multi - R 0 append $slot0_key "a" - R 0 exec - R 0 append $slot1_key "b" - R 0 append $slot101_key "c" - } - - # wait until migration of 0-100 successful - wait_for_asm_done - - # verify task state became completed - assert_equal "completed" [migration_status 0 $task_id state] - assert_equal "completed" [migration_status 1 $task_id state] - - # the appended 99 times should also be migrated - assert_equal [string repeat a 100] [R 1 get $slot0_key] - assert_equal [string repeat b 100] [R 1 get $slot1_key] - - # function should be migrated - assert_equal [R 0 function dump] [R 1 function dump] - # the slave should also get the data - wait_for_ofs_sync [Rn 1] [Rn 4] - - R 4 readonly - assert_equal [string repeat a 100] [R 4 get $slot0_key] - assert_equal [string repeat b 100] [R 4 get $slot1_key] - assert_equal [R 0 function dump] [R 4 function dump] - - # verify key that was not in the slot range is not migrated - assert_equal [string repeat c 100] [R 0 get $slot101_key] - # verify changes in replica - wait_for_ofs_sync [Rn 0] [Rn 3] - R 3 readonly - assert_equal [string repeat c 100] [R 3 get $slot101_key] - - # cleanup - R 0 config set rdb-key-save-delay 0 - R 0 flushall - R 0 function flush - R 1 flushall - R 1 function flush - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - } - } -} - -# Skip most of the tests when running under valgrind since it is hard to -# stabilize tests under valgrind. -if {!$::valgrind} { -start_cluster 3 3 {tags {external:skip cluster} overrides {cluster-node-timeout 60000 cluster-allow-replica-migration no}} { - test "Test CLUSTER MIGRATION IMPORT input validation" { - # invalid arguments - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION IMPORT} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION IMPORT 100} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION IMPORT 100 200 300} - assert_error {*unknown argument*} {R 0 CLUSTER MIGRATION UNKNOWN 1 2} - - # invalid slot range - assert_error {*greater than end slot number*} {R 0 CLUSTER MIGRATION IMPORT 200 100} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT 17000 18000} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT 14000 18000} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT 0 16384} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT 0 -1} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT -1 2} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT -2 -1} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT 10 a} - assert_error {*out of range slot*} {R 0 CLUSTER MIGRATION IMPORT sd sd} - assert_error {*already the owner of the slot*} {R 0 CLUSTER MIGRATION IMPORT 100 200} - } - - test "Test CLUSTER MIGRATION CANCEL input validation" { - # invalid arguments - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION CANCEL} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION CANCEL ID} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION CANCEL ID 12345 EXTRAARG} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION CANCEL ALL EXTRAARG} - assert_error {*unknown argument*} {R 0 CLUSTER MIGRATION CANCEL UNKNOWNARG} - assert_error {*unknown argument*} {R 0 CLUSTER MIGRATION CANCEL abc def} - # empty string id should not cancel any task - assert_equal 0 [R 0 CLUSTER MIGRATION CANCEL ID ""] - } - - test "Test CLUSTER MIGRATION STATUS input validation" { - # invalid arguments - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION STATUS} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION STATUS ID} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION STATUS ID id EXTRAARG} - assert_error {*wrong number of arguments*} {R 0 CLUSTER MIGRATION STATUS ALL EXTRAARG} - assert_error {*unknown argument*} {R 0 CLUSTER MIGRATION STATUS ABC DEF} - assert_error {*unknown argument*} {R 0 CLUSTER MIGRATION STATUS UNKNOWNARG} - # empty string id should not list any task - assert_equal {} [R 0 CLUSTER MIGRATION STATUS ID ""] - } - - test "Test TRIMSLOTS input validation" { - # Wrong number of arguments - assert_error {*wrong number of arguments*} {R 0 TRIMSLOTS} - assert_error {*wrong number of arguments*} {R 0 TRIMSLOTS RANGES} - assert_error {*wrong number of arguments*} {R 0 TRIMSLOTS RANGES 1} - assert_error {*wrong number of arguments*} {R 0 TRIMSLOTS RANGES 2 100} - assert_error {*wrong number of arguments*} {R 0 TRIMSLOTS RANGES 17000 1} - assert_error {*wrong number of arguments*} {R 0 TRIMSLOTS RANGES abc} - - # Missing ranges argument - assert_error {*missing ranges argument*} {R 0 TRIMSLOTS UNKNOWN 1 100 200} - - # Invalid number of ranges - assert_error {*invalid number of ranges*} {R 0 TRIMSLOTS RANGES 0 1 1} - assert_error {*invalid number of ranges*} {R 0 TRIMSLOTS RANGES -1 2 2} - assert_error {*invalid number of ranges*} {R 0 TRIMSLOTS RANGES 17000 1 2} - assert_error {*invalid number of ranges*} {R 0 TRIMSLOTS RANGES 2 100 200 300} - - # Invalid slot numbers - assert_error {*out of range slot*} {R 0 TRIMSLOTS RANGES 1 -1 0} - assert_error {*out of range slot*} {R 0 TRIMSLOTS RANGES 1 -2 -1} - assert_error {*out of range slot*} {R 0 TRIMSLOTS RANGES 1 0 16384} - assert_error {*out of range slot*} {R 0 TRIMSLOTS RANGES 1 abc def} - assert_error {*out of range slot*} {R 0 TRIMSLOTS RANGES 1 100 abc} - - # Start slot greater than end slot - assert_error {*greater than end slot number*} {R 0 TRIMSLOTS RANGES 1 200 100} - } - - test "Test IMPORT not allowed on replica" { - assert_error {* not allowed on replica*} {R 4 CLUSTER MIGRATION IMPORT 100 200} - } - - test "Test IMPORT not allowed during manual migration" { - set dst_id [R 1 CLUSTER MYID] - - # Set a slot to IMPORTING - R 0 CLUSTER SETSLOT 15000 IMPORTING $dst_id - assert_error {*must be STABLE to start*slot migration*} {R 0 CLUSTER MIGRATION IMPORT 100 200} - # Revert the change - R 0 CLUSTER SETSLOT 15000 STABLE - - # Same test with setting a slot to MIGRATING - R 0 CLUSTER SETSLOT 5000 MIGRATING $dst_id - assert_error {*must be STABLE to start*slot migration*} {R 0 CLUSTER MIGRATION IMPORT 100 200} - # Revert the change - R 0 CLUSTER SETSLOT 5000 STABLE - } - - test "Test IMPORT not allowed if the node is already the owner" { - assert_error {*already the owner of the slot*} {R 0 CLUSTER MIGRATION IMPORT 100 100} - } - - test "Test IMPORT not allowed for a slot without an owner" { - # Slot will have no owner - R 0 CLUSTER DELSLOTS 5000 - - assert_error {*slot has no owner: 5000*} {R 0 CLUSTER MIGRATION IMPORT 5000 5000} - - # Revert the change - R 0 CLUSTER ADDSLOTS 5000 - } - - test "Test IMPORT not allowed if slot ranges belong to different nodes" { - assert_error {*slots belong to different source nodes*} {R 0 CLUSTER MIGRATION IMPORT 7000 15000} - assert_error {*slots belong to different source nodes*} {R 0 CLUSTER MIGRATION IMPORT 7000 8000 14000 15000} - } - - test "Test IMPORT not allowed if slot is given multiple times" { - assert_error {*Slot*specified multiple times*} {R 0 CLUSTER MIGRATION IMPORT 7000 8000 8000 9000} - assert_error {*Slot*specified multiple times*} {R 0 CLUSTER MIGRATION IMPORT 7000 8000 7900 9000} - } - - test "Test CLUSTER MIGRATION STATUS ALL lists all tasks" { - # Create 3 completed tasks - R 0 CLUSTER MIGRATION IMPORT 7000 7001 - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 7002 7003 - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 7004 7005 - wait_for_asm_done - - # Get node IDs for verification - set node0_id [R 0 cluster myid] - set node1_id [R 1 cluster myid] - - # Verify CLUSTER MIGRATION STATUS ALL reply from both nodes - foreach node_idx {0 1} { - set tasks [R $node_idx CLUSTER MIGRATION STATUS ALL] - assert_equal 3 [llength $tasks] - - for {set i 0} {$i < 3} {incr i} { - set task [lindex $tasks $i] - - # Verify field order - set expected_fields {id slots source dest operation state - last_error retries create_time start_time - end_time write_pause_ms} - for {set j 0} {$j < [llength $expected_fields]} {incr j} { - set expected_field [lindex $expected_fields $j] - set actual_field [lindex $task [expr $j * 2]] - assert_equal $expected_field $actual_field - } - - # Verify basic fields - assert_equal "completed" [dict get $task state] - assert_equal "" [dict get $task last_error] - assert_equal 0 [dict get $task retries] - assert {[dict get $task write_pause_ms] >= 0} - - # Verify operation based on node - if {$node_idx == 0} { - assert_equal "import" [dict get $task operation] - } else { - assert_equal "migrate" [dict get $task operation] - } - - # Verify node IDs (all tasks: node1 -> node0) - assert_equal $node1_id [dict get $task source] - assert_equal $node0_id [dict get $task dest] - - # Verify timestamps exist and are reasonable - set create_time [dict get $task create_time] - set start_time [dict get $task start_time] - set end_time [dict get $task end_time] - assert {$create_time > 0} - assert {$start_time >= $create_time} - assert {$end_time >= $start_time} - - # Verify specific slot ranges for each task - set slots [dict get $task slots] - if {$i == 0} { - assert_equal "7004-7005" $slots - } elseif {$i == 1} { - assert_equal "7002-7003" $slots - } elseif {$i == 2} { - assert_equal "7000-7001" $slots - } - } - } - - # cleanup - R 1 CLUSTER MIGRATION IMPORT 7000 7005 - wait_for_asm_done - } - - test "Test IMPORT not allowed if there is an overlapping import" { - # Let slot migration take long time, so that we can test overlapping import - R 1 config set rdb-key-save-delay 1000000 - R 1 set tag22273 tag22273 ;# slot hash is 7000 - R 1 set tag9283 tag9283 ;# slot hash is 8000 - - set task_id [R 0 CLUSTER MIGRATION IMPORT 7000 8000] - assert_error {*overlapping import exists*} {R 0 CLUSTER MIGRATION IMPORT 8000 9000} - assert_error {*overlapping import exists*} {R 0 CLUSTER MIGRATION IMPORT 7500 8500} - assert_error {*overlapping import exists*} {R 0 CLUSTER MIGRATION IMPORT 6000 7000} - assert_error {*overlapping import exists*} {R 0 CLUSTER MIGRATION IMPORT 6500 7500} - - wait_for_condition 1000 50 { - [string match {*completed*} [migration_status 0 $task_id state]] && - [string match {*completed*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not start" - } - assert_equal "tag22273" [R 0 get tag22273] - assert_equal "tag9283" [R 0 get tag9283] - R 1 config set rdb-key-save-delay 0 - - # revert the migration - R 1 CLUSTER MIGRATION IMPORT 7000 8000 - wait_for_asm_done - } - - test "Test IMPORT with unsorted and adjacent ranges" { - # Redis should sort and merge adjacent ranges - # Adjacent means: prev.end + 1 == next.start - # e.g. 7000-7001 7002-7003 7004-7005 => 7000-7005 - - # Test with adjacent ranges - set task_id [R 0 CLUSTER MIGRATION IMPORT 7000 7001 7002 7100] - wait_for_asm_done - # verify migration is successfully completed on both nodes - assert_equal "completed" [migration_status 0 $task_id state] - assert_equal "completed" [migration_status 1 $task_id state] - # verify slot ranges are merged correctly - assert_equal "7000-7100" [migration_status 0 $task_id slots] - assert_equal "7000-7100" [migration_status 1 $task_id slots] - - # Test with unsorted and adjacent ranges - set task_id [R 1 CLUSTER MIGRATION IMPORT 7050 7051 7010 7049 7000 7005] - wait_for_asm_done - # verify migration is successfully completed on both nodes - assert_equal "completed" [migration_status 0 $task_id state] - assert_equal "completed" [migration_status 1 $task_id state] - # verify slot ranges are merged correctly - assert_equal "7000-7005 7010-7051" [migration_status 0 $task_id slots] - assert_equal "7000-7005 7010-7051" [migration_status 1 $task_id slots] - - # Another test with unsorted and adjacent ranges - set task_id [R 1 CLUSTER MIGRATION IMPORT 7007 7007 7008 7009 7006 7006] - wait_for_asm_done - # verify migration is successfully completed on both nodes - assert_equal "completed" [migration_status 0 $task_id state] - assert_equal "completed" [migration_status 1 $task_id state] - # verify slot ranges are merged correctly - assert_equal "7006-7009" [migration_status 0 $task_id slots] - assert_equal "7006-7009" [migration_status 1 $task_id slots] - } - - test "Simple slot migration with write load" { - # Perform slot migration while traffic is on and verify data consistency. - # Trimming is disabled on source nodes so, we can compare the dbs after - # migration via DEBUG DIGEST to ensure no data loss during migration. - # Steps: - # 1. Disable trimming on both nodes - # 2. Populate slot 0 on node-0 and slot 6000 on node-1 - # 2. Start write traffic on both nodes - # 3. Migrate slot 0 from node-0 to node-1 - # 4. Migrate slot 6000 from node-1 to node-0 - # 5. Stop write traffic, verify db's are identical. - - # This test runs slowly under the thread sanitizer. - # 1. Increase the lag threshold from the default 1 MB to 10 MB to let the destination catch up easily. - # 2. Increase the write pause timeout from the default 10s to 60s so the source can wait longer. - set prev_config_lag [lindex [R 0 config get cluster-slot-migration-handoff-max-lag-bytes] 1] - R 0 config set cluster-slot-migration-handoff-max-lag-bytes 10mb - R 1 config set cluster-slot-migration-handoff-max-lag-bytes 10mb - set prev_config_timeout [lindex [R 0 config get cluster-slot-migration-write-pause-timeout] 1] - R 0 config set cluster-slot-migration-write-pause-timeout 60000 - R 1 config set cluster-slot-migration-write-pause-timeout 60000 - - R 0 flushall - R 0 debug asm-trim-method none - populate_slot 10000 -idx 0 -slot 0 - - R 1 flushall - R 1 debug asm-trim-method none - populate_slot 10000 -idx 1 -slot 6000 - - # Start write traffic on node-0 - # Throws -MOVED error once asm is completed, catch block will ignore it. - catch { - # Start the slot 0 write load on the R 0 - set port [get_port 0] - set key [slot_key 0 mykey] - set load_handle0 [start_write_load "127.0.0.1" $port 100 $key 0 5] - } - - # Start write traffic on node-1 - # Throws -MOVED error once asm is completed, catch block will ignore it. - catch { - # Start the slot 6000 write load on the R 1 - set port [get_port 1] - set key [slot_key 6000 mykey] - set load_handle1 [start_write_load "127.0.0.1" $port 100 $key 0 5] - } - - # Migrate keys - R 1 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 6000 6100 - wait_for_asm_done - - stop_write_load $load_handle0 - stop_write_load $load_handle1 - - # verify data - assert_morethan [R 0 dbsize] 0 - assert_equal [R 0 debug digest] [R 1 debug digest] - - # cleanup - R 0 config set cluster-slot-migration-handoff-max-lag-bytes $prev_config_lag - R 0 config set cluster-slot-migration-write-pause-timeout $prev_config_timeout - R 0 debug asm-trim-method default - R 0 flushall - R 1 config set cluster-slot-migration-handoff-max-lag-bytes $prev_config_lag - R 1 config set cluster-slot-migration-write-pause-timeout $prev_config_timeout - R 1 debug asm-trim-method default - R 1 flushall - - R 1 CLUSTER MIGRATION IMPORT 6000 6100 - wait_for_asm_done - } - - test "Verify expire time is migrated correctly" { - R 0 flushall - R 1 flushall - - set string_key [slot_key 0 string_key] - set list_key [slot_key 0 list_key] - set hash_key [slot_key 0 hash_key] - set stream_key [slot_key 0 stream_key] - - for {set i 0} {$i < 20} {incr i} { - R 1 hset $hash_key $i $i - R 1 xadd $stream_key * item $i - } - for {set i 0} {$i < 2000} {incr i} { - R 1 lpush $list_key $i - } - - # set expire time of some keys - R 1 set $string_key "a" EX 1000 - R 1 EXPIRE $list_key 1000 - R 1 EXPIRE $hash_key 1000 - - # migrate slot 0-100 to R 0 - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - - # check expire times are migrated correctly - assert_range [R 0 ttl $string_key] 900 1000 - assert_range [R 0 ttl $list_key] 900 1000 - assert_range [R 0 ttl $hash_key] 900 1000 - assert_equal -1 [R 0 ttl $stream_key] - - # cleanup - R 0 flushall - R 1 flushall - R 1 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - } - - test "Slot migration with complex data types can work well" { - R 0 flushall - R 1 flushall - - set list_key [slot_key 0 list_key] - set set_key [slot_key 0 set_key] - set zset_key [slot_key 0 zset_key] - set hash_key [slot_key 0 hash_key] - set stream_key [slot_key 0 stream_key] - - # generate big keys for each data type - for {set i 0} {$i < 1000} {incr i} { - R 1 lpush $list_key $i - R 1 sadd $set_key $i - R 1 zadd $zset_key $i $i - R 1 hset $hash_key $i $i - R 1 xadd $stream_key * item $i - } - - # migrate slot 0-100 to R 0 - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - # check the data on destination node is correct - assert_equal 1000 [R 0 llen $list_key] - assert_equal 1000 [R 0 scard $set_key] - assert_equal 1000 [R 0 zcard $zset_key] - assert_equal 1000 [R 0 hlen $hash_key] - assert_equal 1000 [R 0 xlen $stream_key] - # migrate slot 0-100 to R 1 - R 1 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - } - - proc asm_basic_error_handling_test {operation channel all_states} { - foreach state $all_states { - if {$::verbose} { puts "Testing $operation $channel channel with state: $state"} - - # For states that need incremental data streaming, set a longer delay - set streaming_states [list "streaming-buffer" "accumulate-buffer" "send-bulk-and-stream" "send-stream"] - if {$state in $streaming_states} { - R 1 config set rdb-key-save-delay 1000000 - } - - # Let the destination node take time to stream buffer, so the source node will handle - # slot snapshot child process exit, and then enter "send-stream" state. - if {$state == "send-stream"} { - R 0 config set key-load-delay 100000 - } - - # Start the slot 0 write load on the R 1 - set slot0_key [slot_key 0 mykey] - set load_handle [start_write_load "127.0.0.1" [get_port 1] 100 $slot0_key 500] - - # clear old fail points and set the new fail point - assert_equal {OK} [R 0 debug asm-failpoint "" ""] - assert_equal {OK} [R 1 debug asm-failpoint "" ""] - if {$operation eq "import"} { - assert_equal {OK} [R 0 debug asm-failpoint "import-$channel-channel" $state] - } elseif {$operation eq "migrate"} { - assert_equal {OK} [R 1 debug asm-failpoint "migrate-$channel-channel" $state] - } else { - fail "Unknown operation: $operation" - } - - # Start the migration - set task_id [R 0 CLUSTER MIGRATION IMPORT 0 100] - - # The task should be failed due to the fail point - wait_for_condition 2000 10 { - [string match -nocase "*$channel*${state}*" [migration_status 0 $task_id last_error]] || - [string match -nocase "*$channel*${state}*" [migration_status 1 $task_id last_error]] - } else { - fail "ASM task did not fail with expected error - - (dst: [migration_status 0 $task_id last_error] - src: [migration_status 1 $task_id last_error] - expected: $channel $state)" - } - stop_write_load $load_handle - - # Cancel the task - R 0 CLUSTER MIGRATION CANCEL ID $task_id - R 1 CLUSTER MIGRATION CANCEL ID $task_id - - R 1 config set rdb-key-save-delay 0 - R 0 config set key-load-delay 0 - } - } - - test "Destination node main channel basic error-handling tests " { - set all_states [list \ - "connecting" \ - "auth-reply" \ - "handshake-reply" \ - "syncslots-reply" \ - "accumulate-buffer" \ - "streaming-buffer" \ - "wait-stream-eof" \ - ] - asm_basic_error_handling_test "import" "main" $all_states - } - - test "Destination node rdb channel basic error-handling tests" { - set all_states [list \ - "connecting" \ - "auth-reply" \ - "rdbchannel-reply" \ - "rdbchannel-transfer" \ - ] - asm_basic_error_handling_test "import" "rdb" $all_states - } - - test "Source node main channel basic error-handling tests " { - set all_states [list \ - "wait-rdbchannel" \ - "send-bulk-and-stream" \ - "send-stream" \ - "handoff" \ - ] - asm_basic_error_handling_test "migrate" "main" $all_states - } - - test "Source node rdb channel basic error-handling tests" { - set all_states [list \ - "wait-bgsave-start" \ - "send-bulk-and-stream" \ - ] - asm_basic_error_handling_test "migrate" "rdb" $all_states - } - - test "Migration will be successful after fail points are cleared" { - R 0 flushall - R 1 flushall - set slot0_key [slot_key 0 mykey] - set slot1_key [slot_key 1 mykey] - R 1 set $slot0_key "a" - R 1 set $slot1_key "b" - - # we set a delay to write incremental data - R 1 config set rdb-key-save-delay 1000000 - - # Start the slot 0 write load on the R 1 - set load_handle [start_write_load "127.0.0.1" [get_port 1] 100 $slot0_key] - - # Clear all fail points - assert_equal {OK} [R 0 debug asm-failpoint "" ""] - assert_equal {OK} [R 1 debug asm-failpoint "" ""] - - # Start the migration - set task_id [R 0 CLUSTER MIGRATION IMPORT 0 100] - - # Wait for the migration to complete - wait_for_asm_done - - stop_write_load $load_handle - - # Verify the data is migrated, slot 0 and 1 should belong to R 1 - # slot 0 key should be changed by the write load - assert_not_equal "a" [R 0 get $slot0_key] - assert_equal "b" [R 0 get $slot1_key] - R 1 config set rdb-key-save-delay 0 - } - - test "Client output buffer limit is reached on source side" { - R 0 flushall - R 1 flushall - set r1_pid [S 1 process_id] - R 1 debug repl-pause on-streaming-repl-buf - - # Set a small output buffer limit to trigger the error - R 0 config set client-output-buffer-limit "replica 4mb 0 0" - - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - # some write traffic is to have chance to enter streaming buffer state - set slot0_key [slot_key 0 mykey] - R 0 set $slot0_key "a" - - # after 3 second, the slots snapshot (costs 2s to generate) should be transferred, - # then start streaming buffer - after 3000 - - set loglines [count_log_lines 0] - - # Start the slot 0 write load on the R 0 - set load_handle [start_write_load "127.0.0.1" [get_port 0] 100 $slot0_key 1000] - - # verify the metric is accessible, it is transient, will be reset on disconnect - assert {[S 0 mem_cluster_slot_migration_output_buffer] >= 0} - - # After some time, the client output buffer limit should be reached - wait_for_log_messages 0 {"*Client * closed * for overcoming of output buffer limits.*"} $loglines 1000 10 - wait_for_condition 1000 10 { - [string match {*send*stream*} [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail as expected" - } - - stop_write_load $load_handle - - # Reset configurations - R 0 config set client-output-buffer-limit "replica 0 0 0" - R 0 config set rdb-key-save-delay 0 - - # resume server and clear pause point - resume_process $r1_pid - R 1 debug repl-pause clear - - # Wait for the migration to complete - wait_for_asm_done - } - - test "Full sync buffer limit is reached on destination side" { - # Set a small replication buffer limit to trigger the error - R 0 config set replica-full-sync-buffer-limit 1mb - - # start migration from 1 to 0, cost 4s to transfer slots snapshot - set task_id [setup_slot_migration_with_delay 1 0 0 100 2 2000000] - set loglines [count_log_lines 0] - - # Create some traffic on slot 0 - populate_slot 100 -idx 1 -slot 0 -size 100000 - - # After some time, slots sync buffer limit should be reached, but migration would not fail - # since the buffer will be accumulated on source side from now. - wait_for_log_messages 0 {"*Slots sync buffer limit has been reached*"} $loglines 1000 10 - - # verify the peak value, should be greater than 1mb - assert {[S 0 mem_cluster_slot_migration_input_buffer_peak] > 1000000} - # verify the metric is accessible, it is transient, will be reset on disconnect - assert {[S 0 mem_cluster_slot_migration_input_buffer] >= 0} - - wait_for_asm_done - - # Reset configurations - R 0 config set replica-full-sync-buffer-limit 0 - R 1 config set rdb-key-save-delay 0 - R 1 cluster migration import 0 100 - wait_for_asm_done - } - - test "Expired key is not deleted and SCAN/KEYS/RANDOMKEY/CLUSTER GETKEYSINSLOT filter keys in importing slots" { - set slot0_key [slot_key 0 mykey] - set slot1_key [slot_key 1 mykey] - set slot2_key [slot_key 2 mykey] - R 1 flushall - R 0 flushall - - # we set a delay to write incremental data - R 1 config set rdb-key-save-delay 1000000 - - # set expire time 2s. Generating slot snapshot will 3s, so these - # three keys will be expired after slot snapshot is transferred - R 1 setex $slot0_key 2 "a" - R 1 setex $slot1_key 2 "b" - R 1 hset $slot2_key "f1" "1" - R 1 expire $slot2_key 2 - R 1 hexpire $slot2_key 2 FIELDS 1 "f1" - - set task_id [R 0 CLUSTER MIGRATION IMPORT 0 100] - wait_for_condition 2000 10 { - [string match {*send-bulk-and-stream*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not start" - } - - # update expire time during mirgration - R 1 setex $slot0_key 100 "a" - R 1 expire $slot1_key 80 - R 1 expire $slot2_key 60 - R 1 hincrbyfloat $slot2_key "f1" 1 - R 1 hexpire $slot2_key 60 FIELDS 1 "f1" - - # after 2s, at least a key should be transferred, and should not be deleted - # due to expired, neither active nor lazy expiration (SCAN) takes effect, - # Besides SCAN/KEYS/RANDOMKEY/CLUSTER GETKEYSINSLOT command can not find them - after 2000 - R 3 readonly - foreach id {0 3} { ;# 0 is the master, 3 is the replica - assert_equal {0 {}} [R $id scan 0 count 10] - assert_equal {} [R $id keys "*"] - assert_equal {} [R $id keys "{06S}*"] - assert_equal {} [R $id randomkey] - assert_equal {} [R $id cluster getkeysinslot 0 100] - assert_equal [R $id cluster countkeysinslot 0] 0 - assert_equal [R $id dbsize] 0 - - # but we can see the number of keys is increased in INFO KEYSPACE - assert {[scan [regexp -inline {keys\=([\d]*)} [R $id info keyspace]] keys=%d] >= 1} - assert {[scan [regexp -inline {expires\=([\d]*)} [R $id info keyspace]] expires=%d] >= 1} - } - - wait_for_asm_done - - wait_for_ofs_sync [Rn 0] [Rn 3] - - foreach id {0 3} { ;# 0 is the master, 3 is the replica - # verify the keys are valid - assert_range [R $id ttl $slot0_key] 90 100 - assert_range [R $id ttl $slot1_key] 70 80 - assert_range [R $id ttl $slot2_key] 50 60 - assert_range [R $id httl $slot2_key FIELDS 1 "f1"] 50 60 - - # KEYS/SCAN/RANDOMKEY/CLUSTER GETKEYSINSLOT will find the keys after migration - assert_equal [list 0 [list $slot0_key $slot1_key $slot2_key]] [R $id scan 0 count 10] - assert_equal [list $slot0_key $slot1_key $slot2_key] [R $id keys "*"] - assert_equal [list $slot0_key] [R $id keys "{06S}*"] - assert_not_equal {} [R $id randomkey] - assert_equal [list $slot0_key] [R $id cluster getkeysinslot 0 100] - - # INFO KEYSPACE/DBSIZE/CLUSTER COUNTKEYSINSLOT will also reflect the keys - assert_equal 3 [scan [regexp -inline {keys\=([\d]*)} [R $id info keyspace]] keys=%d] - assert_equal 3 [scan [regexp -inline {expires\=([\d]*)} [R $id info keyspace]] expires=%d] - assert_equal 1 [scan [regexp -inline {subexpiry\=([\d]*)} [R $id info keyspace]] subexpiry=%d] - assert_equal 3 [R $id dbsize] - assert_equal 1 [R $id cluster countkeysinslot 0] - } - - # update expire time to 10ms, after some time, the keys should be deleted due to - # active expiration - R 0 pexpire $slot0_key 10 - R 0 pexpire $slot1_key 10 - R 0 hpexpire $slot2_key 10 FIELDS 1 "f1" ;# the last field is expired, the key will be deleted - wait_for_condition 100 50 { - [scan [regexp -inline {keys\=([\d]*)} [R 0 info keyspace]] keys=%d] == {} && - [scan [regexp -inline {keys\=([\d]*)} [R 3 info keyspace]] keys=%d] == {} - } else { - fail "keys did not expire" - } - - R 1 config set rdb-key-save-delay 0 - } - - test "Eviction does not evict keys in importing slots" { - set slot0_key [slot_key 0 mykey] - set slot1_key [slot_key 1 mykey] - set slot2_key [slot_key 2 mykey] - set slot5462_key [slot_key 5462 mykey] - set slot5463_key [slot_key 5463 mykey] - R 1 flushall - R 0 flushall - - # we set a delay to write incremental data - R 0 config set rdb-key-save-delay 1000000 - - set 1k_str [string repeat "a" 1024] - set 1m_str [string repeat "a" 1048576] - - # set two keys to be evicted - R 1 set $slot5462_key $1k_str - R 1 set $slot5463_key $1k_str - - # set maxmemory to 200kb more than current used memory, - # redis should evict some keys if importing some big keys - set r1_mem_used [S 1 used_memory] - set r1_max_mem [expr {$r1_mem_used + 200*1024}] - R 1 config set maxmemory $r1_max_mem - R 1 config set maxmemory-policy allkeys-lru - - # set 3 keys to be migrated - R 0 set $slot0_key $1m_str - R 0 set $slot1_key $1m_str - R 0 set $slot2_key $1m_str - - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 100] - wait_for_condition 2000 10 { - [string match {*send-bulk-and-stream*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not start" - } - - # after 2.2s, at least two keys should be transferred, they should not be evicted - # but other keys (slot5462_key and slot5463_key) should be evicted - after 2200 - for {set j 0} {$j < 100} {incr j} { R 1 ping } ;# trigger eviction - assert_equal 0 [R 1 exists $slot5462_key] - assert_equal 0 [R 1 exists $slot5463_key] - assert {[scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] >= 2} - - # current used memory should be more than the maxmemory, since the big keys that - # belong importing slots can not be evicted. - set r1_mem_used [S 1 used_memory] - assert {$r1_mem_used > $r1_max_mem + 1024*1024} - - wait_for_asm_done - - # after migration, these big keys should be evicted - for {set j 0} {$j < 100} {incr j} { R 1 ping } ;# trigger eviction - assert_equal {} [scan [regexp -inline {expires\=([\d]*)} [R 1 info keyspace]] expires=%d] - } - - test "Failover will cancel slot migration tasks" { - # migrate slot 0-100 from 1 to 0 - set task_id [setup_slot_migration_with_delay 1 0 0 100] - - # FAILOVER happens on the destination node, instance #3 become master, #0 become slave - failover_and_wait_for_done 3 - - # the old master will cancel the importing task, and the migrating task on - # the source node will be failed - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 0 $task_id state]] && - [string match {*failover*} [migration_status 0 $task_id last_error]] && - [string match {*failed*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not cancel" - } - - # We can restart ASM tasks on new master, migrate slot 0-100 from 1 to 3 - R 1 config set rdb-key-save-delay 0 - set task_id [R 3 CLUSTER MIGRATION IMPORT 0 100] - wait_for_asm_done - - # migrate slot 0-100 from 3 to 1 - set task_id [setup_slot_migration_with_delay 3 1 0 100] - - # FAILOVER happens on the source node, instance #3 become slave, #0 become master - failover_and_wait_for_done 0 - - # the old master will cancel the migrating task, but the destination node will - # retry the importing task, and then succeed. - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 3 $task_id state]] - } else { - fail "ASM task did not cancel" - } - wait_for_asm_done - } - - test "Flush-like command can cancel slot migration task" { - # flushall, flushdb - foreach flushcmd {flushall flushdb} { - # start slot migration from 1 to 0 - set task_id [setup_slot_migration_with_delay 1 0 0 100] - - if {$::verbose} { puts "Testing flush command: $flushcmd"} - R 0 $flushcmd - - # flush-like will cancel the task - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not cancel" - } - } - - R 1 config set rdb-key-save-delay 0 - R 0 cluster migration import 0 100 - wait_for_asm_done - } - - test "CLUSTER SETSLOT command when there is a slot migration task" { - # Setup slot migration test from node 0 to node 1 - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - # Cluster SETSLOT command is not allowed when there is a slot migration task - # on the slot. #0 and #1 are having migration task now. - foreach instance {0 1} { - set node_id [R $instance cluster myid] - - catch {R $instance cluster setslot 0 migrating $node_id} err - assert_match {*in an active atomic slot migration*} $err - - catch {R $instance cluster setslot 0 importing $node_id} err - assert_match {*in an active atomic slot migration*} $err - - catch {R $instance cluster setslot 0 stable} err - assert_match {*in an active atomic slot migration*} $err - - catch {R $instance cluster setslot 0 node $node_id} err - assert_match {*in an active atomic slot migration*} $err - } - - # CLUSTER SETSLOT on other node will cancel the migration task, we update - # the owner of slot 0 (that is migrating from #0 to #1) to #2 on #2, we - # bump the config epoch to make sure the change can update #0 and #1 - # slot configuration, so #0 and #1 will cancel the migration task. - # BTW, if config epoch is not bumped, the slot config of #2 may be - # updated by #0 and #1. - R 2 cluster bumpepoch - R 2 cluster setslot 0 node [R 2 cluster myid] - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 0 $task_id state]] && - [string match {*slots configuration updated*} [migration_status 0 $task_id last_error]] && - [string match {*canceled*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not cancel" - } - - # set slot 0 back to #0 - R 0 cluster bumpepoch - R 0 cluster setslot 0 node [R 0 cluster myid] - wait_for_cluster_propagation - wait_for_cluster_state "ok" - } - - test "CLUSTER DELSLOTSRANGE command cancels a slot migration task" { - # start slot migration from 0 to 1 - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - R 0 cluster delslotsrange 0 100 - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 0 $task_id state]] && - [string match {*slots configuration updated*} [migration_status 0 $task_id last_error]] && - [string match {*failed*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not cancel" - } - R 1 cluster migration cancel id $task_id - - # add the slots back - R 0 cluster addslotsrange 0 100 - wait_for_cluster_propagation - wait_for_cluster_state "ok" - } - - # NOTE: this test needs more than 60s, maybe you can skip when testing - test "CLUSTER FORGET command cancels a slot migration task" { - R 0 config set rdb-key-save-delay 0 - # Migrate all slot on #0 to #1, so we can forget #0 - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 5461] - wait_for_asm_done - - # start slot migration from 1 to 0 - set task_id [setup_slot_migration_with_delay 1 0 0 5461] - - # Forget #0 on #1, the migration task on #1 will be canceled due to node deleted, - # and the importing task on #0 will be failed - R 1 cluster forget [R 0 cluster myid] - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 1 $task_id state]] && - [string match {*node deleted*} [migration_status 1 $task_id last_error]] && - [string match {*failed*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not cancel" - } - - # Add #0 back into cluster - # NOTE: this will cost 60s to let #0 join the cluster since - # other nodes add #0 into black list for 60s after FORGET. - R 1 config set rdb-key-save-delay 0 - R 1 cluster meet "127.0.0.1" [lindex [R 0 config get port] 1] - - # the importing task on #0 will be retried, and eventually succeed - # since now #0 is back in the cluster - wait_for_condition 3000 50 { - [string match {*completed*} [migration_status 0 $task_id state]] && - [string match {*completed*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not finish" - } - - # make sure #0 is completely back to the cluster - wait_for_cluster_propagation - wait_for_cluster_state "ok" - } - - test "CLIENT PAUSE can cancel slot migration task" { - # start slot migration from 0 to 1 - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - # CLIENT PAUSE happens on the destination node, #1 will cancel the importing task - R 1 client pause 100000 write ;# pause 100s - wait_for_condition 1000 50 { - [string match {*canceled*} [migration_status 1 $task_id state]] && - [string match {*client pause*} [migration_status 1 $task_id last_error]] - } else { - fail "ASM task did not cancel" - } - - # start task again - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 100] - after 200 ;# give some time to have chance to schedule the task - # the task should not start since server is paused - assert {[string match {*none*} [migration_status 1 $task_id state]]} - - # unpause the server, the task should start - R 1 client unpause - wait_for_asm_done - - # migrate back to original node #0 - R 0 config set rdb-key-save-delay 0 - R 1 config set rdb-key-save-delay 0 - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - } - - test "Server shutdown can cancel slot migration task, exit with success" { - # start slot migration from 0 to 1 - setup_slot_migration_with_delay 0 1 0 100 - - set loglines [count_log_lines -1] - - # Shutdown the server, it should cancel the migration task - restart_server -1 true false true nosave - - wait_for_log_messages -1 {"*Cancelled due to server shutdown*"} $loglines 100 100 - - wait_for_cluster_propagation - wait_for_cluster_state "ok" - } - - test "Cancel import task when streaming buffer into db" { - # set a delay to have time to cancel import task that is streaming buf to db - R 1 config set key-load-delay 50000 - # start slot migration from 0 to 1 - set task_id [setup_slot_migration_with_delay 0 1 0 100 5] - - # start the slot 0 write load on the node 0 - set slot0_key [slot_key 0 mykey] - set load_handle [start_write_load "127.0.0.1" [get_port 0] 100 $slot0_key 500] - - # wait for entering streaming buffer state - wait_for_condition 1000 10 { - [string match {*streaming-buffer*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not enter streaming buffer state" - } - stop_write_load $load_handle - - # cancel the import task on #1, the destination node works fine - R 1 cluster migration cancel id $task_id - assert_match {*canceled*} [migration_status 1 $task_id state] - - # reset config - R 0 config set key-load-delay 0 - R 1 config set key-load-delay 0 - } - - test "Destination node main channel timeout when waiting stream EOF" { - set task_id [setup_slot_migration_with_delay 0 1 0 100] - R 1 config set repl-timeout 5 - - # pause the source node to make EOF wait timeout. Do not pause - # the child process, so it can deliver slot snapshot to destination - set r0_process_id [S 0 process_id] - pause_process $r0_process_id - - # the destination node will fail after 7s, 5s for EOF wait and 2s for slot snapshot - wait_for_condition 1000 20 { - [string match {*failed*} [migration_status 1 $task_id state]] && - [string match {*Main channel*Connection timeout*wait-stream-eof*} \ - [migration_status 1 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - - # resume the source node - resume_process $r0_process_id - - # After the source node is resumed, the task on source node may receive - # ACKs from destination and consider the task is stream-done. In this case, - # the task on source node will be failed after several seconds - if {[string match {*stream-done*} [migration_status 0 $task_id state]]} { - wait_for_condition 1000 20 { - [string match {*failed*} [migration_status 0 $task_id state]] && - [string match {*Server paused*} [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - } - - R 1 config set repl-timeout 60 - R 0 cluster migration cancel id $task_id - R 1 cluster migration cancel id $task_id - } - - test "Destination node rdb channel timeout when transferring slots snapshot" { - # cost 10s to transfer each key - set task_id [setup_slot_migration_with_delay 0 1 0 100 2 10000000] - R 1 config set repl-timeout 3 - - # the destination node will fail after 3s - wait_for_condition 1000 20 { - [string match {*failed*} [migration_status 1 $task_id state]] && - [string match {*RDB channel*Connection timeout*rdbchannel-transfer*} \ - [migration_status 1 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - - R 1 config set repl-timeout 60 - R 0 cluster migration cancel id $task_id - R 1 cluster migration cancel id $task_id - } - - test "Source node rdb channel timeout when transferring slots snapshot" { - set r1_pid [S 1 process_id] - R 0 flushall - R 0 config set save "" - # generate several large keys, make sure the memory usage is more than - # socket buffer size, so the rdb channel will block and timeout if - # no data is received by destination. - set val [string repeat "a" 102400] ;# 100kb - for {set i 0} {$i < 1000} {incr i} { - set key [slot_key 0 "key$i"] - R 0 set $key $val - } - R 0 config set repl-timeout 3 ;# 3s for rdb channel timeout - R 0 config set rdb-key-save-delay 10000 ;# 1000 keys cost 10s to save - - # start migration from #0 to #1 - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 100] - wait_for_condition 1000 20 { - [string match {*send-bulk-and-stream*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not start" - } - - # pause the destination node to make rdb channel timeout - pause_process $r1_pid - - # the source node will fail, the rdb child process can not - # write data to destination, so it will timeout - wait_for_condition 1000 30 { - [string match {*failed*} [migration_status 0 $task_id state]] && - [string match {*RDB channel*Failed to send slots snapshot*} \ - [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - resume_process $r1_pid - - R 0 config set repl-timeout 60 - R 0 cluster migration cancel id $task_id - R 1 cluster migration cancel id $task_id - } - - test "Source node main channel timeout when sending incremental stream" { - R 0 flushall - R 0 config set repl-timeout 2 ;# 2s for main channel timeout - - set r1_pid [S 1 process_id] - # in order to have time to pause the destination node - R 1 config set key-load-delay 50000 ;# 50ms each 16k data - - # start migration from #0 to #1 - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - # Create 200 keys of 16k size traffic on slot 0, streaming buffer need 10s (200*50ms) - populate_slot 200 -idx 0 -slot 0 -size 16384 - - # wait for streaming buffer state, then pause the destination node - wait_for_condition 1000 20 { - [string match {*streaming-buffer*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not stream buffer, state: [migration_status 1 $task_id state]" - } - pause_process $r1_pid - - # Start the slot 0 write load on the R 0 - set load_handle [start_write_load "127.0.0.1" [get_port 0] 100 [slot_key 0 mykey] 500] - - # the source node will fail after several seconds (including the time - # to fill the socket buffer of source node), the main channel can not - # write data to destination since the destination is paused - wait_for_condition 1000 30 { - [string match {*failed*} [migration_status 0 $task_id state]] && - [string match {*Main channel*Connection timeout*} \ - [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - stop_write_load $load_handle - resume_process $r1_pid - - R 0 config set repl-timeout 60 - R 1 config set key-load-delay 0 - R 0 cluster migration cancel id $task_id - R 1 cluster migration cancel id $task_id - R 0 flushall - } - - test "Source server paused timeout" { - # set timeout to 0, so the task will fail immediately when checking timeout - R 0 config set cluster-slot-migration-write-pause-timeout 0 - - # start migration from node 0 to 1 - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - # start the slot 0 write load on the node 0 - set slot0_key [slot_key 0 mykey] - set load_handle [start_write_load "127.0.0.1" [get_port 0] 100 $slot0_key] - - # node 0 will fail since server paused timeout - wait_for_condition 2000 10 { - [string match {*failed*} [migration_status 0 $task_id state]] && - [string match {*Server paused timeout*} \ - [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - - stop_write_load $load_handle - - # reset config - R 0 config set cluster-slot-migration-write-pause-timeout 10000 - R 0 cluster migration cancel id $task_id - R 1 cluster migration cancel id $task_id - } - - test "Sync buffer drain timeout" { - # set a fail point to avoid the source node to enter handoff prep state - # to test the sync buffer drain timeout - R 0 debug asm-failpoint "migrate-main-channel" "handoff-prep" - R 0 config set cluster-slot-migration-sync-buffer-drain-timeout 5000 - - set r1_pid [S 1 process_id] - - # start migration from node 0 to 1 - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - # start the slot 0 write load on the node 0 - set slot0_key [slot_key 0 mykey] - set load_handle [start_write_load "127.0.0.1" [get_port 0] 100 $slot0_key] - - # wait for entering streaming buffer state - wait_for_condition 1000 10 { - [string match {*wait-stream-eof*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not enter wait-stream-eof state" - } - - pause_process $r1_pid ;# avoid the destination to apply commands - - # node 0 will fail since sync buffer drain timeout - wait_for_condition 2000 10 { - [string match {*failed*} [migration_status 0 $task_id state]] && - [string match {*Sync buffer drain timeout*} \ - [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - - stop_write_load $load_handle - resume_process $r1_pid - - # reset config - R 0 config set cluster-slot-migration-sync-buffer-drain-timeout 60000 - R 0 debug asm-failpoint "" "" - R 0 cluster migration cancel id $task_id - R 1 cluster migration cancel id $task_id - } - - test "Cluster implementation cannot start migrate task temporarily" { - # Inject a fail point to make the source node not ready - R 0 debug asm-failpoint "migrate-main-channel" "none" - - # start migration from node 0 to 1 - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 100] - - # verify source node replies SYNCSLOTS with -NOTREADY - set loglines [count_log_lines -1] - wait_for_log_messages -1 {"*Source node replied to SYNCSLOTS SYNC with -NOTREADY, will retry later*"} $loglines 100 100 - - # clear the fail point and verify the task is completed - R 0 debug asm-failpoint "" "" - wait_for_asm_done - assert_equal "completed" [migration_status 0 $task_id state] - assert_equal "completed" [migration_status 1 $task_id state] - - # cleanup - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - } -} - -start_cluster 3 3 {tags {external:skip cluster} overrides {cluster-node-timeout 60000 cluster-allow-replica-migration no}} { - test "Test bgtrim after a successful migration" { - R 0 debug asm-trim-method bg - R 3 debug asm-trim-method bg - R 0 CONFIG RESETSTAT - R 3 CONFIG RESETSTAT - - R 0 flushall - # Fill slot 0 - populate_slot 1000 -idx 0 -slot 0 - # Fill slot 1 with keys that have TTL - populate_slot 1000 -idx 0 -slot 1 -prefix "expirekey" -expires 100 - # HFE key on slot 2 - set slot2_hfekey [slot_key 2 hfekey] - R 0 HSETEX $slot2_hfekey EX 10 FIELDS 1 f1 v1 - - # Fill slot 101, these keys won't be migrated - populate_slot 1000 -idx 0 -slot 101 - # Fill slot 102 with keys that have TTL - populate_slot 1000 -idx 0 -slot 102 -prefix "expirekey" -expires 100 - # HFE key on slot 103 - set slot103_hfekey [slot_key 103 hfekey] - R 0 HSETEX $slot103_hfekey EX 10 FIELDS 1 f1 v1 - - # migrate slot 0 to node-1 - R 1 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - - # Verify the data is migrated - wait_for_ofs_sync [Rn 0] [Rn 3] - assert_equal 2001 [R 0 dbsize] - assert_equal 2001 [R 3 dbsize] - wait_for_ofs_sync [Rn 1] [Rn 4] - assert_equal 2001 [R 1 dbsize] - assert_equal 2001 [R 4 dbsize] - - # Verify the keys are trimmed lazily - wait_for_condition 1000 10 { - [S 0 lazyfreed_objects] == 2001 && - [S 3 lazyfreed_objects] == 2001 - } else { - puts "lazyfreed_objects: [S 0 lazyfreed_objects] [S 3 lazyfreed_objects]" - fail "Background trim did not happen" - } - - # Cleanup - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - R 0 flushall - R 0 debug asm-trim-method default - R 3 debug asm-trim-method default - } - - test "Test bgtrim after a failed migration" { - R 0 debug asm-trim-method bg - R 3 debug asm-trim-method bg - R 1 CONFIG RESETSTAT - R 4 CONFIG RESETSTAT - - # Fill slot 0 on node-0 and migrate it to node-1 (with some delay) - R 0 flushall - set task_id [setup_slot_migration_with_delay 0 1 0 100 10000 1000] - after 1000 ;# wait some time so that some keys are moved - - # Fail the migration - R 1 CLUSTER MIGRATION CANCEL ID $task_id - wait_for_asm_done - - # Verify the data is not migrated - assert_equal 10000 [R 0 dbsize] - assert_equal 10000 [R 3 dbsize] - - # Verify the keys are trimmed lazily after a failed import on dest side. - wait_for_condition 1000 20 { - [R 1 dbsize] == 0 && - [R 4 dbsize] == 0 && - [S 1 lazyfreed_objects] > 0 && - [S 4 lazyfreed_objects] > 0 - } else { - fail "Background trim did not happen" - } - - # Cleanup - wait_for_asm_done - R 0 flushall - R 0 debug asm-trim-method default - R 3 debug asm-trim-method default - } - - test "Test bgtrim unblocks stream client" { - # Two clients waiting for data on two different streams which are in - # different slots. We are going to migrate one slot, which will unblock - # the client. The other client should still be blocked. - R 0 debug asm-trim-method bg - - set key0 [slot_key 0 mystream] - set key1 [slot_key 1 mystream] - - # First client waits on slot-0 key - R 0 DEL $key0 - R 0 XADD $key0 666 f v - R 0 XGROUP CREATE $key0 mygroup $ - set rd0 [redis_deferring_client] - $rd0 XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS $key0 ">" - wait_for_blocked_clients_count 1 - - # Second client waits on slot-1 key - R 0 DEL $key1 - R 0 XADD $key1 666 f v - R 0 XGROUP CREATE $key1 mygroup $ - set rd1 [redis_deferring_client] - $rd1 XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS $key1 ">" - wait_for_blocked_clients_count 2 - - # Migrate slot 0 - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - - # First client should get MOVED error - assert_error "*MOVED*" {$rd0 read} - $rd0 close - - # Second client should operate normally - R 0 XADD $key1 667 f v - set res [$rd1 read] - assert_equal [lindex $res 0 1 0] {667-0 {f v}} - $rd1 close - - # cleanup - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - R 0 flushall - R 0 debug asm-trim-method default - } - - test "Test bgtrim touches watched keys" { - R 0 debug asm-trim-method bg - - # bgtrim should touch watched keys on migrated slots - set key0 [slot_key 0 key] - R 0 set $key0 30 - R 0 watch $key0 - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - R 0 multi - R 0 ping - assert_equal {} [R 0 exec] - - # bgtrim should not touch watched keys on other slots - set key2 [slot_key 2 key] - R 0 set $key2 30 - R 0 watch $key2 - R 1 CLUSTER MIGRATION IMPORT 1 1 - wait_for_asm_done - R 0 multi - R 0 ping - assert_equal PONG [R 0 exec] - - # cleanup - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 0 1 - wait_for_asm_done - R 0 flushall - R 0 debug asm-trim-method default - } - - test "Test bgtrim after a FAILOVER on destination side" { - R 1 debug asm-trim-method bg - R 4 debug asm-trim-method bg - - set loglines [count_log_lines -4] - - # Fill slot 0 on node-0 and migrate it to node-1 (with some delay) - R 0 flushall - set task_id [setup_slot_migration_with_delay 0 1 0 100 10000 1000] - after 1000 ;# wait some time so that some keys are moved - - # Trigger a failover with force to simulate unreachable master and - # verify unowned keys are trimmed once replica becomes master. - failover_and_wait_for_done 4 force - wait_for_log_messages -4 {"*Detected keys in slots that do not belong*Scheduling trim*"} $loglines 1000 10 - wait_for_condition 1000 10 { - [R 1 dbsize] == 0 && - [R 4 dbsize] == 0 - } else { - fail "Background trim did not happen" - } - - # cleanup - wait_for_cluster_propagation - failover_and_wait_for_done 1 - R 0 config set rdb-key-save-delay 0 - R 1 debug asm-trim-method default - R 4 debug asm-trim-method default - wait_for_asm_done - } - - test "CLUSTER SETSLOT is not allowed if there is a pending trim job" { - R 0 debug asm-trim-method bg - R 3 debug asm-trim-method bg - - # Fill slot 0 on node-0 and migrate it to node-1 (with some delay) - R 0 flushall - set task_id [setup_slot_migration_with_delay 0 1 0 100 10000 1000] - - # Pause will cancel the task and there will be a pending trim job - # until writes are allowed again. - R 1 client pause 100000 write ;# pause 100s - wait_for_asm_done - - # CLUSTER SETSLOT is not allowed if there is a pending trim job. - assert_error {*There is a pending trim job for slot 0*} {R 1 CLUSTER SETSLOT 0 STABLE} - - # Unpause the server, trim will be triggered and SETSLOT will be allowed - R 1 client unpause - R 1 CLUSTER SETSLOT 0 STABLE - } -} - -start_cluster 3 3 {tags {external:skip cluster} overrides {cluster-node-timeout 60000 cluster-allow-replica-migration no save ""}} { - test "Test active trim after a successful migration" { - R 0 debug asm-trim-method active - R 3 debug asm-trim-method active - populate_slot 500 -slot 0 - populate_slot 500 -slot 1 - populate_slot 500 -slot 3 - populate_slot 500 -slot 4 - - # Migrate 1500 keys - R 1 CLUSTER MIGRATION IMPORT 0 1 3 3 - wait_for_asm_done - - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 0 && - [CI 0 cluster_slot_migration_active_trim_current_job_trimmed] == 1500 && - [CI 3 cluster_slot_migration_active_trim_running] == 0 && - [CI 3 cluster_slot_migration_active_trim_current_job_trimmed] == 1500 - } else { - fail "trim failed" - } - - assert_equal 1500 [CI 0 cluster_slot_migration_active_trim_current_job_keys] - assert_equal 1500 [CI 3 cluster_slot_migration_active_trim_current_job_keys] - - assert_equal 500 [R 0 dbsize] - assert_equal 500 [R 3 dbsize] - assert_equal 1500 [R 1 dbsize] - assert_equal 1500 [R 4 dbsize] - assert_equal 0 [R 0 cluster countkeysinslot 0] - assert_equal 0 [R 0 cluster countkeysinslot 1] - assert_equal 0 [R 0 cluster countkeysinslot 3] - assert_equal 500 [R 0 cluster countkeysinslot 4] - - # cleanup - R 0 debug asm-trim-method default - R 3 debug asm-trim-method default - R 0 CLUSTER MIGRATION IMPORT 0 1 3 3 - wait_for_asm_done - R 0 flushall - R 1 flushall - } - - test "Test multiple active trim jobs can be scheduled" { - # Active trim will be scheduled but it won't run - R 0 debug asm-trim-method active -1 - R 3 debug asm-trim-method active -1 - - populate_slot 500 -slot 0 - populate_slot 500 -slot 1 - populate_slot 500 -slot 3 - populate_slot 500 -slot 4 - - # Migrate 1500 keys - R 1 CLUSTER MIGRATION IMPORT 0 1 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 && - [CI 3 cluster_slot_migration_active_trim_running] == 1 - } else { - fail "migrate failed" - } - - # Migrate another slot and verify there are two trim tasks on the source - R 1 CLUSTER MIGRATION IMPORT 3 3 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 2 && - [CI 3 cluster_slot_migration_active_trim_running] == 2 - } else { - fail "migrate failed" - } - - # Enabled active trim and wait until it is completed. - R 0 debug asm-trim-method active 0 - R 3 debug asm-trim-method active 0 - wait_for_asm_done - - assert_equal 500 [R 0 dbsize] - assert_equal 500 [R 3 dbsize] - assert_equal 0 [R 0 cluster countkeysinslot 0] - assert_equal 0 [R 0 cluster countkeysinslot 1] - assert_equal 0 [R 0 cluster countkeysinslot 3] - assert_equal 500 [R 0 cluster countkeysinslot 4] - - # cleanup - R 0 debug asm-trim-method default - R 3 debug asm-trim-method default - R 0 CLUSTER MIGRATION IMPORT 0 1 3 3 - wait_for_asm_done - R 0 flushall - R 1 flushall - } - - test "Test active-trim clears partially imported keys on cancel" { - R 1 debug asm-trim-method active - R 4 debug asm-trim-method active - - # Rdb delivery will take 10 seconds - R 0 config set rdb-key-save-delay 10000 - populate_slot 250 -slot 0 - populate_slot 250 -slot 1 - populate_slot 250 -slot 3 - populate_slot 250 -slot 4 - - R 1 CLUSTER MIGRATION IMPORT 0 100 - after 2000 - R 1 CLUSTER MIGRATION CANCEL ALL - wait_for_asm_done - - assert_morethan [CI 1 cluster_slot_migration_active_trim_current_job_keys] 0 - assert_morethan [CI 4 cluster_slot_migration_active_trim_current_job_trimmed] 0 - - assert_equal 1000 [R 0 dbsize] - assert_equal 1000 [R 3 dbsize] - assert_equal 0 [R 1 dbsize] - assert_equal 0 [R 4 dbsize] - - # Cleanup - R 1 debug asm-trim-method default - R 4 debug asm-trim-method default - R 0 config set rdb-key-save-delay 0 - } - - test "Test active-trim clears partially imported keys on failover" { - R 1 debug asm-trim-method active - R 4 debug asm-trim-method active - - # Rdb delivery will take 10 seconds - R 0 config set rdb-key-save-delay 10000 - - populate_slot 250 -slot 0 - populate_slot 250 -slot 1 - populate_slot 250 -slot 3 - populate_slot 250 -slot 4 - - set prev_trim_started_1 [CI 1 cluster_slot_migration_stats_active_trim_started] - set prev_trim_started_4 [CI 4 cluster_slot_migration_stats_active_trim_started] - - R 1 CLUSTER MIGRATION IMPORT 0 100 - after 2000 - failover_and_wait_for_done 4 - wait_for_asm_done - - # Verify there is at least one trim job started - assert_morethan [CI 1 cluster_slot_migration_stats_active_trim_started] $prev_trim_started_1 - assert_morethan [CI 4 cluster_slot_migration_stats_active_trim_started] $prev_trim_started_4 - - assert_equal 1000 [R 0 dbsize] - assert_equal 1000 [R 3 dbsize] - assert_equal 0 [R 1 dbsize] - assert_equal 0 [R 4 dbsize] - - # Cleanup - failover_and_wait_for_done 1 - R 1 debug asm-trim-method default - R 4 debug asm-trim-method default - R 0 config set rdb-key-save-delay 0 - R 0 flushall - R 1 flushall - } - - test "Test import task does not start if active trim is in progress for the same slots" { - # Active trim will be scheduled but it won't run - R 0 flushall - R 1 flushall - R 0 debug asm-trim-method active -1 - - populate_slot 500 -slot 0 - populate_slot 500 -slot 1 - - # Migrate 1000 keys - R 1 CLUSTER MIGRATION IMPORT 0 1 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 - } else { - fail "migrate failed" - } - - # Try to migrate slots back - R 0 CLUSTER MIGRATION IMPORT 0 1 - wait_for_log_messages 0 {"*Can not start import task*trim in progress for some of the slots*"} 0 1000 10 - - # Enabled active trim and verify slots are imported back - R 0 debug asm-trim-method active 0 - wait_for_asm_done - - assert_equal 1000 [R 0 dbsize] - assert_equal 500 [R 0 cluster countkeysinslot 0] - assert_equal 500 [R 0 cluster countkeysinslot 1] - - # cleanup - R 0 debug asm-trim-method default - R 0 flushall - } - - test "Rdb save during active trim should skip keys in trimmed slots" { - # Insert some delay to activate trim - R 0 debug asm-trim-method active 1000 - R 0 config set repl-diskless-sync-delay 0 - R 0 flushall - - populate_slot 5000 -idx 0 -slot 0 - populate_slot 5000 -idx 0 -slot 1 - populate_slot 5000 -idx 0 -slot 2 - - # Start migration and wait until trim is in progress - R 1 CLUSTER MIGRATION IMPORT 0 1 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 && - [S 0 rdb_bgsave_in_progress] == 0 - } else { - puts "[CI 0 cluster_slot_migration_active_tasks]" - puts "[CI 0 cluster_slot_migration_active_trim_running]" - fail "trim failed" - } - - # Trigger save during active trim - R 0 save - # Wait until the log contains a "keys skipped" message with a non-zero value - wait_for_log_messages 0 {"*BGSAVE done, 5000 keys saved, [1-9]* keys skipped*"} 0 1000 10 - - restart_server 0 yes no yes nosave - assert_equal 5000 [R 0 dbsize] - assert_equal 0 [R 0 cluster countkeysinslot 0] - assert_equal 0 [R 0 cluster countkeysinslot 1] - assert_equal 5000 [R 0 cluster countkeysinslot 2] - - # Cleanup - wait_for_cluster_propagation - wait_for_cluster_state "ok" - R 0 flushall - R 1 flushall - R 0 save - R 0 CLUSTER MIGRATION IMPORT 0 1 - wait_for_asm_done - } - - test "AOF rewrite during active trim should skip keys in trimmed slots" { - R 0 debug asm-trim-method active 1000 - R 0 config set repl-diskless-sync-delay 0 - R 0 config set aof-use-rdb-preamble no - R 0 config set appendonly yes - R 0 config rewrite - R 0 flushall - populate_slot 5000 -idx 0 -slot 0 - populate_slot 5000 -idx 0 -slot 1 - populate_slot 5000 -idx 0 -slot 2 - - R 1 CLUSTER MIGRATION IMPORT 0 1 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 - } else { - puts "[CI 0 cluster_slot_migration_active_tasks]" - puts "[CI 0 cluster_slot_migration_active_trim_running]" - fail "trim failed" - } - - wait_for_condition 50 100 { - [S 0 rdb_bgsave_in_progress] == 0 - } else { - fail "bgsave is in progress" - } - - R 0 bgrewriteaof - # Wait until the log contains a "keys skipped" message with a non-zero value - wait_for_log_messages 0 {"*AOF rewrite done, [1-9]* keys saved, [1-9]* keys skipped*"} 0 1000 10 - - restart_server 0 yes no yes nosave - assert_equal 5000 [R 0 dbsize] - assert_equal 0 [R 0 cluster countkeysinslot 0] - assert_equal 0 [R 0 cluster countkeysinslot 1] - assert_equal 5000 [R 0 cluster countkeysinslot 2] - - # cleanup - R 0 config set appendonly no - R 0 config rewrite - restart_server 0 yes no yes nosave - wait_for_cluster_propagation - wait_for_cluster_state "ok" - R 0 flushall - R 1 flushall - R 0 save - R 0 CLUSTER MIGRATION IMPORT 0 1 - wait_for_asm_done - } - - test "Pause actions will stop active trimming" { - R 0 debug asm-trim-method active 1000 - R 0 config set repl-diskless-sync-delay 0 - R 0 flushall - populate_slot 10000 -idx 0 -slot 0 - - R 1 CLUSTER MIGRATION IMPORT 0 100 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 - } else { - puts "[CI 0 cluster_slot_migration_active_tasks]" - puts "[CI 0 cluster_slot_migration_active_trim_running]" - fail "trim failed" - } - - # Pause the server and verify no keys are trimmed - R 0 client pause 100000 write ;# pause 100s - set prev [CI 0 cluster_slot_migration_active_trim_current_job_trimmed] - after 1000 ; # wait some time to see if any keys are trimmed - set curr [CI 0 cluster_slot_migration_active_trim_current_job_trimmed] - assert_equal $prev $curr - - R 0 client unpause - R 0 debug asm-trim-method default - wait_for_asm_done - assert_equal 0 [R 0 dbsize] - - # revert - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - assert_equal 10000 [R 0 dbsize] - } - - foreach diskless_load {"disabled" "swapdb" "on-empty-db"} { - test "Test fullsync cancels active trim (repl-diskless-load $diskless_load)" { - R 3 debug asm-trim-method active -10 - R 3 config set repl-diskless-load $diskless_load - R 0 flushall - - R 0 config set repl-diskless-sync-delay 0 - populate_slot 10000 -idx 0 -slot 0 - - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 0 && - [CI 3 cluster_slot_migration_active_trim_running] == 1 - } else { - puts "[CI 0 cluster_slot_migration_active_tasks]" - puts "[CI 0 cluster_slot_migration_active_trim_running]" - puts "[CI 3 cluster_slot_migration_active_trim_running]" - fail "trim failed" - } - - set prev_cancelled [CI 3 cluster_slot_migration_stats_active_trim_cancelled] - R 0 config set client-output-buffer-limit "replica 1024 0 0" - - # Trigger a fullsync - populate_slot 1 -idx 0 -size 2000000 -slot 2 - - wait_for_condition 1000 10 { - [CI 3 cluster_slot_migration_active_trim_running] == 0 && - [CI 3 cluster_slot_migration_stats_active_trim_cancelled] == $prev_cancelled + 1 - } else { - puts "[CI 3 cluster_slot_migration_active_trim_running]" - puts "[CI 3 cluster_slot_migration_stats_active_trim_cancelled]" - fail "trim failed" - } - - R 3 debug asm-trim-method active 0 - R 3 config set repl-diskless-load disabled - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - wait_for_ofs_sync [Rn 0] [Rn 3] - assert_equal 10001 [R 0 dbsize] - assert_equal 10001 [R 3 dbsize] - assert_equal 0 [R 1 dbsize] - assert_equal 0 [R 4 dbsize] - R 0 flushall - } - } - - test "Test importing slots while active-trim is in progress for the same slots on replica" { - R 3 debug asm-trim-method active 10000 - R 0 flushall - populate_slot 10000 -slot 0 - wait_for_ofs_sync [Rn 0] [Rn 3] - - # Wait until active trim is in progress on replica - R 1 CLUSTER MIGRATION IMPORT 0 100 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 0 && - [CI 3 cluster_slot_migration_active_trim_running] == 1 - } else { - puts "[CI 0 cluster_slot_migration_active_tasks]" - puts "[CI 0 cluster_slot_migration_active_trim_running]" - puts "[CI 3 cluster_slot_migration_active_trim_running]" - fail "trim failed" - } - - set loglines [count_log_lines -3] - - # Get slots back - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_condition 1000 20 { - [CI 0 cluster_slot_migration_active_tasks] == 1 && - [CI 0 cluster_slot_migration_active_trim_running] == 0 && - [CI 3 cluster_slot_migration_active_trim_running] == 1 - } else { - fail "trim failed" - } - - # Verify replica blocks master until trim is done - wait_for_log_messages -3 {"*Blocking master client until trim job is done*"} $loglines 1000 30 - R 3 debug asm-trim-method active 0 - wait_for_log_messages -3 {"*Unblocking master client after active trim*"} $loglines 1000 30 - - wait_for_asm_done - wait_for_ofs_sync [Rn 0] [Rn 3] - assert_equal 10000 [R 0 dbsize] - assert_equal 10000 [R 3 dbsize] - assert_equal 0 [R 1 dbsize] - assert_equal 0 [R 4 dbsize] - } - - test "TRIMSLOTS should not trim slots that this node is serving" { - assert_error {*the slot 0 is served by this node*} {R 0 trimslots ranges 1 0 0} - assert_error {*READONLY*} {R 3 trimslots ranges 1 0 100} - assert_equal {OK} [R 0 trimslots ranges 1 16383 16383] - assert_error {*READONLY*} {R 3 trimslots ranges 1 16383 16383} - } - - test "Trigger multiple active trim jobs at the same time" { - R 1 debug asm-trim-method active 0 - R 1 flushall - - set prev_trim_done [CI 1 cluster_slot_migration_stats_active_trim_completed] - - R 1 debug populate 1000 [slot_prefix 0] 100 - R 1 debug populate 1000 [slot_prefix 1] 100 - R 1 debug populate 1000 [slot_prefix 2] 100 - - R 1 multi - R 1 trimslots ranges 1 0 0 - R 1 trimslots ranges 1 1 1 - R 1 trimslots ranges 1 2 2 - R 1 exec - - wait_for_condition 1000 10 { - [CI 1 cluster_slot_migration_stats_active_trim_completed] == $prev_trim_done + 3 - } else { - fail "active trim failed" - } - - R 1 flushall - R 1 debug asm-trim-method default - } - - test "Restart will clean up unowned slot keys" { - R 1 flushall - - # generate 1000 keys belonging to slot 0 - R 1 debug populate 1000 [slot_prefix 0] 100 - assert {[scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] >= 1000} - - # restart node-1 - restart_server -1 true false true save - wait_for_cluster_propagation - wait_for_cluster_state "ok" - - # Node-1 has no keys since unowned slot 0 keys were cleaned up during restart - assert {[scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] == {}} - - R 1 flushall - } - - test "Test active trim is used when client tracking is used" { - R 0 flushall - R 1 flushall - R 0 debug asm-trim-method default - R 1 debug asm-trim-method default - - set prev_active_trim [CI 0 cluster_slot_migration_stats_active_trim_completed] - - # Setup a tracking client that is redirected to a pubsub client - set rd_redirection [redis_deferring_client] - $rd_redirection client id - set redir_id [$rd_redirection read] - $rd_redirection subscribe __redis__:invalidate - $rd_redirection read ; # Consume the SUBSCRIBE reply. - - # setup tracking - set key0 [slot_key 0 key] - R 0 CLIENT TRACKING on REDIRECT $redir_id - R 0 SET $key0 1 - R 0 GET $key0 - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_stats_active_trim_completed] == [expr $prev_active_trim + 1] - } else { - fail "active trim did not happen" - } - - # Verify the tracking client received the invalidation message - set msg [$rd_redirection read] - set head [lindex $msg 0] - - if {$head eq "message"} { - # RESP 2 - set got_key [lindex [lindex $msg 2] 0] - } elseif {$head eq "invalidate"} { - # RESP 3 - set got_key [lindex $msg 1 0] - } else { - fail "unexpected invalidation message: $msg" - } - assert_equal $got_key $key0 - - # cleanup - $rd_redirection close - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - R 0 flushall - } -} - -set testmodule [file normalize tests/modules/atomicslotmigration.so] - -start_cluster 3 6 [list tags {external:skip cluster modules} config_lines [list loadmodule $testmodule cluster-node-timeout 60000 cluster-allow-replica-migration no]] { - test "Module api sanity" { - R 0 asm.sanity ;# on master - R 3 asm.sanity ;# on replica - } - - test "Module replicate cross slot command" { - set task_id [setup_slot_migration_with_delay 0 1 0 100] - set listkey [slot_key 0 "asmlist"] - # replicate cross slot command during migrating - R 0 asm.lpush_replicate_crossslot_command $listkey "item1" - - # node 0 will fail due to cross slot - wait_for_condition 2000 10 { - [string match {*canceled*} [migration_status 0 $task_id state]] && - [string match {*cross slot*} [migration_status 0 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - R 1 CLUSTER MIGRATION CANCEL ID $task_id - - # sanity check if lpush replicated correctly to the replica - wait_for_ofs_sync [Rn 0] [Rn 3] - assert_equal {item1} [R 0 lrange $listkey 0 -1] - R 3 readonly - assert_equal {item1} [R 3 lrange $listkey 0 -1] - } - - test "Test RM_ClusterCanAccessKeysInSlot" { - # Test invalid slots - assert_equal 0 [R 0 asm.cluster_can_access_keys_in_slot -1] - assert_equal 0 [R 0 asm.cluster_can_access_keys_in_slot 20000] - assert_equal 0 [R 2 asm.cluster_can_access_keys_in_slot 16384] - assert_equal 0 [R 5 asm.cluster_can_access_keys_in_slot 16384] - - # Test on a master-replica pair - assert_equal 1 [R 0 asm.cluster_can_access_keys_in_slot 0] - assert_equal 1 [R 0 asm.cluster_can_access_keys_in_slot 100] - assert_equal 1 [R 3 asm.cluster_can_access_keys_in_slot 0] - assert_equal 1 [R 3 asm.cluster_can_access_keys_in_slot 100] - - # Test on a master-replica pair - assert_equal 1 [R 2 asm.cluster_can_access_keys_in_slot 16383] - assert_equal 1 [R 5 asm.cluster_can_access_keys_in_slot 16383] - } - - test "Test RM_ClusterCanAccessKeysInSlot returns false for unowned slots" { - # Active trim will be scheduled but it won't run - R 0 debug asm-trim-method active -1 - R 3 debug asm-trim-method active -1 - - setup_slot_migration_with_delay 0 1 0 100 3 1000000 - - # Verify importing slots are not local - assert_equal 0 [R 1 asm.cluster_can_access_keys_in_slot 0] - assert_equal 0 [R 1 asm.cluster_can_access_keys_in_slot 100] - assert_equal 0 [R 4 asm.cluster_can_access_keys_in_slot 0] - assert_equal 0 [R 4 asm.cluster_can_access_keys_in_slot 100] - - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 && - [CI 3 cluster_slot_migration_active_trim_running] == 1 - } else { - fail "migrate failed" - } - - # Wait for config propagation before checking the slot ownership on replica - wait_for_cluster_propagation - - # Verify slots that are being trimmed are not local - assert_equal 0 [R 0 asm.cluster_can_access_keys_in_slot 0] - assert_equal 0 [R 0 asm.cluster_can_access_keys_in_slot 100] - assert_equal 0 [R 3 asm.cluster_can_access_keys_in_slot 0] - assert_equal 0 [R 3 asm.cluster_can_access_keys_in_slot 100] - - # Enabled active trim and wait until it is completed. - R 0 debug asm-trim-method active 0 - R 3 debug asm-trim-method active 0 - wait_for_asm_done - wait_for_ofs_sync [Rn 0] [Rn 3] - - # Verify slots are local after migration - assert_equal 1 [R 1 asm.cluster_can_access_keys_in_slot 0] - assert_equal 1 [R 1 asm.cluster_can_access_keys_in_slot 100] - assert_equal 1 [R 4 asm.cluster_can_access_keys_in_slot 0] - assert_equal 1 [R 4 asm.cluster_can_access_keys_in_slot 100] - - # cleanup - R 0 debug asm-trim-method default - R 3 debug asm-trim-method default - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - R 0 flushall - R 1 flushall - } - - foreach trim_method {"active" "bg"} { - test "Test cluster module notifications on a successful migration ($trim_method-trim)" { - clear_module_event_log - R 0 debug asm-trim-method $trim_method - R 3 debug asm-trim-method $trim_method - R 6 debug asm-trim-method $trim_method - - # Set a key in the slot range - set key [slot_key 0 mykey] - R 0 set $key "value" - - # Migrate the slot ranges - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 100 200 300] - wait_for_asm_done - - set src_id [R 0 cluster myid] - set dest_id [R 1 cluster myid] - - # Verify the events on source, both master and replica - set migrate_event_log [list \ - "sub: cluster-slot-migration-migrate-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100,200-300" \ - "sub: cluster-slot-migration-migrate-completed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100,200-300" \ - ] - assert_equal [R 0 asm.get_cluster_event_log] $migrate_event_log - assert_equal [R 3 asm.get_cluster_event_log] {} - assert_equal [R 6 asm.get_cluster_event_log] {} - - # Verify the events on destination, both master and replica - set import_event_log [list \ - "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100,200-300" \ - "sub: cluster-slot-migration-import-completed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100,200-300" \ - ] - wait_for_condition 500 20 { - [R 1 asm.get_cluster_event_log] eq $import_event_log && - [R 4 asm.get_cluster_event_log] eq $import_event_log && - [R 7 asm.get_cluster_event_log] eq $import_event_log - } else { - puts "R1: [R 1 asm.get_cluster_event_log]" - puts "R4: [R 4 asm.get_cluster_event_log]" - puts "R7: [R 7 asm.get_cluster_event_log]" - fail "ASM import event not received" - } - - # Verify the trim events - if {$trim_method eq "active"} { - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-started, slots:0-100,200-300" \ - "keyspace: key_trimmed, key: $key" \ - "sub: cluster-slot-migration-trim-completed, slots:0-100,200-300" \ - ] - } else { - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-background, slots:0-100,200-300" \ - ] - } - wait_for_condition 500 10 { - [R 0 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 3 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 6 asm.get_cluster_trim_event_log] eq $trim_event_log - } else { - fail "ASM source trim event not received" - } - - # cleanup - R 0 CLUSTER MIGRATION IMPORT 0 100 200 300 - wait_for_asm_done - clear_module_event_log - reset_default_trim_method - R 0 flushall - R 1 flushall - } - - test "Test cluster module notifications on a failed migration ($trim_method-trim)" { - clear_module_event_log - R 1 debug asm-trim-method $trim_method - R 4 debug asm-trim-method $trim_method - R 7 debug asm-trim-method $trim_method - - # Set a key in the slot range - set key [slot_key 0 mykey] - R 0 set $key "value" - - # Start migration and cancel it - set task_id [setup_slot_migration_with_delay 0 1 0 100 0 2000000] - # Wait until at least one key is moved to destination - wait_for_condition 1000 10 { - [scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] >= 1 - } else { - fail "Key not moved to destination" - } - R 1 CLUSTER MIGRATION CANCEL ID $task_id - wait_for_asm_done - - set src_id [R 0 cluster myid] - set dest_id [R 1 cluster myid] - - # Verify the events on source, both master and replica - set migrate_event_log [list \ - "sub: cluster-slot-migration-migrate-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - "sub: cluster-slot-migration-migrate-failed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - ] - assert_equal [R 0 asm.get_cluster_event_log] $migrate_event_log - assert_equal [R 3 asm.get_cluster_event_log] {} - assert_equal [R 6 asm.get_cluster_event_log] {} - - # Verify the events on destination, both master and replica - set import_event_log [list \ - "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - "sub: cluster-slot-migration-import-failed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - ] - wait_for_condition 500 10 { - [R 1 asm.get_cluster_event_log] eq $import_event_log && - [R 4 asm.get_cluster_event_log] eq $import_event_log && - [R 7 asm.get_cluster_event_log] eq $import_event_log - } else { - fail "ASM import event not received" - } - - # Verify the trim events on destination (partially imported keys are trimmed) - if {$trim_method eq "active"} { - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-started, slots:0-100" \ - "keyspace: key_trimmed, key: $key" \ - "sub: cluster-slot-migration-trim-completed, slots:0-100" \ - ] - } else { - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-background, slots:0-100" \ - ] - } - wait_for_condition 500 10 { - [R 1 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 4 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 7 asm.get_cluster_trim_event_log] eq $trim_event_log - } else { - fail "ASM destination trim event not received" - } - - # cleanup - clear_module_event_log - reset_default_trim_method - wait_for_asm_done - R 0 flushall - R 1 flushall - } - - test "Test cluster module notifications on failover ($trim_method-trim)" { - # NOTE: cluster legacy may have a bug, multiple manual failover will fail, - # so only perform one round of failover test, fix it later - if {$trim_method eq "bg"} { - clear_module_event_log - R 1 debug asm-trim-method $trim_method - R 4 debug asm-trim-method $trim_method - R 7 debug asm-trim-method $trim_method - - # Set a key in the slot range - set key [slot_key 0 mykey] - R 0 set $key "value" - - # Start migration - set task_id [setup_slot_migration_with_delay 0 1 0 100 0 2000000] - # Wait until at least one key is moved to destination - wait_for_condition 1000 10 { - [scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] >= 1 - } else { - fail "Key not moved to destination" - } - - failover_and_wait_for_done 4 - wait_for_asm_done - - set src_id [R 0 cluster myid] - set dest_id [R 1 cluster myid] - - # Verify the events on source, both master and replica - set migrate_event_log [list \ - "sub: cluster-slot-migration-migrate-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - "sub: cluster-slot-migration-migrate-failed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - ] - assert_equal [R 0 asm.get_cluster_event_log] $migrate_event_log - assert_equal [R 3 asm.get_cluster_event_log] {} - assert_equal [R 6 asm.get_cluster_event_log] {} - - # Verify the events on destination, both master and replica - set import_event_log [list \ - "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - "sub: cluster-slot-migration-import-failed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - ] - wait_for_condition 500 20 { - [R 1 asm.get_cluster_event_log] eq $import_event_log && - [R 4 asm.get_cluster_event_log] eq $import_event_log && - [R 7 asm.get_cluster_event_log] eq $import_event_log - } else { - puts "R1: [R 1 asm.get_cluster_event_log]" - puts "R4: [R 4 asm.get_cluster_event_log]" - puts "R7: [R 7 asm.get_cluster_event_log]" - fail "ASM import event not received" - } - - # Verify the trim events on destination (partially imported keys are trimmed) - # NOTE: after failover, the new master will initiate the slot trimming, - # and only slot 0 has data, so only slot 0 is trimmed - if {$trim_method eq "active"} { - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-started, slots:0-0" \ - "keyspace: key_trimmed, key: $key" \ - "sub: cluster-slot-migration-trim-completed, slots:0-0" \ - ] - } else { - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-background, slots:0-0" \ - ] - } - wait_for_condition 500 20 { - [R 1 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 4 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 7 asm.get_cluster_trim_event_log] eq $trim_event_log - } else { - puts "R1: [R 1 asm.get_cluster_trim_event_log]" - puts "R4: [R 4 asm.get_cluster_trim_event_log]" - puts "R7: [R 7 asm.get_cluster_trim_event_log]" - fail "ASM destination trim event not received" - } - - # cleanup - failover_and_wait_for_done 1 - clear_module_event_log - reset_default_trim_method - R 0 flushall - R 1 flushall - } - } - } - - foreach with_rdb {"with" "without"} { - test "Test cluster module notifications when replica restart $with_rdb RDB during importing" { - clear_module_event_log - R 1 debug asm-trim-method $trim_method - R 4 debug asm-trim-method $trim_method - R 7 debug asm-trim-method $trim_method - R 4 config set save "" - - set src_id [R 0 cluster myid] - set dest_id [R 1 cluster myid] - - # Set a key in the slot range - set key [slot_key 0 mykey] - R 0 set $key "value" - - # Start migration, 2s delay - set task_id [setup_slot_migration_with_delay 0 1 0 100 0 2000000] - # Wait until at least one key is moved to destination - wait_for_condition 1000 10 { - [scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] >= 1 - } else { - fail "Key not moved to destination" - } - wait_for_ofs_sync [Rn 1] [Rn 4] - - # restart node 4 - if {$with_rdb eq "with"} { - restart_server -4 true false true save ;# rdb save - } else { - restart_server -4 true false true nosave ;# no rdb saved - } - wait_for_cluster_propagation - - wait_for_asm_done - - # started and completed are paired, and not duplicated - set import_event_log [list \ - "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - "sub: cluster-slot-migration-import-completed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - ] - wait_for_condition 500 10 { - [R 1 asm.get_cluster_event_log] eq $import_event_log && - [R 4 asm.get_cluster_event_log] eq $import_event_log && - [R 7 asm.get_cluster_event_log] eq $import_event_log - } else { - fail "ASM import event not received" - } - - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - R 4 save ;# save an empty rdb to override previous one - clear_module_event_log - reset_default_trim_method - R 0 flushall - R 1 flushall - } - } - - test "Test cluster module notifications when replica is disconnected and full resync after importing" { - clear_module_event_log - R 1 debug asm-trim-method $trim_method - R 4 debug asm-trim-method $trim_method - R 7 debug asm-trim-method $trim_method - - set src_id [R 0 cluster myid] - set dest_id [R 1 cluster myid] - - # Set a key in the slot range - set key [slot_key 0 mykey] - R 0 set $key "value" - - # Start migration, 2s delay - set task_id [setup_slot_migration_with_delay 0 1 0 100 0 2000000] - # Wait until at least one key is moved to destination - wait_for_condition 1000 10 { - [scan [regexp -inline {keys\=([\d]*)} [R 1 info keyspace]] keys=%d] >= 1 - } else { - fail "Key not moved to destination" - } - wait_for_ofs_sync [Rn 1] [Rn 4] - - # puase node-4 - set r4_pid [S 4 process_id] - pause_process $r4_pid - - # set a small repl-backlog-size and write some commands to make node-4 - # full resync when reconnecting after waking up - set r1_full_sync [S 1 sync_full] - R 1 config set repl-backlog-size 16kb - R 1 client kill type replica - set 1k_str [string repeat "a" 1024] - for {set i 0} {$i < 2000} {incr i} { - R 1 set [slot_key 6000] $1k_str - } - - # after ASM task is completed, wake up node-4 - wait_for_condition 1000 10 { - [CI 1 cluster_slot_migration_active_tasks] == 0 && - [CI 1 cluster_slot_migration_active_trim_running] == 0 - } else { - fail "ASM tasks did not completed" - } - resume_process $r4_pid - - # make sure full resync happens - wait_for_sync [Rn 4] - wait_for_ofs_sync [Rn 1] [Rn 4] - assert_morethan [S 1 sync_full] $r1_full_sync - - # started and completed are paired, and not duplicated - set import_event_log [list \ - "sub: cluster-slot-migration-import-started, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - "sub: cluster-slot-migration-import-completed, source_node_id:$src_id, destination_node_id:$dest_id, task_id:$task_id, slots:0-100" \ - ] - wait_for_condition 500 10 { - [R 1 asm.get_cluster_event_log] eq $import_event_log && - [R 4 asm.get_cluster_event_log] eq $import_event_log && - [R 7 asm.get_cluster_event_log] eq $import_event_log - } else { - fail "ASM import event not received" - } - - # since ASM task is completed on node-1 before node-4 reconnects, - # no trim event should be received on node-4 - assert_equal {} [R 4 asm.get_cluster_trim_event_log] - - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - clear_module_event_log - reset_default_trim_method - R 0 flushall - R 1 flushall - } - - test "Test new master can trim slots when migration is completed and failover occurs on source side" { - R 0 asm.disable_trim ;# can not start slot trimming on source side - set slot0_key [slot_key 0 mykey] - R 0 set $slot0_key "value" - - # migrate slot 0 from #0 to #1, and wait it completed, but not allow to trim slots - # on source node - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 0] - wait_for_condition 1000 10 { - [string match {*completed*} [migration_status 0 $task_id state]] && - [string match {*completed*} [migration_status 1 $task_id state]] - } else { - fail "ASM task did not complete" - } - # verify trim is not allowed on source node, and replica node doesn't have trim job either - wait_for_ofs_sync [Rn 0] [Rn 3] - assert_equal 1 [R 0 asm.trim_in_progress] - assert_equal "value" [R 0 asm.read_pending_trim_key $slot0_key] - assert_equal 0 [R 3 asm.trim_in_progress] - assert_equal "value" [R 3 asm.read_pending_trim_key $slot0_key] - - set loglines [count_log_lines 0] - - # failover happens on source node, instance #3 become slave, #0 become master - failover_and_wait_for_done 3 - R 0 asm.enable_trim ;# enable trim on old master - - # old master should cancel the pending trim job - wait_for_log_messages 0 {"*Cancelling the pending trim job*"} $loglines 1000 10 - - wait_for_ofs_sync [Rn 3] [Rn 0] - # verify trim is allowed on new master, and the key is trimmed - wait_for_condition 1000 10 { - [R 3 asm.trim_in_progress] == 0 && - [R 3 asm.read_pending_trim_key $slot0_key] eq "" && - [R 0 asm.trim_in_progress] == 0 && - [R 0 asm.read_pending_trim_key $slot0_key] eq "" - } else { - fail "Trim did not complete" - } - - # verify the trim events, use active trim since module is subscribed to trimmed event - set trim_event_log [list \ - "sub: cluster-slot-migration-trim-started, slots:0-0" \ - "keyspace: key_trimmed, key: $slot0_key" \ - "sub: cluster-slot-migration-trim-completed, slots:0-0" \ - ] - wait_for_condition 500 20 { - [R 0 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 3 asm.get_cluster_trim_event_log] eq $trim_event_log && - [R 6 asm.get_cluster_trim_event_log] eq $trim_event_log - } else { - fail "ASM destination trim event not received" - } - - # cleanup - failover_and_wait_for_done 0 - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - clear_module_event_log - reset_default_trim_method - R 0 flushall - R 1 flushall - } - - test "Test module replicates commands at the beginning of slot migration " { - R 0 flushall - R 1 flushall - - # Sanity check - assert_equal 0 [R 1 asm.read_keyless_cmd_val] - assert_equal 0 [R 4 asm.read_keyless_cmd_val] - - # Enable module command replication and set a key to be replicated - # Module will replicate two commands: - # 1- A keyless command: asm.keyless_cmd - # 2- SET command for the given key and value - set keyname [slot_key 0 modulekey] - R 0 asm.replicate_module_command 1 $keyname "value" - - setup_slot_migration_with_delay 0 1 0 100 - wait_for_asm_done - wait_for_ofs_sync [Rn 1] [Rn 4] - - # Verify the commands are replicated - assert_equal 1 [R 1 asm.read_keyless_cmd_val] - assert_equal value [R 1 get $keyname] - - # Verify the commands are replicated to replica - R 4 readonly - assert_equal 1 [R 4 asm.read_keyless_cmd_val] - assert_equal value [R 4 get $keyname] - - # cleanup - R 0 asm.replicate_module_command 0 "" "" - R 0 CLUSTER MIGRATION IMPORT 0 100 - wait_for_asm_done - R 0 flushall - R 1 flushall - } - - test "Test subcommand propagation during slot migration" { - R 0 flushall - R 1 flushall - set task_id [setup_slot_migration_with_delay 0 1 0 100] - - set key [slot_key 0 mykey] - R 0 asm.parent set $key "value" ;# execute a module subcommand - wait_for_asm_done - assert_equal "value" [R 1 GET $key] - - # cleanup - R 0 cluster migration import 0 100 - wait_for_asm_done - } - - test "Test trim method selection based on module keyspace subscription" { - R 0 debug asm-trim-method default - R 1 debug asm-trim-method default - - R 0 flushall - R 1 flushall - - populate_slot 10 -idx 0 -slot 0 - - # Make sure module is subscribed to NOTIFY_KEY_TRIMMED event. In this - # case, active trim must be used. - R 0 asm.subscribe_trimmed_event 1 - set loglines [count_log_lines 0] - R 1 CLUSTER MIGRATION IMPORT 0 15 - wait_for_asm_done - wait_for_log_messages 0 {"*Active trim scheduled for slots: 0-15*"} $loglines 1000 10 - - # Move slots back to node-0. Make sure module is not subscribed to - # NOTIFY_KEY_TRIMMED event. In this case, background trim must be used. - R 1 asm.subscribe_trimmed_event 0 - set loglines [count_log_lines -1] - R 0 CLUSTER MIGRATION IMPORT 0 15 - wait_for_asm_done - wait_for_log_messages -1 {"*Background trim started for slots: 0-15*"} $loglines 1000 10 - - # cleanup - wait_for_asm_done - R 0 asm.subscribe_trimmed_event 1 - R 1 asm.subscribe_trimmed_event 1 - R 0 flushall - R 1 flushall - } - - test "Verify trimmed key value can be read in the server event callback" { - R 0 flushall - set key [slot_key 0] - set value "value123random" - R 0 set $key $value - - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - wait_for_condition 1000 10 { - [R 0 asm.get_last_deleted_key] eq "keyevent: key: $key, value: $value" - } else { - fail "Last deleted key event not received" - } - - # cleanup - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - } - - test "Verify module cannot open a key in a slot that is being trimmed" { - R 0 flushall - R 0 debug asm-trim-method active -1 ;# disable active trim - - set key [slot_key 0] - R 0 set $key value - - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 1 cluster_slot_migration_active_tasks] == 0 && - [CI 0 cluster_slot_migration_active_trim_running] == 1 - } else { - fail "migrate failed" - } - - # We cannot open the key since it is in a slot being trimmed - assert_equal {} [R 0 asm.get $key] - - # cleanup - R 0 debug asm-trim-method default - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - } - - test "Test RM_ClusterGetLocalSlotRanges" { - assert_equal [R 0 asm.cluster_get_local_slot_ranges] {{0 5461}} - assert_equal [R 3 asm.cluster_get_local_slot_ranges] {{0 5461}} - - R 0 cluster migration import 5463 6000 - wait_for_asm_done - wait_for_cluster_propagation - assert_equal [R 0 asm.cluster_get_local_slot_ranges] {{0 5461} {5463 6000}} - assert_equal [R 3 asm.cluster_get_local_slot_ranges] {{0 5461} {5463 6000}} - - R 0 cluster migration import 5462 5462 6001 10922 - wait_for_asm_done - wait_for_cluster_propagation - assert_equal [R 0 asm.cluster_get_local_slot_ranges] {{0 10922}} - assert_equal [R 3 asm.cluster_get_local_slot_ranges] {{0 10922}} - assert_equal [R 1 asm.cluster_get_local_slot_ranges] {} - assert_equal [R 4 asm.cluster_get_local_slot_ranges] {} - } -} - -set testmodule [file normalize tests/modules/atomicslotmigration.so] - -start_cluster 2 0 [list tags {external:skip cluster modules} config_lines [list loadmodule $testmodule cluster-node-timeout 60000 cluster-allow-replica-migration no appendonly yes]] { - test "TRIMSLOTS in AOF will work synchronously on restart" { - # When TRIMSLOTS is replayed from AOF during restart, it must execute - # synchronously rather than using active trim. This prevents race - # conditions where subsequent AOF commands might operate on keys - # that should have been trimmed. - - # Subscribe to key trimmed event to force active trim - R 0 asm.subscribe_trimmed_event 1 - populate_slot 1000 -slot 0 - populate_slot 1000 -slot 1 - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - - # verify active trim is used - assert_equal 1 [CI 0 cluster_slot_migration_stats_active_trim_completed] - - # restart server and verify aof is loaded - restart_server 0 yes no yes nosave - assert {[scan [regexp -inline {aof_current_size:([\d]*)} [R 0 info persistence]] aof_current_size=%d] > 0} - wait_for_cluster_state "ok" - - # verify TRIMSLOTS in AOF is executed synchronously - assert_equal 0 [CI 0 cluster_slot_migration_stats_active_trim_completed] - assert_equal 1000 [R 0 dbsize] - - # cleanup - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - assert_equal 2000 [R 0 dbsize] - R 0 flushall - R 1 flushall - clear_module_event_log - - } - - test "Test trim is disabled when module requests it" { - R 0 asm.disable_trim - - set slot0_key [slot_key 0 mykey] - R 0 set $slot0_key "value" - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 0] - wait_for_condition 1000 10 { - [string match {*completed*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not complete" - } - # since we disable trim, the key should still exist on source, - # we can read it with REDISMODULE_OPEN_KEY_ACCESS_TRIMMED flag - assert_equal "value" [R 0 asm.read_pending_trim_key $slot0_key] - assert_equal 1 [R 0 asm.trim_in_progress] - - # enable trim and verify the key is trimmed - R 0 asm.enable_trim - wait_for_condition 1000 10 { - [R 0 asm.read_pending_trim_key $slot0_key] eq "" && - [R 0 asm.trim_in_progress] == 0 - } else { - fail "Trim did not complete" - } - wait_for_asm_done - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_asm_done - clear_module_event_log - } - - test "Can not start new asm task when trim is not allowed" { - # start a migration task, wait it completed but not allow to trim slots - R 0 asm.disable_trim - set task_id [R 1 CLUSTER MIGRATION IMPORT 0 0] - wait_for_condition 1000 10 { - [string match {*completed*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not complete" - } - # Can not start new migrating task since trim is disabled - set task_id [R 1 CLUSTER MIGRATION IMPORT 1 1] - wait_for_condition 1000 10 { - [string match {*fail*} [migration_status 1 $task_id state]] && - [string match {*Trim is disabled by module*} [migration_status 1 $task_id last_error]] - } else { - fail "ASM task did not fail" - } - R 0 asm.enable_trim - wait_for_asm_done - - # start a migration task, wait it completed but not allow to trim slots - R 0 asm.disable_trim - set task_id [R 1 CLUSTER MIGRATION IMPORT 2 2] - wait_for_condition 1000 10 { - [string match {*completed*} [migration_status 0 $task_id state]] - } else { - fail "ASM task did not complete" - } - set logline [count_log_lines 0] - # Can not start new importing task since trim is disabled - set task_id [R 0 CLUSTER MIGRATION IMPORT 0 1] - wait_for_log_messages 0 {"*Can not start import task*trim is disabled by module*"} $logline 1000 10 - R 0 asm.enable_trim - wait_for_asm_done - } -} - -start_server {tags "cluster external:skip"} { - test "Test RM_ClusterGetLocalSlotRanges without cluster" { - r module load $testmodule - assert_equal [r asm.cluster_get_local_slot_ranges] {{0 16383}} - } -} -} diff --git a/examples/redis-unstable/tests/unit/cluster/cli.tcl b/examples/redis-unstable/tests/unit/cluster/cli.tcl deleted file mode 100644 index ce4629e..0000000 --- a/examples/redis-unstable/tests/unit/cluster/cli.tcl +++ /dev/null @@ -1,415 +0,0 @@ -# Primitive tests on cluster-enabled redis using redis-cli - -source tests/support/cli.tcl - -# make sure the test infra won't use SELECT -set old_singledb $::singledb -set ::singledb 1 - -# cluster creation is complicated with TLS, and the current tests don't really need that coverage -tags {tls:skip external:skip cluster} { - -# start three servers -set base_conf [list cluster-enabled yes cluster-node-timeout 1000] -start_multiple_servers 3 [list overrides $base_conf] { - - set node1 [srv 0 client] - set node2 [srv -1 client] - set node3 [srv -2 client] - set node3_pid [srv -2 pid] - set node3_rd [redis_deferring_client -2] - - test {Create 3 node cluster} { - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - } - - test "Run blocking command on cluster node3" { - # key9184688 is mapped to slot 10923 (first slot of node 3) - $node3_rd brpop key9184688 0 - $node3_rd flush - - wait_for_condition 50 100 { - [s -2 blocked_clients] eq {1} - } else { - fail "Client not blocked" - } - } - - test "Perform a Resharding" { - exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \ - --cluster-to [$node1 cluster myid] \ - --cluster-from [$node3 cluster myid] \ - --cluster-slots 1 - } - - test "Verify command got unblocked after resharding" { - # this (read) will wait for the node3 to realize the new topology - assert_error {*MOVED*} {$node3_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - assert_equal [s -2 blocked_clients] {0} - } - - test "Wait for cluster to be stable" { - # Cluster check just verifies the config state is self-consistent, - # waiting for cluster_state to be okay is an independent check that all the - # nodes actually believe each other are healthy, prevent cluster down error. - wait_for_condition 1000 50 { - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - } - - set node1_rd [redis_deferring_client 0] - - test "use previous hostip in \"cluster-preferred-endpoint-type unknown-endpoint\" mode" { - - # backup and set cluster-preferred-endpoint-type unknown-endpoint - set endpoint_type_before_set [lindex [split [$node1 CONFIG GET cluster-preferred-endpoint-type] " "] 1] - $node1 CONFIG SET cluster-preferred-endpoint-type unknown-endpoint - - # when redis-cli not in cluster mode, return MOVE with empty host - set slot_for_foo [$node1 CLUSTER KEYSLOT foo] - assert_error "*MOVED $slot_for_foo :*" {$node1 set foo bar} - - # when in cluster mode, redirect using previous hostip - assert_equal "[exec src/redis-cli -h 127.0.0.1 -p [srv 0 port] -c set foo bar]" {OK} - assert_match "[exec src/redis-cli -h 127.0.0.1 -p [srv 0 port] -c get foo]" {bar} - - assert_equal [$node1 CONFIG SET cluster-preferred-endpoint-type "$endpoint_type_before_set"] {OK} - } - - test "Sanity test push cmd after resharding" { - assert_error {*MOVED*} {$node3 lpush key9184688 v1} - - $node1_rd brpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - puts "Client not blocked" - puts "read from blocked client: [$node1_rd read]" - fail "Client not blocked" - } - - $node1 lpush key9184688 v2 - assert_equal {key9184688 v2} [$node1_rd read] - } - - $node3_rd close - - test "Run blocking command again on cluster node1" { - $node1 del key9184688 - # key9184688 is mapped to slot 10923 which has been moved to node1 - $node1_rd brpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - fail "Client not blocked" - } - } - - test "Kill a cluster node and wait for fail state" { - # kill node3 in cluster - pause_process $node3_pid - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {fail} && - [CI 1 cluster_state] eq {fail} - } else { - fail "Cluster doesn't fail" - } - } - - test "Verify command got unblocked after cluster failure" { - assert_error {*CLUSTERDOWN*} {$node1_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - } - - resume_process $node3_pid - $node1_rd close - -} ;# stop servers - -# Test redis-cli -- cluster create, add-node, call. -# Test that functions are propagated on add-node -start_multiple_servers 5 [list overrides $base_conf] { - - set node4_rd [redis_client -3] - set node5_rd [redis_client -4] - - test {Functions are added to new node on redis-cli cluster add-node} { - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] - - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # upload a function to all the cluster - exec src/redis-cli --cluster-yes --cluster call 127.0.0.1:[srv 0 port] \ - FUNCTION LOAD {#!lua name=TEST - redis.register_function('test', function() return 'hello' end) - } - - # adding node to the cluster - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -3 port] \ - 127.0.0.1:[srv 0 port] - - wait_for_cluster_size 4 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # make sure 'test' function was added to the new node - assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST] - - # add function to node 5 - assert_equal {TEST} [$node5_rd FUNCTION LOAD {#!lua name=TEST - redis.register_function('test', function() return 'hello' end) - }] - - # make sure functions was added to node 5 - assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST] - - # adding node 5 to the cluster should failed because it already contains the 'test' function - catch { - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -4 port] \ - 127.0.0.1:[srv 0 port] - } e - assert_match {*node already contains functions*} $e - } -} ;# stop servers - -# Test redis-cli --cluster create, add-node. -# Test that one slot can be migrated to and then away from the new node. -test {Migrate the last slot away from a node using redis-cli} { - start_multiple_servers 4 [list overrides $base_conf] { - - # Create a cluster of 3 nodes - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Insert some data - assert_equal OK [exec src/redis-cli -c -p [srv 0 port] SET foo bar] - set slot [exec src/redis-cli -c -p [srv 0 port] CLUSTER KEYSLOT foo] - - # Add new node to the cluster - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -3 port] \ - 127.0.0.1:[srv 0 port] - - # First we wait for new node to be recognized by entire cluster - wait_for_cluster_size 4 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - set newnode_r [redis_client -3] - set newnode_id [$newnode_r CLUSTER MYID] - - # Find out which node has the key "foo" by asking the new node for a - # redirect. - catch { $newnode_r get foo } e - assert_match "MOVED $slot *" $e - lassign [split [lindex $e 2] :] owner_host owner_port - set owner_r [redis $owner_host $owner_port 0 $::tls] - set owner_id [$owner_r CLUSTER MYID] - - # Move slot to new node using plain Redis commands - assert_equal OK [$newnode_r CLUSTER SETSLOT $slot IMPORTING $owner_id] - assert_equal OK [$owner_r CLUSTER SETSLOT $slot MIGRATING $newnode_id] - assert_equal {foo} [$owner_r CLUSTER GETKEYSINSLOT $slot 10] - assert_equal OK [$owner_r MIGRATE 127.0.0.1 [srv -3 port] "" 0 5000 KEYS foo] - assert_equal OK [$newnode_r CLUSTER SETSLOT $slot NODE $newnode_id] - assert_equal OK [$owner_r CLUSTER SETSLOT $slot NODE $newnode_id] - - # Using --cluster check make sure we won't get `Not all slots are covered by nodes`. - # Wait for the cluster to become stable make sure the cluster is up during MIGRATE. - wait_for_condition 1000 50 { - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -3 port]}] == 0 && - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Move the only slot back to original node using redis-cli - exec src/redis-cli --cluster reshard 127.0.0.1:[srv -3 port] \ - --cluster-from $newnode_id \ - --cluster-to $owner_id \ - --cluster-slots 1 \ - --cluster-yes - - # The empty node will become a replica of the new owner before the - # `MOVED` check, so let's wait for the cluster to become stable. - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Check that the key foo has been migrated back to the original owner. - catch { $newnode_r get foo } e - assert_equal "MOVED $slot $owner_host:$owner_port" $e - - # Check that the empty node has turned itself into a replica of the new - # owner and that the new owner knows that. - wait_for_condition 1000 50 { - [string match "*slave*" [$owner_r CLUSTER REPLICAS $owner_id]] - } else { - fail "Empty node didn't turn itself into a replica." - } - } -} - -foreach ip_or_localhost {127.0.0.1 localhost} { - -# Test redis-cli --cluster create, add-node with cluster-port. -# Create five nodes, three with custom cluster_port and two with default values. -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { - - # The first three are used to test --cluster create. - # The last two are used to test --cluster add-node - - test "redis-cli -4 --cluster create using $ip_or_localhost with cluster-port" { - exec src/redis-cli -4 --cluster-yes --cluster create \ - $ip_or_localhost:[srv 0 port] \ - $ip_or_localhost:[srv -1 port] \ - $ip_or_localhost:[srv -2 port] - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Make sure each node can meet other nodes - assert_equal 3 [CI 0 cluster_known_nodes] - assert_equal 3 [CI 1 cluster_known_nodes] - assert_equal 3 [CI 2 cluster_known_nodes] - } - - test "redis-cli -4 --cluster add-node using $ip_or_localhost with cluster-port" { - # Adding node to the cluster (without cluster-port) - exec src/redis-cli -4 --cluster-yes --cluster add-node \ - $ip_or_localhost:[srv -3 port] \ - $ip_or_localhost:[srv 0 port] - - wait_for_cluster_size 4 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Adding node to the cluster (with cluster-port) - exec src/redis-cli -4 --cluster-yes --cluster add-node \ - $ip_or_localhost:[srv -4 port] \ - $ip_or_localhost:[srv 0 port] - - wait_for_cluster_size 5 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} && - [CI 4 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Make sure each node can meet other nodes - assert_equal 5 [CI 0 cluster_known_nodes] - assert_equal 5 [CI 1 cluster_known_nodes] - assert_equal 5 [CI 2 cluster_known_nodes] - assert_equal 5 [CI 3 cluster_known_nodes] - assert_equal 5 [CI 4 cluster_known_nodes] - } -# stop 5 servers -} -} -} -} -} - -} ;# foreach ip_or_localhost - -} ;# tags - -set ::singledb $old_singledb diff --git a/examples/redis-unstable/tests/unit/cluster/cluster-response-tls.tcl b/examples/redis-unstable/tests/unit/cluster/cluster-response-tls.tcl deleted file mode 100644 index a099fa7..0000000 --- a/examples/redis-unstable/tests/unit/cluster/cluster-response-tls.tcl +++ /dev/null @@ -1,110 +0,0 @@ -source tests/support/cluster.tcl - -proc get_port_from_moved_error {e} { - set ip_port [lindex [split $e " "] 2] - return [lindex [split $ip_port ":"] 1] -} - -proc get_pport_by_port {port} { - foreach srv $::servers { - set srv_port [dict get $srv port] - if {$port == $srv_port} { - return [dict get $srv pport] - } - } - return 0 -} - -proc get_port_from_node_info {line} { - set fields [split $line " "] - set addr [lindex $fields 1] - set ip_port [lindex [split $addr "@"] 0] - return [lindex [split $ip_port ":"] 1] -} - -proc cluster_response_tls {tls_cluster} { - - test "CLUSTER SLOTS with different connection type -- tls-cluster $tls_cluster" { - set slots1 [R 0 cluster slots] - set pport [srv 0 pport] - set cluster_client [redis_cluster 127.0.0.1:$pport 0] - set slots2 [$cluster_client cluster slots] - $cluster_client close - # Compare the ports in the first row - assert_no_match [lindex $slots1 0 2 1] [lindex $slots2 0 2 1] - } - - test "CLUSTER NODES return port according to connection type -- tls-cluster $tls_cluster" { - set nodes [R 0 cluster nodes] - set port1 [get_port_from_node_info [lindex [split $nodes "\r\n"] 0]] - set pport [srv 0 pport] - set cluster_client [redis_cluster 127.0.0.1:$pport 0] - set nodes [$cluster_client cluster nodes] - set port2 [get_port_from_node_info [lindex [split $nodes "\r\n"] 0]] - $cluster_client close - assert_not_equal $port1 $port2 - } - - set cluster [redis_cluster 127.0.0.1:[srv 0 port]] - set cluster_pport [redis_cluster 127.0.0.1:[srv 0 pport] 0] - $cluster refresh_nodes_map - - test "Set many keys in the cluster -- tls-cluster $tls_cluster" { - for {set i 0} {$i < 5000} {incr i} { - $cluster set $i $i - assert { [$cluster get $i] eq $i } - } - } - - test "Test cluster responses during migration of slot x -- tls-cluster $tls_cluster" { - set slot 10 - array set nodefrom [$cluster masternode_for_slot $slot] - array set nodeto [$cluster masternode_notfor_slot $slot] - $nodeto(link) cluster setslot $slot importing $nodefrom(id) - $nodefrom(link) cluster setslot $slot migrating $nodeto(id) - - # Get a key from that slot - set key [$nodefrom(link) cluster GETKEYSINSLOT $slot "1"] - # MOVED REPLY - catch {$nodeto(link) set $key "newVal"} e_moved1 - assert_match "*MOVED*" $e_moved1 - # ASK REPLY - catch {$nodefrom(link) set "abc{$key}" "newVal"} e_ask1 - assert_match "*ASK*" $e_ask1 - - # UNSTABLE REPLY - assert_error "*TRYAGAIN*" {$nodefrom(link) mset "a{$key}" "newVal" $key "newVal2"} - - # Connecting using another protocol - array set nodefrom_pport [$cluster_pport masternode_for_slot $slot] - array set nodeto_pport [$cluster_pport masternode_notfor_slot $slot] - - # MOVED REPLY - catch {$nodeto_pport(link) set $key "newVal"} e_moved2 - assert_match "*MOVED*" $e_moved2 - # ASK REPLY - catch {$nodefrom_pport(link) set "abc{$key}" "newVal"} e_ask2 - assert_match "*ASK*" $e_ask2 - # Compare MOVED error's port - set port1 [get_port_from_moved_error $e_moved1] - set port2 [get_port_from_moved_error $e_moved2] - assert_not_equal $port1 $port2 - assert_equal $port1 $nodefrom(port) - assert_equal $port2 [get_pport_by_port $nodefrom(port)] - # Compare ASK error's port - set port1 [get_port_from_moved_error $e_ask1] - set port2 [get_port_from_moved_error $e_ask2] - assert_not_equal $port1 $port2 - assert_equal $port1 $nodeto(port) - assert_equal $port2 [get_pport_by_port $nodeto(port)] - } -} - -if {$::tls} { - start_cluster 3 3 {tags {external:skip cluster tls} overrides {tls-cluster yes tls-replication yes}} { - cluster_response_tls yes - } - start_cluster 3 3 {tags {external:skip cluster tls} overrides {tls-cluster no tls-replication no}} { - cluster_response_tls no - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/failure-marking.tcl b/examples/redis-unstable/tests/unit/cluster/failure-marking.tcl deleted file mode 100644 index c4746c8..0000000 --- a/examples/redis-unstable/tests/unit/cluster/failure-marking.tcl +++ /dev/null @@ -1,53 +0,0 @@ -# Test a single primary can mark replica as `fail` -start_cluster 1 1 {tags {external:skip cluster}} { - - test "Verify that single primary marks replica as failed" { - set primary [srv -0 client] - - set replica1 [srv -1 client] - set replica1_pid [srv -1 pid] - set replica1_instance_id [dict get [cluster_get_myself 1] id] - - assert {[lindex [$primary role] 0] eq {master}} - assert {[lindex [$replica1 role] 0] eq {slave}} - - wait_for_sync $replica1 - - pause_process $replica1_pid - - wait_node_marked_fail 0 $replica1_instance_id - } -} - -# Test multiple primaries wait for a quorum and then mark a replica as `fail` -start_cluster 2 1 {tags {external:skip cluster}} { - - test "Verify that multiple primaries mark replica as failed" { - set primary1 [srv -0 client] - - set primary2 [srv -1 client] - set primary2_pid [srv -1 pid] - - set replica1 [srv -2 client] - set replica1_pid [srv -2 pid] - set replica1_instance_id [dict get [cluster_get_myself 2] id] - - assert {[lindex [$primary1 role] 0] eq {master}} - assert {[lindex [$primary2 role] 0] eq {master}} - assert {[lindex [$replica1 role] 0] eq {slave}} - - wait_for_sync $replica1 - - pause_process $replica1_pid - - # Pause other primary to allow time for pfail flag to appear - pause_process $primary2_pid - - wait_node_marked_pfail 0 $replica1_instance_id - - # Resume other primary and wait for to show replica as failed - resume_process $primary2_pid - - wait_node_marked_fail 0 $replica1_instance_id - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/hostnames.tcl b/examples/redis-unstable/tests/unit/cluster/hostnames.tcl deleted file mode 100644 index 2236228..0000000 --- a/examples/redis-unstable/tests/unit/cluster/hostnames.tcl +++ /dev/null @@ -1,230 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -proc get_slot_field {slot_output shard_id node_id attrib_id} { - return [lindex [lindex [lindex $slot_output $shard_id] $node_id] $attrib_id] -} - -# Start a cluster with 3 masters and 4 replicas. -# These tests rely on specific node ordering, so make sure no node fails over. -start_cluster 3 4 {tags {external:skip cluster} overrides {cluster-replica-no-failover yes}} { -test "Set cluster hostnames and verify they are propagated" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "host-$j.com" - } - - wait_for_condition 50 100 { - [are_hostnames_propagated "host-*.com"] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - - # Now that everything is propagated, assert everyone agrees - wait_for_cluster_propagation -} - -test "Update hostnames and make sure they are all eventually propagated" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "host-updated-$j.com" - } - - wait_for_condition 50 100 { - [are_hostnames_propagated "host-updated-*.com"] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - - # Now that everything is propagated, assert everyone agrees - wait_for_cluster_propagation -} - -test "Remove hostnames and make sure they are all eventually propagated" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "" - } - - wait_for_condition 50 100 { - [are_hostnames_propagated ""] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - - # Now that everything is propagated, assert everyone agrees - wait_for_cluster_propagation -} - -test "Verify cluster-preferred-endpoint-type behavior for redirects and info" { - R 0 config set cluster-announce-hostname "me.com" - R 1 config set cluster-announce-hostname "" - R 2 config set cluster-announce-hostname "them.com" - - wait_for_cluster_propagation - - # Verify default behavior - set slot_result [R 0 cluster slots] - assert_equal "" [lindex [get_slot_field $slot_result 0 2 0] 1] - assert_equal "" [lindex [get_slot_field $slot_result 2 2 0] 1] - assert_equal "hostname" [lindex [get_slot_field $slot_result 0 2 3] 0] - assert_equal "me.com" [lindex [get_slot_field $slot_result 0 2 3] 1] - assert_equal "hostname" [lindex [get_slot_field $slot_result 2 2 3] 0] - assert_equal "them.com" [lindex [get_slot_field $slot_result 2 2 3] 1] - - # Redirect will use the IP address - catch {R 0 set foo foo} redir_err - assert_match "MOVED * 127.0.0.1:*" $redir_err - - # Verify prefer hostname behavior - R 0 config set cluster-preferred-endpoint-type hostname - - set slot_result [R 0 cluster slots] - assert_equal "me.com" [get_slot_field $slot_result 0 2 0] - assert_equal "them.com" [get_slot_field $slot_result 2 2 0] - - # Redirect should use hostname - catch {R 0 set foo foo} redir_err - assert_match "MOVED * them.com:*" $redir_err - - # Redirect to an unknown hostname returns ? - catch {R 0 set barfoo bar} redir_err - assert_match "MOVED * ?:*" $redir_err - - # Verify unknown hostname behavior - R 0 config set cluster-preferred-endpoint-type unknown-endpoint - - # Verify default behavior - set slot_result [R 0 cluster slots] - assert_equal "ip" [lindex [get_slot_field $slot_result 0 2 3] 0] - assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 0 2 3] 1] - assert_equal "ip" [lindex [get_slot_field $slot_result 2 2 3] 0] - assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 2 2 3] 1] - assert_equal "ip" [lindex [get_slot_field $slot_result 1 2 3] 0] - assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 1 2 3] 1] - # Not required by the protocol, but IP comes before hostname - assert_equal "hostname" [lindex [get_slot_field $slot_result 0 2 3] 2] - assert_equal "me.com" [lindex [get_slot_field $slot_result 0 2 3] 3] - assert_equal "hostname" [lindex [get_slot_field $slot_result 2 2 3] 2] - assert_equal "them.com" [lindex [get_slot_field $slot_result 2 2 3] 3] - - # This node doesn't have a hostname - assert_equal 2 [llength [get_slot_field $slot_result 1 2 3]] - - # Redirect should use empty string - catch {R 0 set foo foo} redir_err - assert_match "MOVED * :*" $redir_err - - R 0 config set cluster-preferred-endpoint-type ip -} - -test "Verify the nodes configured with prefer hostname only show hostname for new nodes" { - # Have everyone forget node 6 and isolate it from the cluster. - isolate_node 6 - - set primaries 3 - for {set j 0} {$j < $primaries} {incr j} { - # Set hostnames for the masters, now that the node is isolated - R $j config set cluster-announce-hostname "shard-$j.com" - } - - # Prevent Node 0 and Node 6 from properly meeting, - # they'll hang in the handshake phase. This allows us to - # test the case where we "know" about it but haven't - # successfully retrieved information about it yet. - R 0 DEBUG DROP-CLUSTER-PACKET-FILTER 0 - R 6 DEBUG DROP-CLUSTER-PACKET-FILTER 0 - - # Have a replica meet the isolated node - R 3 cluster meet 127.0.0.1 [srv -6 port] - - # Wait for the isolated node to learn about the rest of the cluster, - # which correspond to a single entry in cluster nodes. Note this - # doesn't mean the isolated node has successfully contacted each - # node. - wait_for_condition 50 100 { - [llength [split [R 6 CLUSTER NODES] "\n"]] eq [expr [llength $::servers] + 1] - } else { - fail "Isolated node didn't learn about the rest of the cluster *" - } - - # Now, we wait until the two nodes that aren't filtering packets - # to accept our isolated nodes connections. At this point they will - # start showing up in cluster slots. - wait_for_condition 50 100 { - [llength [R 6 CLUSTER SLOTS]] eq 2 - } else { - fail "Node did not learn about the 2 shards it can talk to" - } - wait_for_condition 50 100 { - [lindex [get_slot_field [R 6 CLUSTER SLOTS] 0 2 3] 1] eq "shard-1.com" - } else { - fail "hostname for shard-1 didn't reach node 6" - } - - wait_for_condition 50 100 { - [lindex [get_slot_field [R 6 CLUSTER SLOTS] 1 2 3] 1] eq "shard-2.com" - } else { - fail "hostname for shard-2 didn't reach node 6" - } - - # Also make sure we know about the isolated master, we - # just can't reach it. - set master_id [R 0 CLUSTER MYID] - assert_match "*$master_id*" [R 6 CLUSTER NODES] - - # Stop dropping cluster packets, and make sure everything - # stabilizes - R 0 DEBUG DROP-CLUSTER-PACKET-FILTER -1 - R 6 DEBUG DROP-CLUSTER-PACKET-FILTER -1 - - # This operation sometimes spikes to around 5 seconds to resolve the state, - # so it has a higher timeout. - wait_for_condition 50 500 { - [llength [R 6 CLUSTER SLOTS]] eq 3 - } else { - fail "Node did not learn about the 2 shards it can talk to" - } - - for {set j 0} {$j < $primaries} {incr j} { - wait_for_condition 50 100 { - [lindex [get_slot_field [R 6 CLUSTER SLOTS] $j 2 3] 1] eq "shard-$j.com" - } else { - fail "hostname information for shard-$j didn't reach node 6" - } - } -} - -test "Test restart will keep hostname information" { - # Set a new hostname, reboot and make sure it sticks - R 0 config set cluster-announce-hostname "restart-1.com" - - # Store the hostname in the config - R 0 config rewrite - - restart_server 0 true false - set slot_result [R 0 CLUSTER SLOTS] - assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "restart-1.com" - - # As a sanity check, make sure everyone eventually agrees - wait_for_cluster_propagation -} - -test "Test hostname validation" { - catch {R 0 config set cluster-announce-hostname [string repeat x 256]} err - assert_match "*Hostnames must be less than 256 characters*" $err - catch {R 0 config set cluster-announce-hostname "?.com"} err - assert_match "*Hostnames may only contain alphanumeric characters, hyphens or dots*" $err - - # Note this isn't a valid hostname, but it passes our internal validation - R 0 config set cluster-announce-hostname "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-." -} -} diff --git a/examples/redis-unstable/tests/unit/cluster/human-announced-nodename.tcl b/examples/redis-unstable/tests/unit/cluster/human-announced-nodename.tcl deleted file mode 100644 index a595ca6..0000000 --- a/examples/redis-unstable/tests/unit/cluster/human-announced-nodename.tcl +++ /dev/null @@ -1,29 +0,0 @@ -# Check if cluster's view of human announced nodename is reported in logs -start_cluster 3 0 {tags {external:skip cluster}} { - test "Set cluster human announced nodename and let it propagate" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "host-$j.com" - R $j config set cluster-announce-human-nodename "nodename-$j" - } - - # We wait for everyone to agree on the hostnames. Since they are gossiped - # the same way as nodenames, it implies everyone knows the nodenames too. - wait_for_condition 50 100 { - [are_hostnames_propagated "host-*.com"] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - } - - test "Human nodenames are visible in log messages" { - # Pause instance 0, so everyone thinks it is dead - pause_process [srv 0 pid] - - # We're going to use a message we will know will be sent, node unreachable, - # since it includes the other node gossiping. - wait_for_log_messages -1 {"*Node * (nodename-2) reported node * (nodename-0) as not reachable*"} 0 20 500 - wait_for_log_messages -2 {"*Node * (nodename-1) reported node * (nodename-0) as not reachable*"} 0 20 500 - - resume_process [srv 0 pid] - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/internal-secret.tcl b/examples/redis-unstable/tests/unit/cluster/internal-secret.tcl deleted file mode 100644 index f310b74..0000000 --- a/examples/redis-unstable/tests/unit/cluster/internal-secret.tcl +++ /dev/null @@ -1,71 +0,0 @@ -proc num_unique_secrets {num_nodes} { - set secrets [list] - for {set i 0} {$i < $num_nodes} {incr i} { - lappend secrets [R $i debug internal_secret] - } - set num_secrets [llength [lsort -unique $secrets]] - return $num_secrets -} - -proc wait_for_secret_sync {maxtries delay num_nodes} { - wait_for_condition $maxtries $delay { - [num_unique_secrets $num_nodes] eq 1 - } else { - fail "Failed waiting for secrets to sync" - } -} - -start_cluster 3 3 {tags {external:skip cluster}} { - test "Test internal secret sync" { - wait_for_secret_sync 50 100 6 - } - - - set first_shard_host [srv 0 host] - set first_shard_port [srv 0 port] - - if {$::verbose} { - puts {cluster internal secret:} - puts [R 1 debug internal_secret] - } - - test "Join a node to the cluster and make sure it gets the same secret" { - start_server {tags {"external:skip"} overrides {cluster-enabled {yes}}} { - r cluster meet $first_shard_host $first_shard_port - wait_for_condition 50 100 { - [r debug internal_secret] eq [R 1 debug internal_secret] - } else { - puts [r debug internal_secret] - puts [R 1 debug internal_secret] - fail "Secrets not match" - } - } - } - - test "Join another cluster, make sure clusters sync on the internal secret" { - start_server {tags {"external:skip"} overrides {cluster-enabled {yes}}} { - set new_shard_host [srv 0 host] - set new_shard_port [srv 0 port] - start_server {tags {"external:skip"} overrides {cluster-enabled {yes}}} { - r cluster meet $new_shard_host $new_shard_port - wait_for_condition 50 100 { - [r debug internal_secret] eq [r -1 debug internal_secret] - } else { - puts [r debug internal_secret] - puts [r -1 debug internal_secret] - fail "Secrets not match" - } - if {$::verbose} { - puts {new cluster internal secret:} - puts [r -1 debug internal_secret] - } - r cluster meet $first_shard_host $first_shard_port - wait_for_secret_sync 50 100 8 - if {$::verbose} { - puts {internal secret after join to bigger cluster:} - puts [r -1 debug internal_secret] - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/links.tcl b/examples/redis-unstable/tests/unit/cluster/links.tcl deleted file mode 100644 index a202c37..0000000 --- a/examples/redis-unstable/tests/unit/cluster/links.tcl +++ /dev/null @@ -1,292 +0,0 @@ -proc get_links_with_peer {this_instance_id peer_nodename} { - set links [R $this_instance_id cluster links] - set links_with_peer {} - foreach l $links { - if {[dict get $l node] eq $peer_nodename} { - lappend links_with_peer $l - } - } - return $links_with_peer -} - -# Return the entry in CLUSTER LINKS output by instance identified by `this_instance_id` that -# corresponds to the link established toward a peer identified by `peer_nodename` -proc get_link_to_peer {this_instance_id peer_nodename} { - set links_with_peer [get_links_with_peer $this_instance_id $peer_nodename] - foreach l $links_with_peer { - if {[dict get $l direction] eq "to"} { - return $l - } - } - return {} -} - -# Return the entry in CLUSTER LINKS output by instance identified by `this_instance_id` that -# corresponds to the link accepted from a peer identified by `peer_nodename` -proc get_link_from_peer {this_instance_id peer_nodename} { - set links_with_peer [get_links_with_peer $this_instance_id $peer_nodename] - foreach l $links_with_peer { - if {[dict get $l direction] eq "from"} { - return $l - } - } - return {} -} - -# Reset cluster links to their original state -proc reset_links {id} { - set limit [lindex [R $id CONFIG get cluster-link-sendbuf-limit] 1] - - # Set a 1 byte limit and wait for cluster cron to run - # (executes every 100ms) and terminate links - R $id CONFIG SET cluster-link-sendbuf-limit 1 - after 150 - - # Reset limit - R $id CONFIG SET cluster-link-sendbuf-limit $limit - - # Wait until the cluster links come back up for each node - wait_for_condition 50 100 { - [number_of_links $id] == [expr [number_of_peers $id] * 2] - } else { - fail "Cluster links did not come back up" - } -} - -proc number_of_peers {id} { - expr [llength $::servers] - 1 -} - -proc number_of_links {id} { - llength [R $id cluster links] -} - -proc publish_messages {server num_msgs msg_size} { - for {set i 0} {$i < $num_msgs} {incr i} { - $server PUBLISH channel [string repeat "x" $msg_size] - } -} - -start_cluster 1 2 {tags {external:skip cluster}} { - set primary_id 0 - set replica1_id 1 - - set primary [Rn $primary_id] - set replica1 [Rn $replica1_id] - - test "Broadcast message across a cluster shard while a cluster link is down" { - set replica1_node_id [$replica1 CLUSTER MYID] - - set channelname ch3 - - # subscribe on replica1 - set subscribeclient1 [redis_deferring_client -1] - $subscribeclient1 deferred 1 - $subscribeclient1 SSUBSCRIBE $channelname - $subscribeclient1 read - - # subscribe on replica2 - set subscribeclient2 [redis_deferring_client -2] - $subscribeclient2 deferred 1 - $subscribeclient2 SSUBSCRIBE $channelname - $subscribeclient2 read - - # Verify number of links with cluster stable state - assert_equal [expr [number_of_peers $primary_id]*2] [number_of_links $primary_id] - - # Disconnect the cluster between primary and replica1 and publish a message. - $primary MULTI - $primary DEBUG CLUSTERLINK KILL TO $replica1_node_id - $primary SPUBLISH $channelname hello - set res [$primary EXEC] - - # Verify no client exists on the primary to receive the published message. - assert_equal $res {OK 0} - - # Wait for all the cluster links are healthy - wait_for_condition 50 100 { - [number_of_peers $primary_id]*2 == [number_of_links $primary_id] - } else { - fail "All peer links couldn't be established" - } - - # Publish a message afterwards. - $primary SPUBLISH $channelname world - - # Verify replica1 has received only (world) / hello is lost. - assert_equal "smessage ch3 world" [$subscribeclient1 read] - - # Verify replica2 has received both messages (hello/world) - assert_equal "smessage ch3 hello" [$subscribeclient2 read] - assert_equal "smessage ch3 world" [$subscribeclient2 read] - } {} {needs:debug} -} - -start_cluster 3 0 {tags {external:skip cluster}} { - test "Each node has two links with each peer" { - for {set id 0} {$id < [llength $::servers]} {incr id} { - # Assert that from point of view of each node, there are two links for - # each peer. It might take a while for cluster to stabilize so wait up - # to 5 seconds. - wait_for_condition 50 100 { - [number_of_peers $id]*2 == [number_of_links $id] - } else { - assert_equal [expr [number_of_peers $id]*2] [number_of_links $id] - } - - set nodes [get_cluster_nodes $id] - set links [R $id cluster links] - - # For each peer there should be exactly one - # link "to" it and one link "from" it. - foreach n $nodes { - if {[cluster_has_flag $n myself]} continue - set peer [dict get $n id] - set to 0 - set from 0 - foreach l $links { - if {[dict get $l node] eq $peer} { - if {[dict get $l direction] eq "to"} { - incr to - } elseif {[dict get $l direction] eq "from"} { - incr from - } - } - } - assert {$to eq 1} - assert {$from eq 1} - } - } - } - - test {Validate cluster links format} { - set lines [R 0 cluster links] - foreach l $lines { - if {$l eq {}} continue - assert_equal [llength $l] 12 - assert_equal 1 [dict exists $l "direction"] - assert_equal 1 [dict exists $l "node"] - assert_equal 1 [dict exists $l "create-time"] - assert_equal 1 [dict exists $l "events"] - assert_equal 1 [dict exists $l "send-buffer-allocated"] - assert_equal 1 [dict exists $l "send-buffer-used"] - } - } - - set primary1_id 0 - set primary2_id 1 - - set primary1 [Rn $primary1_id] - set primary2 [Rn $primary2_id] - - test "Disconnect link when send buffer limit reached" { - # On primary1, set timeout to 1 hour so links won't get disconnected due to timeouts - set oldtimeout [lindex [$primary1 CONFIG get cluster-node-timeout] 1] - $primary1 CONFIG set cluster-node-timeout [expr 60*60*1000] - - # Get primary1's links with primary2 - set primary2_name [dict get [cluster_get_myself $primary2_id] id] - set orig_link_p1_to_p2 [get_link_to_peer $primary1_id $primary2_name] - set orig_link_p1_from_p2 [get_link_from_peer $primary1_id $primary2_name] - - # On primary1, set cluster link send buffer limit to 256KB, which is large enough to not be - # overflowed by regular gossip messages but also small enough that it doesn't take too much - # memory to overflow it. If it is set too high, Redis may get OOM killed by kernel before this - # limit is overflowed in some RAM-limited test environments. - set oldlimit [lindex [$primary1 CONFIG get cluster-link-sendbuf-limit] 1] - $primary1 CONFIG set cluster-link-sendbuf-limit [expr 256*1024] - assert {[CI $primary1_id total_cluster_links_buffer_limit_exceeded] eq 0} - - # To manufacture an ever-growing send buffer from primary1 to primary2, - # make primary2 unresponsive. - set primary2_pid [srv [expr -1*$primary2_id] pid] - pause_process $primary2_pid - - # On primary1, send 128KB Pubsub messages in a loop until the send buffer of the link from - # primary1 to primary2 exceeds buffer limit therefore be dropped. - # For the send buffer to grow, we need to first exhaust TCP send buffer of primary1 and TCP - # receive buffer of primary2 first. The sizes of these two buffers vary by OS, but 100 128KB - # messages should be sufficient. - set i 0 - wait_for_condition 100 0 { - [catch {incr i} e] == 0 && - [catch {$primary1 publish channel [prepare_value [expr 128*1024]]} e] == 0 && - [catch {after 500} e] == 0 && - [CI $primary1_id total_cluster_links_buffer_limit_exceeded] >= 1 - } else { - fail "Cluster link not freed as expected" - } - - # A new link to primary2 should have been recreated - set new_link_p1_to_p2 [get_link_to_peer $primary1_id $primary2_name] - assert {[dict get $new_link_p1_to_p2 create-time] > [dict get $orig_link_p1_to_p2 create-time]} - - # Link from primary2 should not be affected - set same_link_p1_from_p2 [get_link_from_peer $primary1_id $primary2_name] - assert {[dict get $same_link_p1_from_p2 create-time] eq [dict get $orig_link_p1_from_p2 create-time]} - - # Revive primary2 - resume_process $primary2_pid - - # Reset configs on primary1 so config changes don't leak out to other tests - $primary1 CONFIG set cluster-node-timeout $oldtimeout - $primary1 CONFIG set cluster-link-sendbuf-limit $oldlimit - - reset_links $primary1_id - } - - test "Link memory increases with publishes" { - set server_id 0 - set server [Rn $server_id] - set msg_size 10000 - set num_msgs 10 - - # Remove any sendbuf limit - $primary1 CONFIG set cluster-link-sendbuf-limit 0 - - # Publish ~100KB to one of the servers - $server MULTI - $server INFO memory - publish_messages $server $num_msgs $msg_size - $server INFO memory - set res [$server EXEC] - - set link_mem_before_pubs [getInfoProperty $res mem_cluster_links] - - # Remove the first half of the response string which contains the - # first "INFO memory" results and search for the property again - set res [string range $res [expr [string length $res] / 2] end] - set link_mem_after_pubs [getInfoProperty $res mem_cluster_links] - - # We expect the memory to have increased by more than - # the culmulative size of the publish messages - set mem_diff_floor [expr $msg_size * $num_msgs] - set mem_diff [expr $link_mem_after_pubs - $link_mem_before_pubs] - assert {$mem_diff > $mem_diff_floor} - - # Reset links to ensure no leftover data for the next test - reset_links $server_id - } - - test "Link memory resets after publish messages flush" { - set server [Rn 0] - set msg_size 100000 - set num_msgs 10 - - set link_mem_before [status $server mem_cluster_links] - - # Publish ~1MB to one of the servers - $server MULTI - publish_messages $server $num_msgs $msg_size - $server EXEC - - # Wait until the cluster link memory has returned to below the pre-publish value. - # We can't guarantee it returns to the exact same value since gossip messages - # can cause the values to fluctuate. - wait_for_condition 1000 500 { - [status $server mem_cluster_links] <= $link_mem_before - } else { - fail "Cluster link memory did not settle back to expected range" - } - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/misc.tcl b/examples/redis-unstable/tests/unit/cluster/misc.tcl deleted file mode 100644 index 62bdcf7..0000000 --- a/examples/redis-unstable/tests/unit/cluster/misc.tcl +++ /dev/null @@ -1,36 +0,0 @@ -start_cluster 2 2 {tags {external:skip cluster}} { - test {Key lazy expires during key migration} { - R 0 DEBUG SET-ACTIVE-EXPIRE 0 - - set key_slot [R 0 CLUSTER KEYSLOT FOO] - R 0 set FOO BAR PX 10 - set src_id [R 0 CLUSTER MYID] - set trg_id [R 1 CLUSTER MYID] - R 0 CLUSTER SETSLOT $key_slot MIGRATING $trg_id - R 1 CLUSTER SETSLOT $key_slot IMPORTING $src_id - after 11 - assert_error {ASK*} {R 0 GET FOO} - R 0 ping - } {PONG} - - test "Coverage: Basic cluster commands" { - assert_equal {OK} [R 0 CLUSTER saveconfig] - - set id [R 0 CLUSTER MYID] - assert_equal {0} [R 0 CLUSTER count-failure-reports $id] - - R 0 flushall - assert_equal {OK} [R 0 CLUSTER flushslots] - } - - test "CROSSSLOT error for keys in different slots" { - # Test MSET with keys in different slots - assert_error {*CROSSSLOT Keys in request don't hash to the same slot*} {R 0 MSET foo bar baz qux} - - # Test DEL with keys in different slots - assert_error {*CROSSSLOT Keys in request don't hash to the same slot*} {R 0 DEL foo bar} - - # Test MGET with keys in different slots - assert_error {*CROSSSLOT Keys in request don't hash to the same slot*} {R 0 MGET foo bar} - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/multi-slot-operations.tcl b/examples/redis-unstable/tests/unit/cluster/multi-slot-operations.tcl deleted file mode 100644 index 5d2d03e..0000000 --- a/examples/redis-unstable/tests/unit/cluster/multi-slot-operations.tcl +++ /dev/null @@ -1,182 +0,0 @@ -# This test uses a custom slot allocation for testing -proc cluster_allocate_with_continuous_slots_local {n} { - R 0 cluster ADDSLOTSRANGE 0 3276 - R 1 cluster ADDSLOTSRANGE 3277 6552 - R 2 cluster ADDSLOTSRANGE 6553 9828 - R 3 cluster ADDSLOTSRANGE 9829 13104 - R 4 cluster ADDSLOTSRANGE 13105 16383 -} - -start_cluster 5 0 {tags {external:skip cluster}} { - -set master1 [srv 0 "client"] -set master2 [srv -1 "client"] -set master3 [srv -2 "client"] -set master4 [srv -3 "client"] -set master5 [srv -4 "client"] - -test "Continuous slots distribution" { - assert_match "* 0-3276*" [$master1 CLUSTER NODES] - assert_match "* 3277-6552*" [$master2 CLUSTER NODES] - assert_match "* 6553-9828*" [$master3 CLUSTER NODES] - assert_match "* 9829-13104*" [$master4 CLUSTER NODES] - assert_match "* 13105-16383*" [$master5 CLUSTER NODES] - assert_match "*0 3276*" [$master1 CLUSTER SLOTS] - assert_match "*3277 6552*" [$master2 CLUSTER SLOTS] - assert_match "*6553 9828*" [$master3 CLUSTER SLOTS] - assert_match "*9829 13104*" [$master4 CLUSTER SLOTS] - assert_match "*13105 16383*" [$master5 CLUSTER SLOTS] - - $master1 CLUSTER DELSLOTSRANGE 3001 3050 - assert_match "* 0-3000 3051-3276*" [$master1 CLUSTER NODES] - assert_match "*0 3000*3051 3276*" [$master1 CLUSTER SLOTS] - - $master2 CLUSTER DELSLOTSRANGE 5001 5500 - assert_match "* 3277-5000 5501-6552*" [$master2 CLUSTER NODES] - assert_match "*3277 5000*5501 6552*" [$master2 CLUSTER SLOTS] - - $master3 CLUSTER DELSLOTSRANGE 7001 7100 8001 8500 - assert_match "* 6553-7000 7101-8000 8501-9828*" [$master3 CLUSTER NODES] - assert_match "*6553 7000*7101 8000*8501 9828*" [$master3 CLUSTER SLOTS] - - $master4 CLUSTER DELSLOTSRANGE 11001 12000 12101 12200 - assert_match "* 9829-11000 12001-12100 12201-13104*" [$master4 CLUSTER NODES] - assert_match "*9829 11000*12001 12100*12201 13104*" [$master4 CLUSTER SLOTS] - - $master5 CLUSTER DELSLOTSRANGE 13501 14000 15001 16000 - assert_match "* 13105-13500 14001-15000 16001-16383*" [$master5 CLUSTER NODES] - assert_match "*13105 13500*14001 15000*16001 16383*" [$master5 CLUSTER SLOTS] -} - -test "ADDSLOTS command with several boundary conditions test suite" { - assert_error "ERR Invalid or out of range slot" {R 0 cluster ADDSLOTS 3001 aaa} - assert_error "ERR Invalid or out of range slot" {R 0 cluster ADDSLOTS 3001 -1000} - assert_error "ERR Invalid or out of range slot" {R 0 cluster ADDSLOTS 3001 30003} - - assert_error "ERR Slot 3200 is already busy" {R 0 cluster ADDSLOTS 3200} - assert_error "ERR Slot 8501 is already busy" {R 0 cluster ADDSLOTS 8501} - - assert_error "ERR Slot 3001 specified multiple times" {R 0 cluster ADDSLOTS 3001 3002 3001} -} - -test "ADDSLOTSRANGE command with several boundary conditions test suite" { - # Add multiple slots with incorrect argument number - assert_error "ERR wrong number of arguments for 'cluster|addslotsrange' command" {R 0 cluster ADDSLOTSRANGE 3001 3020 3030} - - # Add multiple slots with invalid input slot - assert_error "ERR Invalid or out of range slot" {R 0 cluster ADDSLOTSRANGE 3001 3020 3030 aaa} - assert_error "ERR Invalid or out of range slot" {R 0 cluster ADDSLOTSRANGE 3001 3020 3030 70000} - assert_error "ERR Invalid or out of range slot" {R 0 cluster ADDSLOTSRANGE 3001 3020 -1000 3030} - - # Add multiple slots when start slot number is greater than the end slot - assert_error "ERR start slot number 3030 is greater than end slot number 3025" {R 0 cluster ADDSLOTSRANGE 3001 3020 3030 3025} - - # Add multiple slots with busy slot - assert_error "ERR Slot 3200 is already busy" {R 0 cluster ADDSLOTSRANGE 3001 3020 3200 3250} - - # Add multiple slots with assigned multiple times - assert_error "ERR Slot 3001 specified multiple times" {R 0 cluster ADDSLOTSRANGE 3001 3020 3001 3020} -} - -test "DELSLOTSRANGE command with several boundary conditions test suite" { - # Delete multiple slots with incorrect argument number - assert_error "ERR wrong number of arguments for 'cluster|delslotsrange' command" {R 0 cluster DELSLOTSRANGE 1000 2000 2100} - assert_match "* 0-3000 3051-3276*" [$master1 CLUSTER NODES] - assert_match "*0 3000*3051 3276*" [$master1 CLUSTER SLOTS] - - # Delete multiple slots with invalid input slot - assert_error "ERR Invalid or out of range slot" {R 0 cluster DELSLOTSRANGE 1000 2000 2100 aaa} - assert_error "ERR Invalid or out of range slot" {R 0 cluster DELSLOTSRANGE 1000 2000 2100 70000} - assert_error "ERR Invalid or out of range slot" {R 0 cluster DELSLOTSRANGE 1000 2000 -2100 2200} - assert_match "* 0-3000 3051-3276*" [$master1 CLUSTER NODES] - assert_match "*0 3000*3051 3276*" [$master1 CLUSTER SLOTS] - - # Delete multiple slots when start slot number is greater than the end slot - assert_error "ERR start slot number 5800 is greater than end slot number 5750" {R 1 cluster DELSLOTSRANGE 5600 5700 5800 5750} - assert_match "* 3277-5000 5501-6552*" [$master2 CLUSTER NODES] - assert_match "*3277 5000*5501 6552*" [$master2 CLUSTER SLOTS] - - # Delete multiple slots with already unassigned - assert_error "ERR Slot 7001 is already unassigned" {R 2 cluster DELSLOTSRANGE 7001 7100 9000 9200} - assert_match "* 6553-7000 7101-8000 8501-9828*" [$master3 CLUSTER NODES] - assert_match "*6553 7000*7101 8000*8501 9828*" [$master3 CLUSTER SLOTS] - - # Delete multiple slots with assigned multiple times - assert_error "ERR Slot 12500 specified multiple times" {R 3 cluster DELSLOTSRANGE 12500 12600 12500 12600} - assert_match "* 9829-11000 12001-12100 12201-13104*" [$master4 CLUSTER NODES] - assert_match "*9829 11000*12001 12100*12201 13104*" [$master4 CLUSTER SLOTS] -} -} cluster_allocate_with_continuous_slots_local - -start_cluster 2 0 {tags {external:skip cluster experimental}} { - -set master1 [srv 0 "client"] -set master2 [srv -1 "client"] - -test "SFLUSH - Errors and output validation" { - assert_match "* 0-8191*" [$master1 CLUSTER NODES] - assert_match "* 8192-16383*" [$master2 CLUSTER NODES] - assert_match "*0 8191*" [$master1 CLUSTER SLOTS] - assert_match "*8192 16383*" [$master2 CLUSTER SLOTS] - - # make master1 non-continuous slots - $master1 cluster DELSLOTSRANGE 1000 2000 - - # Test SFLUSH errors validation - assert_error {ERR wrong number of arguments*} {$master1 SFLUSH 4} - assert_error {ERR wrong number of arguments*} {$master1 SFLUSH 4 SYNC} - assert_error {ERR Invalid or out of range slot} {$master1 SFLUSH x 4} - assert_error {ERR Invalid or out of range slot} {$master1 SFLUSH 0 12x} - assert_error {ERR Slot 3 specified multiple times} {$master1 SFLUSH 2 4 3 5} - assert_error {ERR start slot number 8 is greater than*} {$master1 SFLUSH 8 4} - assert_error {ERR wrong number of arguments*} {$master1 SFLUSH 4 8 10} - assert_error {ERR wrong number of arguments*} {$master1 SFLUSH 0 999 2001 8191 ASYNCX} - - # Test SFLUSH output validation - assert_match "" [$master1 SFLUSH 2 4] - assert_match "" [$master1 SFLUSH 0 4] - assert_match "" [$master2 SFLUSH 0 4] - assert_match "" [$master1 SFLUSH 1 8191] - assert_match "" [$master1 SFLUSH 0 8190] - assert_match "" [$master1 SFLUSH 0 998 2001 8191] - assert_match "" [$master1 SFLUSH 1 999 2001 8191] - assert_match "" [$master1 SFLUSH 0 999 2001 8190] - assert_match "" [$master1 SFLUSH 0 999 2002 8191] - assert_match "{0 999} {2001 8191}" [$master1 SFLUSH 0 999 2001 8191] - assert_match "{0 999} {2001 8191}" [$master1 SFLUSH 0 8191] - assert_match "{0 999} {2001 8191}" [$master1 SFLUSH 0 4000 4001 8191] - assert_match "" [$master2 SFLUSH 8193 16383] - assert_match "" [$master2 SFLUSH 8192 16382] - assert_match "{8192 16383}" [$master2 SFLUSH 8192 16383] - assert_match "{8192 16383}" [$master2 SFLUSH 8192 16383 SYNC] - assert_match "{8192 16383}" [$master2 SFLUSH 8192 16383 ASYNC] - assert_match "{8192 16383}" [$master2 SFLUSH 8192 9000 9001 16383] - assert_match "{8192 16383}" [$master2 SFLUSH 8192 9000 9001 16383 SYNC] - assert_match "{8192 16383}" [$master2 SFLUSH 8192 9000 9001 16383 ASYNC] - - # restore master1 continuous slots - $master1 cluster ADDSLOTSRANGE 1000 2000 -} - -test "SFLUSH - Deletes the keys with argument /SYNC/ASYNC" { - foreach op {"" "SYNC" "ASYNC"} { - for {set i 0} {$i < 100} {incr i} { - catch {$master1 SET key$i val$i} - catch {$master2 SET key$i val$i} - } - - assert {[$master1 DBSIZE] > 0} - assert {[$master2 DBSIZE] > 0} - if {$op eq ""} { - assert_match "{0 8191}" [ $master1 SFLUSH 0 8191] - } else { - assert_match "{0 8191}" [ $master1 SFLUSH 0 8191 $op] - } - assert {[$master1 DBSIZE] == 0} - assert {[$master2 DBSIZE] > 0} - assert_match "{8192 16383}" [ $master2 SFLUSH 8192 16383] - assert {[$master2 DBSIZE] == 0} - } -} - -} diff --git a/examples/redis-unstable/tests/unit/cluster/scripting.tcl b/examples/redis-unstable/tests/unit/cluster/scripting.tcl deleted file mode 100644 index 76aa882..0000000 --- a/examples/redis-unstable/tests/unit/cluster/scripting.tcl +++ /dev/null @@ -1,91 +0,0 @@ -start_cluster 1 0 {tags {external:skip cluster}} { - - test {Eval scripts with shebangs and functions default to no cross slots} { - # Test that scripts with shebang block cross slot operations - assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { - r 0 eval {#!lua - redis.call('set', 'foo', 'bar') - redis.call('set', 'bar', 'foo') - return 'OK' - } 0} - - # Test the functions by default block cross slot operations - r 0 function load REPLACE {#!lua name=crossslot - local function test_cross_slot(keys, args) - redis.call('set', 'foo', 'bar') - redis.call('set', 'bar', 'foo') - return 'OK' - end - - redis.register_function('test_cross_slot', test_cross_slot)} - assert_error "ERR Script attempted to access keys that do not hash to the same slot*" {r FCALL test_cross_slot 0} - } - - test {Cross slot commands are allowed by default for eval scripts and with allow-cross-slot-keys flag} { - # Old style lua scripts are allowed to access cross slot operations - r 0 eval "redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo')" 0 - - # scripts with allow-cross-slot-keys flag are allowed - r 0 eval {#!lua flags=allow-cross-slot-keys - redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo') - } 0 - - # Retrieve data from different slot to verify data has been stored in the correct dictionary in cluster-enabled setup - # during cross-slot operation from the above lua script. - assert_equal "bar" [r 0 get foo] - assert_equal "foo" [r 0 get bar] - r 0 del foo - r 0 del bar - - # Functions with allow-cross-slot-keys flag are allowed - r 0 function load REPLACE {#!lua name=crossslot - local function test_cross_slot(keys, args) - redis.call('set', 'foo', 'bar') - redis.call('set', 'bar', 'foo') - return 'OK' - end - - redis.register_function{function_name='test_cross_slot', callback=test_cross_slot, flags={ 'allow-cross-slot-keys' }}} - r FCALL test_cross_slot 0 - - # Retrieve data from different slot to verify data has been stored in the correct dictionary in cluster-enabled setup - # during cross-slot operation from the above lua function. - assert_equal "bar" [r 0 get foo] - assert_equal "foo" [r 0 get bar] - } - - test {Cross slot commands are also blocked if they disagree with pre-declared keys} { - assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { - r 0 eval {#!lua - redis.call('set', 'foo', 'bar') - return 'OK' - } 1 bar} - } - - test {Cross slot commands are allowed by default if they disagree with pre-declared keys} { - r 0 flushall - r 0 eval "redis.call('set', 'foo', 'bar')" 1 bar - - # Make sure the script writes to the right slot - assert_equal 1 [r 0 cluster COUNTKEYSINSLOT 12182] ;# foo slot - assert_equal 0 [r 0 cluster COUNTKEYSINSLOT 5061] ;# bar slot - } - - test "Function no-cluster flag" { - R 0 function load {#!lua name=test - redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}} - } - catch {R 0 fcall f1 0} e - assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e - } - - test "Script no-cluster flag" { - catch { - R 0 eval {#!lua flags=no-cluster - return 1 - } 0 - } e - - assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/sharded-pubsub.tcl b/examples/redis-unstable/tests/unit/cluster/sharded-pubsub.tcl deleted file mode 100644 index 57b550a..0000000 --- a/examples/redis-unstable/tests/unit/cluster/sharded-pubsub.tcl +++ /dev/null @@ -1,67 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -start_cluster 1 1 {tags {external:skip cluster}} { - set primary_id 0 - set replica1_id 1 - - set primary [Rn $primary_id] - set replica [Rn $replica1_id] - - test "Sharded pubsub publish behavior within multi/exec" { - foreach {node} {primary replica} { - set node [set $node] - $node MULTI - $node SPUBLISH ch1 "hello" - $node EXEC - } - } - - test "Sharded pubsub within multi/exec with cross slot operation" { - $primary MULTI - $primary SPUBLISH ch1 "hello" - $primary GET foo - catch {$primary EXEC} err - assert_match {CROSSSLOT*} $err - } - - test "Sharded pubsub publish behavior within multi/exec with read operation on primary" { - $primary MULTI - $primary SPUBLISH foo "hello" - $primary GET foo - $primary EXEC - } {0 {}} - - test "Sharded pubsub publish behavior within multi/exec with read operation on replica" { - $replica MULTI - $replica SPUBLISH foo "hello" - catch {[$replica GET foo]} err - assert_match {MOVED*} $err - catch {[$replica EXEC]} err - assert_match {EXECABORT*} $err - } - - test "Sharded pubsub publish behavior within multi/exec with write operation on primary" { - $primary MULTI - $primary SPUBLISH foo "hello" - $primary SET foo bar - $primary EXEC - } {0 OK} - - test "Sharded pubsub publish behavior within multi/exec with write operation on replica" { - $replica MULTI - $replica SPUBLISH foo "hello" - catch {[$replica SET foo bar]} err - assert_match {MOVED*} $err - catch {[$replica EXEC]} err - assert_match {EXECABORT*} $err - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/slot-ownership.tcl b/examples/redis-unstable/tests/unit/cluster/slot-ownership.tcl deleted file mode 100644 index 0f3e3cc..0000000 --- a/examples/redis-unstable/tests/unit/cluster/slot-ownership.tcl +++ /dev/null @@ -1,61 +0,0 @@ -start_cluster 2 2 {tags {external:skip cluster}} { - - test "Verify that slot ownership transfer through gossip propagates deletes to replicas" { - assert {[s -2 role] eq {slave}} - wait_for_condition 1000 50 { - [s -2 master_link_status] eq {up} - } else { - fail "Instance #2 master link status is not up" - } - - assert {[s -3 role] eq {slave}} - wait_for_condition 1000 50 { - [s -3 master_link_status] eq {up} - } else { - fail "Instance #3 master link status is not up" - } - - # Set a single key that will be used to test deletion - set key "FOO" - R 0 SET $key TEST - set key_slot [R 0 cluster keyslot $key] - set slot_keys_num [R 0 cluster countkeysinslot $key_slot] - assert {$slot_keys_num > 0} - - # Wait for replica to have the key - R 2 readonly - wait_for_condition 1000 50 { - [R 2 exists $key] eq "1" - } else { - fail "Test key was not replicated" - } - - assert_equal [R 2 cluster countkeysinslot $key_slot] $slot_keys_num - - # Assert other shards in cluster doesn't have the key - assert_equal [R 1 cluster countkeysinslot $key_slot] "0" - assert_equal [R 3 cluster countkeysinslot $key_slot] "0" - - set nodeid [R 1 cluster myid] - - R 1 cluster bumpepoch - # Move $key_slot to node 1 - assert_equal [R 1 cluster setslot $key_slot node $nodeid] "OK" - - wait_for_cluster_propagation - - # src master will delete keys in the slot - wait_for_condition 50 100 { - [R 0 cluster countkeysinslot $key_slot] eq 0 - } else { - fail "master 'countkeysinslot $key_slot' did not eq 0" - } - - # src replica will delete keys in the slot - wait_for_condition 50 100 { - [R 2 cluster countkeysinslot $key_slot] eq 0 - } else { - fail "replica 'countkeysinslot $key_slot' did not eq 0" - } - } -} diff --git a/examples/redis-unstable/tests/unit/cluster/slot-stats.tcl b/examples/redis-unstable/tests/unit/cluster/slot-stats.tcl deleted file mode 100644 index 1123731..0000000 --- a/examples/redis-unstable/tests/unit/cluster/slot-stats.tcl +++ /dev/null @@ -1,1169 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -# Integration tests for CLUSTER SLOT-STATS command. - -# ----------------------------------------------------------------------------- -# Helper functions for CLUSTER SLOT-STATS test cases. -# ----------------------------------------------------------------------------- - -# Converts array RESP response into a dict. -# This is useful for many test cases, where unnecessary nesting is removed. -proc convert_array_into_dict {slot_stats} { - set res [dict create] - foreach slot_stat $slot_stats { - # slot_stat is an array of size 2, where 0th index represents (int) slot, - # and 1st index represents (map) usage statistics. - dict set res [lindex $slot_stat 0] [lindex $slot_stat 1] - } - return $res -} - -proc get_cmdstat_usec {cmd r} { - set cmdstatline [cmdrstat $cmd r] - regexp "usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline -> usec _ - return $usec -} - -proc initialize_expected_slots_dict {} { - set expected_slots [dict create] - for {set i 0} {$i < 16384} {incr i 1} { - dict set expected_slots $i 0 - } - return $expected_slots -} - -proc initialize_expected_slots_dict_with_range {start_slot end_slot} { - assert {$start_slot <= $end_slot} - set expected_slots [dict create] - for {set i $start_slot} {$i <= $end_slot} {incr i 1} { - dict set expected_slots $i 0 - } - return $expected_slots -} - -proc assert_empty_slot_stats {slot_stats metrics_to_assert} { - set slot_stats [convert_array_into_dict $slot_stats] - dict for {slot stats} $slot_stats { - foreach metric_name $metrics_to_assert { - set metric_value [dict get $stats $metric_name] - assert {$metric_value == 0} - } - } -} - -proc assert_empty_slot_stats_with_exception {slot_stats exception_slots metrics_to_assert} { - set slot_stats [convert_array_into_dict $slot_stats] - dict for {slot stats} $exception_slots { - assert {[dict exists $slot_stats $slot]} ;# slot_stats must contain the expected slots. - } - dict for {slot stats} $slot_stats { - if {[dict exists $exception_slots $slot]} { - foreach metric_name $metrics_to_assert { - set metric_value [dict get $exception_slots $slot $metric_name] - assert {[dict get $stats $metric_name] == $metric_value} - } - } else { - dict for {metric value} $stats { - assert {$value == 0} - } - } - } -} - -proc assert_equal_slot_stats {slot_stats_1 slot_stats_2 deterministic_metrics non_deterministic_metrics} { - set slot_stats_1 [convert_array_into_dict $slot_stats_1] - set slot_stats_2 [convert_array_into_dict $slot_stats_2] - assert {[dict size $slot_stats_1] == [dict size $slot_stats_2]} - - dict for {slot stats_1} $slot_stats_1 { - assert {[dict exists $slot_stats_2 $slot]} - set stats_2 [dict get $slot_stats_2 $slot] - - # For deterministic metrics, we assert their equality. - foreach metric $deterministic_metrics { - assert {[dict get $stats_1 $metric] == [dict get $stats_2 $metric]} - } - # For non-deterministic metrics, we assert their non-zeroness as a best-effort. - foreach metric $non_deterministic_metrics { - assert {([dict get $stats_1 $metric] == 0 && [dict get $stats_2 $metric] == 0) || \ - ([dict get $stats_1 $metric] != 0 && [dict get $stats_2 $metric] != 0)} - } - } -} - -proc assert_all_slots_have_been_seen {expected_slots} { - dict for {k v} $expected_slots { - assert {$v == 1} - } -} - -proc assert_slot_visibility {slot_stats expected_slots} { - set slot_stats [convert_array_into_dict $slot_stats] - dict for {slot _} $slot_stats { - assert {[dict exists $expected_slots $slot]} - dict set expected_slots $slot 1 - } - - assert_all_slots_have_been_seen $expected_slots -} - -proc assert_slot_stats_monotonic_order {slot_stats orderby is_desc} { - # For Tcl dict, the order of iteration is the order in which the keys were inserted into the dictionary - # Thus, the response ordering is preserved upon calling 'convert_array_into_dict()'. - # Source: https://www.tcl.tk/man/tcl8.6.11/TclCmd/dict.htm - set slot_stats [convert_array_into_dict $slot_stats] - set prev_metric -1 - dict for {_ stats} $slot_stats { - set curr_metric [dict get $stats $orderby] - if {$prev_metric != -1} { - if {$is_desc == 1} { - assert {$prev_metric >= $curr_metric} - } else { - assert {$prev_metric <= $curr_metric} - } - } - set prev_metric $curr_metric - } -} - -proc assert_slot_stats_monotonic_descent {slot_stats orderby} { - assert_slot_stats_monotonic_order $slot_stats $orderby 1 -} - -proc assert_slot_stats_monotonic_ascent {slot_stats orderby} { - assert_slot_stats_monotonic_order $slot_stats $orderby 0 -} - -proc wait_for_replica_key_exists {key key_count} { - wait_for_condition 1000 50 { - [R 1 exists $key] eq "$key_count" - } else { - fail "Test key was not replicated" - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS cpu-usec metric correctness. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - set key_secondary "FOO2" - set key_secondary_slot [R 0 cluster keyslot $key_secondary] - set metrics_to_assert [list cpu-usec] - - test "CLUSTER SLOT-STATS cpu-usec reset upon CONFIG RESETSTAT." { - R 0 SET $key VALUE - R 0 DEL $key - R 0 CONFIG RESETSTAT - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec reset upon slot migration." { - R 0 SET $key VALUE - - R 0 CLUSTER DELSLOTS $key_slot - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - R 0 CLUSTER ADDSLOTS $key_slot - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for non-slot specific commands." { - R 0 INFO - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for slot specific commands." { - R 0 SET $key VALUE - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set usec [get_cmdstat_usec set r] - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for blocking commands, unblocked on keyspace update." { - # Blocking command with no timeout. Only keyspace update can unblock this client. - set rd [redis_deferring_client] - $rd BLPOP $key 0 - wait_for_blocked_clients_count 1 - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - # When the client is blocked, no accumulation is made. This behaviour is identical to INFO COMMANDSTATS. - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # Unblocking command. - R 0 LPUSH $key value - wait_for_blocked_clients_count 0 - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set lpush_usec [get_cmdstat_usec lpush r] - set blpop_usec [get_cmdstat_usec blpop r] - - # Assert that both blocking and non-blocking command times have been accumulated. - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec [expr $lpush_usec + $blpop_usec] - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for blocking commands, unblocked on timeout." { - # Blocking command with 0.5 seconds timeout. - set rd [redis_deferring_client] - $rd BLPOP $key 0.5 - - # Confirm that the client is blocked, then unblocked within 1 second. - wait_for_blocked_clients_count 1 - wait_for_blocked_clients_count 0 - - # Assert that the blocking command time has been accumulated. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set blpop_usec [get_cmdstat_usec blpop r] - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $blpop_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for transactions." { - set r1 [redis_client] - $r1 MULTI - $r1 SET $key value - $r1 GET $key - - # CPU metric is not accumulated until EXEC is reached. This behaviour is identical to INFO COMMANDSTATS. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # Execute transaction, and assert that all nested command times have been accumulated. - $r1 EXEC - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set exec_usec [get_cmdstat_usec exec r] - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $exec_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for lua-scripts, without cross-slot keys." { - R 0 eval {#!lua - redis.call('set', KEYS[1], 'bar') redis.call('get', KEYS[2]) - } 2 $key $key - - set eval_usec [get_cmdstat_usec eval r] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $eval_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for lua-scripts, with cross-slot keys." { - R 0 eval {#!lua flags=allow-cross-slot-keys - redis.call('set', KEYS[1], 'bar') redis.call('get', ARGV[1]) - } 1 $key $key_secondary - - # For cross-slot, we do not accumulate at all. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for functions, without cross-slot keys." { - R 0 function load replace {#!lua name=f1 - redis.register_function{ - function_name='f1', - callback=function(keys, args) redis.call('set', keys[1], '1') redis.call('get', keys[2]) end - } - } - R 0 fcall f1 2 $key $key - - set fcall_usec [get_cmdstat_usec fcall r] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $fcall_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for functions, with cross-slot keys." { - R 0 function load replace {#!lua name=f1 - redis.register_function{ - function_name='f1', - callback=function(keys, args) redis.call('set', keys[1], '1') redis.call('get', args[1]) end, - flags={'allow-cross-slot-keys'} - } - } - R 0 fcall f1 1 $key $key_secondary - - # For cross-slot, we do not accumulate at all. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS network-bytes-in. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "key" - set key_slot [R 0 cluster keyslot $key] - set metrics_to_assert [list network-bytes-in] - - test "CLUSTER SLOT-STATS network-bytes-in, multi bulk buffer processing." { - # *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - R 0 SET $key value - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 33 - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, in-line buffer processing." { - set rd [redis_deferring_client] - # SET key value\r\n --> 15 bytes. - $rd write "SET $key value\r\n" - $rd flush - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 15 - ] - ] - - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, blocking command." { - set rd [redis_deferring_client] - # *3\r\n$5\r\nblpop\r\n$3\r\nkey\r\n$1\r\n0\r\n --> 31 bytes. - $rd BLPOP $key 0 - wait_for_blocked_clients_count 1 - - # Slot-stats must be empty here, as the client is yet to be unblocked. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # *3\r\n$5\r\nlpush\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 35 bytes. - R 0 LPUSH $key value - wait_for_blocked_clients_count 0 - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 66 ;# 31 + 35 bytes. - ] - ] - - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, multi-exec transaction." { - set r [redis_client] - # *1\r\n$5\r\nmulti\r\n --> 15 bytes. - $r MULTI - # *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - assert {[$r SET $key value] eq {QUEUED}} - # *1\r\n$4\r\nexec\r\n --> 14 bytes. - assert {[$r EXEC] eq {OK}} - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 62 ;# 15 + 33 + 14 bytes. - ] - ] - - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, non slot specific command." { - R 0 INFO - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, pub/sub." { - # PUB/SUB does not get accumulated at per-slot basis, - # as it is cluster-wide and is not slot specific. - set rd [redis_deferring_client] - $rd subscribe channel - R 0 publish channel message - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -start_cluster 1 1 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - set channel "channel" - set key_slot [R 0 cluster keyslot $channel] - set metrics_to_assert [list network-bytes-in] - - # Setup replication. - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Instance #1 master link status is not up" - } - R 1 readonly - - test "CLUSTER SLOT-STATS network-bytes-in, sharded pub/sub." { - set slot [R 0 cluster keyslot $channel] - set primary [Rn 0] - set replica [Rn 1] - set replica_subcriber [redis_deferring_client -1] - $replica_subcriber SSUBSCRIBE $channel - # *2\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n --> 34 bytes. - $primary SPUBLISH $channel hello - # *3\r\n$8\r\nspublish\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. - - set slot_stats [$primary CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 42 - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - - set slot_stats [$replica CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 34 - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS network-bytes-out correctness. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster}} { - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - set expected_slots_to_key_count [dict create $key_slot 1] - set metrics_to_assert [list network-bytes-out] - R 0 CONFIG SET cluster-slot-stats-enabled yes - - test "CLUSTER SLOT-STATS network-bytes-out, for non-slot specific commands." { - R 0 INFO - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-out, for slot specific commands." { - R 0 SET $key value - # +OK\r\n --> 5 bytes - - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 5 - ] - ] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-out, blocking commands." { - set rd [redis_deferring_client] - $rd BLPOP $key 0 - wait_for_blocked_clients_count 1 - - # Assert empty slot stats here, since COB is yet to be flushed due to the block. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # Unblock the command. - # LPUSH client) :1\r\n --> 4 bytes. - # BLPOP client) *2\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 24 bytes, upon unblocking. - R 0 LPUSH $key value - wait_for_blocked_clients_count 0 - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 28 ;# 4 + 24 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -start_cluster 1 1 {tags {external:skip cluster}} { - - # Define shared variables. - set key "FOO" - set key_slot [R 0 CLUSTER KEYSLOT $key] - set metrics_to_assert [list network-bytes-out] - R 0 CONFIG SET cluster-slot-stats-enabled yes - - # Setup replication. - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Instance #1 master link status is not up" - } - R 1 readonly - - test "CLUSTER SLOT-STATS network-bytes-out, replication stream egress." { - assert_equal [R 0 SET $key VALUE] {OK} - # Local client) +OK\r\n --> 5 bytes. - # Replication stream) *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 38 ;# 5 + 33 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } -} - -start_cluster 1 1 {tags {external:skip cluster}} { - - # Define shared variables. - set channel "channel" - set key_slot [R 0 cluster keyslot $channel] - set channel_secondary "channel2" - set key_slot_secondary [R 0 cluster keyslot $channel_secondary] - set metrics_to_assert [list network-bytes-out] - R 0 CONFIG SET cluster-slot-stats-enabled yes - - test "CLUSTER SLOT-STATS network-bytes-out, sharded pub/sub, single channel." { - set slot [R 0 cluster keyslot $channel] - set publisher [Rn 0] - set subscriber [redis_client] - set replica [redis_deferring_client -1] - - # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n:1\r\n --> 38 bytes - $subscriber SSUBSCRIBE $channel - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 38 - ] - ] - R 0 CONFIG RESETSTAT - - # Publisher client) :1\r\n --> 4 bytes. - # Subscriber client) *3\r\n$8\r\nsmessage\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. - assert_equal 1 [$publisher SPUBLISH $channel hello] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 46 ;# 4 + 42 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - $subscriber QUIT - R 0 FLUSHALL - R 0 CONFIG RESETSTAT - - test "CLUSTER SLOT-STATS network-bytes-out, sharded pub/sub, cross-slot channels." { - set slot [R 0 cluster keyslot $channel] - set publisher [Rn 0] - set subscriber [redis_client] - set replica [redis_deferring_client -1] - - # Stack multi-slot subscriptions against a single client. - # For primary channel; - # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n:1\r\n --> 38 bytes - # For secondary channel; - # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$8\r\nchannel2\r\n:1\r\n --> 39 bytes - $subscriber SSUBSCRIBE $channel - $subscriber SSUBSCRIBE $channel_secondary - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create \ - $key_slot [ \ - dict create network-bytes-out 38 - ] \ - $key_slot_secondary [ \ - dict create network-bytes-out 39 - ] - ] - R 0 CONFIG RESETSTAT - - # For primary channel; - # Publisher client) :1\r\n --> 4 bytes. - # Subscriber client) *3\r\n$8\r\nsmessage\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. - # For secondary channel; - # Publisher client) :1\r\n --> 4 bytes. - # Subscriber client) *3\r\n$8\r\nsmessage\r\n$8\r\nchannel2\r\n$5\r\nhello\r\n --> 43 bytes. - assert_equal 1 [$publisher SPUBLISH $channel hello] - assert_equal 1 [$publisher SPUBLISH $channel_secondary hello] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create \ - $key_slot [ \ - dict create network-bytes-out 46 ;# 4 + 42 bytes. - ] \ - $key_slot_secondary [ \ - dict create network-bytes-out 47 ;# 4 + 43 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS key-count metric correctness. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - set metrics_to_assert [list key-count] - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 1 - ] - ] - - test "CLUSTER SLOT-STATS contains default value upon redis-server startup" { - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS contains correct metrics upon key introduction" { - R 0 SET $key TEST - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS contains correct metrics upon key mutation" { - R 0 SET $key NEW_VALUE - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS contains correct metrics upon key deletion" { - R 0 DEL $key - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS slot visibility based on slot ownership changes" { - R 0 CONFIG SET cluster-require-full-coverage no - - R 0 CLUSTER DELSLOTS $key_slot - set expected_slots [initialize_expected_slots_dict] - dict unset expected_slots $key_slot - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert {[dict size $expected_slots] == 16383} - assert_slot_visibility $slot_stats $expected_slots - - R 0 CLUSTER ADDSLOTS $key_slot - set expected_slots [initialize_expected_slots_dict] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert {[dict size $expected_slots] == 16384} - assert_slot_visibility $slot_stats $expected_slots - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS SLOTSRANGE sub-argument. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster}} { - - test "CLUSTER SLOT-STATS SLOTSRANGE all slots present" { - set start_slot 100 - set end_slot 102 - set expected_slots [initialize_expected_slots_dict_with_range $start_slot $end_slot] - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE $start_slot $end_slot] - assert_slot_visibility $slot_stats $expected_slots - } - - test "CLUSTER SLOT-STATS SLOTSRANGE some slots missing" { - set start_slot 100 - set end_slot 102 - set expected_slots [initialize_expected_slots_dict_with_range $start_slot $end_slot] - - R 0 CLUSTER DELSLOTS $start_slot - dict unset expected_slots $start_slot - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE $start_slot $end_slot] - assert_slot_visibility $slot_stats $expected_slots - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS ORDERBY sub-argument. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - set metrics [list "key-count" "memory-bytes" "cpu-usec" "network-bytes-in" "network-bytes-out"] - - # SET keys for target hashslots, to encourage ordering. - set hash_tags [list 0 1 2 3 4] - set num_keys 1 - foreach hash_tag $hash_tags { - for {set i 0} {$i < $num_keys} {incr i 1} { - R 0 SET "$i{$hash_tag}" VALUE - } - incr num_keys 1 - } - - # SET keys for random hashslots, for random noise. - set num_keys 0 - while {$num_keys < 1000} { - set random_key [randomInt 16384] - R 0 SET $random_key VALUE - incr num_keys 1 - } - - test "CLUSTER SLOT-STATS ORDERBY DESC correct ordering" { - foreach orderby $metrics { - set slot_stats [R 0 CLUSTER SLOT-STATS ORDERBY $orderby DESC] - assert_slot_stats_monotonic_descent $slot_stats $orderby - } - } - - test "CLUSTER SLOT-STATS ORDERBY ASC correct ordering" { - foreach orderby $metrics { - set slot_stats [R 0 CLUSTER SLOT-STATS ORDERBY $orderby ASC] - assert_slot_stats_monotonic_ascent $slot_stats $orderby - } - } - - test "CLUSTER SLOT-STATS ORDERBY LIMIT correct response pagination, where limit is less than number of assigned slots" { - R 0 FLUSHALL SYNC - R 0 CONFIG RESETSTAT - - foreach orderby $metrics { - set limit 5 - set slot_stats_desc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit DESC] - set slot_stats_asc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit ASC] - set slot_stats_desc_length [llength $slot_stats_desc] - set slot_stats_asc_length [llength $slot_stats_asc] - assert {$limit == $slot_stats_desc_length && $limit == $slot_stats_asc_length} - - # All slot statistics have been reset to 0, so we will order by slot in ascending order. - set expected_slots [dict create 0 0 1 0 2 0 3 0 4 0] - assert_slot_visibility $slot_stats_desc $expected_slots - assert_slot_visibility $slot_stats_asc $expected_slots - } - } - - test "CLUSTER SLOT-STATS ORDERBY LIMIT correct response pagination, where limit is greater than number of assigned slots" { - R 0 CONFIG SET cluster-require-full-coverage no - R 0 FLUSHALL SYNC - R 0 CLUSTER FLUSHSLOTS - R 0 CLUSTER ADDSLOTS 100 101 - - foreach orderby $metrics { - set num_assigned_slots 2 - set limit 5 - set slot_stats_desc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit DESC] - set slot_stats_asc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit ASC] - set slot_stats_desc_length [llength $slot_stats_desc] - set slot_stats_asc_length [llength $slot_stats_asc] - set expected_response_length [expr min($num_assigned_slots, $limit)] - assert {$expected_response_length == $slot_stats_desc_length && $expected_response_length == $slot_stats_asc_length} - - set expected_slots [dict create 100 0 101 0] - assert_slot_visibility $slot_stats_desc $expected_slots - assert_slot_visibility $slot_stats_asc $expected_slots - } - } - - test "CLUSTER SLOT-STATS ORDERBY arg sanity check." { - # Non-existent argument. - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY key-count non-existent-arg} - # Negative LIMIT. - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY key-count DESC LIMIT -1} - # Non-existent ORDERBY metric. - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY non-existent-metric} - # When cluster-slot-stats-enabled config is disabled, you cannot sort using advanced metrics. - R 0 CONFIG SET cluster-slot-stats-enabled no - set orderby "cpu-usec" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - set orderby "network-bytes-in" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - set orderby "network-bytes-out" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - set orderby "memory-bytes" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - - # When only cpu net is enabled, memory-bytes ORDERBY should fail - R 0 CONFIG SET cluster-slot-stats-enabled "cpu net" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY memory-bytes} - } - -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS replication. -# ----------------------------------------------------------------------------- - -start_cluster 1 1 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "key" - set key_slot [R 0 CLUSTER KEYSLOT $key] - set primary [Rn 0] - set replica [Rn 1] - - # For replication, assertions are split between deterministic and non-deterministic metrics. - # * For deterministic metrics, strict equality assertions are made. - # * For non-deterministic metrics, non-zeroness assertions are made. - # Non-zeroness as in, both primary and replica should either have some value, or no value at all. - # - # * key-count is deterministic between primary and its replica. - # * cpu-usec is non-deterministic between primary and its replica. - # * network-bytes-in is deterministic between primary and its replica. - # * network-bytes-out will remain empty in the replica, since primary client do not receive replies, unless for replicationSendAck(). - set deterministic_metrics [list key-count network-bytes-in] - set non_deterministic_metrics [list cpu-usec] - set empty_metrics [list network-bytes-out] - - # Setup replication. - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Instance #1 master link status is not up" - } - R 1 readonly - - test "CLUSTER SLOT-STATS metrics replication for new keys" { - # *3\r\n$3\r\nset\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - R 0 SET $key VALUE - - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 1 network-bytes-in 33 - ] - ] - set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics - - wait_for_condition 500 10 { - [string match {*calls=1,*} [cmdrstat set $replica]] - } else { - fail "Replica did not receive the command." - } - set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics - assert_empty_slot_stats $slot_stats_replica $empty_metrics - } - R 0 CONFIG RESETSTAT - R 1 CONFIG RESETSTAT - - test "CLUSTER SLOT-STATS metrics replication for existing keys" { - # *3\r\n$3\r\nset\r\n$3\r\nkey\r\n$13\r\nvalue_updated\r\n --> 42 bytes. - R 0 SET $key VALUE_UPDATED - - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 1 network-bytes-in 42 - ] - ] - set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics - - wait_for_condition 500 10 { - [string match {*calls=1,*} [cmdrstat set $replica]] - } else { - fail "Replica did not receive the command." - } - set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics - assert_empty_slot_stats $slot_stats_replica $empty_metrics - } - R 0 CONFIG RESETSTAT - R 1 CONFIG RESETSTAT - - test "CLUSTER SLOT-STATS metrics replication for deleting keys" { - # *2\r\n$3\r\ndel\r\n$3\r\nkey\r\n --> 22 bytes. - R 0 DEL $key - - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 0 network-bytes-in 22 - ] - ] - set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics - - wait_for_condition 500 10 { - [string match {*calls=1,*} [cmdrstat del $replica]] - } else { - fail "Replica did not receive the command." - } - set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics - assert_empty_slot_stats $slot_stats_replica $empty_metrics - } - R 0 CONFIG RESETSTAT - R 1 CONFIG RESETSTAT -} - -start_cluster 2 2 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - test "CLUSTER SLOT-STATS reset upon atomic slot migration" { - # key on slot-0 - set key0 "{06S}mykey0" - set key0_slot [R 0 CLUSTER KEYSLOT $key0] - R 0 SET $key0 VALUE - - # Migrate slot-0 to node-1 - R 1 CLUSTER MIGRATION IMPORT 0 0 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 1 cluster_slot_migration_active_tasks] == 0 - } else { - fail "ASM tasks did not complete" - } - - set expected_slot_stats [ - dict create \ - $key0_slot [ \ - dict create key-count 1 \ - dict create cpu-usec 0 \ - dict create network-bytes-in 0 \ - dict create network-bytes-out 0 \ - ] - ] - set metrics_to_assert [list key-count cpu-usec network-bytes-in network-bytes-out] - - # Verify metrics are reset except key-count - set slot_stats [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 0] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - - # Migrate slot-0 back to node-0 - R 0 CLUSTER MIGRATION IMPORT 0 0 - wait_for_condition 1000 10 { - [CI 0 cluster_slot_migration_active_tasks] == 0 && - [CI 1 cluster_slot_migration_active_tasks] == 0 - } else { - fail "ASM tasks did not complete" - } - - # Verify metrics are reset except key-count - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 0] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS memory-bytes field presence. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - - test "CLUSTER SLOT-STATS memory-bytes field present when cluster-slot-stats-enabled set on startup" { - R 0 SET $key VALUE - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - # Verify memory-bytes field is present - assert {[dict exists $slot_stats $key_slot]} - set stats [dict get $slot_stats $key_slot] - assert {[dict exists $stats memory-bytes]} - assert {[dict get $stats memory-bytes] > 0} - } - - test "CLUSTER SLOT-STATS net mem combination shows only net and mem stats" { - R 0 CONFIG SET cluster-slot-stats-enabled "net mem" - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - set stats [dict get $slot_stats $key_slot] - assert {[dict exists $stats memory-bytes]} - assert {[dict exists $stats network-bytes-in]} - assert {[dict exists $stats network-bytes-out]} - assert {![dict exists $stats cpu-usec]} - } - - test "CLUSTER SLOT-STATS cpu mem combination shows only cpu and mem stats" { - R 0 CONFIG SET cluster-slot-stats-enabled "cpu mem" - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - set stats [dict get $slot_stats $key_slot] - assert {[dict exists $stats memory-bytes]} - assert {[dict exists $stats cpu-usec]} - assert {![dict exists $stats network-bytes-in]} - assert {![dict exists $stats network-bytes-out]} - - # Restore to yes for subsequent tests - R 0 CONFIG SET cluster-slot-stats-enabled yes - } - - test "CLUSTER SLOT-STATS memory-bytes field not present after disabling cluster-slot-stats-enabled" { - R 0 CONFIG SET cluster-slot-stats-enabled no - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - # Verify memory-bytes field is not present after disabling config - # (memory tracking is disabled when MEM flag is removed) - assert {[dict exists $slot_stats $key_slot]} - set stats [dict get $slot_stats $key_slot] - assert {![dict exists $stats memory-bytes]} - - # Verify other stats fields are not present - assert {![dict exists $stats cpu-usec]} - assert {![dict exists $stats network-bytes-in]} - assert {![dict exists $stats network-bytes-out]} - } - - test "CLUSTER SLOT-STATS memory tracking cannot be re-enabled after being disabled" { - # Once memory tracking is disabled, it cannot be re-enabled at runtime - assert_error "ERR*memory tracking cannot be enabled at runtime*" {R 0 CONFIG SET cluster-slot-stats-enabled yes} - assert_error "ERR*memory tracking cannot be enabled at runtime*" {R 0 CONFIG SET cluster-slot-stats-enabled mem} - - # But cpu and net can still be enabled - R 0 CONFIG SET cluster-slot-stats-enabled "cpu net" - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - assert {[dict exists $slot_stats $key_slot]} - set stats [dict get $slot_stats $key_slot] - assert {![dict exists $stats memory-bytes]} - assert {[dict exists $stats cpu-usec]} - assert {[dict exists $stats network-bytes-in]} - assert {[dict exists $stats network-bytes-out]} - } -} - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled no}} { - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - - test "CLUSTER SLOT-STATS memory-bytes field not present when cluster-slot-stats-enabled not set on startup" { - R 0 SET $key VALUE - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - # Verify memory-bytes field is not present - assert {[dict exists $slot_stats $key_slot]} - set stats [dict get $slot_stats $key_slot] - assert {![dict exists $stats memory-bytes]} - - # Only key-count should be present - assert {[dict exists $stats key-count]} - assert {[dict get $stats key-count] == 1} - } - - test "CLUSTER SLOT-STATS enabling mem at runtime fails when not enabled at startup" { - # Trying to enable memory tracking at runtime should fail - assert_error "ERR*memory tracking cannot be enabled at runtime*" {R 0 CONFIG SET cluster-slot-stats-enabled mem} - assert_error "ERR*memory tracking cannot be enabled at runtime*" {R 0 CONFIG SET cluster-slot-stats-enabled yes} - assert_error "ERR*memory tracking cannot be enabled at runtime*" {R 0 CONFIG SET cluster-slot-stats-enabled "cpu net mem"} - } - - test "CLUSTER SLOT-STATS enabling cpu and net at runtime works" { - R 0 CONFIG SET cluster-slot-stats-enabled "cpu net" - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set slot_stats [convert_array_into_dict $slot_stats] - - # Verify memory-bytes field is still not present - assert {[dict exists $slot_stats $key_slot]} - set stats [dict get $slot_stats $key_slot] - assert {![dict exists $stats memory-bytes]} - - # Other stats fields should now be present - assert {[dict exists $stats cpu-usec]} - assert {[dict exists $stats network-bytes-in]} - assert {[dict exists $stats network-bytes-out]} - } -} diff --git a/examples/redis-unstable/tests/unit/dump.tcl b/examples/redis-unstable/tests/unit/dump.tcl deleted file mode 100644 index a03c846..0000000 --- a/examples/redis-unstable/tests/unit/dump.tcl +++ /dev/null @@ -1,440 +0,0 @@ -start_server {tags {"dump"}} { - test {DUMP / RESTORE are able to serialize / unserialize a simple key} { - r set foo bar - set encoded [r dump foo] - r del foo - list [r exists foo] [r restore foo 0 $encoded] [r ttl foo] [r get foo] - } {0 OK -1 bar} - - test {RESTORE can set an arbitrary expire to the materialized key} { - r set foo bar - set encoded [r dump foo] - r del foo - r restore foo 5000 $encoded - set ttl [r pttl foo] - assert_range $ttl 3000 5000 - r get foo - } {bar} - - test {RESTORE can set an expire that overflows a 32 bit integer} { - r set foo bar - set encoded [r dump foo] - r del foo - r restore foo 2569591501 $encoded - set ttl [r pttl foo] - assert_range $ttl (2569591501-3000) 2569591501 - r get foo - } {bar} - - test {RESTORE can set an absolute expire} { - r set foo bar - set encoded [r dump foo] - r del foo - set now [clock milliseconds] - r restore foo [expr $now+3000] $encoded absttl - set ttl [r pttl foo] - assert_range $ttl 2000 3100 - r get foo - } {bar} - - test {RESTORE with ABSTTL in the past} { - r set foo bar - set encoded [r dump foo] - set now [clock milliseconds] - r debug set-active-expire 0 - r restore foo [expr $now-3000] $encoded absttl REPLACE - catch {r debug object foo} e - r debug set-active-expire 1 - set e - } {ERR no such key} {needs:debug} - - test {RESTORE can set LRU} { - r set foo bar - set encoded [r dump foo] - r del foo - r config set maxmemory-policy allkeys-lru - r restore foo 0 $encoded idletime 1000 - set idle [r object idletime foo] - assert {$idle >= 1000 && $idle <= 1010} - assert_equal [r get foo] {bar} - r config set maxmemory-policy noeviction - } {OK} {needs:config-maxmemory} - - test {RESTORE with TTL maintain valid object} { - # RESTORE Creates a string with TTL in two steps. The second step potentially - # reallocates the object. Access the object and verify it is not corrupted - r del foo - r set foo bar - set encoded [r dump foo] - # Iterate several times and verify it is consistent - for {set i 0} {$i < 100} {incr i} { - r del foo - r restore foo 1000 $encoded IDLETIME 500 - assert_equal [r get foo] {bar} - } - } - - test {RESTORE can set LFU} { - r set foo bar - set encoded [r dump foo] - r del foo - r config set maxmemory-policy allkeys-lfu - r restore foo 0 $encoded freq 100 - - # We need to determine whether the `object` operation happens within the same minute or crosses into a new one - # This will help us verify if the freq remains 100 or decays due to a minute transition - set start [clock format [clock seconds] -format %M] - set freq [r object freq foo] - set end [clock format [clock seconds] -format %M] - - if { $start == $end } { - # If the minutes haven't changed (i.e., the restore and object happened within the same minute), - # the freq should remain 100 as no decay has occurred yet. - assert {$freq == 100} - } else { - # If the object operation crosses into a new minute, freq may have already decayed by 1 (99), - # or it may still be 100 if the minute update hasn't been applied yet when the operation is performed. - # The decay might only take effect after the operation completes and the minute is updated. - assert {($freq == 100) || ($freq == 99)} - } - - r get foo - assert_equal [r get foo] {bar} - r config set maxmemory-policy noeviction - } {OK} {needs:config-maxmemory} - - test {RESTORE returns an error of the key already exists} { - r set foo bar - set e {} - catch {r restore foo 0 "..."} e - set e - } {*BUSYKEY*} - - test {RESTORE can overwrite an existing key with REPLACE} { - r set foo bar1 - set encoded1 [r dump foo] - r set foo bar2 - set encoded2 [r dump foo] - r del foo - r restore foo 0 $encoded1 - r restore foo 0 $encoded2 replace - r get foo - } {bar2} - - test {RESTORE can detect a syntax error for unrecognized options} { - catch {r restore foo 0 "..." invalid-option} e - set e - } {*syntax*} - - test {RESTORE should not store key that are already expired, with REPLACE will propagate it as DEL or UNLINK} { - r del key1{t} key2{t} - r set key1{t} value2 - r lpush key2{t} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 - - r set key{t} value - set encoded [r dump key{t}] - set now [clock milliseconds] - - set repl [attach_to_replication_stream] - - # Keys that have expired will not be stored. - r config set lazyfree-lazy-server-del no - assert_equal {OK} [r restore key1{t} [expr $now-5000] $encoded replace absttl] - r config set lazyfree-lazy-server-del yes - assert_equal {OK} [r restore key2{t} [expr $now-5000] $encoded replace absttl] - assert_equal {0} [r exists key1{t} key2{t}] - - # Verify the propagate of DEL and UNLINK. - assert_replication_stream $repl { - {select *} - {del key1{t}} - {unlink key2{t}} - } - - close_replication_stream $repl - } {} {needs:repl} - - test {DUMP of non existing key returns nil} { - r dump nonexisting_key - } {} - - test {MIGRATE is caching connections} { - # Note, we run this as first test so that the connection cache - # is empty. - set first [srv 0 client] - r set key "Some Value" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert_match {*migrate_cached_sockets:0*} [r -1 info] - r -1 migrate $second_host $second_port key 9 1000 - assert_match {*migrate_cached_sockets:1*} [r -1 info] - } - } {} {external:skip} - - test {MIGRATE cached connections are released after some time} { - after 15000 - assert_match {*migrate_cached_sockets:0*} [r info] - } - - test {MIGRATE is able to migrate a key between two instances} { - set first [srv 0 client] - r set key "Some Value" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists key] == 1} - assert {[$second exists key] == 0} - set ret [r -1 migrate $second_host $second_port key 9 5000] - assert {$ret eq {OK}} - assert {[$first exists key] == 0} - assert {[$second exists key] == 1} - assert {[$second get key] eq {Some Value}} - assert {[$second ttl key] == -1} - } - } {} {external:skip} - - test {MIGRATE is able to copy a key between two instances} { - set first [srv 0 client] - r del list - r lpush list a b c d - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists list] == 1} - assert {[$second exists list] == 0} - set ret [r -1 migrate $second_host $second_port list 9 5000 copy] - assert {$ret eq {OK}} - assert {[$first exists list] == 1} - assert {[$second exists list] == 1} - assert {[$first lrange list 0 -1] eq [$second lrange list 0 -1]} - } - } {} {external:skip} - - test {MIGRATE will not overwrite existing keys, unless REPLACE is used} { - set first [srv 0 client] - r del list - r lpush list a b c d - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists list] == 1} - assert {[$second exists list] == 0} - $second set list somevalue - catch {r -1 migrate $second_host $second_port list 9 5000 copy} e - assert_match {ERR*} $e - set ret [r -1 migrate $second_host $second_port list 9 5000 copy replace] - assert {$ret eq {OK}} - assert {[$first exists list] == 1} - assert {[$second exists list] == 1} - assert {[$first lrange list 0 -1] eq [$second lrange list 0 -1]} - } - } {} {external:skip} - - test {MIGRATE propagates TTL correctly} { - set first [srv 0 client] - r set key "Some Value" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists key] == 1} - assert {[$second exists key] == 0} - $first expire key 10 - set ret [r -1 migrate $second_host $second_port key 9 5000] - assert {$ret eq {OK}} - assert {[$first exists key] == 0} - assert {[$second exists key] == 1} - assert {[$second get key] eq {Some Value}} - assert {[$second ttl key] >= 7 && [$second ttl key] <= 10} - } - } {} {external:skip} - - test {MIGRATE can correctly transfer large values} { - set first [srv 0 client] - r del key - for {set j 0} {$j < 40000} {incr j} { - r rpush key 1 2 3 4 5 6 7 8 9 10 - r rpush key "item 1" "item 2" "item 3" "item 4" "item 5" \ - "item 6" "item 7" "item 8" "item 9" "item 10" - } - assert {[string length [r dump key]] > (1024*64)} - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists key] == 1} - assert {[$second exists key] == 0} - set ret [r -1 migrate $second_host $second_port key 9 10000] - assert {$ret eq {OK}} - assert {[$first exists key] == 0} - assert {[$second exists key] == 1} - assert {[$second ttl key] == -1} - assert {[$second llen key] == 40000*20} - } - } {} {external:skip} - - test {MIGRATE can correctly transfer hashes} { - set first [srv 0 client] - r del key - r hmset key field1 "item 1" field2 "item 2" field3 "item 3" \ - field4 "item 4" field5 "item 5" field6 "item 6" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists key] == 1} - assert {[$second exists key] == 0} - set ret [r -1 migrate $second_host $second_port key 9 10000] - assert {$ret eq {OK}} - assert {[$first exists key] == 0} - assert {[$second exists key] == 1} - assert {[$second ttl key] == -1} - } - } {} {external:skip} - - test {MIGRATE timeout actually works} { - set first [srv 0 client] - r set key "Some Value" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists key] == 1} - assert {[$second exists key] == 0} - - set rd [redis_deferring_client] - $rd debug sleep 1.0 ; # Make second server unable to reply. - set e {} - catch {r -1 migrate $second_host $second_port key 9 500} e - assert_match {IOERR*} $e - } - } {} {external:skip} - - test {MIGRATE can migrate multiple keys at once} { - set first [srv 0 client] - r set key1 "v1" - r set key2 "v2" - r set key3 "v3" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - assert {[$first exists key1] == 1} - assert {[$second exists key1] == 0} - set ret [r -1 migrate $second_host $second_port "" 9 5000 keys key1 key2 key3] - assert {$ret eq {OK}} - assert {[$first exists key1] == 0} - assert {[$first exists key2] == 0} - assert {[$first exists key3] == 0} - assert {[$second get key1] eq {v1}} - assert {[$second get key2] eq {v2}} - assert {[$second get key3] eq {v3}} - } - } {} {external:skip} - - test {MIGRATE with multiple keys must have empty key arg} { - catch {r MIGRATE 127.0.0.1 6379 NotEmpty 9 5000 keys a b c} e - set e - } {*empty string*} {external:skip} - - test {MIGRATE with multiple keys migrate just existing ones} { - set first [srv 0 client] - r set key1 "v1" - r set key2 "v2" - r set key3 "v3" - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - set ret [r -1 migrate $second_host $second_port "" 9 5000 keys nokey-1 nokey-2 nokey-2] - assert {$ret eq {NOKEY}} - - assert {[$first exists key1] == 1} - assert {[$second exists key1] == 0} - set ret [r -1 migrate $second_host $second_port "" 9 5000 keys nokey-1 key1 nokey-2 key2 nokey-3 key3] - assert {$ret eq {OK}} - assert {[$first exists key1] == 0} - assert {[$first exists key2] == 0} - assert {[$first exists key3] == 0} - assert {[$second get key1] eq {v1}} - assert {[$second get key2] eq {v2}} - assert {[$second get key3] eq {v3}} - } - } {} {external:skip} - - test {MIGRATE with multiple keys: stress command rewriting} { - set first [srv 0 client] - r flushdb - r mset a 1 b 2 c 3 d 4 c 5 e 6 f 7 g 8 h 9 i 10 l 11 m 12 n 13 o 14 p 15 q 16 - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - set ret [r -1 migrate $second_host $second_port "" 9 5000 keys a b c d e f g h i l m n o p q] - - assert {[$first dbsize] == 0} - assert {[$second dbsize] == 15} - } - } {} {external:skip} - - test {MIGRATE with multiple keys: delete just ack keys} { - set first [srv 0 client] - r flushdb - r mset a 1 b 2 c 3 d 4 c 5 e 6 f 7 g 8 h 9 i 10 l 11 m 12 n 13 o 14 p 15 q 16 - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - - $second mset c _ d _; # Two busy keys and no REPLACE used - - catch {r -1 migrate $second_host $second_port "" 9 5000 keys a b c d e f g h i l m n o p q} e - - assert {[$first dbsize] == 2} - assert {[$second dbsize] == 15} - assert {[$first exists c] == 1} - assert {[$first exists d] == 1} - } - } {} {external:skip} - - test {MIGRATE AUTH: correct and wrong password cases} { - set first [srv 0 client] - r del list - r lpush list a b c d - start_server {tags {"repl"}} { - set second [srv 0 client] - set second_host [srv 0 host] - set second_port [srv 0 port] - $second config set requirepass foobar - $second auth foobar - - assert {[$first exists list] == 1} - assert {[$second exists list] == 0} - set ret [r -1 migrate $second_host $second_port list 9 5000 AUTH foobar] - assert {$ret eq {OK}} - assert {[$second exists list] == 1} - assert {[$second lrange list 0 -1] eq {d c b a}} - - r -1 lpush list a b c d - $second config set requirepass foobar2 - catch {r -1 migrate $second_host $second_port list 9 5000 AUTH foobar} err - assert_match {*WRONGPASS*} $err - } - } {} {external:skip} -} diff --git a/examples/redis-unstable/tests/unit/expire.tcl b/examples/redis-unstable/tests/unit/expire.tcl deleted file mode 100644 index 69fa0d1..0000000 --- a/examples/redis-unstable/tests/unit/expire.tcl +++ /dev/null @@ -1,972 +0,0 @@ -start_server {tags {"expire"}} { - test {EXPIRE - set timeouts multiple times} { - r set x foobar - set v1 [r expire x 5] - set v2 [r ttl x] - set v3 [r expire x 10] - set v4 [r ttl x] - r expire x 2 - list $v1 $v2 $v3 $v4 - } {1 [45] 1 10} - - test {EXPIRE - It should be still possible to read 'x'} { - r get x - } {foobar} - - tags {"slow"} { - test {EXPIRE - After 2.1 seconds the key should no longer be here} { - after 2100 - list [r get x] [r exists x] - } {{} 0} - } - - test {EXPIRE - write on expire should work} { - r del x - r lpush x foo - r expire x 1000 - r lpush x bar - r lrange x 0 -1 - } {bar foo} - - test {EXPIREAT - Check for EXPIRE alike behavior} { - r del x - r set x foo - r expireat x [expr [clock seconds]+15] - r ttl x - } {1[345]} - - test {SETEX - Set + Expire combo operation. Check for TTL} { - r setex x 12 test - r ttl x - } {1[012]} - - test {SETEX - Check value} { - r get x - } {test} - - test {SETEX - Overwrite old key} { - r setex y 1 foo - r get y - } {foo} - - tags {"slow"} { - test {SETEX - Wait for the key to expire} { - after 1100 - r get y - } {} - } - - test {SETEX - Wrong time parameter} { - catch {r setex z -10 foo} e - set _ $e - } {*invalid expire*} - - test {PERSIST can undo an EXPIRE} { - r set x foo - r expire x 50 - list [r ttl x] [r persist x] [r ttl x] [r get x] - } {50 1 -1 foo} - - test {PERSIST returns 0 against non existing or non volatile keys} { - r set x foo - list [r persist foo] [r persist nokeyatall] - } {0 0} - - test {EXPIRE precision is now the millisecond} { - # This test is very likely to do a false positive if the - # server is under pressure, so if it does not work give it a few more - # chances. - for {set j 0} {$j < 30} {incr j} { - r del x - r setex x 1 somevalue - after 800 - set a [r get x] - if {$a ne {somevalue}} continue - after 300 - set b [r get x] - if {$b eq {}} break - } - if {$::verbose} { - puts "millisecond expire test attempts: $j" - } - assert_equal $a {somevalue} - assert_equal $b {} - } - - test "PSETEX can set sub-second expires" { - # This test is very likely to do a false positive if the server is - # under pressure, so if it does not work give it a few more chances. - for {set j 0} {$j < 50} {incr j} { - r del x - r psetex x 100 somevalue - set a [r get x] - after 101 - set b [r get x] - - if {$a eq {somevalue} && $b eq {}} break - } - if {$::verbose} { puts "PSETEX sub-second expire test attempts: $j" } - list $a $b - } {somevalue {}} - - test "PEXPIRE can set sub-second expires" { - # This test is very likely to do a false positive if the server is - # under pressure, so if it does not work give it a few more chances. - for {set j 0} {$j < 50} {incr j} { - r set x somevalue - r pexpire x 100 - set c [r get x] - after 101 - set d [r get x] - - if {$c eq {somevalue} && $d eq {}} break - } - if {$::verbose} { puts "PEXPIRE sub-second expire test attempts: $j" } - list $c $d - } {somevalue {}} - - test "PEXPIREAT can set sub-second expires" { - # This test is very likely to do a false positive if the server is - # under pressure, so if it does not work give it a few more chances. - for {set j 0} {$j < 50} {incr j} { - r set x somevalue - set now [r time] - r pexpireat x [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+200] - set e [r get x] - after 201 - set f [r get x] - - if {$e eq {somevalue} && $f eq {}} break - } - if {$::verbose} { puts "PEXPIREAT sub-second expire test attempts: $j" } - list $e $f - } {somevalue {}} - - test {TTL returns time to live in seconds} { - r del x - r setex x 10 somevalue - set ttl [r ttl x] - assert {$ttl > 8 && $ttl <= 10} - } - - test {PTTL returns time to live in milliseconds} { - r del x - r setex x 1 somevalue - set ttl [r pttl x] - assert {$ttl > 500 && $ttl <= 1000} - } - - test {TTL / PTTL / EXPIRETIME / PEXPIRETIME return -1 if key has no expire} { - r del x - r set x hello - list [r ttl x] [r pttl x] [r expiretime x] [r pexpiretime x] - } {-1 -1 -1 -1} - - test {TTL / PTTL / EXPIRETIME / PEXPIRETIME return -2 if key does not exit} { - r del x - list [r ttl x] [r pttl x] [r expiretime x] [r pexpiretime x] - } {-2 -2 -2 -2} - - test {EXPIRETIME returns absolute expiration time in seconds} { - r del x - set abs_expire [expr [clock seconds] + 100] - r set x somevalue exat $abs_expire - assert_equal [r expiretime x] $abs_expire - } - - test {PEXPIRETIME returns absolute expiration time in milliseconds} { - r del x - set abs_expire [expr [clock milliseconds] + 100000] - r set x somevalue pxat $abs_expire - assert_equal [r pexpiretime x] $abs_expire - } - - test {Redis should actively expire keys incrementally} { - r flushdb - r psetex key1 500 a - r psetex key2 500 a - r psetex key3 500 a - assert_equal 3 [r dbsize] - # Redis expires random keys ten times every second so we are - # fairly sure that all the three keys should be evicted after - # two seconds. - wait_for_condition 20 100 { - [r dbsize] eq 0 - } else { - fail "Keys did not actively expire." - } - } - - test {Redis should lazy expire keys} { - r flushdb - r debug set-active-expire 0 - r psetex key1{t} 500 a - r psetex key2{t} 500 a - r psetex key3{t} 500 a - set size1 [r dbsize] - # Redis expires random keys ten times every second so we are - # fairly sure that all the three keys should be evicted after - # one second. - after 1000 - set size2 [r dbsize] - r mget key1{t} key2{t} key3{t} - set size3 [r dbsize] - r debug set-active-expire 1 - list $size1 $size2 $size3 - } {3 3 0} {needs:debug} - - test {EXPIRE should not resurrect keys (issue #1026)} { - r debug set-active-expire 0 - r set foo bar - r pexpire foo 500 - after 1000 - r expire foo 10 - r debug set-active-expire 1 - r exists foo - } {0} {needs:debug} - - test {5 keys in, 5 keys out} { - r flushdb - r set a c - r expire a 5 - r set t c - r set e c - r set s c - r set foo b - assert_equal [lsort [r keys *]] {a e foo s t} - r del a ; # Do not leak volatile keys to other tests - } - - test {EXPIRE with empty string as TTL should report an error} { - r set foo bar - catch {r expire foo ""} e - set e - } {*not an integer*} - - test {SET with EX with big integer should report an error} { - catch {r set foo bar EX 10000000000000000} e - set e - } {ERR invalid expire time in 'set' command} - - test {SET with EX with smallest integer should report an error} { - catch {r SET foo bar EX -9999999999999999} e - set e - } {ERR invalid expire time in 'set' command} - - test {GETEX with big integer should report an error} { - r set foo bar - catch {r GETEX foo EX 10000000000000000} e - set e - } {ERR invalid expire time in 'getex' command} - - test {GETEX with smallest integer should report an error} { - r set foo bar - catch {r GETEX foo EX -9999999999999999} e - set e - } {ERR invalid expire time in 'getex' command} - - test {EXPIRE with big integer overflows when converted to milliseconds} { - r set foo bar - - # Hit `when > LLONG_MAX - basetime` - assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 9223370399119966} - - # Hit `when > LLONG_MAX / 1000` - assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 9223372036854776} - assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 10000000000000000} - assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 18446744073709561} - - assert_equal {-1} [r ttl foo] - } - - test {PEXPIRE with big integer overflow when basetime is added} { - r set foo bar - catch {r PEXPIRE foo 9223372036854770000} e - set e - } {ERR invalid expire time in 'pexpire' command} - - test {EXPIRE with big negative integer} { - r set foo bar - - # Hit `when < LLONG_MIN / 1000` - assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo -9223372036854776} - assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo -9999999999999999} - - r ttl foo - } {-1} - - test {PEXPIREAT with big integer works} { - r set foo bar - r PEXPIREAT foo 9223372036854770000 - } {1} - - test {PEXPIREAT with big negative integer works} { - r set foo bar - r PEXPIREAT foo -9223372036854770000 - r ttl foo - } {-2} - - # Start a new server with empty data and AOF file. - start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} { - test {All time-to-live(TTL) in commands are propagated as absolute timestamp in milliseconds in AOF} { - # This test makes sure that expire times are propagated as absolute - # times to the AOF file and not as relative time, so that when the AOF - # is reloaded the TTLs are not being shifted forward to the future. - # We want the time to logically pass when the server is restarted! - - set aof [get_last_incr_aof_path r] - - # Apply each TTL-related command to a unique key - # SET commands - r set foo1 bar ex 100 - r set foo2 bar px 100000 - r set foo3 bar exat [expr [clock seconds]+100] - r set foo4 bar PXAT [expr [clock milliseconds]+100000] - r setex foo5 100 bar - r psetex foo6 100000 bar - # EXPIRE-family commands - r set foo7 bar - r expire foo7 100 - r set foo8 bar - r pexpire foo8 100000 - r set foo9 bar - r expireat foo9 [expr [clock seconds]+100] - r set foo10 bar - r pexpireat foo10 [expr [clock seconds]*1000+100000] - r set foo11 bar - r expireat foo11 [expr [clock seconds]-100] - # GETEX commands - r set foo12 bar - r getex foo12 ex 100 - r set foo13 bar - r getex foo13 px 100000 - r set foo14 bar - r getex foo14 exat [expr [clock seconds]+100] - r set foo15 bar - r getex foo15 pxat [expr [clock milliseconds]+100000] - # RESTORE commands - r set foo16 bar - set encoded [r dump foo16] - r restore foo17 100000 $encoded - r restore foo18 [expr [clock milliseconds]+100000] $encoded absttl - - # Assert that each TTL-related command are persisted with absolute timestamps in AOF - assert_aof_content $aof { - {select *} - {set foo1 bar PXAT *} - {set foo2 bar PXAT *} - {set foo3 bar PXAT *} - {set foo4 bar PXAT *} - {set foo5 bar PXAT *} - {set foo6 bar PXAT *} - {set foo7 bar} - {pexpireat foo7 *} - {set foo8 bar} - {pexpireat foo8 *} - {set foo9 bar} - {pexpireat foo9 *} - {set foo10 bar} - {pexpireat foo10 *} - {set foo11 bar} - {del foo11} - {set foo12 bar} - {pexpireat foo12 *} - {set foo13 bar} - {pexpireat foo13 *} - {set foo14 bar} - {pexpireat foo14 *} - {set foo15 bar} - {pexpireat foo15 *} - {set foo16 bar} - {restore foo17 * * ABSTTL} - {restore foo18 * * absttl} - } - - # Remember the absolute TTLs of all the keys - set ttl1 [r pexpiretime foo1] - set ttl2 [r pexpiretime foo2] - set ttl3 [r pexpiretime foo3] - set ttl4 [r pexpiretime foo4] - set ttl5 [r pexpiretime foo5] - set ttl6 [r pexpiretime foo6] - set ttl7 [r pexpiretime foo7] - set ttl8 [r pexpiretime foo8] - set ttl9 [r pexpiretime foo9] - set ttl10 [r pexpiretime foo10] - assert_equal "-2" [r pexpiretime foo11] ; # foo11 is gone - set ttl12 [r pexpiretime foo12] - set ttl13 [r pexpiretime foo13] - set ttl14 [r pexpiretime foo14] - set ttl15 [r pexpiretime foo15] - assert_equal "-1" [r pexpiretime foo16] ; # foo16 has no TTL - set ttl17 [r pexpiretime foo17] - set ttl18 [r pexpiretime foo18] - - # Let some time pass and reload data from AOF - after 2000 - r debug loadaof - - # Assert that relative TTLs are roughly the same - assert_range [r ttl foo1] 90 98 - assert_range [r ttl foo2] 90 98 - assert_range [r ttl foo3] 90 98 - assert_range [r ttl foo4] 90 98 - assert_range [r ttl foo5] 90 98 - assert_range [r ttl foo6] 90 98 - assert_range [r ttl foo7] 90 98 - assert_range [r ttl foo8] 90 98 - assert_range [r ttl foo9] 90 98 - assert_range [r ttl foo10] 90 98 - assert_equal [r ttl foo11] "-2" ; # foo11 is gone - assert_range [r ttl foo12] 90 98 - assert_range [r ttl foo13] 90 98 - assert_range [r ttl foo14] 90 98 - assert_range [r ttl foo15] 90 98 - assert_equal [r ttl foo16] "-1" ; # foo16 has no TTL - assert_range [r ttl foo17] 90 98 - assert_range [r ttl foo18] 90 98 - - # Assert that all keys have restored the same absolute TTLs from AOF - assert_equal [r pexpiretime foo1] $ttl1 - assert_equal [r pexpiretime foo2] $ttl2 - assert_equal [r pexpiretime foo3] $ttl3 - assert_equal [r pexpiretime foo4] $ttl4 - assert_equal [r pexpiretime foo5] $ttl5 - assert_equal [r pexpiretime foo6] $ttl6 - assert_equal [r pexpiretime foo7] $ttl7 - assert_equal [r pexpiretime foo8] $ttl8 - assert_equal [r pexpiretime foo9] $ttl9 - assert_equal [r pexpiretime foo10] $ttl10 - assert_equal [r pexpiretime foo11] "-2" ; # foo11 is gone - assert_equal [r pexpiretime foo12] $ttl12 - assert_equal [r pexpiretime foo13] $ttl13 - assert_equal [r pexpiretime foo14] $ttl14 - assert_equal [r pexpiretime foo15] $ttl15 - assert_equal [r pexpiretime foo16] "-1" ; # foo16 has no TTL - assert_equal [r pexpiretime foo17] $ttl17 - assert_equal [r pexpiretime foo18] $ttl18 - } {} {needs:debug} - } - - test {All TTL in commands are propagated as absolute timestamp in replication stream} { - # Make sure that both relative and absolute expire commands are propagated - # as absolute to replicas for two reasons: - # 1) We want to avoid replicas retaining data much longer than primary due - # to replication lag. - # 2) We want to unify the way TTLs are replicated in both RDB and replication - # stream, which is as absolute timestamps. - # See: https://github.com/redis/redis/issues/8433 - - r flushall ; # Clean up keyspace to avoid interference by keys from other tests - set repl [attach_to_replication_stream] - # SET commands - r set foo1 bar ex 200 - r set foo1 bar px 100000 - r set foo1 bar exat [expr [clock seconds]+100] - r set foo1 bar pxat [expr [clock milliseconds]+100000] - r setex foo1 100 bar - r psetex foo1 100000 bar - r set foo2 bar - # EXPIRE-family commands - r expire foo2 100 - r pexpire foo2 100000 - r set foo3 bar - r expireat foo3 [expr [clock seconds]+100] - r pexpireat foo3 [expr [clock seconds]*1000+100000] - r expireat foo3 [expr [clock seconds]-100] - # GETEX-family commands - r set foo4 bar - r getex foo4 ex 200 - r getex foo4 px 200000 - r getex foo4 exat [expr [clock seconds]+100] - r getex foo4 pxat [expr [clock milliseconds]+100000] - # RESTORE commands - r set foo5 bar - set encoded [r dump foo5] - r restore foo6 100000 $encoded - r restore foo7 [expr [clock milliseconds]+100000] $encoded absttl - - assert_replication_stream $repl { - {select *} - {set foo1 bar PXAT *} - {set foo1 bar PXAT *} - {set foo1 bar PXAT *} - {set foo1 bar pxat *} - {set foo1 bar PXAT *} - {set foo1 bar PXAT *} - {set foo2 bar} - {pexpireat foo2 *} - {pexpireat foo2 *} - {set foo3 bar} - {pexpireat foo3 *} - {pexpireat foo3 *} - {del foo3} - {set foo4 bar} - {pexpireat foo4 *} - {pexpireat foo4 *} - {pexpireat foo4 *} - {pexpireat foo4 *} - {set foo5 bar} - {restore foo6 * * ABSTTL} - {restore foo7 * * absttl} - } - close_replication_stream $repl - } {} {needs:repl} - - # Start another server to test replication of TTLs - start_server {tags {needs:repl external:skip}} { - # Set the outer layer server as primary - set primary [srv -1 client] - set primary_host [srv -1 host] - set primary_port [srv -1 port] - # Set this inner layer server as replica - set replica [srv 0 client] - - test {First server should have role slave after REPLICAOF} { - $replica replicaof $primary_host $primary_port - wait_for_condition 50 100 { - [s 0 role] eq {slave} - } else { - fail "Replication not started." - } - } - - test {For all replicated TTL-related commands, absolute expire times are identical on primary and replica} { - # Apply each TTL-related command to a unique key on primary - # SET commands - $primary set foo1 bar ex 100 - $primary set foo2 bar px 100000 - $primary set foo3 bar exat [expr [clock seconds]+100] - $primary set foo4 bar pxat [expr [clock milliseconds]+100000] - $primary setex foo5 100 bar - $primary psetex foo6 100000 bar - # EXPIRE-family commands - $primary set foo7 bar - $primary expire foo7 100 - $primary set foo8 bar - $primary pexpire foo8 100000 - $primary set foo9 bar - $primary expireat foo9 [expr [clock seconds]+100] - $primary set foo10 bar - $primary pexpireat foo10 [expr [clock milliseconds]+100000] - # GETEX commands - $primary set foo11 bar - $primary getex foo11 ex 100 - $primary set foo12 bar - $primary getex foo12 px 100000 - $primary set foo13 bar - $primary getex foo13 exat [expr [clock seconds]+100] - $primary set foo14 bar - $primary getex foo14 pxat [expr [clock milliseconds]+100000] - # RESTORE commands - $primary set foo15 bar - set encoded [$primary dump foo15] - $primary restore foo16 100000 $encoded - $primary restore foo17 [expr [clock milliseconds]+100000] $encoded absttl - - # Wait for replica to get the keys and TTLs - assert {[$primary wait 1 0] == 1} - - # Verify absolute TTLs are identical on primary and replica for all keys - # This is because TTLs are always replicated as absolute values - foreach key [$primary keys *] { - assert_equal [$primary pexpiretime $key] [$replica pexpiretime $key] - } - } - - test {expired key which is created in writeable replicas should be deleted by active expiry} { - $primary flushall - $replica config set replica-read-only no - foreach {yes_or_no} {yes no} { - $replica config set appendonly $yes_or_no - waitForBgrewriteaof $replica - set prev_expired [s expired_keys] - $replica set foo bar PX 1 - wait_for_condition 100 10 { - [s expired_keys] eq $prev_expired + 1 - } else { - fail "key not expired" - } - assert_equal {} [$replica get foo] - } - } - } - - test {SET command will remove expire} { - r set foo bar EX 100 - r set foo bar - r ttl foo - } {-1} - - test {SET command will remove expire with large string (optimization path)} { - # This test specifically targets the dbSetValue optimization path - # that was missing TTL handling for large strings - set large_value [string repeat "A" 1000] - r set foo $large_value EX 100 - r set foo $large_value KEEPTTL - set ttl1 [r ttl foo] - assert {$ttl1 <= 100 && $ttl1 > 90} - - # Plain SET should remove TTL even with large strings - r set foo $large_value - set ttl2 [r ttl foo] - assert_equal $ttl2 -1 - } - - test {SET - use KEEPTTL option, TTL should not be removed} { - r set foo bar EX 100 - r set foo bar KEEPTTL - set ttl [r ttl foo] - assert {$ttl <= 100 && $ttl > 90} - } - - test {SET - use KEEPTTL option, TTL should not be removed after loadaof} { - r config set appendonly yes - r set foo bar EX 100 - r set foo bar2 KEEPTTL - after 2000 - r debug loadaof - set ttl [r ttl foo] - assert {$ttl <= 98 && $ttl > 90} - } {} {needs:debug} - - test {GETEX use of PERSIST option should remove TTL} { - r set foo bar EX 100 - r getex foo PERSIST - r ttl foo - } {-1} - - test {GETEX use of PERSIST option should remove TTL after loadaof} { - r config set appendonly yes - r set foo bar EX 100 - r getex foo PERSIST - r debug loadaof - r ttl foo - } {-1} {needs:debug} - - test {GETEX propagate as to replica as PERSIST, DEL, or nothing} { - # In the above tests, many keys with random expiration times are set, flush - # the DBs to avoid active expiry kicking in and messing the replication streams. - r flushall - set repl [attach_to_replication_stream] - r set foo bar EX 100 - r getex foo PERSIST - r getex foo - r getex foo exat [expr [clock seconds]-100] - assert_replication_stream $repl { - {select *} - {set foo bar PXAT *} - {persist foo} - {del foo} - } - close_replication_stream $repl - } {} {needs:repl} - - test {EXPIRE with NX option on a key with ttl} { - r SET foo bar EX 100 - assert_equal [r EXPIRE foo 200 NX] 0 - assert_range [r TTL foo] 50 100 - } {} - - test {EXPIRE with NX option on a key without ttl} { - r SET foo bar - assert_equal [r EXPIRE foo 200 NX] 1 - assert_range [r TTL foo] 100 200 - } {} - - test {EXPIRE with XX option on a key with ttl} { - r SET foo bar EX 100 - assert_equal [r EXPIRE foo 200 XX] 1 - assert_range [r TTL foo] 100 200 - } {} - - test {EXPIRE with XX option on a key without ttl} { - r SET foo bar - assert_equal [r EXPIRE foo 200 XX] 0 - assert_equal [r TTL foo] -1 - } {} - - test {EXPIRE with GT option on a key with lower ttl} { - r SET foo bar EX 100 - assert_equal [r EXPIRE foo 200 GT] 1 - assert_range [r TTL foo] 100 200 - } {} - - test {EXPIRE with GT option on a key with higher ttl} { - r SET foo bar EX 200 - assert_equal [r EXPIRE foo 100 GT] 0 - assert_range [r TTL foo] 100 200 - } {} - - test {EXPIRE with GT option on a key without ttl} { - r SET foo bar - assert_equal [r EXPIRE foo 200 GT] 0 - assert_equal [r TTL foo] -1 - } {} - - test {EXPIRE with LT option on a key with higher ttl} { - r SET foo bar EX 100 - assert_equal [r EXPIRE foo 200 LT] 0 - assert_range [r TTL foo] 50 100 - } {} - - test {EXPIRE with LT option on a key with lower ttl} { - r SET foo bar EX 200 - assert_equal [r EXPIRE foo 100 LT] 1 - assert_range [r TTL foo] 50 100 - } {} - - test {EXPIRE with LT option on a key without ttl} { - r SET foo bar - assert_equal [r EXPIRE foo 100 LT] 1 - assert_range [r TTL foo] 50 100 - } {} - - test {EXPIRE with LT and XX option on a key with ttl} { - r SET foo bar EX 200 - assert_equal [r EXPIRE foo 100 LT XX] 1 - assert_range [r TTL foo] 50 100 - } {} - - test {EXPIRE with LT and XX option on a key without ttl} { - r SET foo bar - assert_equal [r EXPIRE foo 200 LT XX] 0 - assert_equal [r TTL foo] -1 - } {} - - test {EXPIRE with conflicting options: LT GT} { - catch {r EXPIRE foo 200 LT GT} e - set e - } {ERR GT and LT options at the same time are not compatible} - - test {EXPIRE with conflicting options: NX GT} { - catch {r EXPIRE foo 200 NX GT} e - set e - } {ERR NX and XX, GT or LT options at the same time are not compatible} - - test {EXPIRE with conflicting options: NX LT} { - catch {r EXPIRE foo 200 NX LT} e - set e - } {ERR NX and XX, GT or LT options at the same time are not compatible} - - test {EXPIRE with conflicting options: NX XX} { - catch {r EXPIRE foo 200 NX XX} e - set e - } {ERR NX and XX, GT or LT options at the same time are not compatible} - - test {EXPIRE with unsupported options} { - catch {r EXPIRE foo 200 AB} e - set e - } {ERR Unsupported option AB} - - test {EXPIRE with unsupported options} { - catch {r EXPIRE foo 200 XX AB} e - set e - } {ERR Unsupported option AB} - - test {EXPIRE with negative expiry} { - r SET foo bar EX 100 - assert_equal [r EXPIRE foo -10 LT] 1 - assert_equal [r TTL foo] -2 - } {} - - test {EXPIRE with negative expiry on a non-valitale key} { - r SET foo bar - assert_equal [r EXPIRE foo -10 LT] 1 - assert_equal [r TTL foo] -2 - } {} - - test {EXPIRE with non-existed key} { - assert_equal [r EXPIRE none 100 NX] 0 - assert_equal [r EXPIRE none 100 XX] 0 - assert_equal [r EXPIRE none 100 GT] 0 - assert_equal [r EXPIRE none 100 LT] 0 - } {} - - test {Redis should not propagate the read command on lazy expire} { - r debug set-active-expire 0 - r flushall ; # Clean up keyspace to avoid interference by keys from other tests - r set foo bar PX 1 - set repl [attach_to_replication_stream] - wait_for_condition 50 100 { - [r get foo] eq {} - } else { - fail "Replication not started." - } - - # dummy command to verify nothing else gets into the replication stream. - r set x 1 - - assert_replication_stream $repl { - {select *} - {del foo} - {set x 1} - } - close_replication_stream $repl - assert_equal [r debug set-active-expire 1] {OK} - } {} {needs:debug} - - test {SCAN: Lazy-expire should not be wrapped in MULTI/EXEC} { - r debug set-active-expire 0 - r flushall - - r set foo1 bar PX 1 - r set foo2 bar PX 1 - after 2 - - set repl [attach_to_replication_stream] - - r scan 0 - - assert_replication_stream $repl { - {select *} - {del foo*} - {del foo*} - } - close_replication_stream $repl - assert_equal [r debug set-active-expire 1] {OK} - } {} {needs:debug} - - test {RANDOMKEY: Lazy-expire should not be wrapped in MULTI/EXEC} { - r debug set-active-expire 0 - r flushall - - r set foo1 bar PX 1 - r set foo2 bar PX 1 - after 2 - - set repl [attach_to_replication_stream] - - r randomkey - - assert_replication_stream $repl { - {select *} - {del foo*} - {del foo*} - } - close_replication_stream $repl - assert_equal [r debug set-active-expire 1] {OK} - } {} {needs:debug} -} - -start_cluster 1 0 {tags {"expire external:skip cluster"}} { - test "expire scan should skip dictionaries with lot's of empty buckets" { - r debug set-active-expire 0 - - # Collect two slots to help determine the expiry scan logic is able - # to go past certain slots which aren't valid for scanning at the given point of time. - # And the next non empty slot after that still gets scanned and expiration happens. - - # hashslot(alice) is 749 - r psetex alice 500 val - - # hashslot(foo) is 12182 - # fill data across different slots with expiration - for {set j 1} {$j <= 100} {incr j} { - r psetex "{foo}$j" 500 a - } - # hashslot(key) is 12539 - r psetex key 500 val - - # disable resizing, the reason for not using slow bgsave is because - # it will hit the dict_force_resize_ratio. - r debug dict-resizing 0 - - # delete data to have lot's (99%) of empty buckets (slot 12182 should be skipped) - for {set j 1} {$j <= 99} {incr j} { - r del "{foo}$j" - } - - # Trigger a full traversal of all dictionaries. - r keys * - - r debug set-active-expire 1 - - # Verify {foo}100 still exists and remaining got cleaned up - wait_for_condition 20 100 { - [r dbsize] eq 1 - } else { - if {[r dbsize] eq 0} { - puts [r debug htstats 0] - fail "scan didn't handle slot skipping logic." - } else { - puts [r debug htstats 0] - fail "scan didn't process all valid slots." - } - } - - # Enable resizing - r debug dict-resizing 1 - - # put some data into slot 12182 and trigger the resize - r psetex "{foo}0" 500 a - - # Verify all keys have expired - wait_for_condition 400 100 { - [r dbsize] eq 0 - } else { - puts [r dbsize] - flush stdout - fail "Keys did not actively expire." - } - - # Make sure we don't have any timeouts. - assert_equal 0 [s 0 expired_time_cap_reached_count] - } {} {needs:debug} -} - -# Config lazyexpire-nested-arbitrary-keys test body -proc conf_le_test {option mode} { - r config set lazyexpire-nested-arbitrary-keys $option - r debug set-active-expire 0 - r flushall - r script LOAD {return redis.call('SCAN', 0)} - - r set foo bar - r pexpire foo 1 - after 2 - - set repl [attach_to_replication_stream] - - # First two conditions hit lazy expire within a 'transaction', meaning - # DEL propagation should be blocked if 'lazyexpire-nested-arbitrary-keys' is set. - if {$mode == "lua"} { - r eval "return redis.call('SCAN', 0)" 0 - } elseif {$mode == "multi"} { - r multi - r scan 0 - r exec - } else { - r scan 0 - } - - # dummy command to verify nothing else gets into the replication stream. - r set x 1 - - if {$option == "no" && $mode != "direct"} { - assert_replication_stream $repl { - {select *} - {set x 1} - } - } else { - assert_replication_stream $repl { - {select *} - {del foo} - {set x 1} - } - } - - close_replication_stream $repl - r script flush - assert_equal [r config set lazyexpire-nested-arbitrary-keys yes] {OK} - assert_equal [r debug set-active-expire 1] {OK} -} - -foreach option {yes no} { -foreach mode {direct multi lua} { - start_server {tags {"expire"}} { - test "Config lazyexpire-nested-arbitrary-keys ($option, $mode)" { - conf_le_test $option $mode - } {} {needs:debug repl} - } -}} diff --git a/examples/redis-unstable/tests/unit/functions.tcl b/examples/redis-unstable/tests/unit/functions.tcl deleted file mode 100644 index b9496c9..0000000 --- a/examples/redis-unstable/tests/unit/functions.tcl +++ /dev/null @@ -1,1259 +0,0 @@ -proc get_function_code {args} { - return [format "#!%s name=%s\nredis.register_function('%s', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]] -} - -proc get_no_writes_function_code {args} { - return [format "#!%s name=%s\nredis.register_function{function_name='%s', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]] -} - -start_server {tags {"scripting"}} { - test {FUNCTION - Basic usage} { - r function load [get_function_code LUA test test {return 'hello'}] - r fcall test 0 - } {hello} - - test {FUNCTION - Load with unknown argument} { - catch { - r function load foo bar [get_function_code LUA test test {return 'hello'}] - } e - set _ $e - } {*Unknown option given*} - - test {FUNCTION - Create an already exiting library raise error} { - catch { - r function load [get_function_code LUA test test {return 'hello1'}] - } e - set _ $e - } {*already exists*} - - test {FUNCTION - Create an already exiting library raise error (case insensitive)} { - catch { - r function load [get_function_code LUA test test {return 'hello1'}] - } e - set _ $e - } {*already exists*} - - test {FUNCTION - Create a library with wrong name format} { - catch { - r function load [get_function_code LUA {bad\0foramat} test {return 'hello1'}] - } e - set _ $e - } {*Library names can only contain letters, numbers, or underscores(_)*} - - test {FUNCTION - Create library with unexisting engine} { - catch { - r function load [get_function_code bad_engine test test {return 'hello1'}] - } e - set _ $e - } {*Engine 'bad_engine' not found*} - - test {FUNCTION - Test uncompiled script} { - catch { - r function load replace [get_function_code LUA test test {bad script}] - } e - set _ $e - } {*Error compiling function*} - - test {FUNCTION - test replace argument} { - r function load REPLACE [get_function_code LUA test test {return 'hello1'}] - r fcall test 0 - } {hello1} - - test {FUNCTION - test function case insensitive} { - r fcall TEST 0 - } {hello1} - - test {FUNCTION - test replace argument with failure keeps old libraries} { - catch {r function load REPLACE [get_function_code LUA test test {error}]} e - assert_match {ERR Error compiling function*} $e - r fcall test 0 - } {hello1} - - test {FUNCTION - test function delete} { - r function delete test - catch { - r fcall test 0 - } e - set _ $e - } {*Function not found*} - - test {FUNCTION - test fcall bad arguments} { - r function load [get_function_code LUA test test {return 'hello'}] - catch { - r fcall test bad_arg - } e - set _ $e - } {*Bad number of keys provided*} - - test {FUNCTION - test fcall bad number of keys arguments} { - catch { - r fcall test 10 key1 - } e - set _ $e - } {*Number of keys can't be greater than number of args*} - - test {FUNCTION - test fcall negative number of keys} { - catch { - r fcall test -1 key1 - } e - set _ $e - } {*Number of keys can't be negative*} - - test {FUNCTION - test delete on not exiting library} { - catch { - r function delete test1 - } e - set _ $e - } {*Library not found*} - - test {FUNCTION - test function kill when function is not running} { - catch { - r function kill - } e - set _ $e - } {*No scripts in execution*} - - test {FUNCTION - test wrong subcommand} { - catch { - r function bad_subcommand - } e - set _ $e - } {*unknown subcommand*} - - test {FUNCTION - test loading from rdb} { - r debug reload - r fcall test 0 - } {hello} {needs:debug} - - test {FUNCTION - test debug reload different options} { - catch {r debug reload noflush} e - assert_match "*Error trying to load the RDB*" $e - r debug reload noflush merge - r function list - } {{library_name test engine LUA functions {{name test description {} flags {}}}}} {needs:debug} - - test {FUNCTION - test debug reload with nosave and noflush} { - r function delete test - r set x 1 - r function load [get_function_code LUA test1 test1 {return 'hello'}] - r debug reload - r function load [get_function_code LUA test2 test2 {return 'hello'}] - r debug reload nosave noflush merge - assert_equal [r fcall test1 0] {hello} - assert_equal [r fcall test2 0] {hello} - } {} {needs:debug} - - test {FUNCTION - test flushall and flushdb do not clean functions} { - r function flush - r function load REPLACE [get_function_code lua test test {return redis.call('set', 'x', '1')}] - r flushall - r flushdb - r function list - } {{library_name test engine LUA functions {{name test description {} flags {}}}}} - - test {FUNCTION - test function dump and restore} { - r function flush - r function load [get_function_code lua test test {return 'hello'}] - set e [r function dump] - r function delete test - assert_match {} [r function list] - r function restore $e - r function list - } {{library_name test engine LUA functions {{name test description {} flags {}}}}} - - test {FUNCTION - test function dump and restore with flush argument} { - set e [r function dump] - r function flush - assert_match {} [r function list] - r function restore $e FLUSH - r function list - } {{library_name test engine LUA functions {{name test description {} flags {}}}}} - - test {FUNCTION - test function dump and restore with append argument} { - set e [r function dump] - r function flush - assert_match {} [r function list] - r function load [get_function_code lua test test {return 'hello1'}] - catch {r function restore $e APPEND} err - assert_match {*already exists*} $err - r function flush - r function load [get_function_code lua test1 test1 {return 'hello1'}] - r function restore $e APPEND - assert_match {hello} [r fcall test 0] - assert_match {hello1} [r fcall test1 0] - } - - test {FUNCTION - test function dump and restore with replace argument} { - r function flush - r function load [get_function_code LUA test test {return 'hello'}] - set e [r function dump] - r function flush - assert_match {} [r function list] - r function load [get_function_code lua test test {return 'hello1'}] - assert_match {hello1} [r fcall test 0] - r function restore $e REPLACE - assert_match {hello} [r fcall test 0] - } - - test {FUNCTION - test function restore with bad payload do not drop existing functions} { - r function flush - r function load [get_function_code LUA test test {return 'hello'}] - catch {r function restore bad_payload} e - assert_match {*payload version or checksum are wrong*} $e - r function list - } {{library_name test engine LUA functions {{name test description {} flags {}}}}} - - test {FUNCTION - test function restore with wrong number of arguments} { - catch {r function restore arg1 args2 arg3} e - set _ $e - } {*unknown subcommand or wrong number of arguments for 'restore'. Try FUNCTION HELP.} - - test {FUNCTION - test fcall_ro with write command} { - r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('set', 'x', '1')}] - catch { r fcall_ro test 1 x } e - set _ $e - } {*Write commands are not allowed from read-only scripts*} - - test {FUNCTION - test fcall_ro with read only commands} { - r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('get', 'x')}] - r set x 1 - r fcall_ro test 1 x - } {1} - - test {FUNCTION - test keys and argv} { - r function load REPLACE [get_function_code lua test test {return redis.call('set', KEYS[1], ARGV[1])}] - r fcall test 1 x foo - r get x - } {foo} - - test {FUNCTION - test command get keys on fcall} { - r COMMAND GETKEYS fcall test 1 x foo - } {x} - - test {FUNCTION - test command get keys on fcall_ro} { - r COMMAND GETKEYS fcall_ro test 1 x foo - } {x} - - test {FUNCTION - test function kill} { - set rd [redis_deferring_client] - r config set busy-reply-threshold 10 - r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] - $rd fcall test 0 - after 200 - catch {r ping} e - assert_match {BUSY*} $e - assert_match {running_script {name test command {fcall test 0} duration_ms *} engines {*}} [r FUNCTION STATS] - r function kill - after 200 ; # Give some time to Lua to call the hook again... - assert_equal [r ping] "PONG" - } - - test {FUNCTION - test script kill not working on function} { - set rd [redis_deferring_client] - r config set busy-reply-threshold 10 - r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] - $rd fcall test 0 - after 200 - catch {r ping} e - assert_match {BUSY*} $e - catch {r script kill} e - assert_match {BUSY*} $e - r function kill - after 200 ; # Give some time to Lua to call the hook again... - assert_equal [r ping] "PONG" - } - - test {FUNCTION - test function kill not working on eval} { - set rd [redis_deferring_client] - r config set busy-reply-threshold 10 - $rd eval {local a = 1 while true do a = a + 1 end} 0 - after 200 - catch {r ping} e - assert_match {BUSY*} $e - catch {r function kill} e - assert_match {BUSY*} $e - r script kill - after 200 ; # Give some time to Lua to call the hook again... - assert_equal [r ping] "PONG" - } - - test {FUNCTION - test function flush} { - r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] - assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] - r function flush - assert_match {} [r function list] - - r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] - assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] - r function flush async - assert_match {} [r function list] - - r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] - assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] - r function flush sync - assert_match {} [r function list] - } - - test {FUNCTION - async function flush rebuilds Lua VM without causing race condition between main and lazyfree thread} { - # LAZYFREE_THRESHOLD is 64 - for {set i 0} {$i < 1000} {incr i} { - r function load [get_function_code lua test$i test$i {local a = 1 while true do a = a + 1 end}] - } - assert_morethan [s used_memory_vm_functions] 100000 - r config resetstat - r function flush async - assert_lessthan [s used_memory_vm_functions] 40000 - - # Wait for the completion of lazy free for both functions and engines. - set start_time [clock seconds] - while {1} { - # Tests for race conditions between async function flushes and main thread Lua VM operations. - r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] - if {[s lazyfreed_objects] == 1001 || [expr {[clock seconds] - $start_time}] > 5} { - break - } - } - if {[s lazyfreed_objects] != 1001} { - error "Timeout or unexpected number of lazyfreed_objects: [s lazyfreed_objects]" - } - assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] - } - - test {FUNCTION - test function wrong argument} { - catch {r function flush bad_arg} e - assert_match {*only supports SYNC|ASYNC*} $e - - catch {r function flush sync extra_arg} e - assert_match {*unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.} $e - } -} - -start_server {tags {"scripting repl external:skip"}} { - start_server {} { - test "Connect a replica to the master instance" { - r -1 slaveof [srv 0 host] [srv 0 port] - wait_for_condition 150 100 { - [s -1 role] eq {slave} && - [string match {*master_link_status:up*} [r -1 info replication]] - } else { - fail "Can't turn the instance into a replica" - } - } - - test {FUNCTION - creation is replicated to replica} { - r function load [get_no_writes_function_code LUA test test {return 'hello'}] - wait_for_condition 150 100 { - [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}} - } else { - fail "Failed waiting for function to replicate to replica" - } - } - - test {FUNCTION - call on replica} { - r -1 fcall test 0 - } {hello} - - test {FUNCTION - restore is replicated to replica} { - set e [r function dump] - - r function delete test - wait_for_condition 150 100 { - [r -1 function list] eq {} - } else { - fail "Failed waiting for function to replicate to replica" - } - - assert_equal [r function restore $e] {OK} - - wait_for_condition 150 100 { - [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}} - } else { - fail "Failed waiting for function to replicate to replica" - } - } - - test {FUNCTION - delete is replicated to replica} { - r function delete test - wait_for_condition 150 100 { - [r -1 function list] eq {} - } else { - fail "Failed waiting for function to replicate to replica" - } - } - - test {FUNCTION - flush is replicated to replica} { - r function load [get_function_code LUA test test {return 'hello'}] - wait_for_condition 150 100 { - [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags {}}}}} - } else { - fail "Failed waiting for function to replicate to replica" - } - r function flush - wait_for_condition 150 100 { - [r -1 function list] eq {} - } else { - fail "Failed waiting for function to replicate to replica" - } - } - - test "Disconnecting the replica from master instance" { - r -1 slaveof no one - # creating a function after disconnect to make sure function - # is replicated on rdb phase - r function load [get_no_writes_function_code LUA test test {return 'hello'}] - - # reconnect the replica - r -1 slaveof [srv 0 host] [srv 0 port] - wait_for_condition 150 100 { - [s -1 role] eq {slave} && - [string match {*master_link_status:up*} [r -1 info replication]] - } else { - fail "Can't turn the instance into a replica" - } - } - - test "FUNCTION - test replication to replica on rdb phase" { - r -1 fcall test 0 - } {hello} - - test "FUNCTION - test replication to replica on rdb phase info command" { - r -1 function list - } {{library_name test engine LUA functions {{name test description {} flags no-writes}}}} - - test "FUNCTION - create on read only replica" { - catch { - r -1 function load [get_function_code LUA test test {return 'hello'}] - } e - set _ $e - } {*can't write against a read only replica*} - - test "FUNCTION - delete on read only replica" { - catch { - r -1 function delete test - } e - set _ $e - } {*can't write against a read only replica*} - - test "FUNCTION - function effect is replicated to replica" { - r function load REPLACE [get_function_code LUA test test {return redis.call('set', 'x', '1')}] - r fcall test 1 x - assert {[r get x] eq {1}} - wait_for_condition 150 100 { - [r -1 get x] eq {1} - } else { - fail "Failed waiting function effect to be replicated to replica" - } - } - - test "FUNCTION - modify key space of read only replica" { - catch { - r -1 fcall test 1 x - } e - set _ $e - } {READONLY You can't write against a read only replica.} - } -} - -test {FUNCTION can processes create, delete and flush commands in AOF when doing "debug loadaof" in read-only slaves} { - start_server {} { - r config set appendonly yes - waitForBgrewriteaof r - r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)" - r config set slave-read-only yes - r slaveof 127.0.0.1 0 - r debug loadaof - r slaveof no one - assert_equal [r function list] {{library_name test engine LUA functions {{name test description {} flags {}}}}} - - r FUNCTION DELETE test - - r slaveof 127.0.0.1 0 - r debug loadaof - r slaveof no one - assert_equal [r function list] {} - - r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)" - r FUNCTION FLUSH - - r slaveof 127.0.0.1 0 - r debug loadaof - r slaveof no one - assert_equal [r function list] {} - } -} {} {needs:debug external:skip} - -start_server {tags {"scripting"}} { - test {LIBRARIES - test shared function can access default globals} { - r function load {#!lua name=lib1 - local function ping() - return redis.call('ping') - end - redis.register_function( - 'f1', - function(keys, args) - return ping() - end - ) - } - r fcall f1 0 - } {PONG} - - test {LIBRARIES - usage and code sharing} { - r function load REPLACE {#!lua name=lib1 - local function add1(a) - return a + 1 - end - redis.register_function( - 'f1', - function(keys, args) - return add1(1) - end - ) - redis.register_function( - 'f2', - function(keys, args) - return add1(2) - end - ) - } - assert_equal [r fcall f1 0] {2} - assert_equal [r fcall f2 0] {3} - r function list - } {{library_name lib1 engine LUA functions {*}}} - - test {LIBRARIES - test registration failure revert the entire load} { - catch { - r function load replace {#!lua name=lib1 - local function add1(a) - return a + 2 - end - redis.register_function( - 'f1', - function(keys, args) - return add1(1) - end - ) - redis.register_function( - 'f2', - 'not a function' - ) - } - } e - assert_match {*second argument to redis.register_function must be a function*} $e - assert_equal [r fcall f1 0] {2} - assert_equal [r fcall f2 0] {3} - } - - test {LIBRARIES - test registration function name collision} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function( - 'f1', - function(keys, args) - return 1 - end - ) - } - } e - assert_match {*Function f1 already exists*} $e - assert_equal [r fcall f1 0] {2} - assert_equal [r fcall f2 0] {3} - } - - test {LIBRARIES - test registration function name collision on same library} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function( - 'f1', - function(keys, args) - return 1 - end - ) - redis.register_function( - 'f1', - function(keys, args) - return 1 - end - ) - } - } e - set _ $e - } {*Function already exists in the library*} - - test {LIBRARIES - test registration with no argument} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function() - } - } e - set _ $e - } {*wrong number of arguments to redis.register_function*} - - test {LIBRARIES - test registration with only name} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function('f1') - } - } e - set _ $e - } {*calling redis.register_function with a single argument is only applicable to Lua table*} - - test {LIBRARIES - test registration with to many arguments} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function('f1', function() return 1 end, {}, 'description', 'extra arg') - } - } e - set _ $e - } {*wrong number of arguments to redis.register_function*} - - test {LIBRARIES - test registration with no string name} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function(nil, function() return 1 end) - } - } e - set _ $e - } {*first argument to redis.register_function must be a string*} - - test {LIBRARIES - test registration with wrong name format} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function('test\0test', function() return 1 end) - } - } e - set _ $e - } {*Library names can only contain letters, numbers, or underscores(_) and must be at least one character long*} - - test {LIBRARIES - test registration with empty name} { - catch { - r function load replace {#!lua name=lib2 - redis.register_function('', function() return 1 end) - } - } e - set _ $e - } {*Library names can only contain letters, numbers, or underscores(_) and must be at least one character long*} - - test {LIBRARIES - math.random from function load} { - catch { - r function load replace {#!lua name=lib2 - return math.random() - } - } e - set _ $e - } {*attempted to access nonexistent global variable 'math'*} - - test {LIBRARIES - redis.call from function load} { - catch { - r function load replace {#!lua name=lib2 - return redis.call('ping') - } - } e - set _ $e - } {*attempted to access nonexistent global variable 'call'*} - - test {LIBRARIES - redis.setresp from function load} { - catch { - r function load replace {#!lua name=lib2 - return redis.setresp(3) - } - } e - set _ $e - } {*attempted to access nonexistent global variable 'setresp'*} - - test {LIBRARIES - redis.set_repl from function load} { - catch { - r function load replace {#!lua name=lib2 - return redis.set_repl(redis.REPL_NONE) - } - } e - set _ $e - } {*attempted to access nonexistent global variable 'set_repl'*} - - test {LIBRARIES - redis.acl_check_cmd from function load} { - catch { - r function load replace {#!lua name=lib2 - return redis.acl_check_cmd('set','xx',1) - } - } e - set _ $e - } {*attempted to access nonexistent global variable 'acl_check_cmd'*} - - test {LIBRARIES - malicious access test} { - # the 'library' API is not exposed inside a - # function context and the 'redis' API is not - # expose on the library registration context. - # But a malicious user might find a way to hack it - # (as demonstrated in this test). This is why we - # have another level of protection on the C - # code itself and we want to test it and verify - # that it works properly. - r function load replace {#!lua name=lib1 - local lib = redis - lib.register_function('f1', function () - lib.redis = redis - lib.math = math - return {ok='OK'} - end) - - lib.register_function('f2', function () - lib.register_function('f1', function () - lib.redis = redis - lib.math = math - return {ok='OK'} - end) - end) - } - catch {[r fcall f1 0]} e - assert_match {*Attempt to modify a readonly table*} $e - - catch {[r function load {#!lua name=lib2 - redis.math.random() - }]} e - assert_match {*Script attempted to access nonexistent global variable 'math'*} $e - - catch {[r function load {#!lua name=lib2 - redis.redis.call('ping') - }]} e - assert_match {*Script attempted to access nonexistent global variable 'redis'*} $e - - catch {[r fcall f2 0]} e - assert_match {*can only be called on FUNCTION LOAD command*} $e - } - - test {LIBRARIES - delete removed all functions on library} { - r function delete lib1 - r function list - } {} - - test {LIBRARIES - register function inside a function} { - r function load {#!lua name=lib - redis.register_function( - 'f1', - function(keys, args) - redis.register_function( - 'f2', - function(key, args) - return 2 - end - ) - return 1 - end - ) - } - catch {r fcall f1 0} e - set _ $e - } {*attempt to call field 'register_function' (a nil value)*} - - test {LIBRARIES - register library with no functions} { - r function flush - catch { - r function load {#!lua name=lib - return 1 - } - } e - set _ $e - } {*No functions registered*} - - test {LIBRARIES - load timeout} { - catch { - r function load {#!lua name=lib - local a = 1 - while 1 do a = a + 1 end - } - } e - set _ $e - } {*FUNCTION LOAD timeout*} - - test {LIBRARIES - verify global protection on the load run} { - catch { - r function load {#!lua name=lib - a = 1 - } - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test {LIBRARIES - named arguments} { - r function load {#!lua name=lib - redis.register_function{ - function_name='f1', - callback=function() - return 'hello' - end, - description='some desc' - } - } - r function list - } {{library_name lib engine LUA functions {{name f1 description {some desc} flags {}}}}} - - test {LIBRARIES - named arguments, bad function name} { - catch { - r function load replace {#!lua name=lib - redis.register_function{ - function_name=function() return 1 end, - callback=function() - return 'hello' - end, - description='some desc' - } - } - } e - set _ $e - } {*function_name argument given to redis.register_function must be a string*} - - test {LIBRARIES - named arguments, bad callback type} { - catch { - r function load replace {#!lua name=lib - redis.register_function{ - function_name='f1', - callback='bad', - description='some desc' - } - } - } e - set _ $e - } {*callback argument given to redis.register_function must be a function*} - - test {LIBRARIES - named arguments, bad description} { - catch { - r function load replace {#!lua name=lib - redis.register_function{ - function_name='f1', - callback=function() - return 'hello' - end, - description=function() return 1 end - } - } - } e - set _ $e - } {*description argument given to redis.register_function must be a string*} - - test {LIBRARIES - named arguments, unknown argument} { - catch { - r function load replace {#!lua name=lib - redis.register_function{ - function_name='f1', - callback=function() - return 'hello' - end, - description='desc', - some_unknown='unknown' - } - } - } e - set _ $e - } {*unknown argument given to redis.register_function*} - - test {LIBRARIES - named arguments, missing function name} { - catch { - r function load replace {#!lua name=lib - redis.register_function{ - callback=function() - return 'hello' - end, - description='desc' - } - } - } e - set _ $e - } {*redis.register_function must get a function name argument*} - - test {LIBRARIES - named arguments, missing callback} { - catch { - r function load replace {#!lua name=lib - redis.register_function{ - function_name='f1', - description='desc' - } - } - } e - set _ $e - } {*redis.register_function must get a callback argument*} - - test {FUNCTION - test function restore with function name collision} { - r function flush - r function load {#!lua name=lib1 - local function add1(a) - return a + 1 - end - redis.register_function( - 'f1', - function(keys, args) - return add1(1) - end - ) - redis.register_function( - 'f2', - function(keys, args) - return add1(2) - end - ) - redis.register_function( - 'f3', - function(keys, args) - return add1(3) - end - ) - } - set e [r function dump] - r function flush - - # load a library with different name but with the same function name - r function load {#!lua name=lib1 - redis.register_function( - 'f6', - function(keys, args) - return 7 - end - ) - } - r function load {#!lua name=lib2 - local function add1(a) - return a + 1 - end - redis.register_function( - 'f4', - function(keys, args) - return add1(4) - end - ) - redis.register_function( - 'f5', - function(keys, args) - return add1(5) - end - ) - redis.register_function( - 'f3', - function(keys, args) - return add1(3) - end - ) - } - - catch {r function restore $e} error - assert_match {*Library lib1 already exists*} $error - assert_equal [r fcall f3 0] {4} - assert_equal [r fcall f4 0] {5} - assert_equal [r fcall f5 0] {6} - assert_equal [r fcall f6 0] {7} - - catch {r function restore $e replace} error - assert_match {*Function f3 already exists*} $error - assert_equal [r fcall f3 0] {4} - assert_equal [r fcall f4 0] {5} - assert_equal [r fcall f5 0] {6} - assert_equal [r fcall f6 0] {7} - } - - test {FUNCTION - test function list with code} { - r function flush - r function load {#!lua name=library1 - redis.register_function('f6', function(keys, args) return 7 end) - } - r function list withcode - } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}} library_code {*redis.register_function('f6', function(keys, args) return 7 end)*}}} - - test {FUNCTION - test function list with pattern} { - r function load {#!lua name=lib1 - redis.register_function('f7', function(keys, args) return 7 end) - } - r function list libraryname library* - } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}}}} - - test {FUNCTION - test function list wrong argument} { - catch {r function list bad_argument} e - set _ $e - } {*Unknown argument bad_argument*} - - test {FUNCTION - test function list with bad argument to library name} { - catch {r function list libraryname} e - set _ $e - } {*library name argument was not given*} - - test {FUNCTION - test function list withcode multiple times} { - catch {r function list withcode withcode} e - set _ $e - } {*Unknown argument withcode*} - - test {FUNCTION - test function list libraryname multiple times} { - catch {r function list withcode libraryname foo libraryname foo} e - set _ $e - } {*Unknown argument libraryname*} - - test {FUNCTION - verify OOM on function load and function restore} { - r function flush - r function load replace {#!lua name=test - redis.register_function('f1', function() return 1 end) - } - set payload [r function dump] - r config set maxmemory 1 - - r function flush - catch {r function load replace {#!lua name=test - redis.register_function('f1', function() return 1 end) - }} e - assert_match {*command not allowed when used memory*} $e - - r function flush - catch {r function restore $payload} e - assert_match {*command not allowed when used memory*} $e - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test {FUNCTION - verify allow-omm allows running any command} { - r FUNCTION load replace {#!lua name=f1 - redis.register_function{ - function_name='f1', - callback=function() return redis.call('set', 'x', '1') end, - flags={'allow-oom'} - } - } - - r config set maxmemory 1 - - assert_match {OK} [r fcall f1 1 x] - assert_match {1} [r get x] - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} -} - -start_server {tags {"scripting"}} { - test {FUNCTION - wrong flags type named arguments} { - catch {r function load replace {#!lua name=test - redis.register_function{ - function_name = 'f1', - callback = function() return 1 end, - flags = 'bad flags type' - } - }} e - set _ $e - } {*flags argument to redis.register_function must be a table representing function flags*} - - test {FUNCTION - wrong flag type} { - catch {r function load replace {#!lua name=test - redis.register_function{ - function_name = 'f1', - callback = function() return 1 end, - flags = {function() return 1 end} - } - }} e - set _ $e - } {*unknown flag given*} - - test {FUNCTION - unknown flag} { - catch {r function load replace {#!lua name=test - redis.register_function{ - function_name = 'f1', - callback = function() return 1 end, - flags = {'unknown'} - } - }} e - set _ $e - } {*unknown flag given*} - - test {FUNCTION - write script on fcall_ro} { - r function load replace {#!lua name=test - redis.register_function{ - function_name = 'f1', - callback = function() return redis.call('set', 'x', 1) end - } - } - catch {r fcall_ro f1 1 x} e - set _ $e - } {*Can not execute a script with write flag using \*_ro command*} - - test {FUNCTION - write script with no-writes flag} { - r function load replace {#!lua name=test - redis.register_function{ - function_name = 'f1', - callback = function() return redis.call('set', 'x', 1) end, - flags = {'no-writes'} - } - } - catch {r fcall f1 1 x} e - set _ $e - } {*Write commands are not allowed from read-only scripts*} - - test {FUNCTION - deny oom} { - r FUNCTION load replace {#!lua name=test - redis.register_function('f1', function() return redis.call('set', 'x', '1') end) - } - - r config set maxmemory 1 - - catch {[r fcall f1 1 x]} e - assert_match {OOM *when used memory > 'maxmemory'*} $e - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test {FUNCTION - deny oom on no-writes function} { - r FUNCTION load replace {#!lua name=test - redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}} - } - - r config set maxmemory 1 - - assert_equal [r fcall f1 1 k] hello - assert_equal [r fcall_ro f1 1 k] hello - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test {FUNCTION - allow stale} { - r FUNCTION load replace {#!lua name=test - redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}} - redis.register_function{function_name='f2', callback=function() return 'hello' end, flags={'allow-stale', 'no-writes'}} - redis.register_function{function_name='f3', callback=function() return redis.call('get', 'x') end, flags={'allow-stale', 'no-writes'}} - redis.register_function{function_name='f4', callback=function() return redis.call('info', 'server') end, flags={'allow-stale', 'no-writes'}} - } - - r config set replica-serve-stale-data no - r replicaof 127.0.0.1 1 - - catch {[r fcall f1 0]} e - assert_match {MASTERDOWN *} $e - - assert_equal {hello} [r fcall f2 0] - - catch {[r fcall f3 1 x]} e - assert_match {ERR *Can not execute the command on a stale replica*} $e - - assert_match {*redis_version*} [r fcall f4 0] - - r replicaof no one - r config set replica-serve-stale-data yes - set _ {} - } {} {external:skip} - - test {FUNCTION - redis version api} { - r FUNCTION load replace {#!lua name=test - local version = redis.REDIS_VERSION_NUM - - redis.register_function{function_name='get_version_v1', callback=function() - return string.format('%s.%s.%s', - bit.band(bit.rshift(version, 16), 0x000000ff), - bit.band(bit.rshift(version, 8), 0x000000ff), - bit.band(version, 0x000000ff)) - end} - redis.register_function{function_name='get_version_v2', callback=function() return redis.REDIS_VERSION end} - } - - catch {[r fcall f1 0]} e - assert_equal [r fcall get_version_v1 0] [r fcall get_version_v2 0] - } - - test {FUNCTION - function stats} { - r FUNCTION FLUSH - - r FUNCTION load {#!lua name=test1 - redis.register_function('f1', function() return 1 end) - redis.register_function('f2', function() return 1 end) - } - - r FUNCTION load {#!lua name=test2 - redis.register_function('f3', function() return 1 end) - } - - r function stats - } {running_script {} engines {LUA {libraries_count 2 functions_count 3}}} - - test {FUNCTION - function stats reloaded correctly from rdb} { - r debug reload - r function stats - } {running_script {} engines {LUA {libraries_count 2 functions_count 3}}} {needs:debug} - - test {FUNCTION - function stats delete library} { - r function delete test1 - r function stats - } {running_script {} engines {LUA {libraries_count 1 functions_count 1}}} - - test {FUNCTION - test function stats on loading failure} { - r FUNCTION FLUSH - - r FUNCTION load {#!lua name=test1 - redis.register_function('f1', function() return 1 end) - redis.register_function('f2', function() return 1 end) - } - - catch {r FUNCTION load {#!lua name=test1 - redis.register_function('f3', function() return 1 end) - }} e - assert_match "*Library 'test1' already exists*" $e - - - r function stats - } {running_script {} engines {LUA {libraries_count 1 functions_count 2}}} - - test {FUNCTION - function stats cleaned after flush} { - r function flush - r function stats - } {running_script {} engines {LUA {libraries_count 0 functions_count 0}}} - - test {FUNCTION - function test empty engine} { - catch {r function load replace {#! name=test - redis.register_function('foo', function() return 1 end) - }} e - set _ $e - } {ERR Engine '' not found} - - test {FUNCTION - function test unknown metadata value} { - catch {r function load replace {#!lua name=test foo=bar - redis.register_function('foo', function() return 1 end) - }} e - set _ $e - } {ERR Invalid metadata value given: foo=bar} - - test {FUNCTION - function test no name} { - catch {r function load replace {#!lua - redis.register_function('foo', function() return 1 end) - }} e - set _ $e - } {ERR Library name was not given} - - test {FUNCTION - function test multiple names} { - catch {r function load replace {#!lua name=foo name=bar - redis.register_function('foo', function() return 1 end) - }} e - set _ $e - } {ERR Invalid metadata value, name argument was given multiple times} - - test {FUNCTION - function test name with quotes} { - r function load replace {#!lua name="foo" - redis.register_function('foo', function() return 1 end) - } - } {foo} - - test {FUNCTION - trick global protection 1} { - r FUNCTION FLUSH - - r FUNCTION load {#!lua name=test1 - redis.register_function('f1', function() - mt = getmetatable(_G) - original_globals = mt.__index - original_globals['redis'] = function() return 1 end - end) - } - - catch {[r fcall f1 0]} e - set _ $e - } {*Attempt to modify a readonly table*} - - test {FUNCTION - test getmetatable on script load} { - r FUNCTION FLUSH - - catch { - r FUNCTION load {#!lua name=test1 - mt = getmetatable(_G) - } - } e - - set _ $e - } {*Script attempted to access nonexistent global variable 'getmetatable'*} - -} diff --git a/examples/redis-unstable/tests/unit/geo.tcl b/examples/redis-unstable/tests/unit/geo.tcl deleted file mode 100644 index 6175329..0000000 --- a/examples/redis-unstable/tests/unit/geo.tcl +++ /dev/null @@ -1,768 +0,0 @@ -# Helper functions to simulate search-in-radius in the Tcl side in order to -# verify the Redis implementation with a fuzzy test. -proc geo_degrad deg {expr {$deg*(atan(1)*8/360)}} -proc geo_raddeg rad {expr {$rad/(atan(1)*8/360)}} - -proc geo_distance {lon1d lat1d lon2d lat2d} { - set lon1r [geo_degrad $lon1d] - set lat1r [geo_degrad $lat1d] - set lon2r [geo_degrad $lon2d] - set lat2r [geo_degrad $lat2d] - set v [expr {sin(($lon2r - $lon1r) / 2)}] - set u [expr {sin(($lat2r - $lat1r) / 2)}] - expr {2.0 * 6372797.560856 * \ - asin(sqrt($u * $u + cos($lat1r) * cos($lat2r) * $v * $v))} -} - -proc geo_random_point {lonvar latvar} { - upvar 1 $lonvar lon - upvar 1 $latvar lat - # Note that the actual latitude limit should be -85 to +85, we restrict - # the test to -70 to +70 since in this range the algorithm is more precise - # while outside this range occasionally some element may be missing. - set lon [expr {-180 + rand()*360}] - set lat [expr {-70 + rand()*140}] -} - -# Return elements non common to both the lists. -# This code is from http://wiki.tcl.tk/15489 -proc compare_lists {List1 List2} { - set DiffList {} - foreach Item $List1 { - if {[lsearch -exact $List2 $Item] == -1} { - lappend DiffList $Item - } - } - foreach Item $List2 { - if {[lsearch -exact $List1 $Item] == -1} { - if {[lsearch -exact $DiffList $Item] == -1} { - lappend DiffList $Item - } - } - } - return $DiffList -} - -# return true If a point in circle. -# search_lon and search_lat define the center of the circle, -# and lon, lat define the point being searched. -proc pointInCircle {radius_km lon lat search_lon search_lat} { - set radius_m [expr {$radius_km*1000}] - set distance [geo_distance $lon $lat $search_lon $search_lat] - if {$distance < $radius_m} { - return true - } - return false -} - -# return true If a point in rectangle. -# search_lon and search_lat define the center of the rectangle, -# and lon, lat define the point being searched. -# error: can adjust the width and height of the rectangle according to the error -proc pointInRectangle {width_km height_km lon lat search_lon search_lat error} { - set width_m [expr {$width_km*1000*$error/2}] - set height_m [expr {$height_km*1000*$error/2}] - set lon_distance [geo_distance $lon $lat $search_lon $lat] - set lat_distance [geo_distance $lon $lat $lon $search_lat] - - if {$lon_distance > $width_m || $lat_distance > $height_m} { - return false - } - return true -} - -proc verify_geo_edge_response_bylonlat {expected_response expected_store_response} { - catch {r georadius src{t} 1 1 1 km} response - assert_match $expected_response $response - - catch {r georadius src{t} 1 1 1 km store dest{t}} response - assert_match $expected_store_response $response - - catch {r geosearch src{t} fromlonlat 0 0 byradius 1 km} response - assert_match $expected_response $response - - catch {r geosearchstore dest{t} src{t} fromlonlat 0 0 byradius 1 km} response - assert_match $expected_store_response $response -} - -proc verify_geo_edge_response_bymember {expected_response expected_store_response} { - catch {r georadiusbymember src{t} member 1 km} response - assert_match $expected_response $response - - catch {r georadiusbymember src{t} member 1 km store dest{t}} response - assert_match $expected_store_response $response - - catch {r geosearch src{t} frommember member bybox 1 1 km} response - assert_match $expected_response $response - - catch {r geosearchstore dest{t} src{t} frommember member bybox 1 1 m} response - assert_match $expected_store_response $response -} - -proc verify_geo_edge_response_generic {expected_response} { - catch {r geodist src{t} member 1 km} response - assert_match $expected_response $response - - catch {r geohash src{t} member} response - assert_match $expected_response $response - - catch {r geopos src{t} member} response - assert_match $expected_response $response -} - - -# The following list represents sets of random seed, search position -# and radius that caused bugs in the past. It is used by the randomized -# test later as a starting point. When the regression vectors are scanned -# the code reverts to using random data. -# -# The format is: seed km lon lat -set regression_vectors { - {1482225976969 7083 81.634948934258375 30.561509253718668} - {1482340074151 5416 -70.863281847379767 -46.347003465679947} - {1499014685896 6064 -89.818768962202014 -40.463868561416803} - {1412 156 149.29737817929004 15.95807862745508} - {441574 143 59.235461856813856 66.269555127373678} - {160645 187 -101.88575239939883 49.061997951502917} - {750269 154 -90.187939661642517 66.615930412251487} - {342880 145 163.03472387745728 64.012747720821181} - {729955 143 137.86663517256579 63.986745399416776} - {939895 151 59.149620271823181 65.204186651485145} - {1412 156 149.29737817929004 15.95807862745508} - {564862 149 84.062063109158544 -65.685403922426232} - {1546032440391 16751 -1.8175081637769495 20.665668878082954} -} -set rv_idx 0 - -start_server {tags {"geo"}} { - test {GEO with wrong type src key} { - r set src{t} wrong_type - - verify_geo_edge_response_bylonlat "WRONGTYPE*" "WRONGTYPE*" - verify_geo_edge_response_bymember "WRONGTYPE*" "WRONGTYPE*" - verify_geo_edge_response_generic "WRONGTYPE*" - } - - test {GEO with non existing src key} { - r del src{t} - - verify_geo_edge_response_bylonlat {} 0 - verify_geo_edge_response_bymember {} 0 - } - - test {GEO BYLONLAT with empty search} { - r del src{t} - r geoadd src{t} 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" - - verify_geo_edge_response_bylonlat {} 0 - } - - test {GEO BYMEMBER with non existing member} { - r del src{t} - r geoadd src{t} 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" - - verify_geo_edge_response_bymember "ERR*" "ERR*" - } - - test {GEOADD create} { - r geoadd nyc -73.9454966 40.747533 "lic market" - } {1} - - test {GEOADD update} { - r geoadd nyc -73.9454966 40.747533 "lic market" - } {0} - - test {GEOADD update with CH option} { - assert_equal 1 [r geoadd nyc CH 40.747533 -73.9454966 "lic market"] - lassign [lindex [r geopos nyc "lic market"] 0] x1 y1 - assert {abs($x1) - 40.747 < 0.001} - assert {abs($y1) - 73.945 < 0.001} - } {} - - test {GEOADD update with NX option} { - assert_equal 0 [r geoadd nyc NX -73.9454966 40.747533 "lic market"] - lassign [lindex [r geopos nyc "lic market"] 0] x1 y1 - assert {abs($x1) - 40.747 < 0.001} - assert {abs($y1) - 73.945 < 0.001} - } {} - - test {GEOADD update with XX option} { - assert_equal 0 [r geoadd nyc XX -83.9454966 40.747533 "lic market"] - lassign [lindex [r geopos nyc "lic market"] 0] x1 y1 - assert {abs($x1) - 83.945 < 0.001} - assert {abs($y1) - 40.747 < 0.001} - } {} - - test {GEOADD update with CH NX option} { - r geoadd nyc CH NX -73.9454966 40.747533 "lic market" - } {0} - - test {GEOADD update with CH XX option} { - r geoadd nyc CH XX -73.9454966 40.747533 "lic market" - } {1} - - test {GEOADD update with XX NX option will return syntax error} { - catch { - r geoadd nyc xx nx -73.9454966 40.747533 "lic market" - } err - set err - } {ERR *syntax*} - - test {GEOADD update with invalid option} { - catch { - r geoadd nyc ch xx foo -73.9454966 40.747533 "lic market" - } err - set err - } {ERR *syntax*} - - test {GEOADD invalid coordinates} { - catch { - r geoadd nyc -73.9454966 40.747533 "lic market" \ - foo bar "luck market" - } err - set err - } {*valid*} - - test {GEOADD multi add} { - r geoadd nyc -73.9733487 40.7648057 "central park n/q/r" -73.9903085 40.7362513 "union square" -74.0131604 40.7126674 "wtc one" -73.7858139 40.6428986 "jfk" -73.9375699 40.7498929 "q4" -73.9564142 40.7480973 4545 - } {6} - - test {Check geoset values} { - r zrange nyc 0 -1 withscores - } {{wtc one} 1791873972053020 {union square} 1791875485187452 {central park n/q/r} 1791875761332224 4545 1791875796750882 {lic market} 1791875804419201 q4 1791875830079666 jfk 1791895905559723} - - test {GEORADIUS simple (sorted)} { - r georadius nyc -73.9798091 40.7598464 3 km asc - } {{central park n/q/r} 4545 {union square}} - - test {GEORADIUS_RO simple (sorted)} { - r georadius_ro nyc -73.9798091 40.7598464 3 km asc - } {{central park n/q/r} 4545 {union square}} - - test {GEOSEARCH simple (sorted)} { - r geosearch nyc fromlonlat -73.9798091 40.7598464 bybox 6 6 km asc - } {{central park n/q/r} 4545 {union square} {lic market}} - - test {GEOSEARCH FROMLONLAT and FROMMEMBER cannot exist at the same time} { - catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 frommember xxx bybox 6 6 km asc} e - set e - } {ERR *syntax*} - - test {GEOSEARCH FROMLONLAT and FROMMEMBER one must exist} { - catch {r geosearch nyc bybox 3 3 km asc desc withhash withdist withcoord} e - set e - } {ERR *exactly one of FROMMEMBER or FROMLONLAT*} - - test {GEOSEARCH BYRADIUS and BYBOX cannot exist at the same time} { - catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 byradius 3 km bybox 3 3 km asc} e - set e - } {ERR *syntax*} - - test {GEOSEARCH BYRADIUS and BYBOX one must exist} { - catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 asc desc withhash withdist withcoord} e - set e - } {ERR *exactly one of BYRADIUS and BYBOX*} - - test {GEOSEARCH with STOREDIST option} { - catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 bybox 6 6 km asc storedist} e - set e - } {ERR *syntax*} - - test {GEORADIUS withdist (sorted)} { - r georadius nyc -73.9798091 40.7598464 3 km withdist asc - } {{{central park n/q/r} 0.7750} {4545 2.3651} {{union square} 2.7697}} - - test {GEOSEARCH withdist (sorted)} { - r geosearch nyc fromlonlat -73.9798091 40.7598464 bybox 6 6 km withdist asc - } {{{central park n/q/r} 0.7750} {4545 2.3651} {{union square} 2.7697} {{lic market} 3.1991}} - - test {GEORADIUS with COUNT} { - r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 - } {{central park n/q/r} 4545 {union square}} - - test {GEORADIUS with multiple WITH* tokens} { - assert_match {{{central park n/q/r} 1791875761332224 {-73.97334* 40.76480*}} {4545 1791875796750882 {-73.95641* 40.74809*}}} [r georadius nyc -73.9798091 40.7598464 10 km WITHCOORD WITHHASH COUNT 2] - assert_match {{{central park n/q/r} 1791875761332224 {-73.97334* 40.76480*}} {4545 1791875796750882 {-73.95641* 40.74809*}}} [r georadius nyc -73.9798091 40.7598464 10 km WITHHASH WITHCOORD COUNT 2] - assert_match {{{central park n/q/r} 0.7750 1791875761332224 {-73.97334* 40.76480*}} {4545 2.3651 1791875796750882 {-73.95641* 40.74809*}}} [r georadius nyc -73.9798091 40.7598464 10 km WITHDIST WITHHASH WITHCOORD COUNT 2] - } - - test {GEORADIUS with ANY not sorted by default} { - r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 ANY - } {{wtc one} {union square} {central park n/q/r}} - - test {GEORADIUS with ANY sorted by ASC} { - r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 ANY ASC - } {{central park n/q/r} {union square} {wtc one}} - - test {GEORADIUS with ANY but no COUNT} { - catch {r georadius nyc -73.9798091 40.7598464 10 km ANY ASC} e - set e - } {ERR *ANY*requires*COUNT*} - - test {GEORADIUS with COUNT but missing integer argument} { - catch {r georadius nyc -73.9798091 40.7598464 10 km COUNT} e - set e - } {ERR *syntax*} - - test {GEORADIUS with COUNT DESC} { - r georadius nyc -73.9798091 40.7598464 10 km COUNT 2 DESC - } {{wtc one} q4} - - test {GEORADIUS HUGE, issue #2767} { - r geoadd users -47.271613776683807 -54.534504198047678 user_000000 - llength [r GEORADIUS users 0 0 50000 km WITHCOORD] - } {1} - - test {GEORADIUSBYMEMBER simple (sorted)} { - r georadiusbymember nyc "wtc one" 7 km - } {{wtc one} {union square} {central park n/q/r} 4545 {lic market}} - - test {GEORADIUSBYMEMBER_RO simple (sorted)} { - r georadiusbymember_ro nyc "wtc one" 7 km - } {{wtc one} {union square} {central park n/q/r} 4545 {lic market}} - - test {GEORADIUSBYMEMBER search areas contain satisfied points in oblique direction} { - r del k1 - - r geoadd k1 -0.15307903289794921875 85 n1 0.3515625 85.00019260486917005437 n2 - set ret1 [r GEORADIUSBYMEMBER k1 n1 4891.94 m] - assert_equal $ret1 {n1 n2} - - r zrem k1 n1 n2 - r geoadd k1 -4.95211958885192871094 85 n3 11.25 85.0511 n4 - set ret2 [r GEORADIUSBYMEMBER k1 n3 156544 m] - assert_equal $ret2 {n3 n4} - - r zrem k1 n3 n4 - r geoadd k1 -45 65.50900022111811438208 n5 90 85.0511 n6 - set ret3 [r GEORADIUSBYMEMBER k1 n5 5009431 m] - assert_equal $ret3 {n5 n6} - } - - test {GEORADIUSBYMEMBER crossing pole search} { - r del k1 - r geoadd k1 45 65 n1 -135 85.05 n2 - set ret [r GEORADIUSBYMEMBER k1 n1 5009431 m] - assert_equal $ret {n1 n2} - } - - test {GEOSEARCH FROMMEMBER simple (sorted)} { - r geosearch nyc frommember "wtc one" bybox 14 14 km - } {{wtc one} {union square} {central park n/q/r} 4545 {lic market} q4} - - test {GEOSEARCH vs GEORADIUS} { - r del Sicily - r geoadd Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" - r geoadd Sicily 12.758489 38.788135 "edge1" 17.241510 38.788135 "eage2" - set ret1 [r georadius Sicily 15 37 200 km asc] - assert_equal $ret1 {Catania Palermo} - set ret2 [r geosearch Sicily fromlonlat 15 37 bybox 400 400 km asc] - assert_equal $ret2 {Catania Palermo eage2 edge1} - } - - test {GEOSEARCH non square, long and narrow} { - r del Sicily - r geoadd Sicily 12.75 36.995 "test1" - r geoadd Sicily 12.75 36.50 "test2" - r geoadd Sicily 13.00 36.50 "test3" - # box height=2km width=400km - set ret1 [r geosearch Sicily fromlonlat 15 37 bybox 400 2 km] - assert_equal $ret1 {test1} - - # Add a western Hemisphere point - r geoadd Sicily -1 37.00 "test3" - set ret2 [r geosearch Sicily fromlonlat 15 37 bybox 3000 2 km asc] - assert_equal $ret2 {test1 test3} - } - - test {GEOSEARCH corner point test} { - r del Sicily - r geoadd Sicily 12.758489 38.788135 edge1 17.241510 38.788135 edge2 17.250000 35.202000 edge3 12.750000 35.202000 edge4 12.748489955781654 37 edge5 15 38.798135872540925 edge6 17.251510044218346 37 edge7 15 35.201864127459075 edge8 12.692799634687903 38.798135872540925 corner1 12.692799634687903 38.798135872540925 corner2 17.200560937451133 35.201864127459075 corner3 12.799439062548865 35.201864127459075 corner4 - set ret [lsort [r geosearch Sicily fromlonlat 15 37 bybox 400 400 km asc]] - assert_equal $ret {edge1 edge2 edge5 edge7} - } - - test {GEORADIUSBYMEMBER withdist (sorted)} { - r georadiusbymember nyc "wtc one" 7 km withdist - } {{{wtc one} 0.0000} {{union square} 3.2544} {{central park n/q/r} 6.7000} {4545 6.1975} {{lic market} 6.8969}} - - test {GEOHASH is able to return geohash strings} { - # Example from Wikipedia. - r del points - r geoadd points -5.6 42.6 test - lindex [r geohash points test] 0 - } {ezs42e44yx0} - - test {GEOHASH with only key as argument} { - r del points - r geoadd points 10 20 a 30 40 b - set result [r geohash points] - assert {$result eq {}} - } - - test {GEOPOS simple} { - r del points - r geoadd points 10 20 a 30 40 b - lassign [lindex [r geopos points a b] 0] x1 y1 - lassign [lindex [r geopos points a b] 1] x2 y2 - assert {abs($x1 - 10) < 0.001} - assert {abs($y1 - 20) < 0.001} - assert {abs($x2 - 30) < 0.001} - assert {abs($y2 - 40) < 0.001} - } - - test {GEOPOS missing element} { - r del points - r geoadd points 10 20 a 30 40 b - lindex [r geopos points a x b] 1 - } {} - - test {GEOPOS with only key as argument} { - r del points - r geoadd points 10 20 a 30 40 b - set result [r geopos points] - assert {$result eq {}} - } - - test {GEODIST simple & unit} { - r del points - r geoadd points 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - set m [r geodist points Palermo Catania] - assert {$m > 166274 && $m < 166275} - set km [r geodist points Palermo Catania km] - assert {$km > 166.2 && $km < 166.3} - set dist [r geodist points Palermo Palermo] - assert {$dist eq 0.0000} - } - - test {GEODIST missing elements} { - r del points - r geoadd points 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - set m [r geodist points Palermo Agrigento] - assert {$m eq {}} - set m [r geodist points Ragusa Agrigento] - assert {$m eq {}} - set m [r geodist empty_key Palermo Catania] - assert {$m eq {}} - } - - test {GEORADIUS STORE option: syntax error} { - r del points{t} - r geoadd points{t} 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - catch {r georadius points{t} 13.361389 38.115556 50 km store} e - set e - } {*ERR*syntax*} - - test {GEOSEARCHSTORE STORE option: syntax error} { - catch {r geosearchstore abc{t} points{t} fromlonlat 13.361389 38.115556 byradius 50 km store abc{t}} e - set e - } {*ERR*syntax*} - - test {GEORANGE STORE option: incompatible options} { - r del points{t} - r geoadd points{t} 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - catch {r georadius points{t} 13.361389 38.115556 50 km store points2{t} withdist} e - assert_match {*ERR*} $e - catch {r georadius points{t} 13.361389 38.115556 50 km store points2{t} withhash} e - assert_match {*ERR*} $e - catch {r georadius points{t} 13.361389 38.115556 50 km store points2{t} withcoords} e - assert_match {*ERR*} $e - } - - test {GEORANGE STORE option: plain usage} { - r del points{t} - r geoadd points{t} 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - r georadius points{t} 13.361389 38.115556 500 km store points2{t} - assert_equal [r zrange points{t} 0 -1] [r zrange points2{t} 0 -1] - } - - test {GEORADIUSBYMEMBER STORE/STOREDIST option: plain usage} { - r del points{t} - r geoadd points{t} 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" - - r georadiusbymember points{t} Palermo 500 km store points2{t} - assert_equal {Palermo Catania} [r zrange points2{t} 0 -1] - - r georadiusbymember points{t} Catania 500 km storedist points2{t} - assert_equal {Catania Palermo} [r zrange points2{t} 0 -1] - - set res [r zrange points2{t} 0 -1 withscores] - assert {[lindex $res 1] < 1} - assert {[lindex $res 3] > 166} - } - - test {GEOSEARCHSTORE STORE option: plain usage} { - r geosearchstore points2{t} points{t} fromlonlat 13.361389 38.115556 byradius 500 km - assert_equal [r zrange points{t} 0 -1] [r zrange points2{t} 0 -1] - } - - test {GEORANGE STOREDIST option: plain usage} { - r del points{t} - r geoadd points{t} 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - r georadius points{t} 13.361389 38.115556 500 km storedist points2{t} - set res [r zrange points2{t} 0 -1 withscores] - assert {[lindex $res 1] < 1} - assert {[lindex $res 3] > 166} - assert {[lindex $res 3] < 167} - } - - test {GEOSEARCHSTORE STOREDIST option: plain usage} { - r geosearchstore points2{t} points{t} fromlonlat 13.361389 38.115556 byradius 500 km storedist - set res [r zrange points2{t} 0 -1 withscores] - assert {[lindex $res 1] < 1} - assert {[lindex $res 3] > 166} - assert {[lindex $res 3] < 167} - } - - test {GEORANGE STOREDIST option: COUNT ASC and DESC} { - r del points{t} - r geoadd points{t} 13.361389 38.115556 "Palermo" \ - 15.087269 37.502669 "Catania" - r georadius points{t} 13.361389 38.115556 500 km storedist points2{t} asc count 1 - assert {[r zcard points2{t}] == 1} - set res [r zrange points2{t} 0 -1 withscores] - assert {[lindex $res 0] eq "Palermo"} - - r georadius points{t} 13.361389 38.115556 500 km storedist points2{t} desc count 1 - assert {[r zcard points2{t}] == 1} - set res [r zrange points2{t} 0 -1 withscores] - assert {[lindex $res 0] eq "Catania"} - } - - test {GEOSEARCH the box spans -180° or 180°} { - r del points - r geoadd points 179.5 36 point1 - r geoadd points -179.5 36 point2 - assert_equal {point1 point2} [r geosearch points fromlonlat 179 37 bybox 400 400 km asc] - assert_equal {point2 point1} [r geosearch points fromlonlat -179 37 bybox 400 400 km asc] - } - - test {GEOSEARCH with small distance} { - r del points - r geoadd points -122.407107 37.794300 1 - r geoadd points -122.227336 37.794300 2 - assert_equal {{1 0.0001} {2 9.8182}} [r GEORADIUS points -122.407107 37.794300 30 mi ASC WITHDIST] - } - - foreach {type} {byradius bybox} { - test "GEOSEARCH fuzzy test - $type" { - if {$::accurate} { set attempt 300 } else { set attempt 30 } - while {[incr attempt -1]} { - set rv [lindex $regression_vectors $rv_idx] - incr rv_idx - - set radius_km 0; set width_km 0; set height_km 0 - unset -nocomplain debuginfo - set srand_seed [clock milliseconds] - if {$rv ne {}} {set srand_seed [lindex $rv 0]} - lappend debuginfo "srand_seed is $srand_seed" - expr {srand($srand_seed)} ; # If you need a reproducible run - r del mypoints - - if {[randomInt 10] == 0} { - # From time to time use very big radiuses - if {$type == "byradius"} { - set radius_km [expr {[randomInt 5000]+10}] - } elseif {$type == "bybox"} { - set width_km [expr {[randomInt 5000]+10}] - set height_km [expr {[randomInt 5000]+10}] - } - } else { - # Normally use a few - ~200km radiuses to stress - # test the code the most in edge cases. - if {$type == "byradius"} { - set radius_km [expr {[randomInt 200]+10}] - } elseif {$type == "bybox"} { - set width_km [expr {[randomInt 200]+10}] - set height_km [expr {[randomInt 200]+10}] - } - } - if {$rv ne {}} { - set radius_km [lindex $rv 1] - set width_km [lindex $rv 1] - set height_km [lindex $rv 1] - } - geo_random_point search_lon search_lat - if {$rv ne {}} { - set search_lon [lindex $rv 2] - set search_lat [lindex $rv 3] - } - lappend debuginfo "Search area: $search_lon,$search_lat $radius_km $width_km $height_km km" - set tcl_result {} - set argv {} - for {set j 0} {$j < 20000} {incr j} { - geo_random_point lon lat - lappend argv $lon $lat "place:$j" - if {$type == "byradius"} { - if {[pointInCircle $radius_km $lon $lat $search_lon $search_lat]} { - lappend tcl_result "place:$j" - } - } elseif {$type == "bybox"} { - if {[pointInRectangle $width_km $height_km $lon $lat $search_lon $search_lat 1]} { - lappend tcl_result "place:$j" - } - } - lappend debuginfo "place:$j $lon $lat" - } - r geoadd mypoints {*}$argv - if {$type == "byradius"} { - set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat byradius $radius_km km]] - } elseif {$type == "bybox"} { - set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_km $height_km km]] - } - set res2 [lsort $tcl_result] - set test_result OK - - if {$res != $res2} { - set rounding_errors 0 - set diff [compare_lists $res $res2] - foreach place $diff { - lassign [lindex [r geopos mypoints $place] 0] lon lat - set mydist [geo_distance $lon $lat $search_lon $search_lat] - set mydist [expr $mydist/1000] - if {$type == "byradius"} { - if {($mydist / $radius_km) > 0.999} { - incr rounding_errors - continue - } - if {$mydist < [expr {$radius_km*1000}]} { - # This is a false positive for redis since given the - # same points the higher precision calculation provided - # by TCL shows the point within range - incr rounding_errors - continue - } - } elseif {$type == "bybox"} { - # we add 0.1% error for floating point calculation error - if {[pointInRectangle $width_km $height_km $lon $lat $search_lon $search_lat 1.001]} { - incr rounding_errors - continue - } - } - } - - # Make sure this is a real error and not a rounidng issue. - if {[llength $diff] == $rounding_errors} { - set res $res2; # Error silenced - } - } - - if {$res != $res2} { - set diff [compare_lists $res $res2] - puts "*** Possible problem in GEO radius query ***" - puts "Redis: $res" - puts "Tcl : $res2" - puts "Diff : $diff" - puts [join $debuginfo "\n"] - foreach place $diff { - if {[lsearch -exact $res2 $place] != -1} { - set where "(only in Tcl)" - } else { - set where "(only in Redis)" - } - lassign [lindex [r geopos mypoints $place] 0] lon lat - set mydist [geo_distance $lon $lat $search_lon $search_lat] - set mydist [expr $mydist/1000] - puts "$place -> [r geopos mypoints $place] $mydist $where" - } - set test_result FAIL - } - unset -nocomplain debuginfo - if {$test_result ne {OK}} break - } - set test_result - } {OK} - } - - test {GEOSEARCH box edges fuzzy test} { - if {$::accurate} { set attempt 300 } else { set attempt 30 } - while {[incr attempt -1]} { - unset -nocomplain debuginfo - set srand_seed [clock milliseconds] - lappend debuginfo "srand_seed is $srand_seed" - expr {srand($srand_seed)} ; # If you need a reproducible run - r del mypoints - - geo_random_point search_lon search_lat - set width_m [expr {[randomInt 10000]+10}] - set height_m [expr {[randomInt 10000]+10}] - set lat_delta [geo_raddeg [expr {$height_m/2/6372797.560856}]] - set long_delta_top [geo_raddeg [expr {$width_m/2/6372797.560856/cos([geo_degrad [expr {$search_lat+$lat_delta}]])}]] - set long_delta_middle [geo_raddeg [expr {$width_m/2/6372797.560856/cos([geo_degrad $search_lat])}]] - set long_delta_bottom [geo_raddeg [expr {$width_m/2/6372797.560856/cos([geo_degrad [expr {$search_lat-$lat_delta}]])}]] - - # Total of 8 points are generated, which are located at each vertex and the center of each side - set points(north) [list $search_lon [expr {$search_lat+$lat_delta}]] - set points(south) [list $search_lon [expr {$search_lat-$lat_delta}]] - set points(east) [list [expr {$search_lon+$long_delta_middle}] $search_lat] - set points(west) [list [expr {$search_lon-$long_delta_middle}] $search_lat] - set points(north_east) [list [expr {$search_lon+$long_delta_top}] [expr {$search_lat+$lat_delta}]] - set points(north_west) [list [expr {$search_lon-$long_delta_top}] [expr {$search_lat+$lat_delta}]] - set points(south_east) [list [expr {$search_lon+$long_delta_bottom}] [expr {$search_lat-$lat_delta}]] - set points(south_west) [list [expr {$search_lon-$long_delta_bottom}] [expr {$search_lat-$lat_delta}]] - - lappend debuginfo "Search area: geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_m $height_m m" - set tcl_result {} - foreach name [array names points] { - set x [lindex $points($name) 0] - set y [lindex $points($name) 1] - # If longitude crosses -180° or 180°, we need to convert it. - # latitude doesn't have this problem, because it's scope is -70~70, see geo_random_point - if {$x > 180} { - set x [expr {$x-360}] - } elseif {$x < -180} { - set x [expr {$x+360}] - } - r geoadd mypoints $x $y place:$name - lappend tcl_result "place:$name" - lappend debuginfo "geoadd mypoints $x $y place:$name" - } - - set res2 [lsort $tcl_result] - - # make the box larger by two meter in each direction to put the coordinate slightly inside the box. - set height_new [expr {$height_m+4}] - set width_new [expr {$width_m+4}] - set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]] - if {$res != $res2} { - set diff [compare_lists $res $res2] - lappend debuginfo "res: $res, res2: $res2, diff: $diff" - fail "place should be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new" - } - - # The width decreases and the height increases. Only north and south are found - set width_new [expr {$width_m-4}] - set height_new [expr {$height_m+4}] - set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]] - if {$res != {place:north place:south}} { - lappend debuginfo "res: $res" - fail "place should not be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new" - } - - # The width increases and the height decreases. Only ease and west are found - set width_new [expr {$width_m+4}] - set height_new [expr {$height_m-4}] - set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]] - if {$res != {place:east place:west}} { - lappend debuginfo "res: $res" - fail "place should not be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new" - } - - # make the box smaller by two meter in each direction to put the coordinate slightly outside the box. - set height_new [expr {$height_m-4}] - set width_new [expr {$width_m-4}] - set res [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m] - if {$res != ""} { - lappend debuginfo "res: $res" - fail "place should not be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new" - } - unset -nocomplain debuginfo - } - } -} diff --git a/examples/redis-unstable/tests/unit/hotkeys.tcl b/examples/redis-unstable/tests/unit/hotkeys.tcl deleted file mode 100644 index 2edf396..0000000 --- a/examples/redis-unstable/tests/unit/hotkeys.tcl +++ /dev/null @@ -1,449 +0,0 @@ -# Helper function to convert flat array response to dict -proc hotkeys_array_to_dict {arr} { - set result {} - for {set i 0} {$i < [llength $arr]} {incr i 2} { - set key [lindex $arr $i] - set val [lindex $arr [expr {$i + 1}]] - dict set result $key $val - } - return $result -} - -start_server {tags {"hotkeys"}} { - test {HOTKEYS START - METRICS required} { - r hello 3 - catch {r hotkeys start} err - assert_match "*METRICS parameter is required*" $err - } {} {resp3} - - test {HOTKEYS START - METRICS with CPU only} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 CPU] - r set key1 value1 - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - assert [dict exists $result "total-cpu-time-user-ms"] - assert [dict exists $result "total-cpu-time-sys-ms"] - assert [dict exists $result "by-cpu-time"] - assert {![dict exists $result "total-net-bytes"]} - assert {![dict exists $result "by-net-bytes"]} - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - METRICS with NET only} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 NET] - r set key1 value1 - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - assert [dict exists $result "total-net-bytes"] - assert [dict exists $result "by-net-bytes"] - assert {![dict exists $result "total-cpu-time-user-ms"]} - assert {![dict exists $result "total-cpu-time-sys-ms"]} - assert {![dict exists $result "by-cpu-time"]} - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - METRICS with both CPU and NET} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET] - r set key1 value1 - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - assert [dict exists $result "total-cpu-time-user-ms"] - assert [dict exists $result "total-cpu-time-sys-ms"] - assert [dict exists $result "by-cpu-time"] - assert [dict exists $result "total-net-bytes"] - assert [dict exists $result "by-net-bytes"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - Error: session already started} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 CPU] - catch {r hotkeys start METRICS 1 NET} err - assert_match "*hotkey tracking session already in progress*" $err - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - Error: invalid METRICS count} { - r hello 3 - catch {r hotkeys start METRICS 0} err - assert_match "*METRICS count*" $err - catch {r hotkeys start METRICS -1} err - assert_match "*METRICS count*" $err - } {} {resp3} - - test {HOTKEYS START - Error: METRICS count mismatch} { - r hello 3 - catch {r hotkeys start METRICS 2 CPU} err - assert_match "*METRICS count does not match number of metric types provided*" $err - catch {r hotkeys start METRICS 1 CPU NET} err - assert_match "*syntax error*" $err - catch {r hotkeys start METRICS 3 CPU NET} err - assert_match "*METRICS count*" $err - } {} {resp3} - - test {HOTKEYS START - Error: METRICS invalid metrics} { - r hello 3 - catch {r hotkeys start METRICS 1 GPU} err - assert_match "*METRICS no valid metrics*" $err - catch {r hotkeys start METRICS 2 GPU NYET} err - assert_match "*METRICS no valid metrics*" $err - - # Allowing invalid metrics gives us forward-compatibility - assert_equal {OK} [r hotkeys start METRICS 2 GPU CPU] - - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - Error: METRICS same parameter} { - r hello 3 - catch {r hotkeys start METRICS 2 CPU CPU} err - assert_match "*METRICS CPU*" $err - catch {r hotkeys start METRICS 2 NET NET} err - assert_match "*METRICS NET*" $err - } {} {resp3} - - - test {HOTKEYS START - with COUNT parameter} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET COUNT 20] - - for {set i 0} {$i < 30} {incr i} { - r set "key_$i" "value_$i" - } - - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - set cpu_array [dict get $result "by-cpu-time"] - set net_array [dict get $result "by-net-bytes"] - - set cpu_count [expr {[llength $cpu_array] / 2}] - set net_count [expr {[llength $net_array] / 2}] - - assert_lessthan_equal $cpu_count 20 - assert_lessthan_equal $net_count 20 - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - Error: COUNT out of range} { - r hello 3 - catch {r hotkeys start METRICS 1 CPU COUNT 0} err - assert_match "*COUNT must be between 1 and 64*" $err - catch {r hotkeys start METRICS 1 CPU COUNT 100} err - assert_match "*COUNT must be between 1 and 64*" $err - } {} {resp3} - - test {HOTKEYS START - with DURATION parameter} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 CPU DURATION 1] - after 1500 - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] eq "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - assert_equal 0 [dict get $result "tracking-active"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - with SAMPLE parameter} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 10] - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - Error: SAMPLE ratio invalid} { - r hello 3 - catch {r hotkeys start METRICS 1 CPU SAMPLE 0} err - assert_match "*SAMPLE ratio must be positive*" $err - } {} {resp3} - - test {HOTKEYS START - with SLOTS parameter} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SLOTS 2 0 5] - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS START - Error: SLOTS count mismatch} { - r hello 3 - catch {r hotkeys start METRICS 1 CPU SLOTS 2 0} err - assert_match "*not enough slot numbers provided*" $err - } {} {resp3} - - test {HOTKEYS START - Error: duplicate slots} { - r hello 3 - catch {r hotkeys start METRICS 1 CPU SLOTS 2 0 0} err - assert_match "*duplicate slot number*" $err - } {} {resp3} - - test {HOTKEYS STOP - basic functionality} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET] - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - assert_equal 0 [dict get $result "tracking-active"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS RESET - basic functionality} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 CPU] - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - # After reset, GET should return nil - set result [r hotkeys get] - assert_equal {} $result - } {} {resp3} - - test {HOTKEYS RESET - Error: session in progress} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 CPU] - catch {r hotkeys reset} err - assert_match "*hotkey tracking session in progress, stop tracking first*" $err - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS GET - returns nil when not started} { - r hello 3 - set result [r hotkeys get] - assert_equal {} $result - } {} {resp3} - - test {HOTKEYS GET - sample-ratio field} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 5] - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - assert_equal 5 [dict get $result "sample-ratio"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS GET - selected-slots field} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SLOTS 2 0 5] - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - set slots [dict get $result "selected-slots"] - assert_equal 2 [llength $slots] - assert_equal 0 [lindex $slots 0] - assert_equal 5 [lindex $slots 1] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS GET - conditional fields with sample_ratio > 1 and selected slots} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 10 SLOTS 1 0] - r set key1 value1 - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - # Should have conditional fields - assert [dict exists $result "sampled-command-selected-slots-ms"] - assert [dict exists $result "all-commands-selected-slots-ms"] - assert [dict exists $result "net-bytes-sampled-commands-selected-slots"] - assert [dict exists $result "net-bytes-all-commands-selected-slots"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS GET - no conditional fields with sample_ratio = 1} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SLOTS 1 0] - r set key1 value1 - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - # Should NOT have sampled-commands fields (sample_ratio = 1) - assert {![dict exists $result "sampled-command-selected-slots-ms"]} - assert {![dict exists $result "net-bytes-sampled-commands-selected-slots"]} - - # Should have all-commands-selected-slots fields - assert [dict exists $result "all-commands-selected-slots-ms"] - assert [dict exists $result "net-bytes-all-commands-selected-slots"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - test {HOTKEYS - nested commands} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 1 NET] - r eval "redis.call('set', 'x', 1)" 1 x - r eval "redis.call('set', 'y', 1)" 1 y - r eval "redis.call('set', 'x', 2)" 1 x - r eval "redis.call('set', 'x', 3)" 1 x - - set result [r hotkeys get] - set result [dict get $result "by-net-bytes"] - assert [dict exists $result "x"] - assert [dict exists $result "y"] - - assert_equal {OK} [r hotkeys stop] - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - - - test {HOTKEYS GET - no conditional fields without selected slots} { - r hello 3 - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 10] - r set key1 value1 - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - # Should NOT have selected-slots conditional fields - assert {![dict exists $result "sampled-command-selected-slots-ms"]} - assert {![dict exists $result "all-commands-selected-slots-ms"]} - assert {![dict exists $result "net-bytes-sampled-commands-selected-slots"]} - assert {![dict exists $result "net-bytes-all-commands-selected-slots"]} - - # Should have all-slots fields - assert [dict exists $result "all-commands-all-slots-ms"] - assert [dict exists $result "net-bytes-all-commands-all-slots"] - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - - foreach sample_ratio {1 100 500 1000} { - test "HOTKEYS detection with biased key access, sample ratio = $sample_ratio" { - r hello 3 - - # Generate 100 random keys - set all_keys {} - for {set i 0} {$i < 100} {incr i} { - lappend all_keys "key_[format %03d $i]" - } - - # Choose 20 keys to bias towards. These will be out hot keys - set hot_keys {} - for {set i 0} {$i < 20} {incr i} { - lappend hot_keys [lindex $all_keys $i] - } - - assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE $sample_ratio] - - # Biasing towards the 20 chosen keys when sending commands - set total_commands 50000 - for {set i 0} {$i < $total_commands} {incr i} { - set rand [expr {rand()}] - if {$rand < 0.8} { - set key [lindex $hot_keys [expr {int(rand() * 20)}]] - } else { - set key [lindex $all_keys [expr {20 + int(rand() * 80)}]] - } - r set $key "value_$i" - } - - assert_equal {OK} [r hotkeys stop] - - set result [r hotkeys get] - assert_not_equal $result {} - - # Convert to dict if it's a flat array - if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} { - set result [hotkeys_array_to_dict $result] - } - - set cpu_time_array [dict get $result "by-cpu-time"] - set net_bytes_array [dict get $result "by-net-bytes"] - - set returned_cpu_keys {} - for {set i 0} {$i < [llength $cpu_time_array]} {incr i 2} { - lappend returned_cpu_keys [lindex $cpu_time_array $i] - } - - # Check that most of returned keys (based on cpu time) are from our - # hot_keys list - set num_returned_cpu [llength $returned_cpu_keys] - assert_lessthan_equal $num_returned_cpu 10 - assert_morethan $num_returned_cpu 0 - - set res 0 - foreach key $returned_cpu_keys { - if {[lsearch -exact $hot_keys $key] >= 0} { - incr res - } - } - assert_morethan $res 5 - - set returned_net_keys {} - for {set i 0} {$i < [llength $net_bytes_array]} {incr i 2} { - lappend returned_net_keys [lindex $net_bytes_array $i] - } - - # Same as cpu-time but for net-bytes - set num_returned_net [llength $returned_net_keys] - assert_lessthan_equal $num_returned_net 10 - assert_morethan $num_returned_net 0 - - set res_net 0 - foreach key $returned_net_keys { - if {[lsearch -exact $hot_keys $key] >= 0} { - incr res_net - } - } - assert_morethan $res_net 5 - - assert_equal {OK} [r hotkeys reset] - } {} {resp3} - } -} - diff --git a/examples/redis-unstable/tests/unit/hyperloglog.tcl b/examples/redis-unstable/tests/unit/hyperloglog.tcl deleted file mode 100644 index 2202c30..0000000 --- a/examples/redis-unstable/tests/unit/hyperloglog.tcl +++ /dev/null @@ -1,374 +0,0 @@ -start_server {tags {"hll"}} { - test {HyperLogLog self test passes} { - catch {r pfselftest} e - set e - } {OK} {needs:pfdebug} - - test {PFADD without arguments creates an HLL value} { - r pfadd hll - r exists hll - } {1} - - test {Approximated cardinality after creation is zero} { - r pfcount hll - } {0} - - test {PFADD returns 1 when at least 1 reg was modified} { - r pfadd hll a b c - } {1} - - test {PFADD returns 0 when no reg was modified} { - r pfadd hll a b c - } {0} - - test {PFADD works with empty string (regression)} { - r pfadd hll "" - } - - # Note that the self test stresses much better the - # cardinality estimation error. We are testing just the - # command implementation itself here. - test {PFCOUNT returns approximated cardinality of set} { - r del hll - set res {} - r pfadd hll 1 2 3 4 5 - lappend res [r pfcount hll] - # Call it again to test cached value invalidation. - r pfadd hll 6 7 8 8 9 10 - lappend res [r pfcount hll] - set res - } {5 10} - - test {HyperLogLogs are promote from sparse to dense} { - r del hll - r config set hll-sparse-max-bytes 3000 - set n 0 - while {$n < 100000} { - set elements {} - for {set j 0} {$j < 100} {incr j} {lappend elements [expr rand()]} - incr n 100 - r pfadd hll {*}$elements - set card [r pfcount hll] - set err [expr {abs($card-$n)}] - assert {$err < (double($card)/100)*5} - if {$n < 1000} { - assert {[r pfdebug encoding hll] eq {sparse}} - } elseif {$n > 10000} { - assert {[r pfdebug encoding hll] eq {dense}} - } - } - } {} {needs:pfdebug} - - test {Change hll-sparse-max-bytes} { - r config set hll-sparse-max-bytes 3000 - r del hll - r pfadd hll a b c d e d g h i j k - assert {[r pfdebug encoding hll] eq {sparse}} - r config set hll-sparse-max-bytes 30 - r pfadd hll new_element - assert {[r pfdebug encoding hll] eq {dense}} - } {} {needs:pfdebug} - - test {Hyperloglog promote to dense well in different hll-sparse-max-bytes} { - set max(0) 100 - set max(1) 500 - set max(2) 3000 - for {set i 0} {$i < [array size max]} {incr i} { - r config set hll-sparse-max-bytes $max($i) - r del hll - r pfadd hll - set len [r strlen hll] - while {$len <= $max($i)} { - assert {[r pfdebug encoding hll] eq {sparse}} - set elements {} - for {set j 0} {$j < 10} {incr j} { lappend elements [expr rand()]} - r pfadd hll {*}$elements - set len [r strlen hll] - } - assert {[r pfdebug encoding hll] eq {dense}} - } - } {} {needs:pfdebug} - - test {HyperLogLog sparse encoding stress test} { - for {set x 0} {$x < 1000} {incr x} { - r del hll1 - r del hll2 - set numele [randomInt 100] - set elements {} - for {set j 0} {$j < $numele} {incr j} { - lappend elements [expr rand()] - } - # Force dense representation of hll2 - r pfadd hll2 - r pfdebug todense hll2 - r pfadd hll1 {*}$elements - r pfadd hll2 {*}$elements - assert {[r pfdebug encoding hll1] eq {sparse}} - assert {[r pfdebug encoding hll2] eq {dense}} - # Cardinality estimated should match exactly. - assert {[r pfcount hll1] eq [r pfcount hll2]} - } - } {} {needs:pfdebug} - - test {Corrupted sparse HyperLogLogs are detected: Additional at tail} { - r del hll - r pfadd hll a b c - r append hll "hello" - set e {} - catch {r pfcount hll} e - set e - } {*INVALIDOBJ*} - - test {Corrupted sparse HyperLogLogs are detected: Broken magic} { - r del hll - r pfadd hll a b c - r setrange hll 0 "0123" - set e {} - catch {r pfcount hll} e - set e - } {*WRONGTYPE*} - - test {Corrupted sparse HyperLogLogs are detected: Invalid encoding} { - r del hll - r pfadd hll a b c - r setrange hll 4 "x" - set e {} - catch {r pfcount hll} e - set e - } {*WRONGTYPE*} - - test {Corrupted sparse HyperLogLogs doesn't cause overflow and out-of-bounds with XZERO opcode} { - r del hll - - # Create a sparse-encoded HyperLogLog header - set header "HYLL" - set payload [binary format c12 {1 0 0 0 0 0 0 0 0 0 0 0}] - set pl [binary format a4a12 $header $payload] - - # Create an XZERO opcode with the maximum run length of 16384(2^14) - set runlen [expr 16384 - 1] - set chunk [binary format cc [expr {0b01000000 | ($runlen >> 8)}] [expr {$runlen & 0xff}]] - # Fill the HLL with more than 131072(2^17) XZERO opcodes to make the total - # run length exceed 4GB, will cause an integer overflow. - set repeat [expr 131072 + 1000] - for {set i 0} {$i < $repeat} {incr i} { - append pl $chunk - } - - # Create a VAL opcode with a value that will cause out-of-bounds. - append pl [binary format c 0b11111111] - r set hll $pl - - # This should not overflow and out-of-bounds. - assert_error {*INVALIDOBJ*} {r pfcount hll hll} - assert_error {*INVALIDOBJ*} {r pfdebug getreg hll} - r ping - } - - test {Corrupted sparse HyperLogLogs doesn't cause overflow and out-of-bounds with ZERO opcode} { - r del hll - - # Create a sparse-encoded HyperLogLog header - set header "HYLL" - set payload [binary format c12 {1 0 0 0 0 0 0 0 0 0 0 0}] - set pl [binary format a4a12 $header $payload] - - # # Create an ZERO opcode with the maximum run length of 64(2^6) - set chunk [binary format c [expr {0b00000000 | 0x3f}]] - # Fill the HLL with more than 33554432(2^17) ZERO opcodes to make the total - # run length exceed 4GB, will cause an integer overflow. - set repeat [expr 33554432 + 1000] - for {set i 0} {$i < $repeat} {incr i} { - append pl $chunk - } - - # Create a VAL opcode with a value that will cause out-of-bounds. - append pl [binary format c 0b11111111] - r set hll $pl - - # This should not overflow and out-of-bounds. - assert_error {*INVALIDOBJ*} {r pfcount hll hll} - assert_error {*INVALIDOBJ*} {r pfdebug getreg hll} - r ping - } - - test {Corrupted dense HyperLogLogs are detected: Wrong length} { - r del hll - r pfadd hll a b c - r setrange hll 4 "\x00" - set e {} - catch {r pfcount hll} e - set e - } {*WRONGTYPE*} - - test {Fuzzing dense/sparse encoding: Redis should always detect errors} { - for {set j 0} {$j < 1000} {incr j} { - r del hll - set items {} - set numitems [randomInt 3000] - for {set i 0} {$i < $numitems} {incr i} { - lappend items [expr {rand()}] - } - r pfadd hll {*}$items - - # Corrupt it in some random way. - for {set i 0} {$i < 5} {incr i} { - set len [r strlen hll] - set pos [randomInt $len] - set byte [randstring 1 1 binary] - r setrange hll $pos $byte - # Don't modify more bytes 50% of times - if {rand() < 0.5} break - } - - # Use the hyperloglog to check if it crashes - # Redis in some way. - catch { - r pfcount hll - } - } - } - - test {PFADD, PFCOUNT, PFMERGE type checking works} { - r set foo{t} bar - catch {r pfadd foo{t} 1} e - assert_match {*WRONGTYPE*} $e - catch {r pfcount foo{t}} e - assert_match {*WRONGTYPE*} $e - catch {r pfmerge bar{t} foo{t}} e - assert_match {*WRONGTYPE*} $e - catch {r pfmerge foo{t} bar{t}} e - assert_match {*WRONGTYPE*} $e - } - - test {PFMERGE results on the cardinality of union of sets} { - r del hll{t} hll1{t} hll2{t} hll3{t} - r pfadd hll1{t} a b c - r pfadd hll2{t} b c d - r pfadd hll3{t} c d e - r pfmerge hll{t} hll1{t} hll2{t} hll3{t} - r pfcount hll{t} - } {5} - - test {PFMERGE on missing source keys will create an empty destkey} { - r del sourcekey{t} sourcekey2{t} destkey{t} destkey2{t} - - assert_equal {OK} [r pfmerge destkey{t} sourcekey{t}] - assert_equal 1 [r exists destkey{t}] - assert_equal 0 [r pfcount destkey{t}] - - assert_equal {OK} [r pfmerge destkey2{t} sourcekey{t} sourcekey2{t}] - assert_equal 1 [r exists destkey2{t}] - assert_equal 0 [r pfcount destkey{t}] - } - - test {PFMERGE with one empty input key, create an empty destkey} { - r del destkey - assert_equal {OK} [r pfmerge destkey] - assert_equal 1 [r exists destkey] - assert_equal 0 [r pfcount destkey] - } - - test {PFMERGE with one non-empty input key, dest key is actually one of the source keys} { - r del destkey - assert_equal 1 [r pfadd destkey a b c] - assert_equal {OK} [r pfmerge destkey] - assert_equal 1 [r exists destkey] - assert_equal 3 [r pfcount destkey] - } - - test {PFMERGE results with simd} { - r del hllscalar{t} hllsimd{t} hll1{t} hll2{t} hll3{t} - for {set x 1} {$x < 2000} {incr x} { - r pfadd hll1{t} [expr rand()] - } - for {set x 1} {$x < 4000} {incr x} { - r pfadd hll2{t} [expr rand()] - } - for {set x 1} {$x < 8000} {incr x} { - r pfadd hll3{t} [expr rand()] - } - assert {[r pfcount hll1{t}] > 0} - assert {[r pfcount hll2{t}] > 0} - assert {[r pfcount hll3{t}] > 0} - - r pfdebug simd off - set scalar [r pfcount hll1{t} hll2{t} hll3{t}] - r pfdebug simd on - set simd [r pfcount hll1{t} hll2{t} hll3{t}] - assert {$scalar > 0} - assert {$simd > 0} - assert_equal $scalar $simd - - r pfdebug simd off - r pfmerge hllscalar{t} hll1{t} hll2{t} hll3{t} - r pfdebug simd on - r pfmerge hllsimd{t} hll1{t} hll2{t} hll3{t} - - set scalar [r pfcount hllscalar{t}] - set simd [r pfcount hllsimd{t}] - assert {$scalar > 0} - assert {$simd > 0} - assert_equal $scalar $simd - - set scalar [r get hllscalar{t}] - set simd [r get hllsimd{t}] - assert_equal $scalar $simd - - } {} {needs:pfdebug} - - test {PFCOUNT multiple-keys merge returns cardinality of union #1} { - r del hll1{t} hll2{t} hll3{t} - for {set x 1} {$x < 10000} {incr x} { - r pfadd hll1{t} "foo-$x" - r pfadd hll2{t} "bar-$x" - r pfadd hll3{t} "zap-$x" - - set card [r pfcount hll1{t} hll2{t} hll3{t}] - set realcard [expr {$x*3}] - set err [expr {abs($card-$realcard)}] - assert {$err < (double($card)/100)*5} - } - } - - test {PFCOUNT multiple-keys merge returns cardinality of union #2} { - r del hll1{t} hll2{t} hll3{t} - set elements {} - for {set x 1} {$x < 10000} {incr x} { - for {set j 1} {$j <= 3} {incr j} { - set rint [randomInt 20000] - r pfadd hll$j{t} $rint - lappend elements $rint - } - } - set realcard [llength [lsort -unique $elements]] - set card [r pfcount hll1{t} hll2{t} hll3{t}] - set err [expr {abs($card-$realcard)}] - assert {$err < (double($card)/100)*5} - } - - test {PFDEBUG GETREG returns the HyperLogLog raw registers} { - r del hll - r pfadd hll 1 2 3 - llength [r pfdebug getreg hll] - } {16384} {needs:pfdebug} - - test {PFADD / PFCOUNT cache invalidation works} { - r del hll - r pfadd hll a b c - r pfcount hll - assert {[r getrange hll 15 15] eq "\x00"} - r pfadd hll a b c - assert {[r getrange hll 15 15] eq "\x00"} - r pfadd hll 1 2 3 - assert {[r getrange hll 15 15] eq "\x80"} - } - - test {PFADD with 2GB entry should not crash server due to overflow in MurmurHash64A} { - r config set proto-max-bulk-len 3221225472 - r config set client-query-buffer-limit 3221225472 - r write "*3\r\n\$5\r\nPFADD\r\n\$3\r\nhll\r\n" - write_big_bulk 2147483648; - r ping - } {PONG} {large-memory} -} diff --git a/examples/redis-unstable/tests/unit/info-command.tcl b/examples/redis-unstable/tests/unit/info-command.tcl deleted file mode 100644 index bc24ed2..0000000 --- a/examples/redis-unstable/tests/unit/info-command.tcl +++ /dev/null @@ -1,62 +0,0 @@ -start_server {tags {"info and its relative command"}} { - test "info command with at most one sub command" { - foreach arg {"" "all" "default" "everything"} { - if {$arg == ""} { - set info [r 0 info] - } else { - set info [r 0 info $arg] - } - - assert { [string match "*redis_version*" $info] } - assert { [string match "*used_cpu_user*" $info] } - assert { ![string match "*sentinel_tilt*" $info] } - assert { [string match "*used_memory*" $info] } - if {$arg == "" || $arg == "default"} { - assert { ![string match "*rejected_calls*" $info] } - } else { - assert { [string match "*rejected_calls*" $info] } - } - } - } - - test "info command with one sub-section" { - set info [r info cpu] - assert { [string match "*used_cpu_user*" $info] } - assert { ![string match "*sentinel_tilt*" $info] } - assert { ![string match "*used_memory*" $info] } - - set info [r info sentinel] - assert { ![string match "*sentinel_tilt*" $info] } - assert { ![string match "*used_memory*" $info] } - - set info [r info commandSTATS] ;# test case insensitive compare - assert { ![string match "*used_memory*" $info] } - assert { [string match "*rejected_calls*" $info] } - } - - test "info command with multiple sub-sections" { - set info [r info cpu sentinel] - assert { [string match "*used_cpu_user*" $info] } - assert { ![string match "*sentinel_tilt*" $info] } - assert { ![string match "*master_repl_offset*" $info] } - - set info [r info cpu all] - assert { [string match "*used_cpu_user*" $info] } - assert { ![string match "*sentinel_tilt*" $info] } - assert { [string match "*used_memory*" $info] } - assert { [string match "*master_repl_offset*" $info] } - assert { [string match "*rejected_calls*" $info] } - # check that we didn't get the same info twice - assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] } - - set info [r info cpu default] - assert { [string match "*used_cpu_user*" $info] } - assert { ![string match "*sentinel_tilt*" $info] } - assert { [string match "*used_memory*" $info] } - assert { [string match "*master_repl_offset*" $info] } - assert { ![string match "*rejected_calls*" $info] } - # check that we didn't get the same info twice - assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] } - } - -} diff --git a/examples/redis-unstable/tests/unit/info-keysizes.tcl b/examples/redis-unstable/tests/unit/info-keysizes.tcl deleted file mode 100644 index d55e716..0000000 --- a/examples/redis-unstable/tests/unit/info-keysizes.tcl +++ /dev/null @@ -1,764 +0,0 @@ -################################################################################ -# Test the "info keysizes" command. -# The command returns a histogram of the sizes of keys in the database. -################################################################################ - -# Verify output of "info keysizes" command is as expected. -# -# Arguments: -# cmd - A command that should be run before the verification. -# expOutput - This is a string that represents the expected output abbreviated. -# Instead of the output of "strings_len_exp_distrib" write "STR". -# Similarly for LIST, SET, ZSET and HASH. Spaces and newlines are -# ignored. -# -# Alternatively, you can set "__EVAL_DB_HIST__". The function -# will read all the keys from the server for selected db index, -# ask for their length and compute the expected output. - -# waitCond - If set to 1, the function wait_for_condition 50x50msec for the -# expOutput to match the actual output. -# -# (replicaMode) - Global variable that indicates if the test is running in replica -# mode. If so, run the command on leader, verify the output. Then wait -# for the replica to catch up and verify the output on the replica -# as well. Otherwise, just run the command on the leader and verify -# the output. -proc run_cmd_verify_hist {cmd expOutput {waitCond 0}} { - - #################### internal funcs ################ - proc build_exp_hist {server expOutput} { - if {[regexp {^__EVAL_DB_HIST__\s+(\d+)$} $expOutput -> dbid]} { - set expOutput [eval_db_histogram $server $dbid] - } - - # Replace all placeholders with the actual values. Remove spaces & newlines. - set res [string map { - "STR" "distrib_strings_sizes" - "LIST" "distrib_lists_items" - "SET" "distrib_sets_items" - "ZSET" "distrib_zsets_items" - "HASH" "distrib_hashes_items" - " " "" "\n" "" "\r" "" - } $expOutput] - return $res - } - proc verify_histogram { server expOutput cmd {retries 1} } { - wait_for_condition 50 $retries { - [build_exp_hist $server $expOutput] eq [get_info_hist_stripped $server] - } else { - fail "Expected: \n`[build_exp_hist $server $expOutput]` \ - Actual: `[get_info_hist_stripped $server]`. \nFailed after command: $cmd" - } - } - # Query and Strip result of "info keysizes" from header, spaces, and newlines. - proc get_info_hist_stripped {server} { - set infoStripped [string map { - "# Keysizes" "" - " " "" "\n" "" "\r" "" - } [$server info keysizes] ] - return $infoStripped - } - #################### EOF internal funcs ################ - - uplevel 1 $cmd - global replicaMode - - # ref the leader with `server` variable - if {$replicaMode eq 1} { - set server [srv -1 client] - set replica [srv 0 client] - } else { - set server [srv 0 client] - } - - # Compare the stripped expected output with the actual output from the server - set retries [expr { $waitCond ? 20 : 1}] - verify_histogram $server $expOutput $cmd $retries - - # If we are testing `replicaMode` then need to wait for the replica to catch up - if {$replicaMode eq 1} { - verify_histogram $replica $expOutput $cmd 20 - } -} - -# eval_db_histogram - eval The expected histogram for current db, by -# reading all the keys from the server, query for their length, and computing -# the expected output. -proc eval_db_histogram {server dbid} { - $server select $dbid - array set type_counts {} - - set keys [$server keys *] - foreach key $keys { - set key_type [$server type $key] - switch -exact $key_type { - "string" { - set value [$server strlen $key] - set type "STR" - } - "list" { - set value [$server llen $key] - set type "LIST" - } - "set" { - set value [$server scard $key] - set type "SET" - } - "zset" { - set value [$server zcard $key] - set type "ZSET" - } - "hash" { - set value [$server hlen $key] - set type "HASH" - } - default { - continue ; # Skip unknown types - } - } - - set power 1 - while { ($power * 2) <= $value } { set power [expr {$power * 2}] } - if {$value == 0} { set power 0} - # Store counts by type and size bucket - incr type_counts($type,$power) - } - - set result {} - foreach type {STR LIST SET ZSET HASH} { - if {[array exists type_counts] && [array names type_counts $type,*] ne ""} { - set sorted_powers [lsort -integer [lmap item [array names type_counts $type,*] { - lindex [split $item ,] 1 ; # Extracts only the numeric part - }]] - - set type_result {} - foreach power $sorted_powers { - set display_power $power - if { $power >= 1024 } { set display_power "[expr {$power / 1024}]K" } - lappend type_result "$display_power=$type_counts($type,$power)" - } - lappend result "db${dbid}_$type: [join $type_result ", "]" - } - } - - return [join $result " "] -} - -proc test_all_keysizes { {replMode 0} } { - # If in replica mode then update global var `replicaMode` so function - # `run_cmd_verify_hist` knows to run the command on the leader and then - # wait for the replica to catch up. - global replicaMode - set replicaMode $replMode - # ref the leader with `server` variable - if {$replicaMode eq 1} { - set server [srv -1 client] - set replica [srv 0 client] - set suffixRepl "(replica)" - } else { - set server [srv 0 client] - set suffixRepl "" - } - - test "KEYSIZES - Test i'th bin counts keysizes between (2^i) and (2^(i+1)-1) as expected $suffixRepl" { - set base_string "" - run_cmd_verify_hist {$server FLUSHALL} {} - for {set i 1} {$i <= 10} {incr i} { - append base_string "x" - set log_value [expr {1 << int(log($i) / log(2))}] - #puts "Iteration $i: $base_string (Log base 2 pattern: $log_value)" - run_cmd_verify_hist {$server set mykey $base_string} "db0_STR:$log_value=1" - } - } - - test "KEYSIZES - Histogram values of Bytes, Kilo and Mega $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server set x 0123456789ABCDEF} {db0_STR:16=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:32=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:64=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:128=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:256=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:512=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:1K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:2K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:4K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:8K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:16K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:32K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:64K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:128K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:256K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:512K=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:1M=1} - run_cmd_verify_hist {$server APPEND x [$server get x]} {db0_STR:2M=1} - } - - # It is difficult to predict the actual string length of hyperloglog. To address - # this, we will create expected output by indicating __EVAL_DB_HIST__ to read - # all keys & lengths from server. Based on it, generate the expected output. - test "KEYSIZES - Test hyperloglog $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - # PFADD (sparse & dense) - for {set i 1} {$i <= 3000} {incr i} { - run_cmd_verify_hist {$server PFADD hll1 a$i b$i c$i} {__EVAL_DB_HIST__ 0} - run_cmd_verify_hist {$server PFADD hll2 x$i y$i z$i} {__EVAL_DB_HIST__ 0} - } - # PFMERGE, PFCOUNT (sparse & dense) - for {set i 1} {$i <= 3000} {incr i} { - run_cmd_verify_hist {$server PFADD hll3 x$i y$i z$i} {__EVAL_DB_HIST__ 0} - run_cmd_verify_hist {$server PFMERGE hll4 hll1 hll2 hll3} {__EVAL_DB_HIST__ 0} - run_cmd_verify_hist {$server PFCOUNT hll1 hll2 hll3 hll4} {__EVAL_DB_HIST__ 0} - } - # DEL - run_cmd_verify_hist {$server DEL hll4} {__EVAL_DB_HIST__ 0} - run_cmd_verify_hist {$server DEL hll3} {__EVAL_DB_HIST__ 0} - run_cmd_verify_hist {$server DEL hll1} {__EVAL_DB_HIST__ 0} - run_cmd_verify_hist {$server DEL hll2} {} - # SET overwrites - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server PFADD hll1 a b c d e f g h i j k l m} {db0_STR:32=1} - run_cmd_verify_hist {$server SET hll1 1234567} {db0_STR:4=1} - catch {run_cmd_verify_hist {$server PFADD hll1 a b c d e f g h i j k l m} {db0_STR:4=1}} - run_cmd_verify_hist {} {db0_STR:4=1} - # EXPIRE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server PFADD hll1 a b c d e f g h i j k l m} {db0_STR:32=1} - run_cmd_verify_hist {$server PEXPIRE hll1 50} {db0_STR:32=1} - run_cmd_verify_hist {} {} 1 - } {} {cluster:skip} - - test "KEYSIZES - Test List $suffixRepl" { - # FLUSHALL - run_cmd_verify_hist {$server FLUSHALL} {} - # RPUSH - run_cmd_verify_hist {$server RPUSH l1 1 2 3 4 5} {db0_LIST:4=1} - run_cmd_verify_hist {$server RPUSH l1 6 7 8 9} {db0_LIST:8=1} - # Test also LPUSH, RPUSH, LPUSHX, RPUSHX - run_cmd_verify_hist {$server LPUSH l2 1} {db0_LIST:1=1,8=1} - run_cmd_verify_hist {$server LPUSH l2 2} {db0_LIST:2=1,8=1} - run_cmd_verify_hist {$server LPUSHX l2 3} {db0_LIST:2=1,8=1} - run_cmd_verify_hist {$server RPUSHX l2 4} {db0_LIST:4=1,8=1} - # RPOP - run_cmd_verify_hist {$server RPOP l1} {db0_LIST:4=1,8=1} - run_cmd_verify_hist {$server RPOP l1} {db0_LIST:4=2} - # DEL - run_cmd_verify_hist {$server DEL l1} {db0_LIST:4=1} - # LINSERT, LTRIM - run_cmd_verify_hist {$server RPUSH l3 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14} {db0_LIST:4=1,8=1} - run_cmd_verify_hist {$server LINSERT l3 AFTER 9 10} {db0_LIST:4=1,16=1} - run_cmd_verify_hist {$server LTRIM l3 0 8} {db0_LIST:4=1,8=1} - # DEL - run_cmd_verify_hist {$server DEL l3} {db0_LIST:4=1} - run_cmd_verify_hist {$server DEL l2} {} - # LMOVE, BLMOVE - run_cmd_verify_hist {$server RPUSH l4 1 2 3 4 5 6 7 8} {db0_LIST:8=1} - run_cmd_verify_hist {$server LMOVE l4 l5 LEFT LEFT} {db0_LIST:1=1,4=1} - run_cmd_verify_hist {$server LMOVE l4 l5 RIGHT RIGHT} {db0_LIST:2=1,4=1} - run_cmd_verify_hist {$server LMOVE l4 l5 LEFT RIGHT} {db0_LIST:2=1,4=1} - run_cmd_verify_hist {$server LMOVE l4 l5 RIGHT LEFT} {db0_LIST:4=2} - run_cmd_verify_hist {$server BLMOVE l4 l5 RIGHT LEFT 0} {db0_LIST:2=1,4=1} - # DEL - run_cmd_verify_hist {$server DEL l4} {db0_LIST:4=1} - run_cmd_verify_hist {$server DEL l5} {} - # LMPOP - run_cmd_verify_hist {$server RPUSH l6 1 2 3 4 5 6 7 8 9 10} {db0_LIST:8=1} - run_cmd_verify_hist {$server LMPOP 1 l6 LEFT COUNT 2} {db0_LIST:8=1} - run_cmd_verify_hist {$server LMPOP 1 l6 LEFT COUNT 1} {db0_LIST:4=1} - run_cmd_verify_hist {$server LMPOP 1 l6 LEFT COUNT 6} {db0_LIST:1=1} - # LPOP - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l7 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server LPOP l7} {db0_LIST:2=1} - run_cmd_verify_hist {$server LPOP l7} {db0_LIST:2=1} - run_cmd_verify_hist {$server LPOP l7} {db0_LIST:1=1} - run_cmd_verify_hist {$server LPOP l7} {} - # LREM - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l8 y x y x y x y x y y} {db0_LIST:8=1} - run_cmd_verify_hist {$server LREM l8 3 x} {db0_LIST:4=1} - run_cmd_verify_hist {$server LREM l8 0 y} {db0_LIST:1=1} - run_cmd_verify_hist {$server LREM l8 0 x} {} - # EXPIRE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l9 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server PEXPIRE l9 50} {db0_LIST:4=1} - run_cmd_verify_hist {} {} 1 - # SET overwrites - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l9 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server SET l9 1234567} {db0_STR:4=1} - run_cmd_verify_hist {$server DEL l9} {} - } {} {cluster:skip} - - test "KEYSIZES - Test SET $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - # SADD - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5} {db0_SET:4=1} - run_cmd_verify_hist {$server SADD s1 6 7 8} {db0_SET:8=1} - # Test also SADD, SREM, SMOVE, SPOP - run_cmd_verify_hist {$server SADD s2 1} {db0_SET:1=1,8=1} - run_cmd_verify_hist {$server SADD s2 2} {db0_SET:2=1,8=1} - run_cmd_verify_hist {$server SREM s2 3} {db0_SET:2=1,8=1} - run_cmd_verify_hist {$server SMOVE s2 s3 2} {db0_SET:1=2,8=1} - run_cmd_verify_hist {$server SPOP s3} {db0_SET:1=1,8=1} - run_cmd_verify_hist {$server SPOP s2} {db0_SET:8=1} - run_cmd_verify_hist {$server SPOP s1} {db0_SET:4=1} - run_cmd_verify_hist {$server SPOP s1 7} {} - run_cmd_verify_hist {$server SADD s2 1} {db0_SET:1=1} - run_cmd_verify_hist {$server SMOVE s2 s4 1} {db0_SET:1=1} - run_cmd_verify_hist {$server SREM s4 1} {} - run_cmd_verify_hist {$server SADD s2 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server SPOP s2 7} {db0_SET:1=1} - # SDIFFSTORE - run_cmd_verify_hist {$server flushall} {} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server SADD s2 6 7 8 9 A B C D} {db0_SET:8=2} - run_cmd_verify_hist {$server SADD s3 x} {db0_SET:1=1,8=2} - run_cmd_verify_hist {$server SDIFFSTORE s3 s1 s2} {db0_SET:4=1,8=2} - #SINTERSTORE - run_cmd_verify_hist {$server flushall} {} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server SADD s2 6 7 8 9 A B C D} {db0_SET:8=2} - run_cmd_verify_hist {$server SADD s3 x} {db0_SET:1=1,8=2} - run_cmd_verify_hist {$server SINTERSTORE s3 s1 s2} {db0_SET:2=1,8=2} - #SUNIONSTORE - run_cmd_verify_hist {$server flushall} {} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server SADD s2 6 7 8 9 A B C D} {db0_SET:8=2} - run_cmd_verify_hist {$server SUNIONSTORE s3 s1 s2} {db0_SET:8=3} - run_cmd_verify_hist {$server SADD s4 E F G H} {db0_SET:4=1,8=3} - run_cmd_verify_hist {$server SUNIONSTORE s5 s3 s4} {db0_SET:4=1,8=3,16=1} - # DEL - run_cmd_verify_hist {$server flushall} {} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server DEL s1} {} - # EXPIRE - run_cmd_verify_hist {$server flushall} {} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server PEXPIRE s1 50} {db0_SET:8=1} - run_cmd_verify_hist {} {} 1 - # SET overwrites - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5 6 7 8} {db0_SET:8=1} - run_cmd_verify_hist {$server SET s1 1234567} {db0_STR:4=1} - run_cmd_verify_hist {$server DEL s1} {} - } {} {cluster:skip} - - test "KEYSIZES - Test ZSET $suffixRepl" { - # ZADD, ZREM - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZADD z1 6 f 7 g 8 h 9 i} {db0_ZSET:8=1} - run_cmd_verify_hist {$server ZADD z2 1 a} {db0_ZSET:1=1,8=1} - run_cmd_verify_hist {$server ZREM z1 a} {db0_ZSET:1=1,8=1} - run_cmd_verify_hist {$server ZREM z1 b} {db0_ZSET:1=1,4=1} - run_cmd_verify_hist {$server ZREM z1 c d e f g h i} {db0_ZSET:1=1} - run_cmd_verify_hist {$server ZREM z2 a} {} - # ZREMRANGEBYSCORE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZREMRANGEBYSCORE z1 -inf (2} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZREMRANGEBYSCORE z1 -inf (3} {db0_ZSET:2=1} - run_cmd_verify_hist {$server ZREMRANGEBYSCORE z1 -inf +inf} {} - - # ZREMRANGEBYRANK - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e 6 f} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZREMRANGEBYRANK z1 0 1} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZREMRANGEBYRANK z1 0 0} {db0_ZSET:2=1} - # ZREMRANGEBYLEX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 0 a 0 b 0 c 0 d 0 e 0 f} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZREMRANGEBYLEX z1 - (d} {db0_ZSET:2=1} - # ZUNIONSTORE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZADD z2 6 f 7 g 8 h 9 i} {db0_ZSET:4=2} - run_cmd_verify_hist {$server ZUNIONSTORE z3 2 z1 z2} {db0_ZSET:4=2,8=1} - # ZINTERSTORE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZADD z2 3 c 4 d 5 e 6 f} {db0_ZSET:4=2} - run_cmd_verify_hist {$server ZINTERSTORE z3 2 z1 z2} {db0_ZSET:2=1,4=2} - # BZPOPMIN, BZPOPMAX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server BZPOPMIN z1 0} {db0_ZSET:4=1} - run_cmd_verify_hist {$server BZPOPMAX z1 0} {db0_ZSET:2=1} - # ZDIFFSTORE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZADD z2 3 c 4 d 5 e 6 f} {db0_ZSET:4=2} - run_cmd_verify_hist {$server ZDIFFSTORE z3 2 z1 z2} {db0_ZSET:2=1,4=2} - # ZINTERSTORE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server ZADD z2 3 c 4 d 5 e 6 f} {db0_ZSET:4=2} - run_cmd_verify_hist {$server ZADD z3 1 x} {db0_ZSET:1=1,4=2} - run_cmd_verify_hist {$server ZINTERSTORE z4 2 z1 z2} {db0_ZSET:1=1,2=1,4=2} - run_cmd_verify_hist {$server ZINTERSTORE z4 2 z1 z3} {db0_ZSET:1=1,4=2} - # DEL - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server DEL z1} {} - # EXPIRE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server PEXPIRE z1 50} {db0_ZSET:4=1} - run_cmd_verify_hist {} {} 1 - # SET overwrites - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c 4 d 5 e} {db0_ZSET:4=1} - run_cmd_verify_hist {$server SET z1 1234567} {db0_STR:4=1} - run_cmd_verify_hist {$server DEL z1} {} - # ZMPOP - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c} {db0_ZSET:2=1} - run_cmd_verify_hist {$server ZMPOP 1 z1 MIN} {db0_ZSET:2=1} - run_cmd_verify_hist {$server ZMPOP 1 z1 MAX COUNT 2} {} - - } {} {cluster:skip} - - test "KEYSIZES - Test STRING $suffixRepl" { - # SETRANGE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SET s2 1234567890} {db0_STR:8=1} - run_cmd_verify_hist {$server SETRANGE s2 10 123456} {db0_STR:16=1} - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SETRANGE k 200000 v} {db0_STR:128K=1} - # MSET, MSETNX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server MSET s3 1 s4 2 s5 3} {db0_STR:1=3} - run_cmd_verify_hist {$server MSETNX s6 1 s7 2 s8 3} {db0_STR:1=6} - # DEL - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SET s9 1234567890} {db0_STR:8=1} - run_cmd_verify_hist {$server DEL s9} {} - #EXPIRE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SET s10 1234567890} {db0_STR:8=1} - run_cmd_verify_hist {$server PEXPIRE s10 50} {db0_STR:8=1} - run_cmd_verify_hist {} {} 1 - # SET (+overwrite) - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SET s1 1024} {db0_STR:4=1} - run_cmd_verify_hist {$server SET s1 842} {db0_STR:2=1} - run_cmd_verify_hist {$server SET s1 2} {db0_STR:1=1} - run_cmd_verify_hist {$server SET s1 1234567} {db0_STR:4=1} - # SET (string of length 0) - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SET s1 ""} {db0_STR:0=1} - run_cmd_verify_hist {$server SET s1 ""} {db0_STR:0=1} - run_cmd_verify_hist {$server SET s2 ""} {db0_STR:0=2} - run_cmd_verify_hist {$server SET s2 "bla"} {db0_STR:0=1,2=1} - run_cmd_verify_hist {$server SET s2 ""} {db0_STR:0=2} - run_cmd_verify_hist {$server HSET h f v} {db0_STR:0=2 db0_HASH:1=1} - run_cmd_verify_hist {$server SET h ""} {db0_STR:0=3} - run_cmd_verify_hist {$server DEL h} {db0_STR:0=2} - run_cmd_verify_hist {$server DEL s2} {db0_STR:0=1} - run_cmd_verify_hist {$server DEL s1} {} - # APPEND - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server APPEND s1 x} {db0_STR:1=1} - run_cmd_verify_hist {$server APPEND s2 y} {db0_STR:1=2} - - } {} {cluster:skip} - - test "KEYSIZES - Test complex dataset $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - createComplexDataset $server 1000 - run_cmd_verify_hist {} {__EVAL_DB_HIST__ 0} - - run_cmd_verify_hist {$server FLUSHALL} {} - createComplexDataset $server 1000 {useexpire usehexpire} - run_cmd_verify_hist {} {__EVAL_DB_HIST__ 0} 1 - } {} {cluster:skip} - - start_server {tags {"cluster:skip" "external:skip" "needs:debug"}} { - test "KEYSIZES - Test DEBUG KEYSIZES-HIST-ASSERT command" { - # Test based on debug command rather than __EVAL_DB_HIST__ - r DEBUG KEYSIZES-HIST-ASSERT 1 - r FLUSHALL - createComplexDataset r 100 - createComplexDataset r 100 {useexpire usehexpire} - } - } - - foreach type {listpackex hashtable} { - # Test different implementations of hash tables and listpacks - if {$type eq "hashtable"} { - $server config set hash-max-listpack-entries 0 - } else { - $server config set hash-max-listpack-entries 512 - } - - test "KEYSIZES - Test HASH ($type) $suffixRepl" { - # HSETNX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETNX h1 1 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETNX h1 2 2} {db0_HASH:2=1} - # HSET, HDEL - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSET h2 1 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSET h2 2 2} {db0_HASH:2=1} - run_cmd_verify_hist {$server HDEL h2 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HDEL h2 2} {} - run_cmd_verify_hist {$server HSET h2 1 1 2 2} {db0_HASH:2=1} - run_cmd_verify_hist {$server HDEL h2 1 2} {} - # HGETDEL - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h2 FIELDS 1 1 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETEX h2 FIELDS 1 2 2} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGETDEL h2 FIELDS 1 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HGETDEL h2 FIELDS 1 3} {db0_HASH:1=1} - run_cmd_verify_hist {$server HGETDEL h2 FIELDS 1 2} {} - # HGETEX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 FIELDS 2 f1 1 f2 1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGETEX h1 PXAT 1 FIELDS 1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETEX h1 FIELDS 1 f3 1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGETEX h1 PX 50 FIELDS 1 f2} {db0_HASH:2=1} - run_cmd_verify_hist {} {db0_HASH:1=1} 1 - run_cmd_verify_hist {$server HGETEX h1 PX 50 FIELDS 1 f3} {db0_HASH:1=1} - run_cmd_verify_hist {} {} 1 - # HSETEX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 FIELDS 2 f1 1 f2 1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HSETEX h1 PXAT 1 FIELDS 1 f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETEX h1 FIELDS 1 f3 1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HSETEX h1 PX 50 FIELDS 1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {} {db0_HASH:1=1} 1 - run_cmd_verify_hist {$server HSETEX h1 PX 50 FIELDS 1 f3 v3} {db0_HASH:1=1} - run_cmd_verify_hist {} {} 1 - # HMSET - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HMSET h1 1 1 2 2 3 3} {db0_HASH:2=1} - run_cmd_verify_hist {$server HMSET h1 1 1 2 2 3 3} {db0_HASH:2=1} - run_cmd_verify_hist {$server HMSET h1 1 1 2 2 3 3 4 4} {db0_HASH:4=1} - - # HINCRBY - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server hincrby h1 f1 10} {db0_HASH:1=1} - run_cmd_verify_hist {$server hincrby h1 f1 10} {db0_HASH:1=1} - run_cmd_verify_hist {$server hincrby h1 f2 20} {db0_HASH:2=1} - # HINCRBYFLOAT - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server hincrbyfloat h1 f1 10.5} {db0_HASH:1=1} - run_cmd_verify_hist {$server hincrbyfloat h1 f1 10.5} {db0_HASH:1=1} - run_cmd_verify_hist {$server hincrbyfloat h1 f2 10.5} {db0_HASH:2=1} - # HEXPIRE - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSET h1 f1 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSET h1 f2 1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HPEXPIREAT h1 1 FIELDS 1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSET h1 f3 1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HPEXPIRE h1 50 FIELDS 1 f2} {db0_HASH:2=1} - run_cmd_verify_hist {} {db0_HASH:1=1} 1 - run_cmd_verify_hist {$server HPEXPIRE h1 50 FIELDS 1 f3} {db0_HASH:1=1} - run_cmd_verify_hist {} {} 1 - } - - test "KEYSIZES - Test Hash field lazy expiration ($type) $suffixRepl" { - $server debug set-active-expire 0 - - # HGET - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGET h1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HGET h1 f2} {} - - # HMGET - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HMGET h1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HMGET h1 f2} {} - - # HGETDEL - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGETDEL h1 FIELDS 1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HGETDEL h1 FIELDS 1 f2} {} - - # HGETEX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGETEX h1 PX 1 FIELDS 1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HGETEX h1 PX 1 FIELDS 1 f2} {} - - # HSETNX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {after 5} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETNX h1 f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {$server DEL h1} {} - run_cmd_verify_hist {$server HSETEX h2 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HSETNX h2 f1 v1} {db0_HASH:2=1} - run_cmd_verify_hist {$server HSETNX h2 f2 v2} {db0_HASH:2=1} - - # HSETEX - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {after 5} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HGET h1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HGET h1 f2} {} - - # HEXISTS - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HEXISTS h1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HEXISTS h1 f2} {} - - # HSTRLEN - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 2 f1 v1 f2 v2} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HSTRLEN h1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSTRLEN h1 f2} {} - - # HINCRBYFLOAT - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f1 1} {db0_HASH:1=1} - run_cmd_verify_hist {after 5} {db0_HASH:1=1} - run_cmd_verify_hist {$server HINCRBYFLOAT h1 f1 1.5} {db0_HASH:1=1} - - # HINCRBY - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f1 1} {db0_HASH:1=1} - run_cmd_verify_hist {after 5} {db0_HASH:1=1} - run_cmd_verify_hist {$server HINCRBY h1 f1 1} {db0_HASH:1=1} - run_cmd_verify_hist {$server HSETEX h1 PX 1 FIELDS 1 f2 1} {db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_HASH:2=1} - run_cmd_verify_hist {$server HINCRBY h1 f2 1} {db0_HASH:2=1} - - # SORT - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH user_ids 1 2} {db0_LIST:2=1} - run_cmd_verify_hist {$server HSET user:1 name "Alice" score 50} {db0_LIST:2=1 db0_HASH:2=1} - run_cmd_verify_hist {$server HSETEX user:2 PX 1 FIELDS 2 name "Bob" score 70} {db0_LIST:2=1 db0_HASH:2=2} - run_cmd_verify_hist {after 5} {db0_LIST:2=1 db0_HASH:2=2} - run_cmd_verify_hist {$server SORT user_ids BY user:*->score GET user:*->name GET user:*->score} {db0_LIST:2=1 db0_HASH:2=1} - run_cmd_verify_hist {$server DEL user_ids} {db0_HASH:2=1} - run_cmd_verify_hist {$server RPUSH user_ids 1} {db0_LIST:1=1 db0_HASH:2=1} - run_cmd_verify_hist {$server HSETEX user:1 PX 1 FIELDS 2 name "Alice" score 50} {db0_LIST:1=1 db0_HASH:2=1} - run_cmd_verify_hist {after 5} {db0_LIST:1=1 db0_HASH:2=1} - run_cmd_verify_hist {$server SORT user_ids BY user:*->score GET user:*->name GET user:*->score} {db0_LIST:1=1} - - $server debug set-active-expire 1 - } {OK} {cluster:skip needs:debug} - } - - test "KEYSIZES - Test STRING BITS $suffixRepl" { - # BITOPS - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server SET b1 "x123456789"} {db0_STR:8=1} - run_cmd_verify_hist {$server SET b2 "x12345678"} {db0_STR:8=2} - run_cmd_verify_hist {$server BITOP AND b3 b1 b2} {db0_STR:8=3} - run_cmd_verify_hist {$server BITOP OR b4 b1 b2} {db0_STR:8=4} - run_cmd_verify_hist {$server BITOP XOR b5 b1 b2} {db0_STR:8=5} - # SETBIT - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server setbit b1 71 1} {db0_STR:8=1} - run_cmd_verify_hist {$server setbit b1 72 1} {db0_STR:8=1} - run_cmd_verify_hist {$server setbit b2 72 1} {db0_STR:8=2} - run_cmd_verify_hist {$server setbit b2 640 0} {db0_STR:8=1,64=1} - # BITFIELD - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server bitfield b3 set u8 6 255} {db0_STR:2=1} - run_cmd_verify_hist {$server bitfield b3 set u8 65 255} {db0_STR:8=1} - run_cmd_verify_hist {$server bitfield b4 set u8 1000 255} {db0_STR:8=1,64=1} - } {} {cluster:skip} - - test "KEYSIZES - Test RESTORE $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l10 1 2 3 4} {db0_LIST:4=1} - set encoded [$server dump l10] - run_cmd_verify_hist {$server del l10} {} - run_cmd_verify_hist {$server restore l11 0 $encoded} {db0_LIST:4=1} - } - - test "KEYSIZES - Test RENAME $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l12 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server RENAME l12 l13} {db0_LIST:4=1} - } {} {cluster:skip} - - test "KEYSIZES - Test MOVE $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l1 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server RPUSH l2 1} {db0_LIST:1=1,4=1} - run_cmd_verify_hist {$server MOVE l1 1} {db0_LIST:1=1 db1_LIST:4=1} - } {} {cluster:skip} - - test "KEYSIZES - Test SWAPDB $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l1 1 2 3 4} {db0_LIST:4=1} - $server select 1 - run_cmd_verify_hist {$server ZADD z1 1 A} {db0_LIST:4=1 db1_ZSET:1=1} - run_cmd_verify_hist {$server SWAPDB 0 1} {db0_ZSET:1=1 db1_LIST:4=1} - $server select 0 - } {OK} {singledb:skip} - - test "KEYSIZES - DEBUG RELOAD reset keysizes $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - run_cmd_verify_hist {$server RPUSH l10 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server SET s2 1234567890} {db0_STR:8=1 db0_LIST:4=1} - run_cmd_verify_hist {$server DEBUG RELOAD} {db0_STR:8=1 db0_LIST:4=1} - run_cmd_verify_hist {$server DEL l10} {db0_STR:8=1} - run_cmd_verify_hist {$server DEBUG RELOAD} {db0_STR:8=1} - } {} {cluster:skip needs:debug} - - test "KEYSIZES - Test RDB $suffixRepl" { - run_cmd_verify_hist {$server FLUSHALL} {} - # Write list, set and zset to db0 - run_cmd_verify_hist {$server RPUSH l1 1 2 3 4} {db0_LIST:4=1} - run_cmd_verify_hist {$server SADD s1 1 2 3 4 5} {db0_LIST:4=1 db0_SET:4=1} - run_cmd_verify_hist {$server ZADD z1 1 a 2 b 3 c} {db0_LIST:4=1 db0_SET:4=1 db0_ZSET:2=1} - run_cmd_verify_hist {$server SAVE} {db0_LIST:4=1 db0_SET:4=1 db0_ZSET:2=1} - if {$replicaMode eq 1} { - run_cmd_verify_hist {$replica SAVE} {db0_LIST:4=1 db0_SET:4=1 db0_ZSET:2=1} - run_cmd_verify_hist {restart_server 0 true false} {db0_LIST:4=1 db0_SET:4=1 db0_ZSET:2=1} 1 - } else { - run_cmd_verify_hist {restart_server 0 true false} {db0_LIST:4=1 db0_SET:4=1 db0_ZSET:2=1} - } - } {} {external:skip} -} - -start_server {} { - r select 0 - test_all_keysizes 0 - # Start another server to test replication of KEYSIZES - start_server {tags {needs:repl external:skip}} { - # Set the outer layer server as primary - set primary [srv -1 client] - set primary_host [srv -1 host] - set primary_port [srv -1 port] - # Set this inner layer server as replica - set replica [srv 0 client] - - # Server should have role replica - $replica replicaof $primary_host $primary_port - wait_for_condition 50 100 { [s 0 role] eq {slave} } else { fail "Replication not started." } - - # Test KEYSIZES on leader and replica - $primary select 0 - test_all_keysizes 1 - } -} - -start_cluster 1 0 {tags {external:skip cluster needs:debug} overrides {cluster-slot-stats-enabled yes}} { - test "SLOT-ALLOCSIZE - Test DEBUG ALLOCSIZE-SLOTS-ASSERT command" { - r DEBUG ALLOCSIZE-SLOTS-ASSERT 1 - r FLUSHALL - createComplexDataset r 100 {usetag} - createComplexDataset r 100 {usetag useexpire usehexpire} - } -} diff --git a/examples/redis-unstable/tests/unit/info.tcl b/examples/redis-unstable/tests/unit/info.tcl deleted file mode 100644 index ee6d6d1..0000000 --- a/examples/redis-unstable/tests/unit/info.tcl +++ /dev/null @@ -1,604 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -proc cmdstat {cmd} { - return [cmdrstat $cmd r] -} - -proc errorstat {cmd} { - return [errorrstat $cmd r] -} - -proc latency_percentiles_usec {cmd} { - return [latencyrstat_percentiles $cmd r] -} - -start_server {tags {"info" "external:skip"}} { - start_server {} { - - test {latencystats: disable/enable} { - r config resetstat - r CONFIG SET latency-tracking no - r set a b - assert_match {} [latency_percentiles_usec set] - r CONFIG SET latency-tracking yes - r set a b - assert_match {*p50=*,p99=*,p99.9=*} [latency_percentiles_usec set] - r config resetstat - assert_match {} [latency_percentiles_usec set] - } - - test {latencystats: configure percentiles} { - r config resetstat - assert_match {} [latency_percentiles_usec set] - r CONFIG SET latency-tracking yes - r SET a b - r GET a - assert_match {*p50=*,p99=*,p99.9=*} [latency_percentiles_usec set] - assert_match {*p50=*,p99=*,p99.9=*} [latency_percentiles_usec get] - r CONFIG SET latency-tracking-info-percentiles "0.0 50.0 100.0" - assert_match [r config get latency-tracking-info-percentiles] {latency-tracking-info-percentiles {0 50 100}} - assert_match {*p0=*,p50=*,p100=*} [latency_percentiles_usec set] - assert_match {*p0=*,p50=*,p100=*} [latency_percentiles_usec get] - r config resetstat - assert_match {} [latency_percentiles_usec set] - } - - test {latencystats: bad configure percentiles} { - r config resetstat - set configlatencyline [r config get latency-tracking-info-percentiles] - catch {r CONFIG SET latency-tracking-info-percentiles "10.0 50.0 a"} e - assert_match {ERR CONFIG SET failed*} $e - assert_equal [s total_error_replies] 1 - assert_match [r config get latency-tracking-info-percentiles] $configlatencyline - catch {r CONFIG SET latency-tracking-info-percentiles "10.0 50.0 101.0"} e - assert_match {ERR CONFIG SET failed*} $e - assert_equal [s total_error_replies] 2 - assert_match [r config get latency-tracking-info-percentiles] $configlatencyline - r config resetstat - assert_match {} [errorstat ERR] - } - - test {latencystats: blocking commands} { - r config resetstat - r CONFIG SET latency-tracking yes - r CONFIG SET latency-tracking-info-percentiles "50.0 99.0 99.9" - set rd [redis_deferring_client] - r del list1{t} - - $rd blpop list1{t} 0 - wait_for_blocked_client - r lpush list1{t} a - assert_equal [$rd read] {list1{t} a} - $rd blpop list1{t} 0 - wait_for_blocked_client - r lpush list1{t} b - assert_equal [$rd read] {list1{t} b} - assert_match {*p50=*,p99=*,p99.9=*} [latency_percentiles_usec blpop] - $rd close - } - - test {latencystats: subcommands} { - r config resetstat - r CONFIG SET latency-tracking yes - r CONFIG SET latency-tracking-info-percentiles "50.0 99.0 99.9" - r client id - - assert_match {*p50=*,p99=*,p99.9=*} [latency_percentiles_usec client\\|id] - assert_match {*p50=*,p99=*,p99.9=*} [latency_percentiles_usec config\\|set] - } - - test {latencystats: measure latency} { - r config resetstat - r CONFIG SET latency-tracking yes - r CONFIG SET latency-tracking-info-percentiles "50.0" - r DEBUG sleep 0.05 - r SET k v - set latencystatline_debug [latency_percentiles_usec debug] - set latencystatline_set [latency_percentiles_usec set] - regexp "p50=(.+\..+)" $latencystatline_debug -> p50_debug - regexp "p50=(.+\..+)" $latencystatline_set -> p50_set - assert {$p50_debug >= 50000} - assert {$p50_set >= 0} - assert {$p50_debug >= $p50_set} - } {} {needs:debug} - - test {errorstats: failed call authentication error} { - r config resetstat - assert_match {} [errorstat ERR] - assert_equal [s total_error_replies] 0 - catch {r auth k} e - assert_match {ERR AUTH*} $e - assert_match {*count=1*} [errorstat ERR] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - assert_equal [s total_error_replies] 1 - r config resetstat - assert_match {} [errorstat ERR] - } - - test {errorstats: failed call within MULTI/EXEC} { - r config resetstat - assert_match {} [errorstat ERR] - assert_equal [s total_error_replies] 0 - r multi - r set a b - r auth a - catch {r exec} e - assert_match {ERR AUTH*} $e - assert_match {*count=1*} [errorstat ERR] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat set] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat exec] - assert_equal [s total_error_replies] 1 - - # MULTI/EXEC command errors should still be pinpointed to him - catch {r exec} e - assert_match {ERR EXEC without MULTI} $e - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat exec] - assert_match {*count=2*} [errorstat ERR] - assert_equal [s total_error_replies] 2 - } - - test {errorstats: failed call within LUA} { - r config resetstat - assert_match {} [errorstat ERR] - assert_equal [s total_error_replies] 0 - catch {r eval {redis.pcall('XGROUP', 'CREATECONSUMER', 's1', 'mygroup', 'consumer') return } 0} e - assert_match {*count=1*} [errorstat ERR] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup\\|createconsumer] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat eval] - - # EVAL command errors should still be pinpointed to him - catch {r eval a} e - assert_match {ERR wrong*} $e - assert_match {*calls=1,*,rejected_calls=1,failed_calls=0} [cmdstat eval] - assert_match {*count=2*} [errorstat ERR] - assert_equal [s total_error_replies] 2 - } - - test {errorstats: failed call NOSCRIPT error} { - r config resetstat - assert_equal [s total_error_replies] 0 - assert_match {} [errorstat NOSCRIPT] - catch {r evalsha NotValidShaSUM 0} e - assert_match {NOSCRIPT*} $e - assert_match {*count=1*} [errorstat NOSCRIPT] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat evalsha] - assert_equal [s total_error_replies] 1 - r config resetstat - assert_match {} [errorstat NOSCRIPT] - } - - test {errorstats: failed call NOGROUP error} { - r config resetstat - assert_match {} [errorstat NOGROUP] - r del mystream - r XADD mystream * f v - catch {r XGROUP CREATECONSUMER mystream mygroup consumer} e - assert_match {NOGROUP*} $e - assert_match {*count=1*} [errorstat NOGROUP] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup\\|createconsumer] - r config resetstat - assert_match {} [errorstat NOGROUP] - } - - test {errorstats: rejected call unknown command} { - r config resetstat - assert_equal [s total_error_replies] 0 - assert_match {} [errorstat ERR] - catch {r asdf} e - assert_match {ERR unknown*} $e - assert_match {*count=1*} [errorstat ERR] - assert_equal [s total_error_replies] 1 - r config resetstat - assert_match {} [errorstat ERR] - } - - test {errorstats: rejected call within MULTI/EXEC} { - r config resetstat - assert_equal [s total_error_replies] 0 - assert_match {} [errorstat ERR] - r multi - catch {r set} e - assert_match {ERR wrong number of arguments for 'set' command} $e - catch {r exec} e - assert_match {EXECABORT*} $e - assert_match {*count=1*} [errorstat ERR] - assert_match {*count=1*} [errorstat EXECABORT] - assert_equal [s total_error_replies] 2 - assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat multi] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat exec] - assert_equal [s total_error_replies] 2 - r config resetstat - assert_match {} [errorstat ERR] - } - - test {errorstats: rejected call due to wrong arity} { - r config resetstat - assert_equal [s total_error_replies] 0 - assert_match {} [errorstat ERR] - catch {r set k} e - assert_match {ERR wrong number of arguments for 'set' command} $e - assert_match {*count=1*} [errorstat ERR] - assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set] - # ensure that after a rejected command, valid ones are counted properly - r set k1 v1 - r set k2 v2 - assert_match {calls=2,*,rejected_calls=1,failed_calls=0} [cmdstat set] - assert_equal [s total_error_replies] 1 - } - - test {errorstats: rejected call by OOM error} { - r config resetstat - assert_equal [s total_error_replies] 0 - assert_match {} [errorstat OOM] - r config set maxmemory 1 - catch {r set a b} e - assert_match {OOM*} $e - assert_match {*count=1*} [errorstat OOM] - assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set] - assert_equal [s total_error_replies] 1 - r config resetstat - assert_match {} [errorstat OOM] - r config set maxmemory 0 - } - - test {errorstats: rejected call by authorization error} { - r config resetstat - assert_equal [s total_error_replies] 0 - assert_match {} [errorstat NOPERM] - r ACL SETUSER alice on >p1pp0 ~cached:* +get +info +config - r auth alice p1pp0 - catch {r set a b} e - assert_match {NOPERM*} $e - assert_match {*count=1*} [errorstat NOPERM] - assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set] - assert_equal [s total_error_replies] 1 - r config resetstat - assert_match {} [errorstat NOPERM] - r auth default "" - } - - test {errorstats: blocking commands} { - r config resetstat - set rd [redis_deferring_client] - $rd client id - set rd_id [$rd read] - r del list1{t} - - $rd blpop list1{t} 0 - wait_for_blocked_client - r client unblock $rd_id error - assert_error {UNBLOCKED*} {$rd read} - assert_match {*count=1*} [errorstat UNBLOCKED] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat blpop] - assert_equal [s total_error_replies] 1 - $rd close - } - - test {errorstats: limit errors will not increase indefinitely} { - r config resetstat - for {set j 1} {$j <= 1100} {incr j} { - assert_error "$j my error message" { - r eval {return redis.error_reply(string.format('%s my error message', ARGV[1]))} 0 $j - } - } - - assert_equal [count_log_message 0 "Errorstats stopped adding new errors"] 1 - assert_equal [count_log_message 0 "Current errors code list"] 1 - assert_equal "count=1" [errorstat ERRORSTATS_DISABLED] - - # Since we currently have no metrics exposed for server.errors, we use lazyfree - # to verify that we only have 128 errors. - wait_for_condition 50 100 { - [s lazyfreed_objects] eq 128 - } else { - fail "errorstats resetstat lazyfree error" - } - } - - test {stats: eventloop metrics} { - set info1 [r info stats] - set cycle1 [getInfoProperty $info1 eventloop_cycles] - set el_sum1 [getInfoProperty $info1 eventloop_duration_sum] - set cmd_sum1 [getInfoProperty $info1 eventloop_duration_cmd_sum] - assert_morethan $cycle1 0 - assert_morethan $el_sum1 0 - assert_morethan $cmd_sum1 0 - after 110 ;# default hz is 10, wait for a cron tick. - set info2 [r info stats] - set cycle2 [getInfoProperty $info2 eventloop_cycles] - set el_sum2 [getInfoProperty $info2 eventloop_duration_sum] - set cmd_sum2 [getInfoProperty $info2 eventloop_duration_cmd_sum] - if {$::verbose} { puts "eventloop metrics cycle1: $cycle1, cycle2: $cycle2" } - assert_morethan $cycle2 $cycle1 - assert_lessthan $cycle2 [expr $cycle1+10] ;# we expect 2 or 3 cycles here, but allow some tolerance - if {$::verbose} { puts "eventloop metrics el_sum1: $el_sum1, el_sum2: $el_sum2" } - assert_morethan $el_sum2 $el_sum1 - assert_lessthan $el_sum2 [expr $el_sum1+100000] ;# we expect roughly 100ms here, but allow some tolerance - if {$::verbose} { puts "eventloop metrics cmd_sum1: $cmd_sum1, cmd_sum2: $cmd_sum2" } - assert_morethan $cmd_sum2 $cmd_sum1 - assert_lessthan $cmd_sum2 [expr $cmd_sum1+15000] ;# we expect about tens of ms here, but allow some tolerance - } {} {debug_defrag:skip} - - test {stats: instantaneous metrics} { - r config resetstat - - set multiplier 1 - if {[r config get io-threads] > 1} { - # the IO threads also have clients cron job now, and default hz is 10, - # so the IO thread that have the current client will trigger the main - # thread to run clients cron job, that will also count as a cron tick - set multiplier 2 - } - - set retries 0 - for {set retries 1} {$retries < 4} {incr retries} { - after 1600 ;# hz is 10, wait for 16 cron tick so that sample array is fulfilled - set value [s instantaneous_eventloop_cycles_per_sec] - if {$value > 0} break - } - - assert_lessthan $retries 4 - if {$::verbose} { puts "instantaneous metrics instantaneous_eventloop_cycles_per_sec: $value" } - assert_morethan $value 0 - assert_lessthan $value [expr $retries*15*$multiplier] ;# default hz is 10 - set value [s instantaneous_eventloop_duration_usec] - if {$::verbose} { puts "instantaneous metrics instantaneous_eventloop_duration_usec: $value" } - assert_morethan $value 0 - assert_lessthan $value [expr $retries*22000] ;# default hz is 10, so duration < 1000 / 10, allow some tolerance - } {} {debug_defrag:skip} - - test {stats: debug metrics} { - # make sure debug info is hidden - set info [r info] - assert_equal [getInfoProperty $info eventloop_duration_aof_sum] {} - set info_all [r info all] - assert_equal [getInfoProperty $info_all eventloop_duration_aof_sum] {} - - set info1 [r info debug] - - set aof1 [getInfoProperty $info1 eventloop_duration_aof_sum] - assert {$aof1 >= 0} - set cron1 [getInfoProperty $info1 eventloop_duration_cron_sum] - assert {$cron1 > 0} - set cycle_max1 [getInfoProperty $info1 eventloop_cmd_per_cycle_max] - assert {$cycle_max1 > 0} - set duration_max1 [getInfoProperty $info1 eventloop_duration_max] - assert {$duration_max1 > 0} - - after 110 ;# hz is 10, wait for a cron tick. - set info2 [r info debug] - - set aof2 [getInfoProperty $info2 eventloop_duration_aof_sum] - assert {$aof2 >= $aof1} ;# AOF is disabled, we expect $aof2 == $aof1, but allow some tolerance. - set cron2 [getInfoProperty $info2 eventloop_duration_cron_sum] - assert_morethan $cron2 $cron1 - set cycle_max2 [getInfoProperty $info2 eventloop_cmd_per_cycle_max] - assert {$cycle_max2 >= $cycle_max1} - set duration_max2 [getInfoProperty $info2 eventloop_duration_max] - assert {$duration_max2 >= $duration_max1} - } - - test {stats: client input and output buffer limit disconnections} { - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - r config resetstat - set info [r info stats] - assert_equal [getInfoProperty $info client_query_buffer_limit_disconnections] {0} - assert_equal [getInfoProperty $info client_output_buffer_limit_disconnections] {0} - # set qbuf limit to minimum to test stat - set org_qbuf_limit [lindex [r config get client-query-buffer-limit] 1] - r config set client-query-buffer-limit 1048576 - catch {r set key [string repeat a 1048576]} - set info [r info stats] - assert_equal [getInfoProperty $info client_query_buffer_limit_disconnections] {1} - r config set client-query-buffer-limit $org_qbuf_limit - # set outbuf limit to just 10 to test stat - set org_outbuf_limit [lindex [r config get client-output-buffer-limit] 1] - r config set client-output-buffer-limit "normal 10 0 0" - r set key [string repeat a 100000] ;# to trigger output buffer limit check this needs to be big - catch {r get key} - r config set client-output-buffer-limit $org_outbuf_limit - set info [r info stats] - assert_equal [getInfoProperty $info client_output_buffer_limit_disconnections] {1} - } {} {logreqres:skip} ;# same as obuf-limits.tcl, skip logreqres - - test {clients: pubsub clients} { - set info [r info clients] - assert_equal [getInfoProperty $info pubsub_clients] {0} - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - # basic count - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {1} [subscribe $rd2 {chan2}] - set info [r info clients] - assert_equal [getInfoProperty $info pubsub_clients] {2} - # unsubscribe non existing channel - assert_equal {1} [unsubscribe $rd2 {non-exist-chan}] - set info [r info clients] - assert_equal [getInfoProperty $info pubsub_clients] {2} - # count change when client unsubscribe all channels - assert_equal {0} [unsubscribe $rd2 {chan2}] - set info [r info clients] - assert_equal [getInfoProperty $info pubsub_clients] {1} - # non-pubsub clients should not be involved - assert_equal {0} [unsubscribe $rd2 {non-exist-chan}] - set info [r info clients] - assert_equal [getInfoProperty $info pubsub_clients] {1} - # close all clients - $rd1 close - $rd2 close - wait_for_condition 100 50 { - [getInfoProperty [r info clients] pubsub_clients] eq {0} - } else { - fail "pubsub clients did not clear" - } - } - - test {clients: watching clients} { - set r2 [redis_client] - assert_equal [s watching_clients] 0 - assert_equal [s total_watched_keys] 0 - assert_match {*watch=0*} [r client info] - assert_match {*watch=0*} [$r2 client info] - # count after watch key - $r2 watch key - assert_equal [s watching_clients] 1 - assert_equal [s total_watched_keys] 1 - assert_match {*watch=0*} [r client info] - assert_match {*watch=1*} [$r2 client info] - # the same client watch the same key has no effect - $r2 watch key - assert_equal [s watching_clients] 1 - assert_equal [s total_watched_keys] 1 - assert_match {*watch=0*} [r client info] - assert_match {*watch=1*} [$r2 client info] - # different client watch different key - r watch key2 - assert_equal [s watching_clients] 2 - assert_equal [s total_watched_keys] 2 - assert_match {*watch=1*} [$r2 client info] - assert_match {*watch=1*} [r client info] - # count after unwatch - r unwatch - assert_equal [s watching_clients] 1 - assert_equal [s total_watched_keys] 1 - assert_match {*watch=0*} [r client info] - assert_match {*watch=1*} [$r2 client info] - $r2 unwatch - assert_equal [s watching_clients] 0 - assert_equal [s total_watched_keys] 0 - assert_match {*watch=0*} [r client info] - assert_match {*watch=0*} [$r2 client info] - - # count after watch/multi/exec - $r2 watch key - assert_equal [s watching_clients] 1 - $r2 multi - $r2 exec - assert_equal [s watching_clients] 0 - # count after watch/multi/discard - $r2 watch key - assert_equal [s watching_clients] 1 - $r2 multi - $r2 discard - assert_equal [s watching_clients] 0 - # discard without multi has no effect - $r2 watch key - assert_equal [s watching_clients] 1 - catch {$r2 discard} e - assert_equal [s watching_clients] 1 - # unwatch without watch has no effect - r unwatch - assert_equal [s watching_clients] 1 - # after disconnect, since close may arrive later, or the client may - # be freed asynchronously, we use a wait_for_condition - $r2 close - wait_for_watched_clients_count 0 - } - } -} - -start_server {tags {"info" "external:skip"}} { - test {memory: database and pubsub overhead and rehashing dict count} { - r flushall - - # Better not set ht0_size to 4 since there is a probability that all - # keys will end up in the same bucket and rehashing will ended instantly. - set ht0_size [expr 1 << 3] - # ht1 size is twice the size of ht0 - set ht1_size [expr $ht0_size << 1] - - populate [expr $ht0_size - 1] - - # Verify rehashing is not ongoing - wait_for_condition 100 10 { - [dict get [r memory stats] db.dict.rehashing.count] == 0 - } else { - fail "Rehashing did not finish in time" - } - - # Verify the info reflects steady state - set info_mem [r info memory] - set mem_stats [r memory stats] - assert_equal [getInfoProperty $info_mem mem_overhead_db_hashtable_rehashing] {0} - set ptr_size [expr {[s arch_bits] == 32 ? 4 : 8}] - assert_equal [dict get $mem_stats overhead.db.hashtable.lut] [expr $ht0_size * $ptr_size] - assert_equal [dict get $mem_stats overhead.db.hashtable.rehashing] {0} - assert_equal [dict get $mem_stats db.dict.rehashing.count] {0} - - # Set 2 more keys to trigger rehashing - # get the info within a transaction to make sure the rehashing is not completed - r multi - r set this_will_reach_max_load_factor 1 - r set this_must_be_rehashed 1 - r info memory - r memory stats - set res [r exec] - set info_mem [lindex $res 2] - set mem_stats [lindex $res 3] - - # Verify the info reflects rehashing state - assert_range [getInfoProperty $info_mem mem_overhead_db_hashtable_rehashing] 1 [expr $ht0_size * $ptr_size] - assert_equal [dict get $mem_stats overhead.db.hashtable.lut] [expr ($ht0_size + $ht1_size) * $ptr_size] - assert_equal [dict get $mem_stats overhead.db.hashtable.rehashing] [expr $ht0_size * $ptr_size] - assert_equal [dict get $mem_stats db.dict.rehashing.count] {1} - } - - test {memory: used_memory_peak_time is updated when used_memory_peak is updated} { - r flushall - - # Add a large string to trigger memory peak tracking - set time_before_add_large_str [clock seconds] - r set large_str [string repeat "a" 1000000] - assert {[s used_memory_peak_time] >= $time_before_add_large_str} - - r del large_str - - # Note: this info command must be called after the del operation to ensure - # the peak memory measurement isn't affected by the info command itself - # potentially increasing peak memory. - set peak_value [s used_memory_peak] - - # Add a small string, which cannot exceed the previous peak value - r set small_str [string repeat "a" 1000] - assert {[s used_memory_peak] == $peak_value} - } -} - -start_cluster 1 0 {tags {external:skip cluster}} { - test "Verify that LUT overhead is properly updated when dicts are emptied or reused (issue #13973)" { - R 0 set k v ;# Make dbs overhead displayed - set info_mem [r memory stats] - set overhead_main [dict get $info_mem db.0 overhead.hashtable.main] - set overhead_expires [dict get $info_mem db.0 overhead.hashtable.expires] - assert_range $overhead_main 1 5000 - assert_range $overhead_expires 1 1000 - - # In cluster mode, we use KVSTORE_FREE_EMPTY_DICTS to ensure that dicts - # are freed when they are emptied. This test verifies that after a dict - # is cleared, the lut overhead is properly updated, preventing it from - # growing indefinitely. - for {set j 1} {$j <= 500} {incr j} { - R 0 set k v - R 0 del k - } - R 0 set k v ;# Make dbs overhead displayed - set info_mem [r memory stats] - assert_equal [dict get $info_mem db.0 overhead.hashtable.main] $overhead_main - assert_equal [dict get $info_mem db.0 overhead.hashtable.expires] $overhead_expires - } -} diff --git a/examples/redis-unstable/tests/unit/introspection-2.tcl b/examples/redis-unstable/tests/unit/introspection-2.tcl deleted file mode 100644 index 1e98fdb..0000000 --- a/examples/redis-unstable/tests/unit/introspection-2.tcl +++ /dev/null @@ -1,281 +0,0 @@ -proc cmdstat {cmd} { - return [cmdrstat $cmd r] -} - -proc getlru {key} { - set objinfo [r debug object $key] - foreach info $objinfo { - set kvinfo [split $info ":"] - if {[string compare [lindex $kvinfo 0] "lru"] == 0} { - return [lindex $kvinfo 1] - } - } - fail "Can't get LRU info with DEBUG OBJECT" -} - -start_server {tags {"introspection"}} { - test {The microsecond part of the TIME command will not overflow} { - set now [r time] - set microseconds [lindex $now 1] - assert_morethan $microseconds 0 - assert_lessthan $microseconds 1000000 - } - - test {TTL, TYPE and EXISTS do not alter the last access time of a key} { - r set foo bar - after 3000 - r ttl foo - r type foo - r exists foo - assert {[r object idletime foo] >= 2} - } - - test {TOUCH alters the last access time of a key} { - r set foo bar - after 3000 - r touch foo - assert {[r object idletime foo] < 2} - } - - test {Operations in no-touch mode do not alter the last access time of a key} { - r set foo bar - r client no-touch on - set oldlru [getlru foo] - after 1100 - r get foo - set newlru [getlru foo] - assert_equal $newlru $oldlru - r client no-touch off - r get foo - set newlru [getlru foo] - assert_morethan $newlru $oldlru - } {} {needs:debug} - - test {Operations in no-touch mode TOUCH alters the last access time of a key} { - r set foo bar - r client no-touch on - set oldlru [getlru foo] - after 1100 - r touch foo - set newlru [getlru foo] - assert_morethan $newlru $oldlru - } {} {needs:debug} - - test {Operations in no-touch mode TOUCH from script alters the last access time of a key} { - r set foo bar - r client no-touch on - set oldlru [getlru foo] - after 1100 - assert_equal {1} [r eval "return redis.call('touch', 'foo')" 0] - set newlru [getlru foo] - assert_morethan $newlru $oldlru - } {} {needs:debug} - - test {TOUCH returns the number of existing keys specified} { - r flushdb - r set key1{t} 1 - r set key2{t} 2 - r touch key0{t} key1{t} key2{t} key3{t} - } 2 - - test {command stats for GEOADD} { - r config resetstat - r GEOADD foo 0 0 bar - assert_match {*calls=1,*} [cmdstat geoadd] - assert_match {} [cmdstat zadd] - } {} {needs:config-resetstat} - - test {errors stats for GEOADD} { - r config resetstat - # make sure geo command will failed - r set foo 1 - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r GEOADD foo 0 0 bar} - assert_match {*calls=1*,rejected_calls=0,failed_calls=1*} [cmdstat geoadd] - assert_match {} [cmdstat zadd] - } {} {needs:config-resetstat} - - test {command stats for EXPIRE} { - r config resetstat - r SET foo bar - r EXPIRE foo 0 - assert_match {*calls=1,*} [cmdstat expire] - assert_match {} [cmdstat del] - } {} {needs:config-resetstat} - - test {command stats for BRPOP} { - r config resetstat - r LPUSH list foo - r BRPOP list 0 - assert_match {*calls=1,*} [cmdstat brpop] - assert_match {} [cmdstat rpop] - } {} {needs:config-resetstat} - - test {command stats for MULTI} { - r config resetstat - r MULTI - r set foo{t} bar - r GEOADD foo2{t} 0 0 bar - r EXPIRE foo2{t} 0 - r EXEC - assert_match {*calls=1,*} [cmdstat multi] - assert_match {*calls=1,*} [cmdstat exec] - assert_match {*calls=1,*} [cmdstat set] - assert_match {*calls=1,*} [cmdstat expire] - assert_match {*calls=1,*} [cmdstat geoadd] - } {} {needs:config-resetstat} - - test {command stats for scripts} { - r config resetstat - r set mykey myval - r eval { - redis.call('set', KEYS[1], 0) - redis.call('expire', KEYS[1], 0) - redis.call('geoadd', KEYS[1], 0, 0, "bar") - } 1 mykey - assert_match {*calls=1,*} [cmdstat eval] - assert_match {*calls=2,*} [cmdstat set] - assert_match {*calls=1,*} [cmdstat expire] - assert_match {*calls=1,*} [cmdstat geoadd] - } {} {needs:config-resetstat} - - test {COMMAND COUNT get total number of Redis commands} { - assert_morethan [r command count] 0 - } - - test {COMMAND GETKEYS GET} { - assert_equal {key} [r command getkeys get key] - } - - test {COMMAND GETKEYSANDFLAGS} { - assert_equal {{k1 {OW update}}} [r command getkeysandflags set k1 v1] - assert_equal {{k1 {OW update}} {k2 {OW update}}} [r command getkeysandflags mset k1 v1 k2 v2] - assert_equal {{k1 {RW access delete}} {k2 {RW insert}}} [r command getkeysandflags LMOVE k1 k2 left right] - assert_equal {{k1 {RO access}} {k2 {OW update}}} [r command getkeysandflags sort k1 store k2] - assert_equal {{k1 {RW update}}} [r command getkeysandflags set k1 v1 IFEQ v1] - assert_equal {{k1 {RW access update}}} [r command getkeysandflags set k1 v1 GET] - assert_equal {{k1 {RM delete}}} [r command getkeysandflags delex k1] - assert_equal {{k1 {RW delete}}} [r command getkeysandflags delex k1 ifeq v1] - } - - test {COMMAND GETKEYSANDFLAGS invalid args} { - assert_error "ERR Invalid arguments*" {r command getkeysandflags ZINTERSTORE zz 1443677133621497600 asdf} - } - - test {COMMAND GETKEYSANDFLAGS MSETEX} { - assert_equal {{k1 {OW update}}} [r command getkeysandflags msetex 1 k1 v1 ex 10] - assert_equal {{k1 {OW update}} {k2 {OW update}}} [r command getkeysandflags msetex 2 k1 v1 k2 v2 ex 10] - assert_equal {{k1 {OW update}} {k2 {OW update}} {k3 {OW update}}} [r command getkeysandflags msetex 3 k1 v1 k2 v2 k3 v3 ex 10] - assert_equal {{k1 {OW update}} {k2 {OW update}}} [r command getkeysandflags msetex 2 k1 v1 k2 v2 keepttl] - assert_equal {{k1 {OW update}} {k2 {OW update}}} [r command getkeysandflags msetex 2 k1 v1 k2 v2 ex 10 nx] - } - - test {COMMAND GETKEYS MEMORY USAGE} { - assert_equal {key} [r command getkeys memory usage key] - } - - test {COMMAND GETKEYS XGROUP} { - assert_equal {key} [r command getkeys xgroup create key groupname $] - } - - test {COMMAND GETKEYS EVAL with keys} { - assert_equal {key} [r command getkeys eval "return 1" 1 key] - } - - test {COMMAND GETKEYS EVAL without keys} { - assert_equal {} [r command getkeys eval "return 1" 0] - } - - test {COMMAND GETKEYS LCS} { - assert_equal {key1 key2} [r command getkeys lcs key1 key2] - } - - test {COMMAND GETKEYS MORE THAN 256 KEYS} { - set all_keys [list] - set numkeys 260 - for {set i 1} {$i <= $numkeys} {incr i} { - lappend all_keys "key$i" - } - set all_keys_with_target [linsert $all_keys 0 target] - # we are using ZUNIONSTORE command since in order to reproduce allocation of a new buffer in getKeysPrepareResult - # when numkeys in result > 0 - # we need a command that the final number of keys is not known in the first call to getKeysPrepareResult - # before the fix in that case data of old buffer was not copied to the new result buffer - # causing all previous keys (numkeys) data to be uninitialize - assert_equal $all_keys_with_target [r command getkeys ZUNIONSTORE target $numkeys {*}$all_keys] - } - - test "COMMAND LIST syntax error" { - assert_error "ERR syntax error*" {r command list bad_arg} - assert_error "ERR syntax error*" {r command list filterby bad_arg} - assert_error "ERR syntax error*" {r command list filterby bad_arg bad_arg2} - } - - test "COMMAND LIST WITHOUT FILTERBY" { - set commands [r command list] - assert_not_equal [lsearch $commands "set"] -1 - assert_not_equal [lsearch $commands "client|list"] -1 - } - - test "COMMAND LIST FILTERBY ACLCAT against non existing category" { - assert_equal {} [r command list filterby aclcat non_existing_category] - } - - test "COMMAND LIST FILTERBY ACLCAT - list all commands/subcommands" { - set commands [r command list filterby aclcat scripting] - assert_not_equal [lsearch $commands "eval"] -1 - assert_not_equal [lsearch $commands "script|kill"] -1 - - # Negative check, a command that should not be here - assert_equal [lsearch $commands "set"] -1 - } - - test "COMMAND LIST FILTERBY PATTERN - list all commands/subcommands" { - # Exact command match. - assert_equal {set} [r command list filterby pattern set] - assert_equal {get} [r command list filterby pattern get] - - # Return the parent command and all the subcommands below it. - set commands [r command list filterby pattern config*] - assert_not_equal [lsearch $commands "config"] -1 - assert_not_equal [lsearch $commands "config|get"] -1 - - # We can filter subcommands under a parent command. - set commands [r command list filterby pattern config|*re*] - assert_not_equal [lsearch $commands "config|resetstat"] -1 - assert_not_equal [lsearch $commands "config|rewrite"] -1 - - # We can filter subcommands across parent commands. - set commands [r command list filterby pattern cl*help] - assert_not_equal [lsearch $commands "client|help"] -1 - assert_not_equal [lsearch $commands "cluster|help"] -1 - - # Negative check, command that doesn't exist. - assert_equal {} [r command list filterby pattern non_exists] - assert_equal {} [r command list filterby pattern non_exists*] - } - - test "COMMAND LIST FILTERBY MODULE against non existing module" { - # This should be empty, the real one is in subcommands.tcl - assert_equal {} [r command list filterby module non_existing_module] - } - - test {COMMAND INFO of invalid subcommands} { - assert_equal {{}} [r command info get|key] - assert_equal {{}} [r command info config|get|key] - } - - foreach cmd {SET GET MSET BITFIELD LMOVE LPOP BLPOP PING MEMORY MEMORY|USAGE RENAME GEORADIUS_RO} { - test "$cmd command will not be marked with movablekeys" { - set info [lindex [r command info $cmd] 0] - assert_no_match {*movablekeys*} [lindex $info 2] - } - } - - foreach cmd {ZUNIONSTORE XREAD EVAL SORT SORT_RO MIGRATE GEORADIUS} { - test "$cmd command is marked with movablekeys" { - set info [lindex [r command info $cmd] 0] - assert_match {*movablekeys*} [lindex $info 2] - } - } - -} diff --git a/examples/redis-unstable/tests/unit/introspection.tcl b/examples/redis-unstable/tests/unit/introspection.tcl deleted file mode 100644 index 161ebb8..0000000 --- a/examples/redis-unstable/tests/unit/introspection.tcl +++ /dev/null @@ -1,1101 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of the Redis Source Available License 2.0 -# (RSALv2) or the Server Side Public License v1 (SSPLv1). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -start_server {tags {"introspection"}} { - test "PING" { - assert_equal {PONG} [r ping] - assert_equal {redis} [r ping redis] - assert_error {*wrong number of arguments for 'ping' command} {r ping hello redis} - } - - test {CLIENT LIST} { - set client_list [r client list] - if {[lindex [r config get io-threads] 1] == 1} { - assert_match {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|list user=* redir=-1 resp=* lib-name=* lib-ver=* io-thread=* tot-net-in=* tot-net-out=* tot-cmds=*} $client_list - } else { - assert_match {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=0 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|list user=* redir=-1 resp=* lib-name=* lib-ver=* io-thread=* tot-net-in=* tot-net-out=* tot-cmds=*} $client_list - } - } - - test {CLIENT LIST with IDs} { - set myid [r client id] - set cl [split [r client list id $myid] "\r\n"] - assert_match "id=$myid * cmd=client|list *" [lindex $cl 0] - } - - test {CLIENT INFO} { - set client [r client info] - if {[lindex [r config get io-threads] 1] == 1} { - assert_match {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|info user=* redir=-1 resp=* lib-name=* lib-ver=* io-thread=* tot-net-in=* tot-net-out=* tot-cmds=*} $client - } else { - assert_match {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=0 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|info user=* redir=-1 resp=* lib-name=* lib-ver=* io-thread=* tot-net-in=* tot-net-out=* tot-cmds=*} $client - } - } - - proc get_field_in_client_info {info field} { - set info [string trim $info] - foreach item [split $info " "] { - set kv [split $item "="] - set k [lindex $kv 0] - if {[string match $field $k]} { - return [lindex $kv 1] - } - } - return "" - } - - proc get_field_in_client_list {id client_list filed} { - set list [split $client_list "\r\n"] - foreach info $list { - if {[string match "id=$id *" $info] } { - return [get_field_in_client_info $info $filed] - } - } - return "" - } - - test {CLIENT INFO input/output/cmds-processed stats} { - set info1 [r client info] - set input1 [get_field_in_client_info $info1 "tot-net-in"] - set output1 [get_field_in_client_info $info1 "tot-net-out"] - set cmd1 [get_field_in_client_info $info1 "tot-cmds"] - - # Run a command by that client and test if the stats change correctly - set info2 [r client info] - set input2 [get_field_in_client_info $info2 "tot-net-in"] - set output2 [get_field_in_client_info $info2 "tot-net-out"] - set cmd2 [get_field_in_client_info $info2 "tot-cmds"] - - # NOTE if CLIENT INFO changes it's stats the output_bytes here and in the - # other related tests will need to be updated. - set input_bytes 26 ; # CLIENT INFO request - set output_bytes 300 ; # CLIENT INFO result - set cmds_processed 1 ; # processed the command CLIENT INFO - assert_equal [expr $input1+$input_bytes] $input2 - assert {[expr $output1+$output_bytes] < $output2} - assert_equal [expr $cmd1+$cmds_processed] $cmd2 - } - - test {CLIENT INFO input/output/cmds-processed stats for blocking command} { - r del mylist - set rd [redis_deferring_client] - $rd client id - set rd_id [$rd read] - - set info_list [r client list] - set input1 [get_field_in_client_list $rd_id $info_list "tot-net-in"] - set output1 [get_field_in_client_list $rd_id $info_list "tot-net-out"] - set cmd1 [get_field_in_client_list $rd_id $info_list "tot-cmds"] - $rd blpop mylist 0 - - # Make sure to wait for the $rd client to be blocked - wait_for_blocked_client - - # Check if input stats have changed for $rd. Since command is blocking - # and has not been unblocked yet we expect no change in output/cmds-processed - # stats. - set info_list [r client list] - set input2 [get_field_in_client_list $rd_id $info_list "tot-net-in"] - set output2 [get_field_in_client_list $rd_id $info_list "tot-net-out"] - set cmd2 [get_field_in_client_list $rd_id $info_list "tot-cmds"] - assert_equal [expr $input1+34] $input2 - assert_equal $output1 $output2 - assert_equal $cmd1 $cmd2 - - # Unblock the $rd client (which will send a reply and thus update output - # and cmd-processed stats). - r lpush mylist a - - # Note that the per-client stats are from the POV of the server. The - # deferred client may have not read the response yet, but the stats - # are still updated. - set info_list [r client list] - set input3 [get_field_in_client_list $rd_id $info_list "tot-net-in"] - set output3 [get_field_in_client_list $rd_id $info_list "tot-net-out"] - set cmd3 [get_field_in_client_list $rd_id $info_list "tot-cmds"] - assert_equal $input2 $input3 - assert_equal [expr $output2+23] $output3 - assert_equal [expr $cmd2+1] $cmd3 - - $rd close - } - - test {CLIENT INFO cmds-processed stats for recursive command} { - set info [r client info] - set tot_cmd_before [get_field_in_client_info $info "tot-cmds"] - r eval "redis.call('ping')" 0 - set info [r client info] - set tot_cmd_after [get_field_in_client_info $info "tot-cmds"] - - # We executed 3 commands - EVAL, which in turn executed PING and finally CLIENT INFO - assert_equal [expr $tot_cmd_before+3] $tot_cmd_after - } - - test {CLIENT KILL with illegal arguments} { - assert_error "ERR wrong number of arguments for 'client|kill' command" {r client kill} - assert_error "ERR syntax error*" {r client kill id 10 wrong_arg} - - assert_error "ERR *greater than 0*" {r client kill id str} - assert_error "ERR *greater than 0*" {r client kill id -1} - assert_error "ERR *greater than 0*" {r client kill id 0} - - assert_error "ERR Unknown client type*" {r client kill type wrong_type} - - assert_error "ERR No such user*" {r client kill user wrong_user} - - assert_error "ERR syntax error*" {r client kill skipme yes_or_no} - - assert_error "ERR *not an integer or out of range*" {r client kill maxage str} - assert_error "ERR *not an integer or out of range*" {r client kill maxage 9999999999999999999} - assert_error "ERR *greater than 0*" {r client kill maxage -1} - } - - test {CLIENT KILL maxAGE will kill old clients} { - # This test is very likely to do a false positive if the execute time - # takes longer than the max age, so give it a few more chances. Go with - # 3 retries of increasing sleep_time, i.e. start with 2s, then go 4s, 8s. - set sleep_time 2 - for {set i 0} {$i < 3} {incr i} { - set rd1 [redis_deferring_client] - r debug sleep $sleep_time - set rd2 [redis_deferring_client] - r acl setuser dummy on nopass +ping - $rd1 auth dummy "" - $rd1 read - $rd2 auth dummy "" - $rd2 read - - # Should kill rd1 but not rd2 - set max_age [expr $sleep_time / 2] - set res [r client kill user dummy maxage $max_age] - if {$res == 1} { - break - } else { - # Clean up and try again next time - set sleep_time [expr $sleep_time * 2] - $rd1 close - $rd2 close - } - - } ;# for - - if {$::verbose} { puts "CLIENT KILL maxAGE will kill old clients test attempts: $i" } - assert_equal $res 1 - - # rd2 should still be connected - $rd2 ping - assert_equal "PONG" [$rd2 read] - - $rd1 close - $rd2 close - } {0} {"needs:debug"} - - test {CLIENT KILL SKIPME YES/NO will kill all clients} { - # Kill all clients except `me` - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set connected_clients [s connected_clients] - assert {$connected_clients >= 3} - set res [r client kill skipme yes] - assert {$res == $connected_clients - 1} - wait_for_condition 1000 10 { - [s connected_clients] eq 1 - } else { - fail "Can't kill all clients except the current one" - } - - # Kill all clients, including `me` - set rd3 [redis_deferring_client] - set rd4 [redis_deferring_client] - set connected_clients [s connected_clients] - assert {$connected_clients == 3} - set res [r client kill skipme no] - assert_equal $res $connected_clients - - # After killing `me`, the first ping will throw an error - assert_error "*I/O error*" {r ping} - assert_equal "PONG" [r ping] - - $rd1 close - $rd2 close - $rd3 close - $rd4 close - } - - test {CLIENT command unhappy path coverage} { - assert_error "ERR*wrong number of arguments*" {r client caching} - assert_error "ERR*when the client is in tracking mode*" {r client caching maybe} - assert_error "ERR*syntax*" {r client no-evict wrongInput} - assert_error "ERR*syntax*" {r client reply wrongInput} - assert_error "ERR*syntax*" {r client tracking wrongInput} - assert_error "ERR*syntax*" {r client tracking on wrongInput} - assert_error "ERR*when the client is in tracking mode*" {r client caching off} - assert_error "ERR*when the client is in tracking mode*" {r client caching on} - - r CLIENT TRACKING ON optout - assert_error "ERR*syntax*" {r client caching on} - - r CLIENT TRACKING off optout - assert_error "ERR*when the client is in tracking mode*" {r client caching on} - - assert_error "ERR*No such*" {r client kill 000.123.321.567:0000} - assert_error "ERR*No such*" {r client kill 127.0.0.1:} - - assert_error "ERR*timeout is not an integer*" {r client pause abc} - assert_error "ERR timeout is negative" {r client pause -1} - } - - test "CLIENT KILL close the client connection during bgsave" { - # Start a slow bgsave, trigger an active fork. - r flushall - r set k v - r config set rdb-key-save-delay 10000000 - r bgsave - wait_for_condition 1000 10 { - [s rdb_bgsave_in_progress] eq 1 - } else { - fail "bgsave did not start in time" - } - - # Kill (close) the connection - r client kill skipme no - - # In the past, client connections needed to wait for bgsave - # to end before actually closing, now they are closed immediately. - assert_error "*I/O error*" {r ping} ;# get the error very quickly - assert_equal "PONG" [r ping] - - # Make sure the bgsave is still in progress - assert_equal [s rdb_bgsave_in_progress] 1 - - # Stop the child before we proceed to the next test - r config set rdb-key-save-delay 0 - r flushall - wait_for_condition 1000 10 { - [s rdb_bgsave_in_progress] eq 0 - } else { - fail "bgsave did not stop in time" - } - } {} {needs:save} - - test "CLIENT REPLY OFF/ON: disable all commands reply" { - set rd [redis_deferring_client] - - # These replies were silenced. - $rd client reply off - $rd ping pong - $rd ping pong2 - - $rd client reply on - assert_equal {OK} [$rd read] - $rd ping pong3 - assert_equal {pong3} [$rd read] - - $rd close - } - - test "CLIENT REPLY SKIP: skip the next command reply" { - set rd [redis_deferring_client] - - # The first pong reply was silenced. - $rd client reply skip - $rd ping pong - - $rd ping pong2 - assert_equal {pong2} [$rd read] - - $rd close - } - - test "CLIENT REPLY ON: unset SKIP flag" { - set rd [redis_deferring_client] - - $rd client reply skip - $rd client reply on - assert_equal {OK} [$rd read] ;# OK from CLIENT REPLY ON command - - $rd ping - assert_equal {PONG} [$rd read] - - $rd close - } - - test {MONITOR can log executed commands} { - set rd [redis_deferring_client] - $rd monitor - assert_match {*OK*} [$rd read] - r set foo bar - r get foo - set res [list [$rd read] [$rd read]] - $rd close - set _ $res - } {*"set" "foo"*"get" "foo"*} - - test {MONITOR can log commands issued by the scripting engine} { - set rd [redis_deferring_client] - $rd monitor - $rd read ;# Discard the OK - r eval {redis.call('set',KEYS[1],ARGV[1])} 1 foo bar - assert_match {*eval*} [$rd read] - assert_match {*lua*"set"*"foo"*"bar"*} [$rd read] - $rd close - } - - test {MONITOR can log commands issued by functions} { - r function load replace {#!lua name=test - redis.register_function('test', function() return redis.call('set', 'foo', 'bar') end) - } - set rd [redis_deferring_client] - $rd monitor - $rd read ;# Discard the OK - r fcall test 0 - assert_match {*fcall*test*} [$rd read] - assert_match {*lua*"set"*"foo"*"bar"*} [$rd read] - $rd close - } - - test {MONITOR supports redacting command arguments} { - set rd [redis_deferring_client] - $rd monitor - $rd read ; # Discard the OK - - r migrate [srv 0 host] [srv 0 port] key 9 5000 - r migrate [srv 0 host] [srv 0 port] key 9 5000 AUTH user - r migrate [srv 0 host] [srv 0 port] key 9 5000 AUTH2 user password - catch {r auth not-real} _ - catch {r auth not-real not-a-password} _ - - assert_match {*"key"*"9"*"5000"*} [$rd read] - assert_match {*"key"*"9"*"5000"*"(redacted)"*} [$rd read] - assert_match {*"key"*"9"*"5000"*"(redacted)"*"(redacted)"*} [$rd read] - assert_match {*"auth"*"(redacted)"*} [$rd read] - assert_match {*"auth"*"(redacted)"*"(redacted)"*} [$rd read] - - foreach resp {3 2} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$resp == 3} {continue} - } elseif {$::force_resp3} { - if {$resp == 2} {continue} - } - catch {r hello $resp AUTH not-real not-a-password} _ - assert_match "*\"hello\"*\"$resp\"*\"AUTH\"*\"(redacted)\"*\"(redacted)\"*" [$rd read] - } - $rd close - } {0} {needs:repl} - - test {MONITOR correctly handles multi-exec cases} { - set rd [redis_deferring_client] - $rd monitor - $rd read ; # Discard the OK - - # Make sure multi-exec statements are ordered - # correctly - r multi - r set foo bar - r exec - assert_match {*"multi"*} [$rd read] - assert_match {*"set"*"foo"*"bar"*} [$rd read] - assert_match {*"exec"*} [$rd read] - - # Make sure we close multi statements on errors - r multi - catch {r syntax error} _ - catch {r exec} _ - - assert_match {*"multi"*} [$rd read] - assert_match {*"exec"*} [$rd read] - - $rd close - } - - test {MONITOR log blocked command only once} { - - # need to reconnect in order to reset the clients state - reconnect - - set rd [redis_deferring_client] - set bc [redis_deferring_client] - r del mylist - - $rd monitor - $rd read ; # Discard the OK - - $bc blpop mylist 0 - # make sure the blpop arrives first - $bc flush - after 100 - wait_for_blocked_clients_count 1 - r lpush mylist 1 - wait_for_blocked_clients_count 0 - r lpush mylist 2 - - # we expect to see the blpop on the monitor first - assert_match {*"blpop"*"mylist"*"0"*} [$rd read] - - # we scan out all the info commands on the monitor - set monitor_output [$rd read] - while { [string match {*"info"*} $monitor_output] } { - set monitor_output [$rd read] - } - - # we expect to locate the lpush right when the client was unblocked - assert_match {*"lpush"*"mylist"*"1"*} $monitor_output - - # we scan out all the info commands - set monitor_output [$rd read] - while { [string match {*"info"*} $monitor_output] } { - set monitor_output [$rd read] - } - - # we expect to see the next lpush and not duplicate blpop command - assert_match {*"lpush"*"mylist"*"2"*} $monitor_output - - $rd close - $bc close - } - - test {CLIENT GETNAME should return NIL if name is not assigned} { - r client getname - } {} - - test {CLIENT GETNAME check if name set correctly} { - r client setname testName - r client getName - } {testName} - - test {CLIENT LIST shows empty fields for unassigned names} { - r client list - } {*name= *} - - test {CLIENT SETNAME does not accept spaces} { - catch {r client setname "foo bar"} e - set e - } {ERR*} - - test {CLIENT SETNAME can assign a name to this connection} { - assert_equal [r client setname myname] {OK} - r client list - } {*name=myname*} - - test {CLIENT SETNAME can change the name of an existing connection} { - assert_equal [r client setname someothername] {OK} - r client list - } {*name=someothername*} - - test {After CLIENT SETNAME, connection can still be closed} { - set rd [redis_deferring_client] - $rd client setname foobar - assert_equal [$rd read] "OK" - assert_match {*foobar*} [r client list] - $rd close - # Now the client should no longer be listed - wait_for_condition 50 100 { - [string match {*foobar*} [r client list]] == 0 - } else { - fail "Client still listed in CLIENT LIST after SETNAME." - } - } - - test {CLIENT SETINFO can set a library name to this connection} { - r CLIENT SETINFO lib-name redis.py - r CLIENT SETINFO lib-ver 1.2.3 - r client info - } {*lib-name=redis.py lib-ver=1.2.3*} - - test {CLIENT SETINFO invalid args} { - assert_error {*wrong number of arguments*} {r CLIENT SETINFO lib-name} - assert_error {*cannot contain spaces*} {r CLIENT SETINFO lib-name "redis py"} - assert_error {*newlines*} {r CLIENT SETINFO lib-name "redis.py\n"} - assert_error {*Unrecognized*} {r CLIENT SETINFO badger hamster} - # test that all of these didn't affect the previously set values - r client info - } {*lib-name=redis.py lib-ver=1.2.3*} - - test {RESET does NOT clean library name} { - r reset - r client info - } {*lib-name=redis.py*} {needs:reset} - - test {CLIENT SETINFO can clear library name} { - r CLIENT SETINFO lib-name "" - r client info - } {*lib-name= *} - - test {CONFIG save params special case handled properly} { - # No "save" keyword - defaults should apply - start_server {config "minimal.conf"} { - assert_match [r config get save] {save {3600 1 300 100 60 10000}} - } - - # First "save" keyword overrides hard coded defaults - start_server {config "minimal.conf" overrides {save {100 100}}} { - # Defaults - assert_match [r config get save] {save {100 100}} - } - - # First "save" keyword appends default from config file - start_server {config "default.conf" overrides {save {900 1}} args {--save 100 100}} { - assert_match [r config get save] {save {900 1 100 100}} - } - - # Empty "save" keyword resets all - start_server {config "default.conf" overrides {save {900 1}} args {--save {}}} { - assert_match [r config get save] {save {}} - } - } {} {external:skip} - - test {CONFIG sanity} { - # Do CONFIG GET, CONFIG SET and then CONFIG GET again - # Skip immutable configs, one with no get, and other complicated configs - set skip_configs { - rdbchecksum - daemonize - tcp-backlog - always-show-logo - syslog-enabled - cluster-enabled - disable-thp - aclfile - unixsocket - pidfile - syslog-ident - appendfilename - appenddirname - supervised - syslog-facility - databases - io-threads - logfile - unixsocketperm - replicaof - slaveof - requirepass - server-cpulist - bio-cpulist - aof-rewrite-cpulist - bgsave-cpulist - server_cpulist - bio_cpulist - aof_rewrite_cpulist - bgsave_cpulist - set-proc-title - cluster-config-file - cluster-port - oom-score-adj - oom-score-adj-values - enable-protected-configs - enable-debug-command - enable-module-command - dbfilename - logfile - dir - socket-mark-id - req-res-logfile - client-default-resp - vset-force-single-threaded-execution - } - - if {!$::tls} { - append skip_configs { - tls-prefer-server-ciphers - tls-session-cache-timeout - tls-session-cache-size - tls-session-caching - tls-cert-file - tls-key-file - tls-client-cert-file - tls-client-key-file - tls-dh-params-file - tls-ca-cert-file - tls-ca-cert-dir - tls-protocols - tls-ciphers - tls-ciphersuites - tls-port - } - } - - set configs {} - foreach {k v} [r config get *] { - if {[lsearch $skip_configs $k] != -1} { - continue - } - dict set configs $k $v - # try to set the config to the same value it already has - r config set $k $v - } - - set newconfigs {} - foreach {k v} [r config get *] { - if {[lsearch $skip_configs $k] != -1} { - continue - } - dict set newconfigs $k $v - } - - dict for {k v} $configs { - set vv [dict get $newconfigs $k] - if {$v != $vv} { - fail "config $k mismatch, expecting $v but got $vv" - } - - } - } - - # Do a force-all config rewrite and make sure we're able to parse - # it. - test {CONFIG REWRITE sanity} { - # Capture state of config before - set configs {} - foreach {k v} [r config get *] { - dict set configs $k $v - } - - # Rewrite entire configuration, restart and confirm the - # server is able to parse it and start. - assert_equal [r debug config-rewrite-force-all] "OK" - restart_server 0 true false - wait_done_loading r - - # Verify no changes were introduced - dict for {k v} $configs { - assert_equal $v [lindex [r config get $k] 1] - } - } {} {external:skip} - - test {CONFIG REWRITE handles save and shutdown properly} { - r config set save "3600 1 300 100 60 10000" - r config set shutdown-on-sigterm "nosave now" - r config set shutdown-on-sigint "save" - r config rewrite - restart_server 0 true false - assert_equal [r config get save] {save {3600 1 300 100 60 10000}} - assert_equal [r config get shutdown-on-sigterm] {shutdown-on-sigterm {nosave now}} - assert_equal [r config get shutdown-on-sigint] {shutdown-on-sigint save} - - r config set save "" - r config set shutdown-on-sigterm "default" - r config rewrite - restart_server 0 true false - assert_equal [r config get save] {save {}} - assert_equal [r config get shutdown-on-sigterm] {shutdown-on-sigterm default} - - start_server {config "minimal.conf"} { - assert_equal [r config get save] {save {3600 1 300 100 60 10000}} - r config set save "" - r config rewrite - restart_server 0 true false - assert_equal [r config get save] {save {}} - } - } {} {external:skip} - - test {CONFIG SET with multiple args} { - set some_configs {maxmemory 10000001 repl-backlog-size 10000002 save {3000 5}} - - # Backup - set backups {} - foreach c [dict keys $some_configs] { - lappend backups $c [lindex [r config get $c] 1] - } - - # multi config set and veirfy - assert_equal [eval "r config set $some_configs"] "OK" - dict for {c val} $some_configs { - assert_equal [lindex [r config get $c] 1] $val - } - - # Restore backup - assert_equal [eval "r config set $backups"] "OK" - } - - test {CONFIG SET rollback on set error} { - # This test passes an invalid percent value to maxmemory-clients which should cause an - # input verification failure during the "set" phase before trying to apply the - # configuration. We want to make sure the correct failure happens and everything - # is rolled back. - # backup maxmemory config - set mm_backup [lindex [r config get maxmemory] 1] - set mmc_backup [lindex [r config get maxmemory-clients] 1] - set qbl_backup [lindex [r config get client-query-buffer-limit] 1] - # Set some value to maxmemory - assert_equal [r config set maxmemory 10000002] "OK" - # Set another value to maxmeory together with another invalid config - assert_error "ERR CONFIG SET failed (possibly related to argument 'maxmemory-clients') - percentage argument must be less or equal to 100" { - r config set maxmemory 10000001 maxmemory-clients 200% client-query-buffer-limit invalid - } - # Validate we rolled back to original values - assert_equal [lindex [r config get maxmemory] 1] 10000002 - assert_equal [lindex [r config get maxmemory-clients] 1] $mmc_backup - assert_equal [lindex [r config get client-query-buffer-limit] 1] $qbl_backup - # Make sure we revert back to the previous maxmemory - assert_equal [r config set maxmemory $mm_backup] "OK" - } - - test {CONFIG SET rollback on apply error} { - # This test tries to configure a used port number in redis. This is expected - # to pass the `CONFIG SET` validity checking implementation but fail on - # actual "apply" of the setting. This will validate that after an "apply" - # failure we rollback to the previous values. - proc dummy_accept {chan addr port} {} - - set some_configs {maxmemory 10000001 port 0 client-query-buffer-limit 10m} - - # On Linux we also set the oom score adj which has an apply function. This is - # used to verify that even successful applies are rolled back if some other - # config's apply fails. - set oom_adj_avail [expr {!$::external && [exec uname] == "Linux"}] - if {$oom_adj_avail} { - proc get_oom_score_adj {} { - set pid [srv 0 pid] - set fd [open "/proc/$pid/oom_score_adj" "r"] - set val [gets $fd] - close $fd - return $val - } - set some_configs [linsert $some_configs 0 oom-score-adj yes oom-score-adj-values {1 1 1}] - set read_oom_adj [get_oom_score_adj] - } - - # Backup - set backups {} - foreach c [dict keys $some_configs] { - lappend backups $c [lindex [r config get $c] 1] - } - - set used_port [find_available_port $::baseport $::portcount] - dict set some_configs port $used_port - - # Run a dummy server on used_port so we know we can't configure redis to - # use it. It's ok for this to fail because that means used_port is invalid - # anyway - catch {set sockfd [socket -server dummy_accept -myaddr 127.0.0.1 $used_port]} e - if {$::verbose} { puts "dummy_accept: $e" } - - # Try to listen on the used port, pass some more configs to make sure the - # returned failure message is for the first bad config and everything is rolled back. - assert_error "ERR CONFIG SET failed (possibly related to argument 'port') - Unable to listen on this port*" { - eval "r config set $some_configs" - } - - # Make sure we reverted back to previous configs - dict for {conf val} $backups { - assert_equal [lindex [r config get $conf] 1] $val - } - - if {$oom_adj_avail} { - assert_equal [get_oom_score_adj] $read_oom_adj - } - - # Make sure we can still communicate with the server (on the original port) - set r1 [redis_client] - assert_equal [$r1 ping] "PONG" - $r1 close - close $sockfd - } - - test {CONFIG SET duplicate configs} { - assert_error "ERR *duplicate*" {r config set maxmemory 10000001 maxmemory 10000002} - } - - test {CONFIG SET set immutable} { - assert_error "ERR *immutable*" {r config set daemonize yes} - } - - test {CONFIG GET hidden configs} { - set hidden_config "key-load-delay" - - # When we use a pattern we shouldn't get the hidden config - assert {![dict exists [r config get *] $hidden_config]} - - # When we explicitly request the hidden config we should get it - assert {[dict exists [r config get $hidden_config] "$hidden_config"]} - } - - test {CONFIG GET multiple args} { - set res [r config get maxmemory maxmemory* bind *of] - - # Verify there are no duplicates in the result - assert_equal [expr [llength [dict keys $res]]*2] [llength $res] - - # Verify we got both name and alias in result - assert {[dict exists $res slaveof] && [dict exists $res replicaof]} - - # Verify pattern found multiple maxmemory* configs - assert {[dict exists $res maxmemory] && [dict exists $res maxmemory-samples] && [dict exists $res maxmemory-clients]} - - # Verify we also got the explicit config - assert {[dict exists $res bind]} - } - - test {redis-server command line arguments - error cases} { - # Take '--invalid' as the option. - catch {exec src/redis-server --invalid} err - assert_match {*Bad directive or wrong number of arguments*} $err - - catch {exec src/redis-server --port} err - assert_match {*'port'*wrong number of arguments*} $err - - catch {exec src/redis-server --port 6380 --loglevel} err - assert_match {*'loglevel'*wrong number of arguments*} $err - - # Take `6379` and `6380` as the port option value. - catch {exec src/redis-server --port 6379 6380} err - assert_match {*'port "6379" "6380"'*wrong number of arguments*} $err - - # Take `--loglevel` and `verbose` as the port option value. - catch {exec src/redis-server --port --loglevel verbose} err - assert_match {*'port "--loglevel" "verbose"'*wrong number of arguments*} $err - - # Take `--bla` as the port option value. - catch {exec src/redis-server --port --bla --loglevel verbose} err - assert_match {*'port "--bla"'*argument couldn't be parsed into an integer*} $err - - # Take `--bla` as the loglevel option value. - catch {exec src/redis-server --logfile --my--log--file --loglevel --bla} err - assert_match {*'loglevel "--bla"'*argument(s) must be one of the following*} $err - - # Using MULTI_ARG's own check, empty option value - catch {exec src/redis-server --shutdown-on-sigint} err - assert_match {*'shutdown-on-sigint'*argument(s) must be one of the following*} $err - catch {exec src/redis-server --shutdown-on-sigint "now force" --shutdown-on-sigterm} err - assert_match {*'shutdown-on-sigterm'*argument(s) must be one of the following*} $err - - # Something like `redis-server --some-config --config-value1 --config-value2 --loglevel debug` would break, - # because if you want to pass a value to a config starting with `--`, it can only be a single value. - catch {exec src/redis-server --replicaof 127.0.0.1 abc} err - assert_match {*'replicaof "127.0.0.1" "abc"'*Invalid master port*} $err - catch {exec src/redis-server --replicaof --127.0.0.1 abc} err - assert_match {*'replicaof "--127.0.0.1" "abc"'*Invalid master port*} $err - catch {exec src/redis-server --replicaof --127.0.0.1 --abc} err - assert_match {*'replicaof "--127.0.0.1"'*wrong number of arguments*} $err - } {} {external:skip} - - test {redis-server command line arguments - allow passing option name and option value in the same arg} { - start_server {config "default.conf" args {"--maxmemory 700mb" "--maxmemory-policy volatile-lru"}} { - assert_match [r config get maxmemory] {maxmemory 734003200} - assert_match [r config get maxmemory-policy] {maxmemory-policy volatile-lru} - } - } {} {external:skip} - - test {redis-server command line arguments - wrong usage that we support anyway} { - start_server {config "default.conf" args {loglevel verbose "--maxmemory '700mb'" "--maxmemory-policy 'volatile-lru'"}} { - assert_match [r config get loglevel] {loglevel verbose} - assert_match [r config get maxmemory] {maxmemory 734003200} - assert_match [r config get maxmemory-policy] {maxmemory-policy volatile-lru} - } - } {} {external:skip} - - test {redis-server command line arguments - allow option value to use the `--` prefix} { - start_server {config "default.conf" args {--proc-title-template --my--title--template --loglevel verbose}} { - assert_match [r config get proc-title-template] {proc-title-template --my--title--template} - assert_match [r config get loglevel] {loglevel verbose} - } - } {} {external:skip} - - test {redis-server command line arguments - option name and option value in the same arg and `--` prefix} { - start_server {config "default.conf" args {"--proc-title-template --my--title--template" "--loglevel verbose"}} { - assert_match [r config get proc-title-template] {proc-title-template --my--title--template} - assert_match [r config get loglevel] {loglevel verbose} - } - } {} {external:skip} - - test {redis-server command line arguments - save with empty input} { - start_server {config "default.conf" args {--save --loglevel verbose}} { - assert_match [r config get save] {save {}} - assert_match [r config get loglevel] {loglevel verbose} - } - - start_server {config "default.conf" args {--loglevel verbose --save}} { - assert_match [r config get save] {save {}} - assert_match [r config get loglevel] {loglevel verbose} - } - - start_server {config "default.conf" args {--save {} --loglevel verbose}} { - assert_match [r config get save] {save {}} - assert_match [r config get loglevel] {loglevel verbose} - } - - start_server {config "default.conf" args {--loglevel verbose --save {}}} { - assert_match [r config get save] {save {}} - assert_match [r config get loglevel] {loglevel verbose} - } - - start_server {config "default.conf" args {--proc-title-template --save --save {} --loglevel verbose}} { - assert_match [r config get proc-title-template] {proc-title-template --save} - assert_match [r config get save] {save {}} - assert_match [r config get loglevel] {loglevel verbose} - } - - } {} {external:skip} - - test {redis-server command line arguments - take one bulk string with spaces for MULTI_ARG configs parsing} { - start_server {config "default.conf" args {--shutdown-on-sigint nosave force now --shutdown-on-sigterm "nosave force"}} { - assert_match [r config get shutdown-on-sigint] {shutdown-on-sigint {nosave now force}} - assert_match [r config get shutdown-on-sigterm] {shutdown-on-sigterm {nosave force}} - } - } {} {external:skip} - - # Config file at this point is at a weird state, and includes all - # known keywords. Might be a good idea to avoid adding tests here. -} - -start_server {tags {"introspection external:skip"} overrides {enable-protected-configs {no} enable-debug-command {no}}} { - test {cannot modify protected configuration - no} { - assert_error "ERR *protected*" {r config set dir somedir} - assert_error "ERR *DEBUG command not allowed*" {r DEBUG HELP} - } {} {needs:debug} -} - -start_server {config "minimal.conf" tags {"introspection external:skip"} overrides {protected-mode {no} enable-protected-configs {local} enable-debug-command {local}}} { - test {cannot modify protected configuration - local} { - # verify that for local connection it doesn't error - r config set dbfilename somename - r DEBUG HELP - - # Get a non-loopback address of this instance for this test. - set myaddr [get_nonloopback_addr] - if {$myaddr != "" && ![string match {127.*} $myaddr]} { - # Non-loopback client should fail - set r2 [get_nonloopback_client] - assert_error "ERR *protected*" {$r2 config set dir somedir} - assert_error "ERR *DEBUG command not allowed*" {$r2 DEBUG HELP} - } - } {} {needs:debug} -} - -test {config during loading} { - start_server [list overrides [list key-load-delay 50 loading-process-events-interval-bytes 1024 rdbcompression no save "900 1"]] { - # create a big rdb that will take long to load. it is important - # for keys to be big since the server processes events only once in 2mb. - # 100mb of rdb, 100k keys will load in more than 5 seconds - r debug populate 100000 key 1000 - - restart_server 0 false false - - # make sure it's still loading - assert_equal [s loading] 1 - - # verify some configs are allowed during loading - r config set loglevel debug - assert_equal [lindex [r config get loglevel] 1] debug - - # verify some configs are forbidden during loading - assert_error {LOADING*} {r config set dir asdf} - - # make sure it's still loading - assert_equal [s loading] 1 - - # no need to keep waiting for loading to complete - exec kill [srv 0 pid] - } -} {} {external:skip} - -test {CONFIG REWRITE handles rename-command properly} { - start_server {tags {"introspection"} overrides {rename-command {flushdb badger}}} { - assert_error {ERR unknown command*} {r flushdb} - - r config rewrite - restart_server 0 true false - - assert_error {ERR unknown command*} {r flushdb} - } -} {} {external:skip} - -test {CONFIG REWRITE handles alias config properly} { - start_server {tags {"introspection"} overrides {hash-max-listpack-entries 20 hash-max-ziplist-entries 21}} { - assert_equal [r config get hash-max-listpack-entries] {hash-max-listpack-entries 21} - assert_equal [r config get hash-max-ziplist-entries] {hash-max-ziplist-entries 21} - r config set hash-max-listpack-entries 100 - - r config rewrite - restart_server 0 true false - - assert_equal [r config get hash-max-listpack-entries] {hash-max-listpack-entries 100} - } - # test the order doesn't matter - start_server {tags {"introspection"} overrides {hash-max-ziplist-entries 20 hash-max-listpack-entries 21}} { - assert_equal [r config get hash-max-listpack-entries] {hash-max-listpack-entries 21} - assert_equal [r config get hash-max-ziplist-entries] {hash-max-ziplist-entries 21} - r config set hash-max-listpack-entries 100 - - r config rewrite - restart_server 0 true false - - assert_equal [r config get hash-max-listpack-entries] {hash-max-listpack-entries 100} - } -} {} {external:skip} - -test {IO threads client number} { - start_server {overrides {io-threads 2} tags {external:skip}} { - set iothread_clients [get_io_thread_clients 1] - assert_equal $iothread_clients [s connected_clients] - assert_equal [get_io_thread_clients 0] 0 - - r script debug yes ; # Transfer to main thread - assert_equal [get_io_thread_clients 0] 1 - assert_equal [get_io_thread_clients 1] [expr $iothread_clients - 1] - - set iothread_clients [get_io_thread_clients 1] - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - assert_equal [get_io_thread_clients 1] [expr $iothread_clients + 2] - $rd1 close - $rd2 close - wait_for_condition 1000 10 { - [get_io_thread_clients 1] eq $iothread_clients - } else { - fail "Fail to close clients of io thread 1" - } - assert_equal [get_io_thread_clients 0] 1 - - r script debug no ; # Transfer to io thread - assert_equal [get_io_thread_clients 0] 0 - assert_equal [get_io_thread_clients 1] [expr $iothread_clients + 1] - } -} - -test {Clients are evenly distributed among io threads} { - start_server {overrides {io-threads 4} tags {external:skip}} { - # There might be a client used for health checks (to detect if the server is up) - # that has not been freed timely. This can lead to an inaccurate count of - # connectedclients processed by IO threads. - wait_for_condition 1000 10 { - [s connected_clients] eq 1 - } else { - fail "Fail to wait for connected_clients to be 1" - } - global rdclients - for {set i 1} {$i < 9} {incr i} { - set rdclients($i) [redis_deferring_client] - } - for {set i 1} {$i <= 3} {incr i} { - assert_equal [get_io_thread_clients $i] 3 - } - - $rdclients(3) close - $rdclients(4) close - wait_for_condition 1000 10 { - [get_io_thread_clients 1] eq 2 && - [get_io_thread_clients 2] eq 2 && - [get_io_thread_clients 3] eq 3 - } else { - fail "Fail to close clients" - } - - set $rdclients(3) [redis_deferring_client] - set $rdclients(4) [redis_deferring_client] - for {set i 1} {$i <= 3} {incr i} { - assert_equal [get_io_thread_clients $i] 3 - } - } -} diff --git a/examples/redis-unstable/tests/unit/keyspace.tcl b/examples/redis-unstable/tests/unit/keyspace.tcl deleted file mode 100644 index b46b36b..0000000 --- a/examples/redis-unstable/tests/unit/keyspace.tcl +++ /dev/null @@ -1,557 +0,0 @@ -start_server {tags {"keyspace"}} { - test {DEL against a single item} { - r set x foo - assert {[r get x] eq "foo"} - r del x - r get x - } {} - - test {Vararg DEL} { - r set foo1{t} a - r set foo2{t} b - r set foo3{t} c - list [r del foo1{t} foo2{t} foo3{t} foo4{t}] [r mget foo1{t} foo2{t} foo3{t}] - } {3 {{} {} {}}} - - test {Untagged multi-key commands} { - r mset foo1 a foo2 b foo3 c - assert_equal {a b c {}} [r mget foo1 foo2 foo3 foo4] - r del foo1 foo2 foo3 foo4 - } {3} {cluster:skip} - - test {KEYS with pattern} { - foreach key {key_x key_y key_z foo_a foo_b foo_c} { - r set $key hello - } - lsort [r keys foo*] - } {foo_a foo_b foo_c} - - test {KEYS to get all keys} { - lsort [r keys *] - } {foo_a foo_b foo_c key_x key_y key_z} - - test {DBSIZE} { - r dbsize - } {6} - - test {KEYS with hashtag} { - foreach key {"{a}x" "{a}y" "{a}z" "{b}a" "{b}b" "{b}c"} { - r set $key hello - } - assert_equal [lsort [r keys "{a}*"]] [list "{a}x" "{a}y" "{a}z"] - assert_equal [lsort [r keys "*{b}*"]] [list "{b}a" "{b}b" "{b}c"] - } - - test {DEL all keys} { - foreach key [r keys *] {r del $key} - r dbsize - } {0} - - test "DEL against expired key" { - r debug set-active-expire 0 - r setex keyExpire 1 valExpire - after 1100 - assert_equal 0 [r del keyExpire] - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {EXISTS} { - set res {} - r set newkey test - append res [r exists newkey] - r del newkey - append res [r exists newkey] - } {10} - - test {Zero length value in key. SET/GET/EXISTS} { - r set emptykey {} - set res [r get emptykey] - append res [r exists emptykey] - r del emptykey - append res [r exists emptykey] - } {10} - - test {Commands pipelining} { - set fd [r channel] - puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n" - flush $fd - set res {} - append res [string match OK* [r read]] - append res [r read] - append res [string match PONG* [r read]] - format $res - } {1xyzk1} - - test {Non existing command} { - catch {r foobaredcommand} err - string match ERR* $err - } {1} - - test {RENAME basic usage} { - r set mykey{t} hello - r rename mykey{t} mykey1{t} - r rename mykey1{t} mykey2{t} - r get mykey2{t} - } {hello} - - test {RENAME source key should no longer exist} { - r exists mykey - } {0} - - test {RENAME against already existing key} { - r set mykey{t} a - r set mykey2{t} b - r rename mykey2{t} mykey{t} - set res [r get mykey{t}] - append res [r exists mykey2{t}] - } {b0} - - test {RENAMENX basic usage} { - r del mykey{t} - r del mykey2{t} - r set mykey{t} foobar - r renamenx mykey{t} mykey2{t} - set res [r get mykey2{t}] - append res [r exists mykey{t}] - } {foobar0} - - test {RENAMENX against already existing key} { - r set mykey{t} foo - r set mykey2{t} bar - r renamenx mykey{t} mykey2{t} - } {0} - - test {RENAMENX against already existing key (2)} { - set res [r get mykey{t}] - append res [r get mykey2{t}] - } {foobar} - - test {RENAME against non existing source key} { - catch {r rename nokey{t} foobar{t}} err - format $err - } {ERR*} - - test {RENAME where source and dest key are the same (existing)} { - r set mykey foo - r rename mykey mykey - } {OK} - - test {RENAMENX where source and dest key are the same (existing)} { - r set mykey foo - r renamenx mykey mykey - } {0} - - test {RENAME where source and dest key are the same (non existing)} { - r del mykey - catch {r rename mykey mykey} err - format $err - } {ERR*} - - test {RENAME with volatile key, should move the TTL as well} { - r del mykey{t} mykey2{t} - r set mykey{t} foo - r expire mykey{t} 100 - assert {[r ttl mykey{t}] > 95 && [r ttl mykey{t}] <= 100} - r rename mykey{t} mykey2{t} - assert {[r ttl mykey2{t}] > 95 && [r ttl mykey2{t}] <= 100} - } - - test {RENAME with volatile key, should not inherit TTL of target key} { - r del mykey{t} mykey2{t} - r set mykey{t} foo - r set mykey2{t} bar - r expire mykey2{t} 100 - assert {[r ttl mykey{t}] == -1 && [r ttl mykey2{t}] > 0} - r rename mykey{t} mykey2{t} - r ttl mykey2{t} - } {-1} - - test {DEL all keys again (DB 0)} { - foreach key [r keys *] { - r del $key - } - r dbsize - } {0} - - test {DEL all keys again (DB 1)} { - r select 10 - foreach key [r keys *] { - r del $key - } - set res [r dbsize] - r select 9 - format $res - } {0} {singledb:skip} - - test {COPY basic usage for string} { - r set mykey{t} foobar - set res {} - r copy mykey{t} mynewkey{t} - lappend res [r get mynewkey{t}] - lappend res [r dbsize] - if {$::singledb} { - assert_equal [list foobar 2] [format $res] - } else { - r copy mykey{t} mynewkey{t} DB 10 - r select 10 - lappend res [r get mynewkey{t}] - lappend res [r dbsize] - r select 9 - assert_equal [list foobar 2 foobar 1] [format $res] - } - } - - test {COPY for string does not replace an existing key without REPLACE option} { - r set mykey2{t} hello - catch {r copy mykey2{t} mynewkey{t} DB 10} e - set e - } {0} {singledb:skip} - - test {COPY for string can replace an existing key with REPLACE option} { - r copy mykey2{t} mynewkey{t} DB 10 REPLACE - r select 10 - r get mynewkey{t} - } {hello} {singledb:skip} - - test {COPY for string ensures that copied data is independent of copying data} { - r flushdb - r select 9 - r set mykey{t} foobar - set res {} - r copy mykey{t} mynewkey{t} DB 10 - r select 10 - lappend res [r get mynewkey{t}] - r set mynewkey{t} hoge - lappend res [r get mynewkey{t}] - r select 9 - lappend res [r get mykey{t}] - r select 10 - r flushdb - r select 9 - format $res - } [list foobar hoge foobar] {singledb:skip} - - test {COPY for string does not copy data to no-integer DB} { - r set mykey{t} foobar - catch {r copy mykey{t} mynewkey{t} DB notanumber} e - set e - } {ERR value is not an integer or out of range} - - test {COPY can copy key expire metadata as well} { - r set mykey{t} foobar ex 100 - r copy mykey{t} mynewkey{t} REPLACE - assert {[r ttl mynewkey{t}] > 0 && [r ttl mynewkey{t}] <= 100} - assert {[r get mynewkey{t}] eq "foobar"} - } - - test {COPY does not create an expire if it does not exist} { - r set mykey{t} foobar - assert {[r ttl mykey{t}] == -1} - r copy mykey{t} mynewkey{t} REPLACE - assert {[r ttl mynewkey{t}] == -1} - assert {[r get mynewkey{t}] eq "foobar"} - } - -array set largevalue [generate_largevalue_test_array] -foreach {type large} [array get largevalue] { - set origin_config [config_get_set list-max-listpack-size -1] - test "COPY basic usage for list - $type" { - r del mylist{t} mynewlist{t} - r lpush mylist{t} a b $large c d - assert_encoding $type mylist{t} - r copy mylist{t} mynewlist{t} - assert_encoding $type mynewlist{t} - set digest [debug_digest_value mylist{t}] - assert_equal $digest [debug_digest_value mynewlist{t}] - assert_refcount 1 mylist{t} - assert_refcount 1 mynewlist{t} - r del mylist{t} - assert_equal $digest [debug_digest_value mynewlist{t}] - } - config_set list-max-listpack-size $origin_config -} - - foreach type {intset listpack hashtable} { - test "COPY basic usage for $type set" { - r del set1{t} newset1{t} - r sadd set1{t} 1 2 3 - if {$type ne "intset"} { - r sadd set1{t} a - } - if {$type eq "hashtable"} { - for {set i 4} {$i < 200} {incr i} { - r sadd set1{t} $i - } - } - assert_encoding $type set1{t} - r copy set1{t} newset1{t} - set digest [debug_digest_value set1{t}] - assert_equal $digest [debug_digest_value newset1{t}] - assert_refcount 1 set1{t} - assert_refcount 1 newset1{t} - r del set1{t} - assert_equal $digest [debug_digest_value newset1{t}] - } - } - - test {COPY basic usage for listpack sorted set} { - r del zset1{t} newzset1{t} - r zadd zset1{t} 123 foobar - assert_encoding listpack zset1{t} - r copy zset1{t} newzset1{t} - set digest [debug_digest_value zset1{t}] - assert_equal $digest [debug_digest_value newzset1{t}] - assert_refcount 1 zset1{t} - assert_refcount 1 newzset1{t} - r del zset1{t} - assert_equal $digest [debug_digest_value newzset1{t}] - } - - test {COPY basic usage for skiplist sorted set} { - r del zset2{t} newzset2{t} - set original_max [lindex [r config get zset-max-ziplist-entries] 1] - r config set zset-max-ziplist-entries 0 - for {set j 0} {$j < 130} {incr j} { - r zadd zset2{t} [randomInt 50] ele-[randomInt 10] - } - assert_encoding skiplist zset2{t} - r copy zset2{t} newzset2{t} - set digest [debug_digest_value zset2{t}] - assert_equal $digest [debug_digest_value newzset2{t}] - assert_refcount 1 zset2{t} - assert_refcount 1 newzset2{t} - r del zset2{t} - assert_equal $digest [debug_digest_value newzset2{t}] - r config set zset-max-ziplist-entries $original_max - } - - test {COPY basic usage for listpack hash} { - r config set hash-max-listpack-entries 512 - r del hash1{t} newhash1{t} - r hset hash1{t} tmp 17179869184 - assert_encoding listpack hash1{t} - r copy hash1{t} newhash1{t} - set digest [debug_digest_value hash1{t}] - assert_equal $digest [debug_digest_value newhash1{t}] - assert_refcount 1 hash1{t} - assert_refcount 1 newhash1{t} - r del hash1{t} - assert_equal $digest [debug_digest_value newhash1{t}] - } - - test {COPY basic usage for hashtable hash} { - r del hash2{t} newhash2{t} - set original_max [lindex [r config get hash-max-ziplist-entries] 1] - r config set hash-max-ziplist-entries 0 - for {set i 0} {$i < 64} {incr i} { - r hset hash2{t} [randomValue] [randomValue] - } - assert_encoding hashtable hash2{t} - r copy hash2{t} newhash2{t} - set digest [debug_digest_value hash2{t}] - assert_equal $digest [debug_digest_value newhash2{t}] - assert_refcount 1 hash2{t} - assert_refcount 1 newhash2{t} - r del hash2{t} - assert_equal $digest [debug_digest_value newhash2{t}] - r config set hash-max-ziplist-entries $original_max - } - - test {COPY basic usage for stream} { - r del mystream{t} mynewstream{t} - for {set i 0} {$i < 1000} {incr i} { - r XADD mystream{t} * item 2 value b - } - r copy mystream{t} mynewstream{t} - set digest [debug_digest_value mystream{t}] - assert_equal $digest [debug_digest_value mynewstream{t}] - assert_refcount 1 mystream{t} - assert_refcount 1 mynewstream{t} - r del mystream{t} - assert_equal $digest [debug_digest_value mynewstream{t}] - } - - test {COPY basic usage for stream-cgroups} { - r del x{t} - r XADD x{t} 100 a 1 - set id [r XADD x{t} 101 b 1] - r XADD x{t} 102 c 1 - r XADD x{t} 103 e 1 - r XADD x{t} 104 f 1 - r XADD x{t} 105 g 1 - r XGROUP CREATE x{t} g1 0 - r XGROUP CREATE x{t} g2 0 - r XREADGROUP GROUP g1 Alice COUNT 1 STREAMS x{t} > - r XREADGROUP GROUP g1 Bob COUNT 1 STREAMS x{t} > - r XREADGROUP GROUP g1 Bob NOACK COUNT 1 STREAMS x{t} > - r XREADGROUP GROUP g2 Charlie COUNT 4 STREAMS x{t} > - r XGROUP SETID x{t} g1 $id - r XREADGROUP GROUP g1 Dave COUNT 3 STREAMS x{t} > - r XDEL x{t} 103 - - r copy x{t} newx{t} - set info [r xinfo stream x{t} full] - assert_equal $info [r xinfo stream newx{t} full] - assert_refcount 1 x{t} - assert_refcount 1 newx{t} - r del x{t} - assert_equal $info [r xinfo stream newx{t} full] - r flushdb - } - - test {MOVE basic usage} { - r set mykey foobar - r move mykey 10 - set res {} - lappend res [r exists mykey] - lappend res [r dbsize] - r select 10 - lappend res [r get mykey] - lappend res [r dbsize] - r select 9 - format $res - } [list 0 0 foobar 1] {singledb:skip} - - test {MOVE against key existing in the target DB} { - r set mykey hello - r move mykey 10 - } {0} {singledb:skip} - - test {MOVE against non-integer DB (#1428)} { - r set mykey hello - catch {r move mykey notanumber} e - set e - } {ERR value is not an integer or out of range} {singledb:skip} - - test {MOVE can move key expire metadata as well} { - r select 10 - r flushdb - r select 9 - r set mykey foo ex 100 - r move mykey 10 - assert {[r ttl mykey] == -2} - r select 10 - assert {[r ttl mykey] > 0 && [r ttl mykey] <= 100} - assert {[r get mykey] eq "foo"} - r select 9 - } {OK} {singledb:skip} - - test {MOVE does not create an expire if it does not exist} { - r select 10 - r flushdb - r select 9 - r set mykey foo - r move mykey 10 - assert {[r ttl mykey] == -2} - r select 10 - assert {[r ttl mykey] == -1} - assert {[r get mykey] eq "foo"} - r select 9 - } {OK} {singledb:skip} - - test {SET/GET keys in different DBs} { - r set a hello - r set b world - r select 10 - r set a foo - r set b bared - r select 9 - set res {} - lappend res [r get a] - lappend res [r get b] - r select 10 - lappend res [r get a] - lappend res [r get b] - r select 9 - format $res - } {hello world foo bared} {singledb:skip} - - test {RANDOMKEY} { - r flushdb - r set foo x - r set bar y - set foo_seen 0 - set bar_seen 0 - for {set i 0} {$i < 100} {incr i} { - set rkey [r randomkey] - if {$rkey eq {foo}} { - set foo_seen 1 - } - if {$rkey eq {bar}} { - set bar_seen 1 - } - } - list $foo_seen $bar_seen - } {1 1} - - test {RANDOMKEY against empty DB} { - r flushdb - r randomkey - } {} - - test {RANDOMKEY regression 1} { - r flushdb - r set x 10 - r del x - r randomkey - } {} - - test {KEYS * two times with long key, Github issue #1208} { - r flushdb - r set dlskeriewrioeuwqoirueioqwrueoqwrueqw test - r keys * - r keys * - } {dlskeriewrioeuwqoirueioqwrueoqwrueqw} - - test {Regression for pattern matching long nested loops} { - r flushdb - r SET aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1 - r KEYS "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*b" - } {} - - test {Regression for pattern matching very long nested loops} { - r flushdb - r SET [string repeat "a" 50000] 1 - r KEYS [string repeat "*?" 50000] - } {} - - test {Coverage: basic SWAPDB test and unhappy path} { - r flushall - r select 0 - r set swapkey v1 - r select 1 - assert_match 0 [r dbsize] ;#verify DB[1] has 0 keys - r swapdb 0 1 - assert_match 1 [r dbsize] - r select 0 - assert_match 0 [r dbsize] ;#verify DB[0] has 0 keys - r flushall - assert_error "ERR DB index is out of range*" {r swapdb 44 55} - assert_error "ERR invalid second DB index*" {r swapdb 44 a} - assert_error "ERR invalid first DB index*" {r swapdb a 55} - assert_error "ERR invalid first DB index*" {r swapdb a b} - assert_match "OK" [r swapdb 0 0] - } {} {singledb:skip} - - test {Coverage: SWAPDB and FLUSHDB} { - # set a key in each db and swapdb one of 2 with different db - # and flushdb on swapped db. - r flushall - r select 0 - r set swapkey v1 - r select 1 - r set swapkey1 v1 - assert_no_match "*db2:keys=*" [r info keyspace] - r swapdb 0 2 - r select 0 - assert_match 0 [r dbsize] - assert_no_match "*db0:keys=*" [r info keyspace] - r select 2 - r flushdb - assert_match 0 [r dbsize] - assert_match "*db1:keys=*" [r info keyspace] - assert_no_match "*db0:keys=*" [r info keyspace] - assert_no_match "*db2:keys=*" [r info keyspace] - r flushall - } {OK} {singledb:skip} -} diff --git a/examples/redis-unstable/tests/unit/latency-monitor.tcl b/examples/redis-unstable/tests/unit/latency-monitor.tcl deleted file mode 100644 index 9e714c1..0000000 --- a/examples/redis-unstable/tests/unit/latency-monitor.tcl +++ /dev/null @@ -1,189 +0,0 @@ -start_server {tags {"latency-monitor needs:latency"}} { - # Set a threshold high enough to avoid spurious latency events. - r config set latency-monitor-threshold 200 - r latency reset - - test {LATENCY HISTOGRAM with empty histogram} { - r config resetstat - set histo [dict create {*}[r latency histogram]] - # Config resetstat is recorded - assert_equal [dict size $histo] 1 - assert_match {*config|resetstat*} $histo - } - - test {LATENCY HISTOGRAM all commands} { - r config resetstat - r set a b - r set c d - set histo [dict create {*}[r latency histogram]] - assert_match {calls 2 histogram_usec *} [dict get $histo set] - assert_match {calls 1 histogram_usec *} [dict get $histo "config|resetstat"] - } - - test {LATENCY HISTOGRAM sub commands} { - r config resetstat - r client id - r client list - # parent command reply with its sub commands - set histo [dict create {*}[r latency histogram client]] - assert {[dict size $histo] == 2} - assert_match {calls 1 histogram_usec *} [dict get $histo "client|id"] - assert_match {calls 1 histogram_usec *} [dict get $histo "client|list"] - - # explicitly ask for one sub-command - set histo [dict create {*}[r latency histogram "client|id"]] - assert {[dict size $histo] == 1} - assert_match {calls 1 histogram_usec *} [dict get $histo "client|id"] - } - - test {LATENCY HISTOGRAM with a subset of commands} { - r config resetstat - r set a b - r set c d - r get a - r hset f k v - r hgetall f - set histo [dict create {*}[r latency histogram set hset]] - assert_match {calls 2 histogram_usec *} [dict get $histo set] - assert_match {calls 1 histogram_usec *} [dict get $histo hset] - assert_equal [dict size $histo] 2 - set histo [dict create {*}[r latency histogram hgetall get zadd]] - assert_match {calls 1 histogram_usec *} [dict get $histo hgetall] - assert_match {calls 1 histogram_usec *} [dict get $histo get] - assert_equal [dict size $histo] 2 - } - - test {LATENCY HISTOGRAM command} { - r config resetstat - r set a b - r get a - assert {[llength [r latency histogram set get]] == 4} - } - - test {LATENCY HISTOGRAM with wrong command name skips the invalid one} { - r config resetstat - assert {[llength [r latency histogram blabla]] == 0} - assert {[llength [r latency histogram blabla blabla2 set get]] == 0} - r set a b - r get a - assert_match {calls 1 histogram_usec *} [lindex [r latency histogram blabla blabla2 set get] 1] - assert_match {calls 1 histogram_usec *} [lindex [r latency histogram blabla blabla2 set get] 3] - assert {[string length [r latency histogram blabla set get]] > 0} - } - -tags {"needs:debug"} { - set old_threshold_value [lindex [r config get latency-monitor-threshold] 1] - - test {Test latency events logging} { - r config set latency-monitor-threshold 200 - r latency reset - r debug sleep 0.3 - after 1100 - r debug sleep 0.4 - after 1100 - r debug sleep 0.5 - r config set latency-monitor-threshold 0 - assert {[r latency history command] >= 3} - } - - test {LATENCY HISTORY output is ok} { - set res [r latency history command] - if {$::verbose} { - puts "LATENCY HISTORY data:" - puts $res - } - - set min 250 - set max 450 - foreach event $res { - lassign $event time latency - if {!$::no_latency} { - assert {$latency >= $min && $latency <= $max} - } - incr min 100 - incr max 100 - set last_time $time ; # Used in the next test - } - } - - test {LATENCY LATEST output is ok} { - set res [r latency latest] - if {$::verbose} { - puts "LATENCY LATEST data:" - puts $res - } - - foreach event $res { - lassign $event eventname time latency max - assert {$eventname eq "command"} - if {!$::no_latency} { - assert {$max >= 450 & $max <= 650} - assert {$time == $last_time} - } - break - } - } - - test {LATENCY GRAPH can output the event graph} { - set res [r latency graph command] - if {$::verbose} { - puts "LATENCY GRAPH data:" - puts $res - } - assert_match {*command*high*low*} $res - - # These numbers are taken from the "Test latency events logging" test. - # (debug sleep 0.3) and (debug sleep 0.5), using range to prevent timing issue. - regexp "command - high (.*?) ms, low (.*?) ms" $res -> high low - assert_morethan_equal $high 500 - assert_morethan_equal $low 300 - } - - r config set latency-monitor-threshold $old_threshold_value -} ;# tag - - test {LATENCY of expire events are correctly collected} { - r config set latency-monitor-threshold 20 - r flushdb - if {$::valgrind} {set count 100000} else {set count 1000000} - r eval { - local i = 0 - while (i < tonumber(ARGV[1])) do - redis.call('sadd',KEYS[1],i) - i = i+1 - end - } 1 mybigkey $count - r pexpire mybigkey 50 - wait_for_condition 5 100 { - [r dbsize] == 0 - } else { - fail "key wasn't expired" - } - assert_match {*expire-cycle*} [r latency latest] - - test {LATENCY GRAPH can output the expire event graph} { - assert_match {*expire-cycle*high*low*} [r latency graph expire-cycle] - } - - r config set latency-monitor-threshold 200 - } - - test {LATENCY HISTORY / RESET with wrong event name is fine} { - assert {[llength [r latency history blabla]] == 0} - assert {[r latency reset blabla] == 0} - } - - test {LATENCY DOCTOR produces some output} { - assert {[string length [r latency doctor]] > 0} - } - - test {LATENCY RESET is able to reset events} { - assert {[r latency reset] > 0} - assert {[r latency latest] eq {}} - } - - test {LATENCY HELP should not have unexpected options} { - catch {r LATENCY help xxx} e - assert_match "*wrong number of arguments for 'latency|help' command" $e - } -} diff --git a/examples/redis-unstable/tests/unit/lazyfree.tcl b/examples/redis-unstable/tests/unit/lazyfree.tcl deleted file mode 100644 index 2e60355..0000000 --- a/examples/redis-unstable/tests/unit/lazyfree.tcl +++ /dev/null @@ -1,200 +0,0 @@ -start_server {tags {"lazyfree"}} { - test "UNLINK can reclaim memory in background" { - set orig_mem [s used_memory] - set args {} - for {set i 0} {$i < 100000} {incr i} { - lappend args $i - } - r sadd myset {*}$args - assert {[r scard myset] == 100000} - set peak_mem [s used_memory] - assert {[r unlink myset] == 1} - assert {$peak_mem > $orig_mem+1000000} - wait_for_condition 50 100 { - [s used_memory] < $peak_mem && - [s used_memory] < $orig_mem*2 - } else { - fail "Memory is not reclaimed by UNLINK" - } - } - - test "FLUSHDB ASYNC can reclaim memory in background" { - # make the previous test is really done before sampling used_memory - wait_lazyfree_done r - - set orig_mem [s used_memory] - set args {} - for {set i 0} {$i < 100000} {incr i} { - lappend args $i - } - r sadd myset {*}$args - assert {[r scard myset] == 100000} - set peak_mem [s used_memory] - r flushdb async - assert {$peak_mem > $orig_mem+1000000} - wait_for_condition 50 100 { - [s used_memory] < $peak_mem && - [s used_memory] < $orig_mem*2 - } else { - fail "Memory is not reclaimed by FLUSHDB ASYNC" - } - } - - test "lazy free a stream with all types of metadata" { - # make the previous test is really done before doing RESETSTAT - wait_for_condition 50 100 { - [s lazyfree_pending_objects] == 0 - } else { - fail "lazyfree isn't done" - } - - r config resetstat - r config set stream-node-max-entries 5 - for {set j 0} {$j < 1000} {incr j} { - if {rand() < 0.9} { - r xadd stream * foo $j - } else { - r xadd stream * bar $j - } - } - r xgroup create stream mygroup 0 - set records [r xreadgroup GROUP mygroup Alice COUNT 2 STREAMS stream >] - r xdel stream [lindex [lindex [lindex [lindex $records 0] 1] 1] 0] - r xack stream mygroup [lindex [lindex [lindex [lindex $records 0] 1] 0] 0] - r unlink stream - - # make sure it was lazy freed - wait_for_condition 50 100 { - [s lazyfree_pending_objects] == 0 - } else { - fail "lazyfree isn't done" - } - assert_equal [s lazyfreed_objects] 1 - } {} {needs:config-resetstat} - - test "lazy free a stream with deleted cgroup" { - r config resetstat - r xadd s * a b - r xgroup create s bla $ - r xgroup destroy s bla - r unlink s - - # make sure it was not lazy freed - wait_for_condition 50 100 { - [s lazyfree_pending_objects] == 0 - } else { - fail "lazyfree isn't done" - } - assert_equal [s lazyfreed_objects] 0 - } {} {needs:config-resetstat} - - test "FLUSHALL SYNC optimized to run in bg as blocking FLUSHALL ASYNC" { - set num_keys 1000 - r config resetstat - - # Verify at start there are no lazyfree pending objects - assert_equal [s lazyfree_pending_objects] 0 - - # Fillup DB with items - populate $num_keys - - # Run FLUSHALL SYNC command, optimized as blocking ASYNC - r flushall - - # Verify all keys counted as lazyfreed - assert_equal [s lazyfreed_objects] $num_keys - } - - test "Run consecutive blocking FLUSHALL ASYNC successfully" { - r config resetstat - set rd [redis_deferring_client] - - # Fillup DB with items - r set x 1 - r set y 2 - - $rd write "FLUSHALL\r\nFLUSHALL\r\nFLUSHDB\r\n" - $rd flush - assert_equal [$rd read] {OK} - assert_equal [$rd read] {OK} - assert_equal [$rd read] {OK} - assert_equal [s lazyfreed_objects] 2 - $rd close - } - - test "FLUSHALL SYNC in MULTI not optimized to run as blocking FLUSHALL ASYNC" { - r config resetstat - - # Fillup DB with items - r set x 11 - r set y 22 - - # FLUSHALL SYNC in multi - r multi - r flushall - r exec - - # Verify flushall not run as lazyfree - assert_equal [s lazyfree_pending_objects] 0 - assert_equal [s lazyfreed_objects] 0 - } - - test "Client closed in the middle of blocking FLUSHALL ASYNC" { - set num_keys 100000 - r config resetstat - - # Fillup DB with items - populate $num_keys - - # close client in the middle of ongoing Blocking FLUSHALL ASYNC - set rd [redis_deferring_client] - $rd flushall - $rd close - - # Wait to verify all keys counted as lazyfreed - wait_for_condition 50 100 { - [s lazyfreed_objects] == $num_keys - } else { - fail "Unexpected number of lazyfreed_objects: [s lazyfreed_objects]" - } - } - - test "Pending commands in querybuf processed once unblocking FLUSHALL ASYNC" { - r config resetstat - set rd [redis_deferring_client] - - # Fillup DB with items - r set x 1 - r set y 2 - - $rd write "FLUSHALL\r\nPING\r\n" - $rd flush - assert_equal [$rd read] {OK} - assert_equal [$rd read] {PONG} - assert_equal [s lazyfreed_objects] 2 - $rd close - } - - test "Unblocks client blocked on lazyfree via REPLICAOF command" { - r config resetstat - set rd [redis_deferring_client] - - populate 50000 ;# Just to make flushdb async slower - $rd flushdb - - # Verify flushdb run as lazyfree - wait_for_condition 50 100 { - [s lazyfree_pending_objects] > 0 || - [s lazyfreed_objects] > 0 - } else { - fail "FLUSHDB didn't run as lazyfree" - } - - # Test that slaveof command unblocks clients without assertion failure - r slaveof 127.0.0.1 0 - assert_equal [$rd read] {OK} - $rd close - r ping - r slaveof no one - } {OK} {external:skip} -} diff --git a/examples/redis-unstable/tests/unit/limits.tcl b/examples/redis-unstable/tests/unit/limits.tcl deleted file mode 100644 index 3af1519..0000000 --- a/examples/redis-unstable/tests/unit/limits.tcl +++ /dev/null @@ -1,21 +0,0 @@ -start_server {tags {"limits network external:skip"} overrides {maxclients 10}} { - if {$::tls} { - set expected_code "*I/O error*" - } else { - set expected_code "*ERR max*reached*" - } - test {Check if maxclients works refusing connections} { - set c 0 - catch { - while {$c < 50} { - incr c - set rd [redis_deferring_client] - $rd ping - $rd read - after 100 - } - } e - assert {$c > 8 && $c <= 10} - set e - } $expected_code -} diff --git a/examples/redis-unstable/tests/unit/maxmemory.tcl b/examples/redis-unstable/tests/unit/maxmemory.tcl deleted file mode 100644 index f9b36b4..0000000 --- a/examples/redis-unstable/tests/unit/maxmemory.tcl +++ /dev/null @@ -1,709 +0,0 @@ -start_server {tags {"maxmemory" "external:skip"}} { - - test {SET and RESTORE key nearly as large as the memory limit} { - r flushall - set used [s used_memory] - r config set maxmemory [expr {$used+10000000}] - r set foo [string repeat a 8000000] - set encoded [r dump foo] - r del foo - r restore foo 0 $encoded - r strlen foo - } {8000000} {logreqres:skip} - - r flushall - r config set maxmemory 11mb - r config set maxmemory-policy allkeys-lru - set server_pid [s process_id] - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - proc init_test {client_eviction} { - r flushdb - - set prev_maxmemory_clients [r config get maxmemory-clients] - if $client_eviction { - r config set maxmemory-clients 3mb - r client no-evict on - } else { - r config set maxmemory-clients 0 - } - - r config resetstat - # fill 5mb using 50 keys of 100kb - for {set j 0} {$j < 50} {incr j} { - r setrange $j 100000 x - } - assert_equal [r dbsize] 50 - } - - # Return true if the eviction occurred (client or key) based on argument - proc check_eviction_test {client_eviction} { - set evicted_keys [s evicted_keys] - set evicted_clients [s evicted_clients] - set dbsize [r dbsize] - - if $client_eviction { - if {[lindex [r config get io-threads] 1] == 1} { - return [expr $evicted_clients > 0 && $evicted_keys == 0 && $dbsize == 50] - } else { - return [expr $evicted_clients >= 0 && $evicted_keys >= 0 && $dbsize <= 50] - } - } else { - return [expr $evicted_clients == 0 && $evicted_keys > 0 && $dbsize < 50] - } - } - - # Assert the eviction test passed (and prints some debug info on verbose) - proc verify_eviction_test {client_eviction} { - set evicted_keys [s evicted_keys] - set evicted_clients [s evicted_clients] - set dbsize [r dbsize] - - if $::verbose { - puts "evicted keys: $evicted_keys" - puts "evicted clients: $evicted_clients" - puts "dbsize: $dbsize" - } - - assert [check_eviction_test $client_eviction] - } - - foreach {client_eviction} {false true} { - set clients {} - test "eviction due to output buffers of many MGET clients, client eviction: $client_eviction" { - init_test $client_eviction - - for {set j 0} {$j < 20} {incr j} { - set rr [redis_deferring_client] - lappend clients $rr - } - - # Generate client output buffers via MGET until we can observe some effect on - # keys / client eviction, or we time out. - set t [clock seconds] - while {![check_eviction_test $client_eviction] && [expr [clock seconds] - $t] < 20} { - foreach rr $clients { - if {[catch { - $rr mget 1 - $rr flush - } err]} { - lremove clients $rr - } - } - } - - verify_eviction_test $client_eviction - } - foreach rr $clients { - $rr close - } - - set clients {} - test "eviction due to input buffer of a dead client, client eviction: $client_eviction" { - init_test $client_eviction - - for {set j 0} {$j < 30} {incr j} { - set rr [redis_deferring_client] - lappend clients $rr - } - - foreach rr $clients { - if {[catch { - $rr write "*250\r\n" - for {set j 0} {$j < 249} {incr j} { - $rr write "\$1000\r\n" - $rr write [string repeat x 1000] - $rr write "\r\n" - $rr flush - } - }]} { - lremove clients $rr - } - } - - verify_eviction_test $client_eviction - } - foreach rr $clients { - $rr close - } - - set clients {} - test "eviction due to output buffers of pubsub, client eviction: $client_eviction" { - init_test $client_eviction - - for {set j 0} {$j < 20} {incr j} { - set rr [redis_client] - lappend clients $rr - } - - foreach rr $clients { - $rr subscribe bla - } - - # Generate client output buffers via PUBLISH until we can observe some effect on - # keys / client eviction, or we time out. - set bigstr [string repeat x 100000] - set t [clock seconds] - while {![check_eviction_test $client_eviction] && [expr [clock seconds] - $t] < 20} { - if {[catch { r publish bla $bigstr } err]} { - if $::verbose { - puts "Error publishing: $err" - } - } - } - - verify_eviction_test $client_eviction - } - foreach rr $clients { - $rr close - } - } - -} - -start_server {tags {"maxmemory external:skip"}} { - - foreach policy { - allkeys-random allkeys-lru allkeys-lfu allkeys-lrm volatile-lru volatile-lfu volatile-random volatile-ttl volatile-lrm - } { - test "maxmemory - is the memory limit honoured? (policy $policy)" { - # make sure to start with a blank instance - r flushall - # Get the current memory limit and calculate a new limit. - # We just add 100k to the current memory size so that it is - # fast for us to reach that limit. - set used [s used_memory] - set limit [expr {$used+100*1024}] - r config set maxmemory $limit - r config set maxmemory-policy $policy - # Now add keys until the limit is almost reached. - set numkeys 0 - while 1 { - r setex [randomKey] 10000 x - incr numkeys - if {[s used_memory]+4096 > $limit} { - assert {$numkeys > 10} - break - } - } - # If we add the same number of keys already added again, we - # should still be under the limit. - for {set j 0} {$j < $numkeys} {incr j} { - r setex [randomKey] 10000 x - } - assert {[s used_memory] < ($limit+4096)} - } - } - - foreach policy { - allkeys-random allkeys-lru allkeys-lrm volatile-lru volatile-random volatile-ttl volatile-lrm - } { - test "maxmemory - only allkeys-* should remove non-volatile keys ($policy)" { - # make sure to start with a blank instance - r flushall - # Get the current memory limit and calculate a new limit. - # We just add 100k to the current memory size so that it is - # fast for us to reach that limit. - set used [s used_memory] - set limit [expr {$used+100*1024}] - r config set maxmemory $limit - r config set maxmemory-policy $policy - # Now add keys until the limit is almost reached. - set numkeys 0 - while 1 { - r set [randomKey] x - incr numkeys - if {[s used_memory]+4096 > $limit} { - assert {$numkeys > 10} - break - } - } - # If we add the same number of keys already added again and - # the policy is allkeys-* we should still be under the limit. - # Otherwise we should see an error reported by Redis. - set err 0 - for {set j 0} {$j < $numkeys} {incr j} { - if {[catch {r set [randomKey] x} e]} { - if {[string match {*used memory*} $e]} { - set err 1 - } - } - } - if {[string match allkeys-* $policy]} { - assert {[s used_memory] < ($limit+4096)} - } else { - assert {$err == 1} - } - } - } - - foreach policy { - volatile-lru volatile-lfu volatile-random volatile-ttl volatile-lrm - } { - test "maxmemory - policy $policy should only remove volatile keys." { - # make sure to start with a blank instance - r flushall - # Get the current memory limit and calculate a new limit. - # We just add 100k to the current memory size so that it is - # fast for us to reach that limit. - set used [s used_memory] - set limit [expr {$used+100*1024}] - r config set maxmemory $limit - r config set maxmemory-policy $policy - # Now add keys until the limit is almost reached. - set numkeys 0 - while 1 { - # Odd keys are volatile - # Even keys are non volatile - if {$numkeys % 2} { - r setex "key:$numkeys" 10000 x - } else { - r set "key:$numkeys" x - } - if {[s used_memory]+4096 > $limit} { - assert {$numkeys > 10} - break - } - incr numkeys - } - # Now we add the same number of volatile keys already added. - # We expect Redis to evict only volatile keys in order to make - # space. - set err 0 - for {set j 0} {$j < $numkeys} {incr j} { - catch {r setex "foo:$j" 10000 x} - } - # We should still be under the limit. - assert {[s used_memory] < ($limit+4096)} - # However all our non volatile keys should be here. - for {set j 0} {$j < $numkeys} {incr j 2} { - assert {[r exists "key:$j"]} - } - } - } -} - -# Calculate query buffer memory of slave -proc slave_query_buffer {srv} { - set clients [split [$srv client list] "\r\n"] - set c [lsearch -inline $clients *flags=S*] - if {[string length $c] > 0} { - assert {[regexp {qbuf=([0-9]+)} $c - qbuf]} - assert {[regexp {qbuf-free=([0-9]+)} $c - qbuf_free]} - return [expr $qbuf + $qbuf_free] - } - return 0 -} - -proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline} { - start_server {tags {"maxmemory external:skip"}} { - start_server {} { - set slave_pid [s process_id] - test "$test_name" { - set slave [srv 0 client] - set slave_host [srv 0 host] - set slave_port [srv 0 port] - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - - # Disable slow log for master to avoid memory growth in slow env. - $master config set slowlog-log-slower-than -1 - - # add 100 keys of 100k (10MB total) - for {set j 0} {$j < 100} {incr j} { - $master setrange "key:$j" 100000 asdf - } - - # make sure master doesn't disconnect slave because of timeout - $master config set repl-timeout 1200 ;# 20 minutes (for valgrind and slow machines) - $master config set maxmemory-policy allkeys-random - $master config set client-output-buffer-limit "replica 100000000 100000000 300" - $master config set repl-backlog-size [expr {10*1024}] - - # disable latency tracking - $master config set latency-tracking no - $slave config set latency-tracking no - - $slave slaveof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - - # measure used memory after the slave connected and set maxmemory - set orig_used [s -1 used_memory] - set orig_client_buf [s -1 mem_clients_normal] - set orig_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict] - set orig_used_no_repl [expr {$orig_used - $orig_mem_not_counted_for_evict}] - set limit [expr {$orig_used - $orig_mem_not_counted_for_evict + 32*1024}] - - if {$limit_memory==1} { - $master config set maxmemory $limit - } - - # put the slave to sleep - set rd_slave [redis_deferring_client] - pause_process $slave_pid - - # send some 10mb worth of commands that don't increase the memory usage - if {$pipeline == 1} { - set rd_master [redis_deferring_client -1] - # Send commands in batches and read responses to avoid TCP deadlock. - # Without interleaving reads, the client's send buffer fills up when - # the server's output buffers are full (because we're not reading), - # causing flush to block indefinitely on slow machines. - set batch_size 10000 - for {set k 0} {$k < $cmd_count} {incr k} { - $rd_master setrange key:0 0 [string repeat A $payload_len] - if {($k + 1) % $batch_size == 0} { - # Drain responses to prevent TCP buffer deadlock - for {set j 0} {$j < $batch_size} {incr j} { - $rd_master read - } - } - } - # Read any remaining responses - set remaining [expr {$cmd_count % $batch_size}] - for {set k 0} {$k < $remaining} {incr k} { - $rd_master read - } - } else { - for {set k 0} {$k < $cmd_count} {incr k} { - $master setrange key:0 0 [string repeat A $payload_len] - } - } - - set new_used [s -1 used_memory] - set slave_buf [s -1 mem_clients_slaves] - set client_buf [s -1 mem_clients_normal] - set mem_not_counted_for_evict [s -1 mem_not_counted_for_evict] - set used_no_repl [expr {$new_used - $mem_not_counted_for_evict - [slave_query_buffer $master]}] - # we need to exclude replies buffer and query buffer of replica from used memory. - # removing the replica (output) buffers is done so that we are able to measure any other - # changes to the used memory and see that they're insignificant (the test's purpose is to check that - # the replica buffers are counted correctly, so the used memory growth after deducting them - # should be nearly 0). - # we remove the query buffers because on slow test platforms, they can accumulate many ACKs. - set delta [expr {($used_no_repl - $client_buf) - ($orig_used_no_repl - $orig_client_buf)}] - - assert {[$master dbsize] == 100} - assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers - set delta_max [expr {$cmd_count / 2}] ;# 1 byte unaccounted for, with 1M commands will consume some 1MB - assert {$delta < $delta_max && $delta > -$delta_max} - - $master client kill type slave - set info_str [$master info memory] - set killed_used [getInfoProperty $info_str used_memory] - set killed_mem_not_counted_for_evict [getInfoProperty $info_str mem_not_counted_for_evict] - set killed_slave_buf [s -1 mem_clients_slaves] - # we need to exclude replies buffer and query buffer of slave from used memory after kill slave - set killed_used_no_repl [expr {$killed_used - $killed_mem_not_counted_for_evict - [slave_query_buffer $master]}] - set delta_no_repl [expr {$killed_used_no_repl - $used_no_repl}] - assert {[$master dbsize] == 100} - assert {$killed_slave_buf == 0} - assert {$delta_no_repl > -$delta_max && $delta_no_repl < $delta_max} - - } - # unfreeze slave process (after the 'test' succeeded or failed, but before we attempt to terminate the server - resume_process $slave_pid - } - } -} - -# test that slave buffer are counted correctly -# we wanna use many small commands, and we don't wanna wait long -# so we need to use a pipeline (redis_deferring_client) -# that may cause query buffer to fill and induce eviction, so we disable it -test_slave_buffers {slave buffer are counted correctly} 1000000 10 0 1 - -# test that slave buffer don't induce eviction -# test again with fewer (and bigger) commands without pipeline, but with eviction -test_slave_buffers "replica buffer don't induce eviction" 100000 100 1 0 - -start_server {tags {"maxmemory external:skip"}} { - test {Don't rehash if used memory exceeds maxmemory after rehash} { - r config set latency-tracking no - r config set maxmemory 0 - r config set maxmemory-policy allkeys-random - - # Next rehash size is 8192, that will eat 64k memory - populate 4095 "" 1 - - set used [s used_memory] - set limit [expr {$used + 10*1024}] - r config set maxmemory $limit - - # Adding a key to meet the 1:1 radio. - r set k0 v0 - # The dict has reached 4096, it can be resized in tryResizeHashTables in cron, - # or we add a key to let it check whether it can be resized. - r set k1 v1 - # Next writing command will trigger evicting some keys if last - # command trigger DB dict rehash - r set k2 v2 - # There must be 4098 keys because redis doesn't evict keys. - r dbsize - } {4098} -} - -start_server {tags {"maxmemory external:skip"}} { - test {client tracking don't cause eviction feedback loop} { - r config set latency-tracking no - r config set maxmemory 0 - r config set maxmemory-policy allkeys-lru - r config set maxmemory-eviction-tenacity 100 - - # check if enabling multithreaded IO - set multithreaded 0 - if {[r config get io-threads] > 1} { - set multithreaded 1 - } - - # 10 clients listening on tracking messages - set clients {} - for {set j 0} {$j < 10} {incr j} { - lappend clients [redis_deferring_client] - } - foreach rd $clients { - $rd HELLO 3 - $rd read ; # Consume the HELLO reply - $rd CLIENT TRACKING on - $rd read ; # Consume the CLIENT reply - } - - # populate 300 keys, with long key name and short value - for {set j 0} {$j < 300} {incr j} { - set key $j[string repeat x 1000] - r set $key x - - # for each key, enable caching for this key - foreach rd $clients { - $rd get $key - $rd read - } - } - - # we need to wait one second for the client querybuf excess memory to be - # trimmed by cron, otherwise the INFO used_memory and CONFIG maxmemory - # below (on slow machines) won't be "atomic" and won't trigger eviction. - after 1100 - - # set the memory limit which will cause a few keys to be evicted - # we need to make sure to evict keynames of a total size of more than - # 16kb since the (PROTO_REPLY_CHUNK_BYTES), only after that the - # invalidation messages have a chance to trigger further eviction. - set used [s used_memory] - set limit [expr {$used - 40000}] - r config set maxmemory $limit - - # If multithreaded, we need to let IO threads have chance to reply output - # buffer, to avoid next commands causing eviction. After eviction is performed, - # the next command becomes ready immediately in IO threads, and now we enqueue - # the client to be processed in main thread’s beforeSleep without notification. - # However, invalidation messages generated by eviction may not have been fully - # delivered by that time. As a result, executing the command in beforeSleep of - # the event loop (running eviction) can cause additional keys to be evicted. - if $multithreaded { after 200 } - - # make sure some eviction happened - set evicted [s evicted_keys] - if {$::verbose} { puts "evicted: $evicted" } - - # make sure we didn't drain the database - assert_range [r dbsize] 200 300 - - assert_range $evicted 10 50 - foreach rd $clients { - $rd read ;# make sure we have some invalidation message waiting - $rd close - } - - # eviction continues (known problem described in #8069) - # for now this test only make sures the eviction loop itself doesn't - # have feedback loop - set evicted [s evicted_keys] - if {$::verbose} { puts "evicted: $evicted" } - } -} - -start_server {tags {"maxmemory" "external:skip"}} { - test {propagation with eviction} { - set repl [attach_to_replication_stream] - - r set asdf1 1 - r set asdf2 2 - r set asdf3 3 - - r config set maxmemory-policy allkeys-lru - r config set maxmemory 1 - - wait_for_condition 5000 10 { - [r dbsize] eq 0 - } else { - fail "Not all keys have been evicted" - } - - r config set maxmemory 0 - r config set maxmemory-policy noeviction - - r set asdf4 4 - - assert_replication_stream $repl { - {select *} - {set asdf1 1} - {set asdf2 2} - {set asdf3 3} - {del asdf*} - {del asdf*} - {del asdf*} - {set asdf4 4} - } - close_replication_stream $repl - - r config set maxmemory 0 - r config set maxmemory-policy noeviction - } -} - -start_server {tags {"maxmemory" "external:skip"}} { - test {propagation with eviction in MULTI} { - set repl [attach_to_replication_stream] - - r config set maxmemory-policy allkeys-lru - - r multi - r incr x - r config set maxmemory 1 - r incr x - assert_equal [r exec] {1 OK 2} - - wait_for_condition 5000 10 { - [r dbsize] eq 0 - } else { - fail "Not all keys have been evicted" - } - - assert_replication_stream $repl { - {multi} - {select *} - {incr x} - {incr x} - {exec} - {del x} - } - close_replication_stream $repl - - r config set maxmemory 0 - r config set maxmemory-policy noeviction - } -} - -start_server {tags {"maxmemory" "external:skip"}} { - test {lru/lfu value of the key just added} { - r config set maxmemory-policy allkeys-lru - r set foo a - assert {[r object idletime foo] <= 2} - r del foo - r set foo 1 - r get foo - assert {[r object idletime foo] <= 2} - - r config set maxmemory-policy allkeys-lfu - r del foo - r set foo a - assert {[r object freq foo] == 5} - } -} - -# LRM eviction policy tests -start_server {tags {"maxmemory" "external:skip"}} { - test {LRM: Basic write updates idle time} { - r flushdb - r config set maxmemory-policy allkeys-lrm - - r set foo a - after 2000 - - # Read the key should NOT update LRM - r get foo - assert_morethan_equal [r object idletime foo] 1 - - # LRM should be updated (idletime should be smaller) - r set foo b - assert_lessthan_equal [r object idletime foo] 1 - } {} {slow} - - test {LRM: RENAME updates destination key LRM} { - r flushdb - r set src value - after 2000 - r rename src dst - assert_lessthan_equal [r object idletime dst] 1 - } {} {slow} - - test {LRM: XREADGROUP updates stream LRM} { - r flushdb - r xadd mystream * field value - r xgroup create mystream mygroup 0 - after 2000 - r xreadgroup GROUP mygroup consumer1 STREAMS mystream > - - # LRM should be updated (idletime should be smaller) - assert_lessthan_equal [r object idletime mystream] 1 - } {} {slow} - - test {LRM: Keys with only read operations should be removed first} { - r flushdb - r config set maxmemory 0 - r config set maxmemory-policy allkeys-lrm - r config set maxmemory-samples 64 ;# Ensure eviction sampling can pick all keys - - # Create keys and populate them - # We'll create two groups of keys: - # - read-only keys: will only be read after creation - # - write keys: will be continuously written to - for {set j 0} {$j < 25} {incr j} { - r set "read:$j" [string repeat x 20000] - r set "write:$j" [string repeat x 20000] - } - - after 1000 - - # Perform read and write operations on keys - for {set j 0} {$j < 25} {incr j} { - r get "read:$j" - r set "write:$j" [string repeat y 20000] - } - - # Set memory limit to force eviction - set used [s used_memory] - set limit [expr {$used - 200*1024}] - r config set maxmemory $limit - - # Add more keys to trigger eviction - for {set j 0} {$j < 10} {incr j} { - r set "trigger:$j" [string repeat z 20000] - } - - # Count how many keys from each group survived - set read_survived 0 - set write_survived 0 - for {set j 0} {$j < 25} {incr j} { - if {[r exists "read:$j"]} { - incr read_survived - } - if {[r exists "write:$j"]} { - incr write_survived - } - } - - # If read-only keys haven't been fully evicted, write keys must not be evicted at all. */ - if {$read_survived > 0} { - assert {$write_survived == 25} - } else { - assert {$write_survived > $read_survived} - } - } -} diff --git a/examples/redis-unstable/tests/unit/memefficiency.tcl b/examples/redis-unstable/tests/unit/memefficiency.tcl deleted file mode 100644 index 123bf37..0000000 --- a/examples/redis-unstable/tests/unit/memefficiency.tcl +++ /dev/null @@ -1,1176 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -proc test_memory_efficiency {range} { - r flushall - set rd [redis_deferring_client] - set base_mem [s used_memory] - set written 0 - for {set j 0} {$j < 10000} {incr j} { - set key key:$j - set val [string repeat A [expr {int(rand()*$range)}]] - $rd set $key $val - incr written [string length $key] - incr written [string length $val] - incr written 2 ;# A separator is the minimum to store key-value data. - } - for {set j 0} {$j < 10000} {incr j} { - $rd read ; # Discard replies - } - - set current_mem [s used_memory] - set used [expr {$current_mem-$base_mem}] - set efficiency [expr {double($written)/$used}] - return $efficiency -} - -start_server {tags {"memefficiency external:skip"}} { - foreach {size_range expected_min_efficiency} { - 32 0.15 - 64 0.25 - 128 0.35 - 1024 0.75 - 16384 0.82 - } { - test "Memory efficiency with values in range $size_range" { - set efficiency [test_memory_efficiency $size_range] - assert {$efficiency >= $expected_min_efficiency} - } - } -} - -run_solo {defrag} { - proc wait_for_defrag_stop {maxtries delay {expect_frag 0}} { - wait_for_condition $maxtries $delay { - [s active_defrag_running] eq 0 && ($expect_frag == 0 || [s allocator_frag_ratio] <= $expect_frag) - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - if {$expect_frag != 0} { - fail "defrag didn't stop or failed to achieve expected frag ratio ([s allocator_frag_ratio] > $expect_frag)" - } else { - fail "defrag didn't stop." - } - } - } - - proc discard_replies_every {rd count frequency discard_num} { - if {$count % $frequency == 0} { - for {set k 0} {$k < $discard_num} {incr k} { - $rd read ; # Discard replies - } - } - } - - proc test_active_defrag {type} { - - # note: Disabling lookahead because it changes the number and order of allocations which interferes with defrag and causes tests to fail - r config set lookahead 1 - - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - if {[string match {*jemalloc*} [s mem_allocator]] && [r debug mallctl arenas.page] <= 8192} { - test "Active defrag main dictionary: $type" { - r config set hz 100 - r config set activedefrag no - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 2mb - r config set maxmemory 100mb - r config set maxmemory-policy allkeys-lru - - populate 700000 asdf1 150 - populate 100 asdf1 150 0 false 1000 - populate 170000 asdf2 300 - populate 100 asdf2 300 0 false 1000 - - assert {[scan [regexp -inline {expires\=([\d]*)} [r info keyspace]] expires=%d] > 0} - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - if {$::verbose} { - puts "frag $frag" - } - assert {$frag >= 1.4} - - r config set latency-monitor-threshold 5 - r latency reset - r config set maxmemory 110mb ;# prevent further eviction (not to fail the digest test) - set digest [debug_digest] - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - # Wait for the active defrag to start working (decision once a - # second). - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # This test usually runs for a while, during this interval, we test the range. - assert_range [s active_defrag_running] 65 75 - r config set active-defrag-cycle-min 1 - r config set active-defrag-cycle-max 1 - after 120 ;# serverCron only updates the info once in 100ms - assert_range [s active_defrag_running] 1 1 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - - # Wait for the active defrag to stop working. - wait_for_defrag_stop 2000 100 1.1 - - # Test the fragmentation is lower. - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - set max_latency 0 - foreach event [r latency latest] { - lassign $event eventname time latency max - if {$eventname == "active-defrag-cycle"} { - set max_latency $max - } - } - if {$::verbose} { - puts "frag $frag" - set misses [s active_defrag_misses] - set hits [s active_defrag_hits] - puts "hits: $hits" - puts "misses: $misses" - puts "max latency $max_latency" - puts [r latency latest] - puts [r latency history active-defrag-cycle] - } - # due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75, - # we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher - if {!$::no_latency} { - assert {$max_latency <= 30} - } - } - # verify the data isn't corrupted or changed - set newdigest [debug_digest] - assert {$digest eq $newdigest} - r save ;# saving an rdb iterates over all the data / pointers - - # if defrag is supported, test AOF loading too - if {[r config get activedefrag] eq "activedefrag yes" && $type eq "standalone"} { - test "Active defrag - AOF loading" { - # reset stats and load the AOF file - r config resetstat - r config set key-load-delay -25 ;# sleep on average 1/25 usec - # Note: This test is checking if defrag is working DURING AOF loading (while - # timers are not active). So we don't give any extra time, and we deactivate - # defrag immediately after the AOF loading is complete. During loading, - # defrag will get invoked less often, causing starvation prevention. We - # should expect longer latency measurements. - r debug loadaof - r config set activedefrag no - # measure hits and misses right after aof loading - set misses [s active_defrag_misses] - set hits [s active_defrag_hits] - - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - set max_latency 0 - foreach event [r latency latest] { - lassign $event eventname time latency max - if {$eventname == "while-blocked-cron"} { - set max_latency $max - } - } - if {$::verbose} { - puts "AOF loading:" - puts "frag $frag" - puts "hits: $hits" - puts "misses: $misses" - puts "max latency $max_latency" - puts [r latency latest] - puts [r latency history "while-blocked-cron"] - } - # make sure we had defrag hits during AOF loading - assert {$hits > 100000} - # make sure the defragger did enough work to keep the fragmentation low during loading. - # we cannot check that it went all the way down, since we don't wait for full defrag cycle to complete. - assert {$frag < 1.4} - # since the AOF contains simple (fast) SET commands (and the cron during loading runs every 1024 commands), - # it'll still not block the loading for long periods of time. - if {!$::no_latency} { - assert {$max_latency <= 40} - } - } - } ;# Active defrag - AOF loading - } - r config set appendonly no - r config set key-load-delay 0 - - test "Active defrag eval scripts: $type" { - r flushdb - r script flush sync - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 1500kb - r config set maxmemory 0 - - set n 50000 - - # Populate memory with interleaving script-key pattern of same size - set dummy_script "--[string repeat x 400]\nreturn " - set rd [redis_deferring_client] - # Send commands in batches and read responses to avoid TCP deadlock. - # Without interleaving reads, TCP congestion control can throttle - # the connection when buffers fill, causing the test to hang. - set batch_size 1000 - for {set j 0} {$j < $n} {incr j} { - set val "$dummy_script[format "%06d" $j]" - $rd script load $val - $rd set k$j $val - if {($j + 1) % $batch_size == 0} { - for {set i 0} {$i < $batch_size} {incr i} { - $rd read ; # Discard script load replies - $rd read ; # Discard set replies - } - } - } - # Read remaining responses - set remaining [expr {$n % $batch_size}] - for {set j 0} {$j < $remaining} {incr j} { - $rd read ; # Discard script load replies - $rd read ; # Discard set replies - } - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_lessthan [s allocator_frag_ratio] 1.05 - - # Delete all the keys to create fragmentation - # Use same batching pattern to avoid TCP deadlock - for {set j 0} {$j < $n} {incr j} { - $rd del k$j - if {($j + 1) % $batch_size == 0} { - for {set i 0} {$i < $batch_size} {incr i} { - $rd read - } - } - } - set remaining [expr {$n % $batch_size}] - for {set j 0} {$j < $remaining} {incr j} { $rd read } - if {$type eq "cluster"} { - $rd config resetstat - $rd read ; # Discard config resetstat reply - } - $rd close - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_morethan [s allocator_frag_ratio] 1.4 - - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.05 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - } - # Flush all script to make sure we don't crash after defragging them - r script flush sync - } {OK} - - test "Active defrag big keys: $type" { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-max-scan-fields 1000 - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 2mb - r config set maxmemory 0 - r config set list-max-ziplist-size 5 ;# list of 10k items will have 2000 quicklist nodes - r config set stream-node-max-entries 5 - r config set hash-max-listpack-entries 10 - r hmset hash_lp h1 v1 h2 v2 h3 v3 - assert_encoding listpack hash_lp - r hmset hash_ht h1 v1 h2 v2 h3 v3 h4 v4 h5 v5 h6 v6 h7 v7 h8 v8 h9 v9 h10 v10 h11 v11 - assert_encoding hashtable hash_ht - r lpush list a b c d - r zadd zset 0 a 1 b 2 c 3 d - r sadd set a b c d - r xadd stream * item 1 value a - r xadd stream * item 2 value b - r xgroup create stream mygroup 0 - r xreadgroup GROUP mygroup Alice COUNT 1 STREAMS stream > - - # create big keys with 10k items - # Use batching to avoid TCP deadlock - set rd [redis_deferring_client] - set batch_size 1000 - for {set j 0} {$j < 10000} {incr j} { - $rd hset bighash $j [concat "asdfasdfasdf" $j] - $rd lpush biglist [concat "asdfasdfasdf" $j] - $rd zadd bigzset $j [concat "asdfasdfasdf" $j] - $rd sadd bigset [concat "asdfasdfasdf" $j] - $rd xadd bigstream * item 1 value a - if {($j + 1) % $batch_size == 0} { - for {set i 0} {$i < [expr {$batch_size * 5}]} {incr i} { - $rd read - } - } - } - # Read remaining replies - set remaining [expr {(10000 % $batch_size) * 5}] - for {set j 0} {$j < $remaining} {incr j} { - $rd read - } - - # create some small items (effective in cluster-enabled) - r set "{bighash}smallitem" val - r set "{biglist}smallitem" val - r set "{bigzset}smallitem" val - r set "{bigset}smallitem" val - r set "{bigstream}smallitem" val - - - set expected_frag 1.49 - if {$::accurate} { - # scale the hash to 1m fields in order to have a measurable the latency - set count 0 - for {set j 10000} {$j < 1000000} {incr j} { - $rd hset bighash $j [concat "asdfasdfasdf" $j] - - incr count - discard_replies_every $rd $count 10000 10000 - } - # creating that big hash, increased used_memory, so the relative frag goes down - set expected_frag 1.3 - } - - # add a mass of string keys - set count 0 - for {set j 0} {$j < 500000} {incr j} { - $rd setrange $j 150 a - - incr count - discard_replies_every $rd $count 10000 10000 - } - assert_equal [r dbsize] 500016 - - # create some fragmentation - set count 0 - for {set j 0} {$j < 500000} {incr j 2} { - $rd del $j - - incr count - discard_replies_every $rd $count 10000 10000 - } - assert_equal [r dbsize] 250016 - - # start defrag - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - if {$::verbose} { - puts "frag $frag" - } - assert {$frag >= $expected_frag} - r config set latency-monitor-threshold 5 - r latency reset - - set digest [debug_digest] - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.1 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - set max_latency 0 - foreach event [r latency latest] { - lassign $event eventname time latency max - if {$eventname == "active-defrag-cycle"} { - set max_latency $max - } - } - if {$::verbose} { - puts "frag $frag" - set misses [s active_defrag_misses] - set hits [s active_defrag_hits] - puts "hits: $hits" - puts "misses: $misses" - puts "max latency $max_latency" - puts [r latency latest] - puts [r latency history active-defrag-cycle] - } - # due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75, - # we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher - if {!$::no_latency} { - assert {$max_latency <= 30} - } - } - # verify the data isn't corrupted or changed - set newdigest [debug_digest] - assert {$digest eq $newdigest} - r save ;# saving an rdb iterates over all the data / pointers - } {OK} - - test "Active defrag pubsub: $type" { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 1500kb - r config set maxmemory 0 - - # Populate memory with interleaving pubsub-key pattern of same size - set n 50000 - set dummy_channel "[string repeat x 400]" - set rd [redis_deferring_client] - set rd_pubsub [redis_deferring_client] - for {set j 0} {$j < $n} {incr j} { - set channel_name "$dummy_channel[format "%06d" $j]" - $rd_pubsub subscribe $channel_name - $rd_pubsub read ; # Discard subscribe replies - $rd_pubsub ssubscribe $channel_name - $rd_pubsub read ; # Discard ssubscribe replies - # Pub/Sub clients are handled in the main thread, so their memory is - # allocated there. Using the SETBIT command avoids the main thread - # referencing argv from IO threads. - $rd setbit k$j [expr {[string length $channel_name] * 8}] 1 - $rd read ; # Discard set replies - } - - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_lessthan [s allocator_frag_ratio] 1.05 - - # Delete all the keys to create fragmentation - # Use batching to avoid TCP deadlock - set batch_size 1000 - for {set j 0} {$j < $n} {incr j} { - $rd del k$j - if {($j + 1) % $batch_size == 0} { - for {set i 0} {$i < $batch_size} {incr i} { - $rd read - } - } - } - set remaining [expr {$n % $batch_size}] - for {set j 0} {$j < $remaining} {incr j} { $rd read } - if {$type eq "cluster"} { - $rd config resetstat - $rd read ; # Discard config resetstat reply - } - $rd close - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_morethan [s allocator_frag_ratio] 1.35 - - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.05 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - } - - # Publishes some message to all the pubsub clients to make sure that - # we didn't break the data structure. - for {set j 0} {$j < $n} {incr j} { - set channel "$dummy_channel[format "%06d" $j]" - r publish $channel "hello" - assert_equal "message $channel hello" [$rd_pubsub read] - $rd_pubsub unsubscribe $channel - $rd_pubsub read - r spublish $channel "hello" - assert_equal "smessage $channel hello" [$rd_pubsub read] - $rd_pubsub sunsubscribe $channel - $rd_pubsub read - } - $rd_pubsub close - } - - test "Active defrag IDMP streams: $type" { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 1500kb - r config set maxmemory 0 - - set n 50000 - - # Create the stream first and configure IDMP limits - r xadd idmpstream * dummy value - r xcfgset idmpstream idmp-maxsize 10000 ;# Allow 10000 entries per producer - - # Populate memory with interleaving IDMP stream-key pattern of same size - set dummy_iid "[string repeat x 400]" - set rd [redis_deferring_client] - for {set j 0} {$j < $n} {incr j} { - set producer_id "producer[expr {$j % 10}]" - set iid "$dummy_iid[format "%06d" $j]" - $rd xadd idmpstream IDMP $producer_id $iid * field value - $rd set k$j $iid - } - for {set j 0} {$j < [expr {$n * 2}]} {incr j} { - $rd read ; # Discard replies - } - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_lessthan [s allocator_frag_ratio] 1.05 - - # Verify IDMP structures were created - set idmp_info [r xinfo stream idmpstream full] - set num_producers [dict get $idmp_info pids-tracked] - set num_entries [dict get $idmp_info iids-tracked] - assert {$num_producers == 10} - assert {$num_entries == $n} - - # Delete all the keys to create fragmentation - for {set j 0} {$j < $n} {incr j} { $rd del k$j } - for {set j 0} {$j < $n} {incr j} { $rd read } ; # Discard del replies - if {$type eq "cluster"} { - $rd config resetstat - $rd read ; # Discard config resetstat reply - } - $rd close - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_morethan [s allocator_frag_ratio] 1.35 - - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.1 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - - # Verify IDMP structures are intact after defrag - set idmp_info_after [r xinfo stream idmpstream full] - set num_producers_after [dict get $idmp_info_after pids-tracked] - set num_entries_after [dict get $idmp_info_after iids-tracked] - assert {$num_producers_after == 10} - assert {$num_entries_after == $n} - - # Verify IDMP deduplication still works after defrag - set original_length [r xlen idmpstream] - r xadd idmpstream IDMP producer0 "${dummy_iid}000000" * field newvalue - set new_length [r xlen idmpstream] - assert {$new_length == $original_length} - } - } - - foreach {eb_container fields n} {eblist 16 3000 ebrax 30 1600 large_ebrax 500 100} { - test "Active Defrag HFE with $eb_container: $type" { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-threshold-lower 7 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 1000kb - r config set maxmemory 0 - r config set hash-max-listpack-value 512 - r config set hash-max-listpack-entries 10 - - # Populate memory with interleaving hash field of same size - # Interleave reads to avoid TCP deadlock - set dummy_field "[string repeat x 400]" - set rd [redis_deferring_client] - for {set i 0} {$i < $n} {incr i} { - for {set j 0} {$j < $fields} {incr j} { - $rd hset h$i $dummy_field$j v - $rd hexpire h$i 9999999 FIELDS 1 $dummy_field$j - $rd hset k$i $dummy_field$j v - $rd hexpire k$i 9999999 FIELDS 1 $dummy_field$j - } - $rd expire h$i 9999999 ;# Ensure expire is updated after kvobj reallocation - # Read replies for this iteration to avoid TCP deadlock - for {set j 0} {$j < $fields} {incr j} { - $rd read ; # Discard hset replies - $rd read ; # Discard hexpire replies - $rd read ; # Discard hset replies - $rd read ; # Discard hexpire replies - } - $rd read ; # Discard expire replies - } - - # Coverage for listpackex. - r hset h_lpex $dummy_field v - r hexpire h_lpex 9999999 FIELDS 1 $dummy_field - assert_encoding listpackex h_lpex - - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_lessthan [s allocator_frag_ratio] 1.07 - - # Delete all the keys to create fragmentation - for {set i 0} {$i < $n} {incr i} { - r del k$i - } - $rd close - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_morethan [s allocator_frag_ratio] 1.35 - - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.07 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - } - } - } ;# end of foreach - - test "Active defrag for argv retained by the main thread from IO thread: $type" { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - set io_threads [lindex [r config get io-threads] 1] - if {$io_threads == 1} { - r config set active-defrag-threshold-lower 5 - } else { - r config set active-defrag-threshold-lower 10 - } - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 1000kb - r config set maxmemory 0 - - # Create some clients so that they are distributed among different io threads. - set clients {} - for {set i 0} {$i < 8} {incr i} { - lappend clients [redis_client] - } - - # Populate memory with interleaving key pattern of same size - set dummy "[string repeat x 400]" - set n 10000 - for {set i 0} {$i < [llength $clients]} {incr i} { - set rr [lindex $clients $i] - for {set j 0} {$j < $n} {incr j} { - $rr set "k$i-$j" $dummy - } - } - - # If io-threads is enable, verify that memory allocation is not from the main thread. - if {$io_threads != 1} { - # At least make sure that bin 448 is created in the main thread's arena. - r set k dummy - r del k - - # We created 10000 string keys of 400 bytes each for each client, so when the memory - # allocation for the 448 bin in the main thread is significantly smaller than this, - # we can conclude that the memory allocation is not coming from it. - set malloc_stats [r memory malloc-stats] - if {[regexp {(?s)arenas\[0\]:.*?448[ ]+[\d]+[ ]+([\d]+)[ ]} $malloc_stats - allocated]} { - # Ensure the allocation for bin 448 in the main thread’s arena - # is far less than 4375k (10000 * 448 bytes). - assert_lessthan $allocated 200000 - } else { - fail "Failed to get the main thread's malloc stats." - } - } - - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_lessthan [s allocator_frag_ratio] 1.05 - - # Delete keys with even indices to create fragmentation. - for {set i 0} {$i < [llength $clients]} {incr i} { - set rd [lindex $clients $i] - for {set j 0} {$j < $n} {incr j 2} { - $rd del "k$i-$j" - } - } - for {set i 0} {$i < [llength $clients]} {incr i} { - [lindex $clients $i] close - } - if {$type eq "cluster"} { - r config resetstat - } - - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_morethan [s allocator_frag_ratio] 1.35 - - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - if {$io_threads == 1} { - wait_for_defrag_stop 500 100 1.05 - } else { - # TODO: When multithreading is enabled, argv may be created in the io thread - # and kept in the main thread, which can cause fragmentation to become worse. - wait_for_defrag_stop 500 100 1.1 - } - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - } - } - - if {$type eq "standalone"} { ;# skip in cluster mode - test "Active defrag big list: $type" { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-max-scan-fields 1000 - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 2mb - r config set maxmemory 0 - r config set list-max-ziplist-size 1 ;# list of 100k items will have 100k quicklist nodes - - # create big keys with 10k items - set rd [redis_deferring_client] - - set expected_frag 1.5 - # add a mass of list nodes to two lists (allocations are interlaced) - set val [string repeat A 500] ;# 1 item of 500 bytes puts us in the 640 bytes bin, which has 32 regs, so high potential for fragmentation - set elements 100000 - set count 0 - for {set j 0} {$j < $elements} {incr j} { - $rd lpush biglist1 $val - $rd lpush biglist2 $val - - incr count - discard_replies_every $rd $count 10000 20000 - } - - # create some fragmentation - r del biglist2 - - # start defrag - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - if {$::verbose} { - puts "frag $frag" - } - - assert {$frag >= $expected_frag} - r config set latency-monitor-threshold 5 - r latency reset - - set digest [debug_digest] - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.1 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - set misses [s active_defrag_misses] - set hits [s active_defrag_hits] - set frag [s allocator_frag_ratio] - set max_latency 0 - foreach event [r latency latest] { - lassign $event eventname time latency max - if {$eventname == "active-defrag-cycle"} { - set max_latency $max - } - } - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag_bytes [s allocator_frag_bytes]" - puts "frag $frag" - puts "misses: $misses" - puts "hits: $hits" - puts "max latency $max_latency" - puts [r latency latest] - puts [r latency history active-defrag-cycle] - puts [r memory malloc-stats] - } - # due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75, - # we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher - if {!$::no_latency} { - assert {$max_latency <= 30} - } - - # in extreme cases of stagnation, we see over 5m misses before the tests aborts with "defrag didn't stop", - # in normal cases we only see 100k misses out of 100k elements - assert {$misses < $elements * 2} - } - # verify the data isn't corrupted or changed - set newdigest [debug_digest] - assert {$digest eq $newdigest} - r save ;# saving an rdb iterates over all the data / pointers - r del biglist1 ;# coverage for quicklistBookmarksClear - } {1} - - test "Active defrag edge case: $type" { - # there was an edge case in defrag where all the slabs of a certain bin are exact the same - # % utilization, with the exception of the current slab from which new allocations are made - # if the current slab is lower in utilization the defragger would have ended up in stagnation, - # kept running and not move any allocation. - # this test is more consistent on a fresh server with no history - start_server {tags {"defrag"} overrides {save ""}} { - r flushdb - r config set hz 100 - r config set activedefrag no - wait_for_defrag_stop 500 100 - r config resetstat - r config set active-defrag-max-scan-fields 1000 - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 65 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 1mb - r config set maxmemory 0 - set expected_frag 1.3 - - r debug mallctl-str thread.tcache.flush VOID - # fill the first slab containing 32 regs of 640 bytes. - for {set j 0} {$j < 32} {incr j} { - r setrange "_$j" 600 x - r debug mallctl-str thread.tcache.flush VOID - } - - # add a mass of keys with 600 bytes values, fill the bin of 640 bytes which has 32 regs per slab. - set rd [redis_deferring_client] - set keys 640000 - set count 0 - for {set j 0} {$j < $keys} {incr j} { - $rd setrange $j 600 x - - incr count - discard_replies_every $rd $count 10000 10000 - } - - # create some fragmentation of 50% - set sent 0 - for {set j 0} {$j < $keys} {incr j 1} { - $rd del $j - incr sent - incr j 1 - - discard_replies_every $rd $sent 10000 10000 - } - - # create higher fragmentation in the first slab - for {set j 10} {$j < 32} {incr j} { - r del "_$j" - } - - # start defrag - after 120 ;# serverCron only updates the info once in 100ms - set frag [s allocator_frag_ratio] - if {$::verbose} { - puts "frag $frag" - } - - assert {$frag >= $expected_frag} - - set digest [debug_digest] - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - - # wait for the active defrag to stop working - wait_for_defrag_stop 500 100 1.1 - - # test the fragmentation is lower - after 120 ;# serverCron only updates the info once in 100ms - set misses [s active_defrag_misses] - set hits [s active_defrag_hits] - set frag [s allocator_frag_ratio] - if {$::verbose} { - puts "frag $frag" - puts "hits: $hits" - puts "misses: $misses" - } - assert {$misses < 10000000} ;# when defrag doesn't stop, we have some 30m misses, when it does, we have 2m misses - } - - # verify the data isn't corrupted or changed - set newdigest [debug_digest] - assert {$digest eq $newdigest} - r save ;# saving an rdb iterates over all the data / pointers - } - } ;# standalone - } - } - } - - test "Active defrag can't be triggered during replicaof database flush. See issue #14267" { - start_server {tags {"repl"} overrides {save ""}} { - set master_host [srv 0 host] - set master_port [srv 0 port] - - start_server {overrides {save ""}} { - set replica [srv 0 client] - set rd [redis_deferring_client 0] - - $replica config set hz 100 - $replica config set activedefrag no - $replica config set active-defrag-threshold-lower 5 - $replica config set active-defrag-cycle-min 65 - $replica config set active-defrag-cycle-max 75 - $replica config set active-defrag-ignore-bytes 2mb - - # add a mass of string keys - set count 0 - for {set j 0} {$j < 500000} {incr j} { - $rd setrange $j 150 a - - incr count - discard_replies_every $rd $count 10000 10000 - } - assert_equal [$replica dbsize] 500000 - - # create some fragmentation - set count 0 - for {set j 0} {$j < 500000} {incr j 2} { - $rd del $j - - incr count - discard_replies_every $rd $count 10000 10000 - } - $rd close - assert_equal [$replica dbsize] 250000 - - catch {$replica config set activedefrag yes} e - if {[$replica config get activedefrag] eq "activedefrag yes"} { - # Start replication sync which will flush the replica's database, - # then enable defrag to run concurrently with the database flush. - $replica replicaof $master_host $master_port - - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [$replica info memory] - puts [$replica info stats] - puts [$replica memory malloc-stats] - fail "defrag not started." - } - - wait_for_sync $replica - - # wait for the active defrag to stop working (db has been emptied during replication sync) - wait_for_defrag_stop 500 100 - assert_equal [$replica dbsize] 0 - } - } - } - } {} {defrag external:skip tsan:skip debug_defrag:skip cluster} - - start_cluster 1 0 {tags {"defrag external:skip tsan:skip debug_defrag:skip cluster needs:debug"} overrides {appendonly yes auto-aof-rewrite-percentage 0 save "" loglevel notice}} { - test_active_defrag "cluster" - } - - start_server {tags {"defrag external:skip tsan:skip debug_defrag:skip standalone needs:debug"} overrides {appendonly yes auto-aof-rewrite-percentage 0 save "" loglevel notice}} { - test_active_defrag "standalone" - } -} ;# run_solo diff --git a/examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl b/examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl deleted file mode 100644 index aa571b7..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl +++ /dev/null @@ -1,207 +0,0 @@ -set testmodule [file normalize tests/modules/aclcheck.so] - -start_server {tags {"modules acl external:skip"}} { - r module load $testmodule - - test {test module check acl for command perm} { - # by default all commands allowed - assert_equal [r aclcheck.rm_call.check.cmd set x 5] OK - # block SET command for user - r acl setuser default -set - catch {r aclcheck.rm_call.check.cmd set x 5} e - assert_match {*DENIED CMD*} $e - - # verify that new log entry added - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry username] eq {default}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry object] eq {set}} - assert {[dict get $entry reason] eq {command}} - } - - test {test module check acl for key prefix permission} { - r acl setuser default +set resetkeys ~CART* %W~ORDER* %R~PRODUCT* ~ESCAPED_STAR\\* ~NON_ESCAPED_STAR\\\\* - - # check for key permission of prefix CART* (READ+WRITE) - catch {r aclcheck.set.check.prefixkey "~" CAR CART_CLOTHES_7 5} e - assert_match "*DENIED KEY*" $e - assert_equal [r aclcheck.set.check.prefixkey "~" CART CART 5] OK - assert_equal [r aclcheck.set.check.prefixkey "W" CART_BOOKS CART_BOOKS_12 5] OK - assert_equal [r aclcheck.set.check.prefixkey "R" CART_CLOTHES CART_CLOTHES_7 5] OK - - # check for key permission of prefix ORDER* (WRITE) - catch {r aclcheck.set.check.prefixkey "~" ORDE ORDER_2024_155351 5} e - assert_match "*DENIED KEY*" $e - assert_equal [r aclcheck.set.check.prefixkey "~" ORDER ORDER 5] OK - assert_equal [r aclcheck.set.check.prefixkey "W" ORDER_2024 ORDER_2024_564879 5] OK - assert_equal [r aclcheck.set.check.prefixkey "~" ORDER_2023 ORDER_2023_564879 5] OK - catch {r aclcheck.set.check.prefixkey "R" ORDER_2023 ORDER_2023_564879 5} - assert_match "*DENIED KEY*" $e - - # check for key permission of prefix PRODUCT* (READ) - catch {r aclcheck.set.check.prefixkey "~" PRODUC PRODUCT_CLOTHES_753376 5} e - assert_match "*DENIED KEY*" $e - assert_equal [r aclcheck.set.check.prefixkey "~" PRODUCT PRODUCT 5] OK - assert_equal [r aclcheck.set.check.prefixkey "~" PRODUCT_BOOKS PRODUCT_BOOKS_753376 5] OK - - # pattern ends with a escaped '*' character should not be counted as a prefix - catch {r aclcheck.set.check.prefixkey "~" ESCAPED_STAR ESCAPED_STAR_12 5} e - assert_match "*DENIED KEY*" $e - catch {r aclcheck.set.check.prefixkey "~" ESCAPED_STAR* ESCAPED_STAR* 5} e - assert_match "*DENIED KEY*" $e - assert_equal [r aclcheck.set.check.prefixkey "~" NON_ESCAPED_STAR\\ NON_ESCAPED_STAR\\clothes 5] OK - } - - test {check ACL permissions versus empty string prefix} { - # The empty string should should match all keys permissions - r acl setuser default +set resetkeys %R~* %W~* ~* - assert_equal [r aclcheck.set.check.prefixkey "~" "" CART_BOOKS_12 5] OK - assert_equal [r aclcheck.set.check.prefixkey "W" "" ORDER_2024_564879 5] OK - assert_equal [r aclcheck.set.check.prefixkey "R" "" PRODUCT_BOOKS_753376 5] OK - - # The empty string prefix should not match if cannot access all keys - r acl setuser default +set resetkeys %R~x* %W~x* ~x* - catch {r aclcheck.set.check.prefixkey "~" "" CART_BOOKS_12 5} e - assert_match "*DENIED KEY*" $e - } - - test {test module check acl for key perm} { - # give permission for SET and block all keys but x(READ+WRITE), y(WRITE), z(READ) - r acl setuser default +set resetkeys ~x %W~y %R~z ~ESCAPED_STAR\\* - - assert_equal [r aclcheck.set.check.key "*" x 5] OK - catch {r aclcheck.set.check.key "*" v 5} e - assert_match "*DENIED KEY*" $e - - assert_equal [r aclcheck.set.check.key "~" x 5] OK - assert_equal [r aclcheck.set.check.key "~" y 5] OK - assert_equal [r aclcheck.set.check.key "~" z 5] OK - catch {r aclcheck.set.check.key "~" v 5} e - assert_match "*DENIED KEY*" $e - - assert_equal [r aclcheck.set.check.key "W" y 5] OK - catch {r aclcheck.set.check.key "W" v 5} e - assert_match "*DENIED KEY*" $e - - assert_equal [r aclcheck.set.check.key "R" z 5] OK - catch {r aclcheck.set.check.key "R" v 5} e - assert_match "*DENIED KEY*" $e - - # check pattern ends with escaped '*' character - assert_equal [r aclcheck.set.check.key "~" ESCAPED_STAR* 5] OK - } - - test {test module check acl for module user} { - # the module user has access to all keys - assert_equal [r aclcheck.rm_call.check.cmd.module.user set y 5] OK - } - - test {test module check acl for channel perm} { - # block all channels but ch1 - r acl setuser default resetchannels &ch1 - assert_equal [r aclcheck.publish.check.channel ch1 msg] 0 - catch {r aclcheck.publish.check.channel ch2 msg} e - set e - } {*DENIED CHANNEL*} - - test {test module check acl in rm_call} { - # rm call check for key permission (x: READ + WRITE) - assert_equal [r aclcheck.rm_call set x 5] OK - assert_equal [r aclcheck.rm_call set x 6 get] 5 - - # rm call check for key permission (y: only WRITE) - assert_equal [r aclcheck.rm_call set y 5] OK - assert_error {*NOPERM*} {r aclcheck.rm_call set y 5 get} - assert_error {*NOPERM*No permissions to access a key*} {r aclcheck.rm_call_with_errors set y 5 get} - - # rm call check for key permission (z: only READ) - assert_error {*NOPERM*} {r aclcheck.rm_call set z 5} - catch {r aclcheck.rm_call_with_errors set z 5} e - assert_match {*NOPERM*No permissions to access a key*} $e - assert_error {*NOPERM*} {r aclcheck.rm_call set z 6 get} - assert_error {*NOPERM*No permissions to access a key*} {r aclcheck.rm_call_with_errors set z 6 get} - - # verify that new log entry added - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry username] eq {default}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry object] eq {z}} - assert {[dict get $entry reason] eq {key}} - - # rm call check for command permission - r acl setuser default -set - assert_error {*NOPERM*} {r aclcheck.rm_call set x 5} - assert_error {*NOPERM*has no permissions to run the 'set' command*} {r aclcheck.rm_call_with_errors set x 5} - - # verify that new log entry added - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry username] eq {default}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry object] eq {set}} - assert {[dict get $entry reason] eq {command}} - } - - test {test blocking of Commands outside of OnLoad} { - assert_equal [r block.commands.outside.onload] OK - } - - test {test users to have access to module commands having acl categories} { - r acl SETUSER j1 on >password -@all +@WRITE - r acl SETUSER j2 on >password -@all +@READ - assert_equal [r acl DRYRUN j1 aclcheck.module.command.aclcategories.write] OK - assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.write.function.read.category] OK - assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.read.only.category] OK - } - - test {Unload the module - aclcheck} { - assert_equal {OK} [r module unload aclcheck] - } -} - -start_server {tags {"modules acl external:skip"}} { - test {test existing users to have access to module commands loaded on runtime} { - r acl SETUSER j3 on >password -@all +@WRITE - assert_equal [r module load $testmodule] OK - assert_equal [r acl DRYRUN j3 aclcheck.module.command.aclcategories.write] OK - assert_equal {OK} [r module unload aclcheck] - } -} - -start_server {tags {"modules acl external:skip"}} { - test {test existing users without permissions, do not have access to module commands loaded on runtime.} { - r acl SETUSER j4 on >password -@all +@READ - r acl SETUSER j5 on >password -@all +@WRITE - assert_equal [r module load $testmodule] OK - catch {r acl DRYRUN j4 aclcheck.module.command.aclcategories.write} e - assert_equal {User j4 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e - catch {r acl DRYRUN j5 aclcheck.module.command.aclcategories.write.function.read.category} e - assert_equal {User j5 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e - } - - test {test users without permissions, do not have access to module commands.} { - r acl SETUSER j6 on >password -@all +@READ - catch {r acl DRYRUN j6 aclcheck.module.command.aclcategories.write} e - assert_equal {User j6 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e - r acl SETUSER j7 on >password -@all +@WRITE - catch {r acl DRYRUN j7 aclcheck.module.command.aclcategories.write.function.read.category} e - assert_equal {User j7 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e - } - - test {test if foocategory acl categories is added} { - r acl SETUSER j8 on >password -@all +@foocategory - assert_equal [r acl DRYRUN j8 aclcheck.module.command.test.add.new.aclcategories] OK - } - - test {test permission compaction and simplification for categories added by a module} { - r acl SETUSER j9 on >password -@all +@foocategory -@foocategory - catch {r ACL GETUSER j9} res - assert_equal {-@all -@foocategory} [lindex $res 5] - assert_equal {OK} [r module unload aclcheck] - } -} - -start_server {tags {"modules acl external:skip"}} { - test {test module load fails if exceeds the maximum number of adding acl categories} { - assert_error {ERR Error loading the extension. Please check the server logs.} {r module load $testmodule 1} - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl b/examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl deleted file mode 100644 index d645de4..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl +++ /dev/null @@ -1,437 +0,0 @@ -set testmodule [file normalize tests/modules/blockedclient.so] -set testmodule2 [file normalize tests/modules/postnotifications.so] -set testmodule3 [file normalize tests/modules/blockonkeys.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Locked GIL acquisition from async RM_Call} { - assert_equal {OK} [r do_rm_call_async acquire_gil] - } - - test "Blpop on async RM_Call fire and forget" { - assert_equal {Blocked} [r do_rm_call_fire_and_forget blpop l 0] - r lpush l a - assert_equal {0} [r llen l] - } - - test "Blpop on threaded async RM_Call" { - set rd [redis_deferring_client] - - $rd do_rm_call_async_on_thread blpop l 0 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {l a} - wait_for_blocked_clients_count 0 - $rd close - } - - foreach cmd {do_rm_call_async do_rm_call_async_script_mode } { - - test "Blpop on async RM_Call using $cmd" { - set rd [redis_deferring_client] - - $rd $cmd blpop l 0 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {l a} - wait_for_blocked_clients_count 0 - $rd close - } - - test "Brpop on async RM_Call using $cmd" { - set rd [redis_deferring_client] - - $rd $cmd brpop l 0 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {l a} - wait_for_blocked_clients_count 0 - $rd close - } - - test "Brpoplpush on async RM_Call using $cmd" { - set rd [redis_deferring_client] - - $rd $cmd brpoplpush l1 l2 0 - wait_for_blocked_clients_count 1 - r lpush l1 a - assert_equal [$rd read] {a} - wait_for_blocked_clients_count 0 - $rd close - r lpop l2 - } {a} - - test "Blmove on async RM_Call using $cmd" { - set rd [redis_deferring_client] - - $rd $cmd blmove l1 l2 LEFT LEFT 0 - wait_for_blocked_clients_count 1 - r lpush l1 a - assert_equal [$rd read] {a} - wait_for_blocked_clients_count 0 - $rd close - r lpop l2 - } {a} - - test "Bzpopmin on async RM_Call using $cmd" { - set rd [redis_deferring_client] - - $rd $cmd bzpopmin s 0 - wait_for_blocked_clients_count 1 - r zadd s 10 foo - assert_equal [$rd read] {s foo 10} - wait_for_blocked_clients_count 0 - $rd close - } - - test "Bzpopmax on async RM_Call using $cmd" { - set rd [redis_deferring_client] - - $rd $cmd bzpopmax s 0 - wait_for_blocked_clients_count 1 - r zadd s 10 foo - assert_equal [$rd read] {s foo 10} - wait_for_blocked_clients_count 0 - $rd close - } - } - - test {Nested async RM_Call} { - set rd [redis_deferring_client] - - $rd do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {l a} - wait_for_blocked_clients_count 0 - $rd close - } - - test {Test multiple async RM_Call waiting on the same event} { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - $rd1 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0 - $rd2 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0 - wait_for_blocked_clients_count 2 - r lpush l element element - assert_equal [$rd1 read] {l element} - assert_equal [$rd2 read] {l element} - wait_for_blocked_clients_count 0 - $rd1 close - $rd2 close - } - - test {async RM_Call calls RM_Call} { - assert_equal {PONG} [r do_rm_call_async do_rm_call ping] - } - - test {async RM_Call calls background RM_Call calls RM_Call} { - assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call ping] - } - - test {async RM_Call calls background RM_Call calls RM_Call calls async RM_Call} { - assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call do_rm_call_async ping] - } - - test {async RM_Call inside async RM_Call callback} { - set rd [redis_deferring_client] - $rd wait_and_do_rm_call blpop l 0 - wait_for_blocked_clients_count 1 - - start_server {} { - test "Connect a replica to the master instance" { - r slaveof [srv -1 host] [srv -1 port] - wait_for_condition 50 100 { - [s role] eq {slave} && - [string match {*master_link_status:up*} [r info replication]] - } else { - fail "Can't turn the instance into a replica" - } - } - - assert_equal {1} [r -1 lpush l a] - assert_equal [$rd read] {l a} - } - - wait_for_blocked_clients_count 0 - $rd close - } - - test {Become replica while having async RM_Call running} { - r flushall - set rd [redis_deferring_client] - $rd do_rm_call_async blpop l 0 - wait_for_blocked_clients_count 1 - - #become a replica of a not existing redis - r replicaof localhost 30000 - - catch {[$rd read]} e - assert_match {UNBLOCKED force unblock from blocking operation*} $e - wait_for_blocked_clients_count 0 - - r replicaof no one - - r lpush l 1 - # make sure the async rm_call was aborted - assert_equal [r llen l] {1} - $rd close - } - - test {Pipeline with blocking RM_Call} { - r flushall - set rd [redis_deferring_client] - set buf "" - append buf "do_rm_call_async blpop l 0\r\n" - append buf "ping\r\n" - $rd write $buf - $rd flush - wait_for_blocked_clients_count 1 - - # release the blocked client - r lpush l 1 - - assert_equal [$rd read] {l 1} - assert_equal [$rd read] {PONG} - - wait_for_blocked_clients_count 0 - $rd close - } - - test {blocking RM_Call abort} { - r flushall - set rd [redis_deferring_client] - - $rd client id - set client_id [$rd read] - - $rd do_rm_call_async blpop l 0 - wait_for_blocked_clients_count 1 - - r client kill ID $client_id - assert_error {*error reading reply*} {$rd read} - - wait_for_blocked_clients_count 0 - - r lpush l 1 - # make sure the async rm_call was aborted - assert_equal [r llen l] {1} - $rd close - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Test basic replication stream on unblock handler} { - r flushall - set repl [attach_to_replication_stream] - - set rd [redis_deferring_client] - - $rd do_rm_call_async blpop l 0 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {l a} - - assert_replication_stream $repl { - {select *} - {lpush l a} - {lpop l} - } - close_replication_stream $repl - - wait_for_blocked_clients_count 0 - $rd close - } - - test {Test unblock handler are executed as a unit} { - r flushall - set repl [attach_to_replication_stream] - - set rd [redis_deferring_client] - - $rd blpop_and_set_multiple_keys l x 1 y 2 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {OK} - - assert_replication_stream $repl { - {select *} - {lpush l a} - {multi} - {lpop l} - {set x 1} - {set y 2} - {exec} - } - close_replication_stream $repl - - wait_for_blocked_clients_count 0 - $rd close - } - - test {Test no propagation of blocking command} { - r flushall - set repl [attach_to_replication_stream] - - set rd [redis_deferring_client] - - $rd do_rm_call_async_no_replicate blpop l 0 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {l a} - - # make sure the lpop are not replicated - r set x 1 - - assert_replication_stream $repl { - {select *} - {lpush l a} - {set x 1} - } - close_replication_stream $repl - - wait_for_blocked_clients_count 0 - $rd close - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - r module load $testmodule2 - - test {Test unblock handler are executed as a unit with key space notifications} { - r flushall - set repl [attach_to_replication_stream] - - set rd [redis_deferring_client] - - $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {OK} - - # Explanation of the first multi exec block: - # {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys' - # {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys' - # {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys' - # {incr string_changed{string_foo}} - post notification job that was registered when 'string_foo' changed - # {incr string_changed{string_bar}} - post notification job that was registered when 'string_bar' changed - # {incr string_total} - post notification job that was registered when string_changed{string_foo} changed - # {incr string_total} - post notification job that was registered when string_changed{string_bar} changed - assert_replication_stream $repl { - {select *} - {lpush l a} - {multi} - {lpop l} - {set string_foo 1} - {set string_bar 2} - {incr string_changed{string_foo}} - {incr string_changed{string_bar}} - {incr string_total} - {incr string_total} - {exec} - } - close_replication_stream $repl - - wait_for_blocked_clients_count 0 - $rd close - } - - test {Test unblock handler are executed as a unit with lazy expire} { - r flushall - r DEBUG SET-ACTIVE-EXPIRE 0 - set repl [attach_to_replication_stream] - - set rd [redis_deferring_client] - - $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {OK} - - # set expiration on string_foo - r pexpire string_foo 1 - after 10 - - # now the key should have been expired - $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2 - wait_for_blocked_clients_count 1 - r lpush l a - assert_equal [$rd read] {OK} - - # Explanation of the first multi exec block: - # {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys' - # {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys' - # {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys' - # {incr string_changed{string_foo}} - post notification job that was registered when 'string_foo' changed - # {incr string_changed{string_bar}} - post notification job that was registered when 'string_bar' changed - # {incr string_total} - post notification job that was registered when string_changed{string_foo} changed - # {incr string_total} - post notification job that was registered when string_changed{string_bar} changed - # - # Explanation of the second multi exec block: - # {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys' - # {del string_foo} - lazy expiration of string_foo when 'blpop_and_set_multiple_keys' tries to write to it. - # {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys' - # {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys' - # {incr expired} - the post notification job, registered after string_foo got expired - # {incr string_changed{string_foo}} - post notification job triggered when we set string_foo - # {incr string_changed{string_bar}} - post notification job triggered when we set string_bar - # {incr string_total} - post notification job triggered when we incr 'string_changed{string_foo}' - # {incr string_total} - post notification job triggered when we incr 'string_changed{string_bar}' - assert_replication_stream $repl { - {select *} - {lpush l a} - {multi} - {lpop l} - {set string_foo 1} - {set string_bar 2} - {incr string_changed{string_foo}} - {incr string_changed{string_bar}} - {incr string_total} - {incr string_total} - {exec} - {pexpireat string_foo *} - {lpush l a} - {multi} - {lpop l} - {del string_foo} - {set string_foo 1} - {set string_bar 2} - {incr expired} - {incr string_changed{string_foo}} - {incr string_changed{string_bar}} - {incr string_total} - {incr string_total} - {exec} - } - close_replication_stream $repl - r DEBUG SET-ACTIVE-EXPIRE 1 - - wait_for_blocked_clients_count 0 - $rd close - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - r module load $testmodule3 - - test {Test unblock handler on module blocked on keys} { - set rd [redis_deferring_client] - - r fsl.push l 1 - $rd do_rm_call_async FSL.BPOPGT l 3 0 - wait_for_blocked_clients_count 1 - r fsl.push l 2 - r fsl.push l 3 - r fsl.push l 4 - assert_equal [$rd read] {4} - - wait_for_blocked_clients_count 0 - $rd close - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/auth.tcl b/examples/redis-unstable/tests/unit/moduleapi/auth.tcl deleted file mode 100644 index e8950ed..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/auth.tcl +++ /dev/null @@ -1,90 +0,0 @@ -set testmodule [file normalize tests/modules/auth.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Modules can create a user that can be authenticated} { - # Make sure we start authenticated with default user - r auth default "" - assert_equal [r acl whoami] "default" - r auth.createmoduleuser - - set id [r auth.authmoduleuser] - assert_equal [r client id] $id - - # Verify returned id is the same as our current id and - # we are authenticated with the specified user - assert_equal [r acl whoami] "global" - } - - test {De-authenticating clients is tracked and kills clients} { - assert_equal [r auth.changecount] 0 - r auth.createmoduleuser - - # Catch the I/O exception that was thrown when Redis - # disconnected with us. - catch { [r ping] } e - assert_match {*I/O*} $e - - # Check that a user change was registered - assert_equal [r auth.changecount] 1 - } - - test {Modules can't authenticate with ACLs users that dont exist} { - catch { [r auth.authrealuser auth-module-test-fake] } e - assert_match {*Invalid user*} $e - } - - test {Modules can authenticate with ACL users} { - assert_equal [r acl whoami] "default" - - # Create user to auth into - r acl setuser auth-module-test on allkeys allcommands - - set id [r auth.authrealuser auth-module-test] - - # Verify returned id is the same as our current id and - # we are authenticated with the specified user - assert_equal [r client id] $id - assert_equal [r acl whoami] "auth-module-test" - } - - test {Client callback is called on user switch} { - assert_equal [r auth.changecount] 0 - - # Auth again and validate change count - r auth.authrealuser auth-module-test - assert_equal [r auth.changecount] 1 - - # Re-auth with the default user - r auth default "" - assert_equal [r auth.changecount] 1 - assert_equal [r acl whoami] "default" - - # Re-auth with the default user again, to - # verify the callback isn't fired again - r auth default "" - assert_equal [r auth.changecount] 0 - assert_equal [r acl whoami] "default" - } - - test {modules can redact arguments} { - r config set slowlog-log-slower-than 0 - r slowlog reset - r auth.redact 1 2 3 4 - r auth.redact 1 2 3 - r config set slowlog-log-slower-than -1 - set slowlog_resp [r slowlog get] - - # There will be 3 records, slowlog reset and the - # two auth redact calls. - assert_equal 3 [llength $slowlog_resp] - assert_equal {slowlog reset} [lindex [lindex $slowlog_resp 2] 3] - assert_equal {auth.redact 1 (redacted) 3 (redacted)} [lindex [lindex $slowlog_resp 1] 3] - assert_equal {auth.redact (redacted) 2 (redacted)} [lindex [lindex $slowlog_resp 0] 3] - } - - test "Unload the module - testacl" { - assert_equal {OK} [r module unload testacl] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/basics.tcl b/examples/redis-unstable/tests/unit/moduleapi/basics.tcl deleted file mode 100644 index 49ea9f3..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/basics.tcl +++ /dev/null @@ -1,70 +0,0 @@ -set testmodule [file normalize tests/modules/basics.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {test module api basics} { - r test.basics - } {ALL TESTS PASSED} - - test {test rm_call auto mode} { - r hello 2 - set reply [r test.rmcallautomode] - assert_equal [lindex $reply 0] f1 - assert_equal [lindex $reply 1] v1 - assert_equal [lindex $reply 2] f2 - assert_equal [lindex $reply 3] v2 - r hello 3 - set reply [r test.rmcallautomode] - assert_equal [dict get $reply f1] v1 - assert_equal [dict get $reply f2] v2 - } - - test {test get resp} { - foreach resp {3 2} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$resp == 3} {continue} - } elseif {$::force_resp3} { - if {$resp == 2} {continue} - } - r hello $resp - set reply [r test.getresp] - assert_equal $reply $resp - r hello 2 - } - } - - test "Unload the module - test" { - assert_equal {OK} [r module unload test] - } -} - -start_server {tags {"modules external:skip"} overrides {enable-module-command no}} { - test {module command disabled} { - assert_error "ERR *MODULE command not allowed*" {r module load $testmodule} - } -} - -start_server {tags {"modules external:skip"} overrides {enable-debug-command no}} { - r module load $testmodule - - test {debug command disabled} { - assert_equal {no} [r test.candebug] - } -} - -start_server {tags {"modules external:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - test {debug command enabled} { - assert_equal {yes} [r test.candebug] - } -} - -start_server {tags {"modules external:skip"} overrides {enable-debug-command local}} { - r module load $testmodule - - test {debug commands are enabled for local connection} { - assert_equal {yes} [r test.candebug] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl b/examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl deleted file mode 100644 index 7dcc1d6..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl +++ /dev/null @@ -1,310 +0,0 @@ -set testmodule [file normalize tests/modules/blockedclient.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Locked GIL acquisition} { - assert_match "OK" [r acquire_gil] - } - - test {Locked GIL acquisition during multi} { - r multi - r acquire_gil - assert_equal {{Blocked client is not supported inside multi}} [r exec] - } - - test {Locked GIL acquisition from RM_Call} { - assert_equal {Blocked client is not allowed} [r do_rm_call acquire_gil] - } - - test {Blocking command are not block the client on RM_Call} { - r lpush l test - assert_equal [r do_rm_call blpop l 0] {l test} - - r lpush l test - assert_equal [r do_rm_call brpop l 0] {l test} - - r lpush l1 test - assert_equal [r do_rm_call brpoplpush l1 l2 0] {test} - assert_equal [r do_rm_call brpop l2 0] {l2 test} - - r lpush l1 test - assert_equal [r do_rm_call blmove l1 l2 LEFT LEFT 0] {test} - assert_equal [r do_rm_call brpop l2 0] {l2 test} - - r ZADD zset1 0 a 1 b 2 c - assert_equal [r do_rm_call bzpopmin zset1 0] {zset1 a 0} - assert_equal [r do_rm_call bzpopmax zset1 0] {zset1 c 2} - - r xgroup create s g $ MKSTREAM - r xadd s * foo bar - assert {[r do_rm_call xread BLOCK 0 STREAMS s 0-0] ne {}} - assert {[r do_rm_call xreadgroup group g c BLOCK 0 STREAMS s >] ne {}} - - assert {[r do_rm_call blpop empty_list 0] eq {}} - assert {[r do_rm_call brpop empty_list 0] eq {}} - assert {[r do_rm_call brpoplpush empty_list1 empty_list2 0] eq {}} - assert {[r do_rm_call blmove empty_list1 empty_list2 LEFT LEFT 0] eq {}} - - assert {[r do_rm_call bzpopmin empty_zset 0] eq {}} - assert {[r do_rm_call bzpopmax empty_zset 0] eq {}} - - r xgroup create empty_stream g $ MKSTREAM - assert {[r do_rm_call xread BLOCK 0 STREAMS empty_stream $] eq {}} - assert {[r do_rm_call xreadgroup group g c BLOCK 0 STREAMS empty_stream >] eq {}} - - } - - test {Monitor disallow inside RM_Call} { - set e {} - catch { - r do_rm_call monitor - } e - set e - } {*ERR*DENY BLOCKING*} - - test {subscribe disallow inside RM_Call} { - set e {} - catch { - r do_rm_call subscribe x - } e - set e - } {*ERR*DENY BLOCKING*} - - test {RM_Call from blocked client} { - r hset hash foo bar - r do_bg_rm_call hgetall hash - } {foo bar} - - test {RM_Call from blocked client with script mode} { - r do_bg_rm_call_format S hset k foo bar - } {1} - - test {RM_Call from blocked client with oom mode} { - r config set maxmemory 1 - # will set server.pre_command_oom_state to 1 - assert_error {OOM command not allowed*} {r hset hash foo bar} - r config set maxmemory 0 - # now its should be OK to call OOM commands - r do_bg_rm_call_format M hset k1 foo bar - } {1} {needs:config-maxmemory} - - test {RESP version carries through to blocked client} { - for {set client_proto 2} {$client_proto <= 3} {incr client_proto} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$client_proto == 3} {continue} - } elseif {$::force_resp3} { - if {$client_proto == 2} {continue} - } - r hello $client_proto - r readraw 1 - set ret [r do_fake_bg_true] - if {$client_proto == 2} { - assert_equal $ret {:1} - } else { - assert_equal $ret "#t" - } - r readraw 0 - r hello 2 - } - } - -foreach call_type {nested normal} { - test "Busy module command - $call_type" { - set busy_time_limit 50 - set old_time_limit [lindex [r config get busy-reply-threshold] 1] - r config set busy-reply-threshold $busy_time_limit - set rd [redis_deferring_client] - - # run command that blocks until released - set start [clock clicks -milliseconds] - if {$call_type == "nested"} { - $rd do_rm_call slow_fg_command 0 - } else { - $rd slow_fg_command 0 - } - $rd flush - - # send another command after the blocked one, to make sure we don't attempt to process it - $rd ping - $rd flush - - # make sure we get BUSY error, and that we didn't get it too early - wait_for_condition 50 100 { - ([catch {r ping} reply] == 1) && - ([string match {*BUSY Slow module operation*} $reply]) - } else { - fail "Failed waiting for busy slow response" - } - assert_morethan_equal [expr [clock clicks -milliseconds]-$start] $busy_time_limit - - # abort the blocking operation - r stop_slow_fg_command - wait_for_condition 50 100 { - [catch {r ping} e] == 0 - } else { - fail "Failed waiting for busy command to end" - } - assert_equal [$rd read] "1" - assert_equal [$rd read] "PONG" - - # run command that blocks for 200ms - set start [clock clicks -milliseconds] - if {$call_type == "nested"} { - $rd do_rm_call slow_fg_command 200000 - } else { - $rd slow_fg_command 200000 - } - $rd flush - after 10 ;# try to make sure redis started running the command before we proceed - - # make sure we didn't get BUSY error, it simply blocked till the command was done - r ping - # The command blocks for 200ms, allow 1-2ms clock skew (1%) - # to accommodate differences between using of monotonic timer and ustime - assert_morethan_equal [expr [clock clicks -milliseconds]-$start] 198 - $rd read - - $rd close - r config set busy-reply-threshold $old_time_limit - } -} - - test {RM_Call from blocked client} { - set busy_time_limit 50 - set old_time_limit [lindex [r config get busy-reply-threshold] 1] - r config set busy-reply-threshold $busy_time_limit - - # trigger slow operation - r set_slow_bg_operation 1 - r hset hash foo bar - set rd [redis_deferring_client] - set start [clock clicks -milliseconds] - $rd do_bg_rm_call hgetall hash - - # send another command after the blocked one, to make sure we don't attempt to process it - $rd ping - $rd flush - - # wait till we know we're blocked inside the module - wait_for_condition 50 100 { - [r is_in_slow_bg_operation] eq 1 - } else { - fail "Failed waiting for slow operation to start" - } - - # make sure we get BUSY error, and that we didn't get here too early - assert_error {*BUSY Slow module operation*} {r ping} - assert_morethan_equal [expr [clock clicks -milliseconds]-$start] $busy_time_limit - # abort the blocking operation - r set_slow_bg_operation 0 - - wait_for_condition 50 100 { - [r is_in_slow_bg_operation] eq 0 - } else { - fail "Failed waiting for slow operation to stop" - } - assert_equal [r ping] {PONG} - - r config set busy-reply-threshold $old_time_limit - assert_equal [$rd read] {foo bar} - assert_equal [$rd read] {PONG} - $rd close - } - - test {blocked client reaches client output buffer limit} { - r hset hash big [string repeat x 50000] - r hset hash bada [string repeat x 50000] - r hset hash boom [string repeat x 50000] - r config set client-output-buffer-limit {normal 100000 0 0} - r client setname myclient - catch {r do_bg_rm_call hgetall hash} e - assert_match "*I/O error*" $e - reconnect - set clients [r client list] - assert_no_match "*name=myclient*" $clients - } - - test {module client error stats} { - r config resetstat - - # simple module command that replies with string error - assert_error "ERR unknown command 'hgetalllll'" {r do_rm_call hgetalllll} - assert_equal [errorrstat ERR r] {count=1} - - # simple module command that replies with string error - assert_error "ERR unknown subcommand 'bla'. Try CONFIG HELP." {r do_rm_call config bla} - assert_equal [errorrstat ERR r] {count=2} - - # module command that replies with string error from bg thread - assert_error "NULL reply returned" {r do_bg_rm_call hgetalllll} - assert_equal [errorrstat NULL r] {count=1} - - # module command that returns an arity error - r do_rm_call set x x - assert_error "ERR wrong number of arguments for 'do_rm_call' command" {r do_rm_call} - assert_equal [errorrstat ERR r] {count=3} - - # RM_Call that propagates an error - assert_error "WRONGTYPE*" {r do_rm_call hgetall x} - assert_equal [errorrstat WRONGTYPE r] {count=1} - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdrstat hgetall r] - - # RM_Call from bg thread that propagates an error - assert_error "WRONGTYPE*" {r do_bg_rm_call hgetall x} - assert_equal [errorrstat WRONGTYPE r] {count=2} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat hgetall r] - - assert_equal [s total_error_replies] 6 - assert_match {*calls=5,*,rejected_calls=0,failed_calls=4} [cmdrstat do_rm_call r] - assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat do_bg_rm_call r] - } - - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] { - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - - # Start the replication process... - $replica replicaof $master_host $master_port - wait_for_sync $replica - - test {WAIT command on module blocked client} { - pause_process [srv 0 pid] - - $master do_bg_rm_call_format ! hset bk1 foo bar - - assert_equal [$master wait 1 1000] 0 - resume_process [srv 0 pid] - assert_equal [$master wait 1 1000] 1 - assert_equal [$replica hget bk1 foo] bar - } - } - - test {Unblock by timer} { - # When the client is unlock, we will get the OK reply. - assert_match "OK" [r unblock_by_timer 100 0] - } - - test {block time is shorter than timer period} { - # This command does not have the reply. - set rd [redis_deferring_client] - $rd unblock_by_timer 100 10 - # Wait for the client to unlock. - after 120 - $rd close - } - - test {block time is equal to timer period} { - # These time is equal, they will be unlocked in the same event loop, - # when the client is unlock, we will get the OK reply from timer. - assert_match "OK" [r unblock_by_timer 100 100] - } - - test "Unload the module - blockedclient" { - assert_equal {OK} [r module unload blockedclient] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl b/examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl deleted file mode 100644 index e8b9f55..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl +++ /dev/null @@ -1,124 +0,0 @@ -set testmodule [file normalize tests/modules/blockonbackground.so] - -proc latency_percentiles_usec {cmd} { - return [latencyrstat_percentiles $cmd r] -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test { blocked clients time tracking - check blocked command that uses RedisModule_BlockedClientMeasureTimeStart() is tracking background time} { - r slowlog reset - r config set slowlog-log-slower-than 200000 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r block.debug 0 10000 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r config resetstat - r config set latency-tracking yes - r config set latency-tracking-info-percentiles "50.0" - r block.debug 200 10000 - if {!$::no_latency} { - assert_equal [r slowlog len] 1 - } - - set cmdstatline [cmdrstat block.debug r] - set latencystatline_debug [latency_percentiles_usec block.debug] - - regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline -> usec usec_per_call - regexp "p50=(.+\..+)" $latencystatline_debug -> p50 - assert {$usec >= 100000} - assert {$usec_per_call >= 100000} - assert {$p50 >= 100000} - } - - test { blocked clients time tracking - check blocked command that uses RedisModule_BlockedClientMeasureTimeStart() is tracking background time even in timeout } { - r slowlog reset - r config set slowlog-log-slower-than 200000 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r block.debug 0 20000 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r config resetstat - r block.debug 20000 500 - if {!$::no_latency} { - assert_equal [r slowlog len] 1 - } - - set cmdstatline [cmdrstat block.debug r] - - regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline usec usec_per_call - assert {$usec >= 250000} - assert {$usec_per_call >= 250000} - } - - test { blocked clients time tracking - check blocked command with multiple calls RedisModule_BlockedClientMeasureTimeStart() is tracking the total background time } { - r slowlog reset - r config set slowlog-log-slower-than 200000 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r block.double_debug 0 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r config resetstat - r block.double_debug 100 - if {!$::no_latency} { - assert_equal [r slowlog len] 1 - } - set cmdstatline [cmdrstat block.double_debug r] - - regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline usec usec_per_call - assert {$usec >= 60000} - assert {$usec_per_call >= 60000} - } - - test { blocked clients time tracking - check blocked command without calling RedisModule_BlockedClientMeasureTimeStart() is not reporting background time } { - r slowlog reset - r config set slowlog-log-slower-than 200000 - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - r block.debug_no_track 200 1000 - # ensure slowlog is still empty - if {!$::no_latency} { - assert_equal [r slowlog len] 0 - } - } - - test "client unblock works only for modules with timeout support" { - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - - # Block with a timeout function - may unblock - $rd block.block 20000 - wait_for_condition 50 100 { - [r block.is_blocked] == 1 - } else { - fail "Module did not block" - } - - assert_equal 1 [r client unblock $id] - assert_match {*Timed out*} [$rd read] - - # Block without a timeout function - cannot unblock - $rd block.block 0 - wait_for_condition 50 100 { - [r block.is_blocked] == 1 - } else { - fail "Module did not block" - } - - assert_equal 0 [r client unblock $id] - assert_equal "OK" [r block.release foobar] - assert_equal "foobar" [$rd read] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl b/examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl deleted file mode 100644 index 53aee73..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl +++ /dev/null @@ -1,366 +0,0 @@ -set testmodule [file normalize tests/modules/blockonkeys.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test "Module client blocked on keys: Circular BPOPPUSH" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - r del src dst - - $rd1 fsl.bpoppush src dst 0 - wait_for_blocked_clients_count 1 - - $rd2 fsl.bpoppush dst src 0 - wait_for_blocked_clients_count 2 - - r fsl.push src 42 - wait_for_blocked_clients_count 0 - - assert_equal {42} [r fsl.getall src] - assert_equal {} [r fsl.getall dst] - } - - test "Module client blocked on keys: Self-referential BPOPPUSH" { - set rd1 [redis_deferring_client] - - r del src - - $rd1 fsl.bpoppush src src 0 - wait_for_blocked_clients_count 1 - r fsl.push src 42 - - assert_equal {42} [r fsl.getall src] - } - - test "Module client blocked on keys: BPOPPUSH unblocked by timer" { - set rd1 [redis_deferring_client] - - r del src dst - - set repl [attach_to_replication_stream] - - $rd1 fsl.bpoppush src dst 0 - wait_for_blocked_clients_count 1 - - r fsl.pushtimer src 9000 10 - wait_for_blocked_clients_count 0 - - assert_equal {9000} [r fsl.getall dst] - assert_equal {} [r fsl.getall src] - - assert_replication_stream $repl { - {select *} - {fsl.push src 9000} - {fsl.bpoppush src dst 0} - } - - close_replication_stream $repl - } {} {needs:repl} - - test {Module client blocked on keys (no metadata): No block} { - r del k - r fsl.push k 33 - r fsl.push k 34 - r fsl.bpop k 0 - } {34} - - test {Module client blocked on keys (no metadata): Timeout} { - r del k - set rd [redis_deferring_client] - $rd fsl.bpop k 1 - assert_equal {Request timedout} [$rd read] - } - - test {Module client blocked on keys (no metadata): Blocked} { - r del k - set rd [redis_deferring_client] - $rd fsl.bpop k 0 - wait_for_blocked_clients_count 1 - r fsl.push k 34 - assert_equal {34} [$rd read] - } - - test {Module client blocked on keys (with metadata): No block} { - r del k - r fsl.push k 34 - r fsl.bpopgt k 30 0 - } {34} - - test {Module client blocked on keys (with metadata): Timeout} { - r del k - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - r fsl.push k 33 - $rd fsl.bpopgt k 35 1 - assert_equal {Request timedout} [$rd read] - r client kill id $cid ;# try to smoke-out client-related memory leak - } - - test {Module client blocked on keys (with metadata): Blocked, case 1} { - r del k - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - r fsl.push k 33 - $rd fsl.bpopgt k 33 0 - wait_for_blocked_clients_count 1 - r fsl.push k 34 - assert_equal {34} [$rd read] - r client kill id $cid ;# try to smoke-out client-related memory leak - } - - test {Module client blocked on keys (with metadata): Blocked, case 2} { - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r fsl.push k 33 - r fsl.push k 34 - r fsl.push k 35 - r fsl.push k 36 - assert_equal {36} [$rd read] - } - - test {Module client blocked on keys (with metadata): Blocked, DEL} { - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r del k - assert_error {*UNBLOCKED key no longer exists*} {$rd read} - } - - test {Module client blocked on keys (with metadata): Blocked, FLUSHALL} { - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r flushall - assert_error {*UNBLOCKED key no longer exists*} {$rd read} - } - - test {Module client blocked on keys (with metadata): Blocked, SWAPDB, no key} { - r select 9 - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r swapdb 0 9 - assert_error {*UNBLOCKED key no longer exists*} {$rd read} - } - - test {Module client blocked on keys (with metadata): Blocked, SWAPDB, key exists, case 1} { - ;# Key exists on other db, but wrong type - r flushall - r select 9 - r fsl.push k 32 - r select 0 - r lpush k 38 - r select 9 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r swapdb 0 9 - assert_error {*UNBLOCKED key no longer exists*} {$rd read} - r select 9 - } - - test {Module client blocked on keys (with metadata): Blocked, SWAPDB, key exists, case 2} { - ;# Key exists on other db, with the right type, but the value doesn't allow to unblock - r flushall - r select 9 - r fsl.push k 32 - r select 0 - r fsl.push k 34 - r select 9 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r swapdb 0 9 - assert_equal {1} [s 0 blocked_clients] - r fsl.push k 38 - assert_equal {38} [$rd read] - r select 9 - } - - test {Module client blocked on keys (with metadata): Blocked, SWAPDB, key exists, case 3} { - ;# Key exists on other db, with the right type, the value allows to unblock - r flushall - r select 9 - r fsl.push k 32 - r select 0 - r fsl.push k 38 - r select 9 - set rd [redis_deferring_client] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r swapdb 0 9 - assert_equal {38} [$rd read] - r select 9 - } - - test {Module client blocked on keys (with metadata): Blocked, CLIENT KILL} { - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r client kill id $cid ;# try to smoke-out client-related memory leak - } - - test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK TIMEOUT} { - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r client unblock $cid timeout ;# try to smoke-out client-related memory leak - assert_equal {Request timedout} [$rd read] - } - - test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK ERROR} { - r del k - r fsl.push k 32 - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - $rd fsl.bpopgt k 35 0 - wait_for_blocked_clients_count 1 - r client unblock $cid error ;# try to smoke-out client-related memory leak - assert_error "*unblocked*" {$rd read} - } - - test {Module client blocked on keys, no timeout CB, CLIENT UNBLOCK TIMEOUT} { - r del k - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - $rd fsl.bpop k 0 NO_TO_CB - wait_for_blocked_clients_count 1 - assert_equal [r client unblock $cid timeout] {0} - $rd close - } - - test {Module client blocked on keys, no timeout CB, CLIENT UNBLOCK ERROR} { - r del k - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - $rd fsl.bpop k 0 NO_TO_CB - wait_for_blocked_clients_count 1 - assert_equal [r client unblock $cid error] {0} - $rd close - } - - test {Module client re-blocked on keys after woke up on wrong type} { - r del k - set rd [redis_deferring_client] - $rd fsl.bpop k 0 - wait_for_blocked_clients_count 1 - r lpush k 12 - r lpush k 13 - r lpush k 14 - r del k - r fsl.push k 34 - assert_equal {34} [$rd read] - assert_equal {1} [r get fsl_wrong_type] ;# first lpush caused one wrong-type wake-up - } - - test {Module client blocked on keys woken up by LPUSH} { - r del k - set rd [redis_deferring_client] - $rd blockonkeys.popall k - wait_for_blocked_clients_count 1 - r lpush k 42 squirrel banana - assert_equal {banana squirrel 42} [$rd read] - $rd close - } - - test {Module client unblocks BLPOP} { - r del k - set rd [redis_deferring_client] - $rd blpop k 3 - wait_for_blocked_clients_count 1 - r blockonkeys.lpush k 42 - assert_equal {k 42} [$rd read] - $rd close - } - - test {Module unblocks module blocked on non-empty list} { - r del k - r lpush k aa - # Module client blocks to pop 5 elements from list - set rd [redis_deferring_client] - $rd blockonkeys.blpopn k 5 - wait_for_blocked_clients_count 1 - # Check that RM_SignalKeyAsReady() can wake up BLPOPN - r blockonkeys.lpush_unblock k bb cc ;# Not enough elements for BLPOPN - r lpush k dd ee ff ;# Doesn't unblock module - r blockonkeys.lpush_unblock k gg ;# Unblocks module - assert_equal {gg ff ee dd cc} [$rd read] - $rd close - } - - test {Module explicit unblock when blocked on keys} { - r del k - r set somekey someval - # Module client blocks to pop 5 elements from list - set rd [redis_deferring_client] - $rd blockonkeys.blpopn_or_unblock k 5 0 - wait_for_blocked_clients_count 1 - # will now cause the module to trigger pop but instead will unblock the client from the reply_callback - r lpush k dd - # we should still get unblocked as the command should not reprocess - wait_for_blocked_clients_count 0 - assert_equal {Action aborted} [$rd read] - $rd get somekey - assert_equal {someval} [$rd read] - $rd close - } - - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] { - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - - # Start the replication process... - $replica replicaof $master_host $master_port - wait_for_sync $replica - - test {WAIT command on module blocked client on keys} { - set rd [redis_deferring_client -1] - $rd set x y - $rd read - - pause_process [srv 0 pid] - - $master del k - $rd fsl.bpop k 0 - wait_for_blocked_client -1 - $master fsl.push k 34 - $master fsl.push k 35 - assert_equal {34} [$rd read] - - assert_equal [$master wait 1 1000] 0 - resume_process [srv 0 pid] - assert_equal [$master wait 1 1000] 1 - $rd close - assert_equal {35} [$replica fsl.getall k] - } - } - -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/cluster.tcl b/examples/redis-unstable/tests/unit/moduleapi/cluster.tcl deleted file mode 100644 index 7141fe1..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/cluster.tcl +++ /dev/null @@ -1,227 +0,0 @@ -# Primitive tests on cluster-enabled redis with modules - -source tests/support/cli.tcl - -# cluster creation is complicated with TLS, and the current tests don't really need that coverage -tags {tls:skip external:skip cluster modules} { - -set testmodule_nokey [file normalize tests/modules/blockonbackground.so] -set testmodule_blockedclient [file normalize tests/modules/blockedclient.so] -set testmodule [file normalize tests/modules/blockonkeys.so] - -set modules [list loadmodule $testmodule loadmodule $testmodule_nokey loadmodule $testmodule_blockedclient] -start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] { - - set node1 [srv 0 client] - set node2 [srv -1 client] - set node3 [srv -2 client] - set node3_pid [srv -2 pid] - - test "Run blocking command (blocked on key) on cluster node3" { - # key9184688 is mapped to slot 10923 (first slot of node 3) - set node3_rd [redis_deferring_client -2] - $node3_rd fsl.bpop key9184688 0 - $node3_rd flush - wait_for_condition 50 100 { - [s -2 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (blocked on key) not blocked" - } - } - - test "Run blocking command (no keys) on cluster node2" { - set node2_rd [redis_deferring_client -1] - $node2_rd block.block 0 - $node2_rd flush - - wait_for_condition 50 100 { - [s -1 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (no keys) not blocked" - } - } - - - test "Perform a Resharding" { - exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \ - --cluster-to [$node1 cluster myid] \ - --cluster-from [$node3 cluster myid] \ - --cluster-slots 1 - } - - test "Verify command (no keys) is unaffected after resharding" { - # verify there are blocked clients on node2 - assert_equal [s -1 blocked_clients] {1} - - #release client - $node2 block.release 0 - } - - test "Verify command (blocked on key) got unblocked after resharding" { - # this (read) will wait for the node3 to realize the new topology - assert_error {*MOVED*} {$node3_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - assert_equal [s -2 blocked_clients] {0} - } - - test "Wait for cluster to be stable" { - wait_for_condition 1000 50 { - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - } - - test "Sanity test push cmd after resharding" { - assert_error {*MOVED*} {$node3 fsl.push key9184688 1} - - set node1_rd [redis_deferring_client 0] - $node1_rd fsl.bpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - puts "Client not blocked" - puts "read from blocked client: [$node1_rd read]" - fail "Client not blocked" - } - - $node1 fsl.push key9184688 2 - assert_equal {2} [$node1_rd read] - } - - $node1_rd close - $node2_rd close - $node3_rd close - - test "Run blocking command (blocked on key) again on cluster node1" { - $node1 del key9184688 - # key9184688 is mapped to slot 10923 which has been moved to node1 - set node1_rd [redis_deferring_client 0] - $node1_rd fsl.bpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (blocked on key) again not blocked" - } - } - - test "Run blocking command (no keys) again on cluster node2" { - set node2_rd [redis_deferring_client -1] - - $node2_rd block.block 0 - $node2_rd flush - - wait_for_condition 50 100 { - [s -1 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (no keys) again not blocked" - } - } - - test "Kill a cluster node and wait for fail state" { - # kill node3 in cluster - pause_process $node3_pid - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {fail} && - [CI 1 cluster_state] eq {fail} - } else { - fail "Cluster doesn't fail" - } - } - - test "Verify command (blocked on key) got unblocked after cluster failure" { - assert_error {*CLUSTERDOWN*} {$node1_rd read} - } - - test "Verify command (no keys) got unblocked after cluster failure" { - assert_error {*CLUSTERDOWN*} {$node2_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - } - - test "Verify command RM_Call is rejected when cluster is down" { - assert_error "ERR Can not execute a command 'set' while the cluster is down" {$node1 do_rm_call set x 1} - } - - resume_process $node3_pid - $node1_rd close - $node2_rd close -} - -set testmodule_keyspace_events [file normalize tests/modules/keyspace_events.so] -set testmodule_postnotifications "[file normalize tests/modules/postnotifications.so] with_key_events" -set modules [list loadmodule $testmodule_keyspace_events loadmodule $testmodule_postnotifications] -start_cluster 2 2 [list tags {external:skip cluster modules} config_lines $modules] { - - set master1 [srv 0 client] - set master2 [srv -1 client] - set replica1 [srv -2 client] - set replica2 [srv -3 client] - - test "Verify keys deletion and notification effects happened on cluster slots change are replicated inside multi exec" { - $master2 set count_dels_{4oi} 1 - $master2 del count_dels_{4oi} - assert_equal 1 [$master2 keyspace.get_dels] - wait_for_ofs_sync $master2 $replica2 - assert_equal 1 [$replica2 keyspace.get_dels] - $master2 set count_dels_{4oi} 1 - - set repl [attach_to_replication_stream_on_connection -3] - - $master1 cluster bumpepoch - $master1 cluster setslot 16382 node [$master1 cluster myid] - - wait_for_cluster_propagation - wait_for_condition 50 100 { - [$master2 keyspace.get_dels] eq 2 - } else { - fail "master did not delete the key" - } - wait_for_condition 50 100 { - [$replica2 keyspace.get_dels] eq 2 - } else { - fail "replica did not increase del counter" - } - - # the {lpush before_deleted count_dels_{4oi}} is a post notification job registered when 'count_dels_{4oi}' was removed - assert_replication_stream $repl { - {multi} - {del count_dels_{4oi}} - {keyspace.incr_dels} - {lpush before_deleted count_dels_{4oi}} - {exec} - } - close_replication_stream $repl - } -} - -} - -set testmodule [file normalize tests/modules/basics.so] -set modules [list loadmodule $testmodule] -start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] { - set node1 [srv 0 client] - set node2 [srv -1 client] - set node3 [srv -2 client] - - test "Verify RM_Call inside module load function on cluster mode" { - assert_equal {PONG} [$node1 PING] - assert_equal {PONG} [$node2 PING] - assert_equal {PONG} [$node3 PING] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl b/examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl deleted file mode 100644 index 73be3f8..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl +++ /dev/null @@ -1,50 +0,0 @@ -set testmodule [file normalize tests/modules/cmdintrospection.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - # cmdintrospection.xadd mimics XADD with regards to how - # what COMMAND exposes. There are two differences: - # - # 1. cmdintrospection.xadd (and all module commands) do not have ACL categories - # 2. cmdintrospection.xadd's `group` is "module" - # - # This tests verify that, apart from the above differences, the output of - # COMMAND INFO and COMMAND DOCS are identical for the two commands. - test "Module command introspection via COMMAND INFO" { - set redis_reply [lindex [r command info xadd] 0] - set module_reply [lindex [r command info cmdintrospection.xadd] 0] - for {set i 1} {$i < [llength $redis_reply]} {incr i} { - if {$i == 2} { - # Remove the "module" flag - set mylist [lindex $module_reply $i] - set idx [lsearch $mylist "module"] - set mylist [lreplace $mylist $idx $idx] - lset module_reply $i $mylist - } - if {$i == 6} { - # Skip ACL categories - continue - } - assert_equal [lindex $redis_reply $i] [lindex $module_reply $i] - } - } - - test "Module command introspection via COMMAND DOCS" { - set redis_reply [dict create {*}[lindex [r command docs xadd] 1]] - set module_reply [dict create {*}[lindex [r command docs cmdintrospection.xadd] 1]] - # Compare the maps. We need to pop "group" first. - dict unset redis_reply group - dict unset module_reply group - dict unset module_reply module - if {$::log_req_res} { - dict unset redis_reply reply_schema - } - - assert_equal $redis_reply $module_reply - } - - test "Unload the module - cmdintrospection" { - assert_equal {OK} [r module unload cmdintrospection] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl b/examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl deleted file mode 100644 index 5b600d0..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl +++ /dev/null @@ -1,175 +0,0 @@ -set testmodule [file normalize tests/modules/commandfilter.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - test {Retain a command filter argument} { - # Retain an argument now. Later we'll try to re-read it and make sure - # it is not corrupt and that valgrind does not complain. - r rpush some-list @retain my-retained-string - r commandfilter.retained - } {my-retained-string} - - test {Command Filter handles redirected commands} { - r set mykey @log - r lrange log-key 0 -1 - } "{set mykey @log}" - - test {Command Filter can call RedisModule_CommandFilterArgDelete} { - r rpush mylist elem1 @delme elem2 - r lrange mylist 0 -1 - } {elem1 elem2} - - test {Command Filter can call RedisModule_CommandFilterArgInsert} { - r del mylist - r rpush mylist elem1 @insertbefore elem2 @insertafter elem3 - r lrange mylist 0 -1 - } {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3} - - test {Command Filter can call RedisModule_CommandFilterArgReplace} { - r del mylist - r rpush mylist elem1 @replaceme elem2 - r lrange mylist 0 -1 - } {elem1 --replaced-- elem2} - - test {Command Filter applies on RM_Call() commands} { - r del log-key - r commandfilter.ping - r lrange log-key 0 -1 - } "{ping @log}" - - test {Command Filter applies on Lua redis.call()} { - r del log-key - r eval "redis.call('ping', '@log')" 0 - r lrange log-key 0 -1 - } "{ping @log}" - - test {Command Filter applies on Lua redis.call() that calls a module} { - r del log-key - r eval "redis.call('commandfilter.ping')" 0 - r lrange log-key 0 -1 - } "{ping @log}" - - test {Command Filter strings can be retained} { - r commandfilter.retained - } {my-retained-string} - - test {Command Filter is unregistered implicitly on module unload} { - r del log-key - r module unload commandfilter - r set mykey @log - r lrange log-key 0 -1 - } {} - - r module load $testmodule log-key 0 - - test {Command Filter unregister works as expected} { - # Validate reloading succeeded - r del log-key - r set mykey @log - assert_equal "{set mykey @log}" [r lrange log-key 0 -1] - - # Unregister - r commandfilter.unregister - r del log-key - - r set mykey @log - r lrange log-key 0 -1 - } {} - - r module unload commandfilter - r module load $testmodule log-key 1 - - test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} { - r set mykey @log - assert_equal "{set mykey @log}" [r lrange log-key 0 -1] - - r del log-key - r commandfilter.ping - assert_equal {} [r lrange log-key 0 -1] - - r eval "redis.call('commandfilter.ping')" 0 - assert_equal {} [r lrange log-key 0 -1] - } - - test "Unload the module - commandfilter" { - assert_equal {OK} [r module unload commandfilter] - } -} - -test {RM_CommandFilterArgInsert and script argv caching} { - # coverage for scripts calling commands that expand the argv array - # an attempt to add coverage for a possible bug in luaArgsToRedisArgv - # this test needs a fresh server so that lua_argv_size is 0. - # glibc realloc can return the same pointer even when the size changes - # still this test isn't able to trigger the issue, but we keep it anyway. - start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - r del mylist - # command with 6 args - r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist - # command with 3 args that is changed to 4 - r eval {redis.call('rpush', KEYS[1], '@insertafter')} 1 mylist - # command with 6 args again - r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist - assert_equal [r lrange mylist 0 -1] {elem1 elem2 elem3 elem4 @insertafter --inserted-after-- elem1 elem2 elem3 elem4} - } -} - -# previously, there was a bug that command filters would be rerun (which would cause args to swap back) -# this test is meant to protect against that bug -test {Blocking Commands don't run through command filter when reprocessed} { - start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - r del list1{t} - r del list2{t} - - r lpush list2{t} a b c d e - - set rd [redis_deferring_client] - # we're asking to pop from the left, but the command filter swaps the two arguments, - # if it didn't swap it, we would end up with e d c b a 5 (5 being the left most of the following lpush) - # but since we swap the arguments, we end up with 1 e d c b a (1 being the right most of it). - # if the command filter would run again on unblock, they would be swapped back. - $rd blmove list1{t} list2{t} left right 0 - wait_for_blocked_client - r lpush list1{t} 1 2 3 4 5 - # validate that we moved the correct element with the swapped args - assert_equal [$rd read] 1 - # validate that we moved the correct elements to the correct side of the list - assert_equal [r lpop list2{t}] 1 - - $rd close - } -} - -test {Filtering based on client id} { - start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - set rr [redis_client] - set cid [$rr client id] - r unfilter_clientid $cid - - r rpush mylist elem1 @replaceme elem2 - assert_equal [r lrange mylist 0 -1] {elem1 --replaced-- elem2} - - r del mylist - - assert_equal [$rr rpush mylist elem1 @replaceme elem2] 3 - assert_equal [r lrange mylist 0 -1] {elem1 @replaceme elem2} - - $rr close - } -} - -start_server {tags {"external:skip"}} { - test {OnLoad failure will handle un-registration} { - catch {r module load $testmodule log-key 0 noload} - r set mykey @log - assert_equal [r lrange log-key 0 -1] {} - r rpush mylist elem1 @delme elem2 - assert_equal [r lrange mylist 0 -1] {elem1 @delme elem2} - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl b/examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl deleted file mode 100644 index 11aa962..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl +++ /dev/null @@ -1,227 +0,0 @@ -set testmodule [file normalize tests/modules/configaccess.so] -set othermodule [file normalize tests/modules/moduleconfigs.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - r module loadex $othermodule CONFIG moduleconfigs.mutable_bool yes - - test {Test module config get with standard Redis configs} { - # Test getting standard Redis configs of different types - set maxmemory [r config get maxmemory] - assert_equal [lindex $maxmemory 1] [r configaccess.getnumeric maxmemory] - - set port [r config get port] - assert_equal [lindex $port 1] [r configaccess.getnumeric port] - - set appendonly [r config get appendonly] - assert_equal [string is true [lindex $appendonly 1]] [r configaccess.getbool appendonly] - - # Test string config - set logfile [r config get logfile] - assert_equal [lindex $logfile 1] [r configaccess.get logfile] - - # Test SDS config - set requirepass [r config get requirepass] - assert_equal [lindex $requirepass 1] [r configaccess.get requirepass] - - # Test special config - set oom_score_adj_values [r config get oom-score-adj-values] - assert_equal [lindex $oom_score_adj_values 1] [r configaccess.get oom-score-adj-values] - - set maxmemory_policy_name [r configaccess.getenum maxmemory-policy] - assert_equal [lindex [r config get maxmemory-policy] 1] $maxmemory_policy_name - - # Test percent config - r config set maxmemory 100000 - r configaccess.setnumeric maxmemory-clients -50 - assert_equal [lindex [r config get maxmemory-clients] 1] 50% - - # Test multi-argument enum config - r config set moduleconfigs.flags "one two four" - assert_equal "five two" [r configaccess.getenum moduleconfigs.flags] - - # Test getting multi-argument enum config via generic get - r config set moduleconfigs.flags "two four" - assert_equal "two four" [r configaccess.get moduleconfigs.flags] - } - - test {Test module config get with non-existent configs} { - # Test getting non-existent configs - catch {r configaccess.getnumeric nonexistent_config} err - assert_match "ERR*" $err - - catch {r configaccess.getbool nonexistent_config} err - assert_match "ERR*" $err - - catch {r configaccess.get nonexistent_config} err - assert_match "ERR*" $err - - catch {r configaccess.getenum nonexistent_config} err - assert_match "ERR*" $err - } - - test {Test module config set with standard Redis configs} { - # Test setting numeric config - set old_maxmemory_samples [r config get maxmemory-samples] - r configaccess.setnumeric maxmemory-samples 10 - assert_equal "maxmemory-samples 10" [r config get maxmemory-samples] - r config set maxmemory-samples [lindex $old_maxmemory_samples 1] - - # Test setting bool config - set old_protected_mode [r config get protected-mode] - r configaccess.setbool protected-mode no - assert_equal "protected-mode no" [r config get protected-mode] - r config set protected-mode [lindex $old_protected_mode 1] - - # Test setting string config - set old_masteruser [r config get masteruser] - r configaccess.set masteruser "__newmasteruser__" - assert_equal "__newmasteruser__" [lindex [r config get masteruser] 1] - r config set masteruser [lindex $old_masteruser 1] - - # Test setting enum config - set old_loglevel [r config get loglevel] - r config set loglevel "notice" ; # Set to some value we are sure is different than the one tested - r configaccess.setenum loglevel warning - assert_equal "loglevel warning" [r config get loglevel] - r config set loglevel [lindex $old_loglevel 1] - - # Test setting multi-argument enum config - r config set moduleconfigs.flags "one two four" - assert_equal "moduleconfigs.flags {five two}" [r config get moduleconfigs.flags] - r configaccess.setenum moduleconfigs.flags "two four" - assert_equal "moduleconfigs.flags {two four}" [r config get moduleconfigs.flags] - - # Test setting multi-argument enum config via generic set - r config set moduleconfigs.flags "one two four" - assert_equal "moduleconfigs.flags {five two}" [r config get moduleconfigs.flags] - r configaccess.set moduleconfigs.flags "two four" - assert_equal "moduleconfigs.flags {two four}" [r config get moduleconfigs.flags] - } - - test {Test module config set with module configs} { - # Test setting module bool config - assert_equal "OK" [r configaccess.setbool configaccess.bool no] - assert_equal "configaccess.bool no" [r config get configaccess.bool] - - # Test setting module bool config from another module - assert_equal "OK" [r configaccess.setbool moduleconfigs.mutable_bool no] - assert_equal "moduleconfigs.mutable_bool no" [r config get moduleconfigs.mutable_bool] - - # Test setting module numeric config - assert_equal "OK" [r configaccess.setnumeric moduleconfigs.numeric 100] - assert_equal "moduleconfigs.numeric 100" [r config get moduleconfigs.numeric] - - # Test setting module enum config - assert_equal "OK" [r configaccess.setenum moduleconfigs.enum "five"] - assert_equal "moduleconfigs.enum five" [r config get moduleconfigs.enum] - } - - test {Test module config set with error cases} { - # Test setting a non-existent config - catch {r configaccess.setbool nonexistent_config yes} err - assert_match "*ERR*" $err - - # Test setting a read-only config - catch {r configaccess.setbool moduleconfigs.immutable_bool yes} err - assert_match "*ERR*" $err - - # Test setting an enum config with invalid value - catch {r configaccess.setenumname moduleconfigs.enum "invalid_value"} err - assert_match "*ERR*" $err - - # Test setting a numeric config with out-of-range value - catch {r configaccess.setnumeric moduleconfigs.numeric 5000} err - assert_match "*ERR*" $err - catch {r configaccess.setnumeric maxclients -1} err - assert_match "*Failed to set numeric config maxclients: argument must be between*" $err - catch {r configaccess.setnumeric maxclients -9223372036854775808} err - assert_match "*Failed to set numeric config maxclients: argument must be between*" $err - - # Sanity check - assert_equal [r configaccess.setnumeric maxmemory 18446744073709551615] "OK" - assert_equal [r configaccess.setnumeric maxmemory -1] "OK" - } - - test {Test module get all configs} { - # Get all configs using the module command - set all_configs [r configaccess.getconfigs] - - # Verify the number of configs matches the number of configs returned - # by Redis's native CONFIG GET command. - set all_configs_std_pairs [llength [r config get *]] - - # When comparing with the standard CONFIG GET command, we need to divide - # by 2 because the standard command returns a flattened array of - # key-value pairs whereas our testing command returns an array of pairs. - assert_equal [llength $all_configs] [expr $all_configs_std_pairs / 2] - - # Verify all the configs are present in both replies. - foreach config_pair $all_configs { - assert_equal 2 [llength $config_pair] - set config_name [lindex $config_pair 0] - set config_value [lindex $config_pair 1] - - # Verify that we can get this config using standard config get - set redis_config [r config get $config_name] - assert {[llength $redis_config] != 0} - - assert_equal $config_value [lindex $redis_config 1] - } - - # Test that module configs are also included - set found_module_config 0 - foreach config_pair $all_configs { - set config_name [lindex $config_pair 0] - if {$config_name eq "configaccess.bool"} { - set found_module_config 1 - break - } - } - - assert {$found_module_config == 1} - - # Test pattern matching - set moduleconfigs_count [r configaccess.getconfigs "moduleconfigs.*"] - assert_equal 7 [llength $moduleconfigs_count] - - set memoryconfigs_count [r configaccess.getconfigs "*memory"] - assert_equal 3 [llength $memoryconfigs_count] - } - - test {Test module config type detection} { - # Test getting config types for different config types - assert_equal "bool" [r configaccess.getconfigtype appendonly] - assert_equal "numeric" [r configaccess.getconfigtype port] - assert_equal "string" [r configaccess.getconfigtype logfile] - assert_equal "enum" [r configaccess.getconfigtype maxmemory-policy] - - # Test with module config - assert_equal "bool" [r configaccess.getconfigtype configaccess.bool] - - # Test with non-existent config - catch {r configaccess.getconfigtype nonexistent_config} err - assert_match "ERR Config does not exist" $err - } - - test {Test config rollback on apply} { - set og_port [lindex [r config get port] 1] - - set used_port [find_available_port $::baseport $::portcount] - - # Run a dummy server on used_port so we know we can't configure redis to - # use it. It's ok for this to fail because that means used_port is invalid - # anyway - catch {set sockfd [socket -server dummy_accept -myaddr 127.0.0.1 $used_port]} e - if {$::verbose} { puts "dummy_accept: $e" } - - # Try to listen on the used port, pass some more configs to make sure the - # returned failure message is for the first bad config and everything is rolled back. - assert_error "ERR Failed to set numeric config port: Unable to listen on this port*" { - eval "r configaccess.setnumeric port $used_port" - } - - assert_equal [lindex [r config get port] 1] $og_port - close $sockfd - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/crash.tcl b/examples/redis-unstable/tests/unit/moduleapi/crash.tcl deleted file mode 100644 index 9d9477c..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/crash.tcl +++ /dev/null @@ -1,129 +0,0 @@ -# This file is used to test certain crash edge cases to make sure they produce -# correct stack traces for debugging. -set testmodule [file normalize tests/modules/crash.so] -set backtrace_supported [system_backtrace_supported] - -# Valgrind will complain that the process terminated by a signal, skip it. -if {!$::valgrind && !$::tsan} { - start_server {tags {"modules external:skip"}} { - r module load $testmodule assert - test {Test module crash when info crashes with an assertion } { - catch {r 0 info modulecrash} - set res [wait_for_log_messages 0 {"*=== REDIS BUG REPORT START: Cut & paste starting from here ===*"} 0 10 1000] - set loglines [lindex $res 1] - - set res [wait_for_log_messages 0 {"*ASSERTION FAILED*"} $loglines 10 1000] - set loglines [lindex $res 1] - - set res [wait_for_log_messages 0 {"*RECURSIVE ASSERTION FAILED*"} $loglines 10 1000] - set loglines [lindex $res 1] - - wait_for_log_messages 0 {"*=== REDIS BUG REPORT END. Make sure to include from START to END. ===*"} $loglines 10 1000 - assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT END. Make sure to include from START to END. ==="] - assert_equal 2 [count_log_message 0 "ASSERTION FAILED"] - if {$backtrace_supported} { - # Make sure the crash trace is printed twice. There will be 3 instances of, - # assertCrash 1 in the first stack trace and 2 in the second. - assert_equal 3 [count_log_message 0 "assertCrash"] - } - assert_equal 1 [count_log_message 0 "RECURSIVE ASSERTION FAILED"] - assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT START: Cut & paste starting from here ==="] - } - } - - start_server {tags {"modules external:skip"}} { - r module load $testmodule segfault - test {Test module crash when info crashes with a segfault} { - catch {r 0 info modulecrash} - set res [wait_for_log_messages 0 {"*=== REDIS BUG REPORT START: Cut & paste starting from here ===*"} 0 10 1000] - set loglines [lindex $res 1] - - if {$backtrace_supported} { - set res [wait_for_log_messages 0 {"*Crashed running the instruction at*"} $loglines 10 1000] - set loglines [lindex $res 1] - - set res [wait_for_log_messages 0 {"*Crashed running signal handler. Providing reduced version of recursive crash report*"} $loglines 10 1000] - set loglines [lindex $res 1] - set res [wait_for_log_messages 0 {"*Crashed running the instruction at*"} $loglines 10 1000] - set loglines [lindex $res 1] - } - - wait_for_log_messages 0 {"*=== REDIS BUG REPORT END. Make sure to include from START to END. ===*"} $loglines 10 1000 - assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT END. Make sure to include from START to END. ==="] - assert_equal 1 [count_log_message 0 "Crashed running signal handler. Providing reduced version of recursive crash report"] - if {$backtrace_supported} { - assert_equal 2 [count_log_message 0 "Crashed running the instruction at"] - # Make sure the crash trace is printed twice. There will be 3 instances of - # modulesCollectInfo, 1 in the first stack trace and 2 in the second. - assert_equal 3 [count_log_message 0 "modulesCollectInfo"] - } - assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT START: Cut & paste starting from here ==="] - } - } - - start_server {tags {"modules external:skip"}} { - r module load $testmodule - - # memcheck confuses sanitizer - r config set crash-memcheck-enabled no - - test {Test command tokens are printed when hide-user-data-from-log is enabled (xadd)} { - r config set hide-user-data-from-log yes - catch {r 0 modulecrash.xadd key NOMKSTREAM MAXLEN ~ 1000 * a b} - - wait_for_log_messages 0 {"*argv*0*: *modulecrash.xadd*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*1*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*2*: *NOMKSTREAM*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*3*: *MAXLEN*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*4*: *~*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*5*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*6*: *\**"} 0 10 1000 - wait_for_log_messages 0 {"*argv*7*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*8*: *redacted*"} 0 10 1000 - } - } - - start_server {tags {"modules external:skip"}} { - r module load $testmodule - - # memcheck confuses sanitizer - r config set crash-memcheck-enabled no - - test {Test command tokens are printed when hide-user-data-from-log is enabled (zunion)} { - r config set hide-user-data-from-log yes - catch {r 0 modulecrash.zunion 2 zset1 zset2 WEIGHTS 1 2 WITHSCORES somedata} - - wait_for_log_messages 0 {"*argv*0*: *modulecrash.zunion*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*1*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*2*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*3*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*4*: *WEIGHTS*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*5*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*6*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*7*: *WITHSCORES*"} 0 10 1000 - - # We don't expect arguments after WITHSCORE but just in case there - # is we rather not print it - wait_for_log_messages 0 {"*argv*8*: *redacted*"} 0 10 1000 - } - } - - start_server {tags {"modules external:skip"}} { - r module load $testmodule - - # memcheck confuses sanitizer - r config set crash-memcheck-enabled no - - test {Test subcommand name is printed when hide-user-data-from-log is enabled} { - r config set hide-user-data-from-log yes - catch {r 0 modulecrash.parent subcmd key TOKEN a b} - - wait_for_log_messages 0 {"*argv*0*: *modulecrash.parent*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*1*: *subcmd*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*2*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*3*: *TOKEN*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*4*: *redacted*"} 0 10 1000 - wait_for_log_messages 0 {"*argv*5*: *redacted*"} 0 10 1000 - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/datatype.tcl b/examples/redis-unstable/tests/unit/moduleapi/datatype.tcl deleted file mode 100644 index fffb841..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/datatype.tcl +++ /dev/null @@ -1,238 +0,0 @@ -set testmodule [file normalize tests/modules/datatype.so] - -start_server {tags {"modules external:skip"}} { - test {DataType: test loadex with invalid config} { - catch { r module loadex $testmodule CONFIG invalid_config 1 } e - assert_match {*ERR Error loading the extension*} $e - } - - r module load $testmodule - - test {DataType: Test module is sane, GET/SET work.} { - r datatype.set dtkey 100 stringval - assert {[r datatype.get dtkey] eq {100 stringval}} - } - - test {test blocking of datatype creation outside of OnLoad} { - assert_equal [r block.create.datatype.outside.onload] OK - } - - test {DataType: RM_SaveDataTypeToString(), RM_LoadDataTypeFromStringEncver() work} { - r datatype.set dtkey -1111 MyString - set encoded [r datatype.dump dtkey] - - assert {[r datatype.restore dtkeycopy $encoded 4] eq {4}} - assert {[r datatype.get dtkeycopy] eq {-1111 MyString}} - } - - test {DataType: Handle truncated RM_LoadDataTypeFromStringEncver()} { - r datatype.set dtkey -1111 MyString - set encoded [r datatype.dump dtkey] - set truncated [string range $encoded 0 end-1] - - catch {r datatype.restore dtkeycopy $truncated 4} e - set e - } {*Invalid*} - - test {DataType: ModuleTypeReplaceValue() happy path works} { - r datatype.set key-a 1 AAA - r datatype.set key-b 2 BBB - - assert {[r datatype.swap key-a key-b] eq {OK}} - assert {[r datatype.get key-a] eq {2 BBB}} - assert {[r datatype.get key-b] eq {1 AAA}} - } - - test {DataType: ModuleTypeReplaceValue() fails on non-module keys} { - r datatype.set key-a 1 AAA - r set key-b RedisString - - catch {r datatype.swap key-a key-b} e - set e - } {*ERR*} - - test {DataType: Copy command works for modules} { - # Test failed copies - r datatype.set answer-to-universe 42 AAA - catch {r copy answer-to-universe answer2} e - assert_match {*module key failed to copy*} $e - - # Our module's data type copy function copies the int value as-is - # but appends // to the string value so we can - # track passed arguments. - r datatype.set sourcekey 1234 AAA - r copy sourcekey targetkey - r datatype.get targetkey - } {1234 AAA/sourcekey/targetkey} - - test {DataType: Slow Loading} { - r config set busy-reply-threshold 5000 ;# make sure we're using a high default - # trigger slow loading - r datatype.slow_loading 1 - set rd [redis_deferring_client] - set start [clock clicks -milliseconds] - $rd debug reload - - # wait till we know we're blocked inside the module - wait_for_condition 50 100 { - [r datatype.is_in_slow_loading] eq 1 - } else { - fail "Failed waiting for slow loading to start" - } - - # make sure we get LOADING error, and that we didn't get here late (not waiting for busy-reply-threshold) - assert_error {*LOADING*} {r ping} - assert_lessthan [expr [clock clicks -milliseconds]-$start] 2000 - - # abort the blocking operation - r datatype.slow_loading 0 - wait_for_condition 50 100 { - [s loading] eq {0} - } else { - fail "Failed waiting for loading to end" - } - $rd read - $rd close - } - - test {DataType: check the type name} { - r flushdb - r datatype.set foo 111 bar - assert_type test___dt foo - } - - test {SCAN module datatype} { - r flushdb - populate 1000 - r datatype.set foo 111 bar - set type [r type foo] - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type $type] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 1 [llength $keys] - } - - test {SCAN module datatype with case sensitive} { - r flushdb - populate 1000 - r datatype.set foo 111 bar - set type "tEsT___dT" - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type $type] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 1 [llength $keys] - } - - if {[string match {*jemalloc*} [s mem_allocator]] && [r debug mallctl arenas.page] <= 8192} { - test {Reduce defrag CPU usage when module data can't be defragged} { - r flushdb - r config set hz 100 - r config set activedefrag no - r config set active-defrag-threshold-lower 5 - r config set active-defrag-cycle-min 25 - r config set active-defrag-cycle-max 75 - r config set active-defrag-ignore-bytes 100kb - - # Populate memory with interleaving field of same size. - set n 20000 - set dummy "[string repeat x 400]" - set rd [redis_deferring_client] - for {set i 0} {$i < $n} {incr i} { $rd datatype.set k$i 1 $dummy } - for {set i 0} {$i < [expr $n]} {incr i} { $rd read } ;# Discard replies - - after 120 ;# serverCron only updates the info once in 100ms - if {$::verbose} { - puts "used [s allocator_allocated]" - puts "rss [s allocator_active]" - puts "frag [s allocator_frag_ratio]" - puts "frag_bytes [s allocator_frag_bytes]" - } - assert_lessthan [s allocator_frag_ratio] 1.05 - - for {set i 0} {$i < $n} {incr i 2} { $rd del k$i } - for {set j 0} {$j < $n} {incr j 2} { $rd read } ; # Discard del replies - after 120 ;# serverCron only updates the info once in 100ms - assert_morethan [s allocator_frag_ratio] 1.4 - - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - # wait for the active defrag to start working (decision once a second) - wait_for_condition 50 100 { - [s total_active_defrag_time] ne 0 - } else { - after 120 ;# serverCron only updates the info once in 100ms - puts [r info memory] - puts [r info stats] - puts [r memory malloc-stats] - fail "defrag not started." - } - assert_morethan [s allocator_frag_ratio] 1.4 - - # The cpu usage of defragment will drop to active-defrag-cycle-min - wait_for_condition 1000 50 { - [s active_defrag_running] == 25 - } else { - fail "Unable to reduce the defragmentation speed." - } - - # Fuzzy test to restore defragmentation speed to normal - set end_time [expr {[clock seconds] + 10}] - set speed_restored 0 - while {[clock seconds] < $end_time} { - for {set i 0} {$i < 500} {incr i} { - switch [expr {int(rand() * 3)}] { - 0 { - # Randomly delete a key - set random_key [r RANDOMKEY] - if {$random_key != ""} { - r DEL $random_key - } - } - 1 { - # Randomly overwrite a key - set random_key [r RANDOMKEY] - if {$random_key != ""} { - r datatype.set $random_key 1 $dummy - } - } - 2 { - # Randomly generate a new key - set random_key "key_[expr {int(rand() * 10000)}]" - r datatype.set $random_key 1 $dummy - } - } ;# end of switch - } ;# end of for - - # Wait for defragmentation speed to restore. - if {{[count_log_message $loglines "*Starting active defrag, frag=*%, frag_bytes=*, cpu=5?%*"]} > 1} { - set speed_restored 1 - break; - } - } - # Make sure the speed is restored - assert_equal $speed_restored 1 - - # After the traffic disappears, the defragmentation speed will decrease again. - wait_for_condition 1000 50 { - [s active_defrag_running] == 25 - } else { - fail "Unable to reduce the defragmentation speed after traffic disappears." - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl b/examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl deleted file mode 100644 index 9f40178..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl +++ /dev/null @@ -1,232 +0,0 @@ -set testmodule [file normalize tests/modules/datatype2.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test "datatype2: test mem alloc and free" { - r flushall - - r select 0 - assert_equal 3 [r mem.alloc k1 3] - assert_equal 2 [r mem.alloc k2 2] - - r select 1 - assert_equal 1 [r mem.alloc k1 1] - assert_equal 5 [r mem.alloc k2 5] - - r select 0 - assert_equal 1 [r mem.free k1] - assert_equal 1 [r mem.free k2] - - r select 1 - assert_equal 1 [r mem.free k1] - assert_equal 1 [r mem.free k2] - } - - test "datatype2: test del and unlink" { - r flushall - - assert_equal 100 [r mem.alloc k1 100] - assert_equal 60 [r mem.alloc k2 60] - - assert_equal 1 [r unlink k1] - assert_equal 1 [r del k2] - } - - test "datatype2: test read and write" { - r flushall - - assert_equal 3 [r mem.alloc k1 3] - - set data datatype2 - assert_equal [string length $data] [r mem.write k1 0 $data] - assert_equal $data [r mem.read k1 0] - } - - test "datatype2: test rdb save and load" { - r flushall - - r select 0 - set data k1 - assert_equal 3 [r mem.alloc k1 3] - assert_equal [string length $data] [r mem.write k1 1 $data] - - set data k2 - assert_equal 2 [r mem.alloc k2 2] - assert_equal [string length $data] [r mem.write k2 0 $data] - - r select 1 - set data k3 - assert_equal 3 [r mem.alloc k3 3] - assert_equal [string length $data] [r mem.write k3 1 $data] - - set data k4 - assert_equal 2 [r mem.alloc k4 2] - assert_equal [string length $data] [r mem.write k4 0 $data] - - r bgsave - waitForBgsave r - r debug reload - - r select 0 - assert_equal k1 [r mem.read k1 1] - assert_equal k2 [r mem.read k2 0] - - r select 1 - assert_equal k3 [r mem.read k3 1] - assert_equal k4 [r mem.read k4 0] - } - - test "datatype2: test aof rewrite" { - r flushall - - r select 0 - set data k1 - assert_equal 3 [r mem.alloc k1 3] - assert_equal [string length $data] [r mem.write k1 1 $data] - - set data k2 - assert_equal 2 [r mem.alloc k2 2] - assert_equal [string length $data] [r mem.write k2 0 $data] - - r select 1 - set data k3 - assert_equal 3 [r mem.alloc k3 3] - assert_equal [string length $data] [r mem.write k3 1 $data] - - set data k4 - assert_equal 2 [r mem.alloc k4 2] - assert_equal [string length $data] [r mem.write k4 0 $data] - - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - - r select 0 - assert_equal k1 [r mem.read k1 1] - assert_equal k2 [r mem.read k2 0] - - r select 1 - assert_equal k3 [r mem.read k3 1] - assert_equal k4 [r mem.read k4 0] - } - - test "datatype2: test copy" { - r flushall - - r select 0 - set data k1 - assert_equal 3 [r mem.alloc k1 3] - assert_equal [string length $data] [r mem.write k1 1 $data] - assert_equal $data [r mem.read k1 1] - - set data k2 - assert_equal 2 [r mem.alloc k2 2] - assert_equal [string length $data] [r mem.write k2 0 $data] - assert_equal $data [r mem.read k2 0] - - r select 1 - set data k3 - assert_equal 3 [r mem.alloc k3 3] - assert_equal [string length $data] [r mem.write k3 1 $data] - - set data k4 - assert_equal 2 [r mem.alloc k4 2] - assert_equal [string length $data] [r mem.write k4 0 $data] - - assert_equal {total 5 used 2} [r mem.usage 0] - assert_equal {total 5 used 2} [r mem.usage 1] - - r select 0 - assert_equal 1 [r copy k1 k3] - assert_equal k1 [r mem.read k3 1] - assert_equal {total 8 used 3} [r mem.usage 0] - assert_equal 1 [r copy k2 k1 db 1] - - r select 1 - assert_equal k2 [r mem.read k1 0] - assert_equal {total 8 used 3} [r mem.usage 0] - assert_equal {total 7 used 3} [r mem.usage 1] - } - - test "datatype2: test swapdb" { - r flushall - - r select 0 - set data k1 - assert_equal 5 [r mem.alloc k1 5] - assert_equal [string length $data] [r mem.write k1 1 $data] - assert_equal $data [r mem.read k1 1] - - set data k2 - assert_equal 4 [r mem.alloc k2 4] - assert_equal [string length $data] [r mem.write k2 0 $data] - assert_equal $data [r mem.read k2 0] - - r select 1 - set data k1 - assert_equal 3 [r mem.alloc k3 3] - assert_equal [string length $data] [r mem.write k3 1 $data] - - set data k2 - assert_equal 2 [r mem.alloc k4 2] - assert_equal [string length $data] [r mem.write k4 0 $data] - - assert_equal {total 9 used 2} [r mem.usage 0] - assert_equal {total 5 used 2} [r mem.usage 1] - - assert_equal OK [r swapdb 0 1] - assert_equal {total 9 used 2} [r mem.usage 1] - assert_equal {total 5 used 2} [r mem.usage 0] - } - - test "datatype2: test digest" { - r flushall - - r select 0 - set data k1 - assert_equal 3 [r mem.alloc k1 3] - assert_equal [string length $data] [r mem.write k1 1 $data] - assert_equal $data [r mem.read k1 1] - - set data k2 - assert_equal 2 [r mem.alloc k2 2] - assert_equal [string length $data] [r mem.write k2 0 $data] - assert_equal $data [r mem.read k2 0] - - r select 1 - set data k1 - assert_equal 3 [r mem.alloc k1 3] - assert_equal [string length $data] [r mem.write k1 1 $data] - assert_equal $data [r mem.read k1 1] - - set data k2 - assert_equal 2 [r mem.alloc k2 2] - assert_equal [string length $data] [r mem.write k2 0 $data] - assert_equal $data [r mem.read k2 0] - - r select 0 - set digest0 [debug_digest] - - r select 1 - set digest1 [debug_digest] - - assert_equal $digest0 $digest1 - } - - test "datatype2: test memusage" { - r flushall - - set data k1 - assert_equal 3 [r mem.alloc k1 3] - assert_equal [string length $data] [r mem.write k1 1 $data] - assert_equal $data [r mem.read k1 1] - - set data k2 - assert_equal 3 [r mem.alloc k2 3] - assert_equal [string length $data] [r mem.write k2 0 $data] - assert_equal $data [r mem.read k2 0] - - assert_equal [memory_usage k1] [memory_usage k2] - } -} \ No newline at end of file diff --git a/examples/redis-unstable/tests/unit/moduleapi/defrag.tcl b/examples/redis-unstable/tests/unit/moduleapi/defrag.tcl deleted file mode 100644 index 7e714da..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/defrag.tcl +++ /dev/null @@ -1,101 +0,0 @@ -set testmodule [file normalize tests/modules/defragtest.so] - -start_server {tags {"modules external:skip debug_defrag:skip"} overrides {{save ""}}} { - r module load $testmodule - r config set hz 100 - r config set active-defrag-ignore-bytes 1 - r config set active-defrag-threshold-lower 0 - r config set active-defrag-cycle-min 99 - - # try to enable active defrag, it will fail if redis was compiled without it - catch {r config set activedefrag yes} e - if {[r config get activedefrag] eq "activedefrag yes"} { - - test {Module defrag: simple key defrag works} { - r config set activedefrag no - wait_for_condition 100 50 { - [s active_defrag_running] eq 0 - } else { - fail "Unable to wait for active defrag to stop" - } - - r flushdb - r frag.resetstats - r frag.create key1 1 1000 0 - - r config set activedefrag yes - wait_for_condition 200 50 { - [getInfoProperty [r info defragtest_stats] defragtest_defrag_ended] > 0 - } else { - fail "Unable to wait for a complete defragmentation cycle to finish" - } - - set info [r info defragtest_stats] - assert {[getInfoProperty $info defragtest_datatype_attempts] > 0} - assert_equal 0 [getInfoProperty $info defragtest_datatype_resumes] - assert_morethan [getInfoProperty $info defragtest_datatype_raw_defragged] 0 - assert_morethan [getInfoProperty $info defragtest_defrag_started] 0 - assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0 - } {} {tsan:skip} - - test {Module defrag: late defrag with cursor works} { - r config set activedefrag no - wait_for_condition 100 50 { - [s active_defrag_running] eq 0 - } else { - fail "Unable to wait for active defrag to stop" - } - - r flushdb - r frag.resetstats - - # key can only be defragged in no less than 10 iterations - # due to maxstep - r frag.create key2 10000 100 1000 - - r config set activedefrag yes - wait_for_condition 1000 50 { - [getInfoProperty [r info defragtest_stats] defragtest_defrag_ended] > 0 && - [getInfoProperty [r info defragtest_stats] defragtest_datatype_resumes] > 10 - } else { - fail "Unable to wait for a complete defragmentation cycle to finish" - } - - set info [r info defragtest_stats] - assert_equal 0 [getInfoProperty $info defragtest_datatype_wrong_cursor] - assert_morethan [getInfoProperty $info defragtest_datatype_raw_defragged] 0 - assert_morethan [getInfoProperty $info defragtest_defrag_started] 0 - assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0 - } {} {tsan:skip} - - test {Module defrag: global defrag works} { - r config set activedefrag no - wait_for_condition 100 50 { - [s active_defrag_running] eq 0 - } else { - fail "Unable to wait for active defrag to stop" - } - - r flushdb - r frag.resetstats - r frag.create_frag_global 50000 - r config set activedefrag yes - - wait_for_condition 1000 50 { - [getInfoProperty [r info defragtest_stats] defragtest_defrag_ended] > 0 - } else { - fail "Unable to wait for a complete defragmentation cycle to finish" - } - - set info [r info defragtest_stats] - assert {[getInfoProperty $info defragtest_global_strings_attempts] > 0} - assert {[getInfoProperty $info defragtest_global_dicts_attempts] > 0} - assert {[getInfoProperty $info defragtest_global_dicts_defragged] > 0} - assert {[getInfoProperty $info defragtest_global_dicts_items_defragged] > 0} - assert_morethan [getInfoProperty $info defragtest_defrag_started] 0 - assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0 - assert_morethan [getInfoProperty $info defragtest_global_dicts_resumes] [getInfoProperty $info defragtest_defrag_ended] - assert_morethan [getInfoProperty $info defragtest_global_subdicts_resumes] [getInfoProperty $info defragtest_defrag_ended] - } {} {tsan:skip} - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl b/examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl deleted file mode 100644 index ba0d4a2..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl +++ /dev/null @@ -1,28 +0,0 @@ -set testmodule [file normalize tests/modules/eventloop.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test "Module eventloop sendbytes" { - assert_match "OK" [r test.sendbytes 5000000] - assert_match "OK" [r test.sendbytes 2000000] - } - - test "Module eventloop iteration" { - set iteration [r test.iteration] - set next_iteration [r test.iteration] - assert {$next_iteration > $iteration} - } - - test "Module eventloop sanity" { - r test.sanity - } - - test "Module eventloop oneshot" { - r test.oneshot - } - - test "Unload the module - eventloop" { - assert_equal {OK} [r module unload eventloop] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/fork.tcl b/examples/redis-unstable/tests/unit/moduleapi/fork.tcl deleted file mode 100644 index 19b3a68..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/fork.tcl +++ /dev/null @@ -1,41 +0,0 @@ -set testmodule [file normalize tests/modules/fork.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module fork} { - # the argument to fork.create is the exitcode on termination - # the second argument to fork.create is passed to usleep - r fork.create 3 100000 ;# 100ms - wait_for_condition 20 100 { - [r fork.exitcode] != -1 - } else { - fail "fork didn't terminate" - } - r fork.exitcode - } {3} - - test {Module fork kill} { - # use a longer time to avoid the child exiting before being killed - r fork.create 3 100000000 ;# 100s - wait_for_condition 20 100 { - [count_log_message 0 "fork child started"] == 2 - } else { - fail "fork didn't start" - } - - # module fork twice - assert_error {Fork failed} {r fork.create 0 1} - assert {[count_log_message 0 "Can't fork for module: File exists"] eq "1"} - - r fork.kill - - assert {[count_log_message 0 "Received SIGUSR1 in child"] eq "1"} - # check that it wasn't printed again (the print belong to the previous test) - assert {[count_log_message 0 "fork child exiting"] eq "1"} - } - - test "Unload the module - fork" { - assert_equal {OK} [r module unload fork] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl b/examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl deleted file mode 100644 index abfea5a..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl +++ /dev/null @@ -1,40 +0,0 @@ -set testmodule [file normalize tests/modules/getchannels.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - # Channels are currently used to just validate ACLs, so test them here - r ACL setuser testuser +@all resetchannels &channel &pattern* - - test "module getchannels-api with literals - ACL" { - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command subscribe literal channel subscribe literal pattern1] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command publish literal channel publish literal pattern1] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe literal channel unsubscribe literal pattern1] - - assert_equal "User testuser has no permissions to access the 'nopattern1' channel" [r ACL DRYRUN testuser getchannels.command subscribe literal channel subscribe literal nopattern1] - assert_equal "User testuser has no permissions to access the 'nopattern1' channel" [r ACL DRYRUN testuser getchannels.command publish literal channel subscribe literal nopattern1] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe literal channel unsubscribe literal nopattern1] - - assert_equal "User testuser has no permissions to access the 'otherchannel' channel" [r ACL DRYRUN testuser getchannels.command subscribe literal otherchannel subscribe literal pattern1] - assert_equal "User testuser has no permissions to access the 'otherchannel' channel" [r ACL DRYRUN testuser getchannels.command publish literal otherchannel subscribe literal pattern1] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe literal otherchannel unsubscribe literal pattern1] - } - - test "module getchannels-api with patterns - ACL" { - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command subscribe pattern pattern*] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command publish pattern pattern*] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe pattern pattern*] - - assert_equal "User testuser has no permissions to access the 'pattern1' channel" [r ACL DRYRUN testuser getchannels.command subscribe pattern pattern1 subscribe pattern pattern*] - assert_equal "User testuser has no permissions to access the 'pattern1' channel" [r ACL DRYRUN testuser getchannels.command publish pattern pattern1 subscribe pattern pattern*] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe pattern pattern1 unsubscribe pattern pattern*] - - assert_equal "User testuser has no permissions to access the 'otherpattern*' channel" [r ACL DRYRUN testuser getchannels.command subscribe pattern otherpattern* subscribe pattern pattern*] - assert_equal "User testuser has no permissions to access the 'otherpattern*' channel" [r ACL DRYRUN testuser getchannels.command publish pattern otherpattern* subscribe pattern pattern*] - assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe pattern otherpattern* unsubscribe pattern pattern*] - } - - test "Unload the module - getchannels" { - assert_equal {OK} [r module unload getchannels] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl b/examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl deleted file mode 100644 index 2854b86..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl +++ /dev/null @@ -1,89 +0,0 @@ -set testmodule [file normalize tests/modules/getkeys.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {COMMAND INFO correctly reports a movable keys module command} { - set info [lindex [r command info getkeys.command] 0] - - assert_equal {module movablekeys} [lindex $info 2] - assert_equal {0} [lindex $info 3] - assert_equal {0} [lindex $info 4] - assert_equal {0} [lindex $info 5] - } - - test {COMMAND GETKEYS correctly reports a movable keys module command} { - r command getkeys getkeys.command arg1 arg2 key key1 arg3 key key2 key key3 - } {key1 key2 key3} - - test {COMMAND GETKEYS correctly reports a movable keys module command using flags} { - r command getkeys getkeys.command_with_flags arg1 arg2 key key1 arg3 key key2 key key3 - } {key1 key2 key3} - - test {COMMAND GETKEYSANDFLAGS correctly reports a movable keys module command not using flags} { - r command getkeysandflags getkeys.command arg1 arg2 key key1 arg3 key key2 - } {{key1 {RW access update}} {key2 {RW access update}}} - - test {COMMAND GETKEYSANDFLAGS correctly reports a movable keys module command using flags} { - r command getkeysandflags getkeys.command_with_flags arg1 arg2 key key1 arg3 key key2 key key3 - } {{key1 {RO access}} {key2 {RO access}} {key3 {RO access}}} - - test {RM_GetCommandKeys on non-existing command} { - catch {r getkeys.introspect 0 non-command key1 key2} e - set _ $e - } {*ENOENT*} - - test {RM_GetCommandKeys on built-in fixed keys command} { - r getkeys.introspect 0 set key1 value1 - } {key1} - - test {RM_GetCommandKeys on built-in fixed keys command with flags} { - r getkeys.introspect 1 set key1 value1 - } {{key1 OW}} - - test {RM_GetCommandKeys on EVAL} { - r getkeys.introspect 0 eval "" 4 key1 key2 key3 key4 arg1 arg2 - } {key1 key2 key3 key4} - - test {RM_GetCommandKeys on a movable keys module command} { - r getkeys.introspect 0 getkeys.command arg1 arg2 key key1 arg3 key key2 key key3 - } {key1 key2 key3} - - test {RM_GetCommandKeys on a non-movable module command} { - r getkeys.introspect 0 getkeys.fixed arg1 key1 key2 key3 arg2 - } {key1 key2 key3} - - test {RM_GetCommandKeys with bad arity} { - catch {r getkeys.introspect 0 set key} e - set _ $e - } {*EINVAL*} - - test "introspect with > MAX_KEYS_BUFFER keys triggers RM_GetCommandKeysWithFlags heap alloc" { - set args {} - for {set i 0} {$i < 7} {incr i} { - lappend args key k$i - } - set reply [r getkeys.introspect 1 getkeys.command_with_flags {*}$args] - assert_equal {{k0 RO} {k1 RO} {k2 RO} {k3 RO} {k4 RO} {k5 RO} {k6 RO}} $reply - } - - # user that can only read from "read" keys, write to "write" keys, and read+write to "RW" keys - r ACL setuser testuser +@all %R~read* %W~write* %RW~rw* - - test "module getkeys-api - ACL" { - # legacy triple didn't provide flags, so they require both read and write - assert_equal "OK" [r ACL DRYRUN testuser getkeys.command key rw] - assert_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN testuser getkeys.command key read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN testuser getkeys.command key write] - } - - test "module getkeys-api with flags - ACL" { - assert_equal "OK" [r ACL DRYRUN testuser getkeys.command_with_flags key rw] - assert_equal "OK" [r ACL DRYRUN testuser getkeys.command_with_flags key read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN testuser getkeys.command_with_flags key write] - } - - test "Unload the module - getkeys" { - assert_equal {OK} [r module unload getkeys] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/hash.tcl b/examples/redis-unstable/tests/unit/moduleapi/hash.tcl deleted file mode 100644 index 8373c2d..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/hash.tcl +++ /dev/null @@ -1,176 +0,0 @@ -set testmodule [file normalize tests/modules/hash.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module hash set} { - r set k mystring - assert_error "WRONGTYPE*" {r hash.set k "" hello world} - r del k - # "" = count updates and deletes of existing fields only - assert_equal 0 [r hash.set k "" squirrel yes] - # "a" = COUNT_ALL = count inserted, modified and deleted fields - assert_equal 2 [r hash.set k "a" banana no sushi whynot] - # "n" = NX = only add fields not already existing in the hash - # "x" = XX = only replace the value for existing fields - assert_equal 0 [r hash.set k "n" squirrel hoho what nothing] - assert_equal 1 [r hash.set k "na" squirrel hoho something nice] - assert_equal 0 [r hash.set k "xa" new stuff not inserted] - assert_equal 1 [r hash.set k "x" squirrel ofcourse] - assert_equal 1 [r hash.set k "" sushi :delete: none :delete:] - r hgetall k - } {squirrel ofcourse banana no what nothing something nice} - - test {Module hash - set (override) NX expired field successfully} { - r debug set-active-expire 0 - r del H1 H2 - r hash.set H1 "n" f1 v1 - r hpexpire H1 1 FIELDS 1 f1 - r hash.set H2 "n" f1 v1 f2 v2 - r hpexpire H2 1 FIELDS 1 f1 - after 5 - assert_equal 0 [r hash.set H1 "n" f1 xx] - assert_equal "f1 xx" [r hgetall H1] - assert_equal 0 [r hash.set H2 "n" f1 yy] - assert_equal "f1 f2 v2 yy" [lsort [r hgetall H2]] - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {Module hash - set XX of expired field gets failed as expected} { - r debug set-active-expire 0 - r del H1 H2 - r hash.set H1 "n" f1 v1 - r hpexpire H1 1 FIELDS 1 f1 - r hash.set H2 "n" f1 v1 f2 v2 - r hpexpire H2 1 FIELDS 1 f1 - after 5 - - # expected to fail on condition XX. hgetall should return empty list - r hash.set H1 "x" f1 xx - assert_equal "" [lsort [r hgetall H1]] - # But expired field was not lazy deleted - assert_equal 1 [r hlen H1] - - # expected to fail on condition XX. hgetall should return list without expired f1 - r hash.set H2 "x" f1 yy - assert_equal "f2 v2" [lsort [r hgetall H2]] - # But expired field was not lazy deleted - assert_equal 2 [r hlen H2] - - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {Module hash - test open key with REDISMODULE_OPEN_KEY_ACCESS_EXPIRED to scan expired fields} { - r debug set-active-expire 0 - r del H1 - r hash.set H1 "n" f1 v1 f2 v2 f3 v3 - r hpexpire H1 1 FIELDS 2 f1 f2 - after 10 - # Scan expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - assert_equal "f1 f2 f3 v1 v2 v3" [lsort [r hash.hscan_expired H1]] - # Get expired field with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - assert_equal {v1} [r hash.hget_expired H1 f1] - # Verify we can get the TTL of the expired field as well - set now [expr [clock seconds]*1000] - assert_range [r hash.hget_expire H1 f2] [expr {$now-1000}] [expr {$now+1000}] - # Verify key doesn't exist on normal access without the flag - assert_equal 0 [r hexists H1 f1] - assert_equal 0 [r hexists H1 f2] - # Scan again expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - assert_equal "f3 v3" [lsort [r hash.hscan_expired H1]] - r debug set-active-expire 1 - } - - test {Module hash - test open key with REDISMODULE_OPEN_KEY_ACCESS_EXPIRED to scan expired key} { - r debug set-active-expire 0 - r del H1 - r hash.set H1 "n" f1 v1 f2 v2 f3 v3 - r pexpire H1 5 - after 10 - # Scan expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - assert_equal "f1 f2 f3 v1 v2 v3" [lsort [r hash.hscan_expired H1]] - # Get expired field with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - assert_equal {v1} [r hash.hget_expired H1 f1] - # Verify key doesn't exist on normal access without the flag - assert_equal 0 [r exists H1] - r debug set-active-expire 1 - } - - test {Module hash - Read field expiration time} { - r del H1 - r hash.set H1 "n" f1 v1 f2 v2 f3 v3 f4 v4 - r hexpire H1 10 FIELDS 1 f1 - r hexpire H1 100 FIELDS 1 f2 - r hexpire H1 1000 FIELDS 1 f3 - - # Validate that the expiration times for fields f1, f2, and f3 are correct - set nowMsec [expr [clock seconds]*1000] - assert_range [r hash.hget_expire H1 f1] [expr {$nowMsec+9000}] [expr {$nowMsec+11000}] - assert_range [r hash.hget_expire H1 f2] [expr {$nowMsec+90000}] [expr {$nowMsec+110000}] - assert_range [r hash.hget_expire H1 f3] [expr {$nowMsec+900000}] [expr {$nowMsec+1100000}] - - # Assert that field f4 and f5_not_exist have no expiration (should return -1) - assert_equal [r hash.hget_expire H1 f4] -1 - assert_equal [r hash.hget_expire H1 f5_not_exist] -1 - - # Assert that variadic version of hget_expire works as well - assert_equal [r hash.hget_two_expire H1 f1 f2] [list [r hash.hget_expire H1 f1] [r hash.hget_expire H1 f2]] - } - - test {Module hash - Read minimum expiration time} { - r del H1 - r hash.set H1 "n" f1 v1 f2 v2 f3 v3 f4 v4 - r hexpire H1 100 FIELDS 1 f1 - r hexpire H1 10 FIELDS 1 f2 - r hexpire H1 1000 FIELDS 1 f3 - - # Validate that the minimum expiration time is correct - set nowMsec [expr [clock seconds]*1000] - assert_range [r hash.hget_min_expire H1] [expr {$nowMsec+9000}] [expr {$nowMsec+11000}] - assert_equal [r hash.hget_min_expire H1] [r hash.hget_expire H1 f2] - - # Assert error if key not found - assert_error {*key not found*} {r hash.hget_min_expire non_exist_hash} - - # Assert return -1 if no expiration (=REDISMODULE_NO_EXPIRE) - r del H2 - r hash.set H2 "n" f1 v1 - assert_equal [r hash.hget_min_expire H2] -1 - } - - test {Module hash - KEYSIZES is updated as expected} { - proc run_cmd_verify_hist {cmd expOutput {retries 1}} { - proc K {} {return [string map { "db0_distrib_hashes_items" "db0_HASH" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]} - uplevel 1 $cmd - wait_for_condition 50 $retries { - $expOutput eq [K] - } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" } - } - - r select 0 - r flushall - # Check RM_HashSet - run_cmd_verify_hist {r hash.set H1 "n" f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {r hash.set H2 "n" f1 v1} {db0_HASH:1=2} - run_cmd_verify_hist {r hash.set H2 "n" f1 v1} {db0_HASH:1=2} - run_cmd_verify_hist {r hash.set H2 "x" f1 v1} {db0_HASH:1=2} - run_cmd_verify_hist {r hash.set H3 "x" f1 v1} {db0_HASH:1=2} - run_cmd_verify_hist {r hash.set H1 "n" f2 v2} {db0_HASH:1=1,2=1} - run_cmd_verify_hist {r hash.set H1 "a" f3 v3 f4 v4} {db0_HASH:1=1,4=1} - run_cmd_verify_hist {r del H1} {db0_HASH:1=1} - run_cmd_verify_hist {r del H2} {} - - # Check lazy expire - r debug set-active-expire 0 - run_cmd_verify_hist {r hash.set H1 "n" f1 v1} {db0_HASH:1=1} - run_cmd_verify_hist {r hpexpire H1 1 FIELDS 1 f1} {db0_HASH:1=1} - run_cmd_verify_hist {after 5} {db0_HASH:1=1} - run_cmd_verify_hist {r hash.hget_expired H1 f1} {db0_HASH:1=1} - r debug set-active-expire 1 - run_cmd_verify_hist {after 5} {} 50 - } - - test "Unload the module - hash" { - assert_equal {OK} [r module unload hash] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/hooks.tcl b/examples/redis-unstable/tests/unit/moduleapi/hooks.tcl deleted file mode 100644 index c4be3e4..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/hooks.tcl +++ /dev/null @@ -1,321 +0,0 @@ -set testmodule [file normalize tests/modules/hooks.so] - -tags "modules external:skip" { - start_server [list overrides [list loadmodule "$testmodule" appendonly yes]] { - test {Test module aof save on server start from empty} { - assert {[r hooks.event_count persistence-syncaof-start] == 1} - } - - test {Test clients connection / disconnection hooks} { - for {set j 0} {$j < 2} {incr j} { - set rd1 [redis_deferring_client] - $rd1 close - } - assert {[r hooks.event_count client-connected] > 1} - assert {[r hooks.event_count client-disconnected] > 1} - } - - test {Test module client change event for blocked client} { - set rd [redis_deferring_client] - # select db other than 0 - $rd select 1 - # block on key - $rd brpop foo 0 - # kill blocked client - r client kill skipme yes - # assert server is still up - assert_equal [r ping] PONG - $rd close - } - - test {Test module cron hook} { - after 100 - assert {[r hooks.event_count cron-loop] > 0} - set hz [r hooks.event_last cron-loop] - assert_equal $hz 10 - } - - test {Test module loaded / unloaded hooks} { - set othermodule [file normalize tests/modules/infotest.so] - r module load $othermodule - r module unload infotest - assert_equal [r hooks.event_last module-loaded] "infotest" - assert_equal [r hooks.event_last module-unloaded] "infotest" - } - - test {Test module aofrw hook} { - r debug populate 1000 foo 10000 ;# 10mb worth of data - r config set rdbcompression no ;# rdb progress is only checked once in 2mb - r BGREWRITEAOF - waitForBgrewriteaof r - assert_equal [string match {*module-event-persistence-aof-start*} [exec tail -20 < [srv 0 stdout]]] 1 - assert_equal [string match {*module-event-persistence-end*} [exec tail -20 < [srv 0 stdout]]] 1 - } - - test {Test module aof load and rdb/aof progress hooks} { - # create some aof tail (progress is checked only once in 1000 commands) - for {set j 0} {$j < 4000} {incr j} { - r set "bar$j" x - } - # set some configs that will cause many loading progress events during aof loading - r config set key-load-delay 500 - r config set dynamic-hz no - r config set hz 500 - r DEBUG LOADAOF - assert_equal [r hooks.event_last loading-aof-start] 0 - assert_equal [r hooks.event_last loading-end] 0 - assert {[r hooks.event_count loading-rdb-start] == 0} - assert_lessthan 2 [r hooks.event_count loading-progress-rdb] ;# comes from the preamble section - assert_lessthan 2 [r hooks.event_count loading-progress-aof] - if {$::verbose} { - puts "rdb progress events [r hooks.event_count loading-progress-rdb]" - puts "aof progress events [r hooks.event_count loading-progress-aof]" - } - } - # undo configs before next test - r config set dynamic-hz yes - r config set key-load-delay 0 - - test {Test module rdb save hook} { - # debug reload does: save, flush, load: - assert {[r hooks.event_count persistence-syncrdb-start] == 0} - assert {[r hooks.event_count loading-rdb-start] == 0} - r debug reload - assert {[r hooks.event_count persistence-syncrdb-start] == 1} - assert {[r hooks.event_count loading-rdb-start] == 1} - } - - test {Test key unlink hook} { - r set testkey1 hello - r del testkey1 - assert {[r hooks.event_count key-info-testkey1] == 1} - assert_equal [r hooks.event_last key-info-testkey1] testkey1 - r lpush testkey1 hello - r lpop testkey1 - assert {[r hooks.event_count key-info-testkey1] == 2} - assert_equal [r hooks.event_last key-info-testkey1] testkey1 - r set testkey2 world - r unlink testkey2 - assert {[r hooks.event_count key-info-testkey2] == 1} - assert_equal [r hooks.event_last key-info-testkey2] testkey2 - } - - test {Test removed key event} { - r set str abcd - r set str abcde - # For String Type value is returned - assert_equal {abcd overwritten} [r hooks.is_key_removed str] - assert_equal -1 [r hooks.pexpireat str] - - r del str - assert_equal {abcde deleted} [r hooks.is_key_removed str] - assert_equal -1 [r hooks.pexpireat str] - - # test int encoded string - r set intstr 12345678 - # incr doesn't fire event - r incr intstr - catch {[r hooks.is_key_removed intstr]} output - assert_match {ERR * removed} $output - r del intstr - assert_equal {12345679 deleted} [r hooks.is_key_removed intstr] - - catch {[r hooks.is_key_removed not-exists]} output - assert_match {ERR * removed} $output - - r hset hash f v - r hdel hash f - assert_equal {0 deleted} [r hooks.is_key_removed hash] - - r hset hash f v a b - r del hash - assert_equal {2 deleted} [r hooks.is_key_removed hash] - - r lpush list 1 - r lpop list - assert_equal {0 deleted} [r hooks.is_key_removed list] - - r lpush list 1 2 3 - r del list - assert_equal {3 deleted} [r hooks.is_key_removed list] - - r sadd set 1 - r spop set - assert_equal {0 deleted} [r hooks.is_key_removed set] - - r sadd set 1 2 3 4 - r del set - assert_equal {4 deleted} [r hooks.is_key_removed set] - - r zadd zset 1 f - r zpopmin zset - assert_equal {0 deleted} [r hooks.is_key_removed zset] - - r zadd zset 1 f 2 d - r del zset - assert_equal {2 deleted} [r hooks.is_key_removed zset] - - r xadd stream 1-1 f v - r xdel stream 1-1 - # Stream does not delete object when del entry - catch {[r hooks.is_key_removed stream]} output - assert_match {ERR * removed} $output - r del stream - assert_equal {0 deleted} [r hooks.is_key_removed stream] - - r xadd stream 2-1 f v - r del stream - assert_equal {1 deleted} [r hooks.is_key_removed stream] - - # delete key because of active expire - set size [r dbsize] - r set active-expire abcd px 1 - #ensure active expire - wait_for_condition 50 100 { - [r dbsize] == $size - } else { - fail "Active expire not trigger" - } - assert_equal {abcd expired} [r hooks.is_key_removed active-expire] - # current time is greater than pexpireat - set now [r time] - set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)] - assert {$mill >= [r hooks.pexpireat active-expire]} - - # delete key because of lazy expire - r debug set-active-expire 0 - r set lazy-expire abcd px 1 - after 10 - r get lazy-expire - assert_equal {abcd expired} [r hooks.is_key_removed lazy-expire] - set now [r time] - set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)] - assert {$mill >= [r hooks.pexpireat lazy-expire]} - r debug set-active-expire 1 - - # delete key not yet expired - set now [r time] - set expireat [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+1000000] - r set not-expire abcd pxat $expireat - r del not-expire - assert_equal {abcd deleted} [r hooks.is_key_removed not-expire] - assert_equal $expireat [r hooks.pexpireat not-expire] - - # Test key evict - set used [expr {[s used_memory] - [s mem_not_counted_for_evict]}] - set limit [expr {$used+100*1024}] - set old_policy [lindex [r config get maxmemory-policy] 1] - r config set maxmemory $limit - # We set policy volatile-random, so only keys with ttl will be evicted - r config set maxmemory-policy volatile-random - r setex volatile-key 10000 x - # We use SETBIT here, so we can set a big key and get the used_memory - # bigger than maxmemory. Next command will evict volatile keys. We - # can't use SET, as SET uses big input buffer, so it will fail. - r setbit big-key 1600000 0 ;# this will consume 200kb - r getbit big-key 0 - assert_equal {x evicted} [r hooks.is_key_removed volatile-key] - r config set maxmemory-policy $old_policy - r config set maxmemory 0 - } {OK} {needs:debug} - - test {Test flushdb hooks} { - r flushdb - assert_equal [r hooks.event_last flush-start] 9 - assert_equal [r hooks.event_last flush-end] 9 - r flushall - assert_equal [r hooks.event_last flush-start] -1 - assert_equal [r hooks.event_last flush-end] -1 - } - - # replication related tests - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - start_server {} { - r module load $testmodule - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - $replica replicaof $master_host $master_port - - wait_replica_online $master - - test {Test master link up hook} { - assert_equal [r hooks.event_count masterlink-up] 1 - assert_equal [r hooks.event_count masterlink-down] 0 - } - - test {Test role-replica hook} { - assert_equal [r hooks.event_count role-replica] 1 - assert_equal [r hooks.event_count role-master] 0 - assert_equal [r hooks.event_last role-replica] [s 0 master_host] - } - - test {Test replica-online hook} { - assert_equal [r -1 hooks.event_count replica-online] 1 - assert_equal [r -1 hooks.event_count replica-offline] 0 - } - - test {Test master link down hook} { - r client kill type master - assert_equal [r hooks.event_count masterlink-down] 1 - - wait_for_condition 50 100 { - [string match {*master_link_status:up*} [r info replication]] - } else { - fail "Replica didn't reconnect" - } - - assert_equal [r hooks.event_count masterlink-down] 1 - assert_equal [r hooks.event_count masterlink-up] 2 - } - - wait_for_condition 50 10 { - [string match {*master_link_status:up*} [r info replication]] - } else { - fail "Can't turn the instance into a replica" - } - - $replica replicaof no one - - test {Test role-master hook} { - assert_equal [r hooks.event_count role-replica] 1 - assert_equal [r hooks.event_count role-master] 1 - assert_equal [r hooks.event_last role-master] {} - } - - test {Test replica-offline hook} { - assert_equal [r -1 hooks.event_count replica-online] 2 - assert_equal [r -1 hooks.event_count replica-offline] 2 - } - # get the replica stdout, to be used by the next test - set replica_stdout [srv 0 stdout] - } - - test {Test swapdb hooks} { - r swapdb 0 10 - assert_equal [r hooks.event_last swapdb-first] 0 - assert_equal [r hooks.event_last swapdb-second] 10 - } - - test {Test configchange hooks} { - r config set rdbcompression no - assert_equal [r hooks.event_last config-change-count] 1 - assert_equal [r hooks.event_last config-change-first] rdbcompression - } - - # look into the log file of the server that just exited - test {Test shutdown hook} { - assert_equal [string match {*module-event-shutdown*} [exec tail -5 < $replica_stdout]] 1 - } - } - - start_server {} { - test {OnLoad failure will handle un-registration} { - catch {r module load $testmodule noload} - r flushall - r ping - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/infotest.tcl b/examples/redis-unstable/tests/unit/moduleapi/infotest.tcl deleted file mode 100644 index 493d24d..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/infotest.tcl +++ /dev/null @@ -1,131 +0,0 @@ -set testmodule [file normalize tests/modules/infotest.so] - -# Return value for INFO property -proc field {info property} { - if {[regexp "\r\n$property:(.*?)\r\n" $info _ value]} { - set _ $value - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - test {module reading info} { - # check string, integer and float fields - assert_equal [r info.gets replication role] "master" - assert_equal [r info.getc replication role] "master" - assert_equal [r info.geti stats expired_keys] 0 - assert_equal [r info.getd stats expired_stale_perc] 0 - - # check signed and unsigned - assert_equal [r info.geti infotest infotest_global] -2 - assert_equal [r info.getu infotest infotest_uglobal] -2 - - # the above are always 0, try module info that is non-zero - assert_equal [r info.geti infotest_italian infotest_due] 2 - set tre [r info.getd infotest_italian infotest_tre] - assert {$tre > 3.2 && $tre < 3.4 } - - # search using the wrong section - catch { [r info.gets badname redis_version] } e - assert_match {*not found*} $e - - # check that section filter works - assert { [string match "*usec_per_call*" [r info.gets all cmdstat_info.gets] ] } - catch { [r info.gets default cmdstat_info.gets] ] } e - assert_match {*not found*} $e - } - - test {module info all} { - set info [r info all] - # info all does not contain modules - assert { ![string match "*Spanish*" $info] } - assert { ![string match "*infotest_*" $info] } - assert { [string match "*used_memory*" $info] } - } - - test {module info all infotest} { - set info [r info all infotest] - # info all infotest should contain both ALL and the module information - assert { [string match "*Spanish*" $info] } - assert { [string match "*infotest_*" $info] } - assert { [string match "*used_memory*" $info] } - } - - test {module info everything} { - set info [r info everything] - # info everything contains all default sections, but not ones for crash report - assert { [string match "*infotest_global*" $info] } - assert { [string match "*Spanish*" $info] } - assert { [string match "*Italian*" $info] } - assert { [string match "*used_memory*" $info] } - assert { ![string match "*Klingon*" $info] } - field $info infotest_dos - } {2} - - test {module info modules} { - set info [r info modules] - # info all does not contain modules - assert { [string match "*Spanish*" $info] } - assert { [string match "*infotest_global*" $info] } - assert { ![string match "*used_memory*" $info] } - } - - test {module info one module} { - set info [r info INFOtest] ;# test case insensitive compare - # info all does not contain modules - assert { [string match "*Spanish*" $info] } - assert { ![string match "*used_memory*" $info] } - field $info infotest_global - } {-2} - - test {module info one section} { - set info [r info INFOtest_SpanisH] ;# test case insensitive compare - assert { ![string match "*used_memory*" $info] } - assert { ![string match "*Italian*" $info] } - assert { ![string match "*infotest_global*" $info] } - field $info infotest_uno - } {one} - - test {module info dict} { - set info [r info infotest_keyspace] - set keyspace [field $info infotest_db0] - set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d] - } {3} - - test {module info unsafe fields} { - set info [r info infotest_unsafe] - assert_match {*infotest_unsafe_field:value=1*} $info - } - - test {module info multiply sections without all, everything, default keywords} { - set info [r info replication INFOTEST] - assert { [string match "*Spanish*" $info] } - assert { ![string match "*used_memory*" $info] } - assert { [string match "*repl_offset*" $info] } - } - - test {module info multiply sections with all keyword and modules} { - set info [r info all modules] - assert { [string match "*cluster*" $info] } - assert { [string match "*cmdstat_info*" $info] } - assert { [string match "*infotest_global*" $info] } - } - - test {module info multiply sections with everything keyword} { - set info [r info replication everything cpu] - assert { [string match "*client_recent*" $info] } - assert { [string match "*cmdstat_info*" $info] } - assert { [string match "*Italian*" $info] } - # check that we didn't get the same info twice - assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] } - assert { ![string match "*Italian*Italian*" $info] } - field $info infotest_dos - } {2} - - test "Unload the module - infotest" { - assert_equal {OK} [r module unload infotest] - } - - # TODO: test crash report. -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/infra.tcl b/examples/redis-unstable/tests/unit/moduleapi/infra.tcl deleted file mode 100644 index 80cc962..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/infra.tcl +++ /dev/null @@ -1,25 +0,0 @@ -set testmodule [file normalize tests/modules/infotest.so] - -test {modules config rewrite} { - - start_server {tags {"modules external:skip"}} { - r module load $testmodule - - set modules [lmap x [r module list] {dict get $x name}] - assert_not_equal [lsearch $modules infotest] -1 - - r config rewrite - restart_server 0 true false - - set modules [lmap x [r module list] {dict get $x name}] - assert_not_equal [lsearch $modules infotest] -1 - - assert_equal {OK} [r module unload infotest] - - r config rewrite - restart_server 0 true false - - set modules [lmap x [r module list] {dict get $x name}] - assert_equal [lsearch $modules infotest] -1 - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl b/examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl deleted file mode 100644 index 3d0f1b1..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl +++ /dev/null @@ -1,287 +0,0 @@ -tags {modules external:skip cluster} { -set testmodule [file normalize tests/modules/internalsecret.so] - -set modules [list loadmodule $testmodule] -start_cluster 1 0 [list config_lines $modules] { - set r [srv 0 client] - - test {Internal command without internal connection fails as an unknown command} { - assert_error {*unknown command*} {r internalauth.internalcommand} - } - - test {Wrong internalsecret fails authentication} { - assert_error {*WRONGPASS invalid internal password*} {r auth "internal connection" 123} - } - - test {Internal connection basic flow} { - # A non-internal connection cannot execute internal commands, and they - # seem non-existent to it. - assert_error {*unknown command*} {r internalauth.internalcommand} - - # Authenticate as an internal connection - assert_equal {OK} [r debug mark-internal-client] - - # Now, internal commands are available. - assert_equal {OK} [r internalauth.internalcommand] - } -} - -start_server {} { - r module load $testmodule - - test {Internal secret is not available in non-cluster mode} { - # On non-cluster mode, the internal secret does not exist, nor is the - # internal auth command available - assert_error {*unknown command*} {r internalauth.internalcommand} - assert_error {*ERR no internal secret available*} {r internalauth.getinternalsecret} - assert_error {*Cannot authenticate as an internal connection on non-cluster instances*} {r auth "internal connection" somepassword} - } - - test {marking and un-marking a connection as internal via a debug command} { - # After marking the connection to an internal one via a debug command, - # internal commands succeed. - r debug mark-internal-client - assert_equal {OK} [r internalauth.internalcommand] - - # After unmarking the connection, internal commands fail. - r debug mark-internal-client unmark - assert_error {*unknown command*} {r internalauth.internalcommand} - } -} - -start_server {} { - r module load $testmodule - - test {Test `COMMAND *` commands with\without internal connections} { - # ------------------ Non-internal connection ------------------ - # `COMMAND DOCS ` returns empty response. - assert_equal {} [r command docs internalauth.internalcommand] - - # `COMMAND INFO ` should reply with null for the internal command - assert_equal {{}} [r command info internalauth.internalcommand] - - # `COMMAND GETKEYS/GETKEYSANDFLAGS ` returns an invalid command error - assert_error {*Invalid command specified*} {r command getkeys internalauth.internalcommand} - assert_error {*Invalid command specified*} {r command getkeysandflags internalauth.internalcommand} - - # -------------------- Internal connection -------------------- - # Non-empty response for non-internal connections. - assert_equal {OK} [r debug mark-internal-client] - - # `COMMAND DOCS ` returns a correct response. - assert_match {*internalauth.internalcommand*} [r command docs internalauth.internalcommand] - - # `COMMAND INFO ` should reply with a full response for the internal command - assert_match {*internalauth.internalcommand*} [r command info internalauth.internalcommand] - - # `COMMAND GETKEYS/GETKEYSANDFLAGS ` returns a key error (not related to the internal connection) - assert_error {*ERR The command has no key arguments*} {r command getkeys internalauth.internalcommand} - assert_error {*ERR The command has no key arguments*} {r command getkeysandflags internalauth.internalcommand} - } -} - -start_server {} { - r module load $testmodule - - test {No authentication needed for internal connections} { - # Authenticate with a user that does not have permissions to any command - r acl setuser David on >123 &* ~* -@all +auth +internalauth.getinternalsecret +debug +internalauth.internalcommand - assert_equal {OK} [r auth David 123] - - assert_equal {OK} [r debug mark-internal-client] - # Execute a command for which David does not have permission - assert_equal {OK} [r internalauth.internalcommand] - } -} - -start_server {} { - r module load $testmodule - - test {RM_Call of internal commands without user-flag succeeds only for all connections} { - # Fail before authenticating as an internal connection. - assert_equal {OK} [r internalauth.noninternal_rmcall internalauth.internalcommand] - } - - test {Internal commands via RM_Call succeeds for non-internal connections depending on the user flag} { - # A non-internal connection that calls rm_call of an internal command - assert_equal {OK} [r internalauth.noninternal_rmcall internalauth.internalcommand] - - # A non-internal connection that calls rm_call of an internal command - # with a user flag should fail. - assert_error {*unknown command*} {r internalauth.noninternal_rmcall_withuser internalauth.internalcommand} - } - - test {Internal connections override the user flag} { - # Authenticate as an internal connection - assert_equal {OK} [r debug mark-internal-client] - - assert_equal {OK} [r internalauth.noninternal_rmcall internalauth.internalcommand] - assert_equal {OK} [r internalauth.noninternal_rmcall_withuser internalauth.internalcommand] - } -} - -start_server {} { - r module load $testmodule - - test {RM_Call with the user-flag after setting thread-safe-context from an internal connection should fail} { - # Authenticate as an internal connection - assert_equal {OK} [r debug mark-internal-client] - - # New threadSafeContexts do not inherit the internal flag. - assert_error {*unknown command*} {r internalauth.noninternal_rmcall_detachedcontext_withuser internalauth.internalcommand} - } -} - -start_server {} { - r module load $testmodule - - r config set appendonly yes - r config set appendfsync always - waitForBgrewriteaof r - - test {AOF executes internal commands successfully} { - # Authenticate as an internal connection - assert_equal {OK} [r debug mark-internal-client] - - # Call an internal writing command - assert_equal {OK} [r internalauth.internal_rmcall_replicated set x 5] - - # Reload the server from the AOF - r debug loadaof - - # Check if the internal command was executed successfully - assert_equal {5} [r get x] - } -} - -start_server {} { - r module load $testmodule - - test {Internal commands are not allowed from scripts} { - # Internal commands are not allowed from scripts - assert_error {*not allowed from script*} {r eval {redis.call('internalauth.internalcommand')} 0} - - # Even after authenticating as an internal connection - assert_equal {OK} [r debug mark-internal-client] - assert_error {*not allowed from script*} {r eval {redis.call('internalauth.internalcommand')} 0} - } -} - -start_cluster 1 1 [list config_lines $modules] { - set master [srv 0 client] - set slave [srv -1 client] - - test {Setup master} { - # Authenticate as an internal connection - set reply [$master internalauth.getinternalsecret] - assert_equal {OK} [$master auth "internal connection" $reply] - } - - test {Slaves successfully execute internal commands from the replication link} { - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Master link status is not up" - } - - # Execute internal command in master, that will set `x` to `5`. - assert_equal {OK} [$master internalauth.internal_rmcall_replicated set x 5] - - # Wait for replica to have the key - $slave readonly - wait_for_condition 1000 50 { - [$slave exists x] eq "1" - } else { - fail "Test key was not replicated" - } - - # See that the slave has the same value for `x`. - assert_equal {5} [$slave get x] - } -} - -start_server {} { - r module load $testmodule - - test {Internal commands are not reported in the monitor output for non-internal connections when unsuccessful} { - set rd [redis_deferring_client] - $rd monitor - $rd read ; # Discard the OK - assert_error {*unknown command*} {r internalauth.internalcommand} - - # Assert that the monitor output does not contain the internal command - r ping - assert_match {*ping*} [$rd read] - $rd close - } - - test {Internal commands are not reported in the monitor output for non-internal connections when successful} { - # Authenticate as an internal connection - assert_equal {OK} [r debug mark-internal-client] - - set rd [redis_deferring_client] - $rd monitor - $rd read ; # Discard the OK - assert_equal {OK} [r internalauth.internalcommand] - - # Assert that the monitor output does not contain the internal command - r ping - assert_match {*ping*} [$rd read] - $rd close - } - - test {Internal commands are reported in the monitor output for internal connections} { - set rd [redis_deferring_client] - $rd debug mark-internal-client - assert_equal {OK} [$rd read] - $rd monitor - $rd read ; # Discard the OK - assert_equal {OK} [r internalauth.internalcommand] - - # Assert that the monitor output contains the internal command - assert_match {*internalauth.internalcommand*} [$rd read] - $rd close - } - - test {Internal commands are reported in the slowlog} { - # Set up slowlog to log all commands - r config set slowlog-log-slower-than 0 - - # Execute an internal command - r slowlog reset - r internalauth.internalcommand - - # The slow-log should contain the internal command - set log [r slowlog get 1] - assert_match {*internalauth.internalcommand*} $log - } - - test {Internal commands are reported in the latency report} { - # The latency report should contain the internal command - set report [r latency histogram internalauth.internalcommand] - assert_match {*internalauth.internalcommand*} $report - } - - test {Internal commands are reported in the command stats report} { - # The INFO report should contain the internal command for both the internal - # and non-internal connections. - set report [r info commandstats] - assert_match {*internalauth.internalcommand*} $report - - set report [r info latencystats] - assert_match {*internalauth.internalcommand*} $report - - # Un-mark the connection as internal - r debug mark-internal-client unmark - assert_error {*unknown command*} {r internalauth.internalcommand} - - # We still expect to see the internal command in the report - set report [r info commandstats] - assert_match {*internalauth.internalcommand*} $report - - set report [r info latencystats] - assert_match {*internalauth.internalcommand*} $report - } -} -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl b/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl deleted file mode 100644 index ebb7784..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl +++ /dev/null @@ -1,910 +0,0 @@ -# ============================================================================ -# Key Metadata (keymeta) Test Suite -# ============================================================================ -# -# Tests the Redis module key metadata framework: up to 7 independent metadata -# classes (IDs 1-7) can be attached to keys. Class ID 0 is reserved for key -# expiration. -# -# The following features are sensitive to Key Metadata and are tested here: -# -# - KEY EXPIRATION (class ID 0) -# - Stored at ((uint64_t *)kv) - 1 (first metadata slot) -# - Managed via db->expires dictionary -# - Must be preserved/updated when kvobj is reallocated -# - HASH FIELD EXPIRATION (HFE) -# - NOT in kvobj metadata slots (Maybe in the future...) -# - Managed via db->hexpires ebuckets (holds direct kvobj pointer) -# - Must be removed before kvobj reallocation (hashTypeRemoveFromExpires) -# and restored after (hashTypeAddToExpires) -# - MODULE METADATA (class IDs 1-7) -# - Defines metadata lifecycle via callbacks -# - EMBEDDED STRINGS vs. REGULAR OBJECTS -# - Short strings and numbers are embedded into kvobj -# - The rest are kept as distinct objects -# - LAZYFREE -# ============================================================================ - -set testmodule [file normalize tests/modules/test_keymeta.so] - -# Helper procedure to convert class ID to 4-char-id name -proc cname {cid} { - return "KMT$cid" -} - -# Helper procedure to check if a class should keep metadata for a given operation -proc shouldKeep {cid operation classesSpec} { - upvar $classesSpec specs - set spec $specs($cid) - switch $operation { - "copy" { return [string match "*KEEPONCOPY*" $spec] } - "rename" { return [string match "*KEEPONRENAME*" $spec] } - "move" { return [string match "*KEEPONMOVE*" $spec] } - default { return 0 } - } -} - -# Helper procedure to setup a key with metadata -proc setupKeyMeta {keyname numClasses expiryBefore expiryAfter} { - # Set expiry if requested - if {$expiryBefore} { - r expire $keyname 10000 - assert_range [r ttl $keyname] 9990 10000 - } - - # Set metadata for all classes - for {set i 1} {$i <= $numClasses} {incr i} { - # Set twice to verify overwrite behavior - r keymeta.set [cname $i] $keyname "blabla$i" - assert_equal [r keymeta.get [cname $i] $keyname] "blabla$i" - r keymeta.set [cname $i] $keyname "meta$i" - } - - # Verify metadata was set correctly - for {set i 1} {$i <= $numClasses} {incr i} { - assert_equal [r keymeta.get [cname $i] $keyname] "meta$i" - } - - if {$expiryAfter} { - r expire $keyname 10000 - assert_range [r ttl $keyname] 9990 10000 - } - - if {$expiryBefore} { - assert_range [r ttl $keyname] 9990 10000 - } -} - -# Helper procedure to verify metadata after an operation -proc verifyKeyMeta {keyname operation numClasses hasExpiry classesSpec} { - upvar $classesSpec specs - - # Verify expiry - if {$hasExpiry} { - assert_range [r ttl $keyname] 9990 10000 - } - - # Verify metadata based on class spec - for {set i 1} {$i <= $numClasses} {incr i} { - set expected [expr {[shouldKeep $i $operation specs] ? "meta$i" : ""}] - assert_equal [r keymeta.get [cname $i] $keyname] $expected - } -} - -proc flushallAndVerifyCleanup {} { - r flushall - # Verify all metadata is cleaned up properly - assert_equal [r keymeta.active] 0 -} - -start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - array set classesSpec {} - set classesSpec(1) "KEEPONCOPY:KEEPONRENAME:KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(2) "KEEPONCOPY:KEEPONRENAME:UNLINKFREE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(3) "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(4) "ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(5) "KEEPONRENAME:KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(6) "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set classesSpec(7) "KEEPONMOVE:UNLINKFREE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - - array set classes {} - for {set cid 1} {$cid <= 7} {incr cid} { - set spec $classesSpec($cid) - set classes($cid) [r keymeta.register [cname $cid] 1 $spec] - puts "Registered class $cid with spec $spec" - assert_equal $classes($cid) $cid - } - - # Validates metadata behavior across COPY/RENAME/MOVE operations - # with varying numbers of metadata classes (1-7), key expiration states, - # key types (string/hash), hash field expiration, and metadata class flags - # (KEEPONCOPY, KEEPONRENAME, KEEPONMOVE). - for {set numClasses 1} {$numClasses < 8} {incr numClasses} { - foreach expiryBefore {0 1} { - foreach expiryAfter {0 1} { - set hasExpiry [expr {$expiryBefore || $expiryAfter}] - set expiryStr "expiryBefore=$expiryBefore, expiryAfter=$expiryAfter)" - # Test COPY operation - test "KEYMETA - copy key-string with $numClasses classes, $expiryStr" { - foreach value { 3 "value1" [string repeat "ABCD" 1000]} { - r select 0 - r del k1 k2 - r set k1 $value - setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter - # Copy: - r copy k1 k2 - # Verify: - assert_equal [r get k1] $value - assert_equal [r get k2] $value - # Verify expiry and metadata - verifyKeyMeta k2 "copy" $numClasses $hasExpiry classesSpec - flushallAndVerifyCleanup - } - } - - test "KEYMETA - copy key-hash with $numClasses classes, $expiryStr" { - r select 0 - r del h1 h2 - r HSET h1 field1 "value1" field2 "value2" - r hexpire h1 10000 FIELDS 1 field1 - setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter - # Copy: - r copy h1 h2 - # Verify: - verifyKeyMeta h2 "copy" $numClasses $hasExpiry classesSpec - assert_range [r httl h1 FIELDS 1 field1] 9999 10000 - assert_range [r httl h2 FIELDS 1 field1] 9999 10000 - flushallAndVerifyCleanup - } - - # Test RENAME operation - test "KEYMETA - rename key-string with $numClasses classes, $expiryStr" { - foreach value { 3 "value1" [string repeat "ABCD" 1000]} { - r select 0 - r del k1 k2 - r set k1 $value - setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter - # Rename: - r rename k1 k2 - # Verify: - assert_equal [r exists k1] 0 - assert_equal [r get k2] $value - # Verify expiry and metadata - verifyKeyMeta k2 "rename" $numClasses $hasExpiry classesSpec - flushallAndVerifyCleanup - } - } - - test "KEYMETA - rename key-hash with $numClasses classes, $expiryStr" { - r select 0 - r del h1 h2 - r HSET h1 field1 "value1" field2 "value2" - r hexpire h1 10000 FIELDS 1 field1 - setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter - # Rename: - r rename h1 h2 - # Verify: - assert_equal [r exists h1] 0 - assert_range [r httl h2 FIELDS 1 field1] 9999 10000 - verifyKeyMeta h2 "rename" $numClasses $hasExpiry classesSpec - flushallAndVerifyCleanup - } - - - - # Test MOVE operation - test "KEYMETA - move key-string with $numClasses classes, $expiryStr" { - foreach value { 3 "value1" [string repeat "ABCD" 1000]} { - r select 9 - r del k1 - r select 0 - r del k1 - r set k1 $value - setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter - # Perform move - assert_equal [r move k1 9] 1 - # Verify key moved - assert_equal [r exists k1] 0 - r select 9 - assert_equal [r get k1] $value - # Verify expiry and metadata - verifyKeyMeta k1 "move" $numClasses $hasExpiry classesSpec - r select 0 - flushallAndVerifyCleanup - } - } - - test "KEYMETA - move key-hash with $numClasses classes, $expiryStr" { - r select 9 - r del h1 - r select 0 - r del h1 - r HSET h1 field1 "value1" field2 "value2" - r hexpire h1 10000 FIELDS 1 field1 - setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter - assert_range [r httl h1 FIELDS 1 field1] 9999 10000 - assert_equal [r move h1 9] 1 - assert_equal [r exists h1] 0 - r select 9 - assert_range [r httl h1 FIELDS 1 field1] 9999 10000 - verifyKeyMeta h1 "move" $numClasses $hasExpiry classesSpec - r select 0 - flushallAndVerifyCleanup - } - } - } - } - - test "KEYMETA - Verify active metadata count on copy" { - for {set cid 1} {$cid < 7} {incr cid} { - set numAlloc 0 - flushallAndVerifyCleanup - set dupOnCopy [shouldKeep $cid "copy" classesSpec] - r set k1 "v1" - r keymeta.set [cname $cid] k1 "meta1" - assert_equal [r keymeta.active] [incr numAlloc] - r keymeta.set [cname $cid] k1 "meta1b" - assert_equal [r keymeta.active] $numAlloc - r copy k1 k1copy - assert_equal [r keymeta.active] [incr numAlloc $dupOnCopy] - r del k1 - assert_equal [r keymeta.active] [incr numAlloc -1] - r del k1copy - assert_equal [r keymeta.active] 0 - } - } - - test "KEYMETA - Verify active metadata count on rename" { - for {set cid 1} {$cid <= 7} {incr cid} { - set numAlloc 0 - flushallAndVerifyCleanup - set keepOnRename [shouldKeep $cid "rename" classesSpec] - set discOnRename [expr {!$keepOnRename}] - r set k1 "v1" - r keymeta.set [cname $cid] k1 "meta1" - assert_equal [r keymeta.active] [incr numAlloc] - r rename k1 k1_renamed - assert_equal [r keymeta.active] [incr numAlloc -$discOnRename] - r del k1_renamed - assert_equal [r keymeta.active] 0 - } - } - - test "KEYMETA - Verify active metadata count on move" { - for {set cid 1} {$cid <= 7} {incr cid} { - set numAlloc 0 - r select 0 - flushallAndVerifyCleanup - - set keepOnMove [shouldKeep $cid "move" classesSpec] - set discOnMove [expr {!$keepOnMove}] - - # Create keys with metadata in DB 0 - r set k1 "v1" - r keymeta.set [cname $cid] k1 "meta1" - assert_equal [r keymeta.active] [incr numAlloc] - # Move: metadata discarded if !keepOnMove - r move k1 9 - set active [r keymeta.active] - assert_equal [r keymeta.active] [incr numAlloc -$discOnMove] - # Cleanup - r select 9 - r del k1 - r select 0 - assert_equal [r keymeta.active] 0 - } - } - - test "KEYMETA - Verify metadta cleanup on lazyfree" { - r config set lazyfree-lazy-user-del yes - # Class 2 has UNLINKFREE flag, so it should call unlink callback when lazyfree is enabled - # Class 1 does not have UNLINKFREE flag, so it should only call free callback - foreach {cid} { 1 2 } { - r config resetstat - # Create a large unsorted set collection to ensure it exceeds LAZYFREE_THRESHOLD - for {set i 0} {$i < 1024} {incr i} { r sadd myset $i } - r keymeta.set [cname $cid] myset "meta" - assert_equal [r keymeta.active] 1 - r del myset - - # Wait for lazyfree to complete and verify lazyfreed_objects incremented - wait_for_condition 50 100 { - [s lazyfree_pending_objects] == 0 - } else { - fail "lazyfree isn't done" - } - assert_equal [r keymeta.active] 0 - assert_equal [s lazyfreed_objects] 1 - } - r config set lazyfree-lazy-user-del no - } {OK} {needs:config-resetstat} - - test "KEYMETA - Verify metadata cleanup on expire" { - # Class 2 has UNLINKFREE flag, so it should call unlink callback when lazyfree is enabled - # Class 1 does not have UNLINKFREE flag, so it should only call free callback - foreach {cid} { 1 2 } { - r set mykey "mykey$cid" - r keymeta.set [cname $cid] mykey "meta" - assert_equal [r keymeta.active] 1 - r pexpire mykey 1 - wait_for_condition 50 100 { - [r exists mykey] == 0 - } else { - fail "key not expired" - } - assert_equal [r keymeta.active] 0 - } - } - - # ============================================================================ - # AOF Rewrite Tests - # ============================================================================ - # Note: Full AOF round-trip tests (write → restart → load) are not included - # because the test module registers classes dynamically via commands, which - # creates a chicken-and-egg problem: - # - Classes must be registered BEFORE AOF loading (in RedisModule_OnLoad) - # - But the KEYMETA.REGISTER commands are in the AOF itself - # - When server restarts and loads AOF, classes aren't registered yet - # - KEYMETA.SET commands fail with "metadata class not found" - # - # For production modules, classes MUST be registered in RedisModule_OnLoad() - # to ensure they're available when AOF/RDB files are loaded on server startup. - # See src/module.c documentation for RM_CreateKeyMetaClass() for details. - # - # The test below verifies that AOF callbacks correctly emit KEYMETA.SET commands - # to the AOF file during rewrite, which is the module's responsibility. - test "KEYMETA - AOF rewrite emits correct KEYMETA.SET commands to file" { - # This test verifies that the AOF callback implementation correctly writes - # KEYMETA.SET commands to the AOF file during rewrite. We don't test the - # full round-trip (restart + load) due to the dynamic registration limitation - # explained above. - - r config set appendonly yes - r config set auto-aof-rewrite-percentage 0 - r config set aof-use-rdb-preamble no - # Wait for the initial AOF rewrite that Redis triggers when enabling AOF - waitForBgrewriteaof r - - # Create keys with metadata from multiple classes - r set key1 "value1" - r keymeta.set [cname 1] key1 "metadata_c1" - - r set key2 "value2" - r keymeta.set [cname 2] key2 "metadata_c2" - r keymeta.set [cname 3] key2 "metadata_c3" - - r hset hashkey field1 val1 - r keymeta.set [cname 4] hashkey "hash_meta" - - # Trigger AOF rewrite - r bgrewriteaof - waitForBgrewriteaof r - - # Get the AOF directory and read the AOF file - set aof_dir [lindex [r config get dir] 1] - set aof_base_filename [lindex [r config get appendfilename] 1] - - # Find the base AOF file (after rewrite) - set aof_files [glob -nocomplain -directory $aof_dir appendonlydir/${aof_base_filename}.*.base.aof] - assert {[llength $aof_files] > 0} - - # Read the most recent base AOF file - set aof_file [lindex [lsort $aof_files] end] - set fp [open $aof_file r] - set aof_content [read $fp] - close $fp - - # Verify the AOF contains KEYMETA.SET commands with correct format - assert_match "*KEYMETA.SET*[cname 1]*key1*metadata_c1*" $aof_content - assert_match "*KEYMETA.SET*[cname 2]*key2*metadata_c2*" $aof_content - assert_match "*KEYMETA.SET*[cname 3]*key2*metadata_c3*" $aof_content - assert_match "*KEYMETA.SET*[cname 4]*hashkey*hash_meta*" $aof_content - - # Verify the RESP format is correct by checking for the command structure - # The AOF should contain: *4 (array of 4 elements) - assert_match "*\$11*KEYMETA.SET*" $aof_content - # Count how many KEYMETA.SET commands are in the AOF - set keymeta_count [regexp -all {KEYMETA\.SET} $aof_content] - assert_equal $keymeta_count 4 - } {} {external:skip} - - # ======================================================================== - # RDB Save/Load Tests - # ======================================================================== - - test {RDB: SAVE and reload preserves metadata} { - # Create key with metadata - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - r save - r debug reload - - # Verify metadata persisted after reload - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - flushallAndVerifyCleanup - } {} {external:skip needs:save} - - test {RDB: BGSAVE writes metadata to RDB file} { - # Create keys with different metadata combinations - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - - r set key2 "value2" - r keymeta.set [cname 1] key2 "key2_meta1" - r keymeta.set [cname 2] key2 "key2_meta2" - - # Trigger BGSAVE and reload (debug reload preserves modules) - r bgsave - waitForBgsave r - r debug reload - - # Verify metadata persisted after reload - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - assert_equal [r keymeta.get [cname 1] key2] "key2_meta1" - assert_equal [r keymeta.get [cname 2] key2] "key2_meta2" - - flushallAndVerifyCleanup - } {} {external:skip needs:save} - - test {RDB: Metadata persists with expiretime} { - # Create key with both expiry and metadata - r set key1 "value1" - set expire_time [expr {[clock seconds] + 10000}] - r expireat key1 $expire_time - r keymeta.set [cname 1] key1 "meta_with_expire" - - assert_equal [r expiretime key1] $expire_time - assert_equal [r keymeta.get [cname 1] key1] "meta_with_expire" - - # Reload from RDB - r debug reload - - # Verify metadata and expiry persist after reload - assert_equal [r expiretime key1] $expire_time - assert_equal [r keymeta.get [cname 1] key1] "meta_with_expire" - - flushallAndVerifyCleanup - } {} {external:skip needs:debug} - - test {RDB: Create keys with upto 7 meta classes, with or without expiry} { - # Test all combinations of 1-7 metadata classes, with or without expiry - for {set n 1} {$n <= 7} {incr n} { - foreach hasExpiry {0 1} { - set keyname "key_${n}_exp${hasExpiry}" - r set $keyname "value$n" - - # Set expiry if hasExpiry is 1 - if {$hasExpiry} { - set ttl [expr {3600 + $n}] - r expire $keyname $ttl - # Get the actual expiretime set by Redis to use as expected value - set expExpiry [r expiretime $keyname] - } - - # Create list of class IDs to attach (1 through n) - set class_ids {} - for {set i 1} {$i <= $n} {incr i} { - lappend class_ids $i - } - - # Randomize the order of metadata attachment - set class_ids [lshuffle $class_ids] - - # Attach metadata in randomized order - foreach cid $class_ids { - r keymeta.set [cname $cid] $keyname "meta$cid" - } - - # Verify metadata before RDB save - # Verify exactly n metadata classes are attached - for {set i 1} {$i <= 7} {incr i} { - if {$i <= $n} { - assert_equal [r keymeta.get [cname $i] $keyname] "meta$i" - } else { - assert_equal [r keymeta.get [cname $i] $keyname] "" - } - } - - # Verify expiry before RDB save - if {$hasExpiry} { - set actual_expiretime [r expiretime $keyname] - assert_equal $actual_expiretime $expExpiry - } - - # Save and reload from RDB (debug reload preserves modules) - r save - r debug reload - - # Verify metadata after RDB reload - # Verify exactly n metadata classes are still attached - for {set i 1} {$i <= 7} {incr i} { - if {$i <= $n} { - assert_equal [r keymeta.get [cname $i] $keyname] "meta$i" - } else { - assert_equal [r keymeta.get [cname $i] $keyname] "" - } - } - - # Verify expiry after RDB reload - if {$hasExpiry} { - set actual_expiretime [r expiretime $keyname] - assert_equal $actual_expiretime $expExpiry - } else { - # Verify no expiry set - assert_equal [r expiretime $keyname] -1 - } - flushallAndVerifyCleanup - } - } - } {} {external:skip needs:save} - - # ======================================================================== - # RDB Flag Tests: ALLOW_IGNORE, RDBLOAD, RDBSAVE - # ======================================================================== - - # Test all combinations except the error case (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - foreach RDBLOAD {0 1} { - foreach RDBSAVE {0 1} { - foreach ALLOW_IGNORE {0 1} { - # Skip the error case - we'll test it last since it causes RDB load to fail - if {!$RDBLOAD && $RDBSAVE && !$ALLOW_IGNORE} { continue } - - test "RDB: SAVE and LOAD (ALLOW_IGNORE=$ALLOW_IGNORE, RDBLOAD=$RDBLOAD, RDBSAVE=$RDBSAVE)" { - # Flush all data and save empty RDB to start with a clean slate - r flushall - r save - - # re-register class 1 with new flags. Expected re-registered same class ID - r keymeta.unregister [cname 1] - # dummy default spec - set newSpec "KEEPONCOPY" - if {$ALLOW_IGNORE} { append newSpec ":ALLOWIGNORE" } - if {$RDBLOAD} { append newSpec ":RDBLOAD" } - if {$RDBSAVE} { append newSpec ":RDBSAVE" } - - # Must reuse same class-id that it had before - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - r save - r debug reload - - # Metadata is preserved only when BOTH rdb_save AND rdb_load are enabled - # Otherwise metadata is lost (either not saved, or saved but not loaded) - set metaPreserved [expr {$RDBSAVE && $RDBLOAD}] - set expectedMeta [expr {$metaPreserved ? "key1_meta1" : ""}] - - assert_equal [r keymeta.get [cname 1] key1] $expectedMeta - - flushallAndVerifyCleanup - } {} {external:skip needs:save} - } - } - } - - # Test the error case last (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - # This test causes RDB load to fail, so we test it last to avoid polluting subsequent tests - test "RDB: SAVE and LOAD Invalid combination: (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)" { - # re-register class 1 with RDBSAVE flag but no RDBLOAD or ALLOW_IGNORE - r keymeta.unregister [cname 1] - set newSpec "KEEPONCOPY:RDBSAVE" - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - r save - - # This combination causes RDB load to fail because: - # - Metadata was saved (RDBSAVE=1) - # - Class has no rdb_load callback (RDBLOAD=0) - # - Errors are not ignored (ALLOW_IGNORE=0) - catch {r debug reload} err - assert_match "*Error trying to load the RDB dump*" $err - } {} {external:skip needs:save} - - # ======================================================================== - # DUMP/RESTORE Tests - # ======================================================================== - - test {DUMP/RESTORE: 1 to 7 metadata classes, optional TTL} { - foreach withTTL {0 1} { - for {set numClasses 1} {$numClasses < 8} {incr numClasses} { - # Re-register classes with RDBLOAD and RDBSAVE flags - for {set cid 1} {$cid <= $numClasses} {incr cid} { - r keymeta.unregister [cname $cid] - assert_equal $classes($cid) [r keymeta.register [cname $cid] 1 $classesSpec($cid)] - } - - # Create key with metadata classes - r set key1 "value1" - for {set i 1} {$i <= $numClasses} {incr i} { - r keymeta.set [cname $i] key1 "meta${i}_value" - } - - if {$withTTL} { r expire key1 10000 } - - # Verify all metadata before DUMP - for {set i 1} {$i <= $numClasses} {incr i} { - assert_equal [r keymeta.get [cname $i] key1] "meta${i}_value" - } - - # DUMP the key - set encoded [r dump key1] - - # Delete and RESTORE - r del key1 - r restore key1 [expr {$withTTL ? 10000 : 0}] $encoded - - # Verify all metadata was restored - assert_equal [r get key1] "value1" - for {set i 1} {$i <= $numClasses} {incr i} { - assert_equal [r keymeta.get [cname $i] key1] "meta${i}_value" - } - if {$withTTL} { assert_range [r pttl key1] 9000 10000 } - - flushallAndVerifyCleanup - } - } - } - - test {DUMP/RESTORE: REPLACE with metadata} { - # Create key with metadata - r set key1 value1 - r keymeta.set [cname 1] key1 "meta1_original" - - # DUMP the key - set encoded1 [r dump key1] - - # Create different key with different metadata - r set key1 value2 - r keymeta.set [cname 1] key1 "meta1_new" - - # DUMP the second version - set encoded2 [r dump key1] - - # Delete and restore first version - r del key1 - r restore key1 0 $encoded1 - assert_equal [r get key1] "value1" - assert_equal [r keymeta.get [cname 1] key1] "meta1_original" - - # RESTORE second version with REPLACE - r restore key1 0 $encoded2 replace - assert_equal [r get key1] "value2" - assert_equal [r keymeta.get [cname 1] key1] "meta1_new" - - flushallAndVerifyCleanup - } - - - # Test all combinations except the error case (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - foreach RDBLOAD {0 1} { - foreach RDBSAVE {0 1} { - foreach ALLOW_IGNORE {0 1} { - # Skip the error case - we'll test it last since it causes RESTORE to fail - if {!$RDBLOAD && $RDBSAVE && !$ALLOW_IGNORE} { continue } - - test "DUMP/RESTORE: (ALLOW_IGNORE=$ALLOW_IGNORE, RDBLOAD=$RDBLOAD, RDBSAVE=$RDBSAVE)" { - # re-register class 1 with new flags. Expected re-registered same class ID - r keymeta.unregister [cname 1] - # dummy default spec - set newSpec "KEEPONCOPY" - if {$ALLOW_IGNORE} { append newSpec ":ALLOWIGNORE" } - if {$RDBLOAD} { append newSpec ":RDBLOAD" } - if {$RDBSAVE} { append newSpec ":RDBSAVE" } - - # Must reuse same class-id that it had before - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - # DUMP & RESTORE - set encoded [r dump key1] - r del key1 - r restore key1 0 $encoded - - # Metadata is preserved only when BOTH rdb_save AND rdb_load are enabled - # Otherwise metadata is lost (either not saved, or saved but not loaded) - set metaPreserved [expr {$RDBSAVE && $RDBLOAD}] - set expectedMeta [expr {$metaPreserved ? "key1_meta1" : ""}] - - assert_equal [r keymeta.get [cname 1] key1] $expectedMeta - - flushallAndVerifyCleanup - } - } - } - } - - # Test the error case last (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1) - # This test causes RESTORE to fail, so we test it last to avoid polluting subsequent tests - test "DUMP/RESTORE: Invalid combination: (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)" { - # re-register class 1 with RDBSAVE flag but no RDBLOAD or ALLOW_IGNORE - r keymeta.unregister [cname 1] - set newSpec "KEEPONCOPY:RDBSAVE" - assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec] - - r set key1 "value1" - r keymeta.set [cname 1] key1 "key1_meta1" - assert_equal [r keymeta.get [cname 1] key1] "key1_meta1" - - # DUMP the key - set encoded [r dump key1] - - # Delete and try to RESTORE - r del key1 - - # This combination causes RESTORE to fail because: - # - Metadata was saved (RDBSAVE=1) - # - Class has no rdb_load callback (RDBLOAD=0) - # - Errors are not ignored (ALLOW_IGNORE=0) - catch {r restore key1 0 $encoded} err - assert_match "*Bad data format*" $err - - flushallAndVerifyCleanup - } -} - -test "RDB: Load with different module registration order preserves metadata correctly" { - # This test verifies out-of-order metadata attachment during RDB load. - # When modules register in different order at load time vs save time, - # metadata values should still be correctly associated with their classes. - start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - # Helper function to generate class names (needed in inner scope) - proc cname {id} { return "CLS$id" } - - # Register classes in order: 1, 2, 3 - set spec1 "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec2 "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec3 "KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - - set class1 [r keymeta.register [cname 1] 1 $spec1] - set class2 [r keymeta.register [cname 2] 1 $spec2] - set class3 [r keymeta.register [cname 3] 1 $spec3] - - # Verify class IDs match registration order - assert_equal $class1 1 "Class 1 registered first, gets ID 1" - assert_equal $class2 2 "Class 2 registered second, gets ID 2" - assert_equal $class3 3 "Class 3 registered third, gets ID 3" - - # OUTER SERVER: Create RDB with classes registered in order 1,2,3 - r flushall - r set mykey "myvalue" - r keymeta.set [cname 1] mykey "metadata_for_class1" - r keymeta.set [cname 2] mykey "metadata_for_class2" - r keymeta.set [cname 3] mykey "metadata_for_class3" - - # Verify metadata before save - assert_equal [r keymeta.get [cname 1] mykey] "metadata_for_class1" - assert_equal [r keymeta.get [cname 2] mykey] "metadata_for_class2" - assert_equal [r keymeta.get [cname 3] mykey] "metadata_for_class3" - - r save - - # Get RDB file path & Copy RDB to a temp location with unique name - set rdb_dir [lindex [r config get dir] 1] - set rdb_file [lindex [r config get dbfilename] 1] - set rdb_path [file join $rdb_dir $rdb_file] - set temp_rdb [file join $rdb_dir "temp_metadata_outoforder_[pid].rdb"] - file copy -force $rdb_path $temp_rdb - - # INNER SERVER: Start new server, register classes in DIFFERENT order, then load RDB - start_server [list overrides [list dir $rdb_dir enable-debug-command yes]] { - r module load $testmodule - - # Helper function to generate class names (needed in inner scope) - proc cname {id} { return "CLS$id" } - - # Register classes in DIFFERENT order: 3, 1, 2 - # This simulates a server where modules load in different order - set spec1 "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec2 "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE" - set spec3 "KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE" - - set class3 [r keymeta.register [cname 3] 1 $spec3] - set class1 [r keymeta.register [cname 1] 1 $spec1] - set class2 [r keymeta.register [cname 2] 1 $spec2] - - # Verify class IDs are assigned by REGISTRATION ORDER, not name - # We registered in order 3,1,2, so the runtime IDs are: - # - class3 (name "CLS3") gets ID 1 (first registered) - # - class1 (name "CLS1") gets ID 2 (second registered) - # - class2 (name "CLS2") gets ID 3 (third registered) - # This is DIFFERENT from outer server which registered in order 1,2,3 - assert_equal $class3 1 "Class 3 registered first, gets ID 1" - assert_equal $class1 2 "Class 1 registered second, gets ID 2" - assert_equal $class2 3 "Class 2 registered third, gets ID 3" - - # Copy the saved RDB to this server's dbfilename - set inner_rdb_file [lindex [r config get dbfilename] 1] - set inner_rdb_path [file join $rdb_dir $inner_rdb_file] - file copy -force $temp_rdb $inner_rdb_path - - # NOW load the RDB (AFTER registration in different order) - # Use 'nosave' to reload from the copied RDB without saving current state first - r debug reload nosave - - # Verify the key exists - assert_equal [r exists mykey] 1 "Key should exist after RDB load" - assert_equal [r get mykey] "myvalue" "Key value should be preserved" - - # Verify metadata values are correctly associated with their classes - # WITHOUT metadata would be swapped because: - # - At SAVE time (outer): classes registered in order 1,2,3 - # - At LOAD time (inner): classes registered in order 3,1,2 - # - RDB contains metadata in saved order, but keyMetaClassLookupByName - # maps them back to correct classes by NAME, not by registration order - assert_equal [r keymeta.get [cname 1] mykey] "metadata_for_class1" - assert_equal [r keymeta.get [cname 2] mykey] "metadata_for_class2" - assert_equal [r keymeta.get [cname 3] mykey] "metadata_for_class3" - } - - # Cleanup temp file - file delete $temp_rdb - - } -} {} {external:skip needs:save} - -test "RDB: File size same with/without metadata when no rdb_save callback" { - # This test verifies that when a metadata class has no rdb_save callback, - # the metadata is not serialized to RDB, so the RDB file size should be - # approximately the same (within a small tolerance for header differences). - - start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} { - r module load $testmodule - - # Get RDB directory - set rdb_dir [lindex [r config get dir] 1] - set rdb_file [lindex [r config get dbfilename] 1] - set rdb_path [file join $rdb_dir $rdb_file] - - # Test 1: Create key WITHOUT metadata and save - r flushall - r set key1 "test_value_12345" - r save - set size_without_meta [file size $rdb_path] - - # Test 2: Create identical key WITH metadata (but no rdb_save) and save - # Register a class WITHOUT rdb_save callback (RDBSAVE=0) - # Use ALLOWIGNORE so loading doesn't fail when metadata is missing - set spec "ALLOWIGNORE" - r keymeta.register [cname 1] 1 $spec - - r flushall - r set key1 "test_value_12345" - r keymeta.set [cname 1] key1 "some_metadata_value" - - # Verify metadata is attached - assert_equal [r keymeta.get [cname 1] key1] "some_metadata_value" - - r save - set size_with_meta [file size $rdb_path] - - # The file sizes should be the same (metadata not serialized) - assert_equal $size_without_meta $size_with_meta - } -} {} {external:skip needs:save} - -test "Creating key metadata not during OnLoad should fail" { - # This time start_server without "enable-debug-command yes" - start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command no}} { - r module load $testmodule - # Creating a class not during OnLoad should fail - catch {r keymeta.register [cname 1] 1 "ALLOWIGNORE"} err - assert_match {*failed to create metadata class*} $err - } -} {} {external:skip needs:save} diff --git a/examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl b/examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl deleted file mode 100644 index 5d62a71..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl +++ /dev/null @@ -1,140 +0,0 @@ -set testmodule [file normalize tests/modules/keyspace_events.so] - -tags "modules external:skip" { - start_server [list overrides [list loadmodule "$testmodule"]] { - - # avoid using shared integers, to increase the chance of detection heap issues - r config set maxmemory-policy allkeys-lru - r config set maxmemory 1gb - - test {Test loaded key space event} { - r set x 1 - r hset y f v - r lpush z 1 2 3 - r sadd p 1 2 3 - r zadd t 1 f1 2 f2 - r xadd s * f v - r debug reload - assert_equal {1 x} [r keyspace.is_key_loaded x] - assert_equal {1 y} [r keyspace.is_key_loaded y] - assert_equal {1 z} [r keyspace.is_key_loaded z] - assert_equal {1 p} [r keyspace.is_key_loaded p] - assert_equal {1 t} [r keyspace.is_key_loaded t] - assert_equal {1 s} [r keyspace.is_key_loaded s] - } - - test {Nested multi due to RM_Call} { - r del multi - r del lua - - r set x 1 - r set x_copy 1 - r keyspace.del_key_copy x - r keyspace.incr_case1 x - r keyspace.incr_case2 x - r keyspace.incr_case3 x - assert_equal {} [r get multi] - assert_equal {} [r get lua] - r get x - } {3} - - test {Nested multi due to RM_Call, with client MULTI} { - r del multi - r del lua - - r set x 1 - r set x_copy 1 - r multi - r keyspace.del_key_copy x - r keyspace.incr_case1 x - r keyspace.incr_case2 x - r keyspace.incr_case3 x - r exec - assert_equal {1} [r get multi] - assert_equal {} [r get lua] - r get x - } {3} - - test {Nested multi due to RM_Call, with EVAL} { - r del multi - r del lua - - r set x 1 - r set x_copy 1 - r eval { - redis.pcall('keyspace.del_key_copy', KEYS[1]) - redis.pcall('keyspace.incr_case1', KEYS[1]) - redis.pcall('keyspace.incr_case2', KEYS[1]) - redis.pcall('keyspace.incr_case3', KEYS[1]) - } 1 x - assert_equal {} [r get multi] - assert_equal {1} [r get lua] - r get x - } {3} - - test {Test module key space event} { - r keyspace.notify x - assert_equal {1 x} [r keyspace.is_module_key_notified x] - } - - test "Keyspace notifications: module events test" { - r config set notify-keyspace-events Kd - r del x - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r keyspace.notify x - assert_equal {pmessage * __keyspace@9__:x notify} [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: unsubscribe removes handler" { - r config set notify-keyspace-events KEA - set before [r keyspace.callback_count] - r set a 1 - r del a - wait_for_condition 100 10 { - [r keyspace.callback_count] > $before - } else { - fail "callback did not trigger" - } - set before_unsub [r keyspace.callback_count] - r keyspace.unsubscribe 4 ;# REDISMODULE_NOTIFY_GENERIC - r set a 1 - r del a - set after_unsub [r keyspace.callback_count] - assert_equal $before_unsub $after_unsub - } - - test {Test expired key space event} { - set prev_expired [s expired_keys] - r set exp 1 PX 10 - wait_for_condition 100 10 { - [s expired_keys] eq $prev_expired + 1 - } else { - fail "key not expired" - } - assert_equal [r get testkeyspace:expired] 1 - } - - test "Unload the module - testkeyspace" { - assert_equal {OK} [r module unload testkeyspace] - } - - test "Verify RM_StringDMA with expiration are not causing invalid memory access" { - assert_equal {OK} [r set x 1 EX 1] - } - } - - start_server {} { - test {OnLoad failure will handle un-registration} { - catch {r module load $testmodule noload} - r set x 1 - r hset y f v - r lpush z 1 2 3 - r sadd p 1 2 3 - r zadd t 1 f1 2 f2 - r xadd s * f v - r ping - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl b/examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl deleted file mode 100644 index 3b98b80..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl +++ /dev/null @@ -1,160 +0,0 @@ -set testmodule [file normalize tests/modules/keyspecs.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test "Module key specs: No spec, only legacy triple" { - set reply [lindex [r command info kspec.none] 0] - # Verify (first, last, step) and not movablekeys - assert_equal [lindex $reply 2] {module} - assert_equal [lindex $reply 3] 1 - assert_equal [lindex $reply 4] -1 - assert_equal [lindex $reply 5] 2 - # Verify key-spec auto-generated from the legacy triple - set keyspecs [lindex $reply 8] - assert_equal [llength $keyspecs] 1 - assert_equal [lindex $keyspecs 0] {flags {RW access update} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey -1 keystep 2 limit 0}}} - assert_equal [r command getkeys kspec.none key1 val1 key2 val2] {key1 key2} - } - - test "Module key specs: No spec, only legacy triple with getkeys-api" { - set reply [lindex [r command info kspec.nonewithgetkeys] 0] - # Verify (first, last, step) and movablekeys - assert_equal [lindex $reply 2] {module movablekeys} - assert_equal [lindex $reply 3] 1 - assert_equal [lindex $reply 4] -1 - assert_equal [lindex $reply 5] 2 - # Verify key-spec auto-generated from the legacy triple - set keyspecs [lindex $reply 8] - assert_equal [llength $keyspecs] 1 - assert_equal [lindex $keyspecs 0] {flags {RW access update variable_flags} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey -1 keystep 2 limit 0}}} - assert_equal [r command getkeys kspec.nonewithgetkeys key1 val1 key2 val2] {key1 key2} - } - - test "Module key specs: Two ranges" { - set reply [lindex [r command info kspec.tworanges] 0] - # Verify (first, last, step) and not movablekeys - assert_equal [lindex $reply 2] {module} - assert_equal [lindex $reply 3] 1 - assert_equal [lindex $reply 4] 2 - assert_equal [lindex $reply 5] 1 - # Verify key-specs - set keyspecs [lindex $reply 8] - assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [r command getkeys kspec.tworanges foo bar baz quux] {foo bar} - } - - test "Module key specs: Two ranges with gap" { - set reply [lindex [r command info kspec.tworangeswithgap] 0] - # Verify (first, last, step) and movablekeys - assert_equal [lindex $reply 2] {module movablekeys} - assert_equal [lindex $reply 3] 1 - assert_equal [lindex $reply 4] 1 - assert_equal [lindex $reply 5] 1 - # Verify key-specs - set keyspecs [lindex $reply 8] - assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type index spec {index 3}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [r command getkeys kspec.tworangeswithgap foo bar baz quux] {foo baz} - } - - test "Module key specs: Keyword-only spec clears the legacy triple" { - set reply [lindex [r command info kspec.keyword] 0] - # Verify (first, last, step) and movablekeys - assert_equal [lindex $reply 2] {module movablekeys} - assert_equal [lindex $reply 3] 0 - assert_equal [lindex $reply 4] 0 - assert_equal [lindex $reply 5] 0 - # Verify key-specs - set keyspecs [lindex $reply 8] - assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type keyword spec {keyword KEYS startfrom 1}} find_keys {type range spec {lastkey -1 keystep 1 limit 0}}} - assert_equal [r command getkeys kspec.keyword foo KEYS bar baz] {bar baz} - } - - test "Module key specs: Complex specs, case 1" { - set reply [lindex [r command info kspec.complex1] 0] - # Verify (first, last, step) and movablekeys - assert_equal [lindex $reply 2] {module movablekeys} - assert_equal [lindex $reply 3] 1 - assert_equal [lindex $reply 4] 1 - assert_equal [lindex $reply 5] 1 - # Verify key-specs - set keyspecs [lindex $reply 8] - assert_equal [lindex $keyspecs 0] {flags RO begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type keyword spec {keyword STORE startfrom 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 2] {flags {RO access} begin_search {type keyword spec {keyword KEYS startfrom 2}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}} - assert_equal [r command getkeys kspec.complex1 foo dummy KEYS 1 bar baz STORE quux] {foo quux bar} - } - - test "Module key specs: Complex specs, case 2" { - set reply [lindex [r command info kspec.complex2] 0] - # Verify (first, last, step) and movablekeys - assert_equal [lindex $reply 2] {module movablekeys} - assert_equal [lindex $reply 3] 1 - assert_equal [lindex $reply 4] 2 - assert_equal [lindex $reply 5] 1 - # Verify key-specs - set keyspecs [lindex $reply 8] - assert_equal [lindex $keyspecs 0] {flags {RW update} begin_search {type keyword spec {keyword STORE startfrom 5}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 1] {flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 2] {flags {RO access} begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} - assert_equal [lindex $keyspecs 3] {flags {RW update} begin_search {type index spec {index 3}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}} - assert_equal [lindex $keyspecs 4] {flags {RW update} begin_search {type keyword spec {keyword MOREKEYS startfrom 5}} find_keys {type range spec {lastkey -1 keystep 1 limit 0}}} - assert_equal [r command getkeys kspec.complex2 foo bar 2 baz quux banana STORE dst dummy MOREKEYS hey ho] {dst foo bar baz quux hey ho} - } - - test "Module command list filtering" { - ;# Note: we piggyback this tcl file to test the general functionality of command list filtering - set reply [r command list filterby module keyspecs] - assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.keyword kspec.none kspec.nonewithgetkeys kspec.tworanges kspec.tworangeswithgap} - assert_equal [r command getkeys kspec.complex2 foo bar 2 baz quux banana STORE dst dummy MOREKEYS hey ho] {dst foo bar baz quux hey ho} - } - - test {COMMAND GETKEYSANDFLAGS correctly reports module key-spec without flags} { - r command getkeysandflags kspec.none key1 val1 key2 val2 - } {{key1 {RW access update}} {key2 {RW access update}}} - - test {COMMAND GETKEYSANDFLAGS correctly reports module key-spec with flags} { - r command getkeysandflags kspec.nonewithgetkeys key1 val1 key2 val2 - } {{key1 {RO access}} {key2 {RO access}}} - - test {COMMAND GETKEYSANDFLAGS correctly reports module key-spec flags} { - r command getkeysandflags kspec.keyword keys key1 key2 key3 - } {{key1 {RO access}} {key2 {RO access}} {key3 {RO access}}} - - # user that can only read from "read" keys, write to "write" keys, and read+write to "RW" keys - r ACL setuser testuser +@all %R~read* %W~write* %RW~rw* - - test "Module key specs: No spec, only legacy triple - ACL" { - # legacy triple didn't provide flags, so they require both read and write - assert_equal "OK" [r ACL DRYRUN testuser kspec.none rw val1] - assert_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN testuser kspec.none read val1] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN testuser kspec.none write val1] - } - - test "Module key specs: tworanges - ACL" { - assert_equal "OK" [r ACL DRYRUN testuser kspec.tworanges read write] - assert_equal "OK" [r ACL DRYRUN testuser kspec.tworanges rw rw] - assert_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN testuser kspec.tworanges rw read] - assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN testuser kspec.tworanges write rw] - } - - foreach cmd {kspec.none kspec.tworanges} { - test "$cmd command will not be marked with movablekeys" { - set info [lindex [r command info $cmd] 0] - assert_no_match {*movablekeys*} [lindex $info 2] - } - } - - foreach cmd {kspec.keyword kspec.complex1 kspec.complex2 kspec.nonewithgetkeys} { - test "$cmd command is marked with movablekeys" { - set info [lindex [r command info $cmd] 0] - assert_match {*movablekeys*} [lindex $info 2] - } - } - - test "Unload the module - keyspecs" { - assert_equal {OK} [r module unload keyspecs] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/list.tcl b/examples/redis-unstable/tests/unit/moduleapi/list.tcl deleted file mode 100644 index 5f7532c..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/list.tcl +++ /dev/null @@ -1,225 +0,0 @@ -set testmodule [file normalize tests/modules/list.so] - -# The following arguments can be passed to args: -# i -- the number of inserts -# d -- the number of deletes -# r -- the number of replaces -# index -- the last index -# entry -- The entry pointed to by index -proc verify_list_edit_reply {reply argv} { - foreach {k v} $argv { - assert_equal [dict get $reply $k] $v - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module list set, get, insert, delete} { - r del k - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r list.set k 1 xyz} - r rpush k x - # insert, set, get - r list.insert k 0 foo - r list.insert k -1 bar - r list.set k 1 xyz - assert_equal {foo xyz bar} [r list.getall k] - assert_equal {foo} [r list.get k 0] - assert_equal {xyz} [r list.get k 1] - assert_equal {bar} [r list.get k 2] - assert_equal {bar} [r list.get k -1] - assert_equal {foo} [r list.get k -3] - assert_error {ERR index out*} {r list.get k -4} - assert_error {ERR index out*} {r list.get k 3} - # remove - assert_error {ERR index out*} {r list.delete k -4} - assert_error {ERR index out*} {r list.delete k 3} - r list.delete k 0 - r list.delete k -1 - assert_equal {xyz} [r list.getall k] - # removing the last element deletes the list - r list.delete k 0 - assert_equal 0 [r exists k] - } - - test {Module list iteration} { - r del k - r rpush k x y z - assert_equal {x y z} [r list.getall k] - assert_equal {z y x} [r list.getall k REVERSE] - } - - test {Module list insert & delete} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k ikikdi foo bar baz] {i 3 index 5} - r list.getall k - } {foo x bar y baz} - - test {Module list insert & delete, neg index} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k REVERSE ikikdi foo bar baz] {i 3 index -6} - r list.getall k - } {baz y bar z foo} - - test {Module list set while iterating} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k rkr foo bar] {r 2 index 3} - r list.getall k - } {foo y bar} - - test {Module list set while iterating, neg index} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k reverse rkr foo bar] {r 2 index -4} - r list.getall k - } {bar y foo} - - test {Module list - encoding conversion while inserting} { - r config set list-max-listpack-size 4 - r del k - r rpush k a b c d - assert_encoding listpack k - - # Converts to quicklist after inserting. - r list.edit k dii foo bar - assert_encoding quicklist k - assert_equal [r list.getall k] {foo bar b c d} - - # Converts to listpack after deleting three entries. - r list.edit k ddd e - assert_encoding listpack k - assert_equal [r list.getall k] {c d} - } - - test {Module list - encoding conversion while replacing} { - r config set list-max-listpack-size -1 - r del k - r rpush k x y z - assert_encoding listpack k - - # Converts to quicklist after replacing. - set big [string repeat "x" 4096] - r list.edit k r $big - assert_encoding quicklist k - assert_equal [r list.getall k] "$big y z" - - # Converts to listpack after deleting the big entry. - r list.edit k d - assert_encoding listpack k - assert_equal [r list.getall k] {y z} - } - - test {Module list - list entry and index should be updated when deletion} { - set original_config [config_get_set list-max-listpack-size 1] - - # delete from start (index 0) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l dd] {d 2 index 0 entry z} - assert_equal [r list.getall l] {z} - - # delete from start (index -3) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l reverse kkd] {d 1 index -3} - assert_equal [r list.getall l] {y z} - - # # delete from tail (index 2) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l kkd] {d 1 index 2} - assert_equal [r list.getall l] {x y} - - # # delete from tail (index -1) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l reverse dd] {d 2 index -1 entry x} - assert_equal [r list.getall l] {x} - - # # delete from middle (index 1) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l kdd] {d 2 index 1} - assert_equal [r list.getall l] {x} - - # # delete from middle (index -2) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l reverse kdd] {d 2 index -2} - assert_equal [r list.getall l] {z} - - config_set list-max-listpack-size $original_config - } - - test {Module list - KEYSIZES is updated as expected} { - proc run_cmd_verify_hist {cmd expOutput {retries 1}} { - proc K {} {return [string map { "db0_distrib_lists_items" "db0_LIST" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]} - uplevel 1 $cmd - wait_for_condition 50 $retries { - $expOutput eq [K] - } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" } - } - - r select 0 - - # RedisModule_ListPush & RedisModule_ListDelete - run_cmd_verify_hist {r flushall} {} - run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} - run_cmd_verify_hist {r list.insert L1 0 bla} {db0_LIST:2=1} - run_cmd_verify_hist {r list.delete L1 0} {db0_LIST:1=1} - run_cmd_verify_hist {r list.delete L1 0} {} - - - # RedisModule_ListSet & RedisModule_ListDelete - run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} - run_cmd_verify_hist {r list.set L1 0 bar} {db0_LIST:1=1} - run_cmd_verify_hist {r list.set L1 0 baz} {db0_LIST:1=1} - run_cmd_verify_hist {r list.delete L1 0} {} - - # Check lazy expire - r debug set-active-expire 0 - run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} - run_cmd_verify_hist {r pexpire L1 1} {db0_LIST:1=1} - run_cmd_verify_hist {after 5} {db0_LIST:1=1} - r debug set-active-expire 1 - run_cmd_verify_hist {after 5} {} 50 - } - - test "Unload the module - list" { - assert_equal {OK} [r module unload list] - } -} - -# A basic test that exercises a module's list commands under cluster mode. -# Currently, many module commands are never run even once in a clustered setup. -# This test helps ensure that basic module functionality works correctly and that -# the KEYSIZES histogram remains accurate and that insert & delete was tested. -set testmodule [file normalize tests/modules/list.so] -set modules [list loadmodule $testmodule] -start_cluster 2 2 [list tags {external:skip cluster modules} config_lines [list loadmodule $testmodule enable-debug-command yes]] { - test "Module list - KEYSIZES is updated correctly in cluster mode" { - for {set srvid -2} {$srvid <= 0} {incr srvid} { - set instance [srv $srvid client] - # Assert consistency after each command - $instance DEBUG KEYSIZES-HIST-ASSERT 1 - - for {set i 0} {$i < 50} {incr i} { - for {set j 0} {$j < 4} {incr j} { - catch {$instance list.insert "list:$i" $j "item:$j"} e - if {![string match "OK" $e]} {assert_match "*MOVED*" $e} - } - } - for {set i 0} {$i < 50} {incr i} { - for {set j 0} {$j < 4} {incr j} { - catch {$instance list.delete "list:$i" 0} e - if {![string match "OK" $e]} {assert_match "*MOVED*" $e} - } - } - # Verify also that instance is responsive and didn't crash on assert - assert_equal [$instance dbsize] 0 - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl b/examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl deleted file mode 100644 index f5c4fb3..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl +++ /dev/null @@ -1,21 +0,0 @@ -set testmodule [file normalize tests/modules/mallocsize.so] - - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {MallocSize of raw bytes} { - assert_equal [r mallocsize.setraw key 40] {OK} - assert_morethan [r memory usage key] 40 - } - - test {MallocSize of string} { - assert_equal [r mallocsize.setstr key abcdefg] {OK} - assert_morethan [r memory usage key] 7 ;# Length of "abcdefg" - } - - test {MallocSize of dict} { - assert_equal [r mallocsize.setdict key f1 v1 f2 v2] {OK} - assert_morethan [r memory usage key] 8 ;# Length of "f1v1f2v2" - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/misc.tcl b/examples/redis-unstable/tests/unit/moduleapi/misc.tcl deleted file mode 100644 index b51fffb..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/misc.tcl +++ /dev/null @@ -1,617 +0,0 @@ -set testmodule [file normalize tests/modules/misc.so] - -start_server {overrides {save {900 1}} tags {"modules external:skip"}} { - r module load $testmodule - - test {test RM_Call} { - set info [r test.call_info commandstats] - # cmdstat is not in a default section, so we also test an argument was passed - assert { [string match "*cmdstat_module*" $info] } - } - - test {test RM_Call args array} { - set info [r test.call_generic info commandstats] - # cmdstat is not in a default section, so we also test an argument was passed - assert { [string match "*cmdstat_module*" $info] } - } - - test {test RM_Call recursive} { - set info [r test.call_generic test.call_generic info commandstats] - assert { [string match "*cmdstat_module*" $info] } - } - - test {test redis version} { - set version [s redis_version] - assert_equal $version [r test.redisversion] - } - - test {test long double conversions} { - set ld [r test.ld_conversion] - assert {[string match $ld "0.00000000000000001"]} - } - - test {test unsigned long long conversions} { - set ret [r test.ull_conversion] - assert {[string match $ret "ok"]} - } - - test {test module db commands} { - r set x foo - set key [r test.randomkey] - assert_equal $key "x" - assert_equal [r test.dbsize] 1 - r test.flushall - assert_equal [r test.dbsize] 0 - } - - test {test RedisModule_ResetDataset do not reset functions} { - r function load {#!lua name=lib - redis.register_function('test', function() return 1 end) - } - assert_equal [r function list] {{library_name lib engine LUA functions {{name test description {} flags {}}}}} - r test.flushall - assert_equal [r function list] {{library_name lib engine LUA functions {{name test description {} flags {}}}}} - r function flush - } - - test {test module keyexists} { - r set x foo - assert_equal 1 [r test.keyexists x] - r del x - assert_equal 0 [r test.keyexists x] - } - - test {test module lru api} { - r config set maxmemory-policy allkeys-lru - r set x foo - set lru [r test.getlru x] - assert { $lru <= 1000 } - set was_set [r test.setlru x 100000] - assert { $was_set == 1 } - set idle [r object idletime x] - assert { $idle >= 100 } - set lru [r test.getlru x] - assert { $lru >= 100000 } - r config set maxmemory-policy allkeys-lfu - set lru [r test.getlru x] - assert { $lru == -1 } - set was_set [r test.setlru x 100000] - assert { $was_set == 0 } - } - r config set maxmemory-policy allkeys-lru - - test {test module lfu api} { - r config set maxmemory-policy allkeys-lfu - r set x foo - set lfu [r test.getlfu x] - assert { $lfu >= 1 } - set was_set [r test.setlfu x 100] - assert { $was_set == 1 } - set freq [r object freq x] - assert { $freq <= 100 } - set lfu [r test.getlfu x] - assert { $lfu <= 100 } - r config set maxmemory-policy allkeys-lru - set lfu [r test.getlfu x] - assert { $lfu == -1 } - set was_set [r test.setlfu x 100] - assert { $was_set == 0 } - } - - test {test module clientinfo api} { - # Test basic sanity and SSL flag - set info [r test.clientinfo] - set ssl_flag [expr $::tls ? {"ssl:"} : {":"}] - - assert { [dict get $info db] == 9 } - assert { [dict get $info flags] == "${ssl_flag}::::" } - - # Test MULTI flag - r multi - r test.clientinfo - set info [lindex [r exec] 0] - assert { [dict get $info flags] == "${ssl_flag}::::multi" } - - # Test TRACKING flag - r client tracking on - set info [r test.clientinfo] - assert { [dict get $info flags] == "${ssl_flag}::tracking::" } - r CLIENT TRACKING off - } - - test {tracking with rm_call sanity} { - set rd_trk [redis_client] - $rd_trk HELLO 3 - $rd_trk CLIENT TRACKING on - r MSET key1{t} 1 key2{t} 1 - - # GET triggers tracking, SET does not - $rd_trk test.rm_call GET key1{t} - $rd_trk test.rm_call SET key2{t} 2 - r MSET key1{t} 2 key2{t} 2 - assert_equal {invalidate key1{t}} [$rd_trk read] - assert_equal "PONG" [$rd_trk ping] - $rd_trk close - } - - test {tracking with rm_call with script} { - set rd_trk [redis_client] - $rd_trk HELLO 3 - $rd_trk CLIENT TRACKING on - r MSET key1{t} 1 key2{t} 1 - - # GET triggers tracking, SET does not - $rd_trk test.rm_call EVAL "redis.call('get', 'key1{t}')" 2 key1{t} key2{t} - r MSET key1{t} 2 key2{t} 2 - assert_equal {invalidate key1{t}} [$rd_trk read] - assert_equal "PONG" [$rd_trk ping] - $rd_trk close - } - - test {RM_SignalModifiedKey - tracking invalidation} { - set rd_trk [redis_client] - $rd_trk HELLO 3 - $rd_trk CLIENT TRACKING on - r SET mykey{t} abc - - # Track the key by reading it - $rd_trk GET mykey{t} - - # # Modify the key using module command that calls RM_SignalModifiedKey - r test.signalmodifiedkey mykey{t} - - # # Should receive invalidation message - assert_equal {invalidate mykey{t}} [$rd_trk read] - assert_equal "PONG" [$rd_trk ping] - $rd_trk close - } - - test {RM_SignalModifiedKey - update LRM timestamp} { - set old_policy [config_get_set maxmemory-policy allkeys-lrm] - r SET mykey{t} abc - after 2000 - assert_morethan_equal [r object idletime mykey{t}] 1 - - # LRM should be updated. - r test.signalmodifiedkey mykey{t} - assert_lessthan [r object idletime mykey{t}] 2 - r config set maxmemory-policy $old_policy - } {OK} {slow} - - test {publish to self inside rm_call} { - r hello 3 - r subscribe foo - - # published message comes after the response of the command that issued it. - assert_equal [r test.rm_call publish foo bar] {1} - assert_equal [r read] {message foo bar} - - r unsubscribe foo - r hello 2 - set _ "" - } {} {resp3} - - test {test module get/set client name by id api} { - catch { r test.getname } e - assert_equal "-ERR No name" $e - r client setname nobody - catch { r test.setname "name with spaces" } e - assert_match "*Invalid argument*" $e - assert_equal nobody [r client getname] - assert_equal nobody [r test.getname] - r test.setname somebody - assert_equal somebody [r client getname] - } - - test {test module getclientcert api} { - set cert [r test.getclientcert] - - if {$::tls} { - assert {$cert != ""} - } else { - assert {$cert == ""} - } - } - - test {test detached thread safe cnotext} { - r test.log_tsctx "info" "Test message" - verify_log_message 0 "* Test message*" 0 - } - - test {test RM_Call CLIENT INFO} { - assert_match "*fd=-1*" [r test.call_generic client info] - } - - test {Unsafe command names are sanitized in INFO output} { - r test.weird:cmd - set info [r info commandstats] - assert_match {*cmdstat_test.weird_cmd:calls=1*} $info - } - - test {test monotonic time} { - set x [r test.monotonic_time] - assert { [r test.monotonic_time] >= $x } - } - - test {rm_call OOM} { - r config set maxmemory 1 - r config set maxmemory-policy volatile-lru - - # sanity test plain call - assert_equal {OK} [ - r test.rm_call set x 1 - ] - - # add the M flag - assert_error {OOM *} { - r test.rm_call_flags M set x 1 - - } - - # test a non deny-oom command - assert_equal {1} [ - r test.rm_call_flags M get x - ] - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test {rm_call clear OOM} { - r config set maxmemory 1 - - # verify rm_call fails with OOM - assert_error {OOM *} { - r test.rm_call_flags M set x 1 - } - - # clear OOM state - r config set maxmemory 0 - - # test set command is allowed - r test.rm_call_flags M set x 1 - } {OK} {needs:config-maxmemory} - - test {rm_call OOM Eval} { - r config set maxmemory 1 - r config set maxmemory-policy volatile-lru - - # use the M flag without allow-oom shebang flag - assert_error {OOM *} { - r test.rm_call_flags M eval {#!lua - redis.call('set','x',1) - return 1 - } 1 x - } - - # add the M flag with allow-oom shebang flag - assert_equal {1} [ - r test.rm_call_flags M eval {#!lua flags=allow-oom - redis.call('set','x',1) - return 1 - } 1 x - ] - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test {rm_call write flag} { - # add the W flag - assert_error {ERR Write command 'set' was called while write is not allowed.} { - r test.rm_call_flags W set x 1 - } - - # test a non deny-oom command - r test.rm_call_flags W get x - } {1} - - test {rm_call EVAL} { - r test.rm_call eval { - redis.call('set','x',1) - return 1 - } 1 x - - assert_error {ERR Write commands are not allowed from read-only scripts.*} { - r test.rm_call eval {#!lua flags=no-writes - redis.call('set','x',1) - return 1 - } 1 x - } - } - - # Note: each script is unique, to check that flags are extracted correctly - test {rm_call EVAL - OOM - with M flag} { - r config set maxmemory 1 - - # script without shebang, but uses SET, so fails - assert_error {*OOM command not allowed when used memory > 'maxmemory'*} { - r test.rm_call_flags M eval { - redis.call('set','x',1) - return 1 - } 1 x - } - - # script with an allow-oom flag, succeeds despite using SET - r test.rm_call_flags M eval {#!lua flags=allow-oom - redis.call('set','x', 1) - return 2 - } 1 x - - # script with no-writes flag, implies allow-oom, succeeds - r test.rm_call_flags M eval {#!lua flags=no-writes - redis.call('get','x') - return 2 - } 1 x - - # script with shebang using default flags, so fails regardless of using only GET - assert_error {*OOM command not allowed when used memory > 'maxmemory'*} { - r test.rm_call_flags M eval {#!lua - redis.call('get','x') - return 3 - } 1 x - } - - # script without shebang, but uses GET, so succeeds - r test.rm_call_flags M eval { - redis.call('get','x') - return 4 - } 1 x - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - # All RM_Call for script succeeds in OOM state without using the M flag - test {rm_call EVAL - OOM - without M flag} { - r config set maxmemory 1 - - # no shebang at all - r test.rm_call eval { - redis.call('set','x',1) - return 6 - } 1 x - - # Shebang without flags - r test.rm_call eval {#!lua - redis.call('set','x', 1) - return 7 - } 1 x - - # with allow-oom flag - r test.rm_call eval {#!lua flags=allow-oom - redis.call('set','x', 1) - return 8 - } 1 x - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test "not enough good replicas" { - r set x "some value" - r config set min-replicas-to-write 1 - - # rm_call in script mode - assert_error {NOREPLICAS *} {r test.rm_call_flags S set x s} - - assert_equal [ - r test.rm_call eval {#!lua flags=no-writes - return redis.call('get','x') - } 1 x - ] "some value" - - assert_equal [ - r test.rm_call eval { - return redis.call('get','x') - } 1 x - ] "some value" - - assert_error {NOREPLICAS *} { - r test.rm_call eval {#!lua - return redis.call('get','x') - } 1 x - } - - assert_error {NOREPLICAS *} { - r test.rm_call eval { - return redis.call('set','x', 1) - } 1 x - } - - r config set min-replicas-to-write 0 - } - - test {rm_call EVAL - read-only replica} { - r replicaof 127.0.0.1 1 - - # rm_call in script mode - assert_error {READONLY *} {r test.rm_call_flags S set x 1} - - assert_error {READONLY You can't write against a read only replica. script*} { - r test.rm_call eval { - redis.call('set','x',1) - return 1 - } 1 x - } - - r test.rm_call eval {#!lua flags=no-writes - redis.call('get','x') - return 2 - } 1 x - - assert_error {READONLY Can not run script with write flag on readonly replica*} { - r test.rm_call eval {#!lua - redis.call('get','x') - return 3 - } 1 x - } - - r test.rm_call eval { - redis.call('get','x') - return 4 - } 1 x - - r replicaof no one - } {OK} {needs:config-maxmemory} - - test {rm_call EVAL - stale replica} { - r replicaof 127.0.0.1 1 - r config set replica-serve-stale-data no - - # rm_call in script mode - assert_error {MASTERDOWN *} { - r test.rm_call_flags S get x - } - - assert_error {MASTERDOWN *} { - r test.rm_call eval {#!lua flags=no-writes - redis.call('get','x') - return 2 - } 1 x - } - - assert_error {MASTERDOWN *} { - r test.rm_call eval { - redis.call('get','x') - return 4 - } 1 x - } - - r replicaof no one - r config set replica-serve-stale-data yes - } {OK} {needs:config-maxmemory} - - test "rm_call EVAL - failed bgsave prevents writes" { - r config set rdb-key-save-delay 10000000 - populate 1000 - r set x x - r bgsave - set pid1 [get_child_pid 0] - catch {exec kill -9 $pid1} - waitForBgsave r - - # make sure a read command succeeds - assert_equal [r get x] x - - # make sure a write command fails - assert_error {MISCONF *} {r set x y} - - # rm_call in script mode - assert_error {MISCONF *} {r test.rm_call_flags S set x 1} - - # repeate with script - assert_error {MISCONF *} {r test.rm_call eval { - return redis.call('set','x',1) - } 1 x - } - assert_equal {x} [r test.rm_call eval { - return redis.call('get','x') - } 1 x - ] - - # again with script using shebang - assert_error {MISCONF *} {r test.rm_call eval {#!lua - return redis.call('set','x',1) - } 1 x - } - assert_equal {x} [r test.rm_call eval {#!lua flags=no-writes - return redis.call('get','x') - } 1 x - ] - - r config set rdb-key-save-delay 0 - r bgsave - waitForBgsave r - - # server is writable again - r set x y - } {OK} - - test "malloc API" { - assert_equal {OK} [r test.malloc_api 0] - } - - test "Cluster keyslot" { - assert_equal 12182 [r test.keyslot foo] - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {test Dry Run - OK OOM/ACL} { - set x 5 - r set x $x - catch {r test.rm_call_flags DMC set x 10} e - assert_match {*NULL reply returned*} $e - assert_equal [r get x] 5 - } - - test {test Dry Run - Fail OOM} { - set x 5 - r set x $x - r config set maxmemory 1 - catch {r test.rm_call_flags DM set x 10} e - assert_match {*OOM*} $e - assert_equal [r get x] $x - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test {test Dry Run - Fail ACL} { - set x 5 - r set x $x - # deny all permissions besides the dryrun command - r acl setuser default resetkeys - - catch {r test.rm_call_flags DC set x 10} e - assert_match {*NOPERM No permissions to access a key*} $e - r acl setuser default +@all ~* - assert_equal [r get x] $x - } - - test {test silent open key} { - r debug set-active-expire 0 - r test.clear_n_events - r set x 1 PX 10 - after 1000 - # now the key has been expired, open it silently and make sure not event were fired. - assert_error {key not found} {r test.silent_open_key x} - assert_equal {0} [r test.get_n_events] - } - -if {[string match {*jemalloc*} [s mem_allocator]]} { - test {test RM_Call with large arg for SET command} { - # set a big value to trigger increasing the query buf - r set foo [string repeat A 100000] - # set a smaller value but > PROTO_MBULK_BIG_ARG (32*1024) Redis will try to save the query buf itself on the DB. - r test.call_generic set bar [string repeat A 33000] - # asset the value was trimmed - assert {[r memory usage bar] < 42000}; # 42K to count for Jemalloc's additional memory overhead. - } -} ;# if jemalloc - - test "Unload the module - misc" { - assert_equal {OK} [r module unload misc] - } -} - -start_server {tags {"modules external:skip"}} { - test {Detect incompatible operations in cluster mode for module} { - r config set cluster-compatibility-sample-ratio 100 - set incompatible_ops [s cluster_incompatible_ops] - - # since test.no_cluster_cmd and its subcommand have 'no-cluster' flag, - # they should not be counted as incompatible ops, increment the counter by 2 - r module load $testmodule - assert_equal [expr $incompatible_ops+2] [s cluster_incompatible_ops] - - # incompatible_cluster_cmd is similar with MSET, check if it is counted as - # incompatible ops with different number of keys - # only 1 key, should not increment the counter - r test.incompatible_cluster_cmd foo bar - assert_equal [expr $incompatible_ops+2] [s cluster_incompatible_ops] - # 2 cross slot keys, should increment the counter - r test.incompatible_cluster_cmd foo bar bar foo - assert_equal [expr $incompatible_ops+3] [s cluster_incompatible_ops] - # 2 non cross slot keys, should not increment the counter - r test.incompatible_cluster_cmd foo bar bar{foo} bar - assert_equal [expr $incompatible_ops+3] [s cluster_incompatible_ops] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl b/examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl deleted file mode 100644 index 8de6670..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl +++ /dev/null @@ -1,405 +0,0 @@ -set testmodule [file normalize tests/modules/auth.so] -set testmoduletwo [file normalize tests/modules/moduleauthtwo.so] -set miscmodule [file normalize tests/modules/misc.so] - -proc cmdstat {cmd} { - return [cmdrstat $cmd r] -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - r module load $testmoduletwo - - set hello2_response [r HELLO 2] - set hello3_response [r HELLO 3] - - test {test registering module auth callbacks} { - assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb] - assert_equal {OK} [r testmoduletwo.rm_register_auth_cb] - assert_equal {OK} [r testmoduleone.rm_register_auth_cb] - } - - test {test module AUTH for non existing / disabled users} { - r config resetstat - # Validate that an error is thrown for non existing users. - assert_error {*WRONGPASS*} {r AUTH foo pwd} - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - # Validate that an error is thrown for disabled users. - r acl setuser foo >pwd off ~* &* +@all - assert_error {*WRONGPASS*} {r AUTH foo pwd} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdstat auth] - } - - test {test non blocking module AUTH} { - r config resetstat - # Test for a fixed password user - r acl setuser foo >pwd on ~* &* +@all - assert_equal {OK} [r AUTH foo allow] - assert_error {*Auth denied by Misc Module*} {r AUTH foo deny} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - assert_error {*WRONGPASS*} {r AUTH foo nomatch} - assert_match {*calls=3,*,rejected_calls=0,failed_calls=2} [cmdstat auth] - assert_equal {OK} [r AUTH foo pwd] - # Test for No Pass user - r acl setuser foo on ~* &* +@all nopass - assert_equal {OK} [r AUTH foo allow] - assert_error {*Auth denied by Misc Module*} {r AUTH foo deny} - assert_match {*calls=6,*,rejected_calls=0,failed_calls=3} [cmdstat auth] - assert_equal {OK} [r AUTH foo nomatch] - - # Validate that the Module added an ACL Log entry. - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry username] eq {foo}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry reason] eq {auth}} - assert {[dict get $entry object] eq {Module Auth}} - assert_match {*cmd=auth*} [dict get $entry client-info] - r ACL LOG RESET - } - - test {test non blocking module HELLO AUTH} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - # Validate proto 2 and 3 in case of success - assert_equal $hello2_response [r HELLO 2 AUTH foo pwd] - assert_equal $hello2_response [r HELLO 2 AUTH foo allow] - assert_equal $hello3_response [r HELLO 3 AUTH foo pwd] - assert_equal $hello3_response [r HELLO 3 AUTH foo allow] - # Validate denying AUTH for the HELLO cmd - assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo deny} - assert_match {*calls=5,*,rejected_calls=0,failed_calls=1} [cmdstat hello] - assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch} - assert_match {*calls=6,*,rejected_calls=0,failed_calls=2} [cmdstat hello] - assert_error {*Auth denied by Misc Module*} {r HELLO 3 AUTH foo deny} - assert_match {*calls=7,*,rejected_calls=0,failed_calls=3} [cmdstat hello] - assert_error {*WRONGPASS*} {r HELLO 3 AUTH foo nomatch} - assert_match {*calls=8,*,rejected_calls=0,failed_calls=4} [cmdstat hello] - - # Validate that the Module added an ACL Log entry. - set entry [lindex [r ACL LOG] 1] - assert {[dict get $entry username] eq {foo}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry reason] eq {auth}} - assert {[dict get $entry object] eq {Module Auth}} - assert_match {*cmd=hello*} [dict get $entry client-info] - r ACL LOG RESET - } - - test {test non blocking module HELLO AUTH SETNAME} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - # Validate clientname is set on success - assert_equal $hello2_response [r HELLO 2 AUTH foo pwd setname client1] - assert {[r client getname] eq {client1}} - assert_equal $hello2_response [r HELLO 2 AUTH foo allow setname client2] - assert {[r client getname] eq {client2}} - # Validate clientname is not updated on failure - r client setname client0 - assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo deny setname client1} - assert {[r client getname] eq {client0}} - assert_match {*calls=3,*,rejected_calls=0,failed_calls=1} [cmdstat hello] - assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch setname client2} - assert {[r client getname] eq {client0}} - assert_match {*calls=4,*,rejected_calls=0,failed_calls=2} [cmdstat hello] - } - - test {test blocking module AUTH} { - r config resetstat - # Test for a fixed password user - r acl setuser foo >pwd on ~* &* +@all - assert_equal {OK} [r AUTH foo block_allow] - assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - assert_error {*WRONGPASS*} {r AUTH foo nomatch} - assert_match {*calls=3,*,rejected_calls=0,failed_calls=2} [cmdstat auth] - assert_equal {OK} [r AUTH foo pwd] - # Test for No Pass user - r acl setuser foo on ~* &* +@all nopass - assert_equal {OK} [r AUTH foo block_allow] - assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny} - assert_match {*calls=6,*,rejected_calls=0,failed_calls=3} [cmdstat auth] - assert_equal {OK} [r AUTH foo nomatch] - # Validate that every Blocking AUTH command took at least 500000 usec. - set stats [cmdstat auth] - regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call - assert {$usec_per_call >= 500000} - - # Validate that the Module added an ACL Log entry. - set entry [lindex [r ACL LOG] 0] - assert {[dict get $entry username] eq {foo}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry reason] eq {auth}} - assert {[dict get $entry object] eq {Module Auth}} - assert_match {*cmd=auth*} [dict get $entry client-info] - r ACL LOG RESET - } - - test {test blocking module HELLO AUTH} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - # validate proto 2 and 3 in case of success - assert_equal $hello2_response [r HELLO 2 AUTH foo pwd] - assert_equal $hello2_response [r HELLO 2 AUTH foo block_allow] - assert_equal $hello3_response [r HELLO 3 AUTH foo pwd] - assert_equal $hello3_response [r HELLO 3 AUTH foo block_allow] - # validate denying AUTH for the HELLO cmd - assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo block_deny} - assert_match {*calls=5,*,rejected_calls=0,failed_calls=1} [cmdstat hello] - assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch} - assert_match {*calls=6,*,rejected_calls=0,failed_calls=2} [cmdstat hello] - assert_error {*Auth denied by Misc Module*} {r HELLO 3 AUTH foo block_deny} - assert_match {*calls=7,*,rejected_calls=0,failed_calls=3} [cmdstat hello] - assert_error {*WRONGPASS*} {r HELLO 3 AUTH foo nomatch} - assert_match {*calls=8,*,rejected_calls=0,failed_calls=4} [cmdstat hello] - # Validate that every HELLO AUTH command took at least 500000 usec. - set stats [cmdstat hello] - regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call - assert {$usec_per_call >= 500000} - - # Validate that the Module added an ACL Log entry. - set entry [lindex [r ACL LOG] 1] - assert {[dict get $entry username] eq {foo}} - assert {[dict get $entry context] eq {module}} - assert {[dict get $entry reason] eq {auth}} - assert {[dict get $entry object] eq {Module Auth}} - assert_match {*cmd=hello*} [dict get $entry client-info] - r ACL LOG RESET - } - - test {test blocking module HELLO AUTH SETNAME} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - # Validate clientname is set on success - assert_equal $hello2_response [r HELLO 2 AUTH foo pwd setname client1] - assert {[r client getname] eq {client1}} - assert_equal $hello2_response [r HELLO 2 AUTH foo block_allow setname client2] - assert {[r client getname] eq {client2}} - # Validate clientname is not updated on failure - r client setname client0 - assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo block_deny setname client1} - assert {[r client getname] eq {client0}} - assert_match {*calls=3,*,rejected_calls=0,failed_calls=1} [cmdstat hello] - assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch setname client2} - assert {[r client getname] eq {client0}} - assert_match {*calls=4,*,rejected_calls=0,failed_calls=2} [cmdstat hello] - # Validate that every HELLO AUTH SETNAME command took at least 500000 usec. - set stats [cmdstat hello] - regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call - assert {$usec_per_call >= 500000} - } - - test {test AUTH after registering multiple module auth callbacks} { - r config resetstat - - # Register two more callbacks from the same module. - assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb] - assert_equal {OK} [r testmoduleone.rm_register_auth_cb] - - # Register another module auth callback from the second module. - assert_equal {OK} [r testmoduletwo.rm_register_auth_cb] - - r acl setuser foo >pwd on ~* &* +@all - - # Case 1 - Non Blocking Success - assert_equal {OK} [r AUTH foo allow] - - # Case 2 - Non Blocking Deny - assert_error {*Auth denied by Misc Module*} {r AUTH foo deny} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - - r config resetstat - - # Case 3 - Blocking Success - assert_equal {OK} [r AUTH foo block_allow] - - # Case 4 - Blocking Deny - assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - - # Validate that every Blocking AUTH command took at least 500000 usec. - set stats [cmdstat auth] - regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call - assert {$usec_per_call >= 500000} - - r config resetstat - - # Case 5 - Non Blocking Success via the second module. - assert_equal {OK} [r AUTH foo allow_two] - - # Case 6 - Non Blocking Deny via the second module. - assert_error {*Auth denied by Misc Module*} {r AUTH foo deny_two} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - - r config resetstat - - # Case 7 - All four auth callbacks "Skip" by not explicitly allowing or denying. - assert_error {*WRONGPASS*} {r AUTH foo nomatch} - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - assert_equal {OK} [r AUTH foo pwd] - - # Because we had to attempt all 4 callbacks, validate that the AUTH command took at least - # 1000000 usec (each blocking callback takes 500000 usec). - set stats [cmdstat auth] - regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call - assert {$usec_per_call >= 1000000} - } - - test {module auth during blocking module auth} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - set rd [redis_deferring_client] - set rd_two [redis_deferring_client] - - # Attempt blocking module auth. While this ongoing, attempt non blocking module auth from - # moduleone/moduletwo and start another blocking module auth from another deferring client. - $rd AUTH foo block_allow - wait_for_blocked_clients_count 1 - assert_equal {OK} [r AUTH foo allow] - assert_equal {OK} [r AUTH foo allow_two] - # Validate that the non blocking module auth cmds finished before any blocking module auth. - set info_clients [r info clients] - assert_match "*blocked_clients:1*" $info_clients - $rd_two AUTH foo block_allow - - # Validate that all of the AUTH commands succeeded. - wait_for_blocked_clients_count 0 500 10 - $rd flush - assert_equal [$rd read] "OK" - $rd_two flush - assert_equal [$rd_two read] "OK" - assert_match {*calls=4,*,rejected_calls=0,failed_calls=0} [cmdstat auth] - } - - test {module auth inside MULTI EXEC} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - - # Validate that non blocking module auth inside MULTI succeeds. - r multi - r AUTH foo allow - assert_equal {OK} [r exec] - - # Validate that blocking module auth inside MULTI throws an err. - r multi - r AUTH foo block_allow - assert_error {*ERR Blocking module command called from transaction*} {r exec} - assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - } - - test {Disabling Redis User during blocking module auth} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - set rd [redis_deferring_client] - - # Attempt blocking module auth and disable the Redis user while module auth is in progress. - $rd AUTH foo pwd - wait_for_blocked_clients_count 1 - r acl setuser foo >pwd off ~* &* +@all - - # Validate that module auth failed. - wait_for_blocked_clients_count 0 500 10 - $rd flush - assert_error {*WRONGPASS*} { $rd read } - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - } - - test {Killing a client in the middle of blocking module auth} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - set rd [redis_deferring_client] - $rd client id - set cid [$rd read] - - # Attempt blocking module auth command on client `cid` and kill the client while module auth - # is in progress. - $rd AUTH foo pwd - wait_for_blocked_clients_count 1 - r client kill id $cid - - # Validate that the blocked client count goes to 0 and no AUTH command is tracked. - wait_for_blocked_clients_count 0 500 10 - $rd flush - assert_error {*I/O error reading reply*} { $rd read } - assert_match {} [cmdstat auth] - } - - test {test RM_AbortBlock Module API during blocking module auth} { - r config resetstat - r acl setuser foo >pwd on ~* &* +@all - - # Attempt module auth. With the "block_abort" as the password, the "testacl.so" module - # blocks the client and uses the RM_AbortBlock API. This should result in module auth - # failing and the client being unblocked with the default AUTH err message. - assert_error {*WRONGPASS*} {r AUTH foo block_abort} - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth] - } - - test {test RM_RegisterAuthCallback Module API during blocking module auth} { - r config resetstat - r acl setuser foo >defaultpwd on ~* &* +@all - set rd [redis_deferring_client] - - # Start the module auth attempt with the standard Redis auth password for the user. This - # will result in all module auth cbs attempted and then standard Redis auth will be tried. - $rd AUTH foo defaultpwd - wait_for_blocked_clients_count 1 - - # Validate that we allow modules to register module auth cbs while module auth is already - # in progress. - assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb] - assert_equal {OK} [r testmoduletwo.rm_register_auth_cb] - - # Validate that blocking module auth succeeds. - wait_for_blocked_clients_count 0 500 10 - $rd flush - assert_equal [$rd read] "OK" - set stats [cmdstat auth] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} $stats - - # Validate that even the new blocking module auth cb which was registered in the middle of - # blocking module auth is attempted - making it take twice the duration (2x 500000 us). - regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call - assert {$usec_per_call >= 1000000} - } - - test {Module unload during blocking module auth} { - r config resetstat - r module load $miscmodule - set rd [redis_deferring_client] - r acl setuser foo >pwd on ~* &* +@all - - # Start a blocking module auth attempt. - $rd AUTH foo block_allow - wait_for_blocked_clients_count 1 - - # moduleone and moduletwo have module auth cbs registered. Because blocking module auth is - # ongoing, they cannot be unloaded. - catch {r module unload testacl} e - assert_match {*the module has blocked clients*} $e - # The moduleauthtwo module can be unregistered because no client is blocked on it. - assert_equal "OK" [r module unload moduleauthtwo] - - # The misc module does not have module auth cbs registered, so it can be unloaded even when - # blocking module auth is ongoing. - assert_equal "OK" [r module unload misc] - - # Validate that blocking module auth succeeds. - wait_for_blocked_clients_count 0 500 10 - $rd flush - assert_equal [$rd read] "OK" - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat auth] - - # Validate that unloading the moduleauthtwo module does not unregister module auth cbs of - # of the testacl module. Module based auth should succeed. - assert_equal {OK} [r AUTH foo allow] - - # Validate that the testacl module can be unloaded since blocking module auth is done. - r module unload testacl - - # Validate that since all module auth cbs are unregistered, module auth attempts fail. - assert_error {*WRONGPASS*} {r AUTH foo block_allow} - assert_error {*WRONGPASS*} {r AUTH foo allow_two} - assert_error {*WRONGPASS*} {r AUTH foo allow} - assert_match {*calls=5,*,rejected_calls=0,failed_calls=3} [cmdstat auth] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl b/examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl deleted file mode 100644 index 25ed33a..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl +++ /dev/null @@ -1,386 +0,0 @@ -set testmodule [file normalize tests/modules/moduleconfigs.so] -set testmoduletwo [file normalize tests/modules/moduleconfigstwo.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - test {Config get commands work} { - # Make sure config get module config works - assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1 - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" - assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - - # Check un-prefixed and aliased configuration - assert_equal [r config get unprefix-bool] "unprefix-bool yes" - assert_equal [r config get unprefix-noalias-bool] "unprefix-noalias-bool yes" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes" - assert_equal [r config get unprefix.numeric] "unprefix.numeric -1" - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias -1" - assert_equal [r config get unprefix-string] "unprefix-string {secret unprefix}" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias {secret unprefix}" - assert_equal [r config get unprefix-enum] "unprefix-enum one" - assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one" - } - - test {Config set commands work} { - # Make sure that config sets work during runtime - r config set moduleconfigs.mutable_bool no - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no" - r config set moduleconfigs.memory_numeric 1mb - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576" - r config set moduleconfigs.string wafflewednesdays - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string wafflewednesdays" - set not_embstr [string repeat A 50] - r config set moduleconfigs.string $not_embstr - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string $not_embstr" - r config set moduleconfigs.string \x73\x75\x70\x65\x72\x20\x00\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64 - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" - r config set moduleconfigs.enum two - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" - r config set moduleconfigs.flags two - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags two" - r config set moduleconfigs.numeric -2 - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2" - - # Check un-prefixed and aliased configuration - r config set unprefix-bool no - assert_equal [r config get unprefix-bool] "unprefix-bool no" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias no" - r config set unprefix-bool-alias yes - assert_equal [r config get unprefix-bool] "unprefix-bool yes" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes" - r config set unprefix.numeric 5 - assert_equal [r config get unprefix.numeric] "unprefix.numeric 5" - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 5" - r config set unprefix.numeric-alias 6 - assert_equal [r config get unprefix.numeric] "unprefix.numeric 6" - r config set unprefix.string-alias "blabla" - assert_equal [r config get unprefix-string] "unprefix-string blabla" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias blabla" - r config set unprefix-enum two - assert_equal [r config get unprefix-enum] "unprefix-enum two" - assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias two" - } - - test {Config set commands enum flags} { - r config set moduleconfigs.flags "none" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags none" - - r config set moduleconfigs.flags "two four" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}" - - r config set moduleconfigs.flags "five" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags five" - - r config set moduleconfigs.flags "one four" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags five" - - r config set moduleconfigs.flags "one two four" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {five two}" - } - - test {Immutable flag works properly and rejected strings dont leak} { - # Configs flagged immutable should not allow sets - catch {[r config set moduleconfigs.immutable_bool yes]} e - assert_match {*can't set immutable config*} $e - catch {[r config set moduleconfigs.string rejectisfreed]} e - assert_match {*Cannot set string to 'rejectisfreed'*} $e - } - - test {Numeric limits work properly} { - # Configs over/under the limit shouldn't be allowed, and memory configs should only take memory values - catch {[r config set moduleconfigs.memory_numeric 200gb]} e - assert_match {*argument must be between*} $e - catch {[r config set moduleconfigs.memory_numeric -5]} e - assert_match {*argument must be a memory value*} $e - catch {[r config set moduleconfigs.numeric -10]} e - assert_match {*argument must be between*} $e - } - - test {Enums only able to be set to passed in values} { - # Module authors specify what values are valid for enums, check that only those values are ok on a set - catch {[r config set moduleconfigs.enum asdf]} e - assert_match {*must be one of the following*} $e - } - - test {test blocking of config registration and load outside of OnLoad} { - assert_equal [r block.register.configs.outside.onload] OK - } - - test {Unload removes module configs} { - r module unload moduleconfigs - assert_equal [r config get moduleconfigs.*] "" - r module load $testmodule - # these should have reverted back to their module specified values - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" - assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - - # Check un-prefixed and aliased configuration - assert_equal [r config get unprefix-bool] "unprefix-bool yes" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes" - assert_equal [r config get unprefix.numeric] "unprefix.numeric -1" - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias -1" - assert_equal [r config get unprefix-string] "unprefix-string {secret unprefix}" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias {secret unprefix}" - assert_equal [r config get unprefix-enum] "unprefix-enum one" - assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one" - - - r module unload moduleconfigs - } - - test {test loadex functionality} { - r module loadex $testmodule CONFIG moduleconfigs.mutable_bool no \ - CONFIG moduleconfigs.immutable_bool yes \ - CONFIG moduleconfigs.memory_numeric 2mb \ - CONFIG moduleconfigs.string tclortickle \ - CONFIG unprefix-bool no \ - CONFIG unprefix.numeric-alias 123 \ - CONFIG unprefix-string abc_def \ - - assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1 - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no" - assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool yes" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string tclortickle" - # Configs that were not changed should still be their module specified value - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - - # Check un-prefixed and aliased configuration - assert_equal [r config get unprefix-bool] "unprefix-bool no" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias no" - assert_equal [r config get unprefix.numeric] "unprefix.numeric 123" - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 123" - assert_equal [r config get unprefix-string] "unprefix-string abc_def" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias abc_def" - assert_equal [r config get unprefix-enum] "unprefix-enum one" - assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one" - - - } - - test {apply function works} { - catch {[r config set moduleconfigs.mutable_bool yes]} e - assert_match {*Bool configs*} $e - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no" - catch {[r config set moduleconfigs.memory_numeric 1000 moduleconfigs.numeric 1000]} e - assert_match {*cannot equal*} $e - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - r module unload moduleconfigs - } - - test {test double config argument to loadex} { - r module loadex $testmodule CONFIG moduleconfigs.mutable_bool yes \ - CONFIG moduleconfigs.mutable_bool no \ - CONFIG unprefix.numeric-alias 1 \ - CONFIG unprefix.numeric-alias 2 \ - CONFIG unprefix-string blabla - - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no" - # Check un-prefixed and aliased configuration - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 2" - assert_equal [r config get unprefix.numeric] "unprefix.numeric 2" - assert_equal [r config get unprefix-string] "unprefix-string blabla" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias blabla" - r module unload moduleconfigs - } - - test {missing loadconfigs call} { - catch {[r module loadex $testmodule CONFIG moduleconfigs.string "cool" ARGS noload]} e - assert_match {*ERR*} $e - } - - test {test loadex rejects bad configs} { - # Bad config 200gb is over the limit - catch {[r module loadex $testmodule CONFIG moduleconfigs.memory_numeric 200gb ARGS]} e - assert_match {*ERR*} $e - # We should completely remove all configs on a failed load - assert_equal [r config get moduleconfigs.*] "" - # No value for config, should error out - catch {[r module loadex $testmodule CONFIG moduleconfigs.mutable_bool CONFIG moduleconfigs.enum two ARGS]} e - assert_match {*ERR*} $e - assert_equal [r config get moduleconfigs.*] "" - # Asan will catch this if this string is not freed - catch {[r module loadex $testmodule CONFIG moduleconfigs.string rejectisfreed]} - assert_match {*ERR*} $e - assert_equal [r config get moduleconfigs.*] "" - # test we can't set random configs - catch {[r module loadex $testmodule CONFIG maxclients 333]} - assert_match {*ERR*} $e - assert_equal [r config get moduleconfigs.*] "" - assert_not_equal [r config get maxclients] "maxclients 333" - # test we can't set other module's configs - r module load $testmoduletwo - catch {[r module loadex $testmodule CONFIG configs.test no]} - assert_match {*ERR*} $e - assert_equal [r config get configs.test] "configs.test yes" - r module unload configs - # Verify config name and its alias being used together gets failed - catch {[r module loadex $testmodule CONFIG unprefix.numeric 1 CONFIG unprefix.numeric-alias 1]} - assert_match {*ERR*} $e - } - - test {test config rewrite with dynamic load} { - #translates to: super \0secret password - r module loadex $testmodule CONFIG moduleconfigs.string \x73\x75\x70\x65\x72\x20\x00\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64 ARGS - assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1 - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" - r config set moduleconfigs.mutable_bool yes - r config set moduleconfigs.memory_numeric 750 - r config set moduleconfigs.enum two - r config set moduleconfigs.flags "four two" - r config set unprefix-bool-alias no - r config set unprefix.numeric 456 - r config set unprefix.string-alias "unprefix" - r config set unprefix-enum two - r config rewrite - restart_server 0 true false - # Ensure configs we rewrote are present and that the conf file is readable - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 750" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - - # Check unprefixed configuration and alias - assert_equal [r config get unprefix-bool] "unprefix-bool no" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias no" - assert_equal [r config get unprefix.numeric] "unprefix.numeric 456" - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 456" - assert_equal [r config get unprefix-string] "unprefix-string unprefix" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias unprefix" - assert_equal [r config get unprefix-enum] "unprefix-enum two" - assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias two" - - r module unload moduleconfigs - } - - test {test multiple modules with configs} { - r module load $testmodule - r module loadex $testmoduletwo CONFIG configs.test yes - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" - assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - assert_equal [r config get configs.test] "configs.test yes" - r config set moduleconfigs.mutable_bool no - r config set moduleconfigs.string nice - r config set moduleconfigs.enum two - r config set configs.test no - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string nice" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" - assert_equal [r config get configs.test] "configs.test no" - r config rewrite - # test we can load from conf file with multiple different modules. - restart_server 0 true false - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string nice" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" - assert_equal [r config get configs.test] "configs.test no" - r module unload moduleconfigs - r module unload configs - } - - test {test 1.module load 2.config rewrite 3.module unload 4.config rewrite works} { - # Configs need to be removed from the old config file in this case. - r module loadex $testmodule CONFIG moduleconfigs.memory_numeric 500 ARGS - assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1 - r config rewrite - r module unload moduleconfigs - r config rewrite - restart_server 0 true false - # Ensure configs we rewrote are no longer present - assert_equal [r config get moduleconfigs.*] "" - } - test {startup moduleconfigs} { - # No loadmodule directive - catch {exec src/redis-server --moduleconfigs.string "hello"} err - assert_match {*Module Configuration detected without loadmodule directive or no ApplyConfig call: aborting*} $err - - # Bad config value - catch {exec src/redis-server --loadmodule "$testmodule" --moduleconfigs.string "rejectisfreed"} err - assert_match {*Issue during loading of configuration moduleconfigs.string : Cannot set string to 'rejectisfreed'*} $err - - # missing LoadConfigs call - catch {exec src/redis-server --loadmodule "$testmodule" noload --moduleconfigs.string "hello"} err - assert_match {*Module Configurations were not set, missing LoadConfigs call. Unloading the module.*} $err - - # successful - start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two moduleconfigs.flags "two four"] tags {"external:skip"}] { - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string bootedup" - assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" - assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no" - assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" - assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}" - assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" - - # Check un-prefixed and aliased configuration - assert_equal [r config get unprefix-bool] "unprefix-bool yes" - assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes" - assert_equal [r config get unprefix.numeric] "unprefix.numeric -1" - assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias -1" - assert_equal [r config get unprefix-string] "unprefix-string {secret unprefix}" - assert_equal [r config get unprefix.string-alias] "unprefix.string-alias {secret unprefix}" - assert_equal [r config get unprefix-enum] "unprefix-enum one" - assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one" - } - } - - test {loadmodule CONFIG values take precedence over module loadex ARGS values} { - # Load module with conflicting CONFIG and ARGS values - r module loadex $testmodule \ - CONFIG moduleconfigs.string goo \ - CONFIG moduleconfigs.memory_numeric 2mb \ - ARGS override-default - - # Verify CONFIG values took precedence over the values that override-default would have caused the module to set - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string goo" - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152" - - r module unload moduleconfigs - } - - # Test: Ensure that modified configuration values from ARGS are correctly written to the config file - test {Modified ARGS values are persisted after config rewrite when set through CONFIG commands} { - # Load module with non-default ARGS values - r module loadex $testmodule ARGS override-default - - # Verify the initial values were overwritten - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 123" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string foo" - - # Set new values to simulate user configuration changes - r config set moduleconfigs.memory_numeric 1mb - r config set moduleconfigs.string "modified_value" - - # Verify that the changes took effect - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string modified_value" - - # Perform a config rewrite - r config rewrite - - restart_server 0 true false - assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576" - assert_equal [r config get moduleconfigs.string] "moduleconfigs.string modified_value" - r module unload moduleconfigs - } -} - diff --git a/examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl b/examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl deleted file mode 100644 index 31a4669..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl +++ /dev/null @@ -1,219 +0,0 @@ -set testmodule [file normalize tests/modules/postnotifications.so] - -tags "modules external:skip" { - start_server {} { - r module load $testmodule with_key_events - - test {Test write on post notification callback} { - set repl [attach_to_replication_stream] - - r set string_x 1 - assert_equal {1} [r get string_changed{string_x}] - assert_equal {1} [r get string_total] - - r set string_x 2 - assert_equal {2} [r get string_changed{string_x}] - assert_equal {2} [r get string_total] - - # the {lpush before_overwritten string_x} is a post notification job registered when 'string_x' was overwritten - assert_replication_stream $repl { - {multi} - {select *} - {set string_x 1} - {incr string_changed{string_x}} - {incr string_total} - {exec} - {multi} - {set string_x 2} - {lpush before_overwritten string_x} - {incr string_changed{string_x}} - {incr string_total} - {exec} - } - close_replication_stream $repl - } - - test {Test write on post notification callback from module thread} { - r flushall - set repl [attach_to_replication_stream] - - assert_equal {OK} [r postnotification.async_set] - assert_equal {1} [r get string_changed{string_x}] - assert_equal {1} [r get string_total] - - assert_replication_stream $repl { - {multi} - {select *} - {set string_x 1} - {incr string_changed{string_x}} - {incr string_total} - {exec} - } - close_replication_stream $repl - } - - test {Test active expire} { - r flushall - set repl [attach_to_replication_stream] - - r set x 1 - r pexpire x 10 - - wait_for_condition 100 50 { - [r keys expired] == {expired} - } else { - puts [r keys *] - fail "Failed waiting for x to expired" - } - - # the {lpush before_expired x} is a post notification job registered before 'x' got expired - assert_replication_stream $repl { - {select *} - {set x 1} - {pexpireat x *} - {multi} - {del x} - {lpush before_expired x} - {incr expired} - {exec} - } - close_replication_stream $repl - } - - test {Test lazy expire} { - r flushall - r DEBUG SET-ACTIVE-EXPIRE 0 - set repl [attach_to_replication_stream] - - r set x 1 - r pexpire x 1 - after 10 - assert_equal {} [r get x] - - # the {lpush before_expired x} is a post notification job registered before 'x' got expired - assert_replication_stream $repl { - {select *} - {set x 1} - {pexpireat x *} - {multi} - {del x} - {lpush before_expired x} - {incr expired} - {exec} - } - close_replication_stream $repl - r DEBUG SET-ACTIVE-EXPIRE 1 - } {OK} {needs:debug} - - test {Test lazy expire inside post job notification} { - r flushall - r DEBUG SET-ACTIVE-EXPIRE 0 - set repl [attach_to_replication_stream] - - r set x 1 - r pexpire x 1 - after 10 - assert_equal {OK} [r set read_x 1] - - # the {lpush before_expired x} is a post notification job registered before 'x' got expired - assert_replication_stream $repl { - {select *} - {set x 1} - {pexpireat x *} - {multi} - {set read_x 1} - {del x} - {lpush before_expired x} - {incr expired} - {exec} - } - close_replication_stream $repl - r DEBUG SET-ACTIVE-EXPIRE 1 - } {OK} {needs:debug} - - test {Test nested keyspace notification} { - r flushall - set repl [attach_to_replication_stream] - - assert_equal {OK} [r set write_sync_write_sync_x 1] - - assert_replication_stream $repl { - {multi} - {select *} - {set x 1} - {set write_sync_x 1} - {set write_sync_write_sync_x 1} - {exec} - } - close_replication_stream $repl - } - - test {Test eviction} { - r flushall - set repl [attach_to_replication_stream] - r set x 1 - r config set maxmemory-policy allkeys-random - r config set maxmemory 1 - - assert_error {OOM *} {r set y 1} - - # the {lpush before_evicted x} is a post notification job registered before 'x' got evicted - assert_replication_stream $repl { - {select *} - {set x 1} - {multi} - {del x} - {lpush before_evicted x} - {incr evicted} - {exec} - } - close_replication_stream $repl - } {} {needs:config-maxmemory} - } -} - -set testmodule2 [file normalize tests/modules/keyspace_events.so] - -tags "modules external:skip" { - start_server {} { - r module load $testmodule with_key_events - r module load $testmodule2 - test {Test write on post notification callback} { - set repl [attach_to_replication_stream] - - r set string_x 1 - assert_equal {1} [r get string_changed{string_x}] - assert_equal {1} [r get string_total] - - r set string_x 2 - assert_equal {2} [r get string_changed{string_x}] - assert_equal {2} [r get string_total] - - r set string1_x 1 - assert_equal {1} [r get string_changed{string1_x}] - assert_equal {3} [r get string_total] - - # the {lpush before_overwritten string_x} is a post notification job registered before 'string_x' got overwritten - assert_replication_stream $repl { - {multi} - {select *} - {set string_x 1} - {incr string_changed{string_x}} - {incr string_total} - {exec} - {multi} - {set string_x 2} - {lpush before_overwritten string_x} - {incr string_changed{string_x}} - {incr string_total} - {exec} - {multi} - {set string1_x 1} - {incr string_changed{string1_x}} - {incr string_total} - {exec} - } - close_replication_stream $repl - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/propagate.tcl b/examples/redis-unstable/tests/unit/moduleapi/propagate.tcl deleted file mode 100644 index d3d08d9..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/propagate.tcl +++ /dev/null @@ -1,806 +0,0 @@ -set testmodule [file normalize tests/modules/propagate.so] -set miscmodule [file normalize tests/modules/misc.so] -set keyspace_events [file normalize tests/modules/keyspace_events.so] - -tags "modules external:skip" { - test {Modules can propagate in async and threaded contexts} { - start_server [list overrides [list loadmodule "$testmodule"]] { - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - $replica module load $keyspace_events - start_server [list overrides [list loadmodule "$testmodule"]] { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - $master module load $keyspace_events - - # Start the replication process... - $replica replicaof $master_host $master_port - wait_for_sync $replica - after 1000 - - test {module propagates from timer} { - set repl [attach_to_replication_stream] - - $master propagate-test.timer - - wait_for_condition 500 10 { - [$replica get timer] eq "3" - } else { - fail "The two counters don't match the expected value." - } - - assert_replication_stream $repl { - {select *} - {incr timer} - {incr timer} - {incr timer} - } - close_replication_stream $repl - } - - test {module propagation with notifications} { - set repl [attach_to_replication_stream] - - $master set x y - - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {set x y} - {exec} - } - close_replication_stream $repl - } - - test {module propagation with notifications with multi} { - set repl [attach_to_replication_stream] - - $master multi - $master set x1 y1 - $master set x2 y2 - $master exec - - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {set x1 y1} - {incr notifications} - {set x2 y2} - {exec} - } - close_replication_stream $repl - } - - test {module propagation with notifications with active-expire} { - $master debug set-active-expire 1 - set repl [attach_to_replication_stream] - - $master set asdf1 1 PX 300 - $master set asdf2 2 PX 300 - $master set asdf3 3 PX 300 - - wait_for_condition 500 10 { - [$replica keys asdf*] eq {} - } else { - fail "Not all keys have expired" - } - - # Note whenever there's double notification: SET with PX issues two separate - # notifications: one for "set" and one for "expire" - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {incr notifications} - {set asdf1 1 PXAT *} - {exec} - {multi} - {incr notifications} - {incr notifications} - {set asdf2 2 PXAT *} - {exec} - {multi} - {incr notifications} - {incr notifications} - {set asdf3 3 PXAT *} - {exec} - {multi} - {incr notifications} - {incr notifications} - {incr testkeyspace:expired} - {del asdf*} - {exec} - {multi} - {incr notifications} - {incr notifications} - {incr testkeyspace:expired} - {del asdf*} - {exec} - {multi} - {incr notifications} - {incr notifications} - {incr testkeyspace:expired} - {del asdf*} - {exec} - } - close_replication_stream $repl - - $master debug set-active-expire 0 - } - - test {module propagation with notifications with eviction case 1} { - $master flushall - $master set asdf1 1 - $master set asdf2 2 - $master set asdf3 3 - - $master config set maxmemory-policy allkeys-random - $master config set maxmemory 1 - - # Please note the following loop: - # We evict a key and send a notification, which does INCR on the "notifications" key, so - # that every time we evict any key, "notifications" key exist (it happens inside the - # performEvictions loop). So even evicting "notifications" causes INCR on "notifications". - # If maxmemory_eviction_tenacity would have been set to 100 this would be an endless loop, but - # since the default is 10, at some point the performEvictions loop would end. - # Bottom line: "notifications" always exists and we can't really determine the order of evictions - # This test is here only for sanity - - # The replica will get the notification with multi exec and we have a generic notification handler - # that performs `RedisModule_Call(ctx, "INCR", "c", "multi");` if the notification is inside multi exec. - # so we will have 2 keys, "notifications" and "multi". - wait_for_condition 500 10 { - [$replica dbsize] eq 2 - } else { - fail "Not all keys have been evicted" - } - - $master config set maxmemory 0 - $master config set maxmemory-policy noeviction - } - - test {module propagation with notifications with eviction case 2} { - $master flushall - set repl [attach_to_replication_stream] - - $master set asdf1 1 EX 300 - $master set asdf2 2 EX 300 - $master set asdf3 3 EX 300 - - # Please note we use volatile eviction to prevent the loop described in the test above. - # "notifications" is not volatile so it always remains - $master config resetstat - $master config set maxmemory-policy volatile-ttl - $master config set maxmemory 1 - - wait_for_condition 500 10 { - [s evicted_keys] eq 3 - } else { - fail "Not all keys have been evicted" - } - - $master config set maxmemory 0 - $master config set maxmemory-policy noeviction - - $master set asdf4 4 - - # Note whenever there's double notification: SET with EX issues two separate - # notifications: one for "set" and one for "expire" - # Note that although CONFIG SET maxmemory is called in this flow (see issue #10014), - # eviction will happen and will not induce propagation of the CONFIG command (see #10019). - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {incr notifications} - {set asdf1 1 PXAT *} - {exec} - {multi} - {incr notifications} - {incr notifications} - {set asdf2 2 PXAT *} - {exec} - {multi} - {incr notifications} - {incr notifications} - {set asdf3 3 PXAT *} - {exec} - {multi} - {incr notifications} - {del asdf*} - {exec} - {multi} - {incr notifications} - {del asdf*} - {exec} - {multi} - {incr notifications} - {del asdf*} - {exec} - {multi} - {incr notifications} - {set asdf4 4} - {exec} - } - close_replication_stream $repl - } - - test {module propagation with timer and CONFIG SET maxmemory} { - set repl [attach_to_replication_stream] - - $master config resetstat - $master config set maxmemory-policy volatile-random - - $master propagate-test.timer-maxmemory - - # Wait until the volatile keys are evicted - wait_for_condition 500 10 { - [s evicted_keys] eq 2 - } else { - fail "Not all keys have been evicted" - } - - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {incr notifications} - {set timer-maxmemory-volatile-start 1 PXAT *} - {incr timer-maxmemory-middle} - {incr notifications} - {incr notifications} - {set timer-maxmemory-volatile-end 1 PXAT *} - {exec} - {multi} - {incr notifications} - {del timer-maxmemory-volatile-*} - {exec} - {multi} - {incr notifications} - {del timer-maxmemory-volatile-*} - {exec} - } - close_replication_stream $repl - - $master config set maxmemory 0 - $master config set maxmemory-policy noeviction - } - - test {module propagation with timer and EVAL} { - set repl [attach_to_replication_stream] - - $master propagate-test.timer-eval - - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {incrby timer-eval-start 1} - {incr notifications} - {set foo bar} - {incr timer-eval-middle} - {incr notifications} - {incrby timer-eval-end 1} - {exec} - } - close_replication_stream $repl - } - - test {module propagates nested ctx case1} { - set repl [attach_to_replication_stream] - - $master propagate-test.timer-nested - - wait_for_condition 500 10 { - [$replica get timer-nested-end] eq "1" - } else { - fail "The two counters don't match the expected value." - } - - assert_replication_stream $repl { - {multi} - {select *} - {incrby timer-nested-start 1} - {incrby timer-nested-end 1} - {exec} - } - close_replication_stream $repl - - # Note propagate-test.timer-nested just propagates INCRBY, causing an - # inconsistency, so we flush - $master flushall - } - - test {module propagates nested ctx case2} { - set repl [attach_to_replication_stream] - - $master propagate-test.timer-nested-repl - - wait_for_condition 500 10 { - [$replica get timer-nested-end] eq "1" - } else { - fail "The two counters don't match the expected value." - } - - assert_replication_stream $repl { - {multi} - {select *} - {incrby timer-nested-start 1} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr counter-3} - {incr counter-4} - {incr notifications} - {incr after-call} - {incr notifications} - {incr before-call-2} - {incr notifications} - {incr asdf} - {incr notifications} - {del asdf} - {incr notifications} - {incr after-call-2} - {incr notifications} - {incr timer-nested-middle} - {incrby timer-nested-end 1} - {exec} - } - close_replication_stream $repl - - # Note propagate-test.timer-nested-repl just propagates INCRBY, causing an - # inconsistency, so we flush - $master flushall - } - - test {module propagates from thread} { - set repl [attach_to_replication_stream] - - $master propagate-test.thread - - wait_for_condition 500 10 { - [$replica get a-from-thread] eq "3" - } else { - fail "The two counters don't match the expected value." - } - - assert_replication_stream $repl { - {multi} - {select *} - {incr a-from-thread} - {incr notifications} - {incr thread-call} - {incr b-from-thread} - {exec} - {multi} - {incr a-from-thread} - {incr notifications} - {incr thread-call} - {incr b-from-thread} - {exec} - {multi} - {incr a-from-thread} - {incr notifications} - {incr thread-call} - {incr b-from-thread} - {exec} - } - close_replication_stream $repl - } - - test {module propagates from thread with detached ctx} { - set repl [attach_to_replication_stream] - - $master propagate-test.detached-thread - - wait_for_condition 500 10 { - [$replica get thread-detached-after] eq "1" - } else { - fail "The key doesn't match the expected value." - } - - assert_replication_stream $repl { - {multi} - {select *} - {incr thread-detached-before} - {incr notifications} - {incr thread-detached-1} - {incr notifications} - {incr thread-detached-2} - {incr thread-detached-after} - {exec} - } - close_replication_stream $repl - } - - test {module propagates from command} { - set repl [attach_to_replication_stream] - - $master propagate-test.simple - $master propagate-test.mixed - - assert_replication_stream $repl { - {multi} - {select *} - {incr counter-1} - {incr counter-2} - {exec} - {multi} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr notifications} - {incr after-call} - {exec} - } - close_replication_stream $repl - } - - test {module propagates from EVAL} { - set repl [attach_to_replication_stream] - - assert_equal [ $master eval { \ - redis.call("propagate-test.simple"); \ - redis.call("set", "x", "y"); \ - redis.call("propagate-test.mixed"); return "OK" } 0 ] {OK} - - assert_replication_stream $repl { - {multi} - {select *} - {incr counter-1} - {incr counter-2} - {incr notifications} - {set x y} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr notifications} - {incr after-call} - {exec} - } - close_replication_stream $repl - } - - test {module propagates from command after good EVAL} { - set repl [attach_to_replication_stream] - - assert_equal [ $master eval { return "hello" } 0 ] {hello} - $master propagate-test.simple - $master propagate-test.mixed - - assert_replication_stream $repl { - {multi} - {select *} - {incr counter-1} - {incr counter-2} - {exec} - {multi} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr notifications} - {incr after-call} - {exec} - } - close_replication_stream $repl - } - - test {module propagates from command after bad EVAL} { - set repl [attach_to_replication_stream] - - catch { $master eval { return "hello" } -12 } e - assert_equal $e {ERR Number of keys can't be negative} - $master propagate-test.simple - $master propagate-test.mixed - - assert_replication_stream $repl { - {multi} - {select *} - {incr counter-1} - {incr counter-2} - {exec} - {multi} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr notifications} - {incr after-call} - {exec} - } - close_replication_stream $repl - } - - test {module propagates from multi-exec} { - set repl [attach_to_replication_stream] - - $master multi - $master propagate-test.simple - $master propagate-test.mixed - $master propagate-test.timer-nested-repl - $master exec - - wait_for_condition 500 10 { - [$replica get timer-nested-end] eq "1" - } else { - fail "The two counters don't match the expected value." - } - - assert_replication_stream $repl { - {multi} - {select *} - {incr counter-1} - {incr counter-2} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr notifications} - {incr after-call} - {exec} - {multi} - {incrby timer-nested-start 1} - {incr notifications} - {incr using-call} - {incr counter-1} - {incr counter-2} - {incr counter-3} - {incr counter-4} - {incr notifications} - {incr after-call} - {incr notifications} - {incr before-call-2} - {incr notifications} - {incr asdf} - {incr notifications} - {del asdf} - {incr notifications} - {incr after-call-2} - {incr notifications} - {incr timer-nested-middle} - {incrby timer-nested-end 1} - {exec} - } - close_replication_stream $repl - - # Note propagate-test.timer-nested just propagates INCRBY, causing an - # inconsistency, so we flush - $master flushall - } - - test {module RM_Call of expired key propagation} { - $master debug set-active-expire 0 - - $master set k1 900 px 100 - after 110 - - set repl [attach_to_replication_stream] - $master propagate-test.incr k1 - - assert_replication_stream $repl { - {multi} - {select *} - {del k1} - {propagate-test.incr k1} - {exec} - } - close_replication_stream $repl - - assert_equal [$master get k1] 1 - assert_equal [$master ttl k1] -1 - - wait_for_condition 50 100 { - [$replica get k1] eq 1 && - [$replica ttl k1] eq -1 - } else { - fail "failed RM_Call of expired key propagation" - } - } - - test {module notification on set} { - set repl [attach_to_replication_stream] - - $master SADD s foo - - wait_for_condition 500 10 { - [$replica SCARD s] eq "1" - } else { - fail "Failed to wait for set to be replicated" - } - - $master SPOP s 1 - - wait_for_condition 500 10 { - [$replica SCARD s] eq "0" - } else { - fail "Failed to wait for set to be replicated" - } - - # Currently the `del` command comes after the notification. - # When we fix spop to fire notification at the end (like all other commands), - # the `del` will come first. - assert_replication_stream $repl { - {multi} - {select *} - {incr notifications} - {sadd s foo} - {exec} - {multi} - {incr notifications} - {incr notifications} - {del s} - {exec} - } - close_replication_stream $repl - } - - test {module key miss notification do not cause read command to be replicated} { - set repl [attach_to_replication_stream] - - $master flushall - - $master get unexisting_key - - wait_for_condition 500 10 { - [$replica get missed] eq "1" - } else { - fail "Failed to wait for set to be replicated" - } - - # Test is checking a wrong!!! behavior that causes a read command to be replicated to replica/aof. - # We keep the test to verify that such a wrong behavior does not cause any crashes. - assert_replication_stream $repl { - {select *} - {flushall} - {multi} - {incr notifications} - {incr missed} - {get unexisting_key} - {exec} - } - - close_replication_stream $repl - } - - test "Unload the module - propagate-test/testkeyspace" { - assert_equal {OK} [r module unload propagate-test] - assert_equal {OK} [r module unload testkeyspace] - } - - assert_equal [s -1 unexpected_error_replies] 0 - } - } - } -} - - -tags "modules aof external:skip" { - foreach aofload_type {debug_cmd startup} { - test "Modules RM_Replicate replicates MULTI/EXEC correctly: AOF-load type $aofload_type" { - start_server [list overrides [list loadmodule "$testmodule"]] { - # Enable the AOF - r config set appendonly yes - r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. - waitForBgrewriteaof r - - r propagate-test.simple - r propagate-test.mixed - r multi - r propagate-test.simple - r propagate-test.mixed - r exec - - assert_equal [r get counter-1] {} - assert_equal [r get counter-2] {} - assert_equal [r get using-call] 2 - assert_equal [r get after-call] 2 - assert_equal [r get notifications] 4 - - # Load the AOF - if {$aofload_type == "debug_cmd"} { - r debug loadaof - } else { - r config rewrite - restart_server 0 true false - wait_done_loading r - } - - # This module behaves bad on purpose, it only calls - # RM_Replicate for counter-1 and counter-2 so values - # after AOF-load are different - assert_equal [r get counter-1] 4 - assert_equal [r get counter-2] 4 - assert_equal [r get using-call] 2 - assert_equal [r get after-call] 2 - # 4+4+2+2 commands from AOF (just above) + 4 "INCR notifications" from AOF + 4 notifications for these INCRs - assert_equal [r get notifications] 20 - - assert_equal {OK} [r module unload propagate-test] - assert_equal [s 0 unexpected_error_replies] 0 - } - } - test "Modules RM_Call does not update stats during aof load: AOF-load type $aofload_type" { - start_server [list overrides [list loadmodule "$miscmodule"]] { - # Enable the AOF - r config set appendonly yes - r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. - waitForBgrewriteaof r - - r config resetstat - r set foo bar - r EVAL {return redis.call('SET', KEYS[1], ARGV[1])} 1 foo bar2 - r test.rm_call_replicate set foo bar3 - r EVAL {return redis.call('test.rm_call_replicate',ARGV[1],KEYS[1],ARGV[2])} 1 foo set bar4 - - r multi - r set foo bar5 - r EVAL {return redis.call('SET', KEYS[1], ARGV[1])} 1 foo bar6 - r test.rm_call_replicate set foo bar7 - r EVAL {return redis.call('test.rm_call_replicate',ARGV[1],KEYS[1],ARGV[2])} 1 foo set bar8 - r exec - - assert_match {*calls=8,*,rejected_calls=0,failed_calls=0} [cmdrstat set r] - - - # Load the AOF - if {$aofload_type == "debug_cmd"} { - r config resetstat - r debug loadaof - } else { - r config rewrite - restart_server 0 true false - wait_done_loading r - } - - assert_no_match {*calls=*} [cmdrstat set r] - - } - } - } -} - -# This test does not really test module functionality, but rather uses a module -# command to test Redis replication mechanisms. -test {Replicas that was marked as CLIENT_CLOSE_ASAP should not keep the replication backlog from been trimmed} { - start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] { - set replica [srv 0 client] - start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - $master config set client-output-buffer-limit "replica 10mb 5mb 0" - - # Start the replication process... - $replica replicaof $master_host $master_port - wait_for_sync $replica - - test {module propagates from timer} { - # Replicate large commands to make the replica disconnected. - $master write [format_command propagate-test.verbatim 100000 [string repeat "a" 1000]] ;# almost 100mb - # Execute this command together with module commands within the same - # event loop to prevent periodic cleanup of replication backlog. - $master write [format_command info memory] - $master flush - $master read ;# propagate-test.verbatim - set res [$master read] ;# info memory - - # Wait for the replica to be disconnected. - wait_for_log_messages 0 {"*flags=S*scheduled to be closed ASAP for overcoming of output buffer limits*"} 0 1500 10 - # Due to the replica reaching the soft limit (5MB), memory peaks should not significantly - # exceed the replica soft limit. Furthermore, as the replica release its reference to - # replication backlog, it should be properly trimmed, the memory usage of replication - # backlog should not significantly exceed repl-backlog-size (default 1MB). */ - assert_lessthan [getInfoProperty $res used_memory_peak] 20000000;# less than 20mb - assert_lessthan [getInfoProperty $res mem_replication_backlog] 2000000;# less than 2mb - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/publish.tcl b/examples/redis-unstable/tests/unit/moduleapi/publish.tcl deleted file mode 100644 index ef56b98..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/publish.tcl +++ /dev/null @@ -1,34 +0,0 @@ -set testmodule [file normalize tests/modules/publish.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {PUBLISH and SPUBLISH via a module} { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {1} [subscribe $rd2 {chan1}] - assert_equal 1 [r publish.shard chan1 hello] - assert_equal 1 [r publish.classic chan1 world] - assert_equal {smessage chan1 hello} [$rd1 read] - assert_equal {message chan1 world} [$rd2 read] - $rd1 close - $rd2 close - } - - test {module publish to self with multi message} { - r hello 3 - r subscribe foo - - # published message comes after the response of the command that issued it. - assert_equal [r publish.classic_multi foo bar vaz] {1 1} - assert_equal [r read] {message foo bar} - assert_equal [r read] {message foo vaz} - - r unsubscribe foo - r hello 2 - set _ "" - } {} {resp3} - -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl b/examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl deleted file mode 100644 index ba2cfd6..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl +++ /dev/null @@ -1,203 +0,0 @@ -set testmodule [file normalize tests/modules/rdbloadsave.so] - -start_server {tags {"modules external:skip debug_defrag:skip"}} { - r module load $testmodule - - test "Module rdbloadsave sanity" { - r test.sanity - - # Try to load non-existing file - assert_error {*No such file or directory*} {r test.rdbload sanity.rdb} - - r set x 1 - assert_equal OK [r test.rdbsave sanity.rdb] - - r flushdb - assert_equal OK [r test.rdbload sanity.rdb] - assert_equal 1 [r get x] - } - - test "Module rdbloadsave test with pipelining" { - r config set save "" - r config set loading-process-events-interval-bytes 1024 - r config set key-load-delay 50 - r flushdb - - populate 3000 a 1024 - r set x 111 - assert_equal [r dbsize] 3001 - - assert_equal OK [r test.rdbsave blabla.rdb] - r flushdb - assert_equal [r dbsize] 0 - - # Send commands with pipeline. First command will call RM_RdbLoad() in - # the command callback. While loading RDB, Redis can go to networking to - # reply -LOADING. By sending commands in pipeline, we verify it doesn't - # cause a problem. - # e.g. Redis won't try to process next message of the current client - # while it is in the command callback for that client . - set rd1 [redis_deferring_client] - $rd1 test.rdbload blabla.rdb - - wait_for_condition 50 100 { - [s loading] eq 1 - } else { - fail "Redis did not start loading or loaded RDB too fast" - } - - $rd1 get x - $rd1 dbsize - - assert_equal OK [$rd1 read] - assert_equal 111 [$rd1 read] - assert_equal 3001 [$rd1 read] - r flushdb - r config set key-load-delay 0 - } - - test "Module rdbloadsave with aof" { - r config set save "" - - # Enable the AOF - r config set appendonly yes - r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. - waitForBgrewriteaof r - - r set k v1 - assert_equal OK [r test.rdbsave aoftest.rdb] - - r set k v2 - r config set rdb-key-save-delay 10000000 - r bgrewriteaof - - # RM_RdbLoad() should kill aof fork - assert_equal OK [r test.rdbload aoftest.rdb] - - wait_for_condition 50 100 { - [string match {*Killing*AOF*child*} [exec tail -20 < [srv 0 stdout]]] - } else { - fail "Can't find 'Killing AOF child' in recent log lines" - } - - # Verify the value in the loaded rdb - assert_equal v1 [r get k] - - # Verify aof is still enabled after RM_RdbLoad() call - assert_equal 1 [s aof_enabled] - - r flushdb - r config set rdb-key-save-delay 0 - r config set appendonly no - } - - test "Module rdbloadsave with bgsave" { - r flushdb - r config set save "" - - r set k v1 - assert_equal OK [r test.rdbsave bgsave.rdb] - - r set k v2 - r config set rdb-key-save-delay 500000 - r bgsave - - # RM_RdbLoad() should kill RDB fork - assert_equal OK [r test.rdbload bgsave.rdb] - - wait_for_condition 10 1000 { - [string match {*Background*saving*terminated*} [exec tail -20 < [srv 0 stdout]]] - } else { - fail "Can't find 'Background saving terminated' in recent log lines" - } - - assert_equal v1 [r get k] - r flushall - waitForBgsave r - r config set rdb-key-save-delay 0 - } - - test "Module rdbloadsave calls rdbsave in a module fork" { - r flushdb - r config set save "" - r config set rdb-key-save-delay 500000 - - r set k v1 - - # Module will call RM_Fork() before calling RM_RdbSave() - assert_equal OK [r test.rdbsave_fork rdbfork.rdb] - assert_equal [s module_fork_in_progress] 1 - - wait_for_condition 10 1000 { - [status r module_fork_in_progress] == "0" - } else { - fail "Module fork didn't finish" - } - - r set k v2 - assert_equal OK [r test.rdbload rdbfork.rdb] - assert_equal v1 [r get k] - - r config set rdb-key-save-delay 0 - } - - test "Unload the module - rdbloadsave" { - assert_equal {OK} [r module unload rdbloadsave] - } - - tags {repl} { - test {Module rdbloadsave on master and replica} { - start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] { - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - - $master set x 10000 - - # Start the replication process... - $replica replicaof $master_host $master_port - - wait_for_condition 100 100 { - [status $master sync_full] == 1 - } else { - fail "Master <-> Replica didn't start the full sync" - } - - # RM_RdbSave() is allowed on replicas - assert_equal OK [$replica test.rdbsave rep.rdb] - - # RM_RdbLoad() is not allowed on replicas - assert_error {*supported*} {$replica test.rdbload rep.rdb} - - assert_equal OK [$master test.rdbsave master.rdb] - $master set x 20000 - - wait_for_condition 100 100 { - [$replica get x] == 20000 - } else { - fail "Replica didn't get the update" - } - - # Loading RDB on master will drop replicas - assert_equal OK [$master test.rdbload master.rdb] - - wait_for_condition 100 100 { - [status $master sync_full] == 2 - } else { - fail "Master <-> Replica didn't start the full sync" - } - - wait_for_condition 100 100 { - [$replica get x] == 10000 - } else { - fail "Replica didn't get the update" - } - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/reply.tcl b/examples/redis-unstable/tests/unit/moduleapi/reply.tcl deleted file mode 100644 index af012f2..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/reply.tcl +++ /dev/null @@ -1,152 +0,0 @@ -set testmodule [file normalize tests/modules/reply.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - # test all with hello 2/3 - for {set proto 2} {$proto <= 3} {incr proto} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$proto == 3} {continue} - } elseif {$::force_resp3} { - if {$proto == 2} {continue} - } - r hello $proto - - test "RESP$proto: RM_ReplyWithString: an string reply" { - # RedisString - set string [r rw.string "Redis"] - assert_equal "Redis" $string - # C string - set string [r rw.cstring] - assert_equal "A simple string" $string - } - - test "RESP$proto: RM_ReplyWithBigNumber: an string reply" { - assert_equal "123456778901234567890" [r rw.bignumber "123456778901234567890"] - } - - test "RESP$proto: RM_ReplyWithInt: an integer reply" { - assert_equal 42 [r rw.int 42] - } - - test "RESP$proto: RM_ReplyWithDouble: a float reply" { - assert_equal 3.141 [r rw.double 3.141] - } - - test "RESP$proto: RM_ReplyWithDouble: inf" { - if {$proto == 2} { - assert_equal "inf" [r rw.double inf] - assert_equal "-inf" [r rw.double -inf] - } else { - # TCL convert inf to different results on different platforms, e.g. inf on mac - # and Inf on others, so use readraw to verify the protocol - r readraw 1 - assert_equal ",inf" [r rw.double inf] - assert_equal ",-inf" [r rw.double -inf] - r readraw 0 - } - } - - test "RESP$proto: RM_ReplyWithDouble: NaN" { - if {$proto == 2} { - assert_equal "nan" [r rw.double 0 0] - assert_equal "nan" [r rw.double] - } else { - # TCL won't convert nan into a double, use readraw to verify the protocol - r readraw 1 - assert_equal ",nan" [r rw.double 0 0] - assert_equal ",nan" [r rw.double] - r readraw 0 - } - } - - set ld 0.00000000000000001 - test "RESP$proto: RM_ReplyWithLongDouble: a float reply" { - if {$proto == 2} { - # here the response gets to TCL as a string - assert_equal $ld [r rw.longdouble $ld] - } else { - # TCL doesn't support long double and the test infra converts it to a - # normal double which causes precision loss. so we use readraw instead - r readraw 1 - assert_equal ",$ld" [r rw.longdouble $ld] - r readraw 0 - } - } - - test "RESP$proto: RM_ReplyWithVerbatimString: a string reply" { - assert_equal "bla\nbla\nbla" [r rw.verbatim "bla\nbla\nbla"] - } - - test "RESP$proto: RM_ReplyWithArray: an array reply" { - assert_equal {0 1 2 3 4} [r rw.array 5] - } - - test "RESP$proto: RM_ReplyWithMap: an map reply" { - set res [r rw.map 3] - if {$proto == 2} { - assert_equal {0 0 1 1.5 2 3} $res - } else { - assert_equal [dict create 0 0.0 1 1.5 2 3.0] $res - } - } - - test "RESP$proto: RM_ReplyWithSet: an set reply" { - assert_equal {0 1 2} [r rw.set 3] - } - - test "RESP$proto: RM_ReplyWithAttribute: an set reply" { - if {$proto == 2} { - catch {[r rw.attribute 3]} e - assert_match "Attributes aren't supported by RESP 2" $e - } else { - r readraw 1 - set res [r rw.attribute 3] - assert_equal [r read] {:0} - assert_equal [r read] {,0} - assert_equal [r read] {:1} - assert_equal [r read] {,1.5} - assert_equal [r read] {:2} - assert_equal [r read] {,3} - assert_equal [r read] {+OK} - r readraw 0 - } - } - - test "RESP$proto: RM_ReplyWithBool: a boolean reply" { - assert_equal {0 1} [r rw.bool] - } - - test "RESP$proto: RM_ReplyWithNull: a NULL reply" { - assert_equal {} [r rw.null] - } - - test "RESP$proto: RM_ReplyWithError: an error reply" { - catch {r rw.error} e - assert_match "An error" $e - } - - test "RESP$proto: RM_ReplyWithErrorFormat: error format reply" { - catch {r rw.error_format "An error: %s" foo} e - assert_match "An error: foo" $e ;# Should not be used by a user, but compatible with RM_ReplyError - - catch {r rw.error_format "-ERR An error: %s" foo2} e - assert_match "-ERR An error: foo2" $e ;# Should not be used by a user, but compatible with RM_ReplyError (There are two hyphens, TCL removes the first one) - - catch {r rw.error_format "-WRONGTYPE A type error: %s" foo3} e - assert_match "-WRONGTYPE A type error: foo3" $e ;# Should not be used by a user, but compatible with RM_ReplyError (There are two hyphens, TCL removes the first one) - - catch {r rw.error_format "ERR An error: %s" foo4} e - assert_match "ERR An error: foo4" $e - - catch {r rw.error_format "WRONGTYPE A type error: %s" foo5} e - assert_match "WRONGTYPE A type error: foo5" $e - } - - r hello 2 - } - - test "Unload the module - replywith" { - assert_equal {OK} [r module unload replywith] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/scan.tcl b/examples/redis-unstable/tests/unit/moduleapi/scan.tcl deleted file mode 100644 index c880477..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/scan.tcl +++ /dev/null @@ -1,103 +0,0 @@ -set testmodule [file normalize tests/modules/scan.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module scan keyspace} { - # the module create a scan command with filtering which also return values - r set x 1 - r set y 2 - r set z 3 - r hset h f v - lsort [r scan.scan_strings] - } {{x 1} {y 2} {z 3}} - - test {Module scan hash listpack} { - r hmset hh f1 v1 f2 v2 - assert_encoding listpack hh - lsort [r scan.scan_key hh] - } {{f1 v1} {f2 v2}} - - test {Module scan hash listpack with int value} { - r hmset hh1 f1 1 - assert_encoding listpack hh1 - lsort [r scan.scan_key hh1] - } {{f1 1}} - - test {Module scan hash listpack with hexpire} { - r debug set-active-expire 0 - r hmset hh f1 v1 f2 v2 f3 v3 - r hexpire hh 100000 fields 1 f1 - r hpexpire hh 1 fields 1 f3 - after 10 - assert_range [r httl hh fields 1 f1] 10000 100000 - assert_encoding listpackex hh - r debug set-active-expire 1 - lsort [r scan.scan_key hh] - } {{f1 v1} {f2 v2}} {needs:debug} - - test {Module scan hash dict} { - r config set hash-max-ziplist-entries 2 - r hmset hh f3 v3 - assert_encoding hashtable hh - lsort [r scan.scan_key hh] - } {{f1 v1} {f2 v2} {f3 v3}} - - test {Module scan hash dict with hexpire} { - r config set hash-max-listpack-entries 1 - r del hh - r hmset hh f1 v1 f2 v2 f3 v3 - r hexpire hh 100000 fields 1 f2 - r hpexpire hh 5 fields 1 f3 - assert_range [r httl hh fields 1 f2] 10000 100000 - assert_encoding hashtable hh - after 10 - lsort [r scan.scan_key hh] - } {{f1 v1} {f2 v2}} - - test {Module scan hash with hexpire can return no items} { - r del hh - r debug set-active-expire 0 - r hmset hh f1 v1 f2 v2 f3 v3 - r hpexpire hh 1 fields 3 f1 f2 f3 - after 10 - assert_equal [r scan.scan_key hh] {} - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {Module scan zset listpack} { - r zadd zz 1 f1 2 f2 - assert_encoding listpack zz - lsort [r scan.scan_key zz] - } {{f1 1} {f2 2}} - - test {Module scan zset skiplist} { - r config set zset-max-ziplist-entries 2 - r zadd zz 3 f3 - assert_encoding skiplist zz - lsort [r scan.scan_key zz] - } {{f1 1} {f2 2} {f3 3}} - - test {Module scan set intset} { - r sadd ss 1 2 - assert_encoding intset ss - lsort [r scan.scan_key ss] - } {{1 {}} {2 {}}} - - test {Module scan set dict} { - r config set set-max-intset-entries 2 - r sadd ss 3 - assert_encoding hashtable ss - lsort [r scan.scan_key ss] - } {{1 {}} {2 {}} {3 {}}} - - test {Module scan set listpack} { - r sadd ss1 a b c - assert_encoding listpack ss1 - lsort [r scan.scan_key ss1] - } {{a {}} {b {}} {c {}}} - - test "Unload the module - scan" { - assert_equal {OK} [r module unload scan] - } -} \ No newline at end of file diff --git a/examples/redis-unstable/tests/unit/moduleapi/stream.tcl b/examples/redis-unstable/tests/unit/moduleapi/stream.tcl deleted file mode 100644 index cc235f8..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/stream.tcl +++ /dev/null @@ -1,176 +0,0 @@ -set testmodule [file normalize tests/modules/stream.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module stream add and delete} { - r del mystream - # add to empty key - set streamid1 [r stream.add mystream item 1 value a] - # add to existing stream - set streamid2 [r stream.add mystream item 2 value b] - # check result - assert { [string match "*-*" $streamid1] } - set items [r XRANGE mystream - +] - assert_equal $items \ - "{$streamid1 {item 1 value a}} {$streamid2 {item 2 value b}}" - # delete one of them and try deleting non-existing ID - assert_equal OK [r stream.delete mystream $streamid1] - assert_error "ERR StreamDelete*" {r stream.delete mystream 123-456} - assert_error "Invalid stream ID*" {r stream.delete mystream foo} - assert_equal "{$streamid2 {item 2 value b}}" [r XRANGE mystream - +] - # check error condition: wrong type - r del mystream - r set mystream mystring - assert_error "ERR StreamAdd*" {r stream.add mystream item 1 value a} - assert_error "ERR StreamDelete*" {r stream.delete mystream 123-456} - } - - test {Module stream add unblocks blocking xread} { - r del mystream - - # Blocking XREAD on an empty key - set rd1 [redis_deferring_client] - $rd1 XREAD BLOCK 3000 STREAMS mystream $ - # wait until client is actually blocked - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - fail "Client is not blocked" - } - set id [r stream.add mystream field 1 value a] - assert_equal "{mystream {{$id {field 1 value a}}}}" [$rd1 read] - - # Blocking XREAD on an existing stream - set rd2 [redis_deferring_client] - $rd2 XREAD BLOCK 3000 STREAMS mystream $ - # wait until client is actually blocked - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - fail "Client is not blocked" - } - set id [r stream.add mystream field 2 value b] - assert_equal "{mystream {{$id {field 2 value b}}}}" [$rd2 read] - } - - test {Module stream add benchmark (1M stream add)} { - set n 1000000 - r del mystream - set result [r stream.addn mystream $n field value] - assert_equal $result $n - } - - test {Module stream XADD big fields doesn't create empty key} { - set original_proto [config_get_set proto-max-bulk-len 2147483647] ;#2gb - set original_query [config_get_set client-query-buffer-limit 2147483647] ;#2gb - - r del mystream - r write "*4\r\n\$10\r\nstream.add\r\n\$8\r\nmystream\r\n\$5\r\nfield\r\n" - catch { - write_big_bulk 1073741824 ;#1gb - } err - assert {$err eq "ERR StreamAdd failed"} - assert_equal 0 [r exists mystream] - - # restore defaults - r config set proto-max-bulk-len $original_proto - r config set client-query-buffer-limit $original_query - } {OK} {large-memory} - - test {Module stream iterator} { - r del mystream - set streamid1 [r xadd mystream * item 1 value a] - set streamid2 [r xadd mystream * item 2 value b] - # range result - set result1 [r stream.range mystream "-" "+"] - set expect1 [r xrange mystream "-" "+"] - assert_equal $result1 $expect1 - # reverse range - set result_rev [r stream.range mystream "+" "-"] - set expect_rev [r xrevrange mystream "+" "-"] - assert_equal $result_rev $expect_rev - - # only one item: range with startid = endid - set result2 [r stream.range mystream "-" $streamid1] - assert_equal $result2 "{$streamid1 {item 1 value a}}" - assert_equal $result2 [list [list $streamid1 {item 1 value a}]] - # only one item: range with startid = endid - set result3 [r stream.range mystream $streamid2 $streamid2] - assert_equal $result3 "{$streamid2 {item 2 value b}}" - assert_equal $result3 [list [list $streamid2 {item 2 value b}]] - } - - test {Module stream iterator delete} { - r del mystream - set id1 [r xadd mystream * normal item] - set id2 [r xadd mystream * selfdestruct yes] - set id3 [r xadd mystream * another item] - # stream.range deletes the "selfdestruct" item after returning it - assert_equal \ - "{$id1 {normal item}} {$id2 {selfdestruct yes}} {$id3 {another item}}" \ - [r stream.range mystream - +] - # now, the "selfdestruct" item is gone - assert_equal \ - "{$id1 {normal item}} {$id3 {another item}}" \ - [r stream.range mystream - +] - } - - test {Module stream trim by length} { - r del mystream - # exact maxlen - r xadd mystream * item 1 value a - r xadd mystream * item 2 value b - r xadd mystream * item 3 value c - assert_equal 3 [r xlen mystream] - assert_equal 0 [r stream.trim mystream maxlen = 5] - assert_equal 3 [r xlen mystream] - assert_equal 2 [r stream.trim mystream maxlen = 1] - assert_equal 1 [r xlen mystream] - assert_equal 1 [r stream.trim mystream maxlen = 0] - # check that there is no limit for exact maxlen - r stream.addn mystream 20000 item x value y - assert_equal 20000 [r stream.trim mystream maxlen = 0] - # approx maxlen (100 items per node implies default limit 10K items) - r stream.addn mystream 20000 item x value y - assert_equal 20000 [r xlen mystream] - assert_equal 10000 [r stream.trim mystream maxlen ~ 2] - assert_equal 9900 [r stream.trim mystream maxlen ~ 2] - assert_equal 0 [r stream.trim mystream maxlen ~ 2] - assert_equal 100 [r xlen mystream] - assert_equal 100 [r stream.trim mystream maxlen ~ 0] - assert_equal 0 [r xlen mystream] - } - - test {Module stream trim by ID} { - r del mystream - # exact minid - r xadd mystream * item 1 value a - r xadd mystream * item 2 value b - set minid [r xadd mystream * item 3 value c] - assert_equal 3 [r xlen mystream] - assert_equal 0 [r stream.trim mystream minid = -] - assert_equal 3 [r xlen mystream] - assert_equal 2 [r stream.trim mystream minid = $minid] - assert_equal 1 [r xlen mystream] - assert_equal 1 [r stream.trim mystream minid = +] - # check that there is no limit for exact minid - r stream.addn mystream 20000 item x value y - assert_equal 20000 [r stream.trim mystream minid = +] - # approx minid (100 items per node implies default limit 10K items) - r stream.addn mystream 19980 item x value y - set minid [r xadd mystream * item x value y] - r stream.addn mystream 19 item x value y - assert_equal 20000 [r xlen mystream] - assert_equal 10000 [r stream.trim mystream minid ~ $minid] - assert_equal 9900 [r stream.trim mystream minid ~ $minid] - assert_equal 0 [r stream.trim mystream minid ~ $minid] - assert_equal 100 [r xlen mystream] - assert_equal 100 [r stream.trim mystream minid ~ +] - assert_equal 0 [r xlen mystream] - } - - test "Unload the module - stream" { - assert_equal {OK} [r module unload stream] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl b/examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl deleted file mode 100644 index f3734a8..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl +++ /dev/null @@ -1,61 +0,0 @@ -set testmodule [file normalize tests/modules/subcommands.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test "Module subcommands via COMMAND" { - # Verify that module subcommands are displayed correctly in COMMAND - set command_reply [r command info subcommands.bitarray] - set first_cmd [lindex $command_reply 0] - set subcmds_in_command [lsort [lindex $first_cmd 9]] - assert_equal [lindex $subcmds_in_command 0] {subcommands.bitarray|get -2 module 1 1 1 {} {} {{flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}} - assert_equal [lindex $subcmds_in_command 1] {subcommands.bitarray|set -2 module 1 1 1 {} {} {{flags {RW update} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}} - - # Verify that module subcommands are displayed correctly in COMMAND DOCS - set docs_reply [r command docs subcommands.bitarray] - set docs [dict create {*}[lindex $docs_reply 1]] - set subcmds_in_cmd_docs [dict create {*}[dict get $docs subcommands]] - assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|get"] {group module module subcommands} - assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {group module module subcommands} - } - - test "Module pure-container command fails on arity error" { - catch {r subcommands.bitarray} e - assert_match {*wrong number of arguments for 'subcommands.bitarray' command} $e - - # Subcommands can be called - assert_equal [r subcommands.bitarray get k1] {OK} - - # Subcommand arity error - catch {r subcommands.bitarray get k1 8 90} e - assert_match {*wrong number of arguments for 'subcommands.bitarray|get' command} $e - } - - test "Module get current command fullname" { - assert_equal [r subcommands.parent_get_fullname] {subcommands.parent_get_fullname} - } - - test "Module get current subcommand fullname" { - assert_equal [r subcommands.sub get_fullname] {subcommands.sub|get_fullname} - } - - test "COMMAND LIST FILTERBY MODULE" { - assert_equal {} [r command list filterby module non_existing] - - set commands [r command list filterby module subcommands] - assert_not_equal [lsearch $commands "subcommands.bitarray"] -1 - assert_not_equal [lsearch $commands "subcommands.bitarray|set"] -1 - assert_not_equal [lsearch $commands "subcommands.parent_get_fullname"] -1 - assert_not_equal [lsearch $commands "subcommands.sub|get_fullname"] -1 - - assert_equal [lsearch $commands "set"] -1 - } - - test "Internal container command without subcommand returns missing subcommand error" { - assert_error {*missing subcommand*} {r subcommands.internal_container} - } - - test "Unload the module - subcommands" { - assert_equal {OK} [r module unload subcommands] - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl b/examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl deleted file mode 100644 index 4c50b65..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl +++ /dev/null @@ -1,32 +0,0 @@ -set testmodule [file normalize tests/modules/test_lazyfree.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test "modules allocated memory can be reclaimed in the background" { - set orig_mem [s used_memory] - set rd [redis_deferring_client] - - # LAZYFREE_THRESHOLD is 64 - for {set i 0} {$i < 10000} {incr i} { - $rd lazyfreelink.insert lazykey $i - } - - for {set j 0} {$j < 10000} {incr j} { - $rd read - } - - assert {[r lazyfreelink.len lazykey] == 10000} - - set peak_mem [s used_memory] - assert {[r unlink lazykey] == 1} - assert {$peak_mem > $orig_mem+10000} - wait_for_condition 50 100 { - [s used_memory] < $peak_mem && - [s used_memory] < $orig_mem*2 && - [string match {*lazyfreed_objects:1*} [r info Memory]] - } else { - fail "Module memory is not reclaimed by UNLINK" - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl b/examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl deleted file mode 100644 index e44cf42..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl +++ /dev/null @@ -1,311 +0,0 @@ -# This module can be configure with multiple options given as flags on module load time -# 0 - not aux fields will be declared (this is the default) -# 1 << 0 - use aux_save2 api -# 1 << 1 - call aux callback before key space -# 1 << 2 - call aux callback after key space -# 1 << 3 - do not save data on aux callback -set testmodule [file normalize tests/modules/testrdb.so] - -tags "modules external:skip" { - test {modules are able to persist types} { - start_server [list overrides [list loadmodule "$testmodule"]] { - r testrdb.set.key key1 value1 - assert_equal "value1" [r testrdb.get.key key1] - r debug reload - assert_equal "value1" [r testrdb.get.key key1] - } - } - - test {modules global are lost without aux} { - set server_path [tmpdir "server.module-testrdb"] - start_server [list overrides [list loadmodule "$testmodule" "dir" $server_path] keep_persistence true] { - r testrdb.set.before global1 - assert_equal "global1" [r testrdb.get.before] - } - start_server [list overrides [list loadmodule "$testmodule" "dir" $server_path]] { - assert_equal "" [r testrdb.get.before] - } - } - - test {aux that saves no data are not saved to the rdb when aux_save2 is used} { - set server_path [tmpdir "server.module-testrdb"] - puts $server_path - # 15 == 1111 - use aux_save2 before and after key space without data - start_server [list overrides [list loadmodule "$testmodule 15" "dir" $server_path] keep_persistence true] { - r set x 1 - r save - } - start_server [list overrides [list "dir" $server_path] keep_persistence true] { - # make sure server started successfully without the module. - assert_equal {1} [r get x] - } - } - - test {aux that saves no data are saved to the rdb when aux_save is used} { - set server_path [tmpdir "server.module-testrdb"] - puts $server_path - # 14 == 1110 - use aux_save before and after key space without data - start_server [list overrides [list loadmodule "$testmodule 14" "dir" $server_path] keep_persistence true] { - r set x 1 - r save - } - start_server [list overrides [list loadmodule "$testmodule 14" "dir" $server_path] keep_persistence true] { - # make sure server started successfully and aux_save was called twice. - assert_equal {1} [r get x] - assert_equal {2} [r testrdb.get.n_aux_load_called] - } - } - - foreach test_case {6 7} { - # 6 == 0110 - use aux_save before and after key space with data - # 7 == 0111 - use aux_save2 before and after key space with data - test {modules are able to persist globals before and after} { - set server_path [tmpdir "server.module-testrdb"] - start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"] keep_persistence true] { - r testrdb.set.before global1 - r testrdb.set.after global2 - assert_equal "global1" [r testrdb.get.before] - assert_equal "global2" [r testrdb.get.after] - } - start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"]] { - assert_equal "global1" [r testrdb.get.before] - assert_equal "global2" [r testrdb.get.after] - } - - } - } - - foreach test_case {4 5} { - # 4 == 0100 - use aux_save after key space with data - # 5 == 0101 - use aux_save2 after key space with data - test {modules are able to persist globals just after} { - set server_path [tmpdir "server.module-testrdb"] - start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"] keep_persistence true] { - r testrdb.set.after global2 - assert_equal "global2" [r testrdb.get.after] - } - start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"]] { - assert_equal "global2" [r testrdb.get.after] - } - } - } - - test {Verify module options info} { - start_server [list overrides [list loadmodule "$testmodule"]] { - assert_match "*\[handle-io-errors|handle-repl-async-load\]*" [r info modules] - } - } - - tags {repl} { - test {diskless loading short read with module} { - start_server [list overrides [list loadmodule "$testmodule"]] { - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - start_server [list overrides [list loadmodule "$testmodule"]] { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - - # Set master and replica to use diskless replication - $master config set repl-diskless-sync yes - $master config set rdbcompression no - $replica config set repl-diskless-load swapdb - $master config set hz 500 - $replica config set hz 500 - $master config set dynamic-hz no - $replica config set dynamic-hz no - set start [clock clicks -milliseconds] - # Generate small keys - for {set k 0} {$k < 20000} {incr k} { - r testrdb.set.key keysmall$k [string repeat A [expr {int(rand()*100)}]] - } - # Generate larger keys - for {set k 0} {$k < 30} {incr k} { - r testrdb.set.key key$k [string repeat A [expr {int(rand()*1000000)}]] - } - - if {$::verbose} { - set end [clock clicks -milliseconds] - set duration [expr $end - $start] - puts "filling took $duration ms (TODO: use pipeline)" - set start [clock clicks -milliseconds] - } - - # Start the replication process... - set loglines [count_log_lines -1] - $master config set repl-diskless-sync-delay 0 - $replica replicaof $master_host $master_port - - # kill the replication at various points - set attempts 100 - if {$::accurate} { set attempts 500 } - for {set i 0} {$i < $attempts} {incr i} { - # wait for the replica to start reading the rdb - # using the log file since the replica only responds to INFO once in 2mb - set res [wait_for_log_messages -1 {"*Loading DB in memory*"} $loglines 2000 1] - set loglines [lindex $res 1] - - # add some additional random sleep so that we kill the master on a different place each time - after [expr {int(rand()*50)}] - - # kill the replica connection on the master - set killed [$master client kill type replica] - - set res [wait_for_log_messages -1 {"*Internal error in RDB*" "*Finished with success*" "*Successful partial resynchronization*"} $loglines 500 10] - if {$::verbose} { puts $res } - set log_text [lindex $res 0] - set loglines [lindex $res 1] - if {![string match "*Internal error in RDB*" $log_text]} { - # force the replica to try another full sync - $master multi - $master client kill type replica - $master set asdf asdf - # fill replication backlog with new content - $master config set repl-backlog-size 16384 - for {set keyid 0} {$keyid < 10} {incr keyid} { - $master set "$keyid string_$keyid" [string repeat A 16384] - } - $master exec - } - - # wait for loading to stop (fail) - # After a loading successfully, next loop will enter `async_loading` - wait_for_condition 1000 1 { - [s -1 async_loading] eq 0 && - [s -1 loading] eq 0 - } else { - fail "Replica didn't disconnect" - } - } - if {$::verbose} { - set end [clock clicks -milliseconds] - set duration [expr $end - $start] - puts "test took $duration ms" - } - # enable fast shutdown - $master config set rdb-key-save-delay 0 - } - } - } - - # Module events for diskless load swapdb when async_loading (matching master replid) - foreach test_case {6 7} { - # 6 == 0110 - use aux_save before and after key space with data - # 7 == 0111 - use aux_save2 before and after key space with data - foreach testType {Successful Aborted} { - start_server [list overrides [list loadmodule "$testmodule $test_case"] tags [list external:skip]] { - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - set replica_log [srv 0 stdout] - start_server [list overrides [list loadmodule "$testmodule $test_case"]] { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - - set start [clock clicks -milliseconds] - - # Set master and replica to use diskless replication on swapdb mode - $master config set repl-diskless-sync yes - $master config set repl-diskless-sync-delay 0 - $master config set save "" - $replica config set repl-diskless-load swapdb - $replica config set save "" - - # Initial sync to have matching replids between master and replica - $replica replicaof $master_host $master_port - - # Let replica finish initial sync with master - wait_for_condition 100 100 { - [s -1 master_link_status] eq "up" - } else { - fail "Master <-> Replica didn't finish sync" - } - - # Set global values on module so we can check if module event callbacks will pick it up correctly - $master testrdb.set.before value1_master - $replica testrdb.set.before value1_replica - - # Put different data sets on the master and replica - # We need to put large keys on the master since the replica replies to info only once in 2mb - $replica debug populate 200 slave 10 - $master debug populate 1000 master 100000 - $master config set rdbcompression no - - # Force the replica to try another full sync (this time it will have matching master replid) - $master multi - $master client kill type replica - # Fill replication backlog with new content - $master config set repl-backlog-size 16384 - for {set keyid 0} {$keyid < 10} {incr keyid} { - $master set "$keyid string_$keyid" [string repeat A 16384] - } - $master exec - - switch $testType { - "Aborted" { - # Set master with a slow rdb generation, so that we can easily intercept loading - # 10ms per key, with 1000 keys is 10 seconds - $master config set rdb-key-save-delay 10000 - - test {Diskless load swapdb RedisModuleEvent_ReplAsyncLoad handling: during loading, can keep module variable same as before} { - # Wait for the replica to start reading the rdb and module for acknowledgement - # We wanna abort only after the temp db was populated by REDISMODULE_AUX_BEFORE_RDB - wait_for_condition 100 100 { - [s -1 async_loading] eq 1 && [$replica testrdb.async_loading.get.before] eq "value1_master" - } else { - fail "Module didn't receive or react to REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED" - } - - assert_equal [$replica dbsize] 200 - assert_equal value1_replica [$replica testrdb.get.before] - } - - # Make sure that next sync will not start immediately so that we can catch the replica in between syncs - $master config set repl-diskless-sync-delay 5 - - # Kill the replica connection on the master - set killed [$master client kill type replica] - - test {Diskless load swapdb RedisModuleEvent_ReplAsyncLoad handling: when loading aborted, can keep module variable same as before} { - # Wait for loading to stop (fail) and module for acknowledgement - wait_for_condition 100 100 { - [s -1 async_loading] eq 0 && [$replica testrdb.async_loading.get.before] eq "" - } else { - fail "Module didn't receive or react to REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED" - } - - assert_equal [$replica dbsize] 200 - assert_equal value1_replica [$replica testrdb.get.before] - } - - # Speed up shutdown - $master config set rdb-key-save-delay 0 - } - "Successful" { - # Let replica finish sync with master - wait_for_condition 100 100 { - [s -1 master_link_status] eq "up" - } else { - fail "Master <-> Replica didn't finish sync" - } - - test {Diskless load swapdb RedisModuleEvent_ReplAsyncLoad handling: after db loaded, can set module variable with new value} { - assert_equal [$replica dbsize] 1010 - assert_equal value1_master [$replica testrdb.get.before] - } - } - } - - if {$::verbose} { - set end [clock clicks -milliseconds] - set duration [expr $end - $start] - puts "test took $duration ms" - } - } - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/timer.tcl b/examples/redis-unstable/tests/unit/moduleapi/timer.tcl deleted file mode 100644 index 1e83888..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/timer.tcl +++ /dev/null @@ -1,99 +0,0 @@ -set testmodule [file normalize tests/modules/timer.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {RM_CreateTimer: a sequence of timers work} { - # We can't guarantee same-ms but we try using MULTI/EXEC - r multi - for {set i 0} {$i < 20} {incr i} { - r test.createtimer 10 timer-incr-key - } - r exec - - after 500 - assert_equal 20 [r get timer-incr-key] - } - - test {RM_GetTimer: basic sanity} { - # Getting non-existing timer - assert_equal {} [r test.gettimer 0] - - # Getting a real timer - set id [r test.createtimer 10000 timer-incr-key] - set info [r test.gettimer $id] - - assert_equal "timer-incr-key" [lindex $info 0] - set remaining [lindex $info 1] - assert {$remaining < 10000 && $remaining > 1} - # Stop the timer after get timer test - assert_equal 1 [r test.stoptimer $id] - } - - test {RM_StopTimer: basic sanity} { - r set "timer-incr-key" 0 - set id [r test.createtimer 1000 timer-incr-key] - - assert_equal 1 [r test.stoptimer $id] - - # Wait to be sure timer doesn't execute - after 2000 - assert_equal 0 [r get timer-incr-key] - - # Stop non-existing timer - assert_equal 0 [r test.stoptimer $id] - } - - test {Timer appears non-existing after it fires} { - r set "timer-incr-key" 0 - set id [r test.createtimer 10 timer-incr-key] - - # verify timer fired - after 500 - assert_equal 1 [r get timer-incr-key] - - # verify id does not exist - assert_equal {} [r test.gettimer $id] - } - - test "Module can be unloaded when timer was finished" { - r set "timer-incr-key" 0 - r test.createtimer 500 timer-incr-key - - # Make sure the Timer has not been fired - assert_equal 0 [r get timer-incr-key] - # Module can not be unloaded since the timer was ongoing - catch {r module unload timer} err - assert_match {*the module holds timer that is not fired*} $err - - # Wait to be sure timer has been finished - wait_for_condition 10 500 { - [r get timer-incr-key] == 1 - } else { - fail "Timer not fired" - } - - # Timer fired, can be unloaded now. - assert_equal {OK} [r module unload timer] - } - - test "Module can be unloaded when timer was stopped" { - r module load $testmodule - r set "timer-incr-key" 0 - set id [r test.createtimer 5000 timer-incr-key] - - # Module can not be unloaded since the timer was ongoing - catch {r module unload timer} err - assert_match {*the module holds timer that is not fired*} $err - - # Stop the timer - assert_equal 1 [r test.stoptimer $id] - - # Make sure the Timer has not been fired - assert_equal 0 [r get timer-incr-key] - - # Timer has stopped, can be unloaded now. - assert_equal {OK} [r module unload timer] - } -} - diff --git a/examples/redis-unstable/tests/unit/moduleapi/usercall.tcl b/examples/redis-unstable/tests/unit/moduleapi/usercall.tcl deleted file mode 100644 index 16b4ff1..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/usercall.tcl +++ /dev/null @@ -1,186 +0,0 @@ -set testmodule [file normalize tests/modules/usercall.so] - -set test_script_set "#!lua -redis.call('set','x',1) -return 1" - -set test_script_get "#!lua -redis.call('get','x') -return 1" - -start_server {tags {"modules usercall external:skip"}} { - r module load $testmodule - - # baseline test that module isn't doing anything weird - test {test module check regular redis command without user/acl} { - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - assert_equal [r usercall.call_without_user set x 5] OK - assert_equal [r usercall.reset_user] OK - } - - # call with user with acl set on it, but without testing the acl - test {test module check regular redis command with user} { - assert_equal [r set x 5] OK - - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - # off and sanitize-payload because module user / default value - assert_equal [r usercall.get_acl] "off sanitize-payload ~* &* +@all -set" - - # doesn't fail for regular commands as just testing acl here - assert_equal [r usercall.call_with_user_flag {} set x 10] OK - - assert_equal [r get x] 10 - assert_equal [r usercall.reset_user] OK - } - - # call with user with acl set on it, but with testing the acl in rm_call (for cmd itself) - test {test module check regular redis command with user and acl} { - assert_equal [r set x 5] OK - - r ACL LOG RESET - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - # off and sanitize-payload because module user / default value - assert_equal [r usercall.get_acl] "off sanitize-payload ~* &* +@all -set" - - # fails here as testing acl in rm call - assert_error {*NOPERM User module_user has no permissions*} {r usercall.call_with_user_flag C set x 10} - - assert_equal [r usercall.call_with_user_flag C get x] 5 - - # verify that new log entry added - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] {module_user} - assert_equal [dict get $entry context] {module} - assert_equal [dict get $entry object] {set} - assert_equal [dict get $entry reason] {command} - assert_match {*cmd=usercall.call_with_user_flag*} [dict get $entry client-info] - - assert_equal [r usercall.reset_user] OK - } - - # call with user with acl set on it, but with testing the acl in rm_call (for cmd itself) - test {test module check regular redis command with user and acl from blocked background thread} { - assert_equal [r set x 5] OK - - r ACL LOG RESET - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - - # fails here as testing acl in rm call from a background thread - assert_error {*NOPERM User module_user has no permissions*} {r usercall.call_with_user_bg C set x 10} - - assert_equal [r usercall.call_with_user_bg C get x] 5 - - # verify that new log entry added - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] {module_user} - assert_equal [dict get $entry context] {module} - assert_equal [dict get $entry object] {set} - assert_equal [dict get $entry reason] {command} - assert_match {*cmd=NULL*} [dict get $entry client-info] - - assert_equal [r usercall.reset_user] OK - } - - # baseline script test, call without user on script - test {test module check eval script without user} { - set sha_set [r script load $test_script_set] - set sha_get [r script load $test_script_get] - - assert_equal [r usercall.call_without_user evalsha $sha_set 0] 1 - assert_equal [r usercall.call_without_user evalsha $sha_get 0] 1 - } - - # baseline script test, call without user on script - test {test module check eval script with user being set, but not acl testing} { - set sha_set [r script load $test_script_set] - set sha_get [r script load $test_script_get] - - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - # off and sanitize-payload because module user / default value - assert_equal [r usercall.get_acl] "off sanitize-payload ~* &* +@all -set" - - # passes as not checking ACL - assert_equal [r usercall.call_with_user_flag {} evalsha $sha_set 0] 1 - assert_equal [r usercall.call_with_user_flag {} evalsha $sha_get 0] 1 - } - - # call with user on script (without rm_call acl check) to ensure user carries through to script execution - # we already tested the check in rm_call above, here we are checking the script itself will enforce ACL - test {test module check eval script with user and acl} { - set sha_set [r script load $test_script_set] - set sha_get [r script load $test_script_get] - - r ACL LOG RESET - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - - # fails here in script, as rm_call will permit the eval call - catch {r usercall.call_with_user_flag C evalsha $sha_set 0} e - assert_match {*ERR ACL failure in script*} $e - - assert_equal [r usercall.call_with_user_flag C evalsha $sha_get 0] 1 - - # verify that new log entry added - set entry [lindex [r ACL LOG] 0] - assert_equal [dict get $entry username] {module_user} - assert_equal [dict get $entry context] {lua} - assert_equal [dict get $entry object] {set} - assert_equal [dict get $entry reason] {command} - assert_match {*cmd=usercall.call_with_user_flag*} [dict get $entry client-info] - } - - test {server not crashing when MONITOR is fed from spawned thread} { - set rd [redis_deferring_client] - $rd monitor - - r ACL LOG RESET - assert_equal [r usercall.reset_user] OK - assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK - - r flushdb - r set x x - - # This is enough. This checks that we don't crash inside - # updateClientMemUsageAndBucket - assert_equal x [r usercall.call_with_user_bg C get x] - - $rd close - } - - start_server {tags {"wait aof network external:skip"}} { - set slave [srv 0 client] - set slave_host [srv 0 host] - set slave_port [srv 0 port] - set slave_pid [srv 0 pid] - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - - $master config set appendonly yes - $master config set appendfsync everysec - $slave config set appendonly yes - $slave config set appendfsync everysec - - test {Setup slave} { - $slave slaveof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - } - - test {test module replicate only to replicas and WAITAOF} { - $master set x 1 - assert_equal [$master waitaof 1 1 10000] {1 1} - $master usercall.call_with_user_flag A! config set loglevel notice - # Make sure WAITAOF doesn't hang - assert_equal [$master waitaof 1 1 10000] {1 1} - } - } -} diff --git a/examples/redis-unstable/tests/unit/moduleapi/zset.tcl b/examples/redis-unstable/tests/unit/moduleapi/zset.tcl deleted file mode 100644 index f2a324c..0000000 --- a/examples/redis-unstable/tests/unit/moduleapi/zset.tcl +++ /dev/null @@ -1,121 +0,0 @@ -set testmodule [file normalize tests/modules/zset.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module zset rem} { - r del k - r zadd k 100 hello 200 world - assert_equal 1 [r zset.rem k hello] - assert_equal 0 [r zset.rem k hello] - assert_equal 1 [r exists k] - # Check that removing the last element deletes the key - assert_equal 1 [r zset.rem k world] - assert_equal 0 [r exists k] - } - - test {Module zset add} { - r del k - # Check that failure does not create empty key - assert_error "ERR ZsetAdd failed" {r zset.add k nan hello} - assert_equal 0 [r exists k] - - r zset.add k 100 hello - assert_equal {hello 100} [r zrange k 0 -1 withscores] - } - - test {Module zset incrby} { - r del k - # Check that failure does not create empty key - assert_error "ERR ZsetIncrby failed" {r zset.incrby k hello nan} - assert_equal 0 [r exists k] - - r zset.incrby k hello 100 - assert_equal {hello 100} [r zrange k 0 -1 withscores] - } - - test {Module zset - KEYSIZES is updated as expected (like test at hash.tcl)} { - proc run_cmd_verify_hist {cmd expOutput {retries 1}} { - proc K {} {return [string map { "db0_distrib_zsets_items" "db0_ZSET" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]} - uplevel 1 $cmd - wait_for_condition 50 $retries { - $expOutput eq [K] - } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" } - } - - r select 0 - - #RedisModule_ZsetAdd, RedisModule_ZsetRem - run_cmd_verify_hist {r FLUSHALL} {} - run_cmd_verify_hist {r zset.add k 100 hello} {db0_ZSET:1=1} - run_cmd_verify_hist {r zset.add k 101 bye} {db0_ZSET:2=1} - run_cmd_verify_hist {r zset.rem k hello} {db0_ZSET:1=1} - run_cmd_verify_hist {r zset.rem k bye} {} - - #RM_ZsetIncrby - run_cmd_verify_hist {r FLUSHALL} {} - run_cmd_verify_hist {r zset.incrby k hello 100} {db0_ZSET:1=1} - run_cmd_verify_hist {r zset.incrby k hello 100} {db0_ZSET:1=1} - run_cmd_verify_hist {r zset.rem k hello} {} - - # Check lazy expire - r debug set-active-expire 0 - run_cmd_verify_hist {r zset.add k 100 hello} {db0_ZSET:1=1} - run_cmd_verify_hist {r pexpire k 2} {db0_ZSET:1=1} - run_cmd_verify_hist {after 5} {db0_ZSET:1=1} - r debug set-active-expire 1 - run_cmd_verify_hist {after 5} {} 50 - } - - test {Module zset DELALL functionality} { - # Clean up any existing keys - r flushall - - # Create some zsets and other types of keys - r zadd zset1 100 hello 200 world - r zadd zset2 300 foo 400 bar - r zadd zset3 500 baz - r set string1 "value1" - r hset hash1 field1 value1 - r lpush list1 item1 - - # Verify we have the expected keys - assert_equal 6 [r dbsize] - assert_equal 3 [llength [r keys zset*]] - - # Run zset.delall - set deleted [r zset.delall] - assert_equal 3 $deleted - - # Verify only zsets were deleted - assert_equal 3 [r dbsize] - assert_equal 0 [llength [r keys zset*]] - assert_equal 1 [r exists string1] - assert_equal 1 [r exists hash1] - assert_equal 1 [r exists list1] - - # Test with no zsets - set deleted [r zset.delall] - assert_equal 0 $deleted - assert_equal 3 [r dbsize] - } - - test {Module zset DELALL not in transaction} { - set repl [attach_to_replication_stream] - r zadd z1 1 e1 - r zadd z2 1 e1 - r zset.delall - assert_replication_stream $repl { - {select *} - {zadd z1 1 e1} - {zadd z2 1 e1} - {del z*} - {del z*} - } - close_replication_stream $repl - } {} {needs:repl} - - test "Unload the module - zset" { - assert_equal {OK} [r module unload zset] - } -} diff --git a/examples/redis-unstable/tests/unit/multi.tcl b/examples/redis-unstable/tests/unit/multi.tcl deleted file mode 100644 index 4e8e807..0000000 --- a/examples/redis-unstable/tests/unit/multi.tcl +++ /dev/null @@ -1,925 +0,0 @@ -proc wait_for_dbsize {size} { - set r2 [redis_client] - wait_for_condition 50 100 { - [$r2 dbsize] == $size - } else { - fail "Target dbsize not reached" - } - $r2 close -} - -start_server {tags {"multi"}} { - test {MULTI / EXEC basics} { - r del mylist - r rpush mylist a - r rpush mylist b - r rpush mylist c - r multi - set v1 [r lrange mylist 0 -1] - set v2 [r ping] - set v3 [r exec] - list $v1 $v2 $v3 - } {QUEUED QUEUED {{a b c} PONG}} - - test {DISCARD} { - r del mylist - r rpush mylist a - r rpush mylist b - r rpush mylist c - r multi - set v1 [r del mylist] - set v2 [r discard] - set v3 [r lrange mylist 0 -1] - list $v1 $v2 $v3 - } {QUEUED OK {a b c}} - - test {Nested MULTI are not allowed} { - set err {} - r multi - catch {[r multi]} err - r exec - set _ $err - } {*ERR MULTI*} - - test {MULTI where commands alter argc/argv} { - r sadd myset a - r multi - r spop myset - list [r exec] [r exists myset] - } {a 0} - - test {WATCH inside MULTI is not allowed} { - set err {} - r multi - catch {[r watch x]} err - r exec - set _ $err - } {*ERR WATCH*} - - test {EXEC fails if there are errors while queueing commands #1} { - r del foo1{t} foo2{t} - r multi - r set foo1{t} bar1 - catch {r non-existing-command} - r set foo2{t} bar2 - catch {r exec} e - assert_match {EXECABORT*} $e - list [r exists foo1{t}] [r exists foo2{t}] - } {0 0} - - test {EXEC fails if there are errors while queueing commands #2} { - set rd [redis_deferring_client] - r del foo1{t} foo2{t} - r multi - r set foo1{t} bar1 - $rd config set maxmemory 1 - assert {[$rd read] eq {OK}} - catch {r lpush mylist{t} myvalue} - $rd config set maxmemory 0 - assert {[$rd read] eq {OK}} - r set foo2{t} bar2 - catch {r exec} e - assert_match {EXECABORT*} $e - $rd close - list [r exists foo1{t}] [r exists foo2{t}] - } {0 0} {needs:config-maxmemory} - - test {If EXEC aborts, the client MULTI state is cleared} { - r del foo1{t} foo2{t} - r multi - r set foo1{t} bar1 - catch {r non-existing-command} - r set foo2{t} bar2 - catch {r exec} e - assert_match {EXECABORT*} $e - r ping - } {PONG} - - test {EXEC works on WATCHed key not modified} { - r watch x{t} y{t} z{t} - r watch k{t} - r multi - r ping - r exec - } {PONG} - - test {EXEC fail on WATCHed key modified (1 key of 1 watched)} { - r set x 30 - r watch x - r set x 40 - r multi - r ping - r exec - } {} - - test {EXEC fail on WATCHed key modified (1 key of 5 watched)} { - r set x{t} 30 - r watch a{t} b{t} x{t} k{t} z{t} - r set x{t} 40 - r multi - r ping - r exec - } {} - - test {EXEC fail on WATCHed key modified by SORT with STORE even if the result is empty} { - r flushdb - r lpush foo bar - r watch foo - r sort emptylist store foo - r multi - r ping - r exec - } {} {cluster:skip} - - test {EXEC fail on lazy expired WATCHed key} { - r del key - r debug set-active-expire 0 - - for {set j 0} {$j < 10} {incr j} { - r set key 1 px 100 - r watch key - after 101 - r multi - r incr key - - set res [r exec] - if {$res eq {}} break - } - if {$::verbose} { puts "EXEC fail on lazy expired WATCHed key attempts: $j" } - - r debug set-active-expire 1 - set _ $res - } {} {needs:debug} - - test {WATCH stale keys should not fail EXEC} { - r del x - r debug set-active-expire 0 - r set x foo px 1 - after 2 - r watch x - r multi - r ping - assert_equal {PONG} [r exec] - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {Delete WATCHed stale keys should not fail EXEC} { - r del x - r debug set-active-expire 0 - r set x foo px 1 - after 2 - r watch x - # EXISTS triggers lazy expiry/deletion - assert_equal 0 [r exists x] - r multi - r ping - assert_equal {PONG} [r exec] - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {FLUSHDB while watching stale keys should not fail EXEC} { - r del x - r debug set-active-expire 0 - r set x foo px 1 - after 2 - r watch x - r flushdb - r multi - r ping - assert_equal {PONG} [r exec] - r debug set-active-expire 1 - } {OK} {needs:debug} - - test {After successful EXEC key is no longer watched} { - r set x 30 - r watch x - r multi - r ping - r exec - r set x 40 - r multi - r ping - r exec - } {PONG} - - test {After failed EXEC key is no longer watched} { - r set x 30 - r watch x - r set x 40 - r multi - r ping - r exec - r set x 40 - r multi - r ping - r exec - } {PONG} - - test {It is possible to UNWATCH} { - r set x 30 - r watch x - r set x 40 - r unwatch - r multi - r ping - r exec - } {PONG} - - test {UNWATCH when there is nothing watched works as expected} { - r unwatch - } {OK} - - test {FLUSHALL is able to touch the watched keys} { - r set x 30 - r watch x - r flushall - r multi - r ping - r exec - } {} - - test {FLUSHALL does not touch non affected keys} { - r del x - r watch x - r flushall - r multi - r ping - r exec - } {PONG} - - test {FLUSHDB is able to touch the watched keys} { - r set x 30 - r watch x - r flushdb - r multi - r ping - r exec - } {} - - test {FLUSHDB does not touch non affected keys} { - r del x - r watch x - r flushdb - r multi - r ping - r exec - } {PONG} - - test {SWAPDB is able to touch the watched keys that exist} { - r flushall - r select 0 - r set x 30 - r watch x ;# make sure x (set to 30) doesn't change (SWAPDB will "delete" it) - r swapdb 0 1 - r multi - r ping - r exec - } {} {singledb:skip} - - test {SWAPDB is able to touch the watched keys that do not exist} { - r flushall - r select 1 - r set x 30 - r select 0 - r watch x ;# make sure the key x (currently missing) doesn't change (SWAPDB will create it) - r swapdb 0 1 - r multi - r ping - r exec - } {} {singledb:skip} - - test {SWAPDB does not touch watched stale keys} { - r flushall - r select 1 - r debug set-active-expire 0 - r set x foo px 1 - after 2 - r watch x - r swapdb 0 1 ; # expired key replaced with no key => no change - r multi - r ping - assert_equal {PONG} [r exec] - r debug set-active-expire 1 - } {OK} {singledb:skip needs:debug} - - test {SWAPDB does not touch non-existing key replaced with stale key} { - r flushall - r select 0 - r debug set-active-expire 0 - r set x foo px 1 - after 2 - r select 1 - r watch x - r swapdb 0 1 ; # no key replaced with expired key => no change - r multi - r ping - assert_equal {PONG} [r exec] - r debug set-active-expire 1 - } {OK} {singledb:skip needs:debug} - - test {SWAPDB does not touch stale key replaced with another stale key} { - r flushall - r debug set-active-expire 0 - r select 1 - r set x foo px 1 - r select 0 - r set x bar px 1 - after 2 - r select 1 - r watch x - r swapdb 0 1 ; # no key replaced with expired key => no change - r multi - r ping - assert_equal {PONG} [r exec] - r debug set-active-expire 1 - } {OK} {singledb:skip needs:debug} - - test {WATCH is able to remember the DB a key belongs to} { - r select 5 - r set x 30 - r watch x - r select 1 - r set x 10 - r select 5 - r multi - r ping - set res [r exec] - # Restore original DB - r select 9 - set res - } {PONG} {singledb:skip} - - test {WATCH will consider touched keys target of EXPIRE} { - r del x - r set x foo - r watch x - r expire x 10 - r multi - r ping - r exec - } {} - - test {WATCH will consider touched expired keys} { - r flushall - r del x - r set x foo - r expire x 1 - r watch x - - # Wait for the keys to expire. - wait_for_dbsize 0 - - r multi - r ping - r exec - } {} - - test {DISCARD should clear the WATCH dirty flag on the client} { - r watch x - r set x 10 - r multi - r discard - r multi - r incr x - r exec - } {11} - - test {DISCARD should UNWATCH all the keys} { - r watch x - r set x 10 - r multi - r discard - r set x 10 - r multi - r incr x - r exec - } {11} - - test {MULTI / EXEC is not propagated (single write command)} { - set repl [attach_to_replication_stream] - r multi - r set foo bar - r exec - r set foo2 bar - assert_replication_stream $repl { - {select *} - {set foo bar} - {set foo2 bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI / EXEC is propagated correctly (multiple commands)} { - set repl [attach_to_replication_stream] - r multi - r set foo{t} bar - r get foo{t} - r set foo2{t} bar2 - r get foo2{t} - r set foo3{t} bar3 - r get foo3{t} - r exec - - assert_replication_stream $repl { - {multi} - {select *} - {set foo{t} bar} - {set foo2{t} bar2} - {set foo3{t} bar3} - {exec} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI / EXEC is propagated correctly (multiple commands with SELECT)} { - set repl [attach_to_replication_stream] - r multi - r select 1 - r set foo{t} bar - r get foo{t} - r select 2 - r set foo2{t} bar2 - r get foo2{t} - r select 3 - r set foo3{t} bar3 - r get foo3{t} - r exec - - assert_replication_stream $repl { - {multi} - {select *} - {set foo{t} bar} - {select *} - {set foo2{t} bar2} - {select *} - {set foo3{t} bar3} - {exec} - } - close_replication_stream $repl - } {} {needs:repl singledb:skip} - - test {MULTI / EXEC is propagated correctly (empty transaction)} { - set repl [attach_to_replication_stream] - r multi - r exec - r set foo bar - assert_replication_stream $repl { - {select *} - {set foo bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI / EXEC is propagated correctly (read-only commands)} { - r set foo value1 - set repl [attach_to_replication_stream] - r multi - r get foo - r exec - r set foo value2 - assert_replication_stream $repl { - {select *} - {set foo value2} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI / EXEC is propagated correctly (write command, no effect)} { - r del bar - r del foo - set repl [attach_to_replication_stream] - r multi - r del foo - r exec - - # add another command so that when we see it we know multi-exec wasn't - # propagated - r incr foo - - assert_replication_stream $repl { - {select *} - {incr foo} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI / EXEC with REPLICAOF} { - # This test verifies that if we demote a master to replica inside a transaction, the - # entire transaction is not propagated to the already-connected replica - set repl [attach_to_replication_stream] - r set foo bar - r multi - r set foo2 bar - r replicaof localhost 9999 - r set foo3 bar - r exec - catch {r set foo4 bar} e - assert_match {READONLY*} $e - assert_replication_stream $repl { - {select *} - {set foo bar} - } - r replicaof no one - } {OK} {needs:repl cluster:skip} - - test {DISCARD should not fail during OOM} { - set rd [redis_deferring_client] - $rd config set maxmemory 1 - assert {[$rd read] eq {OK}} - r multi - catch {r set x 1} e - assert_match {OOM*} $e - r discard - $rd config set maxmemory 0 - assert {[$rd read] eq {OK}} - $rd close - r ping - } {PONG} {needs:config-maxmemory} - - test {MULTI and script timeout} { - # check that if MULTI arrives during timeout, it is either refused, or - # allowed to pass, and we don't end up executing half of the transaction - set rd1 [redis_deferring_client] - set r2 [redis_client] - r config set lua-time-limit 10 - r set xx 1 - $rd1 eval {while true do end} 0 - after 200 - catch { $r2 multi; } e - catch { $r2 incr xx; } e - r script kill - after 200 ; # Give some time to Lua to call the hook again... - catch { $r2 incr xx; } e - catch { $r2 exec; } e - assert_match {EXECABORT*previous errors*} $e - set xx [r get xx] - # make sure that either the whole transcation passed or none of it (we actually expect none) - assert { $xx == 1 || $xx == 3} - # check that the connection is no longer in multi state - set pong [$r2 ping asdf] - assert_equal $pong "asdf" - $rd1 close; $r2 close - } - - test {EXEC and script timeout} { - # check that if EXEC arrives during timeout, we don't end up executing - # half of the transaction, and also that we exit the multi state - set rd1 [redis_deferring_client] - set r2 [redis_client] - r config set lua-time-limit 10 - r set xx 1 - catch { $r2 multi; } e - catch { $r2 incr xx; } e - $rd1 eval {while true do end} 0 - after 200 - catch { $r2 incr xx; } e - catch { $r2 exec; } e - assert_match {EXECABORT*BUSY*} $e - r script kill - after 200 ; # Give some time to Lua to call the hook again... - set xx [r get xx] - # make sure that either the whole transcation passed or none of it (we actually expect none) - assert { $xx == 1 || $xx == 3} - # check that the connection is no longer in multi state - set pong [$r2 ping asdf] - assert_equal $pong "asdf" - $rd1 close; $r2 close - } - - test {MULTI-EXEC body and script timeout} { - # check that we don't run an incomplete transaction due to some commands - # arriving during busy script - set rd1 [redis_deferring_client] - set r2 [redis_client] - r config set lua-time-limit 10 - r set xx 1 - catch { $r2 multi; } e - catch { $r2 incr xx; } e - $rd1 eval {while true do end} 0 - after 200 - catch { $r2 incr xx; } e - r script kill - after 200 ; # Give some time to Lua to call the hook again... - catch { $r2 exec; } e - assert_match {EXECABORT*previous errors*} $e - set xx [r get xx] - # make sure that either the whole transcation passed or none of it (we actually expect none) - assert { $xx == 1 || $xx == 3} - # check that the connection is no longer in multi state - set pong [$r2 ping asdf] - assert_equal $pong "asdf" - $rd1 close; $r2 close - } - - test {just EXEC and script timeout} { - # check that if EXEC arrives during timeout, we don't end up executing - # actual commands during busy script, and also that we exit the multi state - set rd1 [redis_deferring_client] - set r2 [redis_client] - r config set lua-time-limit 10 - r set xx 1 - catch { $r2 multi; } e - catch { $r2 incr xx; } e - $rd1 eval {while true do end} 0 - after 200 - catch { $r2 exec; } e - assert_match {EXECABORT*BUSY*} $e - r script kill - after 200 ; # Give some time to Lua to call the hook again... - set xx [r get xx] - # make we didn't execute the transaction - assert { $xx == 1} - # check that the connection is no longer in multi state - set pong [$r2 ping asdf] - assert_equal $pong "asdf" - $rd1 close; $r2 close - } - - test {exec with write commands and state change} { - # check that exec that contains write commands fails if server state changed since they were queued - set r1 [redis_client] - r set xx 1 - r multi - r incr xx - $r1 config set min-replicas-to-write 2 - catch {r exec} e - assert_match {*EXECABORT*NOREPLICAS*} $e - set xx [r get xx] - # make sure that the INCR wasn't executed - assert { $xx == 1} - $r1 config set min-replicas-to-write 0 - $r1 close - } {0} {needs:repl} - - test {exec with read commands and stale replica state change} { - # check that exec that contains read commands fails if server state changed since they were queued - r config set replica-serve-stale-data no - set r1 [redis_client] - r set xx 1 - - # check that GET and PING are disallowed on stale replica, even if the replica becomes stale only after queuing. - r multi - r get xx - $r1 replicaof localhsot 0 - catch {r exec} e - assert_match {*EXECABORT*MASTERDOWN*} $e - - # reset - $r1 replicaof no one - - r multi - r ping - $r1 replicaof localhsot 0 - catch {r exec} e - assert_match {*EXECABORT*MASTERDOWN*} $e - - # check that when replica is not stale, GET is allowed - # while we're at it, let's check that multi is allowed on stale replica too - r multi - $r1 replicaof no one - r get xx - set xx [r exec] - # make sure that the INCR was executed - assert { $xx == 1 } - $r1 close - } {0} {needs:repl cluster:skip} - - test {EXEC with only read commands should not be rejected when OOM} { - set r2 [redis_client] - - r set x value - r multi - r get x - r ping - - # enforcing OOM - $r2 config set maxmemory 1 - - # finish the multi transaction with exec - assert { [r exec] == {value PONG} } - - # releasing OOM - $r2 config set maxmemory 0 - $r2 close - } {0} {needs:config-maxmemory} - - test {EXEC with at least one use-memory command should fail} { - set r2 [redis_client] - - r multi - r set x 1 - r get x - - # enforcing OOM - $r2 config set maxmemory 1 - - # finish the multi transaction with exec - catch {r exec} e - assert_match {EXECABORT*OOM*} $e - - # releasing OOM - $r2 config set maxmemory 0 - $r2 close - } {0} {needs:config-maxmemory} - - test {Blocking commands ignores the timeout} { - r xgroup create s{t} g $ MKSTREAM - - set m [r multi] - r blpop empty_list{t} 0 - r brpop empty_list{t} 0 - r brpoplpush empty_list1{t} empty_list2{t} 0 - r blmove empty_list1{t} empty_list2{t} LEFT LEFT 0 - r bzpopmin empty_zset{t} 0 - r bzpopmax empty_zset{t} 0 - r xread BLOCK 0 STREAMS s{t} $ - r xreadgroup group g c BLOCK 0 STREAMS s{t} > - set res [r exec] - - list $m $res - } {OK {{} {} {} {} {} {} {} {}}} - - test {MULTI propagation of PUBLISH} { - set repl [attach_to_replication_stream] - - r multi - r publish bla bla - r exec - - assert_replication_stream $repl { - {select *} - {publish bla bla} - } - close_replication_stream $repl - } {} {needs:repl cluster:skip} - - test {MULTI propagation of SCRIPT LOAD} { - set repl [attach_to_replication_stream] - - # make sure that SCRIPT LOAD inside MULTI isn't propagated - r multi - r script load {redis.call('set', KEYS[1], 'foo')} - r set foo bar - set res [r exec] - set sha [lindex $res 0] - - assert_replication_stream $repl { - {select *} - {set foo bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI propagation of EVAL} { - set repl [attach_to_replication_stream] - - # make sure that EVAL inside MULTI is propagated in a transaction in effects - r multi - r eval {redis.call('set', KEYS[1], 'bar')} 1 bar - r exec - - assert_replication_stream $repl { - {select *} - {set bar bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MULTI propagation of SCRIPT FLUSH} { - set repl [attach_to_replication_stream] - - # make sure that SCRIPT FLUSH isn't propagated - r multi - r script flush - r set foo bar - r exec - - assert_replication_stream $repl { - {select *} - {set foo bar} - } - close_replication_stream $repl - } {} {needs:repl} - - tags {"stream"} { - test {MULTI propagation of XREADGROUP} { - set repl [attach_to_replication_stream] - - r XADD mystream * foo bar - r XADD mystream * foo2 bar2 - r XADD mystream * foo3 bar3 - r XGROUP CREATE mystream mygroup 0 - - # make sure the XCALIM (propagated by XREADGROUP) is indeed inside MULTI/EXEC - r multi - r XREADGROUP GROUP mygroup consumer1 COUNT 2 STREAMS mystream ">" - r XREADGROUP GROUP mygroup consumer1 STREAMS mystream ">" - r exec - - assert_replication_stream $repl { - {select *} - {xadd *} - {xadd *} - {xadd *} - {xgroup CREATE *} - {multi} - {xclaim *} - {xclaim *} - {xgroup SETID * ENTRIESREAD *} - {xclaim *} - {xgroup SETID * ENTRIESREAD *} - {exec} - } - close_replication_stream $repl - } {} {needs:repl} - } - - foreach {cmd} {SAVE SHUTDOWN} { - test "MULTI with $cmd" { - r del foo - r multi - r set foo bar - catch {r $cmd} e1 - catch {r exec} e2 - assert_match {*Command not allowed inside a transaction*} $e1 - assert_match {EXECABORT*} $e2 - r get foo - } {} - } - - test "MULTI with BGREWRITEAOF" { - set forks [s total_forks] - r multi - r set foo bar - r BGREWRITEAOF - set res [r exec] - assert_match "*rewriting scheduled*" [lindex $res 1] - wait_for_condition 50 100 { - [s total_forks] > $forks - } else { - fail "aofrw didn't start" - } - waitForBgrewriteaof r - } {} {external:skip} - - test "MULTI with config set appendonly" { - set lines [count_log_lines 0] - set forks [s total_forks] - r multi - r set foo bar - r config set appendonly yes - r exec - verify_log_message 0 "*AOF background was scheduled*" $lines - wait_for_condition 50 100 { - [s total_forks] > $forks - } else { - fail "aofrw didn't start" - } - waitForBgrewriteaof r - } {} {external:skip} - - test "MULTI with config error" { - r multi - r set foo bar - r config set maxmemory bla - - # letting the redis parser read it, it'll throw an exception instead of - # reply with an array that contains an error, so we switch to reading - # raw RESP instead - r readraw 1 - - set res [r exec] - assert_equal $res "*2" - set res [r read] - assert_equal $res "+OK" - set res [r read] - r readraw 0 - set _ $res - } {*CONFIG SET failed*} - - test "Flushall while watching several keys by one client" { - r flushall - r mset a{t} a b{t} b - r watch b{t} a{t} - r flushall - r ping - } -} - -start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always} tags {external:skip}} { - test {MULTI with FLUSHALL and AOF} { - set aof [get_last_incr_aof_path r] - r multi - r set foo bar - r flushall - r exec - assert_aof_content $aof { - {multi} - {select *} - {set *} - {flushall} - {exec} - } - r get foo - } {} -} diff --git a/examples/redis-unstable/tests/unit/networking.tcl b/examples/redis-unstable/tests/unit/networking.tcl deleted file mode 100644 index 6a04c22..0000000 --- a/examples/redis-unstable/tests/unit/networking.tcl +++ /dev/null @@ -1,446 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2025-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -source tests/support/cli.tcl - -test {CONFIG SET port number} { - start_server {} { - if {$::tls} { set port_cfg tls-port} else { set port_cfg port } - - # available port - set avail_port [find_available_port $::baseport $::portcount] - set rd [redis [srv 0 host] [srv 0 port] 0 $::tls] - $rd CONFIG SET $port_cfg $avail_port - $rd close - set rd [redis [srv 0 host] $avail_port 0 $::tls] - $rd PING - - # already inuse port - catch {$rd CONFIG SET $port_cfg $::test_server_port} e - assert_match {*Unable to listen on this port*} $e - $rd close - - # make sure server still listening on the previous port - set rd [redis [srv 0 host] $avail_port 0 $::tls] - $rd PING - $rd close - } -} {} {external:skip} - -test {CONFIG SET bind address} { - start_server {} { - # non-valid address - catch {r CONFIG SET bind "999.999.999.999"} e - assert_match {*Failed to bind to specified addresses*} $e - - # make sure server still bound to the previous address - set rd [redis [srv 0 host] [srv 0 port] 0 $::tls] - $rd PING - $rd close - } -} {} {external:skip} - -# Attempt to connect to host using a client bound to bindaddr, -# and return a non-zero value if successful within specified -# millisecond timeout, or zero otherwise. -proc test_loopback {host bindaddr timeout} { - if {[exec uname] != {Linux}} { - return 0 - } - - after $timeout set ::test_loopback_state timeout - if {[catch { - set server_sock [socket -server accept 0] - set port [lindex [fconfigure $server_sock -sockname] 2] } err]} { - return 0 - } - - proc accept {channel clientaddr clientport} { - set ::test_loopback_state "connected" - close $channel - } - - if {[catch {set client_sock [socket -async -myaddr $bindaddr $host $port]} err]} { - puts "test_loopback: Client connect failed: $err" - } else { - close $client_sock - } - - vwait ::test_loopback_state - close $server_sock - - return [expr {$::test_loopback_state == {connected}}] -} - -test {CONFIG SET bind-source-addr} { - if {[test_loopback 127.0.0.1 127.0.0.2 1000]} { - start_server {} { - start_server {} { - set replica [srv 0 client] - set master [srv -1 client] - - $master config set protected-mode no - - $replica config set bind-source-addr 127.0.0.2 - $replica replicaof [srv -1 host] [srv -1 port] - - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - - assert_match {*ip=127.0.0.2*} [s -1 slave0] - } - } - } else { - if {$::verbose} { puts "Skipping bind-source-addr test." } - } -} {} {external:skip} - -start_server {config "minimal.conf" tags {"external:skip"}} { - test {Default bind address configuration handling} { - # Default is explicit and sane - assert_equal "* -::*" [lindex [r CONFIG GET bind] 1] - - # CONFIG REWRITE acknowledges this as a default - r CONFIG REWRITE - assert_equal 0 [count_message_lines [srv 0 config_file] bind] - - # Removing the bind address works - r CONFIG SET bind "" - assert_equal "" [lindex [r CONFIG GET bind] 1] - - # No additional clients can connect - catch {redis_client} err - assert_match {*connection refused*} $err - - # CONFIG REWRITE handles empty bindaddr - r CONFIG REWRITE - assert_equal 1 [count_message_lines [srv 0 config_file] bind] - - # Make sure we're able to restart - restart_server 0 0 0 0 - - # Make sure bind parameter is as expected and server handles binding - # accordingly. - # (it seems that rediscli_exec behaves differently in RESP3, possibly - # because CONFIG GET returns a dict instead of a list so redis-cli emits - # it in a single line) - if {$::force_resp3} { - assert_equal {{bind }} [rediscli_exec 0 config get bind] - } else { - assert_equal {bind {}} [rediscli_exec 0 config get bind] - } - catch {reconnect 0} err - assert_match {*connection refused*} $err - - assert_equal {OK} [rediscli_exec 0 config set bind *] - reconnect 0 - r ping - } {PONG} - - test {Protected mode works as expected} { - # Get a non-loopback address of this instance for this test. - set myaddr [get_nonloopback_addr] - if {$myaddr != "" && ![string match {127.*} $myaddr]} { - # Non-loopback client should fail by default - set r2 [get_nonloopback_client] - catch {$r2 ping} err - assert_match {*DENIED*} $err - - # Bind configuration should not matter - assert_equal {OK} [r config set bind "*"] - set r2 [get_nonloopback_client] - catch {$r2 ping} err - assert_match {*DENIED*} $err - - # Setting a password should disable protected mode - assert_equal {OK} [r config set requirepass "secret"] - set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] - assert_equal {OK} [$r2 auth secret] - assert_equal {PONG} [$r2 ping] - - # Clearing the password re-enables protected mode - assert_equal {OK} [r config set requirepass ""] - set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] - assert_match {*DENIED*} $err - - # Explicitly disabling protected-mode works - assert_equal {OK} [r config set protected-mode no] - set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] - assert_equal {PONG} [$r2 ping] - } - } -} - -start_server {config "minimal.conf" tags {"external:skip"} overrides {enable-debug-command {yes} io-threads 2}} { - set server_pid [s process_id] - # Since each thread may perform memory prefetch independently, this test is - # only run when the number of IO threads is 2 to ensure deterministic results. - if {[r config get io-threads] eq "io-threads 2"} { - test {prefetch works as expected when killing a client from the middle of prefetch commands batch} { - # Create 16 (prefetch batch size) +1 clients - for {set i 0} {$i < 16} {incr i} { - set rd$i [redis_deferring_client] - } - - # set a key that will be later be prefetch - r set a 0 - - # Get the client ID of rd4 - $rd4 client id - set rd4_id [$rd4 read] - - # Create a batch of commands by suspending the server for a while - # before responding to the first command - pause_process $server_pid - - # The first client will kill the fourth client - $rd0 client kill id $rd4_id - - # Send set commands for all clients except the first - for {set i 1} {$i < 16} {incr i} { - [set rd$i] set $i $i - [set rd$i] flush - } - - # Resume the server - resume_process $server_pid - - # Read the results - assert_equal {1} [$rd0 read] - catch {$rd4 read} res - if {$res eq "OK"} { - # maybe OK then err, we can not control the order of execution - catch {$rd4 read} err - } else { - set err $res - } - assert_match {I/O error reading reply} $err - - # verify the prefetch stats are as expected - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - assert_range $prefetch_entries 2 15; # With slower machines, the number of prefetch entries can be lower - set prefetch_batches [getInfoProperty $info io_threaded_total_prefetch_batches] - assert_range $prefetch_batches 1 7; # With slower machines, the number of batches can be higher - - # verify other clients are working as expected - for {set i 1} {$i < 16} {incr i} { - if {$i != 4} { ;# 4th client was killed - [set rd$i] get $i - assert_equal {OK} [[set rd$i] read] - assert_equal $i [[set rd$i] read] - } - } - } - - test {prefetch works as expected when changing the batch size while executing the commands batch} { - # Create 16 (default prefetch batch size) clients - for {set i 0} {$i < 16} {incr i} { - set rd$i [redis_deferring_client] - } - - # Create a batch of commands by suspending the server for a while - # before responding to the first command - pause_process $server_pid - - # Send set commands for all clients the 5th client will change the prefetch batch size - for {set i 0} {$i < 16} {incr i} { - if {$i == 4} { - [set rd$i] config set prefetch-batch-max-size 1 - } - [set rd$i] set a $i - [set rd$i] flush - } - # Resume the server - resume_process $server_pid - # Read the results - for {set i 0} {$i < 16} {incr i} { - assert_equal {OK} [[set rd$i] read] - [set rd$i] close - } - - # assert the configured prefetch batch size was changed - assert {[r config get prefetch-batch-max-size] eq "prefetch-batch-max-size 1"} - } - - proc do_prefetch_batch {server_pid batch_size} { - # Create clients - for {set i 0} {$i < $batch_size} {incr i} { - set rd$i [redis_deferring_client] - } - - # Suspend the server to batch the commands - pause_process $server_pid - - # Send commands from all clients - for {set i 0} {$i < $batch_size} {incr i} { - [set rd$i] set a $i - [set rd$i] flush - } - - # Resume the server to process the batch - resume_process $server_pid - - # Verify responses - for {set i 0} {$i < $batch_size} {incr i} { - assert_equal {OK} [[set rd$i] read] - [set rd$i] close - } - } - - test {no prefetch when the batch size is set to 0} { - # set the batch size to 0 - r config set prefetch-batch-max-size 0 - # save the current value of prefetch entries - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - - do_prefetch_batch $server_pid 16 - - # assert the prefetch entries did not change - set info [r info stats] - set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - assert_equal $prefetch_entries $new_prefetch_entries - } - - test {Prefetch can resume working when the configuration option is set to a non-zero value} { - # save the current value of prefetch entries - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # set the batch size to 0 - r config set prefetch-batch-max-size 16 - - do_prefetch_batch $server_pid 16 - - # assert the prefetch entries did not change - set info [r info stats] - set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # With slower machines, the number of prefetch entries can be lower - assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 16}] - } - - test {Prefetch works with batch size greater than 16 (buffer overflow regression test)} { - # save the current value of prefetch entries - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # set the batch size to a value greater than the old hardcoded limit of 16 - r config set prefetch-batch-max-size 64 - - # Create a batch with more than 16 clients to trigger the old buffer overflow - do_prefetch_batch $server_pid 64 - - # verify the prefetch entries increased - set info [r info stats] - set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # With slower machines, the number of prefetch entries can be lower - assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 64}] - } - - test {Prefetch works with maximum batch size of 128 and client number larger than batch size} { - # save the current value of prefetch entries - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # set the batch size to the maximum allowed value - r config set prefetch-batch-max-size 128 - - # Create a batch with 300 clients to test the maximum limit - do_prefetch_batch $server_pid 300 - - # verify the prefetch entries increased - set info [r info stats] - set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # With slower machines, the number of prefetch entries can be lower - assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 300}] - } - } -} - -start_server {tags {"timeout external:skip"}} { - test {Multiple clients idle timeout test} { - # set client timeout to 1 second - r config set timeout 1 - - # create multiple client connections - set clients {} - set num_clients 10 - - for {set i 0} {$i < $num_clients} {incr i} { - set client [redis_deferring_client] - $client ping - assert_equal "PONG" [$client read] - lappend clients $client - } - assert_equal [llength $clients] $num_clients - - # wait for 2.5 seconds - after 2500 - - # try to send commands to all clients - they should all fail due to timeout - set disconnected_count 0 - foreach client $clients { - $client ping - if {[catch {$client read} err]} { - incr disconnected_count - # expected error patterns for connection timeout - assert_match {*I/O error*} $err - } - catch {$client close} - } - - # all clients should have been disconnected due to timeout - assert_equal $disconnected_count $num_clients - - # redis server still works well - reconnect - assert_equal "PONG" [r ping] - } -} - -test {Pending command pool expansion and shrinking} { - start_server {overrides {loglevel debug io-threads 1} tags {external:skip}} { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - # Client1 sends 16 commands in pipeline, and was blocked at the first command - set buf "" - append buf "blpop mylist 0\r\n" - for {set i 1} {$i < 16} {incr i} { - append buf "set key$i value$i\r\n" - } - $rd1 write $buf - $rd1 flush - wait_for_blocked_clients_count 1 - - # Client2 sends 1 command, this will trigger pending command pool expansion - # from 16 to 32 since A client has used up all 16 commands in the command pool. - $rd2 set bkey bvalue - assert_equal {OK} [$rd2 read] - - # Unblock client1, allowing it to return all pending commands back to the pool. - r lpush mylist unblock_value - assert_equal {mylist unblock_value} [$rd1 read] - for {set i 1} {$i < 16} {incr i} { - assert_equal {OK} [$rd1 read] - } - - # Wait for the pending command pool to shrink back to 16 due to low utilization. - wait_for_log_messages 0 {"*Shrunk pending command pool: capacity 32->16*"} 0 10 1000 - - $rd1 close - $rd2 close - } -} diff --git a/examples/redis-unstable/tests/unit/obuf-limits.tcl b/examples/redis-unstable/tests/unit/obuf-limits.tcl deleted file mode 100644 index 148187b..0000000 --- a/examples/redis-unstable/tests/unit/obuf-limits.tcl +++ /dev/null @@ -1,240 +0,0 @@ -start_server {tags {"obuf-limits external:skip logreqres:skip"}} { - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - test {CONFIG SET client-output-buffer-limit} { - set oldval [lindex [r config get client-output-buffer-limit] 1] - - catch {r config set client-output-buffer-limit "wrong number"} e - assert_match {*Wrong*arguments*} $e - - catch {r config set client-output-buffer-limit "invalid_class 10mb 10mb 60"} e - assert_match {*Invalid*client*class*} $e - catch {r config set client-output-buffer-limit "master 10mb 10mb 60"} e - assert_match {*Invalid*client*class*} $e - - catch {r config set client-output-buffer-limit "normal 10mbs 10mb 60"} e - assert_match {*Error*hard*} $e - - catch {r config set client-output-buffer-limit "replica 10mb 10mbs 60"} e - assert_match {*Error*soft*} $e - - catch {r config set client-output-buffer-limit "pubsub 10mb 10mb 60s"} e - assert_match {*Error*soft_seconds*} $e - - r config set client-output-buffer-limit "normal 1mb 2mb 60 replica 3mb 4mb 70 pubsub 5mb 6mb 80" - set res [lindex [r config get client-output-buffer-limit] 1] - assert_equal $res "normal 1048576 2097152 60 slave 3145728 4194304 70 pubsub 5242880 6291456 80" - - # Set back to the original value. - r config set client-output-buffer-limit $oldval - } - - test {Client output buffer hard limit is enforced} { - r config set client-output-buffer-limit {pubsub 100000 0 0} - set rd1 [redis_deferring_client] - - $rd1 subscribe foo - set reply [$rd1 read] - assert {$reply eq "subscribe foo 1"} - - set omem 0 - while 1 { - # The larger content size ensures that client.buf gets filled more quickly, - # allowing us to correctly observe the gradual increase of `omem` - r publish foo [string repeat bar 50] - set clients [split [r client list] "\r\n"] - set c [split [lindex $clients 1] " "] - if {![regexp {omem=([0-9]+)} $c - omem]} break - if {$omem > 200000} break - } - assert {$omem >= 70000 && $omem < 200000} - $rd1 close - } - - foreach {soft_limit_time wait_for_timeout} {3 yes - 4 no } { - if $wait_for_timeout { - set test_name "Client output buffer soft limit is enforced if time is overreached" - } else { - set test_name "Client output buffer soft limit is not enforced too early and is enforced when no traffic" - } - - test $test_name { - r config set client-output-buffer-limit "pubsub 0 100000 $soft_limit_time" - set soft_limit_time [expr $soft_limit_time*1000] - set rd1 [redis_deferring_client] - - $rd1 client setname test_client - set reply [$rd1 read] - assert {$reply eq "OK"} - - $rd1 subscribe foo - set reply [$rd1 read] - assert {$reply eq "subscribe foo 1"} - - set omem 0 - set start_time 0 - set time_elapsed 0 - set last_under_limit_time [clock milliseconds] - while 1 { - r publish foo [string repeat "x" 1000] - set clients [split [r client list] "\r\n"] - set c [lsearch -inline $clients *name=test_client*] - if {$start_time != 0} { - set time_elapsed [expr {[clock milliseconds]-$start_time}] - # Make sure test isn't taking too long - assert {$time_elapsed <= [expr $soft_limit_time+3000]} - } - if {$wait_for_timeout && $c == ""} { - # Make sure we're disconnected when we reach the soft limit - assert {$omem >= 100000 && $time_elapsed >= $soft_limit_time} - break - } else { - assert {[regexp {omem=([0-9]+)} $c - omem]} - } - if {$omem > 100000} { - if {$start_time == 0} {set start_time $last_under_limit_time} - if {!$wait_for_timeout && $time_elapsed >= [expr $soft_limit_time-1000]} break - # Slow down loop when omem has reached the limit. - after 10 - } else { - # if the OS socket buffers swallowed what we previously filled, reset the start timer. - set start_time 0 - set last_under_limit_time [clock milliseconds] - } - } - - if {!$wait_for_timeout} { - # After we completely stopped the traffic, wait for soft limit to time out - set timeout [expr {$soft_limit_time+1500 - ([clock milliseconds]-$start_time)}] - wait_for_condition [expr $timeout/10] 10 { - [lsearch [split [r client list] "\r\n"] *name=test_client*] == -1 - } else { - fail "Soft limit timed out but client still connected" - } - } - - $rd1 close - } - } - - test {No response for single command if client output buffer hard limit is enforced} { - r config set latency-tracking no - r config set client-output-buffer-limit {normal 100000 0 0} - # Total size of all items must be more than 100k - set item [string repeat "x" 1000] - for {set i 0} {$i < 150} {incr i} { - r lpush mylist $item - } - set orig_mem [s used_memory] - # Set client name and get all items - set rd [redis_deferring_client] - $rd client setname mybiglist - assert {[$rd read] eq "OK"} - $rd lrange mylist 0 -1 - $rd flush - after 100 - - # Before we read reply, redis will close this client. - set clients [r client list] - assert_no_match "*name=mybiglist*" $clients - set cur_mem [s used_memory] - # 10k just is a deviation threshold - assert {$cur_mem < 10000 + $orig_mem} - - # Read nothing - set fd [$rd channel] - assert_equal {} [$rd rawread] - } - - # Note: This test assumes that what's written with one write, will be read by redis in one read. - # this assumption is wrong, but seem to work empirically (for now) - test {No response for multi commands in pipeline if client output buffer limit is enforced} { - r config set client-output-buffer-limit {normal 100000 0 0} - set value [string repeat "x" 10000] - r set bigkey $value - set rd [redis_deferring_client] - $rd client setname multicommands - assert_equal "OK" [$rd read] - - set server_pid [s process_id] - # Pause the server, so that the client's write will be buffered - pause_process $server_pid - - # Create a pipeline of commands that will be processed in one socket read. - # It is important to use one write, in TLS mode independent writes seem - # to wait for response from the server. - # Total size should be less than OS socket buffer, redis can - # execute all commands in this pipeline when it wakes up. - set buf "" - for {set i 0} {$i < 15} {incr i} { - append buf "set $i $i\r\n" - append buf "get $i\r\n" - append buf "del $i\r\n" - # One bigkey is 10k, total response size must be more than 100k - append buf "get bigkey\r\n" - } - $rd write $buf - $rd flush - - # Resume the server to process the pipeline in one go - resume_process $server_pid - # Make sure the pipeline of commands is processed - wait_for_condition 100 10 { - [expr {[regexp {calls=(\d+)} [cmdrstat get r] -> calls] ? $calls : 0}] >= 5 - } else { - fail "the pipeline of commands commands is not processed" - } - - # Redis must wake up if it can send reply - assert_equal "PONG" [r ping] - set clients [r client list] - assert_no_match "*name=multicommands*" $clients - assert_equal {} [$rd rawread] - } - - test {Execute transactions completely even if client output buffer limit is enforced} { - r config set client-output-buffer-limit {normal 100000 0 0} - # Total size of all items must be more than 100k - set item [string repeat "x" 1000] - for {set i 0} {$i < 150} {incr i} { - r lpush mylist2 $item - } - - # Output buffer limit is enforced during executing transaction - r client setname transactionclient - r set k1 v1 - r multi - r set k2 v2 - r get k2 - r lrange mylist2 0 -1 - r set k3 v3 - r del k1 - catch {[r exec]} e - assert_match "*I/O error*" $e - reconnect - set clients [r client list] - assert_no_match "*name=transactionclient*" $clients - - # Transactions should be executed completely - assert_equal {} [r get k1] - assert_equal "v2" [r get k2] - assert_equal "v3" [r get k3] - } - - test "Obuf limit, HRANDFIELD with huge count stopped mid-run" { - r config set client-output-buffer-limit {normal 1000000 0 0} - r hset myhash a b - catch {r hrandfield myhash -999999999} e - assert_match "*I/O error*" $e - reconnect - } - - test "Obuf limit, KEYS stopped mid-run" { - r config set client-output-buffer-limit {normal 100000 0 0} - populate 1000 "long-key-name-prefix-of-100-chars-------------------------------------------------------------------" - catch {r keys *} e - assert_match "*I/O error*" $e - reconnect - } -} diff --git a/examples/redis-unstable/tests/unit/oom-score-adj.tcl b/examples/redis-unstable/tests/unit/oom-score-adj.tcl deleted file mode 100644 index 4c32a31..0000000 --- a/examples/redis-unstable/tests/unit/oom-score-adj.tcl +++ /dev/null @@ -1,144 +0,0 @@ -set system_name [string tolower [exec uname -s]] - -if {$system_name eq {linux}} { - start_server {tags {"oom-score-adj external:skip"}} { - proc get_oom_score_adj {{pid ""}} { - if {$pid == ""} { - set pid [srv 0 pid] - } - set fd [open "/proc/$pid/oom_score_adj" "r"] - set val [gets $fd] - close $fd - - return $val - } - - proc set_oom_score_adj {score {pid ""}} { - if {$pid == ""} { - set pid [srv 0 pid] - } - set fd [open "/proc/$pid/oom_score_adj" "w"] - puts $fd $score - close $fd - } - - test {CONFIG SET oom-score-adj works as expected} { - set base [get_oom_score_adj] - - # Enable oom-score-adj, check defaults - r config set oom-score-adj-values "10 20 30" - r config set oom-score-adj yes - - assert {[get_oom_score_adj] == [expr $base + 10]} - - # Modify current class - r config set oom-score-adj-values "15 20 30" - assert {[get_oom_score_adj] == [expr $base + 15]} - - # Check replica class - r replicaof localhost 1 - assert {[get_oom_score_adj] == [expr $base + 20]} - r replicaof no one - assert {[get_oom_score_adj] == [expr $base + 15]} - - # Check child process - r set key-a value-a - r config set rdb-key-save-delay 1000000 - r bgsave - - set child_pid [get_child_pid 0] - # Wait until background child process to setOOMScoreAdj success. - wait_for_condition 100 10 { - [get_oom_score_adj $child_pid] == [expr $base + 30] - } else { - fail "Set oom-score-adj of background child process is not ok" - } - } - - # Determine whether the current user is unprivileged - set original_value [exec cat /proc/self/oom_score_adj] - catch { - set fd [open "/proc/self/oom_score_adj" "w"] - puts $fd -1000 - close $fd - } e - # Failed oom-score-adj tests can only run unprivileged - if {[string match "*permission denied*" $e]} { - test {CONFIG SET oom-score-adj handles configuration failures} { - # Bad config - r config set oom-score-adj no - r config set oom-score-adj-values "-1000 -1000 -1000" - - # Make sure it fails - catch {r config set oom-score-adj yes} e - assert_match {*Failed to set*} $e - - # Make sure it remains off - assert {[r config get oom-score-adj] == "oom-score-adj no"} - - # Fix config - r config set oom-score-adj-values "0 100 100" - r config set oom-score-adj yes - - # Make sure it fails - catch {r config set oom-score-adj-values "-1000 -1000 -1000"} e - assert_match {*Failed*} $e - - # Make sure previous values remain - assert {[r config get oom-score-adj-values] == {oom-score-adj-values {0 100 100}}} - } - } else { - # Restore the original oom_score_adj value - set fd [open "/proc/self/oom_score_adj" "w"] - puts $fd $original_value - close $fd - } - - test {CONFIG SET oom-score-adj-values doesn't touch proc when disabled} { - set orig_osa [get_oom_score_adj] - - set other_val1 [expr $orig_osa + 1] - set other_val2 [expr $orig_osa + 2] - - r config set oom-score-adj no - - set_oom_score_adj $other_val2 - assert_equal [get_oom_score_adj] $other_val2 - - r config set oom-score-adj-values "$other_val1 $other_val1 $other_val1" - - assert_equal [get_oom_score_adj] $other_val2 - } - - test {CONFIG SET oom score restored on disable} { - r config set oom-score-adj no - set custom_oom [expr [get_oom_score_adj] + 1] - set_oom_score_adj $custom_oom - assert_equal [get_oom_score_adj] $custom_oom - - r config set oom-score-adj-values "9 9 9" oom-score-adj yes - assert_equal [get_oom_score_adj] [expr 9+$custom_oom] - - r config set oom-score-adj no - assert_equal [get_oom_score_adj] $custom_oom - } - - test {CONFIG SET oom score relative and absolute} { - r config set oom-score-adj no - set base_oom [get_oom_score_adj] - - set custom_oom 9 - r config set oom-score-adj-values "$custom_oom $custom_oom $custom_oom" oom-score-adj relative - assert_equal [get_oom_score_adj] [expr $base_oom+$custom_oom] - - set custom_oom [expr [get_oom_score_adj] + 1] - r config set oom-score-adj-values "$custom_oom $custom_oom $custom_oom" oom-score-adj absolute - assert_equal [get_oom_score_adj] $custom_oom - } - - test {CONFIG SET out-of-range oom score} { - assert_error {ERR *must be between -2000 and 2000*} {r config set oom-score-adj-values "-2001 -2001 -2001"} - assert_error {ERR *must be between -2000 and 2000*} {r config set oom-score-adj-values "2001 2001 2001"} - } - } -} diff --git a/examples/redis-unstable/tests/unit/other.tcl b/examples/redis-unstable/tests/unit/other.tcl deleted file mode 100644 index 2faa7e9..0000000 --- a/examples/redis-unstable/tests/unit/other.tcl +++ /dev/null @@ -1,733 +0,0 @@ -start_server {tags {"other"}} { - if {$::force_failure} { - # This is used just for test suite development purposes. - test {Failing test} { - format err - } {ok} - } - - test {Coverage: HELP commands} { - assert_match "*OBJECT *" [r OBJECT HELP] - assert_match "*MEMORY *" [r MEMORY HELP] - assert_match "*PUBSUB *" [r PUBSUB HELP] - assert_match "*SLOWLOG *" [r SLOWLOG HELP] - assert_match "*CLIENT *" [r CLIENT HELP] - assert_match "*COMMAND *" [r COMMAND HELP] - assert_match "*CONFIG *" [r CONFIG HELP] - assert_match "*FUNCTION *" [r FUNCTION HELP] - assert_match "*MODULE *" [r MODULE HELP] - } - - test {Coverage: MEMORY MALLOC-STATS} { - if {[string match {*jemalloc*} [s mem_allocator]]} { - assert_match "*jemalloc*" [r memory malloc-stats] - } - } - - test {Coverage: MEMORY PURGE} { - if {[string match {*jemalloc*} [s mem_allocator]]} { - assert_equal {OK} [r memory purge] - } - } - - test {SAVE - make sure there are all the types as values} { - # Wait for a background saving in progress to terminate - waitForBgsave r - r lpush mysavelist hello - r lpush mysavelist world - r set myemptykey {} - r set mynormalkey {blablablba} - r zadd mytestzset 10 a - r zadd mytestzset 20 b - r zadd mytestzset 30 c - r save - } {OK} {needs:save} - - tags {slow} { - if {$::accurate} {set iterations 10000} else {set iterations 1000} - foreach fuzztype {binary alpha compr} { - test "FUZZ stresser with data model $fuzztype" { - set err 0 - for {set i 0} {$i < $iterations} {incr i} { - set fuzz [randstring 0 512 $fuzztype] - r set foo $fuzz - set got [r get foo] - if {$got ne $fuzz} { - set err [list $fuzz $got] - break - } - } - set _ $err - } {0} - } - } - - start_server {overrides {save ""} tags {external:skip}} { - test {FLUSHALL should not reset the dirty counter if we disable save} { - r set key value - r flushall - assert_morethan [s rdb_changes_since_last_save] 0 - } - - test {FLUSHALL should reset the dirty counter to 0 if we enable save} { - r config set save "3600 1 300 100 60 10000" - r set key value - r flushall - assert_equal [s rdb_changes_since_last_save] 0 - } - - test {FLUSHALL and bgsave} { - r config set save "3600 1 300 100 60 10000" - r set x y - r bgsave - r set x y - r multi - r debug sleep 1 - # by the time we'll get to run flushall, the child will finish, - # but the parent will be unaware of it, and it could wrongly set the dirty counter. - r flushall - r exec - assert_equal [s rdb_changes_since_last_save] 0 - } - } - - test {BGSAVE} { - # Use FLUSHALL instead of FLUSHDB, FLUSHALL do a foreground save - # and reset the dirty counter to 0, so we won't trigger an unexpected bgsave. - r flushall - r save - r set x 10 - r bgsave - waitForBgsave r - r debug reload - r get x - } {10} {needs:debug needs:save} - - test {SELECT an out of range DB} { - catch {r select 1000000} err - set _ $err - } {*index is out of range*} {cluster:skip} - - tags {consistency} { - proc check_consistency {dumpname code} { - set dump [csvdump r] - set sha1 [debug_digest] - - uplevel 1 $code - - set sha1_after [debug_digest] - if {$sha1 eq $sha1_after} { - return 1 - } - - # Failed - set newdump [csvdump r] - puts "Consistency test failed!" - puts "You can inspect the two dumps in /tmp/${dumpname}*.txt" - - set fd [open /tmp/${dumpname}1.txt w] - puts $fd $dump - close $fd - set fd [open /tmp/${dumpname}2.txt w] - puts $fd $newdump - close $fd - - return 0 - } - - if {$::accurate} {set numops 10000} else {set numops 1000} - test {Check consistency of different data types after a reload} { - r flushdb - # TODO: integrate usehexpire following next commit that will support replication - createComplexDataset r $numops {usetag usehexpire} - if {$::ignoredigest} { - set _ 1 - } else { - check_consistency {repldump} { - r debug reload - } - } - } {1} {needs:debug} - - test {Same dataset digest if saving/reloading as AOF?} { - if {$::ignoredigest} { - set _ 1 - } else { - check_consistency {aofdump} { - r config set aof-use-rdb-preamble no - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - } - } - } {1} {needs:debug} - } - - test {EXPIRES after a reload (snapshot + append only file rewrite)} { - r flushdb - r set x 10 - r expire x 1000 - r save - r debug reload - set ttl [r ttl x] - set e1 [expr {$ttl > 900 && $ttl <= 1000}] - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - set ttl [r ttl x] - set e2 [expr {$ttl > 900 && $ttl <= 1000}] - list $e1 $e2 - } {1 1} {needs:debug needs:save} - - test {EXPIRES after AOF reload (without rewrite)} { - r flushdb - r config set appendonly yes - r config set aof-use-rdb-preamble no - r set x somevalue - r expire x 1000 - r setex y 2000 somevalue - r set z somevalue - r expireat z [expr {[clock seconds]+3000}] - - # Milliseconds variants - r set px somevalue - r pexpire px 1000000 - r psetex py 2000000 somevalue - r set pz somevalue - r pexpireat pz [expr {([clock seconds]+3000)*1000}] - - # Reload and check - waitForBgrewriteaof r - # We need to wait two seconds to avoid false positives here, otherwise - # the DEBUG LOADAOF command may read a partial file. - # Another solution would be to set the fsync policy to no, since this - # prevents write() to be delayed by the completion of fsync(). - after 2000 - r debug loadaof - set ttl [r ttl x] - assert {$ttl > 900 && $ttl <= 1000} - set ttl [r ttl y] - assert {$ttl > 1900 && $ttl <= 2000} - set ttl [r ttl z] - assert {$ttl > 2900 && $ttl <= 3000} - set ttl [r ttl px] - assert {$ttl > 900 && $ttl <= 1000} - set ttl [r ttl py] - assert {$ttl > 1900 && $ttl <= 2000} - set ttl [r ttl pz] - assert {$ttl > 2900 && $ttl <= 3000} - r config set appendonly no - } {OK} {needs:debug} - - tags {protocol} { - test {PIPELINING stresser (also a regression for the old epoll bug)} { - if {$::tls} { - set fd2 [::tls::socket [srv host] [srv port]] - } else { - set fd2 [socket [srv host] [srv port]] - } - fconfigure $fd2 -encoding binary -translation binary - if {!$::singledb} { - puts -nonewline $fd2 "SELECT 9\r\n" - flush $fd2 - gets $fd2 - } - - for {set i 0} {$i < 100000} {incr i} { - set q {} - set val "0000${i}0000" - append q "SET key:$i $val\r\n" - puts -nonewline $fd2 $q - set q {} - append q "GET key:$i\r\n" - puts -nonewline $fd2 $q - } - flush $fd2 - - for {set i 0} {$i < 100000} {incr i} { - gets $fd2 line - gets $fd2 count - set count [string range $count 1 end] - set val [read $fd2 $count] - read $fd2 2 - } - close $fd2 - set _ 1 - } {1} - } - - test {APPEND basics} { - r del foo - list [r append foo bar] [r get foo] \ - [r append foo 100] [r get foo] - } {3 bar 6 bar100} - - test {APPEND basics, integer encoded values} { - set res {} - r del foo - r append foo 1 - r append foo 2 - lappend res [r get foo] - r set foo 1 - r append foo 2 - lappend res [r get foo] - } {12 12} - - test {APPEND fuzzing} { - set err {} - foreach type {binary alpha compr} { - set buf {} - r del x - for {set i 0} {$i < 1000} {incr i} { - set bin [randstring 0 10 $type] - append buf $bin - r append x $bin - } - if {$buf != [r get x]} { - set err "Expected '$buf' found '[r get x]'" - break - } - } - set _ $err - } {} - - # Leave the user with a clean DB before to exit - test {FLUSHDB} { - set aux {} - if {$::singledb} { - r flushdb - lappend aux 0 [r dbsize] - } else { - r select 9 - r flushdb - lappend aux [r dbsize] - r select 10 - r flushdb - lappend aux [r dbsize] - } - } {0 0} - - test {Perform a final SAVE to leave a clean DB on disk} { - waitForBgsave r - r save - } {OK} {needs:save} - - test {RESET clears client state} { - r client setname test-client - r client tracking on - - assert_equal [r reset] "RESET" - set client [r client list] - assert_match {*name= *} $client - assert_match {*flags=N *} $client - } {} {needs:reset} - - test {RESET clears MONITOR state} { - set rd [redis_deferring_client] - $rd monitor - assert_equal [$rd read] "OK" - - $rd reset - assert_equal [$rd read] "RESET" - $rd close - - assert_no_match {*flags=O*} [r client list] - } {} {needs:reset} - - test {RESET clears and discards MULTI state} { - r multi - r set key-a a - - r reset - catch {r exec} err - assert_match {*EXEC without MULTI*} $err - } {} {needs:reset} - - test {RESET clears Pub/Sub state} { - r subscribe channel-1 - r reset - - # confirm we're not subscribed by executing another command - r set key val - } {OK} {needs:reset} - - test {RESET clears authenticated state} { - r acl setuser user1 on >secret +@all - r auth user1 secret - assert_equal [r acl whoami] user1 - - r reset - - assert_equal [r acl whoami] default - } {} {needs:reset} - - test "Subcommand syntax error crash (issue #10070)" { - assert_error {*unknown command*} {r GET|} - assert_error {*unknown command*} {r GET|SET} - assert_error {*unknown command*} {r GET|SET|OTHER} - assert_error {*unknown command*} {r CONFIG|GET GET_XX} - assert_error {*unknown subcommand*} {r CONFIG GET_XX} - } -} - -start_server {tags {"other external:skip"}} { - test {Don't rehash if redis has child process} { - r config set save "" - r config set rdb-key-save-delay 1000000 - - populate 4095 "" 1 - r bgsave - wait_for_condition 10 100 { - [s rdb_bgsave_in_progress] eq 1 - } else { - fail "bgsave did not start in time" - } - - r mset k1 v1 k2 v2 - # Hash table should not rehash - assert_no_match "*table size: 8192*" [r debug HTSTATS 9] - exec kill -9 [get_child_pid 0] - waitForBgsave r - - # Hash table should rehash since there is no child process, - # size is power of two and over 4096, so it is 8192 - wait_for_condition 50 100 { - [string match "*table size: 8192*" [r debug HTSTATS 9]] - } else { - fail "hash table did not rehash after child process killed" - } - } {} {needs:debug needs:local-process} -} - -proc read_proc_title {pid} { - set fd [open "/proc/$pid/cmdline" "r"] - set cmdline [read $fd 1024] - close $fd - - return $cmdline -} - -start_server {tags {"other external:skip"}} { - test {Process title set as expected} { - # Test only on Linux where it's easy to get cmdline without relying on tools. - # Skip valgrind as it messes up the arguments. - set os [exec uname] - if {$os == "Linux" && !$::valgrind} { - # Set a custom template - r config set "proc-title-template" "TEST {title} {listen-addr} {port} {tls-port} {unixsocket} {config-file}" - set cmdline [read_proc_title [srv 0 pid]] - - assert_equal "TEST" [lindex $cmdline 0] - assert_match "*/redis-server" [lindex $cmdline 1] - - if {$::tls} { - set expect_port [srv 0 pport] - set expect_tls_port [srv 0 port] - set port [srv 0 pport] - } else { - set expect_port [srv 0 port] - set expect_tls_port 0 - set port [srv 0 port] - } - - assert_equal "$::host:$port" [lindex $cmdline 2] - assert_equal $expect_port [lindex $cmdline 3] - assert_equal $expect_tls_port [lindex $cmdline 4] - assert_match "*/tests/tmp/server.*/socket" [lindex $cmdline 5] - assert_match "*/tests/tmp/redis.conf.*" [lindex $cmdline 6] - - # Try setting a bad template - catch {r config set "proc-title-template" "{invalid-var}"} err - assert_match {*template format is invalid*} $err - } - } -} - -start_cluster 1 0 {tags {"other external:skip cluster slow"}} { - r config set dynamic-hz no hz 500 - test "Redis can trigger resizing" { - r flushall - # hashslot(foo) is 12182 - for {set j 1} {$j <= 128} {incr j} { - r set "{foo}$j" a - } - assert_match "*table size: 128*" [r debug HTSTATS 0] - - # disable resizing, the reason for not using slow bgsave is because - # it will hit the dict_force_resize_ratio. - r debug dict-resizing 0 - - # delete data to have lot's (96%) of empty buckets - for {set j 1} {$j <= 123} {incr j} { - r del "{foo}$j" - } - assert_match "*table size: 128*" [r debug HTSTATS 0] - - # enable resizing - r debug dict-resizing 1 - - # waiting for serverCron to resize the tables - wait_for_condition 1000 10 { - [string match {*table size: 8*} [r debug HTSTATS 0]] - } else { - puts [r debug HTSTATS 0] - fail "hash tables weren't resize." - } - } {} {needs:debug} - - test "Redis can rewind and trigger smaller slot resizing" { - # hashslot(foo) is 12182 - # hashslot(alice) is 749, smaller than hashslot(foo), - # attempt to trigger a resize on it, see details in #12802. - for {set j 1} {$j <= 128} {incr j} { - r set "{alice}$j" a - } - - # disable resizing, the reason for not using slow bgsave is because - # it will hit the dict_force_resize_ratio. - r debug dict-resizing 0 - - for {set j 1} {$j <= 123} {incr j} { - r del "{alice}$j" - } - - # enable resizing - r debug dict-resizing 1 - - # waiting for serverCron to resize the tables - wait_for_condition 1000 10 { - [string match {*table size: 16*} [r debug HTSTATS 0]] - } else { - puts [r debug HTSTATS 0] - fail "hash tables weren't resize." - } - } {} {needs:debug} -} - -start_server {tags {"other external:skip"}} { - test "Redis can resize empty dict" { - # Write and then delete 128 keys, creating an empty dict - r flushall - - # Add one key to the db just to create the dict and get its initial size - r set x 1 - set initial_size [dict get [r memory stats] db.9 overhead.hashtable.main] - - # Now add 128 keys and then delete them - for {set j 1} {$j <= 128} {incr j} { - r set $j{b} a - } - - for {set j 1} {$j <= 128} {incr j} { - r del $j{b} - } - - # dict must have expanded. Verify it eventually shrinks back to its initial size. - wait_for_condition 100 50 { - [dict get [r memory stats] db.9 overhead.hashtable.main] == $initial_size - } else { - fail "dict did not resize in time to its initial size" - } - } -} - -start_server {tags {"other external:skip"} overrides {cluster-compatibility-sample-ratio 100}} { - test {Cross DB command is incompatible with cluster mode} { - set incompatible_ops [s cluster_incompatible_ops] - - # SELECT with 0 is compatible command in cluster mode - assert_equal {OK} [r select 0] - assert_equal $incompatible_ops [s cluster_incompatible_ops] - - # SELECT with nonzero is incompatible command in cluster mode - assert_equal {OK} [r select 1] - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # SWAPDB is incompatible command in cluster mode - assert_equal {OK} [r swapdb 0 1] - assert_equal [expr $incompatible_ops + 2] [s cluster_incompatible_ops] - - - # If destination db in COPY command is equal to source db, it is compatible - # with cluster mode, otherwise it is incompatible. - r select 0 - r set key1 value1 - set incompatible_ops [s cluster_incompatible_ops] - assert_equal {1} [r copy key1 key2{key1}] ;# destination db is equal to source db - assert_equal $incompatible_ops [s cluster_incompatible_ops] - assert_equal {1} [r copy key2{key1} key1 db 1] ;# destination db is not equal to source db - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # If destination db in MOVE command is not equal to source db, it is incompatible - # with cluster mode. - r set key3 value3 - assert_equal {1} [r move key3 1] - assert_equal [expr $incompatible_ops + 2] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {Function no-cluster flag is incompatible with cluster mode} { - set incompatible_ops [s cluster_incompatible_ops] - - # no-cluster flag is incompatible with cluster mode - r function load {#!lua name=test - redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}} - } - r fcall f1 0 - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # It is compatible without no-cluster flag, should not increase the cluster_incompatible_ops - r function load {#!lua name=test2 - redis.register_function{function_name='f2', callback=function() return 'hello' end} - } - r fcall f2 0 - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {Script no-cluster flag is incompatible with cluster mode} { - set incompatible_ops [s cluster_incompatible_ops] - - # no-cluster flag is incompatible with cluster mode - r eval {#!lua flags=no-cluster - return 1 - } 0 - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # It is compatible without no-cluster flag, should not increase the cluster_incompatible_ops - r eval {#!lua - return 1 - } 0 - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {SORT command incompatible operations with cluster mode} { - set incompatible_ops [s cluster_incompatible_ops] - - # If the BY pattern slot is not equal with the slot of keys, we consider - # an incompatible behavior, otherwise it is compatible, should not increase - # the cluster_incompatible_ops - r lpush mylist 1 2 3 - for {set i 1} {$i < 4} {incr i} { - r set weight_$i [expr 4 - $i] - } - assert_equal {3 2 1} [r sort mylist BY weight_*] - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - # weight{mylist}_* and mylist have the same slot - for {set i 1} {$i < 4} {incr i} { - r set weight{mylist}_$i [expr 4 - $i] - } - assert_equal {3 2 1} [r sort mylist BY weight{mylist}_*] - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # If the GET pattern slot is not equal with the slot of keys, we consider - # an incompatible behavior, otherwise it is compatible, should not increase - # the cluster_incompatible_ops - for {set i 1} {$i < 4} {incr i} { - r set object_$i o_$i - } - assert_equal {o_3 o_2 o_1} [r sort mylist BY weight{mylist}_* GET object_*] - assert_equal [expr $incompatible_ops + 2] [s cluster_incompatible_ops] - # object{mylist}_*, weight{mylist}_* and mylist have the same slot - for {set i 1} {$i < 4} {incr i} { - r set object{mylist}_$i o_$i - } - assert_equal {o_3 o_2 o_1} [r sort mylist BY weight{mylist}_* GET object{mylist}_*] - assert_equal [expr $incompatible_ops + 2] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {Normal cross slot commands are incompatible with cluster mode} { - # Normal cross slot command - set incompatible_ops [s cluster_incompatible_ops] - r mset foo bar bar foo - r del foo bar - assert_equal [expr $incompatible_ops + 2] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {Transaction is incompatible with cluster mode} { - set incompatible_ops [s cluster_incompatible_ops] - - # Incomplete transaction - catch {r EXEC} - r multi - r exec - assert_equal $incompatible_ops [s cluster_incompatible_ops] - - # Transaction, SET and DEL have keys with different slots - r multi - r set foo bar - r del bar - r exec - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {Lua scripts are incompatible with cluster mode} { - # Lua script, declared keys have different slots, it is not a compatible operation - set incompatible_ops [s cluster_incompatible_ops] - r eval {#!lua - redis.call('mset', KEYS[1], 0, KEYS[2], 0) - } 2 foo bar - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # Lua script, no declared keys, but accessing keys have different slots, - # it is not a compatible operation - set incompatible_ops [s cluster_incompatible_ops] - r eval {#!lua - redis.call('mset', 'foo', 0, 'bar', 0) - } 0 - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # Lua script, declared keys have the same slot, but accessing keys - # have different slots in one command, even with flag 'allow-cross-slot-keys', - # it still is not a compatible operation - set incompatible_ops [s cluster_incompatible_ops] - r eval {#!lua flags=allow-cross-slot-keys - redis.call('mset', 'foo', 0, 'bar', 0) - redis.call('mset', KEYS[1], 0, KEYS[2], 0) - } 2 foo bar{foo} - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - - # Lua script, declared keys have the same slot, but accessing keys have different slots - # in multiple commands, and with flag 'allow-cross-slot-keys', it is a compatible operation - set incompatible_ops [s cluster_incompatible_ops] - r eval {#!lua flags=allow-cross-slot-keys - redis.call('set', 'foo', 0) - redis.call('set', 'bar', 0) - redis.call('mset', KEYS[1], 0, KEYS[2], 0) - } 2 foo bar{foo} - assert_equal $incompatible_ops [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {Shard subscribe commands are incompatible with cluster mode} { - set rd1 [redis_deferring_client] - set incompatible_ops [s cluster_incompatible_ops] - assert_equal {1 2} [ssubscribe $rd1 {foo bar}] - assert_equal [expr $incompatible_ops + 1] [s cluster_incompatible_ops] - } {} {cluster:skip} - - test {cluster-compatibility-sample-ratio configuration can work} { - # Disable cluster compatibility sampling, no increase in cluster_incompatible_ops - set incompatible_ops [s cluster_incompatible_ops] - r config set cluster-compatibility-sample-ratio 0 - for {set i 0} {$i < 100} {incr i} { - r mset foo bar$i bar foo$i - } - # Enable cluster compatibility sampling again to show the metric - r config set cluster-compatibility-sample-ratio 1 - assert_equal $incompatible_ops [s cluster_incompatible_ops] - - # 100% sample ratio, all operations should increase cluster_incompatible_ops - set incompatible_ops [s cluster_incompatible_ops] - r config set cluster-compatibility-sample-ratio 100 - for {set i 0} {$i < 100} {incr i} { - r mset foo bar$i bar foo$i - } - assert_equal [expr $incompatible_ops + 100] [s cluster_incompatible_ops] - - # 30% sample ratio, cluster_incompatible_ops should increase between 20% and 40% - set incompatible_ops [s cluster_incompatible_ops] - r config set cluster-compatibility-sample-ratio 30 - for {set i 0} {$i < 1000} {incr i} { - r mset foo bar$i bar foo$i - } - assert_range [s cluster_incompatible_ops] [expr $incompatible_ops + 200] [expr $incompatible_ops + 400] - } {} {cluster:skip} -} diff --git a/examples/redis-unstable/tests/unit/pause.tcl b/examples/redis-unstable/tests/unit/pause.tcl deleted file mode 100644 index c649db9..0000000 --- a/examples/redis-unstable/tests/unit/pause.tcl +++ /dev/null @@ -1,431 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -start_server {tags {"pause network"}} { - test "Test read commands are not blocked by client pause" { - r client PAUSE 100000 WRITE - set rd [redis_deferring_client] - $rd GET FOO - $rd PING - $rd INFO - assert_equal [s 0 blocked_clients] 0 - r client unpause - $rd close - } - - test "Test old pause-all takes precedence over new pause-write (less restrictive)" { - # Scenario: - # 1. Run 'PAUSE ALL' for 200msec - # 2. Run 'PAUSE WRITE' for 10 msec - # 3. Wait 50msec - # 4. 'GET FOO'. - # Expected that: - # - While the time of the second 'PAUSE' is shorter than first 'PAUSE', - # pause-client feature will stick to the longer one, i.e, will be paused - # up to 200msec. - # - The GET command will be postponed ~200msec, even though last command - # paused only WRITE. This is because the first 'PAUSE ALL' command is - # more restrictive than the second 'PAUSE WRITE' and pause-client feature - # preserve most restrictive configuration among multiple settings. - set rd [redis_deferring_client] - $rd SET FOO BAR - $rd read - - set test_start_time [clock milliseconds] - r client PAUSE 200 ALL - r client PAUSE 20 WRITE - after 50 - $rd get FOO - $rd read - set elapsed [expr {[clock milliseconds]-$test_start_time}] - assert_lessthan 200 $elapsed - $rd close - } - - test "Test new pause time is smaller than old one, then old time preserved" { - r client PAUSE 60000 WRITE - r client PAUSE 10 WRITE - after 100 - set rd [redis_deferring_client] - $rd SET FOO BAR - wait_for_blocked_clients_count 1 100 10 - - r client unpause - assert_match "OK" [$rd read] - $rd close - } - - test "Test write commands are paused by RO" { - r client PAUSE 60000 WRITE - - set rd [redis_deferring_client] - $rd SET FOO BAR - wait_for_blocked_clients_count 1 50 100 - - r client unpause - assert_match "OK" [$rd read] - $rd close - } - - test "Test special commands are paused by RO" { - r PFADD pause-hll test - r client PAUSE 100000 WRITE - - # Test that pfcount, which can replicate, is also blocked - set rd [redis_deferring_client] - $rd PFCOUNT pause-hll - wait_for_blocked_clients_count 1 50 100 - - # Test that publish, which adds the message to the replication - # stream is blocked. - set rd2 [redis_deferring_client] - $rd2 publish foo bar - wait_for_blocked_clients_count 2 50 100 - - r client unpause - assert_match "1" [$rd read] - assert_match "0" [$rd2 read] - $rd close - $rd2 close - } - - test "Test read/admin multi-execs are not blocked by pause RO" { - r SET FOO BAR - r client PAUSE 100000 WRITE - set rr [redis_client] - assert_equal [$rr MULTI] "OK" - assert_equal [$rr PING] "QUEUED" - assert_equal [$rr GET FOO] "QUEUED" - assert_match "PONG BAR" [$rr EXEC] - assert_equal [s 0 blocked_clients] 0 - r client unpause - $rr close - } - - test "Test write multi-execs are blocked by pause RO" { - set rd [redis_deferring_client] - $rd MULTI - assert_equal [$rd read] "OK" - $rd SET FOO BAR - assert_equal [$rd read] "QUEUED" - r client PAUSE 60000 WRITE - $rd EXEC - wait_for_blocked_clients_count 1 50 100 - r client unpause - assert_match "OK" [$rd read] - $rd close - } - - test "Test scripts are blocked by pause RO" { - r client PAUSE 60000 WRITE - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - $rd EVAL "return 1" 0 - - # test a script with a shebang and no flags for coverage - $rd2 EVAL {#!lua - return 1 - } 0 - - wait_for_blocked_clients_count 2 50 100 - r client unpause - assert_match "1" [$rd read] - assert_match "1" [$rd2 read] - $rd close - $rd2 close - } - - test "Test RO scripts are not blocked by pause RO" { - r set x y - # create a function for later - r FUNCTION load replace {#!lua name=f1 - redis.register_function{ - function_name='f1', - callback=function() return "hello" end, - flags={'no-writes'} - } - } - - r client PAUSE 6000000 WRITE - set rr [redis_client] - - # test an eval that's for sure not in the script cache - assert_equal [$rr EVAL {#!lua flags=no-writes - return 'unique script' - } 0 - ] "unique script" - - # for sanity, repeat that EVAL on a script that's already cached - assert_equal [$rr EVAL {#!lua flags=no-writes - return 'unique script' - } 0 - ] "unique script" - - # test EVAL_RO on a unique script that's for sure not in the cache - assert_equal [$rr EVAL_RO { - return redis.call('GeT', 'x')..' unique script' - } 1 x - ] "y unique script" - - # test with evalsha - set sha [$rr script load {#!lua flags=no-writes - return 2 - }] - assert_equal [$rr EVALSHA $sha 0] 2 - - # test with function - assert_equal [$rr fcall f1 0] hello - - r client unpause - $rr close - } - - test "Test read-only scripts in multi-exec are not blocked by pause RO" { - r SET FOO BAR - r client PAUSE 100000 WRITE - set rr [redis_client] - assert_equal [$rr MULTI] "OK" - assert_equal [$rr EVAL {#!lua flags=no-writes - return 12 - } 0 - ] QUEUED - assert_equal [$rr EVAL {#!lua flags=no-writes - return 13 - } 0 - ] QUEUED - assert_match "12 13" [$rr EXEC] - assert_equal [s 0 blocked_clients] 0 - r client unpause - $rr close - } - - test "Test write scripts in multi-exec are blocked by pause RO" { - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - - # one with a shebang - $rd MULTI - assert_equal [$rd read] "OK" - $rd EVAL {#!lua - return 12 - } 0 - assert_equal [$rd read] "QUEUED" - - # one without a shebang - $rd2 MULTI - assert_equal [$rd2 read] "OK" - $rd2 EVAL {#!lua - return 13 - } 0 - assert_equal [$rd2 read] "QUEUED" - - r client PAUSE 60000 WRITE - $rd EXEC - $rd2 EXEC - wait_for_blocked_clients_count 2 50 100 - r client unpause - assert_match "12" [$rd read] - assert_match "13" [$rd2 read] - $rd close - $rd2 close - } - - test "Test may-replicate commands are rejected in RO scripts" { - # that's specifically important for CLIENT PAUSE WRITE - assert_error {ERR Write commands are not allowed from read-only scripts. script:*} { - r EVAL_RO "return redis.call('publish','ch','msg')" 0 - } - assert_error {ERR Write commands are not allowed from read-only scripts. script:*} { - r EVAL {#!lua flags=no-writes - return redis.call('publish','ch','msg') - } 0 - } - # make sure that publish isn't blocked from a non-RO script - assert_equal [r EVAL "return redis.call('publish','ch','msg')" 0] 0 - } - - test "Test multiple clients can be queued up and unblocked" { - r client PAUSE 60000 WRITE - set clients [list [redis_deferring_client] [redis_deferring_client] [redis_deferring_client]] - foreach client $clients { - $client SET FOO BAR - } - - wait_for_blocked_clients_count 3 50 100 - r client unpause - foreach client $clients { - assert_match "OK" [$client read] - $client close - } - } - - test "Test clients with syntax errors will get responses immediately" { - r client PAUSE 100000 WRITE - catch {r set FOO} err - assert_match "ERR wrong number of arguments for 'set' command" $err - r client unpause - } - - test "Test both active and passive expires are skipped during client pause" { - set expired_keys [s 0 expired_keys] - r multi - r set foo{t} bar{t} PX 10 - r set bar{t} foo{t} PX 10 - r client PAUSE 50000 WRITE - r exec - - wait_for_condition 10 100 { - [r get foo{t}] == {} && [r get bar{t}] == {} - } else { - fail "Keys were never logically expired" - } - - # No keys should actually have been expired - assert_match $expired_keys [s 0 expired_keys] - - r client unpause - - # Force the keys to expire - r get foo{t} - r get bar{t} - - # Now that clients have been unpaused, expires should go through - assert_match [expr $expired_keys + 2] [s 0 expired_keys] - } - - test "Test that client pause starts at the end of a transaction" { - r MULTI - r SET FOO1{t} BAR - r client PAUSE 60000 WRITE - r SET FOO2{t} BAR - r exec - - set rd [redis_deferring_client] - $rd SET FOO3{t} BAR - - wait_for_blocked_clients_count 1 50 100 - - assert_match "BAR" [r GET FOO1{t}] - assert_match "BAR" [r GET FOO2{t}] - assert_match "" [r GET FOO3{t}] - - r client unpause - assert_match "OK" [$rd read] - $rd close - } - - start_server {tags {needs:repl external:skip}} { - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - - # Avoid PINGs - $master config set repl-ping-replica-period 3600 - r replicaof $master_host $master_port - - wait_for_condition 50 100 { - [s master_link_status] eq {up} - } else { - fail "Replication not started." - } - - test "Test when replica paused, offset would not grow" { - $master set foo bar - set old_master_offset [status $master master_repl_offset] - - wait_for_condition 50 100 { - [s slave_repl_offset] == [status $master master_repl_offset] - } else { - fail "Replication offset not matched." - } - - r client pause 100000 write - $master set foo2 bar2 - - # Make sure replica received data from master - wait_for_condition 50 100 { - [s slave_read_repl_offset] == [status $master master_repl_offset] - } else { - fail "Replication not work." - } - - # Replica would not apply the write command - assert {[s slave_repl_offset] == $old_master_offset} - r get foo2 - } {} - - test "Test replica offset would grow after unpause" { - r client unpause - wait_for_condition 50 100 { - [s slave_repl_offset] == [status $master master_repl_offset] - } else { - fail "Replication not continue." - } - r get foo2 - } {bar2} - } - - test "Test the randomkey command will not cause the server to get into an infinite loop during the client pause write" { - r flushall - - r multi - r set key value px 3 - r client pause 10000 write - r exec - - after 5 - - wait_for_condition 50 100 { - [r randomkey] == "key" - } else { - fail "execute randomkey failed, caused by the infinite loop" - } - - r client unpause - assert_equal [r randomkey] {} - } - - test "CLIENT UNBLOCK is not allow to unblock client blocked by CLIENT PAUSE" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - $rd1 client id - $rd2 client id - set client_id1 [$rd1 read] - set client_id2 [$rd2 read] - - r del mylist - r client pause 100000 write - $rd1 blpop mylist 0 - $rd2 blpop mylist 0 - wait_for_blocked_clients_count 2 50 100 - - # This used to trigger a panic. - assert_equal 0 [r client unblock $client_id1 timeout] - # This used to return a UNBLOCKED error. - assert_equal 0 [r client unblock $client_id2 error] - - # After the unpause, it must be able to unblock the client. - r client unpause - assert_equal 1 [r client unblock $client_id1 timeout] - assert_equal 1 [r client unblock $client_id2 error] - assert_equal {} [$rd1 read] - assert_error "UNBLOCKED*" {$rd2 read} - - $rd1 close - $rd2 close - } - - # Make sure we unpause at the end - r client unpause -} diff --git a/examples/redis-unstable/tests/unit/printver.tcl b/examples/redis-unstable/tests/unit/printver.tcl deleted file mode 100644 index c80f451..0000000 --- a/examples/redis-unstable/tests/unit/printver.tcl +++ /dev/null @@ -1,6 +0,0 @@ -start_server {} { - set i [r info] - regexp {redis_version:(.*?)\r\n} $i - version - regexp {redis_git_sha1:(.*?)\r\n} $i - sha1 - puts "Testing Redis version $version ($sha1)" -} diff --git a/examples/redis-unstable/tests/unit/protocol.tcl b/examples/redis-unstable/tests/unit/protocol.tcl deleted file mode 100644 index 7c62b58..0000000 --- a/examples/redis-unstable/tests/unit/protocol.tcl +++ /dev/null @@ -1,309 +0,0 @@ -start_server {tags {"protocol network"}} { - test "Handle an empty query" { - reconnect - r write "\r\n" - r flush - assert_equal "PONG" [r ping] - } - - test "Negative multibulk length" { - reconnect - r write "*-10\r\n" - r flush - assert_equal PONG [r ping] - } - - test "Out of range multibulk length" { - reconnect - r write "*3000000000\r\n" - r flush - assert_error "*invalid multibulk length*" {r read} - } - - test "Wrong multibulk payload header" { - reconnect - r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\nfooz\r\n" - r flush - assert_error "*expected '$', got 'f'*" {r read} - } - - test "Negative multibulk payload length" { - reconnect - r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$-10\r\n" - r flush - assert_error "*invalid bulk length*" {r read} - } - - test "Out of range multibulk payload length" { - reconnect - r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$2000000000\r\n" - r flush - assert_error "*invalid bulk length*" {r read} - } - - test "Non-number multibulk payload length" { - reconnect - r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n" - r flush - assert_error "*invalid bulk length*" {r read} - } - - test "Multi bulk request not followed by bulk arguments" { - reconnect - r write "*1\r\nfoo\r\n" - r flush - assert_error "*expected '$', got 'f'*" {r read} - } - - test "Generic wrong number of args" { - reconnect - assert_error "*wrong*arguments*ping*" {r ping x y z} - } - - test "Unbalanced number of quotes" { - reconnect - r write "set \"\"\"test-key\"\"\" test-value\r\n" - r write "ping\r\n" - r flush - assert_error "*unbalanced*" {r read} - } - - set c 0 - foreach seq [list "\x00" "*\x00" "$\x00"] { - incr c - test "Protocol desync regression test #$c" { - if {$::tls} { - set s [::tls::socket [srv 0 host] [srv 0 port]] - } else { - set s [socket [srv 0 host] [srv 0 port]] - } - puts -nonewline $s $seq - set payload [string repeat A 1024]"\n" - set test_start [clock seconds] - set test_time_limit 30 - while 1 { - if {[catch { - puts -nonewline $s payload - flush $s - incr payload_size [string length $payload] - }]} { - set retval [gets $s] - close $s - break - } else { - set elapsed [expr {[clock seconds]-$test_start}] - if {$elapsed > $test_time_limit} { - close $s - error "assertion:Redis did not closed connection after protocol desync" - } - } - } - set retval - } {*Protocol error*} - } - unset c - - # recover the broken connection - reconnect - r ping - - # raw RESP response tests - r readraw 1 - - set nullres {*-1} - if {$::force_resp3} { - set nullres {_} - } - - test "raw protocol response" { - r srandmember nonexisting_key - } "$nullres" - - r deferred 1 - - test "raw protocol response - deferred" { - r srandmember nonexisting_key - r read - } "$nullres" - - test "raw protocol response - multiline" { - r sadd ss a - assert_equal [r read] {:1} - r srandmember ss 100 - assert_equal [r read] {*1} - assert_equal [r read] {$1} - assert_equal [r read] {a} - } - - test "bulk reply protocol" { - # value=2 (int encoding) - r set crlf 2 - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 7] "\$1\r\n2\r\n" - - # value=2147483647 (int encoding) - r set crlf 2147483647 - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 17] "\$10\r\n2147483647\r\n" - - # value=-2147483648 (int encoding) - r set crlf -2147483648 - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 18] "\$11\r\n-2147483648\r\n" - - # value=-9223372036854775809 (embstr encoding) - r set crlf -9223372036854775809 - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 27] "\$20\r\n-9223372036854775809\r\n" - - # value=9223372036854775808 (embstr encoding) - r set crlf 9223372036854775808 - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 26] "\$19\r\n9223372036854775808\r\n" - - # normal sds (embstr encoding) - r set crlf aaaaaaaaaaaaaaaa - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 23] "\$16\r\naaaaaaaaaaaaaaaa\r\n" - - # normal sds (raw string encoding) with 45 'a' - set rawstr [string repeat "a" 45] - r set crlf $rawstr - assert_equal [r rawread 5] "+OK\r\n" - r get crlf - assert_equal [r rawread 52] "\$45\r\n$rawstr\r\n" - - r del crlf - assert_equal [r rawread 4] ":1\r\n" - } - - # restore connection settings - r readraw 0 - r deferred 0 - - # check the connection still works - assert_equal [r ping] {PONG} - - test {RESP3 attributes} { - r hello 3 - assert_equal {Some real reply following the attribute} [r debug protocol attrib] - assert_equal {key-popularity {key:123 90}} [r attributes] - - # make sure attributes are not kept from previous command - r ping - assert_error {*attributes* no such element in array} {r attributes} - - # restore state - r hello 2 - set _ "" - } {} {needs:debug resp3} - - test {RESP3 attributes readraw} { - r hello 3 - r readraw 1 - r deferred 1 - - r debug protocol attrib - assert_equal [r read] {|1} - assert_equal [r read] {$14} - assert_equal [r read] {key-popularity} - assert_equal [r read] {*2} - assert_equal [r read] {$7} - assert_equal [r read] {key:123} - assert_equal [r read] {:90} - assert_equal [r read] {$39} - assert_equal [r read] {Some real reply following the attribute} - - # restore state - r readraw 0 - r deferred 0 - r hello 2 - set _ {} - } {} {needs:debug resp3} - - test {RESP3 attributes on RESP2} { - r hello 2 - set res [r debug protocol attrib] - set _ $res - } {Some real reply following the attribute} {needs:debug} - - test "test big number parsing" { - r hello 3 - r debug protocol bignum - } {1234567999999999999999999999999999999} {needs:debug resp3} - - test "test bool parsing" { - r hello 3 - assert_equal [r debug protocol true] 1 - assert_equal [r debug protocol false] 0 - r hello 2 - assert_equal [r debug protocol true] 1 - assert_equal [r debug protocol false] 0 - set _ {} - } {} {needs:debug resp3} - - test "test verbatim str parsing" { - r hello 3 - r debug protocol verbatim - } "This is a verbatim\nstring" {needs:debug resp3} - - test "test large number of args" { - r flushdb - set args [split [string trim [string repeat "k v " 10000]]] - lappend args "{k}2" v2 - r mset {*}$args - assert_equal [r get "{k}2"] v2 - } - - test "test argument rewriting - issue 9598" { - # INCRBYFLOAT uses argument rewriting for correct float value propagation. - # We use it to make sure argument rewriting works properly. It's important - # this test is run under valgrind to verify there are no memory leaks in - # arg buffer handling. - r flushdb - - # Test normal argument handling - r set k 0 - assert_equal [r incrbyfloat k 1.0] 1 - - # Test argument handing in multi-state buffers - r multi - r incrbyfloat k 1.0 - assert_equal [r exec] 2 - } - -} - -start_server {tags {"regression"}} { - test "Regression for a crash with blocking ops and pipelining" { - set rd [redis_deferring_client] - set fd [r channel] - set proto "*3\r\n\$5\r\nBLPOP\r\n\$6\r\nnolist\r\n\$1\r\n0\r\n" - puts -nonewline $fd $proto$proto - flush $fd - set res {} - - $rd rpush nolist a - $rd read - $rd rpush nolist a - $rd read - $rd close - } -} - -start_server {tags {"regression"}} { - test "Regression for a crash with cron release of client arguments" { - r write "*3\r\n" - r flush - after 3000 ;# wait for c->argv to be released due to timeout - r write "\$3\r\nSET\r\n\$3\r\nkey\r\n\$1\r\n0\r\n" - r flush - r read - } {OK} -} diff --git a/examples/redis-unstable/tests/unit/pubsub.tcl b/examples/redis-unstable/tests/unit/pubsub.tcl deleted file mode 100644 index 24f779f..0000000 --- a/examples/redis-unstable/tests/unit/pubsub.tcl +++ /dev/null @@ -1,1016 +0,0 @@ -start_server {tags {"pubsub network"}} { - if {$::singledb} { - set db 0 - } else { - set db 9 - } - - foreach resp {2 3} { - set rd1 [redis_deferring_client] - if {[lsearch $::denytags "resp3"] >= 0} { - if {$resp == 3} {continue} - } elseif {$::force_resp3} { - if {$resp == 2} {continue} - } - - $rd1 hello $resp - $rd1 read - - test "Pub/Sub PING on RESP$resp" { - subscribe $rd1 somechannel - # While subscribed to non-zero channels PING works in Pub/Sub mode. - $rd1 ping - $rd1 ping "foo" - # In RESP3, the SUBSCRIBEd client can issue any command and get a reply, so the PINGs are standard - # In RESP2, only a handful of commands are allowed after a client is SUBSCRIBED (PING is one of them). - # For some reason, the reply in that case is an array with two elements: "pong" and argv[1] or an empty string - # God knows why. Done in commit 2264b981 - if {$resp == 3} { - assert_equal {PONG} [$rd1 read] - assert_equal {foo} [$rd1 read] - } else { - assert_equal {pong {}} [$rd1 read] - assert_equal {pong foo} [$rd1 read] - } - unsubscribe $rd1 somechannel - # Now we are unsubscribed, PING should just return PONG. - $rd1 ping - assert_equal {PONG} [$rd1 read] - - } - $rd1 close - } - - test "PUBLISH/SUBSCRIBE basics" { - set rd1 [redis_deferring_client] - - # subscribe to two channels - assert_equal {1 2} [subscribe $rd1 {chan1 chan2}] - assert_equal 1 [r publish chan1 hello] - assert_equal 1 [r publish chan2 world] - assert_equal {message chan1 hello} [$rd1 read] - assert_equal {message chan2 world} [$rd1 read] - - # unsubscribe from one of the channels - unsubscribe $rd1 {chan1} - assert_equal 0 [r publish chan1 hello] - assert_equal 1 [r publish chan2 world] - assert_equal {message chan2 world} [$rd1 read] - - # unsubscribe from the remaining channel - unsubscribe $rd1 {chan2} - assert_equal 0 [r publish chan1 hello] - assert_equal 0 [r publish chan2 world] - - # clean up clients - $rd1 close - } - - test "PUBLISH/SUBSCRIBE with two clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - assert_equal {1} [subscribe $rd1 {chan1}] - assert_equal {1} [subscribe $rd2 {chan1}] - assert_equal 2 [r publish chan1 hello] - assert_equal {message chan1 hello} [$rd1 read] - assert_equal {message chan1 hello} [$rd2 read] - - # clean up clients - $rd1 close - $rd2 close - } - - test "PUBLISH/SUBSCRIBE after UNSUBSCRIBE without arguments" { - set rd1 [redis_deferring_client] - assert_equal {1 2 3} [subscribe $rd1 {chan1 chan2 chan3}] - unsubscribe $rd1 - wait_for_condition 100 10 { - [regexp {cmd=unsubscribe} [r client list]] eq 1 - } else { - fail "unsubscribe did not arrive" - } - assert_equal 0 [r publish chan1 hello] - assert_equal 0 [r publish chan2 hello] - assert_equal 0 [r publish chan3 hello] - - # clean up clients - $rd1 close - } - - test "SUBSCRIBE to one channel more than once" { - set rd1 [redis_deferring_client] - assert_equal {1 1 1} [subscribe $rd1 {chan1 chan1 chan1}] - assert_equal 1 [r publish chan1 hello] - assert_equal {message chan1 hello} [$rd1 read] - - # clean up clients - $rd1 close - } - - test "UNSUBSCRIBE from non-subscribed channels" { - set rd1 [redis_deferring_client] - assert_equal {0 0 0} [unsubscribe $rd1 {foo bar quux}] - - # clean up clients - $rd1 close - } - - test "PUBLISH/PSUBSCRIBE basics" { - set rd1 [redis_deferring_client] - - # subscribe to two patterns - assert_equal {1 2} [psubscribe $rd1 {foo.* bar.*}] - assert_equal 1 [r publish foo.1 hello] - assert_equal 1 [r publish bar.1 hello] - assert_equal 0 [r publish foo1 hello] - assert_equal 0 [r publish barfoo.1 hello] - assert_equal 0 [r publish qux.1 hello] - assert_equal {pmessage foo.* foo.1 hello} [$rd1 read] - assert_equal {pmessage bar.* bar.1 hello} [$rd1 read] - - # unsubscribe from one of the patterns - assert_equal {1} [punsubscribe $rd1 {foo.*}] - assert_equal 0 [r publish foo.1 hello] - assert_equal 1 [r publish bar.1 hello] - assert_equal {pmessage bar.* bar.1 hello} [$rd1 read] - - # unsubscribe from the remaining pattern - assert_equal {0} [punsubscribe $rd1 {bar.*}] - assert_equal 0 [r publish foo.1 hello] - assert_equal 0 [r publish bar.1 hello] - - # clean up clients - $rd1 close - } - - test "PUBLISH/PSUBSCRIBE with two clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - assert_equal {1} [psubscribe $rd1 {chan.*}] - assert_equal {1} [psubscribe $rd2 {chan.*}] - assert_equal 2 [r publish chan.foo hello] - assert_equal {pmessage chan.* chan.foo hello} [$rd1 read] - assert_equal {pmessage chan.* chan.foo hello} [$rd2 read] - - # clean up clients - $rd1 close - $rd2 close - } - - test "PUBLISH/PSUBSCRIBE after PUNSUBSCRIBE without arguments" { - set rd1 [redis_deferring_client] - assert_equal {1 2 3} [psubscribe $rd1 {chan1.* chan2.* chan3.*}] - punsubscribe $rd1 - wait_for_condition 100 10 { - [regexp {cmd=punsubscribe} [r client list]] eq 1 - } else { - fail "punsubscribe did not arrive" - } - assert_equal 0 [r publish chan1.hi hello] - assert_equal 0 [r publish chan2.hi hello] - assert_equal 0 [r publish chan3.hi hello] - - # clean up clients - $rd1 close - } - - test "PubSub messages with CLIENT REPLY OFF" { - set rd [redis_deferring_client] - $rd hello 3 - $rd read ;# Discard the hello reply - - # Test that the subscribe/psubscribe notification is ok - $rd client reply off - assert_equal {1} [subscribe $rd channel] - assert_equal {2} [psubscribe $rd ch*] - - # Test that the publish notification is ok - $rd client reply off - assert_equal 2 [r publish channel hello] - assert_equal {message channel hello} [$rd read] - assert_equal {pmessage ch* channel hello} [$rd read] - - # Test that the unsubscribe/punsubscribe notification is ok - $rd client reply off - assert_equal {1} [unsubscribe $rd channel] - assert_equal {0} [punsubscribe $rd ch*] - - $rd close - } {0} {resp3} - - test "PUNSUBSCRIBE from non-subscribed channels" { - set rd1 [redis_deferring_client] - assert_equal {0 0 0} [punsubscribe $rd1 {foo.* bar.* quux.*}] - - # clean up clients - $rd1 close - } - - test "NUMSUB returns numbers, not strings (#1561)" { - r pubsub numsub abc def - } {abc 0 def 0} - - test "NUMPATs returns the number of unique patterns" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - # Three unique patterns and one that overlaps - psubscribe $rd1 "foo*" - psubscribe $rd2 "foo*" - psubscribe $rd1 "bar*" - psubscribe $rd2 "baz*" - - set patterns [r pubsub numpat] - - # clean up clients - punsubscribe $rd1 - punsubscribe $rd2 - assert_equal 3 $patterns - $rd1 close - $rd2 close - } - - test "Mix SUBSCRIBE and PSUBSCRIBE" { - set rd1 [redis_deferring_client] - assert_equal {1} [subscribe $rd1 {foo.bar}] - assert_equal {2} [psubscribe $rd1 {foo.*}] - - assert_equal 2 [r publish foo.bar hello] - assert_equal {message foo.bar hello} [$rd1 read] - assert_equal {pmessage foo.* foo.bar hello} [$rd1 read] - - # clean up clients - $rd1 close - } - - test "PUNSUBSCRIBE and UNSUBSCRIBE should always reply" { - # Make sure we are not subscribed to any channel at all. - r punsubscribe - r unsubscribe - # Now check if the commands still reply correctly. - set reply1 [r punsubscribe] - set reply2 [r unsubscribe] - concat $reply1 $reply2 - } {punsubscribe {} 0 unsubscribe {} 0} - - ### Keyspace events notification tests - - test "Keyspace notifications: we receive keyspace notifications" { - r config set notify-keyspace-events KA - set rd1 [redis_deferring_client] - $rd1 CLIENT REPLY OFF ;# Make sure it works even if replies are silenced - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - assert_equal "pmessage * __keyspace@${db}__:foo set" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: we receive keyevent notifications" { - r config set notify-keyspace-events EA - r del foo - set rd1 [redis_deferring_client] - $rd1 CLIENT REPLY SKIP ;# Make sure it works even if replies are silenced - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - assert_equal "pmessage * __keyevent@${db}__:set foo" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: we can receive both kind of events" { - r config set notify-keyspace-events KEA - r del foo - set rd1 [redis_deferring_client] - $rd1 CLIENT REPLY ON ;# Just coverage - assert_equal {OK} [$rd1 read] - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - assert_equal "pmessage * __keyspace@${db}__:foo set" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:set foo" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: we are able to mask events" { - r config set notify-keyspace-events KEl - r del mylist - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - r lpush mylist a - # No notification for set, because only list commands are enabled. - assert_equal "pmessage * __keyspace@${db}__:mylist lpush" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:lpush mylist" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: general events test" { - r config set notify-keyspace-events KEg - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - r expire foo 1 - r del foo - assert_equal "pmessage * __keyspace@${db}__:foo expire" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:expire foo" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:foo del" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:del foo" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: list events test" { - r config set notify-keyspace-events KEl - r del mylist - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r lpush mylist a - r rpush mylist a - r rpop mylist - assert_equal "pmessage * __keyspace@${db}__:mylist lpush" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:lpush mylist" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mylist rpush" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:rpush mylist" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mylist rpop" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:rpop mylist" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: set events test" { - r config set notify-keyspace-events Ks - r del myset - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r sadd myset a b c d - r srem myset x - r sadd myset x y z - r srem myset x - assert_equal "pmessage * __keyspace@${db}__:myset sadd" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myset sadd" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myset srem" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: zset events test" { - r config set notify-keyspace-events Kz - r del myzset - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r zadd myzset 1 a 2 b - r zrem myzset x - r zadd myzset 3 x 4 y 5 z - r zrem myzset x - assert_equal "pmessage * __keyspace@${db}__:myzset zadd" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myzset zadd" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myzset zrem" [$rd1 read] - $rd1 close - } - - foreach {type max_lp_entries} {listpackex 512 hashtable 0} { - test "Keyspace notifications: hash events test ($type)" { - r config set hash-max-listpack-entries $max_lp_entries - r config set notify-keyspace-events Khg - r del myhash - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r hmset myhash yes 1 no 0 f1 1 f2 2 f3_hdel 3 - r hincrby myhash yes 10 - r hexpire myhash 999999 FIELDS 1 yes - r hexpireat myhash [expr {[clock seconds] + 999999}] NX FIELDS 1 no - r hpexpire myhash 999999 FIELDS 1 yes - r hpersist myhash FIELDS 1 yes - r hpexpire myhash 0 FIELDS 1 yes - assert_encoding $type myhash - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hincrby" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hpersist" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - - # Test that we will get `hexpired` notification when - # a hash field is removed by active expire. - r hpexpire myhash 10 FIELDS 1 no - after 100 ;# Wait for active expire - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - - # Test that when a field with TTL is deleted by commands like hdel without - # updating the global DS, active expire will not send a notification. - r hpexpire myhash 100 FIELDS 1 f3_hdel - r hdel myhash f3_hdel - after 200 ;# Wait for active expire - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - - # Test that we will get `hexpired` notification when - # a hash field is removed by lazy expire. - r debug set-active-expire 0 - r hpexpire myhash 10 FIELDS 2 f1 f2 - after 20 - r hmget myhash f1 f2 ;# Trigger lazy expire - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - # We should get only one `hexpired` notification even two fields was expired. - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - # We should get a `del` notification after all fields were expired. - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - r debug set-active-expire 1 - - - # Test HSETEX, HGETEX and HGETDEL notifications - r hsetex myhash FIELDS 3 f4 v4 f5 v5 f6 v6 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - - # hgetex sets ttl in past - r hgetex myhash PX 0 FIELDS 1 f4 - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - - # hgetex sets ttl - r hgetex myhash EXAT [expr {[clock seconds] + 999999}] FIELDS 1 f5 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - - # hgetex persists field - r hgetex myhash PERSIST FIELDS 1 f5 - assert_equal "pmessage * __keyspace@${db}__:myhash hpersist" [$rd1 read] - - # hgetex sets expiry for one field and lazy expiry deletes another field - # (KSN should be 1-hexpired 2-hexpire) - r debug set-active-expire 0 - r hsetex myhash PX 1 FIELDS 1 f5 v5 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 10 - r hgetex myhash EX 100 FIELDS 2 f5 f6 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - - # hgetex lazy expiry deletes the only field and the key - # (KSN should be 1-hexpired 2-del) - r hsetex myhash PX 1 FIELDS 2 f5 v5 f6 v6 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 10 - r hgetex myhash FIELDS 2 f5 f6 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - r debug set-active-expire 1 - - # hgetex sets an expired ttl for the only field and deletes the key - # (KSN should be 1-hdel 2-del) - r hsetex myhash EX 100 FIELDS 1 f5 v5 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 10 - r hgetex myhash PX 0 FIELDS 1 f5 - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - - r hsetex myhash FIELDS 2 f5 v5 f6 v6 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - - # hgetdel deletes a field - r hgetdel myhash FIELDS 1 f5 - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - - # hsetex sets field and expiry time - r hsetex myhash EXAT [expr {[clock seconds] + 999999}] FIELDS 1 f6 v6 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - - # hsetex sets field and ttl in the past - r hsetex myhash PX 0 FIELDS 1 f6 v6 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - - # Test that we will get `hexpired` notification when a hash field is - # removed by lazy expire using hgetdel command - r debug set-active-expire 0 - r hsetex myhash PX 10 FIELDS 1 f1 v1 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - - # Set another field - r hsetex myhash FIELDS 1 f2 v2 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - # Wait until field expires - after 20 - r hgetdel myhash FIELDS 1 f1 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - # Get and delete the only field - r hgetdel myhash FIELDS 1 f2 - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - - # HGETDEL deletes one field and the other field is lazily expired - # (KSN should be 1-hexpired 2-hdel) - r hsetex myhash FIELDS 2 f1 v1 f2 v2 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - r hsetex myhash PX 1 FIELDS 1 f3 v3 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 10 - r hgetdel myhash FIELDS 2 f1 f3 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - - # HGETDEL, deletes one field and the last field lazily expires - # (KSN should be 1-hexpired 2-hdel 3-del) - r hsetex myhash FIELDS 1 f1 v1 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - r hsetex myhash PX 1 FIELDS 1 f2 v2 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 10 - r hgetdel myhash FIELDS 2 f1 f2 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - r debug set-active-expire 1 - - $rd1 close - } {0} {needs:debug} - } ;# foreach - - test "Keyspace notifications: stream events test" { - r config set notify-keyspace-events Kt - r del mystream - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r xgroup create mystream mygroup $ mkstream - r xgroup createconsumer mystream mygroup Bob - set id [r xadd mystream 1 field1 A] - r xreadgroup group mygroup Alice STREAMS mystream > - r xclaim mystream mygroup Mike 0 $id force - # Not notify because of "Lee" not exists. - r xgroup delconsumer mystream mygroup Lee - # Not notify because of "Bob" exists. - r xautoclaim mystream mygroup Bob 0 $id - r xgroup delconsumer mystream mygroup Bob - assert_equal "pmessage * __keyspace@${db}__:mystream xgroup-create" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mystream xgroup-createconsumer" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mystream xadd" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mystream xgroup-createconsumer" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mystream xgroup-createconsumer" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:mystream xgroup-delconsumer" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications:FXX/FNX with HSETEX cmd" { - r config set notify-keyspace-events Khxg - r del myhash - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r debug set-active-expire 0 - - # FXX on logically expired field - r hset myhash f v - r hset myhash f2 v - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - r hpexpire myhash 10 FIELDS 1 f - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 15 - assert_equal [r HSETEX myhash FXX PX 10 FIELDS 1 f v] 0 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - r hdel myhash f2 - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - assert_equal 0 [r exists myhash] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - - # FXX with past expiry - r HSET myhash f1 v1 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - set past [expr {[clock seconds] - 2}] - assert_equal [r hsetex myhash FXX EXAT $past FIELDS 1 f1 v1] 1 - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - - # FXX overwrite + full key expiry - r hset myhash f v - r hset myhash f2 v - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - r hpexpire myhash 10 FIELDS 1 f - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 15 - set past [expr {[clock milliseconds] - 5000}] - assert_equal [r hsetex myhash FXX PXAT $past FIELDS 1 f v] 0 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - r hpexpire myhash 10 FIELDS 1 f2 - after 15 - r hget myhash f2 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - - # FNX on logically expired field - r del myhash - r hset myhash f v - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - r hpexpire myhash 10 FIELDS 1 f - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - after 15 - assert_equal [r HSETEX myhash FNX PX 1000 FIELDS 1 f v] 1 - assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read] - - # FNX with past expiry - r del myhash - r hset myhash f v - assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - set past [expr {[clock seconds] - 2}] - assert_equal [r hsetex myhash FNX EXAT $past FIELDS 1 f1 v1] 1 - # f1 is created and immediately expired - assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read] - assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read] - - r debug set-active-expire 1 - $rd1 close - } {0} {needs:debug} - - test "Keyspace notifications: expired events (triggered expire)" { - r config set notify-keyspace-events Ex - r del foo - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r psetex foo 100 1 - wait_for_condition 50 100 { - [r exists foo] == 0 - } else { - fail "Key does not expire?!" - } - assert_equal "pmessage * __keyevent@${db}__:expired foo" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: expired events (background expire)" { - r config set notify-keyspace-events Ex - r del foo - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r psetex foo 100 1 - assert_equal "pmessage * __keyevent@${db}__:expired foo" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: evicted events" { - r config set notify-keyspace-events Ee - r config set maxmemory-policy allkeys-lru - r flushdb - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - r config set maxmemory 1 - assert_equal "pmessage * __keyevent@${db}__:evicted foo" [$rd1 read] - r config set maxmemory 0 - $rd1 close - r config set maxmemory-policy noeviction - } {OK} {needs:config-maxmemory} - - test "Keyspace notifications: test CONFIG GET/SET of event flags" { - r config set notify-keyspace-events gKE - assert_equal {gKE} [lindex [r config get notify-keyspace-events] 1] - r config set notify-keyspace-events {$lshzxeKE} - assert_equal {$lshzxeKE} [lindex [r config get notify-keyspace-events] 1] - r config set notify-keyspace-events KA - assert_equal {AK} [lindex [r config get notify-keyspace-events] 1] - r config set notify-keyspace-events EA - assert_equal {AE} [lindex [r config get notify-keyspace-events] 1] - } - - test "Keyspace notifications: new key test" { - r config set notify-keyspace-events En - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - r set foo bar - # second set of foo should not cause a 'new' event - r set foo baz - r set bar bar - assert_equal "pmessage * __keyevent@${db}__:new foo" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:new bar" [$rd1 read] - $rd1 close - } - - ### overwritten and type_changed events - - test "Keyspace notifications: overwritten events - string to string" { - r config set notify-keyspace-events Eo - r del foo - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - # First set - should not trigger overwritten (new key) - r set foo bar - - # Second set - should trigger overwritten (same type) - r set foo baz - - assert_equal "pmessage * __keyevent@${db}__:overwritten foo" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: type_changed events - hash to string" { - r config set notify-keyspace-events Ec - r del testkey - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - # Set as hash first - r hset testkey field "hash_value" - - # Change to string - should trigger type_changed - r set testkey "string_value" - - assert_equal "pmessage * __keyevent@${db}__:type_changed testkey" [$rd1 read] - $rd1 close - } - - test "Keyspace notifications: both overwritten and type_changed events" { - r config set notify-keyspace-events Eoc - r del testkey3 - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - # Set as hash first - r hset testkey3 field "hash_value" - - # Change to string - should trigger both overwritten and type_changed - r set testkey3 "string_value" - - assert_equal "pmessage * __keyevent@${db}__:overwritten testkey3" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed testkey3" [$rd1 read] - - $rd1 close - } - - test "Keyspace notifications: configuration flags work correctly" { - # Test that 'o' flag enables override notifications - r config set notify-keyspace-events o - set config [r config get notify-keyspace-events] - assert {[lindex $config 1] eq "o"} - - # Test that 'c' flag enables type_changed notifications - r config set notify-keyspace-events c - set config [r config get notify-keyspace-events] - assert {[lindex $config 1] eq "c"} - - # Test that both flags can be combined - r config set notify-keyspace-events oc - set config [r config get notify-keyspace-events] - assert {[lindex $config 1] eq "oc"} - } - - ### RESTORE command tests for type_changed KSN types - - test "Keyspace notifications: RESTORE REPLACE different type - restore, overwritten and type_changed events" { - r config set notify-keyspace-events Egoc - r del restore_test_key3 - - # Create a string value and dump it (do this before subscribing) - r set temp_key "string_value" - set dump_data [r dump temp_key] - r del temp_key - - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - # Create initial hash key - r hset restore_test_key3 field "hash_value" - - # Restore with REPLACE - should emit restore, overwritten and type_changed events - r restore restore_test_key3 0 $dump_data REPLACE - - assert_equal "pmessage * __keyevent@${db}__:restore restore_test_key3" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:overwritten restore_test_key3" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed restore_test_key3" [$rd1 read] - - $rd1 close - } - - ### SET command tests for overwritten and type_changed KSN types - - test "Keyspace notifications: SET on existing string key - overwritten event" { - r config set notify-keyspace-events EAo - r del set_test_key1 - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - # Create initial string key - r set set_test_key1 "initial_value" - assert_equal "pmessage * __keyevent@${db}__:set set_test_key1" [$rd1 read] - - # Set new value on existing string key - should emit overwritten event - r set set_test_key1 "new_value" - - assert_equal "pmessage * __keyevent@${db}__:overwritten set_test_key1" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:set set_test_key1" [$rd1 read] - - $rd1 close - } - - test "Keyspace notifications: setKey on existing different type key - overwritten and type_changed events" { - r config set notify-keyspace-events Eoc - - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - r flushdb - r hset set_test_key2 field "hash_value" - r set set_test_key2 "string_value" - assert_equal "pmessage * __keyevent@${db}__:overwritten set_test_key2" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed set_test_key2" [$rd1 read] - - # overwritten and type_changed events should be emitted for any->any - # type conversion that uses the setKey command - r flushdb - r lpush l{t} 1 2 3 - r sadd s1{t} "A" - r sadd s2{t} "B" - r sunionstore l{t} s1{t} s2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten l{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed l{t}" [$rd1 read] - - r flushdb - r sadd s1{t} "A" - r set x{t} "\x0f" - r set y{t} "\xff" - r bitop and s1{t} x{t} y{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten s1{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed s1{t}" [$rd1 read] - - $rd1 close - } - - test "Keyspace notifications: overwritten and type_changed events for RENAME and COPY commands" { - r config set notify-keyspace-events Eoc - - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - # test COPY events - r flushdb - r hset hs{t} 1 2 3 4 - r lpush l{t} 1 2 3 4 - r copy hs{t} l{t} replace - - assert_equal "pmessage * __keyevent@${db}__:overwritten l{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed l{t}" [$rd1 read] - - # test rename RENAME events - r flushdb - r hset hs{t} field "hash_value" - r sadd x{t} 1 2 3 - r rename x{t} hs{t} - - assert_equal "pmessage * __keyevent@${db}__:overwritten hs{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed hs{t}" [$rd1 read] - - $rd1 close - } - - test "Keyspace notifications: overwritten and type_changed for *STORE* commands" { - r config set notify-keyspace-events Eoc - - set rd1 [redis_deferring_client] - assert_equal {1} [psubscribe $rd1 *] - - r flushdb - r set x{t} x - - # SORT - r lpush l{t} 4 3 2 1 - r sort l{t} store x{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten x{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed x{t}" [$rd1 read] - - # SDIFFSTORE - r sadd s1{t} a b c d - r sadd s2{t} b e f - r sdiffstore x{t} s1{t} s2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten x{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed x{t}" [$rd1 read] - - # SINTERSTORE - r set d1{t} x - r sinterstore d1{t} s1{t} s2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d1{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d1{t}" [$rd1 read] - - # SUNIONSTORE - r set d2{t} x - r sunionstore d2{t} s1{t} s2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d2{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d2{t}" [$rd1 read] - - # ZUNIONSTORE - r set d3{t} x - r zadd z1{t} 1 a 2 b - r zadd z2{t} 3 c 4 d - r zunionstore d3{t} 2 z1{t} z2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d3{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d3{t}" [$rd1 read] - - # ZINTERSTORE - r set d4{t} x - r zadd z2{t} 2 a - r zinterstore d4{t} 2 z1{t} z2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d4{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d4{t}" [$rd1 read] - - # ZDIFFSTORE - r set d5{t} x - r zdiffstore d5{t} 2 z1{t} z2{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d5{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d5{t}" [$rd1 read] - - # ZRANGESTORE - r set d6{t} x - r zadd zsrc{t} 1 a 2 b 3 c 4 d - r zrangestore d6{t} zsrc{t} 1 2 - assert_equal "pmessage * __keyevent@${db}__:overwritten d6{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d6{t}" [$rd1 read] - - # GEORADIUS with STORE - r set d7{t} x - r geoadd geo{t} 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" - r georadius geo{t} 15 37 200 km store d7{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d7{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d7{t}" [$rd1 read] - - # GEORADIUS with STOREDIST - r set d8{t} x - r georadius geo{t} 15 37 200 km storedist d8{t} - assert_equal "pmessage * __keyevent@${db}__:overwritten d8{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d8{t}" [$rd1 read] - - # GEOSEARCHSTORE - r set d9{t} x - r geosearchstore d9{t} geo{t} fromlonlat 15 37 byradius 200 km - assert_equal "pmessage * __keyevent@${db}__:overwritten d9{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d9{t}" [$rd1 read] - - # GEOSEARCHSTORE with STOREDIST - r set d10{t} x - r geosearchstore d10{t} geo{t} fromlonlat 15 37 byradius 200 km storedist - assert_equal "pmessage * __keyevent@${db}__:overwritten d10{t}" [$rd1 read] - assert_equal "pmessage * __keyevent@${db}__:type_changed d10{t}" [$rd1 read] - - $rd1 close - } - - test "publish to self inside multi" { - r hello 3 - r subscribe foo - r multi - r ping abc - r publish foo bar - r publish foo vaz - r ping def - assert_equal [r exec] {abc 1 1 def} - assert_equal [r read] {message foo bar} - assert_equal [r read] {message foo vaz} - } {} {resp3} - - test "publish to self inside script" { - r hello 3 - r subscribe foo - set res [r eval { - redis.call("ping","abc") - redis.call("publish","foo","bar") - redis.call("publish","foo","vaz") - redis.call("ping","def") - return "bla"} 0] - assert_equal $res {bla} - assert_equal [r read] {message foo bar} - assert_equal [r read] {message foo vaz} - } {} {resp3} - - test "unsubscribe inside multi, and publish to self" { - r hello 3 - - # Note: SUBSCRIBE and UNSUBSCRIBE with multiple channels in the same command, - # breaks the multi response, see https://github.com/redis/redis/issues/12207 - # this is just a temporary sanity test to detect unintended breakage. - - # subscribe for 3 channels actually emits 3 "responses" - assert_equal "subscribe foo 1" [r subscribe foo bar baz] - assert_equal "subscribe bar 2" [r read] - assert_equal "subscribe baz 3" [r read] - - r multi - r ping abc - r unsubscribe bar - r unsubscribe baz - r ping def - assert_equal [r exec] {abc {unsubscribe bar 2} {unsubscribe baz 1} def} - - # published message comes after the publish command's response. - assert_equal [r publish foo vaz] {1} - assert_equal [r read] {message foo vaz} - } {} {resp3} - -} diff --git a/examples/redis-unstable/tests/unit/pubsubshard.tcl b/examples/redis-unstable/tests/unit/pubsubshard.tcl deleted file mode 100644 index a3c841d..0000000 --- a/examples/redis-unstable/tests/unit/pubsubshard.tcl +++ /dev/null @@ -1,169 +0,0 @@ -start_server {tags {"pubsubshard external:skip"}} { - test "SPUBLISH/SSUBSCRIBE basics" { - set rd1 [redis_deferring_client] - - # subscribe to two channels - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {2} [ssubscribe $rd1 {chan2}] - assert_equal 1 [r SPUBLISH chan1 hello] - assert_equal 1 [r SPUBLISH chan2 world] - assert_equal {smessage chan1 hello} [$rd1 read] - assert_equal {smessage chan2 world} [$rd1 read] - - # unsubscribe from one of the channels - sunsubscribe $rd1 {chan1} - assert_equal 0 [r SPUBLISH chan1 hello] - assert_equal 1 [r SPUBLISH chan2 world] - assert_equal {smessage chan2 world} [$rd1 read] - - # unsubscribe from the remaining channel - sunsubscribe $rd1 {chan2} - assert_equal 0 [r SPUBLISH chan1 hello] - assert_equal 0 [r SPUBLISH chan2 world] - - # clean up clients - $rd1 close - } - - test "SPUBLISH/SSUBSCRIBE with two clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {1} [ssubscribe $rd2 {chan1}] - assert_equal 2 [r SPUBLISH chan1 hello] - assert_equal {smessage chan1 hello} [$rd1 read] - assert_equal {smessage chan1 hello} [$rd2 read] - - # clean up clients - $rd1 close - $rd2 close - } - - test "SPUBLISH/SSUBSCRIBE after UNSUBSCRIBE without arguments" { - set rd1 [redis_deferring_client] - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {2} [ssubscribe $rd1 {chan2}] - assert_equal {3} [ssubscribe $rd1 {chan3}] - sunsubscribe $rd1 - wait_for_condition 100 10 { - [regexp {cmd=sunsubscribe} [r client list]] eq 1 - } else { - fail "sunsubscribe did not arrive" - } - assert_equal 0 [r SPUBLISH chan1 hello] - assert_equal 0 [r SPUBLISH chan2 hello] - assert_equal 0 [r SPUBLISH chan3 hello] - - # clean up clients - $rd1 close - } - - test "SSUBSCRIBE to one channel more than once" { - set rd1 [redis_deferring_client] - assert_equal {1 1 1} [ssubscribe $rd1 {chan1 chan1 chan1}] - assert_equal 1 [r SPUBLISH chan1 hello] - assert_equal {smessage chan1 hello} [$rd1 read] - - # clean up clients - $rd1 close - } - - test "SUNSUBSCRIBE from non-subscribed channels" { - set rd1 [redis_deferring_client] - assert_equal {0} [sunsubscribe $rd1 {foo}] - assert_equal {0} [sunsubscribe $rd1 {bar}] - assert_equal {0} [sunsubscribe $rd1 {quux}] - - # clean up clients - $rd1 close - } - - test "PUBSUB command basics" { - r pubsub shardnumsub abc def - } {abc 0 def 0} - - test "SPUBLISH/SSUBSCRIBE with two clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {1} [ssubscribe $rd2 {chan1}] - assert_equal 2 [r SPUBLISH chan1 hello] - assert_equal "chan1 2" [r pubsub shardnumsub chan1] - assert_equal "chan1" [r pubsub shardchannels] - - # clean up clients - $rd1 close - $rd2 close - } - - test "SPUBLISH/SSUBSCRIBE with PUBLISH/SUBSCRIBE" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - assert_equal {1} [ssubscribe $rd1 {chan1}] - assert_equal {1} [subscribe $rd2 {chan1}] - assert_equal 1 [r SPUBLISH chan1 hello] - assert_equal 1 [r publish chan1 hello] - assert_equal "chan1 1" [r pubsub shardnumsub chan1] - assert_equal "chan1 1" [r pubsub numsub chan1] - assert_equal "chan1" [r pubsub shardchannels] - assert_equal "chan1" [r pubsub channels] - - $rd1 close - $rd2 close - } - - test "PubSubShard with CLIENT REPLY OFF" { - set rd [redis_deferring_client] - $rd hello 3 - $rd read ;# Discard the hello reply - - # Test that the ssubscribe notification is ok - $rd client reply off - $rd ping - assert_equal {1} [ssubscribe $rd channel] - - # Test that the spublish notification is ok - $rd client reply off - $rd ping - assert_equal 1 [r spublish channel hello] - assert_equal {smessage channel hello} [$rd read] - - # Test that sunsubscribe notification is ok - $rd client reply off - $rd ping - assert_equal {0} [sunsubscribe $rd channel] - - $rd close - } -} - -start_server {tags {"pubsubshard external:skip"}} { -start_server {tags {"pubsubshard external:skip"}} { - set node_0 [srv 0 client] - set node_0_host [srv 0 host] - set node_0_port [srv 0 port] - - set node_1 [srv -1 client] - set node_1_host [srv -1 host] - set node_1_port [srv -1 port] - - test {setup replication for following tests} { - $node_1 replicaof $node_0_host $node_0_port - wait_for_sync $node_1 - } - - test {publish message to master and receive on replica} { - set rd0 [redis_deferring_client node_0_host node_0_port] - set rd1 [redis_deferring_client node_1_host node_1_port] - - assert_equal {1} [ssubscribe $rd1 {chan1}] - $rd0 SPUBLISH chan1 hello - assert_equal {smessage chan1 hello} [$rd1 read] - $rd0 SPUBLISH chan1 world - assert_equal {smessage chan1 world} [$rd1 read] - } -} -} \ No newline at end of file diff --git a/examples/redis-unstable/tests/unit/querybuf.tcl b/examples/redis-unstable/tests/unit/querybuf.tcl deleted file mode 100644 index 47eb948..0000000 --- a/examples/redis-unstable/tests/unit/querybuf.tcl +++ /dev/null @@ -1,179 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# -proc client_idle_sec {name} { - set clients [split [r client list] "\r\n"] - set c [lsearch -inline $clients *name=$name*] - assert {[regexp {idle=([0-9]+)} $c - idle]} - return $idle -} - -# Calculate query buffer memory of client -proc client_query_buffer {name} { - set clients [split [r client list] "\r\n"] - set c [lsearch -inline $clients *name=$name*] - if {[string length $c] > 0} { - assert {[regexp {qbuf=([0-9]+)} $c - qbuf]} - assert {[regexp {qbuf-free=([0-9]+)} $c - qbuf_free]} - return [expr $qbuf + $qbuf_free] - } - return 0 -} - -start_server {tags {"querybuf slow"}} { - # increase the execution frequency of clientsCron - r config set hz 100 - - # The test will run at least 2s to check if client query - # buffer will be resized when client idle 2s. - test "query buffer resized correctly" { - - set rd [redis_deferring_client] - - $rd client setname test_client - $rd read - - # Make sure query buff has size of 0 bytes at start as the client uses the reusable qb. - assert {[client_query_buffer test_client] == 0} - - # Pause cron to prevent premature shrinking (timing issue). - r debug pause-cron 1 - - # Send partial command to client to make sure it doesn't use the reusable qb. - $rd write "*3\r\n\$3\r\nset\r\n\$2\r\na" - $rd flush - # Wait for the client to start using a private query buffer. - wait_for_condition 1000 10 { - [client_query_buffer test_client] > 0 - } else { - fail "client should start using a private query buffer" - } - - # send the rest of the command - $rd write "a\r\n\$1\r\nb\r\n" - $rd flush - assert_equal {OK} [$rd read] - - set orig_test_client_qbuf [client_query_buffer test_client] - # Make sure query buff has less than the peak resize threshold (PROTO_RESIZE_THRESHOLD) 32k - # but at least the basic IO reading buffer size (PROTO_IOBUF_LEN) 16k - set MAX_QUERY_BUFFER_SIZE [expr 32768 + 2] ; # 32k + 2, allowing for potential greedy allocation of (16k + 1) * 2 bytes for the query buffer. - assert {$orig_test_client_qbuf >= 16384 && $orig_test_client_qbuf <= $MAX_QUERY_BUFFER_SIZE} - - # Allow shrinking to occur - r debug pause-cron 0 - - # Check that the initial query buffer is resized after 2 sec - wait_for_condition 1000 10 { - [client_idle_sec test_client] >= 3 && [client_query_buffer test_client] < $orig_test_client_qbuf - } else { - fail "query buffer was not resized" - } - $rd close - } - - test "query buffer resized correctly when not idle" { - # Pause cron to prevent premature shrinking (timing issue). - r debug pause-cron 1 - - # Memory will increase by more than 32k due to client query buffer. - set rd [redis_client] - $rd client setname test_client - - # Create a large query buffer (more than PROTO_RESIZE_THRESHOLD - 32k) - $rd set x [string repeat A 400000] - - # Make sure query buff is larger than the peak resize threshold (PROTO_RESIZE_THRESHOLD) 32k - set orig_test_client_qbuf [client_query_buffer test_client] - assert {$orig_test_client_qbuf > 32768} - - r debug pause-cron 0 - - # Wait for qbuf to shrink due to lower peak - set t [clock milliseconds] - while true { - # Write something smaller, so query buf peak can shrink - $rd set x [string repeat A 100] - set new_test_client_qbuf [client_query_buffer test_client] - if {$new_test_client_qbuf < $orig_test_client_qbuf} { break } - if {[expr [clock milliseconds] - $t] > 1000} { break } - after 10 - } - # Validate qbuf shrunk but isn't 0 since we maintain room based on latest peak - assert {[client_query_buffer test_client] > 0 && [client_query_buffer test_client] < $orig_test_client_qbuf} - $rd close - } {0} {needs:debug} - - test "query buffer resized correctly with fat argv" { - set rd [redis_client] - $rd client setname test_client - - # Pause cron to prevent premature shrinking (timing issue). - r debug pause-cron 1 - - $rd write "*3\r\n\$3\r\nset\r\n\$1\r\na\r\n\$1000000\r\n" - $rd flush - - # Wait for the client to start using a private query buffer of > 1000000 size. - wait_for_condition 1000 10 { - [client_query_buffer test_client] > 1000000 - } else { - fail "client should start using a private query buffer" - } - - # Send the start of the arg and make sure the client is not using reusable qb for it rather a private buf of > 1000000 size. - $rd write "a" - $rd flush - - r debug pause-cron 0 - - after 120 - if {[client_query_buffer test_client] < 1000000} { - fail "query buffer should not be resized when client idle time smaller than 2s" - } - - # Check that the query buffer is resized after 2 sec - wait_for_condition 1000 10 { - [client_idle_sec test_client] >= 3 && [client_query_buffer test_client] < 1000000 - } else { - fail "query buffer should be resized when client idle time bigger than 2s" - } - - $rd close - } -} - -start_server {tags {"querybuf"}} { - test "Client executes small argv commands using reusable query buffer" { - set rd [redis_deferring_client] - $rd client setname test_client - $rd read - set res [r client list] - - # Verify that the client does not create a private query buffer after - # executing a small parameter command. - assert_match {*name=test_client * qbuf=0 qbuf-free=0 * cmd=client|setname *} $res - - # The client executing the command is currently using the reusable query buffer, - # so the size shown is that of the reusable query buffer. It will be returned - # to the reusable query buffer after command execution. - # Note that if IO threads are enabled, the reusable query buffer will be dereferenced earlier. - if {[lindex [r config get io-threads] 1] == 1} { - assert_match {*qbuf=26 qbuf-free=* cmd=client|list *} $res - } else { - assert_match {*qbuf=0 qbuf-free=* cmd=client|list *} $res - } - - $rd close - } -} diff --git a/examples/redis-unstable/tests/unit/quit.tcl b/examples/redis-unstable/tests/unit/quit.tcl deleted file mode 100644 index 50ccab1..0000000 --- a/examples/redis-unstable/tests/unit/quit.tcl +++ /dev/null @@ -1,33 +0,0 @@ -start_server {tags {"quit"}} { - - test "QUIT returns OK" { - reconnect - assert_equal OK [r quit] - assert_error * {r ping} - } - - test "Pipelined commands after QUIT must not be executed" { - reconnect - r write [format_command quit] - r write [format_command set foo bar] - r flush - assert_equal OK [r read] - assert_error * {r read} - - reconnect - assert_equal {} [r get foo] - } - - test "Pipelined commands after QUIT that exceed read buffer size" { - reconnect - r write [format_command quit] - r write [format_command set foo [string repeat "x" 1024]] - r flush - assert_equal OK [r read] - assert_error * {r read} - - reconnect - assert_equal {} [r get foo] - - } -} diff --git a/examples/redis-unstable/tests/unit/replybufsize.tcl b/examples/redis-unstable/tests/unit/replybufsize.tcl deleted file mode 100644 index 302417c..0000000 --- a/examples/redis-unstable/tests/unit/replybufsize.tcl +++ /dev/null @@ -1,48 +0,0 @@ -proc get_reply_buffer_size {cname} { - - set clients [split [string trim [r client list]] "\r\n"] - set c [lsearch -inline $clients *name=$cname*] - if {![regexp rbs=(\[a-zA-Z0-9-\]+) $c - rbufsize]} { - error "field rbs not found in $c" - } - return $rbufsize -} - -start_server {tags {"replybufsize"}} { - - test {verify reply buffer limits} { - # In order to reduce test time we can set the peak reset time very low - r debug replybuffer peak-reset-time 100 - r debug reply-copy-avoidance 0 ;# Disable copy avoidance because it affects memory usage - - # Create a simple idle test client - variable tc [redis_client] - $tc client setname test_client - - # make sure the client is idle for 1 seconds to make it shrink the reply buffer - wait_for_condition 10 100 { - [get_reply_buffer_size test_client] >= 1024 && [get_reply_buffer_size test_client] < 2046 - } else { - set rbs [get_reply_buffer_size test_client] - fail "reply buffer of idle client is $rbs after 1 seconds" - } - - r set bigval [string repeat x 32768] - - # In order to reduce test time we can set the peak reset time very low - r debug replybuffer peak-reset-time never - - wait_for_condition 10 100 { - [$tc get bigval ; get_reply_buffer_size test_client] >= 16384 && [get_reply_buffer_size test_client] < 32768 - } else { - set rbs [get_reply_buffer_size test_client] - fail "reply buffer of busy client is $rbs after 1 seconds" - } - - # Restore the peak reset time to default - r debug replybuffer peak-reset-time reset - - $tc close - } {0} {needs:debug} -} - \ No newline at end of file diff --git a/examples/redis-unstable/tests/unit/scan.tcl b/examples/redis-unstable/tests/unit/scan.tcl deleted file mode 100644 index 6a092cb..0000000 --- a/examples/redis-unstable/tests/unit/scan.tcl +++ /dev/null @@ -1,505 +0,0 @@ -proc test_scan {type} { - test "{$type} SCAN basic" { - r flushdb - populate 1000 - - set cur 0 - set keys {} - while 1 { - set res [r scan $cur] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - set keys [lsort -unique $keys] - assert_equal 1000 [llength $keys] - } - - test "{$type} SCAN COUNT" { - r flushdb - populate 1000 - - set cur 0 - set keys {} - while 1 { - set res [r scan $cur count 5] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - set keys [lsort -unique $keys] - assert_equal 1000 [llength $keys] - } - - test "{$type} SCAN MATCH" { - r flushdb - populate 1000 - - set cur 0 - set keys {} - while 1 { - set res [r scan $cur match "key:1??"] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - set keys [lsort -unique $keys] - assert_equal 100 [llength $keys] - } - - test "{$type} SCAN TYPE" { - r flushdb - # populate only creates strings - populate 1000 - - # Check non-strings are excluded - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type "list"] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 0 [llength $keys] - - # Check strings are included - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type "string"] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 1000 [llength $keys] - - # Check all three args work together - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type "string" match "key:*" count 10] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 1000 [llength $keys] - } - - test "{$type} SCAN unknown type" { - r flushdb - # make sure that passive expiration is triggered by the scan - r debug set-active-expire 0 - - populate 1000 - r hset hash f v - r pexpire hash 1 - - after 2 - - # TODO: remove this in redis 8.0 - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type "string1"] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 0 [llength $keys] - # make sure that expired key have been removed by scan command - assert_equal 1000 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d] - - # TODO: uncomment in redis 8.0 - #assert_error "*unknown type name*" {r scan 0 type "string1"} - # expired key will be no touched by scan command - #assert_equal 1001 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d] - r debug set-active-expire 1 - } {OK} {needs:debug} - - test "{$type} SCAN with expired keys" { - r flushdb - # make sure that passive expiration is triggered by the scan - r debug set-active-expire 0 - - populate 1000 - r set foo bar - r pexpire foo 1 - - # add a hash type key - r hset hash f v - r pexpire hash 1 - - after 2 - - set cur 0 - set keys {} - while 1 { - set res [r scan $cur count 10] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 1000 [llength $keys] - - # make sure that expired key have been removed by scan command - assert_equal 1000 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d] - - r debug set-active-expire 1 - } {OK} {needs:debug} - - test "{$type} SCAN with expired keys with TYPE filter and PATTERN filter" { - r flushdb - # make sure that passive expiration is triggered by the scan - r debug set-active-expire 0 - - populate 1000 - r set key:foo bar - r pexpire key:foo 1 - - # add a hash type key - r hset key:hash f v - r pexpire key:hash 1 - - # add a pattern key - r set boo far - r pexpire boo 1 - - after 2 - - set cur 0 - set keys {} - while 1 { - set res [r scan $cur type "string" match key* count 10] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - assert_equal 1000 [llength $keys] - - # make sure that expired key have been removed by scan command, - # pattern check before expired so key filtered by pattern will not be removed - # but expiration check is before type check so key:foo and key:hash will be removed - assert_equal 1001 [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d] - - r debug set-active-expire 1 - } {OK} {needs:debug} - - foreach enc {intset listpack hashtable} { - test "{$type} SSCAN with encoding $enc" { - # Create the Set - r del set - if {$enc eq {intset}} { - set prefix "" - } else { - set prefix "ele:" - } - set count [expr {$enc eq "hashtable" ? 200 : 100}] - set elements {} - for {set j 0} {$j < $count} {incr j} { - lappend elements ${prefix}${j} - } - r sadd set {*}$elements - - # Verify that the encoding matches. - assert_encoding $enc set - - # Test SSCAN - set cur 0 - set keys {} - while 1 { - set res [r sscan set $cur] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - set keys [lsort -unique $keys] - assert_equal $count [llength $keys] - } - } - - foreach enc {listpack hashtable} { - test "{$type} HSCAN with encoding $enc" { - # Create the Hash - r del hash - if {$enc eq {listpack}} { - set count 30 - } else { - set count 1000 - } - set elements {} - for {set j 0} {$j < $count} {incr j} { - lappend elements key:$j $j - } - r hmset hash {*}$elements - - # Verify that the encoding matches. - assert_encoding $enc hash - - # Test HSCAN - set cur 0 - set keys {} - while 1 { - set res [r hscan hash $cur] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - set keys2 {} - foreach {k v} $keys { - assert {$k eq "key:$v"} - lappend keys2 $k - } - - set keys2 [lsort -unique $keys2] - assert_equal $count [llength $keys2] - - # Test NOVALUES - set res [r hscan hash 0 count 1000 novalues] - assert_equal [lsort $keys2] [lsort [lindex $res 1]] - } - - test "{$type} HSCAN with large value $enc" { - r del hash - - if {$enc eq {listpack}} { - set count 60 - } else { - set count 170 - } - - set val1 [string repeat "1" $count] - r hset hash $val1 $val1 - - set val2 [string repeat "2" $count] - r hset hash $val2 $val2 - - set res [lsort [lindex [r hscan hash 0] 1]] - assert_equal $val1 [lindex $res 0] - assert_equal $val1 [lindex $res 1] - assert_equal $val2 [lindex $res 2] - assert_equal $val2 [lindex $res 3] - - set res [lsort [lindex [r hscan hash 0 novalues] 1]] - assert_equal $val1 [lindex $res 0] - assert_equal $val2 [lindex $res 1] - } - } - - foreach enc {listpack skiplist} { - test "{$type} ZSCAN with encoding $enc" { - # Create the Sorted Set - r del zset - if {$enc eq {listpack}} { - set count 30 - } else { - set count 1000 - } - set elements {} - for {set j 0} {$j < $count} {incr j} { - lappend elements $j key:$j - } - r zadd zset {*}$elements - - # Verify that the encoding matches. - assert_encoding $enc zset - - # Test ZSCAN - set cur 0 - set keys {} - while 1 { - set res [r zscan zset $cur] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - } - - set keys2 {} - foreach {k v} $keys { - assert {$k eq "key:$v"} - lappend keys2 $k - } - - set keys2 [lsort -unique $keys2] - assert_equal $count [llength $keys2] - } - } - - test "{$type} SCAN guarantees check under write load" { - r flushdb - populate 100 - - # We start scanning here, so keys from 0 to 99 should all be - # reported at the end of the iteration. - set keys {} - while 1 { - set res [r scan $cur] - set cur [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cur == 0} break - # Write 10 random keys at every SCAN iteration. - for {set j 0} {$j < 10} {incr j} { - r set addedkey:[randomInt 1000] foo - } - } - - set keys2 {} - foreach k $keys { - if {[string length $k] > 6} continue - lappend keys2 $k - } - - set keys2 [lsort -unique $keys2] - assert_equal 100 [llength $keys2] - } - - test "{$type} SSCAN with integer encoded object (issue #1345)" { - set objects {1 a} - r del set - r sadd set {*}$objects - set res [r sscan set 0 MATCH *a* COUNT 100] - assert_equal [lsort -unique [lindex $res 1]] {a} - set res [r sscan set 0 MATCH *1* COUNT 100] - assert_equal [lsort -unique [lindex $res 1]] {1} - } - - test "{$type} SSCAN with PATTERN" { - r del mykey - r sadd mykey foo fab fiz foobar 1 2 3 4 - set res [r sscan mykey 0 MATCH foo* COUNT 10000] - lsort -unique [lindex $res 1] - } {foo foobar} - - test "{$type} HSCAN with PATTERN" { - r del mykey - r hmset mykey foo 1 fab 2 fiz 3 foobar 10 1 a 2 b 3 c 4 d - set res [r hscan mykey 0 MATCH foo* COUNT 10000] - lsort -unique [lindex $res 1] - } {1 10 foo foobar} - - test "{$type} HSCAN with NOVALUES" { - r del mykey - r hmset mykey foo 1 fab 2 fiz 3 foobar 10 1 a 2 b 3 c 4 d - set res [r hscan mykey 0 NOVALUES] - lsort -unique [lindex $res 1] - } {1 2 3 4 fab fiz foo foobar} - - test "{$type} ZSCAN with PATTERN" { - r del mykey - r zadd mykey 1 foo 2 fab 3 fiz 10 foobar - set res [r zscan mykey 0 MATCH foo* COUNT 10000] - lsort -unique [lindex $res 1] - } - - test "{$type} ZSCAN scores: regression test for issue #2175" { - r del mykey - for {set j 0} {$j < 500} {incr j} { - r zadd mykey 9.8813129168249309e-323 $j - } - set res [lindex [r zscan mykey 0] 1] - set first_score [lindex $res 1] - assert {$first_score != 0} - } - - test "{$type} SCAN regression test for issue #4906" { - for {set k 0} {$k < 100} {incr k} { - r del set - r sadd set x; # Make sure it's not intset encoded - set toremove {} - unset -nocomplain found - array set found {} - - # Populate the set - set numele [expr {101+[randomInt 1000]}] - for {set j 0} {$j < $numele} {incr j} { - r sadd set $j - if {$j >= 100} { - lappend toremove $j - } - } - - # Start scanning - set cursor 0 - set iteration 0 - set del_iteration [randomInt 10] - while {!($cursor == 0 && $iteration != 0)} { - lassign [r sscan set $cursor] cursor items - - # Mark found items. We expect to find from 0 to 99 at the end - # since those elements will never be removed during the scanning. - foreach i $items { - set found($i) 1 - } - incr iteration - # At some point remove most of the items to trigger the - # rehashing to a smaller hash table. - if {$iteration == $del_iteration} { - r srem set {*}$toremove - } - } - - # Verify that SSCAN reported everything from 0 to 99 - for {set j 0} {$j < 100} {incr j} { - if {![info exists found($j)]} { - fail "SSCAN element missing $j" - } - } - } - } - - test "{$type} SCAN MATCH pattern implies cluster slot" { - # Tests the code path for an optimization for patterns like "{foo}-*" - # which implies that all matching keys belong to one slot. - r flushdb - for {set j 0} {$j < 100} {incr j} { - r set "{foo}-$j" "foo"; # slot 12182 - r set "{bar}-$j" "bar"; # slot 5061 - r set "{boo}-$j" "boo"; # slot 13142 - } - - set cursor 0 - set keys {} - while 1 { - set res [r scan $cursor match "{foo}-*"] - set cursor [lindex $res 0] - set k [lindex $res 1] - lappend keys {*}$k - if {$cursor == 0} break - } - - set keys [lsort -unique $keys] - assert_equal 100 [llength $keys] - } -} - -start_server {tags {"scan network standalone"}} { - test_scan "standalone" -} - -start_cluster 1 0 {tags {"external:skip cluster scan"}} { - test_scan "cluster" -} diff --git a/examples/redis-unstable/tests/unit/scripting.tcl b/examples/redis-unstable/tests/unit/scripting.tcl deleted file mode 100644 index 911f114..0000000 --- a/examples/redis-unstable/tests/unit/scripting.tcl +++ /dev/null @@ -1,2688 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -foreach is_eval {0 1} { - -if {$is_eval == 1} { - proc run_script {args} { - r eval {*}$args - } - proc run_script_ro {args} { - r eval_ro {*}$args - } - proc run_script_on_connection {args} { - [lindex $args 0] eval {*}[lrange $args 1 end] - } - proc kill_script {args} { - r script kill - } -} else { - proc run_script {args} { - r function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0]] - if {[r readingraw] eq 1} { - # read name - assert_equal {test} [r read] - } - r fcall test {*}[lrange $args 1 end] - } - proc run_script_ro {args} { - r function load replace [format "#!lua name=test\nredis.register_function{function_name='test', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0]] - if {[r readingraw] eq 1} { - # read name - assert_equal {test} [r read] - } - r fcall_ro test {*}[lrange $args 1 end] - } - proc run_script_on_connection {args} { - set rd [lindex $args 0] - $rd function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 1]] - # read name - $rd read - $rd fcall test {*}[lrange $args 2 end] - } - proc kill_script {args} { - r function kill - } -} - -start_server {tags {"scripting"}} { - - if {$is_eval eq 1} { - test {Script - disallow write on OOM} { - r config set maxmemory 1 - - catch {[r eval "redis.call('set', 'x', 1)" 0]} e - assert_match {*command not allowed when used memory*} $e - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - } ;# is_eval - - test {EVAL - Does Lua interpreter replies to our requests?} { - run_script {return 'hello'} 0 - } {hello} - - test {EVAL - Return _G} { - run_script {return _G} 0 - } {} - - test {EVAL - Return table with a metatable that raise error} { - run_script {local a = {}; setmetatable(a,{__index=function() foo() end}) return a} 0 - } {} - - test {EVAL - Return table with a metatable that call redis} { - run_script {local a = {}; setmetatable(a,{__index=function() redis.call('set', 'x', '1') end}) return a} 1 x - # make sure x was not set - r get x - } {} - - test {EVAL - Lua integer -> Redis protocol type conversion} { - run_script {return 100.5} 0 - } {100} - - test {EVAL - Lua string -> Redis protocol type conversion} { - run_script {return 'hello world'} 0 - } {hello world} - - test {EVAL - Lua true boolean -> Redis protocol type conversion} { - run_script {return true} 0 - } {1} - - test {EVAL - Lua false boolean -> Redis protocol type conversion} { - run_script {return false} 0 - } {} - - test {EVAL - Lua status code reply -> Redis protocol type conversion} { - run_script {return {ok='fine'}} 0 - } {fine} - - test {EVAL - Lua error reply -> Redis protocol type conversion} { - catch { - run_script {return {err='ERR this is an error'}} 0 - } e - set _ $e - } {ERR this is an error} - - test {EVAL - Lua table -> Redis protocol type conversion} { - run_script {return {1,2,3,'ciao',{1,2}}} 0 - } {1 2 3 ciao {1 2}} - - test {EVAL - Are the KEYS and ARGV arrays populated correctly?} { - run_script {return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}} 2 a{t} b{t} c{t} d{t} - } {a{t} b{t} c{t} d{t}} - - test {EVAL - is Lua able to call Redis API?} { - r set mykey myval - run_script {return redis.call('get',KEYS[1])} 1 mykey - } {myval} - - if {$is_eval eq 1} { - # eval sha is only relevant for is_eval Lua - test {EVALSHA - Can we call a SHA1 if already defined?} { - r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey - } {myval} - - test {EVALSHA_RO - Can we call a SHA1 if already defined?} { - r evalsha_ro fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey - } {myval} - - test {EVALSHA - Can we call a SHA1 in uppercase?} { - r evalsha FD758D1589D044DD850A6F05D52F2EEFD27F033F 1 mykey - } {myval} - - test {EVALSHA - Do we get an error on invalid SHA1?} { - catch {r evalsha NotValidShaSUM 0} e - set _ $e - } {NOSCRIPT*} - - test {EVALSHA - Do we get an error on non defined SHA1?} { - catch {r evalsha ffd632c7d33e571e9f24556ebed26c3479a87130 0} e - set _ $e - } {NOSCRIPT*} - } ;# is_eval - - test {EVAL - Redis integer -> Lua type conversion} { - r set x 0 - run_script { - local foo = redis.pcall('incr',KEYS[1]) - return {type(foo),foo} - } 1 x - } {number 1} - - test {EVAL - Lua number -> Redis integer conversion} { - r del hash - run_script { - local foo = redis.pcall('hincrby','hash','field',200000000) - return {type(foo),foo} - } 0 - } {number 200000000} - - test {EVAL - Redis bulk -> Lua type conversion} { - r set mykey myval - run_script { - local foo = redis.pcall('get',KEYS[1]) - return {type(foo),foo} - } 1 mykey - } {string myval} - - test {EVAL - Redis multi bulk -> Lua type conversion} { - r del mylist - r rpush mylist a - r rpush mylist b - r rpush mylist c - run_script { - local foo = redis.pcall('lrange',KEYS[1],0,-1) - return {type(foo),foo[1],foo[2],foo[3],# foo} - } 1 mylist - } {table a b c 3} - - test {EVAL - Redis status reply -> Lua type conversion} { - run_script { - local foo = redis.pcall('set',KEYS[1],'myval') - return {type(foo),foo['ok']} - } 1 mykey - } {table OK} - - test {EVAL - Redis error reply -> Lua type conversion} { - r set mykey myval - run_script { - local foo = redis.pcall('incr',KEYS[1]) - return {type(foo),foo['err']} - } 1 mykey - } {table {ERR value is not an integer or out of range}} - - test {EVAL - Redis nil bulk reply -> Lua type conversion} { - r del mykey - run_script { - local foo = redis.pcall('get',KEYS[1]) - return {type(foo),foo == false} - } 1 mykey - } {boolean 1} - - test {EVAL - Is the Lua client using the currently selected DB?} { - r set mykey "this is DB 9" - r select 10 - r set mykey "this is DB 10" - run_script {return redis.pcall('get',KEYS[1])} 1 mykey - } {this is DB 10} {singledb:skip} - - test {EVAL - SELECT inside Lua should not affect the caller} { - # here we DB 10 is selected - r set mykey "original value" - run_script {return redis.pcall('select','9')} 0 - set res [r get mykey] - r select 9 - set res - } {original value} {singledb:skip} - - if 0 { - test {EVAL - Script can't run more than configured time limit} { - r config set lua-time-limit 1 - catch { - run_script { - local i = 0 - while true do i=i+1 end - } 0 - } e - set _ $e - } {*execution time*} - } - - test {EVAL - Scripts do not block on blpop command} { - r lpush l 1 - r lpop l - run_script {return redis.pcall('blpop','l',0)} 1 l - } {} - - test {EVAL - Scripts do not block on brpop command} { - r lpush l 1 - r lpop l - run_script {return redis.pcall('brpop','l',0)} 1 l - } {} - - test {EVAL - Scripts do not block on brpoplpush command} { - r lpush empty_list1{t} 1 - r lpop empty_list1{t} - run_script {return redis.pcall('brpoplpush','empty_list1{t}', 'empty_list2{t}',0)} 2 empty_list1{t} empty_list2{t} - } {} - - test {EVAL - Scripts do not block on blmove command} { - r lpush empty_list1{t} 1 - r lpop empty_list1{t} - run_script {return redis.pcall('blmove','empty_list1{t}', 'empty_list2{t}', 'LEFT', 'LEFT', 0)} 2 empty_list1{t} empty_list2{t} - } {} - - test {EVAL - Scripts do not block on bzpopmin command} { - r zadd empty_zset 10 foo - r zmpop 1 empty_zset MIN - run_script {return redis.pcall('bzpopmin','empty_zset', 0)} 1 empty_zset - } {} - - test {EVAL - Scripts do not block on bzpopmax command} { - r zadd empty_zset 10 foo - r zmpop 1 empty_zset MIN - run_script {return redis.pcall('bzpopmax','empty_zset', 0)} 1 empty_zset - } {} - - test {EVAL - Scripts do not block on wait} { - run_script {return redis.pcall('wait','1','0')} 0 - } {0} - - test {EVAL - Scripts do not block on waitaof} { - r config set appendonly no - run_script {return redis.pcall('waitaof','0','1','0')} 0 - } {0 0} - - test {EVAL - Scripts do not block on XREAD with BLOCK option} { - r del s - r xgroup create s g $ MKSTREAM - set res [run_script {return redis.pcall('xread','STREAMS','s','$')} 1 s] - assert {$res eq {}} - run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s - } {} - - test {EVAL - Scripts do not block on XREADGROUP with BLOCK option} { - set res [run_script {return redis.pcall('xreadgroup','group','g','c','STREAMS','s','>')} 1 s] - assert {$res eq {}} - run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s - } {} - - test {EVAL - Scripts do not block on XREAD with BLOCK option -- non empty stream} { - r XADD s * a 1 - set res [run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s] - assert {$res eq {}} - - set res [run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','0-0')} 1 s] - assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {a 1}} - } - - test {EVAL - Scripts do not block on XREADGROUP with BLOCK option -- non empty stream} { - r XADD s * b 2 - set res [ - run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s - ] - assert {[llength [lindex $res 0 1]] == 2} - lindex $res 0 1 0 1 - } {a 1} - - test {EVAL - Scripts can run non-deterministic commands} { - set e {} - catch { - run_script {redis.pcall('randomkey'); return redis.pcall('set','x','ciao')} 1 x - } e - set e - } {*OK*} - - test {EVAL - No arguments to redis.call/pcall is considered an error} { - set e {} - catch {run_script {return redis.call()} 0} e - set e - } {*one argument*} - - test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} { - set e {} - catch { - run_script "redis.call('nosuchcommand')" 0 - } e - set e - } {*Unknown Redis*} - - test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} { - set e {} - catch { - run_script "redis.call('get','a','b','c')" 0 - } e - set e - } {*number of args*} - - test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} { - set e {} - r set foo bar - catch { - run_script {redis.call('lpush',KEYS[1],'val')} 1 foo - } e - set e - } {*against a key*} - - test {EVAL - Test table unpack with invalid indexes} { - catch {run_script { return {unpack({1,2,3}, -2, 2147483647)} } 0} e - assert_match {*too many results to unpack*} $e - catch {run_script { return {unpack({1,2,3}, 0, 2147483647)} } 0} e - assert_match {*too many results to unpack*} $e - catch {run_script { return {unpack({1,2,3}, -2147483648, -2)} } 0} e - assert_match {*too many results to unpack*} $e - set res [run_script { return {unpack({1,2,3}, -1, -2)} } 0] - assert_match {} $res - set res [run_script { return {unpack({1,2,3}, 1, -1)} } 0] - assert_match {} $res - - # unpack with range -1 to 5, verify nil indexes - set res [run_script { - local function unpack_to_list(t, i, j) - local n, v = select('#', unpack(t, i, j)), {unpack(t, i, j)} - for i = 1, n do v[i] = v[i] or '_NIL_' end - v.n = n - return v - end - - return unpack_to_list({1,2,3}, -1, 5) - } 0] - assert_match {_NIL_ _NIL_ 1 2 3 _NIL_ _NIL_} $res - - # unpack with negative range, verify nil indexes - set res [run_script { - local function unpack_to_list(t, i, j) - local n, v = select('#', unpack(t, i, j)), {unpack(t, i, j)} - for i = 1, n do v[i] = v[i] or '_NIL_' end - v.n = n - return v - end - - return unpack_to_list({1,2,3}, -2147483648, -2147483646) - } 0] - assert_match {_NIL_ _NIL_ _NIL_} $res - } {} - - test {EVAL - JSON numeric decoding} { - # We must return the table as a string because otherwise - # Redis converts floats to ints and we get 0 and 1023 instead - # of 0.0003 and 1023.2 as the parsed output. - run_script {return - table.concat( - cjson.decode( - "[0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10]"), " ") - } 0 - } {0 -5000 -1 0.0003 1023.2 0} - - test {EVAL - JSON string decoding} { - run_script {local decoded = cjson.decode('{"keya": "a", "keyb": "b"}') - return {decoded.keya, decoded.keyb} - } 0 - } {a b} - - test {EVAL - JSON empty array decoding} { - # Default behavior - assert_equal "{}" [run_script { - return cjson.encode(cjson.decode('[]')) - } 0] - assert_equal "{}" [run_script { - cjson.decode_array_with_array_mt(false) - return cjson.encode(cjson.decode('[]')) - } 0] - assert_equal "{\"item\":{}}" [run_script { - cjson.decode_array_with_array_mt(false) - return cjson.encode(cjson.decode('{"item": []}')) - } 0] - - # With array metatable - assert_equal "\[\]" [run_script { - cjson.decode_array_with_array_mt(true) - return cjson.encode(cjson.decode('[]')) - } 0] - assert_equal "{\"item\":\[\]}" [run_script { - cjson.decode_array_with_array_mt(true) - return cjson.encode(cjson.decode('{"item": []}')) - } 0] - } - - test {EVAL - JSON empty array decoding after element removal} { - # Default: emptied array becomes object - assert_equal "{}" [run_script { - cjson.decode_array_with_array_mt(false) - local t = cjson.decode('[1, 2]') - -- emptying the array - t[1] = nil - t[2] = nil - return cjson.encode(t) - } 0] - - # With array metatable: emptied array stays array - assert_equal "\[\]" [run_script { - cjson.decode_array_with_array_mt(true) - local t = cjson.decode('[1, 2]') - -- emptying the array - t[1] = nil - t[2] = nil - return cjson.encode(t) - } 0] - } - - test {EVAL - cjson array metatable modification should be readonly} { - catch { - run_script { - cjson.decode_array_with_array_mt(true) - local t = cjson.decode('[]') - getmetatable(t).__is_cjson_array = function() return 1 end - return cjson.encode(t) - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test {EVAL - JSON smoke test} { - run_script { - local some_map = { - s1="Some string", - n1=100, - a1={"Some","String","Array"}, - nil1=nil, - b1=true, - b2=false} - local encoded = cjson.encode(some_map) - local decoded = cjson.decode(encoded) - assert(table.concat(some_map) == table.concat(decoded)) - - cjson.encode_keep_buffer(false) - encoded = cjson.encode(some_map) - decoded = cjson.decode(encoded) - assert(table.concat(some_map) == table.concat(decoded)) - - -- Table with numeric keys - local table1 = {one="one", [1]="one"} - encoded = cjson.encode(table1) - decoded = cjson.decode(encoded) - assert(decoded["one"] == table1["one"]) - assert(decoded["1"] == table1[1]) - - -- Array - local array1 = {[1]="one", [2]="two"} - encoded = cjson.encode(array1) - decoded = cjson.decode(encoded) - assert(table.concat(array1) == table.concat(decoded)) - - -- Invalid keys - local invalid_map = {} - invalid_map[false] = "false" - local ok, encoded = pcall(cjson.encode, invalid_map) - assert(ok == false) - - -- Max depth - cjson.encode_max_depth(1) - ok, encoded = pcall(cjson.encode, some_map) - assert(ok == false) - - cjson.decode_max_depth(1) - ok, decoded = pcall(cjson.decode, '{"obj": {"array": [1,2,3,4]}}') - assert(ok == false) - - -- Invalid numbers - ok, encoded = pcall(cjson.encode, {num1=0/0}) - assert(ok == false) - cjson.encode_invalid_numbers(true) - ok, encoded = pcall(cjson.encode, {num1=0/0}) - assert(ok == true) - - -- Restore defaults - cjson.decode_max_depth(1000) - cjson.encode_max_depth(1000) - cjson.encode_invalid_numbers(false) - } 0 - } - - test {EVAL - cmsgpack can pack double?} { - run_script {local encoded = cmsgpack.pack(0.1) - local h = "" - for i = 1, #encoded do - h = h .. string.format("%02x",string.byte(encoded,i)) - end - return h - } 0 - } {cb3fb999999999999a} - - test {EVAL - cmsgpack can pack negative int64?} { - run_script {local encoded = cmsgpack.pack(-1099511627776) - local h = "" - for i = 1, #encoded do - h = h .. string.format("%02x",string.byte(encoded,i)) - end - return h - } 0 - } {d3ffffff0000000000} - - test {EVAL - cmsgpack pack/unpack smoke test} { - run_script { - local str_lt_32 = string.rep("x", 30) - local str_lt_255 = string.rep("x", 250) - local str_lt_65535 = string.rep("x", 65530) - local str_long = string.rep("x", 100000) - local array_lt_15 = {1, 2, 3, 4, 5} - local array_lt_65535 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18} - local array_big = {} - for i=1, 100000 do - array_big[i] = i - end - local map_lt_15 = {a=1, b=2} - local map_big = {} - for i=1, 100000 do - map_big[tostring(i)] = i - end - local some_map = { - s1=str_lt_32, - s2=str_lt_255, - s3=str_lt_65535, - s4=str_long, - d1=0.1, - i1=1, - i2=250, - i3=65530, - i4=100000, - i5=2^40, - i6=-1, - i7=-120, - i8=-32000, - i9=-100000, - i10=-3147483648, - a1=array_lt_15, - a2=array_lt_65535, - a3=array_big, - m1=map_lt_15, - m2=map_big, - b1=false, - b2=true, - n=nil - } - local encoded = cmsgpack.pack(some_map) - local decoded = cmsgpack.unpack(encoded) - assert(table.concat(some_map) == table.concat(decoded)) - local offset, decoded_one = cmsgpack.unpack_one(encoded, 0) - assert(table.concat(some_map) == table.concat(decoded_one)) - assert(offset == -1) - - local encoded_multiple = cmsgpack.pack(str_lt_32, str_lt_255, str_lt_65535, str_long) - local offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, 0) - assert(obj == str_lt_32) - offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, offset) - assert(obj == str_lt_255) - offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, offset) - assert(obj == str_lt_65535) - offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, offset) - assert(obj == str_long) - assert(offset == -1) - } 0 - } - - test {EVAL - cmsgpack can pack and unpack circular references?} { - run_script {local a = {x=nil,y=5} - local b = {x=a} - a['x'] = b - local encoded = cmsgpack.pack(a) - local h = "" - -- cmsgpack encodes to a depth of 16, but can't encode - -- references, so the encoded object has a deep copy recursive - -- depth of 16. - for i = 1, #encoded do - h = h .. string.format("%02x",string.byte(encoded,i)) - end - -- when unpacked, re.x.x != re because the unpack creates - -- individual tables down to a depth of 16. - -- (that's why the encoded output is so large) - local re = cmsgpack.unpack(encoded) - assert(re) - assert(re.x) - assert(re.x.x.y == re.y) - assert(re.x.x.x.x.y == re.y) - assert(re.x.x.x.x.x.x.y == re.y) - assert(re.x.x.x.x.x.x.x.x.x.x.y == re.y) - -- maximum working depth: - assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.y == re.y) - -- now the last x would be b above and has no y - assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x) - -- so, the final x.x is at the depth limit and was assigned nil - assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x == nil) - return {h, re.x.x.x.x.x.x.x.x.y == re.y, re.y == 5} - } 0 - } {82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0 1 1} - - test {EVAL - Numerical sanity check from bitop} { - run_script {assert(0x7fffffff == 2147483647, "broken hex literals"); - assert(0xffffffff == -1 or 0xffffffff == 2^32-1, - "broken hex literals"); - assert(tostring(-1) == "-1", "broken tostring()"); - assert(tostring(0xffffffff) == "-1" or - tostring(0xffffffff) == "4294967295", - "broken tostring()") - } 0 - } {} - - test {EVAL - Verify minimal bitop functionality} { - run_script {assert(bit.tobit(1) == 1); - assert(bit.band(1) == 1); - assert(bit.bxor(1,2) == 3); - assert(bit.bor(1,2,4,8,16,32,64,128) == 255) - } 0 - } {} - - test {EVAL - Able to parse trailing comments} { - run_script {return 'hello' --trailing comment} 0 - } {hello} - - test {EVAL_RO - Successful case} { - r set foo bar - assert_equal bar [run_script_ro {return redis.call('get', KEYS[1]);} 1 foo] - } - - test {EVAL_RO - Cannot run write commands} { - r set foo bar - catch {run_script_ro {redis.call('del', KEYS[1]);} 1 foo} e - set e - } {ERR Write commands are not allowed from read-only scripts*} - - if {$is_eval eq 1} { - # script command is only relevant for is_eval Lua - test {SCRIPTING FLUSH - is able to clear the scripts cache?} { - r set mykey myval - - r script load {return redis.call('get',KEYS[1])} - set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey] - assert_equal $v myval - r script flush - assert_error {NOSCRIPT*} {r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey} - - r eval {return redis.call('get',KEYS[1])} 1 mykey - set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey] - assert_equal $v myval - r script flush - assert_error {NOSCRIPT*} {r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey} - } - - test {SCRIPTING FLUSH ASYNC} { - for {set j 0} {$j < 100} {incr j} { - r script load "return $j" - } - assert { [string match "*number_of_cached_scripts:100*" [r info Memory]] } - r script flush async - assert { [string match "*number_of_cached_scripts:0*" [r info Memory]] } - } - - test {SCRIPT EXISTS - can detect already defined scripts?} { - r eval "return 1+1" 0 - r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda - } {1 0} - - test {SCRIPT LOAD - is able to register scripts in the scripting cache} { - list \ - [r script load "return 'loaded'"] \ - [r evalsha b534286061d4b9e4026607613b95c06c06015ae8 0] - } {b534286061d4b9e4026607613b95c06c06015ae8 loaded} - - test "SORT is normally not alpha re-ordered for the scripting engine" { - r del myset - r sadd myset 1 2 3 4 10 - r eval {return redis.call('sort',KEYS[1],'desc')} 1 myset - } {10 4 3 2 1} {cluster:skip} - - test "SORT BY output gets ordered for scripting" { - r del myset - r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz - r eval {return redis.call('sort',KEYS[1],'by','_')} 1 myset - } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {cluster:skip} - - test "SORT BY with GET gets ordered for scripting" { - r del myset - r sadd myset a b c - r eval {return redis.call('sort',KEYS[1],'by','_','get','#','get','_:*')} 1 myset - } {a {} b {} c {}} {cluster:skip} - } ;# is_eval - - test "redis.sha1hex() implementation" { - list [run_script {return redis.sha1hex('')} 0] \ - [run_script {return redis.sha1hex('Pizza & Mandolino')} 0] - } {da39a3ee5e6b4b0d3255bfef95601890afd80709 74822d82031af7493c20eefa13bd07ec4fada82f} - - test "Measures elapsed time os.clock()" { - set escaped [run_script { - local start = os.clock() - while os.clock() - start < 1 do end - return {double = os.clock() - start} - } 0] - assert_morethan_equal $escaped 1 ;# 1 second - } - - test "Prohibit dangerous lua methods in sandbox" { - assert_equal "" [run_script { - local allowed_methods = {"clock"} - -- Find a value from a tuple and return the position. - local indexOf = function(tuple, value) - for i, v in ipairs(tuple) do - if v == value then return i end - end - return nil - end - -- Check for disallowed methods and verify all allowed methods exist. - -- If an allowed method is found, it's removed from 'allowed_methods'. - -- If 'allowed_methods' is empty at the end, all allowed methods were found. - for key, value in pairs(os) do - local index = indexOf(allowed_methods, key) - if index == nil or type(value) ~= "function" then - return "Disallowed "..type(value)..":"..key - end - table.remove(allowed_methods, index) - end - if #allowed_methods ~= 0 then - return "Expected method not found: "..table.concat(allowed_methods, ",") - end - return "" - } 0] - } - - test "Verify execution of prohibit dangerous Lua methods will fail" { - assert_error {ERR *attempt to call field 'execute'*} {run_script {os.execute()} 0} - assert_error {ERR *attempt to call field 'exit'*} {run_script {os.exit()} 0} - assert_error {ERR *attempt to call field 'getenv'*} {run_script {os.getenv()} 0} - assert_error {ERR *attempt to call field 'remove'*} {run_script {os.remove()} 0} - assert_error {ERR *attempt to call field 'rename'*} {run_script {os.rename()} 0} - assert_error {ERR *attempt to call field 'setlocale'*} {run_script {os.setlocale()} 0} - assert_error {ERR *attempt to call field 'tmpname'*} {run_script {os.tmpname()} 0} - } - - test {Globals protection reading an undeclared global variable} { - catch {run_script {return a} 0} e - set e - } {ERR *attempted to access * global*} - - test {Globals protection setting an undeclared global*} { - catch {run_script {a=10} 0} e - set e - } {ERR *Attempt to modify a readonly table*} - - test {lua bit.tohex bug} { - set res [run_script {return bit.tohex(65535, -2147483648)} 0] - r ping - set res - } {0000FFFF} - - test {Test an example script DECR_IF_GT} { - set decr_if_gt { - local current - - current = redis.call('get',KEYS[1]) - if not current then return nil end - if current > ARGV[1] then - return redis.call('decr',KEYS[1]) - else - return redis.call('get',KEYS[1]) - end - } - r set foo 5 - set res {} - lappend res [run_script $decr_if_gt 1 foo 2] - lappend res [run_script $decr_if_gt 1 foo 2] - lappend res [run_script $decr_if_gt 1 foo 2] - lappend res [run_script $decr_if_gt 1 foo 2] - lappend res [run_script $decr_if_gt 1 foo 2] - set res - } {4 3 2 2 2} - - if {$is_eval eq 1} { - # random handling is only relevant for is_eval Lua - test {random numbers are random now} { - set rand1 [r eval {return tostring(math.random())} 0] - wait_for_condition 100 1 { - $rand1 ne [r eval {return tostring(math.random())} 0] - } else { - fail "random numbers should be random, now it's fixed value" - } - } - - test {Scripting engine PRNG can be seeded correctly} { - set rand1 [r eval { - math.randomseed(ARGV[1]); return tostring(math.random()) - } 0 10] - set rand2 [r eval { - math.randomseed(ARGV[1]); return tostring(math.random()) - } 0 10] - set rand3 [r eval { - math.randomseed(ARGV[1]); return tostring(math.random()) - } 0 20] - assert_equal $rand1 $rand2 - assert {$rand2 ne $rand3} - } - } ;# is_eval - - test {EVAL does not leak in the Lua stack} { - r script flush ;# reset Lua VM - r set x 0 - # Use a non blocking client to speedup the loop. - set rd [redis_deferring_client] - for {set j 0} {$j < 10000} {incr j} { - run_script_on_connection $rd {return redis.call("incr",KEYS[1])} 1 x - } - for {set j 0} {$j < 10000} {incr j} { - $rd read - } - assert {[s used_memory_lua] < 1024*100} - $rd close - r get x - } {10000} - - if {$is_eval eq 1} { - test {SPOP: We can call scripts rewriting client->argv from Lua} { - set repl [attach_to_replication_stream] - #this sadd operation is for external-cluster test. If myset doesn't exist, 'del myset' won't get propagated. - r sadd myset ppp - r del myset - r sadd myset a b c - assert {[r eval {return redis.call('spop', 'myset')} 0] ne {}} - assert {[r eval {return redis.call('spop', 'myset', 1)} 0] ne {}} - assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] ne {}} - # this one below should not be replicated - assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] eq {}} - r set trailingkey 1 - assert_replication_stream $repl { - {select *} - {sadd *} - {del *} - {sadd *} - {srem myset *} - {srem myset *} - {srem myset *} - {set *} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MGET: mget shouldn't be propagated in Lua} { - set repl [attach_to_replication_stream] - r mset a{t} 1 b{t} 2 c{t} 3 d{t} 4 - #read-only, won't be replicated - assert {[r eval {return redis.call('mget', 'a{t}', 'b{t}', 'c{t}', 'd{t}')} 0] eq {1 2 3 4}} - r set trailingkey 2 - assert_replication_stream $repl { - {select *} - {mset *} - {set *} - } - close_replication_stream $repl - } {} {needs:repl} - - test {EXPIRE: We can call scripts rewriting client->argv from Lua} { - set repl [attach_to_replication_stream] - r set expirekey 1 - #should be replicated as EXPIREAT - assert {[r eval {return redis.call('expire', KEYS[1], ARGV[1])} 1 expirekey 3] eq 1} - - assert_replication_stream $repl { - {select *} - {set *} - {pexpireat expirekey *} - } - close_replication_stream $repl - } {} {needs:repl} - - test {INCRBYFLOAT: We can call scripts expanding client->argv from Lua} { - # coverage for scripts calling commands that expand the argv array - # an attempt to add coverage for a possible bug in luaArgsToRedisArgv - # this test needs a fresh server so that lua_argv_size is 0. - # glibc realloc can return the same pointer even when the size changes - # still this test isn't able to trigger the issue, but we keep it anyway. - start_server {tags {"scripting"}} { - set repl [attach_to_replication_stream] - # a command with 5 argsument - r eval {redis.call('hmget', KEYS[1], 1, 2, 3)} 1 key - # then a command with 3 that is replicated as one with 4 - r eval {redis.call('incrbyfloat', KEYS[1], 1)} 1 key - # then a command with 4 args - r eval {redis.call('set', KEYS[1], '1', 'KEEPTTL')} 1 key - - assert_replication_stream $repl { - {select *} - {set key 1 KEEPTTL} - {set key 1 KEEPTTL} - } - close_replication_stream $repl - } - } {} {needs:repl} - - } ;# is_eval - - test {Call Redis command with many args from Lua (issue #1764)} { - run_script { - local i - local x={} - redis.call('del','mylist') - for i=1,100 do - table.insert(x,i) - end - redis.call('rpush','mylist',unpack(x)) - return redis.call('lrange','mylist',0,-1) - } 1 mylist - } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100} - - test {Number conversion precision test (issue #1118)} { - run_script { - local value = 9007199254740991 - redis.call("set","foo",value) - return redis.call("get","foo") - } 1 foo - } {9007199254740991} - - test {String containing number precision test (regression of issue #1118)} { - run_script { - redis.call("set", "key", "12039611435714932082") - return redis.call("get", "key") - } 1 key - } {12039611435714932082} - - test {Verify negative arg count is error instead of crash (issue #1842)} { - catch { run_script { return "hello" } -12 } e - set e - } {ERR Number of keys can't be negative} - - test {Scripts can handle commands with incorrect arity} { - assert_error "ERR Wrong number of args calling Redis command from script*" {run_script "redis.call('set','invalid')" 0} - assert_error "ERR Wrong number of args calling Redis command from script*" {run_script "redis.call('incr')" 0} - } - - test {Correct handling of reused argv (issue #1939)} { - run_script { - for i = 0, 10 do - redis.call('SET', 'a{t}', '1') - redis.call('MGET', 'a{t}', 'b{t}', 'c{t}') - redis.call('EXPIRE', 'a{t}', 0) - redis.call('GET', 'a{t}') - redis.call('MGET', 'a{t}', 'b{t}', 'c{t}') - end - } 3 a{t} b{t} c{t} - } - - test {Functions in the Redis namespace are able to report errors} { - catch { - run_script { - redis.sha1hex() - } 0 - } e - set e - } {*wrong number*} - - test {CLUSTER RESET can not be invoke from within a script} { - catch { - run_script { - redis.call('cluster', 'reset', 'hard') - } 0 - } e - set _ $e - } {*command is not allowed*} - - test {Script with RESP3 map} { - set expected_dict [dict create field value] - set expected_list [list field value] - - # Sanity test for RESP3 without scripts - r HELLO 3 - r hset hash field value - set res [r hgetall hash] - assert_equal $res $expected_dict - - # Test RESP3 client with script in both RESP2 and RESP3 modes - set res [run_script {redis.setresp(3); return redis.call('hgetall', KEYS[1])} 1 hash] - assert_equal $res $expected_dict - set res [run_script {redis.setresp(2); return redis.call('hgetall', KEYS[1])} 1 hash] - assert_equal $res $expected_list - - # Test RESP2 client with script in both RESP2 and RESP3 modes - r HELLO 2 - set res [run_script {redis.setresp(3); return redis.call('hgetall', KEYS[1])} 1 hash] - assert_equal $res $expected_list - set res [run_script {redis.setresp(2); return redis.call('hgetall', KEYS[1])} 1 hash] - assert_equal $res $expected_list - } {} {resp3} - - if {!$::log_req_res} { # this test creates a huge nested array which python can't handle (RecursionError: maximum recursion depth exceeded in comparison) - test {Script return recursive object} { - r readraw 1 - set res [run_script {local a = {}; local b = {a}; a[1] = b; return a} 0] - # drain the response - while {true} { - if {$res == "-ERR reached lua stack limit"} { - break - } - assert_equal $res "*1" - set res [r read] - } - r readraw 0 - # make sure the connection is still valid - assert_equal [r ping] {PONG} - } - } - - test {Script check unpack with massive arguments} { - run_script { - local a = {} - for i=1,7999 do - a[i] = 1 - end - return redis.call("lpush", "l", unpack(a)) - } 1 l - } {7999} - - test "Script read key with expiration set" { - r SET key value EX 10 - assert_equal [run_script { - if redis.call("EXISTS", "key") then - return redis.call("GET", "key") - else - return redis.call("EXISTS", "key") - end - } 1 key] "value" - } - - test "Script del key with expiration set" { - r SET key value EX 10 - assert_equal [run_script { - redis.call("DEL", "key") - return redis.call("EXISTS", "key") - } 1 key] 0 - } - - test "Script ACL check" { - r acl setuser bob on {>123} {+@scripting} {+set} {~x*} - assert_equal [r auth bob 123] {OK} - - # Check permission granted - assert_equal [run_script { - return redis.acl_check_cmd('set','xx',1) - } 1 xx] 1 - - # Check permission denied unauthorised command - assert_equal [run_script { - return redis.acl_check_cmd('hset','xx','f',1) - } 1 xx] {} - - # Check permission denied unauthorised key - # Note: we don't pass the "yy" key as an argument to the script so key acl checks won't block the script - assert_equal [run_script { - return redis.acl_check_cmd('set','yy',1) - } 0] {} - - # Check error due to invalid command - assert_error {ERR *Invalid command passed to redis.acl_check_cmd()*} {run_script { - return redis.acl_check_cmd('invalid-cmd','arg') - } 0} - } - - test "Binary code loading failed" { - assert_error {ERR *attempt to call a nil value*} {run_script { - return loadstring(string.dump(function() return 1 end))() - } 0} - } - - test "Try trick global protection 1" { - catch { - run_script { - setmetatable(_G, {}) - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick global protection 2" { - catch { - run_script { - local g = getmetatable(_G) - g.__index = {} - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick global protection 3" { - catch { - run_script { - redis = function() return 1 end - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick global protection 4" { - catch { - run_script { - _G = {} - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick readonly table on redis table" { - catch { - run_script { - redis.call = function() return 1 end - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick readonly table on json table" { - catch { - run_script { - cjson.encode = function() return 1 end - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick readonly table on cmsgpack table" { - catch { - run_script { - cmsgpack.pack = function() return 1 end - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick readonly table on bit table" { - catch { - run_script { - bit.lshift = function() return 1 end - } 0 - } e - set _ $e - } {*Attempt to modify a readonly table*} - - test "Try trick readonly table on basic types metatable" { - # Run the following scripts for basic types. Either getmetatable() - # should return nil or the metatable must be readonly. - set scripts { - {getmetatable(nil).__index = function() return 1 end} - {getmetatable('').__index = function() return 1 end} - {getmetatable(123.222).__index = function() return 1 end} - {getmetatable(true).__index = function() return 1 end} - {getmetatable(function() return 1 end).__index = function() return 1 end} - {getmetatable(coroutine.create(function() return 1 end)).__index = function() return 1 end} - } - - foreach code $scripts { - catch {run_script $code 0} e - assert { - [string match "*attempt to index a nil value script*" $e] || - [string match "*Attempt to modify a readonly table*" $e] - } - } - } - - test "Test loadfile are not available" { - catch { - run_script { - loadfile('some file') - } 0 - } e - set _ $e - } {*Script attempted to access nonexistent global variable 'loadfile'*} - - test "Test dofile are not available" { - catch { - run_script { - dofile('some file') - } 0 - } e - set _ $e - } {*Script attempted to access nonexistent global variable 'dofile'*} - - test "Test print are not available" { - catch { - run_script { - print('some data') - } 0 - } e - set _ $e - } {*Script attempted to access nonexistent global variable 'print'*} -} - -# start a new server to test the large-memory tests -start_server {tags {"scripting external:skip large-memory"}} { - test {EVAL - JSON string encoding a string larger than 2GB} { - run_script { - local s = string.rep("a", 1024 * 1024 * 1024) - return #cjson.encode(s..s..s) - } 0 - } {3221225474} ;# length includes two double quotes at both ends - - test {EVAL - Test long escape sequences for strings} { - run_script { - -- Generate 1gb '==...==' separator - local s = string.rep('=', 1024 * 1024) - local t = {} for i=1,1024 do t[i] = s end - local sep = table.concat(t) - collectgarbage('collect') - - local code = table.concat({'return [',sep,'[x]',sep,']'}) - collectgarbage('collect') - - -- Load the code and run it. Script will return the string length. - -- Escape sequence: [=....=[ to ]=...=] will be ignored - -- Actual string is a single character: 'x'. Script will return 1 - local func = loadstring(code) - return #func() - } 0 - } {1} - - test {EVAL - Lua can parse string with too many new lines} { - # Create a long string consisting only of newline characters. When Lua - # fails to parse a string, it typically includes a snippet like - # "... near ..." in the error message to indicate the last recognizable - # token. In this test, since the input contains only newlines, there - # should be no identifiable token, so the error message should contain - # only the actual error, without a near clause. - - run_script { - local s = string.rep('\n', 1024 * 1024) - local t = {} for i=1,2048 do t[#t+1] = s end - local lines = table.concat(t) - local fn, err = loadstring(lines) - return err - } 0 - } {*chunk has too many lines} -} - -# Start a new server to test lua-enable-deprecated-api config -foreach enabled {no yes} { -start_server [subst {tags {"scripting external:skip"} overrides {lua-enable-deprecated-api $enabled}}] { - test "Test setfenv availability lua-enable-deprecated-api=$enabled" { - catch { - run_script { - local f = function() return 1 end - setfenv(f, {}) - return 0 - } 0 - } e - if {$enabled} { - assert_equal $e 0 - } else { - assert_match {*Script attempted to access nonexistent global variable 'setfenv'*} $e - } - } - - test "Test getfenv availability lua-enable-deprecated-api=$enabled" { - catch { - run_script { - local f = function() return 1 end - getfenv(f) - return 0 - } 0 - } e - if {$enabled} { - assert_equal $e 0 - } else { - assert_match {*Script attempted to access nonexistent global variable 'getfenv'*} $e - } - } - - test "Test newproxy availability lua-enable-deprecated-api=$enabled" { - catch { - run_script { - getmetatable(newproxy(true)).__gc = function() return 1 end - return 0 - } 0 - } e - if {$enabled} { - assert_equal $e 0 - } else { - assert_match {*Script attempted to access nonexistent global variable 'newproxy'*} $e - } - } -} -} - -# Start a new server since the last test in this stanza will kill the -# instance at all. -start_server {tags {"scripting"}} { - test {Timedout read-only scripts can be killed by SCRIPT KILL} { - set rd [redis_deferring_client] - r config set lua-time-limit 10 - run_script_on_connection $rd {while true do end} 0 - after 200 - catch {r ping} e - assert_match {BUSY*} $e - kill_script - after 200 ; # Give some time to Lua to call the hook again... - assert_equal [r ping] "PONG" - $rd close - } - - test {Timedout read-only scripts can be killed by SCRIPT KILL even when use pcall} { - set rd [redis_deferring_client] - r config set lua-time-limit 10 - run_script_on_connection $rd {local f = function() while 1 do redis.call('ping') end end while 1 do pcall(f) end} 0 - - wait_for_condition 50 100 { - [catch {r ping} e] == 1 - } else { - fail "Can't wait for script to start running" - } - catch {r ping} e - assert_match {BUSY*} $e - - kill_script - - wait_for_condition 50 100 { - [catch {r ping} e] == 0 - } else { - fail "Can't wait for script to be killed" - } - assert_equal [r ping] "PONG" - - catch {$rd read} res - $rd close - - assert_match {*killed by user*} $res - } - - test {Timedout script does not cause a false dead client} { - set rd [redis_deferring_client] - r config set lua-time-limit 10 - - # senging (in a pipeline): - # 1. eval "while 1 do redis.call('ping') end" 0 - # 2. ping - if {$is_eval == 1} { - set buf "*3\r\n\$4\r\neval\r\n\$33\r\nwhile 1 do redis.call('ping') end\r\n\$1\r\n0\r\n" - append buf "*1\r\n\$4\r\nping\r\n" - } else { - set buf "*4\r\n\$8\r\nfunction\r\n\$4\r\nload\r\n\$7\r\nreplace\r\n\$97\r\n#!lua name=test\nredis.register_function('test', function() while 1 do redis.call('ping') end end)\r\n" - append buf "*3\r\n\$5\r\nfcall\r\n\$4\r\ntest\r\n\$1\r\n0\r\n" - append buf "*1\r\n\$4\r\nping\r\n" - } - $rd write $buf - $rd flush - - wait_for_condition 50 100 { - [catch {r ping} e] == 1 - } else { - fail "Can't wait for script to start running" - } - catch {r ping} e - assert_match {BUSY*} $e - - kill_script - wait_for_condition 50 100 { - [catch {r ping} e] == 0 - } else { - fail "Can't wait for script to be killed" - } - assert_equal [r ping] "PONG" - - if {$is_eval == 0} { - # read the function name - assert_match {test} [$rd read] - } - - catch {$rd read} res - assert_match {*killed by user*} $res - - set res [$rd read] - assert_match {*PONG*} $res - - $rd close - } - - test {Timedout script link is still usable after Lua returns} { - r config set lua-time-limit 10 - run_script {for i=1,100000 do redis.call('ping') end return 'ok'} 0 - r ping - } {PONG} - - test {Timedout scripts and unblocked command} { - # make sure a command that's allowed during BUSY doesn't trigger an unblocked command - - # enable AOF to also expose an assertion if the bug would happen - r flushall - r config set appendonly yes - - # create clients, and set one to block waiting for key 'x' - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - set r3 [redis_client] - $rd2 blpop x 0 - wait_for_blocked_clients_count 1 - - # hack: allow the script to use client list command so that we can control when it aborts - r DEBUG set-disable-deny-scripts 1 - r config set lua-time-limit 10 - run_script_on_connection $rd { - local clients - redis.call('lpush',KEYS[1],'y'); - while true do - clients = redis.call('client','list') - if string.find(clients, 'abortscript') ~= nil then break end - end - redis.call('lpush',KEYS[1],'z'); - return clients - } 1 x - - # wait for the script to be busy - after 200 - catch {r ping} e - assert_match {BUSY*} $e - - # run cause the script to abort, and run a command that could have processed - # unblocked clients (due to a bug) - $r3 hello 2 setname abortscript - - # make sure the script completed before the pop was processed - assert_equal [$rd2 read] {x z} - assert_match {*abortscript*} [$rd read] - - $rd close - $rd2 close - $r3 close - r DEBUG set-disable-deny-scripts 0 - } {OK} {external:skip needs:debug} - - test {Timedout scripts that modified data can't be killed by SCRIPT KILL} { - set rd [redis_deferring_client] - r config set lua-time-limit 10 - run_script_on_connection $rd {redis.call('set',KEYS[1],'y'); while true do end} 1 x - after 200 - catch {r ping} e - assert_match {BUSY*} $e - catch {kill_script} e - assert_match {UNKILLABLE*} $e - catch {r ping} e - assert_match {BUSY*} $e - } {} {external:skip} - - # Note: keep this test at the end of this server stanza because it - # kills the server. - test {SHUTDOWN NOSAVE can kill a timedout script anyway} { - # The server should be still unresponding to normal commands. - catch {r ping} e - assert_match {BUSY*} $e - catch {r shutdown nosave} - # Make sure the server was killed - catch {set rd [redis_deferring_client]} e - assert_match {*connection refused*} $e - } {} {external:skip} -} - - start_server {tags {"scripting repl needs:debug external:skip"}} { - start_server {} { - test "Before the replica connects we issue two EVAL commands" { - # One with an error, but still executing a command. - # SHA is: 67164fc43fa971f76fd1aaeeaf60c1c178d25876 - catch { - run_script {redis.call('incr',KEYS[1]); redis.call('nonexisting')} 1 x - } - # One command is correct: - # SHA is: 6f5ade10a69975e903c6d07b10ea44c6382381a5 - run_script {return redis.call('incr',KEYS[1])} 1 x - } {2} - - test "Connect a replica to the master instance" { - r -1 slaveof [srv 0 host] [srv 0 port] - wait_for_condition 50 100 { - [s -1 role] eq {slave} && - [string match {*master_link_status:up*} [r -1 info replication]] - } else { - fail "Can't turn the instance into a replica" - } - } - - if {$is_eval eq 1} { - test "Now use EVALSHA against the master, with both SHAs" { - # The server should replicate successful and unsuccessful - # commands as EVAL instead of EVALSHA. - catch { - r evalsha 67164fc43fa971f76fd1aaeeaf60c1c178d25876 1 x - } - r evalsha 6f5ade10a69975e903c6d07b10ea44c6382381a5 1 x - } {4} - - test "'x' should be '4' for EVALSHA being replicated by effects" { - wait_for_condition 50 100 { - [r -1 get x] eq {4} - } else { - fail "Expected 4 in x, but value is '[r -1 get x]'" - } - } - } ;# is_eval - - test "Replication of script multiple pushes to list with BLPOP" { - set rd [redis_deferring_client] - $rd brpop a 0 - run_script { - redis.call("lpush",KEYS[1],"1"); - redis.call("lpush",KEYS[1],"2"); - } 1 a - set res [$rd read] - $rd close - wait_for_condition 50 100 { - [r -1 lrange a 0 -1] eq [r lrange a 0 -1] - } else { - fail "Expected list 'a' in replica and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'" - } - set res - } {a 1} - - if {$is_eval eq 1} { - test "EVALSHA replication when first call is readonly" { - r del x - r eval {if tonumber(ARGV[1]) > 0 then redis.call('incr', KEYS[1]) end} 1 x 0 - r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 0 - r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 1 - wait_for_condition 50 100 { - [r -1 get x] eq {1} - } else { - fail "Expected 1 in x, but value is '[r -1 get x]'" - } - } - } ;# is_eval - - test "Lua scripts using SELECT are replicated correctly" { - run_script { - redis.call("set","foo1","bar1") - redis.call("select","10") - redis.call("incr","x") - redis.call("select","11") - redis.call("incr","z") - } 3 foo1 x z - run_script { - redis.call("set","foo1","bar1") - redis.call("select","10") - redis.call("incr","x") - redis.call("select","11") - redis.call("incr","z") - } 3 foo1 x z - wait_for_condition 50 100 { - [debug_digest -1] eq [debug_digest] - } else { - fail "Master-Replica desync after Lua script using SELECT." - } - } {} {singledb:skip} - } - } - -start_server {tags {"scripting repl external:skip"}} { - start_server {overrides {appendonly yes aof-use-rdb-preamble no}} { - test "Connect a replica to the master instance" { - r -1 slaveof [srv 0 host] [srv 0 port] - wait_for_condition 50 100 { - [s -1 role] eq {slave} && - [string match {*master_link_status:up*} [r -1 info replication]] - } else { - fail "Can't turn the instance into a replica" - } - } - - # replicate_commands is the default on Redis Function - test "Redis.replicate_commands() can be issued anywhere now" { - r eval { - redis.call('set','foo','bar'); - return redis.replicate_commands(); - } 0 - } {1} - - test "Redis.set_repl() can be issued before replicate_commands() now" { - catch { - r eval { - redis.set_repl(redis.REPL_ALL); - } 0 - } e - set e - } {} - - test "Redis.set_repl() don't accept invalid values" { - catch { - run_script { - redis.set_repl(12345); - } 0 - } e - set e - } {*Invalid*flags*} - - test "Test selective replication of certain Redis commands from Lua" { - r del a b c d - run_script { - redis.call('set','a','1'); - redis.set_repl(redis.REPL_NONE); - redis.call('set','b','2'); - redis.set_repl(redis.REPL_AOF); - redis.call('set','c','3'); - redis.set_repl(redis.REPL_ALL); - redis.call('set','d','4'); - } 4 a b c d - - wait_for_condition 50 100 { - [r -1 mget a b c d] eq {1 {} {} 4} - } else { - fail "Only a and d should be replicated to replica" - } - - # Master should have everything right now - assert {[r mget a b c d] eq {1 2 3 4}} - - # After an AOF reload only a, c and d should exist - r debug loadaof - - assert {[r mget a b c d] eq {1 {} 3 4}} - } - - test "PRNG is seeded randomly for command replication" { - if {$is_eval eq 1} { - # on is_eval Lua we need to call redis.replicate_commands() to get real randomization - set a [ - run_script { - redis.replicate_commands() - return math.random()*100000; - } 0 - ] - set b [ - run_script { - redis.replicate_commands() - return math.random()*100000; - } 0 - ] - } else { - set a [ - run_script { - return math.random()*100000; - } 0 - ] - set b [ - run_script { - return math.random()*100000; - } 0 - ] - } - assert {$a ne $b} - } - - test "Using side effects is not a problem with command replication" { - run_script { - redis.call('set','time',redis.call('time')[1]) - } 0 - - assert {[r get time] ne {}} - - wait_for_condition 50 100 { - [r get time] eq [r -1 get time] - } else { - fail "Time key does not match between master and replica" - } - } - } -} - -if {$is_eval eq 1} { -start_server {tags {"scripting external:skip"}} { - r script debug sync - r eval {return 'hello'} 0 - r eval {return 'hello'} 0 -} - -start_server {tags {"scripting needs:debug external:skip"}} { - test {Test scripting debug protocol parsing} { - r script debug sync - r eval {return 'hello'} 0 - catch {r 'hello\0world'} e - assert_match {*Unknown Redis Lua debugger command*} $e - catch {r 'hello\0'} e - assert_match {*Unknown Redis Lua debugger command*} $e - catch {r '\0hello'} e - assert_match {*Unknown Redis Lua debugger command*} $e - catch {r '\0hello\0'} e - assert_match {*Unknown Redis Lua debugger command*} $e - } - - test {Test scripting debug lua stack overflow} { - r script debug sync - r eval {return 'hello'} 0 - set cmd "*101\r\n\$5\r\nredis\r\n" - append cmd [string repeat "\$4\r\ntest\r\n" 100] - r write $cmd - r flush - set ret [r read] - assert_match {*Unknown Redis command called from script*} $ret - # make sure the server is still ok - reconnect - assert_equal [r ping] {PONG} - } -} - -start_server {tags {"scripting external:skip"}} { - test {Lua scripts eviction does not generate many scripts} { - r script flush - r config resetstat - - # "return 1" sha is: e0e1f9fabfc9d4800c877a703b823ac0578ff8db - # "return 500" sha is: 98fe65896b61b785c5ed328a5a0a1421f4f1490c - for {set j 1} {$j <= 250} {incr j} { - r eval "return $j" 0 - } - for {set j 251} {$j <= 500} {incr j} { - r eval_ro "return $j" 0 - } - assert_equal [s number_of_cached_scripts] 500 - assert_equal 1 [r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0] - assert_equal 1 [r evalsha_ro e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0] - assert_equal 500 [r evalsha 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0] - assert_equal 500 [r evalsha_ro 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0] - - # Scripts between "return 1" and "return 500" are evicted - for {set j 501} {$j <= 750} {incr j} { - r eval "return $j" 0 - } - for {set j 751} {$j <= 1000} {incr j} { - r eval "return $j" 0 - } - assert_error {NOSCRIPT*} {r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0} - assert_error {NOSCRIPT*} {r evalsha_ro e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0} - assert_error {NOSCRIPT*} {r evalsha 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0} - assert_error {NOSCRIPT*} {r evalsha_ro 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0} - - assert_equal [s evicted_scripts] 500 - assert_equal [s number_of_cached_scripts] 500 - } - - test {Lua scripts eviction is plain LRU} { - r script flush - r config resetstat - - # "return 1" sha is: e0e1f9fabfc9d4800c877a703b823ac0578ff8db - # "return 2" sha is: 7f923f79fe76194c868d7e1d0820de36700eb649 - # "return 3" sha is: 09d3822de862f46d784e6a36848b4f0736dda47a - # "return 500" sha is: 98fe65896b61b785c5ed328a5a0a1421f4f1490c - # "return 1000" sha is: 94f1a7bc9f985a1a1d5a826a85579137d9d840c8 - for {set j 1} {$j <= 500} {incr j} { - r eval "return $j" 0 - } - - # Call "return 1" to move it to the tail. - r eval "return 1" 0 - # Call "return 2" to move it to the tail. - r evalsha 7f923f79fe76194c868d7e1d0820de36700eb649 0 - # Create a new script, "return 3" will be evicted. - r eval "return 1000" 0 - # "return 1" is ok since it was moved to tail. - assert_equal 1 [r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0] - # "return 2" is ok since it was moved to tail. - assert_equal 1 [r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0] - # "return 3" was evicted. - assert_error {NOSCRIPT*} {r evalsha 09d3822de862f46d784e6a36848b4f0736dda47a 0} - # Others are ok. - assert_equal 500 [r evalsha 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0] - assert_equal 1000 [r evalsha 94f1a7bc9f985a1a1d5a826a85579137d9d840c8 0] - - assert_equal [s evicted_scripts] 1 - assert_equal [s number_of_cached_scripts] 500 - } - - test {Lua scripts eviction does not affect script load} { - r script flush - r config resetstat - - set num [randomRange 500 1000] - for {set j 1} {$j <= $num} {incr j} { - r script load "return $j" - r eval "return 'str_$j'" 0 - } - set evicted [s evicted_scripts] - set cached [s number_of_cached_scripts] - # evicted = num eval scripts - 500 eval scripts - assert_equal $evicted [expr $num-500] - # cached = num load scripts + 500 eval scripts - assert_equal $cached [expr $num+500] - } -} - -} ;# is_eval - -start_server {tags {"scripting needs:debug"}} { - r debug set-disable-deny-scripts 1 - - for {set i 2} {$i <= 3} {incr i} { - for {set client_proto 2} {$client_proto <= 3} {incr client_proto} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$client_proto == 3} {continue} - } elseif {$::force_resp3} { - if {$client_proto == 2} {continue} - } - r hello $client_proto - set extra "RESP$i/$client_proto" - r readraw 1 - - test "test $extra big number protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'bignum')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {$37} - assert_equal [r read] {1234567999999999999999999999999999999} - } else { - assert_equal $ret {(1234567999999999999999999999999999999} - } - } - - test "test $extra malformed big number protocol parsing" { - set ret [run_script "return {big_number='123\\r\\n123'}" 0] - if {$client_proto == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {$8} - assert_equal [r read] {123 123} - } else { - assert_equal $ret {(123 123} - } - } - - test "test $extra map protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'map')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {*6} - } else { - assert_equal $ret {%3} - } - for {set j 0} {$j < 6} {incr j} { - r read - } - } - - test "test $extra set protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'set')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {*3} - } else { - assert_equal $ret {~3} - } - for {set j 0} {$j < 3} {incr j} { - r read - } - } - - test "test $extra double protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'double')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {$5} - assert_equal [r read] {3.141} - } else { - assert_equal $ret {,3.141} - } - } - - test "test $extra null protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'null')" 0] - if {$client_proto == 2} { - # null is a special case in which a Lua client format does not effect the reply to the client - assert_equal $ret {$-1} - } else { - assert_equal $ret {_} - } - } {} - - test "test $extra verbatim protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'verbatim')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {$25} - assert_equal [r read] {This is a verbatim} - assert_equal [r read] {string} - } else { - assert_equal $ret {=29} - assert_equal [r read] {txt:This is a verbatim} - assert_equal [r read] {string} - } - } - - test "test $extra true protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'true')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {:1} - } else { - assert_equal $ret {#t} - } - } - - test "test $extra false protocol parsing" { - set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'false')" 0] - if {$client_proto == 2 || $i == 2} { - # if either Lua or the client is RESP2 the reply will be RESP2 - assert_equal $ret {:0} - } else { - assert_equal $ret {#f} - } - } - - r readraw 0 - r hello 2 - } - } - - # attribute is not relevant to test with resp2 - test {test resp3 attribute protocol parsing} { - # attributes are not (yet) expose to the script - # So here we just check the parser handles them and they are ignored. - run_script "redis.setresp(3);return redis.call('debug', 'protocol', 'attrib')" 0 - } {Some real reply following the attribute} - - test "Script block the time during execution" { - assert_equal [run_script { - redis.call("SET", "key", "value", "PX", "1") - redis.call("DEBUG", "SLEEP", 0.01) - return redis.call("EXISTS", "key") - } 1 key] 1 - - assert_equal 0 [r EXISTS key] - } - - test "Script delete the expired key" { - r DEBUG set-active-expire 0 - r SET key value PX 1 - after 2 - - # use DEBUG OBJECT to make sure it doesn't error (means the key still exists) - r DEBUG OBJECT key - - assert_equal [run_script {return redis.call('EXISTS', 'key')} 1 key] 0 - assert_equal 0 [r EXISTS key] - r DEBUG set-active-expire 1 - } - - test "TIME command using cached time" { - set res [run_script { - local result1 = {redis.call("TIME")} - redis.call("DEBUG", "SLEEP", 0.01) - local result2 = {redis.call("TIME")} - return {result1, result2} - } 0] - assert_equal [lindex $res 0] [lindex $res 1] - } - - test "Script block the time in some expiration related commands" { - # The test uses different commands to set the "same" expiration time for different keys, - # and interspersed with "DEBUG SLEEP", to verify that time is frozen in script. - # The commands involved are [P]TTL / SET EX[PX] / [P]EXPIRE / GETEX / [P]SETEX / [P]EXPIRETIME - set res [run_script { - redis.call("SET", "key1{t}", "value", "EX", 1) - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("SET", "key2{t}", "value", "PX", 1000) - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("SET", "key3{t}", "value") - redis.call("EXPIRE", "key3{t}", 1) - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("SET", "key4{t}", "value") - redis.call("PEXPIRE", "key4{t}", 1000) - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("SETEX", "key5{t}", 1, "value") - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("PSETEX", "key6{t}", 1000, "value") - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("SET", "key7{t}", "value") - redis.call("GETEX", "key7{t}", "EX", 1) - redis.call("DEBUG", "SLEEP", 0.01) - - redis.call("SET", "key8{t}", "value") - redis.call("GETEX", "key8{t}", "PX", 1000) - redis.call("DEBUG", "SLEEP", 0.01) - - local ttl_results = {redis.call("TTL", "key1{t}"), - redis.call("TTL", "key2{t}"), - redis.call("TTL", "key3{t}"), - redis.call("TTL", "key4{t}"), - redis.call("TTL", "key5{t}"), - redis.call("TTL", "key6{t}"), - redis.call("TTL", "key7{t}"), - redis.call("TTL", "key8{t}")} - - local pttl_results = {redis.call("PTTL", "key1{t}"), - redis.call("PTTL", "key2{t}"), - redis.call("PTTL", "key3{t}"), - redis.call("PTTL", "key4{t}"), - redis.call("PTTL", "key5{t}"), - redis.call("PTTL", "key6{t}"), - redis.call("PTTL", "key7{t}"), - redis.call("PTTL", "key8{t}")} - - local expiretime_results = {redis.call("EXPIRETIME", "key1{t}"), - redis.call("EXPIRETIME", "key2{t}"), - redis.call("EXPIRETIME", "key3{t}"), - redis.call("EXPIRETIME", "key4{t}"), - redis.call("EXPIRETIME", "key5{t}"), - redis.call("EXPIRETIME", "key6{t}"), - redis.call("EXPIRETIME", "key7{t}"), - redis.call("EXPIRETIME", "key8{t}")} - - local pexpiretime_results = {redis.call("PEXPIRETIME", "key1{t}"), - redis.call("PEXPIRETIME", "key2{t}"), - redis.call("PEXPIRETIME", "key3{t}"), - redis.call("PEXPIRETIME", "key4{t}"), - redis.call("PEXPIRETIME", "key5{t}"), - redis.call("PEXPIRETIME", "key6{t}"), - redis.call("PEXPIRETIME", "key7{t}"), - redis.call("PEXPIRETIME", "key8{t}")} - - return {ttl_results, pttl_results, expiretime_results, pexpiretime_results} - } 8 key1{t} key2{t} key3{t} key4{t} key5{t} key6{t} key7{t} key8{t}] - - # The elements in each list are equal. - assert_equal 1 [llength [lsort -unique [lindex $res 0]]] - assert_equal 1 [llength [lsort -unique [lindex $res 1]]] - assert_equal 1 [llength [lsort -unique [lindex $res 2]]] - assert_equal 1 [llength [lsort -unique [lindex $res 3]]] - - # Then we check that the expiration time is set successfully. - assert_morethan [lindex $res 0] 0 - assert_morethan [lindex $res 1] 0 - assert_morethan [lindex $res 2] 0 - assert_morethan [lindex $res 3] 0 - } - - test "RESTORE expired keys with expiration time" { - set res [run_script { - redis.call("SET", "key1{t}", "value") - local encoded = redis.call("DUMP", "key1{t}") - - redis.call("RESTORE", "key2{t}", 1, encoded, "REPLACE") - redis.call("DEBUG", "SLEEP", 0.01) - redis.call("RESTORE", "key3{t}", 1, encoded, "REPLACE") - - return {redis.call("PEXPIRETIME", "key2{t}"), redis.call("PEXPIRETIME", "key3{t}")} - } 3 key1{t} key2{t} key3{t}] - - # Can get the expiration time and they are all equal. - assert_morethan [lindex $res 0] 0 - assert_equal [lindex $res 0] [lindex $res 1] - } - - r debug set-disable-deny-scripts 0 -} - -start_server {tags {"scripting"}} { - test "Test script flush will not leak memory - script:$is_eval" { - r flushall - r script flush - r function flush - - # This is a best-effort test to check we don't leak some resources on - # script flush and function flush commands. For lua vm, we create a - # jemalloc thread cache. On each script flush command, thread cache is - # destroyed and we create a new one. In this test, running script flush - # many times to verify there is no increase in the memory usage while - # re-creating some of the resources for lua vm. - set used_memory [s used_memory] - set allocator_allocated [s allocator_allocated] - - r multi - for {set j 1} {$j <= 500} {incr j} { - if {$is_eval} { - r SCRIPT FLUSH - } else { - r FUNCTION FLUSH - } - } - r exec - - # Verify used memory is not (much) higher. - assert_lessthan [s used_memory] [expr $used_memory*1.5] - assert_lessthan [s allocator_allocated] [expr $allocator_allocated*1.5] - } - - test "Verify Lua performs GC correctly after script loading" { - set dummy_script "--[string repeat x 10]\nreturn " - set n 50000 - for {set i 0} {$i < $n} {incr i} { - set script "$dummy_script[format "%06d" $i]" - if {$is_eval} { - r script load $script - } else { - r function load "#!lua name=test$i\nredis.register_function('test$i', function(KEYS, ARGV)\n $script \nend)" - } - } - - if {$is_eval} { - assert_lessthan [s used_memory_lua] 17500000 - } else { - assert_lessthan [s used_memory_vm_functions] 14500000 - } - } {} {debug_defrag:skip} -} -} ;# foreach is_eval - - -# Scripting "shebang" notation tests -start_server {tags {"scripting"}} { - test "Shebang support for lua engine" { - catch { - r eval {#!not-lua - return 1 - } 0 - } e - assert_match {*Unexpected engine in script shebang*} $e - - assert_equal [r eval {#!lua - return 1 - } 0] 1 - } - - test "Unknown shebang option" { - catch { - r eval {#!lua badger=data - return 1 - } 0 - } e - assert_match {*Unknown lua shebang option*} $e - } - - test "Unknown shebang flag" { - catch { - r eval {#!lua flags=allow-oom,what? - return 1 - } 0 - } e - assert_match {*Unexpected flag in script shebang*} $e - } - - test "allow-oom shebang flag" { - r set x 123 - - r config set maxmemory 1 - - # Fail to execute deny-oom command in OOM condition (backwards compatibility mode without flags) - assert_error {OOM command not allowed when used memory > 'maxmemory'*} { - r eval { - redis.call('set','x',1) - return 1 - } 1 x - } - # Can execute non deny-oom commands in OOM condition (backwards compatibility mode without flags) - assert_equal [ - r eval { - return redis.call('get','x') - } 1 x - ] {123} - - # Fail to execute regardless of script content when we use default flags in OOM condition - assert_error {OOM *} { - r eval {#!lua flags= - return 1 - } 0 - } - - # Script with allow-oom can write despite being in OOM state - assert_equal [ - r eval {#!lua flags=allow-oom - redis.call('set','x',1) - return 1 - } 1 x - ] 1 - - # read-only scripts implies allow-oom - assert_equal [ - r eval {#!lua flags=no-writes - redis.call('get','x') - return 1 - } 0 - ] 1 - assert_equal [ - r eval_ro {#!lua flags=no-writes - redis.call('get','x') - return 1 - } 1 x - ] 1 - - # Script with no shebang can read in OOM state - assert_equal [ - r eval { - redis.call('get','x') - return 1 - } 1 x - ] 1 - - # Script with no shebang can read in OOM state (eval_ro variant) - assert_equal [ - r eval_ro { - redis.call('get','x') - return 1 - } 1 x - ] 1 - - r config set maxmemory 0 - } {OK} {needs:config-maxmemory} - - test "no-writes shebang flag" { - assert_error {ERR Write commands are not allowed from read-only scripts*} { - r eval {#!lua flags=no-writes - redis.call('set','x',1) - return 1 - } 1 x - } - } - - start_server {tags {"external:skip"}} { - r -1 set x "some value" - test "no-writes shebang flag on replica" { - r replicaof [srv -1 host] [srv -1 port] - wait_for_condition 50 100 { - [s role] eq {slave} && - [string match {*master_link_status:up*} [r info replication]] - } else { - fail "Can't turn the instance into a replica" - } - - assert_equal [ - r eval {#!lua flags=no-writes - return redis.call('get','x') - } 1 x - ] "some value" - - assert_error {READONLY You can't write against a read only replica.} { - r eval {#!lua - return redis.call('get','x') - } 1 x - } - - # test no-write inside multi-exec - r multi - r eval {#!lua flags=no-writes - redis.call('get','x') - return 1 - } 1 x - assert_equal [r exec] 1 - - # test no shebang without write inside multi-exec - r multi - r eval { - redis.call('get','x') - return 1 - } 1 x - assert_equal [r exec] 1 - - # temporarily set the server to master, so it doesn't block the queuing - # and we can test the evaluation of the flags on exec - r replicaof no one - set rr [redis_client] - set rr2 [redis_client] - $rr multi - $rr2 multi - - # test write inside multi-exec - # we don't need to do any actual write - $rr eval {#!lua - return 1 - } 0 - - # test no shebang with write inside multi-exec - $rr2 eval { - redis.call('set','x',1) - return 1 - } 1 x - - r replicaof [srv -1 host] [srv -1 port] - - # To avoid -LOADING reply, wait until replica syncs with master. - wait_for_condition 50 100 { - [s master_link_status] eq {up} - } else { - fail "Replica did not sync in time." - } - - assert_error {EXECABORT Transaction discarded because of: READONLY *} {$rr exec} - assert_error {READONLY You can't write against a read only replica. script: *} {$rr2 exec} - $rr close - $rr2 close - } - } - - test "not enough good replicas" { - r set x "some value" - r config set min-replicas-to-write 1 - - assert_equal [ - r eval {#!lua flags=no-writes - return redis.call('get','x') - } 1 x - ] "some value" - - assert_equal [ - r eval { - return redis.call('get','x') - } 1 x - ] "some value" - - assert_error {NOREPLICAS *} { - r eval {#!lua - return redis.call('get','x') - } 1 x - } - - assert_error {NOREPLICAS *} { - r eval { - return redis.call('set','x', 1) - } 1 x - } - - r config set min-replicas-to-write 0 - } - - test "not enough good replicas state change during long script" { - r set x "pre-script value" - r config set min-replicas-to-write 1 - r config set lua-time-limit 10 - start_server {tags {"external:skip"}} { - # add a replica and wait for the master to recognize it's online - r slaveof [srv -1 host] [srv -1 port] - wait_replica_online [srv -1 client] - - # run a slow script that does one write, then waits for INFO to indicate - # that the replica dropped, and then runs another write - set rd [redis_deferring_client -1] - $rd eval { - redis.call('set','x',"script value") - while true do - local info = redis.call('info','replication') - if (string.match(info, "connected_slaves:0")) then - redis.call('set','x',info) - break - end - end - return 1 - } 1 x - - # wait for the script to time out and yield - wait_for_condition 100 100 { - [catch {r -1 ping} e] == 1 - } else { - fail "Can't wait for script to start running" - } - catch {r -1 ping} e - assert_match {BUSY*} $e - - # cause the replica to disconnect (triggering the busy script to exit) - r slaveof no one - - # make sure the script was able to write after the replica dropped - assert_equal [$rd read] 1 - assert_match {*connected_slaves:0*} [r -1 get x] - - $rd close - } - r config set min-replicas-to-write 0 - r config set lua-time-limit 5000 - } {OK} {external:skip needs:repl} - - test "allow-stale shebang flag" { - r config set replica-serve-stale-data no - r replicaof 127.0.0.1 1 - - assert_error {MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.} { - r eval { - return redis.call('get','x') - } 1 x - } - - assert_error {MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.} { - r eval {#!lua flags=no-writes - return 1 - } 0 - } - - assert_equal [ - r eval {#!lua flags=allow-stale,no-writes - return 1 - } 0 - ] 1 - - - assert_error {*Can not execute the command on a stale replica*} { - r eval {#!lua flags=allow-stale,no-writes - return redis.call('get','x') - } 1 x - } - - assert_match {foobar} [ - r eval {#!lua flags=allow-stale,no-writes - return redis.call('echo','foobar') - } 0 - ] - - # Test again with EVALSHA - set sha [ - r script load {#!lua flags=allow-stale,no-writes - return redis.call('echo','foobar') - } - ] - assert_match {foobar} [r evalsha $sha 0] - - r replicaof no one - r config set replica-serve-stale-data yes - set _ {} - } {} {external:skip} - - test "reject script do not cause a Lua stack leak" { - r config set maxmemory 1 - for {set i 0} {$i < 50} {incr i} { - assert_error {OOM *} {r eval {#!lua - return 1 - } 0} - } - r config set maxmemory 0 - assert_equal [r eval {#!lua - return 1 - } 0] 1 - } -} - -# Additional eval only tests -start_server {tags {"scripting"}} { - test "Consistent eval error reporting" { - r config resetstat - r config set maxmemory 1 - # Script aborted due to Redis state (OOM) should report script execution error with detailed internal error - assert_error {OOM command not allowed when used memory > 'maxmemory'*} { - r eval {return redis.call('set','x','y')} 1 x - } - assert_equal [errorrstat OOM r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r] - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r] - - # redis.pcall() failure due to Redis state (OOM) returns lua error table with Redis error message without '-' prefix - r config resetstat - assert_equal [ - r eval { - local t = redis.pcall('set','x','y') - if t['err'] == "OOM command not allowed when used memory > 'maxmemory'." then - return 1 - else - return 0 - end - } 1 x - ] 1 - # error stats were not incremented - assert_equal [errorrstat ERR r] {} - assert_equal [errorrstat OOM r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r] - assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval r] - - # Returning an error object from lua is handled as a valid RESP error result. - r config resetstat - assert_error {OOM command not allowed when used memory > 'maxmemory'.} { - r eval { return redis.pcall('set','x','y') } 1 x - } - assert_equal [errorrstat ERR r] {} - assert_equal [errorrstat OOM r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r] - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r] - - r config set maxmemory 0 - r config resetstat - # Script aborted due to error result of Redis command - assert_error {ERR DB index is out of range*} { - r eval {return redis.call('select',99)} 0 - } - assert_equal [errorrstat ERR r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat select r] - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r] - - # redis.pcall() failure due to error in Redis command returns lua error table with redis error message without '-' prefix - r config resetstat - assert_equal [ - r eval { - local t = redis.pcall('select',99) - if t['err'] == "ERR DB index is out of range" then - return 1 - else - return 0 - end - } 0 - ] 1 - assert_equal [errorrstat ERR r] {count=1} ; - assert_equal [s total_error_replies] {1} - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat select r] - assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval r] - - # Script aborted due to scripting specific error state (write cmd with eval_ro) should report script execution error with detailed internal error - r config resetstat - assert_error {ERR Write commands are not allowed from read-only scripts*} { - r eval_ro {return redis.call('set','x','y')} 1 x - } - assert_equal [errorrstat ERR r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r] - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval_ro r] - - # redis.pcall() failure due to scripting specific error state (write cmd with eval_ro) returns lua error table with Redis error message without '-' prefix - r config resetstat - assert_equal [ - r eval_ro { - local t = redis.pcall('set','x','y') - if t['err'] == "ERR Write commands are not allowed from read-only scripts." then - return 1 - else - return 0 - end - } 1 x - ] 1 - assert_equal [errorrstat ERR r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r] - assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval_ro r] - - r config resetstat - # make sure geoadd will failed - r set Sicily 1 - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} { - r eval {return redis.call('GEOADD', 'Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania')} 1 x - } - assert_equal [errorrstat WRONGTYPE r] {count=1} - assert_equal [s total_error_replies] {1} - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat geoadd r] - assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r] - } {} {cluster:skip} - - test "LUA redis.error_reply API" { - r config resetstat - assert_error {MY_ERR_CODE custom msg} { - r eval {return redis.error_reply("MY_ERR_CODE custom msg")} 0 - } - assert_equal [errorrstat MY_ERR_CODE r] {count=1} - } - - test "LUA redis.error_reply API with empty string" { - r config resetstat - assert_error {ERR} { - r eval {return redis.error_reply("")} 0 - } - assert_equal [errorrstat ERR r] {count=1} - } - - test "LUA redis.status_reply API" { - r config resetstat - r readraw 1 - assert_equal [ - r eval {return redis.status_reply("MY_OK_CODE custom msg")} 0 - ] {+MY_OK_CODE custom msg} - r readraw 0 - assert_equal [errorrstat MY_ERR_CODE r] {} ;# error stats were not incremented - } - - test "LUA test pcall" { - assert_equal [ - r eval {local status, res = pcall(function() return 1 end); return 'status: ' .. tostring(status) .. ' result: ' .. res} 0 - ] {status: true result: 1} - } - - test "LUA test pcall with error" { - assert_match {status: false result:*Script attempted to access nonexistent global variable 'foo'} [ - r eval {local status, res = pcall(function() return foo end); return 'status: ' .. tostring(status) .. ' result: ' .. res} 0 - ] - } - - test "LUA test pcall with non string/integer arg" { - assert_error "ERR Lua redis lib command arguments must be strings or integers*" { - r eval { - local x={} - return redis.call("ping", x) - } 0 - } - # run another command, to make sure the cached argv array survived - assert_equal [ - r eval { - return redis.call("ping", "asdf") - } 0 - ] {asdf} - } - - test "LUA test trim string as expected" { - # this test may fail if we use different memory allocator than jemalloc, as libc for example may keep the old size on realloc. - if {[string match {*jemalloc*} [s mem_allocator]]} { - # test that when using LUA cache mechanism, if there is free space in the argv array, the string is trimmed. - r set foo [string repeat "a" 45] - set expected_memory [r memory usage foo] - - # Jemalloc will allocate for the requested 63 bytes, 80 bytes. - # We can't test for larger sizes because LUA_CMD_OBJCACHE_MAX_LEN is 64. - # This value will be recycled to be used in the next argument. - # We use SETNX to avoid saving the string which will prevent us to reuse it in the next command. - r eval { - return redis.call("SETNX", "foo", string.rep("a", 63)) - } 0 - - # Jemalloc will allocate for the request 45 bytes, 56 bytes. - # we can't test for smaller sizes because OBJ_ENCODING_EMBSTR_SIZE_LIMIT is 44 where no trim is done. - r eval { - return redis.call("SET", "foo", string.rep("a", 45)) - } 0 - - # Assert the string has been trimmed and the 80 bytes from the previous alloc were not kept. - assert { [r memory usage foo] <= $expected_memory}; - } - } - - test {EVAL - explicit error() call handling} { - # error("simple string error") - assert_error {ERR user_script:1: simple string error script: *} { - r eval "error('simple string error')" 0 - } - - # error({"err": "ERR table error"}) - assert_error {ERR table error script: *} { - r eval "error({err='ERR table error'})" 0 - } - - # error({}) - assert_error {ERR unknown error script: *} { - r eval "error({})" 0 - } - } -} diff --git a/examples/redis-unstable/tests/unit/shutdown.tcl b/examples/redis-unstable/tests/unit/shutdown.tcl deleted file mode 100644 index 7504851..0000000 --- a/examples/redis-unstable/tests/unit/shutdown.tcl +++ /dev/null @@ -1,133 +0,0 @@ -start_server {tags {"shutdown external:skip"}} { - test {Temp rdb will be deleted if we use bg_unlink when shutdown} { - for {set i 0} {$i < 20} {incr i} { - r set $i $i - } - r config set rdb-key-save-delay 10000000 - - # Child is dumping rdb - r bgsave - wait_for_condition 1000 10 { - [s rdb_bgsave_in_progress] eq 1 - } else { - fail "bgsave did not start in time" - } - after 100 ;# give the child a bit of time for the file to be created - - set dir [lindex [r config get dir] 1] - set child_pid [get_child_pid 0] - set temp_rdb [file join [lindex [r config get dir] 1] temp-${child_pid}.rdb] - # Temp rdb must be existed - assert {[file exists $temp_rdb]} - - catch {r shutdown nosave} - # Make sure the server was killed - catch {set rd [redis_deferring_client]} e - assert_match {*connection refused*} $e - - # Temp rdb file must be deleted - assert {![file exists $temp_rdb]} - } -} - -start_server {tags {"shutdown external:skip"} overrides {save {900 1}}} { - test {SHUTDOWN ABORT can cancel SIGTERM} { - r debug pause-cron 1 - set pid [s process_id] - exec kill -SIGTERM $pid - after 10; # Give signal handler some time to run - r shutdown abort - verify_log_message 0 "*Shutdown manually aborted*" 0 - r debug pause-cron 0 - r ping - } {PONG} - - test {Temp rdb will be deleted in signal handle} { - for {set i 0} {$i < 20} {incr i} { - r set $i $i - } - # It will cost 2s (20 * 100ms) to dump rdb - r config set rdb-key-save-delay 100000 - - set pid [s process_id] - set temp_rdb [file join [lindex [r config get dir] 1] temp-${pid}.rdb] - - # trigger a shutdown which will save an rdb - exec kill -SIGINT $pid - # Wait for creation of temp rdb - wait_for_condition 50 10 { - [file exists $temp_rdb] - } else { - fail "Can't trigger rdb save on shutdown" - } - - # Insist on immediate shutdown, temp rdb file must be deleted - exec kill -SIGINT $pid - # wait for the rdb file to be deleted - wait_for_condition 50 10 { - ![file exists $temp_rdb] - } else { - fail "Can't trigger rdb save on shutdown" - } - } -} - -start_server {tags {"shutdown external:skip"} overrides {save {900 1}}} { - set pid [s process_id] - set dump_rdb [file join [lindex [r config get dir] 1] dump.rdb] - - test {RDB save will be failed in shutdown} { - for {set i 0} {$i < 20} {incr i} { - r set $i $i - } - - # create a folder called 'dump.rdb' to trigger temp-rdb rename failure - # and it will cause rdb save to fail eventually. - if {[file exists $dump_rdb]} { - exec rm -f $dump_rdb - } - exec mkdir -p $dump_rdb - } - test {SHUTDOWN will abort if rdb save failed on signal} { - # trigger a shutdown which will save an rdb - exec kill -SIGINT $pid - wait_for_log_messages 0 {"*Error trying to save the DB, can't exit*"} 0 100 10 - } - test {SHUTDOWN will abort if rdb save failed on shutdown command} { - catch {[r shutdown]} err - assert_match {*Errors trying to SHUTDOWN*} $err - # make sure the server is still alive - assert_equal [r ping] {PONG} - } - test {SHUTDOWN can proceed if shutdown command was with nosave} { - catch {[r shutdown nosave]} - wait_for_log_messages 0 {"*ready to exit, bye bye*"} 0 100 10 - } - test {Clean up rdb same named folder} { - exec rm -r $dump_rdb - } -} - - -start_server {tags {"shutdown external:skip"} overrides {appendonly no}} { - test {SHUTDOWN SIGTERM will abort if there's an initial AOFRW - default} { - r config set shutdown-on-sigterm default - r config set rdb-key-save-delay 10000000 - for {set i 0} {$i < 10} {incr i} { - r set $i $i - } - - r config set appendonly yes - wait_for_condition 1000 10 { - [s aof_rewrite_in_progress] eq 1 - } else { - fail "aof rewrite did not start in time" - } - - set pid [s process_id] - exec kill -SIGTERM $pid - wait_for_log_messages 0 {"*Writing initial AOF, can't exit*"} 0 1000 10 - - r config set shutdown-on-sigterm force - } -} diff --git a/examples/redis-unstable/tests/unit/slowlog.tcl b/examples/redis-unstable/tests/unit/slowlog.tcl deleted file mode 100644 index e7f82ce..0000000 --- a/examples/redis-unstable/tests/unit/slowlog.tcl +++ /dev/null @@ -1,251 +0,0 @@ -start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} { - test {SLOWLOG - check that it starts with an empty log} { - if {$::external} { - r slowlog reset - } - r slowlog len - } {0} - - test {SLOWLOG - only logs commands taking more time than specified} { - r config set slowlog-log-slower-than 100000 - r ping - assert_equal [r slowlog len] 0 - r debug sleep 0.2 - assert_equal [r slowlog len] 1 - } {} {needs:debug} - - test {SLOWLOG - zero max length is correctly handled} { - r SLOWLOG reset - r config set slowlog-max-len 0 - r config set slowlog-log-slower-than 0 - for {set i 0} {$i < 100} {incr i} { - r ping - } - r slowlog len - } {0} - - test {SLOWLOG - max entries is correctly handled} { - r config set slowlog-log-slower-than 0 - r config set slowlog-max-len 10 - for {set i 0} {$i < 100} {incr i} { - r ping - } - r slowlog len - } {10} - - test {SLOWLOG - GET optional argument to limit output len works} { - - assert_equal 5 [llength [r slowlog get 5]] - assert_equal 10 [llength [r slowlog get -1]] - assert_equal 10 [llength [r slowlog get 20]] - } - - test {SLOWLOG - RESET subcommand works} { - r config set slowlog-log-slower-than 100000 - r slowlog reset - r slowlog len - } {0} - - test {SLOWLOG - logged entry sanity check} { - r client setname foobar - r debug sleep 0.2 - set e [lindex [r slowlog get] 0] - assert_equal [llength $e] 6 - if {!$::external} { - assert_equal [lindex $e 0] 106 - } - assert_equal [expr {[lindex $e 2] > 100000}] 1 - assert_equal [lindex $e 3] {debug sleep 0.2} - assert_equal {foobar} [lindex $e 5] - } {} {needs:debug} - - test {SLOWLOG - Certain commands are omitted that contain sensitive information} { - r config set slowlog-max-len 100 - r config set slowlog-log-slower-than 0 - r slowlog reset - catch {r acl setuser "slowlog test user" +get +set} _ - r config set masteruser "" - r config set masterauth "" - r config set requirepass "" - r config set tls-key-file-pass "" - r config set tls-client-key-file-pass "" - r acl setuser slowlog-test-user +get +set - r acl getuser slowlog-test-user - r acl deluser slowlog-test-user non-existing-user - r config set slowlog-log-slower-than 0 - r config set slowlog-log-slower-than -1 - set slowlog_resp [r slowlog get -1] - - # Make sure normal configs work, but the two sensitive - # commands are omitted or redacted - assert_equal 11 [llength $slowlog_resp] - assert_equal {slowlog reset} [lindex [lindex $slowlog_resp 10] 3] - assert_equal {acl setuser (redacted) (redacted) (redacted)} [lindex [lindex $slowlog_resp 9] 3] - assert_equal {config set masteruser (redacted)} [lindex [lindex $slowlog_resp 8] 3] - assert_equal {config set masterauth (redacted)} [lindex [lindex $slowlog_resp 7] 3] - assert_equal {config set requirepass (redacted)} [lindex [lindex $slowlog_resp 6] 3] - assert_equal {config set tls-key-file-pass (redacted)} [lindex [lindex $slowlog_resp 5] 3] - assert_equal {config set tls-client-key-file-pass (redacted)} [lindex [lindex $slowlog_resp 4] 3] - assert_equal {acl setuser (redacted) (redacted) (redacted)} [lindex [lindex $slowlog_resp 3] 3] - assert_equal {acl getuser (redacted)} [lindex [lindex $slowlog_resp 2] 3] - assert_equal {acl deluser (redacted) (redacted)} [lindex [lindex $slowlog_resp 1] 3] - assert_equal {config set slowlog-log-slower-than 0} [lindex [lindex $slowlog_resp 0] 3] - } {} {needs:repl} - - test {SLOWLOG - Some commands can redact sensitive fields} { - r config set slowlog-log-slower-than 0 - r slowlog reset - r migrate [srv 0 host] [srv 0 port] key 9 5000 - r migrate [srv 0 host] [srv 0 port] key 9 5000 AUTH user - r migrate [srv 0 host] [srv 0 port] key 9 5000 AUTH2 user password - r config set slowlog-log-slower-than -1 - set slowlog_resp [r slowlog get] - - # Make sure all 3 commands were logged, but the sensitive fields are omitted - assert_equal 4 [llength $slowlog_resp] - assert_match {* key 9 5000} [lindex [lindex $slowlog_resp 2] 3] - assert_match {* key 9 5000 AUTH (redacted)} [lindex [lindex $slowlog_resp 1] 3] - assert_match {* key 9 5000 AUTH2 (redacted) (redacted)} [lindex [lindex $slowlog_resp 0] 3] - } {} {needs:repl} - - test {SLOWLOG - Rewritten commands are logged as their original command} { - r config set slowlog-log-slower-than 0 - - # Test rewriting client arguments - r sadd set a b c d e - r slowlog reset - - # SPOP is rewritten as DEL when all keys are removed - r spop set 10 - assert_equal {spop set 10} [lindex [lindex [r slowlog get] 0] 3] - - # Test replacing client arguments - r slowlog reset - - # GEOADD is replicated as ZADD - r geoadd cool-cities -122.33207 47.60621 Seattle - assert_equal {geoadd cool-cities -122.33207 47.60621 Seattle} [lindex [lindex [r slowlog get] 0] 3] - - # Test replacing a single command argument - r set A 5 - r slowlog reset - - # GETSET is replicated as SET - r getset a 5 - assert_equal {getset a 5} [lindex [lindex [r slowlog get] 0] 3] - - # INCRBYFLOAT calls rewrite multiple times, so it's a special case - r set A 0 - r slowlog reset - - # INCRBYFLOAT is replicated as SET - r INCRBYFLOAT A 1.0 - assert_equal {INCRBYFLOAT A 1.0} [lindex [lindex [r slowlog get] 0] 3] - - # blocked BLPOP is replicated as LPOP - set rd [redis_deferring_client] - $rd blpop l 0 - wait_for_blocked_clients_count 1 50 100 - r multi - r lpush l foo - r slowlog reset - r exec - $rd read - $rd close - assert_equal {blpop l 0} [lindex [lindex [r slowlog get] 0] 3] - } - - test {SLOWLOG - commands with too many arguments are trimmed} { - r config set slowlog-log-slower-than 0 - r slowlog reset - r sadd set 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 - set e [lindex [r slowlog get] end-1] - lindex $e 3 - } {sadd set 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 {... (2 more arguments)}} - - test {SLOWLOG - too long arguments are trimmed} { - r config set slowlog-log-slower-than 0 - r slowlog reset - set arg [string repeat A 129] - r sadd set foo $arg - set e [lindex [r slowlog get] end-1] - lindex $e 3 - } {sadd set foo {AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... (1 more bytes)}} - - test {SLOWLOG - EXEC is not logged, just executed commands} { - r config set slowlog-log-slower-than 100000 - r slowlog reset - assert_equal [r slowlog len] 0 - r multi - r debug sleep 0.2 - r exec - assert_equal [r slowlog len] 1 - set e [lindex [r slowlog get] 0] - assert_equal [lindex $e 3] {debug sleep 0.2} - } {} {needs:debug} - - test {SLOWLOG - can clean older entries} { - r client setname lastentry_client - r config set slowlog-max-len 1 - r debug sleep 0.2 - assert {[llength [r slowlog get]] == 1} - set e [lindex [r slowlog get] 0] - assert_equal {lastentry_client} [lindex $e 5] - } {} {needs:debug} - - test {SLOWLOG - can be disabled} { - r config set slowlog-max-len 1 - r config set slowlog-log-slower-than 1 - r slowlog reset - r debug sleep 0.2 - assert_equal [r slowlog len] 1 - r config set slowlog-log-slower-than -1 - r slowlog reset - r debug sleep 0.2 - assert_equal [r slowlog len] 0 - } {} {needs:debug} - - test {SLOWLOG - count must be >= -1} { - assert_error "ERR count should be greater than or equal to -1" {r slowlog get -2} - assert_error "ERR count should be greater than or equal to -1" {r slowlog get -222} - } - - test {SLOWLOG - get all slow logs} { - r config set slowlog-log-slower-than 0 - r config set slowlog-max-len 3 - r slowlog reset - - r set key test - r sadd set a b c - r incr num - r lpush list a - - assert_equal [r slowlog len] 3 - assert_equal 0 [llength [r slowlog get 0]] - assert_equal 1 [llength [r slowlog get 1]] - assert_equal 3 [llength [r slowlog get -1]] - assert_equal 3 [llength [r slowlog get 3]] - } - - test {SLOWLOG - blocking command is reported only after unblocked} { - # Cleanup first - r del mylist - # create a test client - set rd [redis_deferring_client] - - # config the slowlog and reset - r config set slowlog-log-slower-than 0 - r config set slowlog-max-len 110 - r slowlog reset - - $rd BLPOP mylist 0 - wait_for_blocked_clients_count 1 50 20 - assert_equal 0 [llength [regexp -all -inline (?=BLPOP) [r slowlog get]]] - - r LPUSH mylist 1 - wait_for_blocked_clients_count 0 50 20 - assert_equal 1 [llength [regexp -all -inline (?=BLPOP) [r slowlog get]]] - - $rd close - } -} diff --git a/examples/redis-unstable/tests/unit/sort.tcl b/examples/redis-unstable/tests/unit/sort.tcl deleted file mode 100644 index 35ec160..0000000 --- a/examples/redis-unstable/tests/unit/sort.tcl +++ /dev/null @@ -1,417 +0,0 @@ -start_server { - tags {"sort"} - overrides { - "list-max-ziplist-size" 16 - "set-max-intset-entries" 32 - } -} { - proc create_random_dataset {num cmd} { - set tosort {} - set result {} - array set seenrand {} - r del tosort - for {set i 0} {$i < $num} {incr i} { - # Make sure all the weights are different because - # Redis does not use a stable sort but Tcl does. - while 1 { - randpath { - set rint [expr int(rand()*1000000)] - } { - set rint [expr rand()] - } - if {![info exists seenrand($rint)]} break - } - set seenrand($rint) x - r $cmd tosort $i - r set weight_$i $rint - r hset wobj_$i weight $rint - lappend tosort [list $i $rint] - } - set sorted [lsort -index 1 -real $tosort] - for {set i 0} {$i < $num} {incr i} { - lappend result [lindex $sorted $i 0] - } - set _ $result - } - - proc check_sort_store_encoding {key} { - set listpack_max_size [lindex [r config get list-max-ziplist-size] 1] - - # When the length or size of quicklist is less than the limit, - # it will be converted to listpack. - if {[r llen $key] <= $listpack_max_size} { - assert_encoding listpack $key - } else { - assert_encoding quicklist $key - } - } - - foreach {num cmd enc title} { - 16 lpush listpack "Listpack" - 1000 lpush quicklist "Quicklist" - 10000 lpush quicklist "Big Quicklist" - 16 sadd intset "Intset" - 1000 sadd hashtable "Hash table" - 10000 sadd hashtable "Big Hash table" - } { - set result [create_random_dataset $num $cmd] - assert_encoding $enc tosort - - test "$title: SORT BY key" { - assert_equal $result [r sort tosort BY weight_*] - } {} {cluster:skip} - - test "$title: SORT BY key with limit" { - assert_equal [lrange $result 5 9] [r sort tosort BY weight_* LIMIT 5 5] - } {} {cluster:skip} - - test "$title: SORT BY hash field" { - assert_equal $result [r sort tosort BY wobj_*->weight] - } {} {cluster:skip} - } - - set result [create_random_dataset 16 lpush] - test "SORT GET #" { - assert_equal [lsort -integer $result] [r sort tosort GET #] - } - -foreach command {SORT SORT_RO} { - test "$command GET " { - r del foo - set res [r $command tosort GET foo] - assert_equal 16 [llength $res] - foreach item $res { assert_equal {} $item } - } {} {cluster:skip} -} - - test "SORT GET (key and hash) with sanity check" { - set l1 [r sort tosort GET # GET weight_*] - set l2 [r sort tosort GET # GET wobj_*->weight] - foreach {id1 w1} $l1 {id2 w2} $l2 { - assert_equal $id1 $id2 - assert_equal $w1 [r get weight_$id1] - assert_equal $w2 [r get weight_$id1] - } - } {} {cluster:skip} - - test "SORT BY key STORE" { - r sort tosort BY weight_* store sort-res - assert_equal $result [r lrange sort-res 0 -1] - assert_equal 16 [r llen sort-res] - check_sort_store_encoding sort-res - } {} {cluster:skip} - - test "SORT BY hash field STORE" { - r sort tosort BY wobj_*->weight store sort-res - assert_equal $result [r lrange sort-res 0 -1] - assert_equal 16 [r llen sort-res] - check_sort_store_encoding sort-res - } {} {cluster:skip} - - test "SORT extracts STORE correctly" { - r command getkeys sort abc store def - } {abc def} - - test "SORT_RO get keys" { - r command getkeys sort_ro abc - } {abc} - - test "SORT extracts multiple STORE correctly" { - r command getkeys sort abc store invalid store stillbad store def - } {abc def} - - test "SORT DESC" { - assert_equal [lsort -decreasing -integer $result] [r sort tosort DESC] - } - - test "SORT ALPHA against integer encoded strings" { - r del mylist - r lpush mylist 2 - r lpush mylist 1 - r lpush mylist 3 - r lpush mylist 10 - r sort mylist alpha - } {1 10 2 3} - - test "SORT sorted set" { - r del zset - r zadd zset 1 a - r zadd zset 5 b - r zadd zset 2 c - r zadd zset 10 d - r zadd zset 3 e - r sort zset alpha desc - } {e d c b a} - - test "SORT sorted set BY nosort should retain ordering" { - r del zset - r zadd zset 1 a - r zadd zset 5 b - r zadd zset 2 c - r zadd zset 10 d - r zadd zset 3 e - r multi - r sort zset by nosort asc - r sort zset by nosort desc - r exec - } {{a c e b d} {d b e c a}} - - test "SORT sorted set BY nosort + LIMIT" { - r del zset - r zadd zset 1 a - r zadd zset 5 b - r zadd zset 2 c - r zadd zset 10 d - r zadd zset 3 e - assert_equal [r sort zset by nosort asc limit 0 1] {a} - assert_equal [r sort zset by nosort desc limit 0 1] {d} - assert_equal [r sort zset by nosort asc limit 0 2] {a c} - assert_equal [r sort zset by nosort desc limit 0 2] {d b} - assert_equal [r sort zset by nosort limit 5 10] {} - assert_equal [r sort zset by nosort limit -10 100] {a c e b d} - } - - test "SORT sorted set BY nosort works as expected from scripts" { - r del zset - r zadd zset 1 a - r zadd zset 5 b - r zadd zset 2 c - r zadd zset 10 d - r zadd zset 3 e - r eval { - return {redis.call('sort',KEYS[1],'by','nosort','asc'), - redis.call('sort',KEYS[1],'by','nosort','desc')} - } 1 zset - } {{a c e b d} {d b e c a}} - - test "SORT sorted set: +inf and -inf handling" { - r del zset - r zadd zset -100 a - r zadd zset 200 b - r zadd zset -300 c - r zadd zset 1000000 d - r zadd zset +inf max - r zadd zset -inf min - r zrange zset 0 -1 - } {min c a b d max} - - test "SORT regression for issue #19, sorting floats" { - r flushdb - set floats {1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15} - foreach x $floats { - r lpush mylist $x - } - assert_equal [lsort -real $floats] [r sort mylist] - } - - test "SORT with STORE returns zero if result is empty (github issue 224)" { - r flushdb - r sort foo{t} store bar{t} - } {0} - - test "SORT with STORE does not create empty lists (github issue 224)" { - r flushdb - r lpush foo{t} bar - r sort foo{t} alpha limit 10 10 store zap{t} - r exists zap{t} - } {0} - - test "SORT with STORE removes key if result is empty (github issue 227)" { - r flushdb - r lpush foo{t} bar - r sort emptylist{t} store foo{t} - r exists foo{t} - } {0} - - test "SORT with BY and STORE should still order output" { - r del myset mylist - r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz - r sort myset alpha by _ store mylist - r lrange mylist 0 -1 - } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {cluster:skip} - - test "SORT will complain with numerical sorting and bad doubles (1)" { - r del myset - r sadd myset 1 2 3 4 not-a-double - set e {} - catch {r sort myset} e - set e - } {*ERR*double*} - - test "SORT will complain with numerical sorting and bad doubles (2)" { - r del myset - r sadd myset 1 2 3 4 - r mset score:1 10 score:2 20 score:3 30 score:4 not-a-double - set e {} - catch {r sort myset by score:*} e - set e - } {*ERR*double*} {cluster:skip} - - test "SORT BY sub-sorts lexicographically if score is the same" { - r del myset - r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz - foreach ele {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} { - set score:$ele 100 - } - r sort myset by score:* - } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {cluster:skip} - - test "SORT GET with pattern ending with just -> does not get hash field" { - r del mylist - r lpush mylist a - r set x:a-> 100 - r sort mylist by num get x:*-> - } {100} {cluster:skip} - - test "SORT by nosort retains native order for lists" { - r del testa - r lpush testa 2 1 4 3 5 - r sort testa by nosort - } {5 3 4 1 2} {cluster:skip} - - test "SORT by nosort plus store retains native order for lists" { - r del testa - r lpush testa 2 1 4 3 5 - r sort testa by nosort store testb - r lrange testb 0 -1 - } {5 3 4 1 2} {cluster:skip} - - test "SORT by nosort with limit returns based on original list order" { - r sort testa by nosort limit 0 3 store testb - r lrange testb 0 -1 - } {5 3 4} {cluster:skip} - - test "SORT_RO - Successful case" { - r del mylist - r lpush mylist a - r set x:a 100 - r sort_ro mylist by nosort get x:*-> - } {100} {cluster:skip} - - test "SORT_RO - Cannot run with STORE arg" { - catch {r sort_ro foolist STORE bar} e - set e - } {ERR syntax error} - - tags {"slow"} { - set num 100 - set res [create_random_dataset $num lpush] - - test "SORT speed, $num element list BY key, 100 times" { - set start [clock clicks -milliseconds] - for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort BY weight_* LIMIT 0 10] - } - set elapsed [expr [clock clicks -milliseconds]-$start] - if {$::verbose} { - puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " - flush stdout - } - } {} {cluster:skip} - - test "SORT speed, $num element list BY hash field, 100 times" { - set start [clock clicks -milliseconds] - for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort BY wobj_*->weight LIMIT 0 10] - } - set elapsed [expr [clock clicks -milliseconds]-$start] - if {$::verbose} { - puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " - flush stdout - } - } {} {cluster:skip} - - test "SORT speed, $num element list directly, 100 times" { - set start [clock clicks -milliseconds] - for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort LIMIT 0 10] - } - set elapsed [expr [clock clicks -milliseconds]-$start] - if {$::verbose} { - puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " - flush stdout - } - } - - test "SORT speed, $num element list BY , 100 times" { - set start [clock clicks -milliseconds] - for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort BY nokey LIMIT 0 10] - } - set elapsed [expr [clock clicks -milliseconds]-$start] - if {$::verbose} { - puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " - flush stdout - } - } {} {cluster:skip} - } - - test {SETRANGE with huge offset} { - r lpush L 2 1 0 - # expecting a different outcome on 32 and 64 bit systems - foreach value {9223372036854775807 2147483647} { - catch {[r sort_ro L by a limit 2 $value]} res - if {![string match "2" $res] && ![string match "*out of range*" $res]} { - assert_not_equal $res "expecting an error or 2" - } - } - } - - test {SORT STORE quicklist with the right options} { - set origin_config [config_get_set list-max-listpack-size -1] - r del lst{t} lst_dst{t} - r config set list-max-listpack-size -1 - r config set list-compress-depth 12 - r lpush lst{t} {*}[split [string repeat "1" 6000] ""] - r sort lst{t} store lst_dst{t} - assert_encoding quicklist lst_dst{t} - assert_match "*ql_listpack_max:-1 ql_compressed:1*" [r debug object lst_dst{t}] - config_set list-max-listpack-size $origin_config - } {} {needs:debug} -} - -start_cluster 1 0 {tags {"external:skip cluster sort"}} { - - r flushall - r lpush "{a}mylist" 1 2 3 - r set "{a}by1" 20 - r set "{a}by2" 30 - r set "{a}by3" 0 - r set "{a}get1" 200 - r set "{a}get2" 100 - r set "{a}get3" 30 - - test "sort by in cluster mode" { - catch {r sort "{a}mylist" by by*} e - assert_match {ERR BY option of SORT denied in Cluster mode when *} $e - r sort "{a}mylist" by "{a}by*" - } {3 1 2} - - test "sort get in cluster mode" { - catch {r sort "{a}mylist" by "{a}by*" get get*} e - assert_match {ERR GET option of SORT denied in Cluster mode when *} $e - r sort "{a}mylist" by "{a}by*" get "{a}get*" - } {30 200 100} - - test "sort get # in cluster mode" { - assert_equal [r sort "{a}mylist" by "{a}by*" get # ] {3 1 2} - r sort "{a}mylist" by "{a}by*" get "{a}get*" get # - } {30 3 200 1 100 2} - - test "sort_ro by in cluster mode" { - catch {r sort_ro "{a}mylist" by by*} e - assert_match {ERR BY option of SORT denied in Cluster mode when *} $e - r sort_ro "{a}mylist" by "{a}by*" - } {3 1 2} - - test "sort_ro get in cluster mode" { - catch {r sort_ro "{a}mylist" by "{a}by*" get get*} e - assert_match {ERR GET option of SORT denied in Cluster mode when *} $e - r sort_ro "{a}mylist" by "{a}by*" get "{a}get*" - } {30 200 100} - - test "sort_ro get # in cluster mode" { - assert_equal [r sort_ro "{a}mylist" by "{a}by*" get # ] {3 1 2} - r sort_ro "{a}mylist" by "{a}by*" get "{a}get*" get # - } {30 3 200 1 100 2} -} diff --git a/examples/redis-unstable/tests/unit/tls.tcl b/examples/redis-unstable/tests/unit/tls.tcl deleted file mode 100644 index ab57bbe..0000000 --- a/examples/redis-unstable/tests/unit/tls.tcl +++ /dev/null @@ -1,219 +0,0 @@ -start_server {tags {"tls"}} { - if {$::tls} { - package require tls - - test {TLS: Not accepting non-TLS connections on a TLS port} { - set s [redis [srv 0 host] [srv 0 port]] - catch {$s PING} e - set e - } {*I/O error*} - - test {TLS: Verify tls-auth-clients behaves as expected} { - set s [redis [srv 0 host] [srv 0 port]] - ::tls::import [$s channel] - catch {$s PING} e - assert_match {*error*} $e - - r CONFIG SET tls-auth-clients no - - set s [redis [srv 0 host] [srv 0 port]] - ::tls::import [$s channel] - catch {$s PING} e - assert_match {PONG} $e - - r CONFIG SET tls-auth-clients optional - - set s [redis [srv 0 host] [srv 0 port]] - ::tls::import [$s channel] - catch {$s PING} e - assert_match {PONG} $e - - r CONFIG SET tls-auth-clients yes - - set s [redis [srv 0 host] [srv 0 port]] - ::tls::import [$s channel] - catch {$s PING} e - assert_match {*error*} $e - } - - test {TLS: Verify tls-protocols behaves as expected} { - r CONFIG SET tls-protocols TLSv1.2 - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.2 0}] - catch {$s PING} e - assert_match {*I/O error*} $e - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.2 1}] - catch {$s PING} e - assert_match {PONG} $e - - r CONFIG SET tls-protocols "" - } - - test {TLS: Verify tls-ciphers behaves as expected} { - r CONFIG SET tls-protocols TLSv1.2 - r CONFIG SET tls-ciphers "DEFAULT:-AES128-SHA256" - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "-ALL:AES128-SHA256"}] - catch {$s PING} e - assert_match {*I/O error*} $e - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "-ALL:AES256-SHA256"}] - catch {$s PING} e - assert_match {PONG} $e - - r CONFIG SET tls-ciphers "DEFAULT" - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "-ALL:AES128-SHA256"}] - catch {$s PING} e - assert_match {PONG} $e - - r CONFIG SET tls-protocols "" - r CONFIG SET tls-ciphers "DEFAULT" - } - - test {TLS: Verify tls-prefer-server-ciphers behaves as expected} { - r CONFIG SET tls-protocols TLSv1.2 - r CONFIG SET tls-ciphers "AES128-SHA256:AES256-SHA256" - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "AES256-SHA256:AES128-SHA256"}] - catch {$s PING} e - assert_match {PONG} $e - - assert_equal "AES256-SHA256" [dict get [::tls::status [$s channel]] cipher] - - r CONFIG SET tls-prefer-server-ciphers yes - - set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "AES256-SHA256:AES128-SHA256"}] - catch {$s PING} e - assert_match {PONG} $e - - assert_equal "AES128-SHA256" [dict get [::tls::status [$s channel]] cipher] - - r CONFIG SET tls-protocols "" - r CONFIG SET tls-ciphers "DEFAULT" - } - - test {TLS: Verify tls-cert-file is also used as a client cert if none specified} { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - - # Use a non-restricted client/server cert for the replica - set redis_crt [format "%s/tests/tls/redis.crt" [pwd]] - set redis_key [format "%s/tests/tls/redis.key" [pwd]] - - start_server [list overrides [list tls-cert-file $redis_crt tls-key-file $redis_key] \ - omit [list tls-client-cert-file tls-client-key-file]] { - set replica [srv 0 client] - $replica replicaof $master_host $master_port - wait_for_condition 30 100 { - [string match {*master_link_status:up*} [$replica info replication]] - } else { - fail "Can't authenticate to master using just tls-cert-file!" - } - } - } - - test {TLS: switch between tcp and tls ports} { - set srv_port [srv 0 port] - - # TLS - set rd [redis [srv 0 host] $srv_port 0 1] - $rd PING - - # TCP - $rd CONFIG SET tls-port 0 - $rd CONFIG SET port $srv_port - $rd close - - set rd [redis [srv 0 host] $srv_port 0 0] - $rd PING - - # TLS - $rd CONFIG SET port 0 - $rd CONFIG SET tls-port $srv_port - $rd close - - set rd [redis [srv 0 host] $srv_port 0 1] - $rd PING - $rd close - } - - test {TLS: Working with an encrypted keyfile} { - # Create an encrypted version - set keyfile [lindex [r config get tls-key-file] 1] - set keyfile_encrypted "$keyfile.encrypted" - exec -ignorestderr openssl rsa -in $keyfile -out $keyfile_encrypted -aes256 -passout pass:1234 2>/dev/null - - # Using it without a password fails - catch {r config set tls-key-file $keyfile_encrypted} e - assert_match {*Unable to update TLS*} $e - - # Now use a password - r config set tls-key-file-pass 1234 - r config set tls-key-file $keyfile_encrypted - } - - test {TLS: Auto-authenticate using tls-auth-clients-user (CN)} { - # Create a user matching the CN in the client certificate (CN=Client-only) - r ACL SETUSER {Client-only} on >clientpass allcommands allkeys - - # Map the client certificate CN to the ACL user name. - r CONFIG SET tls-auth-clients-user CN - - # Connect over TLS using the test client certificate (CN=Client-only) - set s [redis [srv 0 host] [srv 0 port] 0 1] - catch {$s PING} e - assert_match {PONG} $e - assert_equal "Client-only" [$s ACL WHOAMI] - } - - foreach user_type {"non-existent" "disabled"} { - test "TLS: $user_type user cannot auto-authenticate via certificate" { - if {$user_type eq "non-existent"} { - # Ensure the Client-only user does not exist so auto-auth will fail - catch {r ACL DELUSER {Client-only}} - } else { - r ACL SETUSER {Client-only} on >clientpass allcommands allkeys - r ACL SETUSER {Client-only} off ;# Disable the user - } - r ACL LOG RESET - r CONFIG SET tls-auth-clients-user CN - - # Capture the current value of acl_access_denied_tls_cert from INFO stats - set info_before [r INFO stats] - regexp {acl_access_denied_tls_cert:(\d+)} $info_before -> before - - # Connect over TLS using the test client certificate (CN=Client-only) - # Since there is no matching ACL user or user is disabled, auto-auth should fail - # and the connection should remain authenticated as the default user - set s [redis [srv 0 host] [srv 0 port] 0 1] - assert_equal "default" [$s ACL WHOAMI] - - # The ACL LOG should contain a single entry with reason "tls-cert" - # and username "Client-only" - set log [r ACL LOG] - assert_equal 1 [llength $log] - set entry [lindex $log 0] - assert_equal "tls-cert" [dict get $entry reason] - assert_equal "Client-only" [dict get $entry username] - - # INFO stats should report that acl_access_denied_tls_cert increased by 1 - set info_after [r INFO stats] - regexp {acl_access_denied_tls_cert:(\d+)} $info_after -> after - assert {$after == $before + 1} - - # Verify fallback to password auth works after cert auth fails - r ACL SETUSER testuser on >testpass +@all ~* - $s AUTH testuser testpass - assert_equal "testuser" [$s ACL WHOAMI] - assert_equal "PONG" [$s PING] - - # Clean up - r ACL DELUSER testuser - catch {r ACL DELUSER {Client-only}} - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/tracking.tcl b/examples/redis-unstable/tests/unit/tracking.tcl deleted file mode 100644 index 666b593..0000000 --- a/examples/redis-unstable/tests/unit/tracking.tcl +++ /dev/null @@ -1,902 +0,0 @@ -# logreqres:skip because it seems many of these tests rely heavily on RESP2 -start_server {tags {"tracking network logreqres:skip"}} { - # Create a deferred client we'll use to redirect invalidation - # messages to. - set rd_redirection [redis_deferring_client] - $rd_redirection client id - set redir_id [$rd_redirection read] - $rd_redirection subscribe __redis__:invalidate - $rd_redirection read ; # Consume the SUBSCRIBE reply. - - # Create another client that's not used as a redirection client - # We should always keep this client's buffer clean - set rd [redis_deferring_client] - - # Client to be used for SET and GET commands - # We don't read this client's buffer - set rd_sg [redis_client] - - proc clean_all {} { - uplevel { - # We should make r TRACKING off first. If r is in RESP3, - # r FLUSH ALL will send us tracking-redir-broken or other - # info which will not be consumed. - r CLIENT TRACKING off - $rd QUIT - $rd_redirection QUIT - set rd [redis_deferring_client] - set rd_redirection [redis_deferring_client] - $rd_redirection client id - set redir_id [$rd_redirection read] - $rd_redirection subscribe __redis__:invalidate - $rd_redirection read ; # Consume the SUBSCRIBE reply. - r FLUSHALL - r HELLO 2 - r config set tracking-table-max-keys 1000000 - } - } - - test {Clients are able to enable tracking and redirect it} { - r CLIENT TRACKING on REDIRECT $redir_id - } {*OK} - - test {The other connection is able to get invalidations} { - r SET a{t} 1 - r SET b{t} 1 - r GET a{t} - r INCR b{t} ; # This key should not be notified, since it wasn't fetched. - r INCR a{t} - set keys [lindex [$rd_redirection read] 2] - assert {[llength $keys] == 1} - assert {[lindex $keys 0] eq {a{t}}} - } - - test {The client is now able to disable tracking} { - # Make sure to add a few more keys in the tracking list - # so that we can check for leaks, as a side effect. - r MGET a{t} b{t} c{t} d{t} e{t} f{t} g{t} - r CLIENT TRACKING off - } {*OK} - - test {Clients can enable the BCAST mode with the empty prefix} { - r CLIENT TRACKING on BCAST REDIRECT $redir_id - } {*OK*} - - test {The connection gets invalidation messages about all the keys} { - r MSET a{t} 1 b{t} 2 c{t} 3 - set keys [lsort [lindex [$rd_redirection read] 2]] - assert {$keys eq {a{t} b{t} c{t}}} - } - - test {Clients can enable the BCAST mode with prefixes} { - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST REDIRECT $redir_id PREFIX a: PREFIX b: - r MULTI - r INCR a:1{t} - r INCR a:2{t} - r INCR b:1{t} - r INCR b:2{t} - # we should not get this key - r INCR c:1{t} - r EXEC - # Because of the internals, we know we are going to receive - # two separated notifications for the two different prefixes. - set keys1 [lsort [lindex [$rd_redirection read] 2]] - set keys2 [lsort [lindex [$rd_redirection read] 2]] - set keys [lsort [list {*}$keys1 {*}$keys2]] - assert {$keys eq {a:1{t} a:2{t} b:1{t} b:2{t}}} - } - - test {Adding prefixes to BCAST mode works} { - r CLIENT TRACKING on BCAST REDIRECT $redir_id PREFIX c: - r INCR c:1234 - set keys [lsort [lindex [$rd_redirection read] 2]] - assert {$keys eq {c:1234}} - } - - test {Tracking NOLOOP mode in standard mode works} { - r CLIENT TRACKING off - r CLIENT TRACKING on REDIRECT $redir_id NOLOOP - r MGET otherkey1{t} loopkey{t} otherkey2{t} - $rd_sg SET otherkey1{t} 1; # We should get this - r SET loopkey{t} 1 ; # We should not get this - $rd_sg SET otherkey2{t} 1; # We should get this - # Because of the internals, we know we are going to receive - # two separated notifications for the two different keys. - set keys1 [lsort [lindex [$rd_redirection read] 2]] - set keys2 [lsort [lindex [$rd_redirection read] 2]] - set keys [lsort [list {*}$keys1 {*}$keys2]] - assert {$keys eq {otherkey1{t} otherkey2{t}}} - } - - test {Tracking NOLOOP mode in BCAST mode works} { - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP - $rd_sg SET otherkey1 1; # We should get this - r SET loopkey 1 ; # We should not get this - $rd_sg SET otherkey2 1; # We should get this - # Because $rd_sg send command synchronously, we know we are - # going to receive two separated notifications. - set keys1 [lsort [lindex [$rd_redirection read] 2]] - set keys2 [lsort [lindex [$rd_redirection read] 2]] - set keys [lsort [list {*}$keys1 {*}$keys2]] - assert {$keys eq {otherkey1 otherkey2}} - } - - test {Tracking gets notification of expired keys} { - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP - r SET mykey myval px 1 - r SET mykeyotherkey myval ; # We should not get it - after 1000 - set keys [lsort [lindex [$rd_redirection read] 2]] - assert {$keys eq {mykey}} - } - - test {Tracking gets notification of lazy expired keys} { - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP - # Use multi-exec to expose a race where the key gets an two invalidations - # in the same event loop, once by the client so filtered by NOLOOP, and - # the second one by the lazy expire - r MULTI - r SET mykey{t} myval px 1 - r SET mykeyotherkey{t} myval ; # We should not get it - r DEBUG SLEEP 0.1 - r GET mykey{t} - r EXEC - set keys [lsort [lindex [$rd_redirection read] 2]] - assert {$keys eq {mykey{t}}} - } {} {needs:debug} - - test {HELLO 3 reply is correct} { - set reply [r HELLO 3] - assert_equal [dict get $reply proto] 3 - } - - test {HELLO without protover} { - set reply [r HELLO 3] - assert_equal [dict get $reply proto] 3 - - set reply [r HELLO] - assert_equal [dict get $reply proto] 3 - - set reply [r HELLO 2] - assert_equal [dict get $reply proto] 2 - - set reply [r HELLO] - assert_equal [dict get $reply proto] 2 - - # restore RESP3 for next test - r HELLO 3 - } - - test {RESP3 based basic invalidation} { - r CLIENT TRACKING off - r CLIENT TRACKING on - $rd_sg SET key1 1 - r GET key1 - $rd_sg SET key1 2 - r read - } {invalidate key1} - - test {RESP3 tracking redirection} { - r CLIENT TRACKING off - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_sg SET key1 2 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key1}} - } - - test {Invalidations of previous keys can be redirected after switching to RESP3} { - r HELLO 2 - $rd_sg SET key1 1 - r GET key1 - r HELLO 3 - $rd_sg SET key1 2 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key1}} - } - - test {Invalidations of new keys can be redirected after switching to RESP3} { - r HELLO 3 - $rd_sg SET key1 1 - r GET key1 - $rd_sg SET key1 2 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key1}} - } - - test {Invalid keys should not be tracked for scripts in NOLOOP mode} { - $rd_sg CLIENT TRACKING off - $rd_sg CLIENT TRACKING on NOLOOP - $rd_sg HELLO 3 - $rd_sg SET key1 1 - assert_equal "1" [$rd_sg GET key1] - - # For write command in script, invalid key should not be tracked with NOLOOP flag - $rd_sg eval "return redis.call('set', 'key1', '2')" 1 key1 - assert_equal "2" [$rd_sg GET key1] - $rd_sg CLIENT TRACKING off - } - - test {Tracking only occurs for scripts when a command calls a read-only command} { - r CLIENT TRACKING off - r CLIENT TRACKING on - $rd_sg MSET key2{t} 1 key2{t} 1 - - # If a script doesn't call any read command, don't track any keys - r EVAL "redis.call('set', 'key3{t}', 'bar')" 2 key1{t} key2{t} - $rd_sg MSET key2{t} 2 key1{t} 2 - assert_equal "PONG" [r ping] - - # If a script calls a read command, just the read keys - r EVAL "redis.call('get', 'key2{t}')" 2 key1{t} key2{t} - $rd_sg MSET key2{t} 2 key3{t} 2 - assert_equal {invalidate key2{t}} [r read] - assert_equal "PONG" [r ping] - - # RO variants work like the normal variants - - # If a RO script doesn't call any read command, don't track any keys - r EVAL_RO "redis.call('ping')" 2 key1{t} key2{t} - $rd_sg MSET key2{t} 2 key1{t} 2 - assert_equal "PONG" [r ping] - - # If a RO script calls a read command, just the read keys - r EVAL_RO "redis.call('get', 'key2{t}')" 2 key1{t} key2{t} - $rd_sg MSET key2{t} 2 key3{t} 2 - assert_equal {invalidate key2{t}} [r read] - assert_equal "PONG" [r ping] - } - - test {RESP3 Client gets tracking-redir-broken push message after cached key changed when rediretion client is terminated} { - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_redirection QUIT - assert_equal OK [$rd_redirection read] - $rd_sg SET key1 2 - set MAX_TRIES 100 - set res -1 - for {set i 0} {$i <= $MAX_TRIES && $res < 0} {incr i} { - set res [lsearch -exact [r PING] "tracking-redir-broken"] - } - assert {$res >= 0} - # Consume PING reply - assert_equal PONG [r read] - - # Reinstantiating after QUIT - set rd_redirection [redis_deferring_client] - $rd_redirection CLIENT ID - set redir_id [$rd_redirection read] - $rd_redirection SUBSCRIBE __redis__:invalidate - $rd_redirection read ; # Consume the SUBSCRIBE reply - } - - test {Different clients can redirect to the same connection} { - r CLIENT TRACKING on REDIRECT $redir_id - $rd CLIENT TRACKING on REDIRECT $redir_id - assert_equal OK [$rd read] ; # Consume the TRACKING reply - $rd_sg MSET key1{t} 1 key2{t} 1 - r GET key1{t} - $rd GET key2{t} - assert_equal 1 [$rd read] ; # Consume the GET reply - $rd_sg INCR key1{t} - $rd_sg INCR key2{t} - set res1 [lindex [$rd_redirection read] 2] - set res2 [lindex [$rd_redirection read] 2] - assert {$res1 eq {key1{t}}} - assert {$res2 eq {key2{t}}} - } - - test {Different clients using different protocols can track the same key} { - $rd HELLO 3 - set reply [$rd read] ; # Consume the HELLO reply - assert_equal 3 [dict get $reply proto] - $rd CLIENT TRACKING on - assert_equal OK [$rd read] ; # Consume the TRACKING reply - $rd_sg set key1 1 - r GET key1 - $rd GET key1 - assert_equal 1 [$rd read] ; # Consume the GET reply - $rd_sg INCR key1 - set res1 [lindex [$rd_redirection read] 2] - $rd PING ; # Non redirecting client has to talk to the server in order to get invalidation message - set res2 [lindex [split [$rd read] " "] 1] - assert_equal PONG [$rd read] ; # Consume the PING reply, which comes together with the invalidation message - assert {$res1 eq {key1}} - assert {$res2 eq {key1}} - } - - test {No invalidation message when using OPTIN option} { - r CLIENT TRACKING on OPTIN REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 ; # This key should not be notified, since OPTIN is on and CLIENT CACHING yes wasn't called - $rd_sg SET key1 2 - # Preparing some message to consume on $rd_redirection so we don't get blocked - r CLIENT TRACKING off - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key2 1 - r GET key2 ; # This key should be notified - $rd_sg SET key2 2 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key2}} - } - - test {Invalidation message sent when using OPTIN option with CLIENT CACHING yes} { - r CLIENT TRACKING on OPTIN REDIRECT $redir_id - $rd_sg SET key1 3 - r CLIENT CACHING yes - r GET key1 - $rd_sg SET key1 4 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key1}} - } - - test {Invalidation message sent when using OPTOUT option} { - r CLIENT TRACKING off - r CLIENT TRACKING on OPTOUT REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_sg SET key1 2 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key1}} - } - - test {No invalidation message when using OPTOUT option with CLIENT CACHING no} { - $rd_sg SET key1 1 - r CLIENT CACHING no - r GET key1 ; # This key should not be notified, since OPTOUT is on and CLIENT CACHING no was called - $rd_sg SET key1 2 - # Preparing some message to consume on $rd_redirection so we don't get blocked - $rd_sg SET key2 1 - r GET key2 ; # This key should be notified - $rd_sg SET key2 2 - set res [lindex [$rd_redirection read] 2] - assert {$res eq {key2}} - } - - test {Able to redirect to a RESP3 client} { - $rd_redirection UNSUBSCRIBE __redis__:invalidate ; # Need to unsub first before we can do HELLO 3 - set res [$rd_redirection read] ; # Consume the UNSUBSCRIBE reply - assert_equal {__redis__:invalidate} [lindex $res 1] - $rd_redirection HELLO 3 - set res [$rd_redirection read] ; # Consume the HELLO reply - assert_equal [dict get $reply proto] 3 - $rd_redirection SUBSCRIBE __redis__:invalidate - set res [$rd_redirection read] ; # Consume the SUBSCRIBE reply - assert_equal {__redis__:invalidate} [lindex $res 1] - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_sg INCR key1 - set res [lindex [$rd_redirection read] 1] - assert {$res eq {key1}} - $rd_redirection HELLO 2 - set res [$rd_redirection read] ; # Consume the HELLO reply - assert_equal [dict get $res proto] 2 - } - - test {After switching from normal tracking to BCAST mode, no invalidation message is produced for pre-BCAST keys} { - r CLIENT TRACKING off - r HELLO 3 - r CLIENT TRACKING on - $rd_sg SET key1 1 - r GET key1 - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST - $rd_sg INCR key1 - set inv_msg [r PING] - set ping_reply [r read] - assert {$inv_msg eq {invalidate key1}} - assert {$ping_reply eq {PONG}} - } - - test {BCAST with prefix collisions throw errors} { - set r [redis_client] - catch {$r CLIENT TRACKING ON BCAST PREFIX FOOBAR PREFIX FOO} output - assert_match {ERR Prefix 'FOOBAR'*'FOO'*} $output - - catch {$r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX FOOBAR} output - assert_match {ERR Prefix 'FOO'*'FOOBAR'*} $output - - $r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX BAR - catch {$r CLIENT TRACKING ON BCAST PREFIX FO} output - assert_match {ERR Prefix 'FO'*'FOO'*} $output - - catch {$r CLIENT TRACKING ON BCAST PREFIX BARB} output - assert_match {ERR Prefix 'BARB'*'BAR'*} $output - - $r CLIENT TRACKING OFF - } - - test {hdel deliver invalidate message after response in the same connection} { - r CLIENT TRACKING off - r HELLO 3 - r CLIENT TRACKING on - r HSET myhash f 1 - r HGET myhash f - set res [r HDEL myhash f] - assert_equal $res 1 - set res [r read] - assert_equal $res {invalidate myhash} - } - - test {Tracking invalidation message is not interleaved with multiple keys response} { - r CLIENT TRACKING off - r HELLO 3 - r CLIENT TRACKING on - # We need disable active expire, so we can trigger lazy expire - r DEBUG SET-ACTIVE-EXPIRE 0 - r MULTI - r MSET x{t} 1 y{t} 2 - r PEXPIRE y{t} 100 - r GET y{t} - r EXEC - after 110 - # Read expired key y{t}, generate invalidate message about this key - set res [r MGET x{t} y{t}] - assert_equal $res {1 {}} - # Consume the invalidate message which is after command response - set res [r read] - assert_equal $res {invalidate y{t}} - r DEBUG SET-ACTIVE-EXPIRE 1 - } {OK} {needs:debug} - - test {Tracking invalidation message is not interleaved with transaction response} { - r CLIENT TRACKING off - r HELLO 3 - r CLIENT TRACKING on - r MSET a{t} 1 b{t} 2 - r GET a{t} - # Start a transaction, make a{t} generate an invalidate message - r MULTI - r INCR a{t} - r GET b{t} - set res [r EXEC] - assert_equal $res {2 2} - set res [r read] - # Consume the invalidate message which is after command response - assert_equal $res {invalidate a{t}} - } - - test {Tracking invalidation message of eviction keys should be before response} { - # Get the current memory limit and calculate a new limit. - r CLIENT TRACKING off - r HELLO 3 - r CLIENT TRACKING on - - # make the previous test is really done before sampling used_memory - wait_lazyfree_done r - - set used [expr {[s used_memory] - [s mem_not_counted_for_evict]}] - set limit [expr {$used+100*1024}] - set old_policy [lindex [r config get maxmemory-policy] 1] - r config set maxmemory $limit - # We set policy volatile-random, so only keys with ttl will be evicted - r config set maxmemory-policy volatile-random - # Add a volatile key and tracking it. - r setex volatile-key 10000 x - r get volatile-key - # We use SETBIT here, so we can set a big key and get the used_memory - # bigger than maxmemory. Next command will evict volatile keys. We - # can't use SET, as SET uses big input buffer, so it will fail. - r setbit big-key 1600000 0 ;# this will consume 200kb - # volatile-key is evicted before response. - set res [r getbit big-key 0] - assert_equal $res {invalidate volatile-key} - set res [r read] - assert_equal $res 0 - r config set maxmemory-policy $old_policy - r config set maxmemory 0 - } - - test {Unblocked BLMOVE gets notification after response} { - r RPUSH list2{t} a - $rd HELLO 3 - $rd read - $rd CLIENT TRACKING on - $rd read - # Tracking key list2{t} - $rd LRANGE list2{t} 0 -1 - $rd read - # We block on list1{t} - $rd BLMOVE list1{t} list2{t} left left 0 - wait_for_blocked_clients_count 1 - # unblock $rd, list2{t} gets element and generate invalidation message - r rpush list1{t} foo - assert_equal [$rd read] {foo} - assert_equal [$rd read] {invalidate list2{t}} - } - - test {Tracking gets notification on tracking table key eviction} { - r CLIENT TRACKING off - r CLIENT TRACKING on REDIRECT $redir_id NOLOOP - r MSET key1{t} 1 key2{t} 2 - # Let the server track the two keys for us - r MGET key1{t} key2{t} - # Force the eviction of all the keys but one: - r config set tracking-table-max-keys 1 - # Note that we may have other keys in the table for this client, - # since we disabled/enabled tracking multiple time with the same - # ID, and tracking does not do ID cleanups for performance reasons. - # So we check that eventually we'll receive one or the other key, - # otherwise the test will die for timeout. - while 1 { - set keys [lindex [$rd_redirection read] 2] - if {$keys eq {key1{t}} || $keys eq {key2{t}}} break - } - # We should receive an expire notification for one of - # the two keys (only one must remain) - assert {$keys eq {key1{t}} || $keys eq {key2{t}}} - } - - test {Invalidation message received for flushall} { - clean_all - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_sg FLUSHALL - set msg [$rd_redirection read] - assert {[lindex msg 2] eq {} } - } - - test {Invalidation message received for flushdb} { - clean_all - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_sg FLUSHDB - set msg [$rd_redirection read] - assert {[lindex msg 2] eq {} } - } - - test {Test ASYNC flushall} { - clean_all - r CLIENT TRACKING on REDIRECT $redir_id - r GET key1 - r GET key2 - assert_equal [s 0 tracking_total_keys] 2 - $rd_sg FLUSHALL ASYNC - assert_equal [s 0 tracking_total_keys] 0 - assert_equal [lindex [$rd_redirection read] 2] {} - } - - test {flushdb tracking invalidation message is not interleaved with transaction response} { - clean_all - r HELLO 3 - r CLIENT TRACKING on - r SET a{t} 1 - r GET a{t} - r MULTI - r FLUSHDB - set res [r EXEC] - assert_equal $res {OK} - # Consume the invalidate message which is after command response - r read - } {invalidate {}} - - # Keys are defined to be evicted 100 at a time by default. - # If after eviction the number of keys still surpasses the limit - # defined in tracking-table-max-keys, we increases eviction - # effort to 200, and then 300, etc. - # This test tests this effort incrementation. - test {Server is able to evacuate enough keys when num of keys surpasses limit by more than defined initial effort} { - clean_all - set NUM_OF_KEYS_TO_TEST 250 - set TRACKING_TABLE_MAX_KEYS 1 - r CLIENT TRACKING on REDIRECT $redir_id - for {set i 0} {$i < $NUM_OF_KEYS_TO_TEST} {incr i} { - $rd_sg SET key$i $i - r GET key$i - } - r config set tracking-table-max-keys $TRACKING_TABLE_MAX_KEYS - # If not enough keys are evicted, we won't get enough invalidation - # messages, and "$rd_redirection read" will block. - # If too many keys are evicted, we will get too many invalidation - # messages, and the assert will fail. - for {set i 0} {$i < $NUM_OF_KEYS_TO_TEST - $TRACKING_TABLE_MAX_KEYS} {incr i} { - $rd_redirection read - } - $rd_redirection PING - assert {[$rd_redirection read] eq {pong {}}} - } - - test {Tracking info is correct} { - clean_all - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - $rd_sg SET key2 2 - r GET key1 - r GET key2 - $rd CLIENT TRACKING on BCAST PREFIX prefix: - assert [string match *OK* [$rd read]] - $rd_sg SET prefix:key1 1 - $rd_sg SET prefix:key2 2 - set info [r info] - regexp "\r\ntracking_total_items:(.*?)\r\n" $info _ total_items - regexp "\r\ntracking_total_keys:(.*?)\r\n" $info _ total_keys - regexp "\r\ntracking_total_prefixes:(.*?)\r\n" $info _ total_prefixes - regexp "\r\ntracking_clients:(.*?)\r\n" $info _ tracking_clients - assert {$total_items == 2} - assert {$total_keys == 2} - assert {$total_prefixes == 1} - assert {$tracking_clients == 2} - } - - test {CLIENT GETREDIR provides correct client id} { - set res [r CLIENT GETREDIR] - assert_equal $redir_id $res - r CLIENT TRACKING off - set res [r CLIENT GETREDIR] - assert_equal -1 $res - r CLIENT TRACKING on - set res [r CLIENT GETREDIR] - assert_equal 0 $res - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking off} { - r CLIENT TRACKING off - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {off} $flags - set redirect [dict get $res redirect] - assert_equal {-1} $redirect - set prefixes [dict get $res prefixes] - assert_equal {} $prefixes - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking on} { - r CLIENT TRACKING on - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on} $flags - set redirect [dict get $res redirect] - assert_equal {0} $redirect - set prefixes [dict get $res prefixes] - assert_equal {} $prefixes - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking on with options} { - r CLIENT TRACKING on REDIRECT $redir_id noloop - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on noloop} $flags - set redirect [dict get $res redirect] - assert_equal $redir_id $redirect - set prefixes [dict get $res prefixes] - assert_equal {} $prefixes - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking optin} { - r CLIENT TRACKING off - r CLIENT TRACKING on optin - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on optin} $flags - set redirect [dict get $res redirect] - assert_equal {0} $redirect - set prefixes [dict get $res prefixes] - assert_equal {} $prefixes - - r CLIENT CACHING yes - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on optin caching-yes} $flags - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking optout} { - r CLIENT TRACKING off - r CLIENT TRACKING on optout - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on optout} $flags - set redirect [dict get $res redirect] - assert_equal {0} $redirect - set prefixes [dict get $res prefixes] - assert_equal {} $prefixes - - r CLIENT CACHING no - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on optout caching-no} $flags - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking bcast mode} { - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST PREFIX foo PREFIX bar - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on bcast} $flags - set redirect [dict get $res redirect] - assert_equal {0} $redirect - set prefixes [lsort [dict get $res prefixes]] - assert_equal {bar foo} $prefixes - - r CLIENT TRACKING off - r CLIENT TRACKING on BCAST - set res [r client trackinginfo] - set prefixes [dict get $res prefixes] - assert_equal {{}} $prefixes - } - - test {CLIENT TRACKINGINFO provides reasonable results when tracking redir broken} { - clean_all - r HELLO 3 - r CLIENT TRACKING on REDIRECT $redir_id - $rd_sg SET key1 1 - r GET key1 - $rd_redirection QUIT - assert_equal OK [$rd_redirection read] - $rd_sg SET key1 2 - set res [lsearch -exact [r read] "tracking-redir-broken"] - assert {$res >= 0} - set res [r client trackinginfo] - set flags [dict get $res flags] - assert_equal {on broken_redirect} $flags - set redirect [dict get $res redirect] - assert_equal $redir_id $redirect - set prefixes [dict get $res prefixes] - assert_equal {} $prefixes - } - - test {Regression test for #11715} { - # This issue manifests when a client invalidates keys through the max key - # limit, which invalidates keys to get Redis below the limit, but no command is - # then executed. This can occur in several ways but the simplest is through - # multi-exec which queues commands. - clean_all - r config set tracking-table-max-keys 2 - - # The cron will invalidate keys if we're above the limit, so disable it. - r debug pause-cron 1 - - # Set up a client that has listened to 2 keys and start a multi, this - # sets up the crash for later. - $rd HELLO 3 - $rd read - $rd CLIENT TRACKING on - assert_match "OK" [$rd read] - $rd mget "1{tag}" "2{tag}" - assert_match "{} {}" [$rd read] - $rd multi - assert_match "OK" [$rd read] - - # Reduce the tracking table keys to 1, this doesn't immediately take affect, but - # instead will apply on the next command. - r config set tracking-table-max-keys 1 - - # This command will get queued, so make sure this command doesn't crash. - $rd ping - $rd exec - - # Validate we got some invalidation message and then the command was queued. - assert_match "invalidate *{tag}" [$rd read] - assert_match "QUEUED" [$rd read] - assert_match "PONG" [$rd read] - - r debug pause-cron 0 - } {OK} {needs:debug} - - foreach resp {3 2} { - test "RESP$resp based basic invalidation with client reply off" { - # This entire test is mostly irrelevant for RESP2, but we run it anyway just for some extra coverage. - clean_all - - $rd hello $resp - $rd read - $rd client tracking on - $rd read - - $rd_sg set foo bar - $rd get foo - $rd read - - $rd client reply off - - $rd_sg set foo bar2 - - if {$resp == 3} { - assert_equal {invalidate foo} [$rd read] - } elseif {$resp == 2} { } ;# Just coverage - - # Verify things didn't get messed up and no unexpected reply was pushed to the client. - $rd client reply on - assert_equal {OK} [$rd read] - $rd ping - assert_equal {PONG} [$rd read] - } - } - - test {RESP3 based basic redirect invalidation with client reply off} { - clean_all - - set rd_redir [redis_deferring_client] - $rd_redir hello 3 - $rd_redir read - - $rd_redir client id - set rd_redir_id [$rd_redir read] - - $rd client tracking on redirect $rd_redir_id - $rd read - - $rd_sg set foo bar - $rd get foo - $rd read - - $rd_redir client reply off - - $rd_sg set foo bar2 - assert_equal {invalidate foo} [$rd_redir read] - - # Verify things didn't get messed up and no unexpected reply was pushed to the client. - $rd_redir client reply on - assert_equal {OK} [$rd_redir read] - $rd_redir ping - assert_equal {PONG} [$rd_redir read] - - $rd_redir close - } - - test {RESP3 based basic tracking-redir-broken with client reply off} { - clean_all - - $rd hello 3 - $rd read - $rd client tracking on redirect $redir_id - $rd read - - $rd_sg set foo bar - $rd get foo - $rd read - - $rd client reply off - - $rd_redirection quit - $rd_redirection read - - $rd_sg set foo bar2 - - set res [lsearch -exact [$rd read] "tracking-redir-broken"] - assert_morethan_equal $res 0 - - # Verify things didn't get messed up and no unexpected reply was pushed to the client. - $rd client reply on - assert_equal {OK} [$rd read] - $rd ping - assert_equal {PONG} [$rd read] - } - - $rd_redirection close - $rd_sg close - $rd close -} - -# Just some extra coverage for --log-req-res, because we do not -# run the full tracking unit in that mode -start_server {tags {"tracking network"}} { - test {Coverage: Basic CLIENT CACHING} { - set rd_redirection [redis_deferring_client] - $rd_redirection client id - set redir_id [$rd_redirection read] - assert_equal {OK} [r CLIENT TRACKING on OPTIN REDIRECT $redir_id] - assert_equal {OK} [r CLIENT CACHING yes] - r CLIENT TRACKING off - } {OK} - - test {Coverage: Basic CLIENT REPLY} { - r CLIENT REPLY on - } {OK} - - test {Coverage: Basic CLIENT TRACKINGINFO} { - r CLIENT TRACKINGINFO - } {flags off redirect -1 prefixes {}} - - test {Coverage: Basic CLIENT GETREDIR} { - r CLIENT GETREDIR - } {-1} -} diff --git a/examples/redis-unstable/tests/unit/type/hash-field-expire.tcl b/examples/redis-unstable/tests/unit/type/hash-field-expire.tcl deleted file mode 100644 index bacc5c6..0000000 --- a/examples/redis-unstable/tests/unit/type/hash-field-expire.tcl +++ /dev/null @@ -1,2508 +0,0 @@ -######## HEXPIRE family commands -# Field does not exists -set E_NO_FIELD -2 -# Specified NX | XX | GT | LT condition not met -set E_FAIL 0 -# expiration time set/updated -set E_OK 1 -# Field deleted because the specified expiration time is in the past -set E_DELETED 2 - -######## HTTL family commands -set T_NO_FIELD -2 -set T_NO_EXPIRY -1 - -######## HPERIST -set P_NO_FIELD -2 -set P_NO_EXPIRY -1 -set P_OK 1 - -############################### AUX FUNCS ###################################### - -proc get_stat_subexpiry {r} { - set input_string [r info keyspace] - set hash_count 0 - - foreach line [split $input_string \n] { - if {[regexp {subexpiry=(\d+)} $line -> value]} { - return $value - } - } - - return 0 -} - -proc get_keys {l} { - set res {} - foreach entry $l { - set key [lindex $entry 0] - lappend res $key - } - return $res -} - -proc dumpAllHashes {client} { - set keyAndFields(0,0) 0 - unset keyAndFields - # keep keys sorted for comparison - foreach key [lsort [$client keys *]] { - set fields [$client hgetall $key] - foreach f $fields { - set keyAndFields($key,$f) [$client hpexpiretime $key FIELDS 1 $f] - } - } - return [array get keyAndFields] -} - -############################### TESTS ######################################### - -start_server {tags {"external:skip needs:debug"}} { - foreach type {listpackex hashtable} { - if {$type eq "hashtable"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Returns array if the key does not exist" { - r del myhash - assert_equal [r HEXPIRE myhash 1000 FIELDS 1 a] [list $E_NO_FIELD] - assert_equal [r HEXPIREAT myhash 1000 FIELDS 1 a] [list $E_NO_FIELD] - assert_equal [r HPEXPIRE myhash 1000 FIELDS 2 a b] [list $E_NO_FIELD $E_NO_FIELD] - assert_equal [r HPEXPIREAT myhash 1000 FIELDS 2 a b] [list $E_NO_FIELD $E_NO_FIELD] - } - - test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Verify that the expire time does not overflow" { - r del myhash - r hset myhash f1 v1 - # The expire time can't be negative. - assert_error {ERR invalid expire time, must be >= 0} {r HEXPIRE myhash -1 FIELDS 1 f1} - assert_error {ERR invalid expire time, must be >= 0} {r HEXPIRE myhash -9223372036854775808 FIELDS 1 f1} - # The expire time can't be greater than the EB_EXPIRE_TIME_MAX - assert_error {ERR invalid expire time in 'hexpire' command} {r HEXPIRE myhash [expr (1<<48) / 1000] FIELDS 1 f1} - assert_error {ERR invalid expire time in 'hexpireat' command} {r HEXPIREAT myhash [expr (1<<48) / 1000 + [clock seconds] + 100] FIELDS 1 f1} - assert_error {ERR invalid expire time in 'hpexpire' command} {r HPEXPIRE myhash [expr (1<<48)] FIELDS 1 f1} - assert_error {ERR invalid expire time in 'hpexpireat' command} {r HPEXPIREAT myhash [expr (1<<48) + [clock milliseconds] + 100] FIELDS 1 f1} - } - - test "HPEXPIRE(AT) - Test 'NX' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hpexpire myhash 1000 NX FIELDS 1 field1] [list $E_OK] - assert_equal [r hpexpire myhash 1000 NX FIELDS 2 field1 field2] [list $E_FAIL $E_OK] - - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1000)*1000}] NX FIELDS 1 field1] [list $E_OK] - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1000)*1000}] NX FIELDS 2 field1 field2] [list $E_FAIL $E_OK] - } - - test "HPEXPIRE(AT) - Test 'XX' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hpexpire myhash 1000 NX FIELDS 2 field1 field2] [list $E_OK $E_OK] - assert_equal [r hpexpire myhash 1000 XX FIELDS 2 field1 field3] [list $E_OK $E_FAIL] - - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1000)*1000}] NX FIELDS 2 field1 field2] [list $E_OK $E_OK] - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1000)*1000}] XX FIELDS 2 field1 field3] [list $E_OK $E_FAIL] - } - - test "HPEXPIRE(AT) - Test 'GT' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 - assert_equal [r hpexpire myhash 1000 NX FIELDS 1 field1] [list $E_OK] - assert_equal [r hpexpire myhash 2000 NX FIELDS 1 field2] [list $E_OK] - assert_equal [r hpexpire myhash 1500 GT FIELDS 2 field1 field2] [list $E_OK $E_FAIL] - - r del myhash - r hset myhash field1 value1 field2 value2 - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1000)*1000}] NX FIELDS 1 field1] [list $E_OK] - assert_equal [r hpexpireat myhash [expr {([clock seconds]+2000)*1000}] NX FIELDS 1 field2] [list $E_OK] - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1500)*1000}] GT FIELDS 2 field1 field2] [list $E_OK $E_FAIL] - } - - test "HPEXPIRE(AT) - Test 'LT' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hpexpire myhash 1000 NX FIELDS 1 field1] [list $E_OK] - assert_equal [r hpexpire myhash 2000 NX FIELDS 1 field2] [list $E_OK] - assert_equal [r hpexpire myhash 1500 LT FIELDS 3 field1 field2 field3] [list $E_FAIL $E_OK $E_OK] - - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1000)*1000}] NX FIELDS 1 field1] [list $E_OK] - assert_equal [r hpexpireat myhash [expr {([clock seconds]+2000)*1000}] NX FIELDS 1 field2] [list $E_OK] - assert_equal [r hpexpireat myhash [expr {([clock seconds]+1500)*1000}] LT FIELDS 3 field1 field2 field3] [list $E_FAIL $E_OK $E_OK] - } - - test "HPEXPIREAT - field not exists or TTL is in the past ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f4 v4 - r hexpire myhash 1000 NX FIELDS 1 f4 - assert_equal [r hpexpireat myhash [expr {([clock seconds]-1)*1000}] NX FIELDS 4 f1 f2 f3 f4] "$E_DELETED $E_DELETED $E_NO_FIELD $E_FAIL" - assert_equal [r hexists myhash field1] 0 - } - - test "HPEXPIRE - wrong number of arguments ($type)" { - r del myhash - r hset myhash f1 v1 - assert_error {*Parameter `numFields` should be greater than 0} {r hpexpire myhash 1000 NX FIELDS 0 f1 f2 f3} - # not match with actual number of fields - assert_error {*wrong number of arguments*} {r hpexpire myhash 1000 NX FIELDS 4 f1 f2 f3} - assert_error {*unknown argument*} {r hpexpire myhash 1000 NX FIELDS 2 f1 f2 f3} - } - - test "HPEXPIRE - parameter expire-time near limit of 2^46 ($type)" { - r del myhash - r hset myhash f1 v1 - # below & above - assert_equal [r hpexpire myhash [expr (1<<46) - [clock milliseconds] - 1000 ] FIELDS 1 f1] [list $E_OK] - assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<46) - [clock milliseconds] + 100 ] FIELDS 1 f1} - } - - test "Lazy Expire - fields are lazy deleted ($type)" { - r debug set-active-expire 0 - r del myhash - - r hset myhash f1 v1 f2 v2 f3 v3 - r hpexpire myhash 1 NX FIELDS 3 f1 f2 f3 - after 5 - - # Verify that still exists even if all fields are expired - assert_equal 1 [r EXISTS myhash] - - # Verify that len counts also expired fields - assert_equal 3 [r HLEN myhash] - - # Trying access to expired field should delete it. Len should be updated - assert_equal 0 [r hexists myhash f1] - assert_equal 2 [r HLEN myhash] - - # Trying access another expired field should delete it. Len should be updated - assert_equal "" [r hget myhash f2] - assert_equal 1 [r HLEN myhash] - - # Trying access last expired field should delete it. hash shouldn't exists afterward. - assert_equal 0 [r hstrlen myhash f3] - assert_equal 0 [r HLEN myhash] - assert_equal 0 [r EXISTS myhash] - - # Restore default - r debug set-active-expire 1 - } - - test "Active Expire - deletes hash that all its fields got expired ($type)" { - r flushall - - set hash_sizes {1 15 16 17 31 32 33 40} - foreach h $hash_sizes { - for {set i 1} {$i <= $h} {incr i} { - # Random expiration time (Take care expired not after "mix$h") - r hset hrand$h f$i v$i - r hpexpire hrand$h [expr {70 + int(rand() * 30)}] FIELDS 1 f$i - assert_equal 1 [r HEXISTS hrand$h f$i] - - # Same expiration time (Take care expired not after "mix$h") - r hset same$h f$i v$i - r hpexpire same$h 100 FIELDS 1 f$i - assert_equal 1 [r HEXISTS same$h f$i] - - # same expiration time - r hset mix$h f$i v$i fieldWithoutExpire$i v$i - r hpexpire mix$h 100 FIELDS 1 f$i - assert_equal 1 [r HEXISTS mix$h f$i] - } - } - - # Wait for active expire - wait_for_condition 50 20 { [r EXISTS same40] == 0 } else { fail "hash `same40` should be expired" } - - # Verify that all fields got expired and keys got deleted - foreach h $hash_sizes { - wait_for_condition 50 20 { - [r HLEN mix$h] == $h - } else { - fail "volatile fields of hash `mix$h` should be expired" - } - - for {set i 1} {$i <= $h} {incr i} { - assert_equal 0 [r HEXISTS mix$h f$i] - } - assert_equal 0 [r EXISTS hrand$h] - assert_equal 0 [r EXISTS same$h] - } - } - - test "HPEXPIRE - Flushall deletes all pending expired fields ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 - r hpexpire myhash 10000 NX FIELDS 1 field1 - r hpexpire myhash 10000 NX FIELDS 1 field2 - r flushall - r del myhash - r hset myhash field1 value1 field2 value2 - r hpexpire myhash 10000 NX FIELDS 1 field1 - r hpexpire myhash 10000 NX FIELDS 1 field2 - r flushall async - } - - test "HTTL/HPTTL - Returns array if the key does not exist" { - r del myhash - assert_equal [r HTTL myhash FIELDS 1 a] [list $T_NO_FIELD] - assert_equal [r HPTTL myhash FIELDS 2 a b] [list $T_NO_FIELD $T_NO_FIELD] - } - - test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" { - r del myhash - r HSET myhash field1 value1 field2 value2 - r HPEXPIRE myhash 1000 NX FIELDS 1 field1 - - foreach cmd {HTTL HPTTL} { - assert_equal [r $cmd myhash FIELDS 2 field2 non_exists_field] "$T_NO_EXPIRY $T_NO_FIELD" - # not match with actual number of fields - assert_error {*numfields* parameter must match the number of arguments*} {r $cmd myhash FIELDS 1 non_exists_field1 non_exists_field2} - assert_error {*numfields* parameter must match the number of arguments*} {r $cmd myhash FIELDS 3 non_exists_field1 non_exists_field2} - } - } - - test "HTTL/HPTTL - returns time to live in seconds/msillisec ($type)" { - r del myhash - r HSET myhash field1 value1 field2 value2 - r HPEXPIRE myhash 2000 NX FIELDS 2 field1 field2 - set ttlArray [r HTTL myhash FIELDS 2 field1 field2] - assert_range [lindex $ttlArray 0] 1 2 - set ttl [r HPTTL myhash FIELDS 1 field1] - assert_range $ttl 1000 2000 - } - - test "HEXPIRETIME/HPEXPIRETIME - Returns array if the key does not exist" { - r del myhash - assert_equal [r HEXPIRETIME myhash FIELDS 1 a] [list $T_NO_FIELD] - assert_equal [r HPEXPIRETIME myhash FIELDS 2 a b] [list $T_NO_FIELD $T_NO_FIELD] - } - - test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" { - r del myhash - r HSET myhash field1 value1 - set lo [expr {[clock seconds] + 1}] - set hi [expr {[clock seconds] + 2}] - r HPEXPIRE myhash 1000 NX FIELDS 1 field1 - assert_range [r HEXPIRETIME myhash FIELDS 1 field1] $lo $hi - assert_range [r HPEXPIRETIME myhash FIELDS 1 field1] [expr $lo*1000] [expr $hi*1000] - } - - test "HPEXPIRETIME persists after RDB reload ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 - r hpexpire myhash 150 NX FIELDS 1 field1 - set before [r HPEXPIRETIME myhash FIELDS 1 field1] - r debug reload - set after [r HPEXPIRETIME myhash FIELDS 1 field1] - assert_equal $before $after - # field2 should not have expiration - assert_equal [r HTTL myhash FIELDS 1 field2] $T_NO_EXPIRY - assert_equal [get_stat_subexpiry r] 1 - # Wait for field1 to expire robustly - wait_for_condition 50 20 { [get_stat_subexpiry r] == 0 } else { fail "subexpiry should be 0" } - assert_equal [r hget myhash field1] "" - # field2 remains without expiration - assert_equal [r HTTL myhash FIELDS 1 field2] $T_NO_EXPIRY - } - - # For hash data type that had in the past HFEs, Verify that after RDB - # reload it still won't be counted in `subexpiry`. - test "Verify hash that had HFEs won't be counted in INFO keyspace also after reload ($type)" { - # Prepare a hash with one field that will expire before the RDB is written - r flushall - r hset myhash field1 value1 field2 value2 - r hpexpire myhash 1 NX FIELDS 1 field1 - wait_for_condition 50 20 { [get_stat_subexpiry r] == 0 } else { fail "`field1` should be expired" } - - # Disable active expire to prevent the probability of the key from being - # added-and-deleted from `subexpiry` just before verifying get_stat_subexpiry() - r debug set-active-expire 0 - - r debug reload - - # Now verify no sub-expiry keys exist after reload (i.e. not registered in estore) - assert_equal [get_stat_subexpiry r] 0 - - # Restore to support active expire - r debug set-active-expire 1 - } - - # Test case where PERSIST was used, and active expire didn't do any cleanup yet - test "Verify hash with PERSIST'd field won't be counted in INFO keyspace after reload ($type)" { - r debug set-active-expire 0 - r del myhash - r hset myhash f1 v1 f2 v2 - r hexpire myhash 10000 FIELDS 1 f1 - - # Verify subexpiry is 1 (field has expiration) - assert_equal [get_stat_subexpiry r] 1 - - # Persist the field (remove expiration) - assert_equal [r hpersist myhash FIELDS 1 f1] $P_OK - - # subexpiry should still be 1 because active expire hasn't cleaned up yet. - # We avoid paying the cost of updating subexpiry data structure (estore) - # and leave the cleanup to efficient active expire - assert_equal [get_stat_subexpiry r] 1 - - # After RDB reload, subexpiry should be 0 (field no longer has expiration - # and RESTORE should "accurately" identify that and avoid registering it - # in estore) - r debug reload - assert_equal [get_stat_subexpiry r] 0 - - # Verify both fields exist and have no expiration - assert_equal [r hget myhash f1] "v1" - assert_equal [r hget myhash f2] "v2" - assert_equal [r httl myhash FIELDS 2 f1 f2] "$T_NO_EXPIRY $T_NO_EXPIRY" - - # Restore to support active expire - r debug set-active-expire 1 - } - - test "HTTL/HPTTL - Verify TTL progress until expiration ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 - r hpexpire myhash 1000 NX FIELDS 1 field1 - assert_range [r HPTTL myhash FIELDS 1 field1] 100 1000 - assert_range [r HTTL myhash FIELDS 1 field1] 0 1 - after 100 - assert_range [r HPTTL myhash FIELDS 1 field1] 1 901 - after 910 - assert_equal [r HPTTL myhash FIELDS 1 field1] $T_NO_FIELD - assert_equal [r HTTL myhash FIELDS 1 field1] $T_NO_FIELD - } - - test "HPEXPIRE - DEL hash with non expired fields (valgrind test) ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 - r hpexpire myhash 10000 NX FIELDS 1 field1 - r del myhash - } - - test "HEXPIREAT - Set time in the past ($type)" { - r del myhash - r hset myhash field1 value1 - assert_equal [r hexpireat myhash [expr {[clock seconds] - 1}] NX FIELDS 1 field1] $E_DELETED - assert_equal [r hexists myhash field1] 0 - } - - test "HEXPIREAT - Set time and then get TTL ($type)" { - r del myhash - r hset myhash field1 value1 - - r hexpireat myhash [expr {[clock seconds] + 2}] NX FIELDS 1 field1 - assert_range [r hpttl myhash FIELDS 1 field1] 500 2000 - assert_range [r httl myhash FIELDS 1 field1] 1 2 - - r hexpireat myhash [expr {[clock seconds] + 5}] XX FIELDS 1 field1 - assert_range [r httl myhash FIELDS 1 field1] 4 5 - } - - test "Lazy Expire - delete hash with expired fields ($type)" { - r del myhash - r debug set-active-expire 0 - r hset myhash k v - r hpexpire myhash 1 NX FIELDS 1 k - after 5 - r del myhash - r debug set-active-expire 1 - } - - test "Test HRANDFIELD deletes all expired fields ($type)" { - r debug set-active-expire 0 - r flushall - r config resetstat - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hpexpire myhash 1 FIELDS 2 f1 f2 - after 5 - assert_equal [lsort [r hrandfield myhash 5]] "f3 f4 f5" - assert_equal [s expired_subkeys] 2 - r hpexpire myhash 1 FIELDS 3 f3 f4 f5 - after 5 - assert_equal [lsort [r hrandfield myhash 5]] "" - assert_equal [r keys *] "" - - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - r hpexpire myhash 1 FIELDS 1 f1 - after 5 - set res [r hrandfield myhash] - assert {$res == "f2" || $res == "f3"} - r hpexpire myhash 1 FIELDS 1 f2 - after 5 - assert_equal [lsort [r hrandfield myhash 5]] "f3" - r hpexpire myhash 1 FIELDS 1 f3 - after 5 - assert_equal [r hrandfield myhash] "" - assert_equal [r keys *] "" - - r debug set-active-expire 1 - } - - test "Lazy Expire - HLEN does count expired fields ($type)" { - # Enforce only lazy expire - r debug set-active-expire 0 - - r del h1 h4 h18 h20 - r hset h1 k1 v1 - r hpexpire h1 1 NX FIELDS 1 k1 - - r hset h4 k1 v1 k2 v2 k3 v3 k4 v4 - r hpexpire h4 1 NX FIELDS 3 k1 k3 k4 - - # beyond 16 fields: HFE DS (ebuckets) converts from list to rax - - r hset h18 k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 k15 v15 k16 v16 k17 v17 k18 v18 - r hpexpire h18 1 NX FIELDS 18 k1 k2 k3 k4 k5 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16 k17 k18 - - r hset h20 k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 k15 v15 k16 v16 k17 v17 k18 v18 k19 v19 k20 v20 - r hpexpire h20 1 NX FIELDS 2 k1 k2 - - after 10 - - assert_equal [r hlen h1] 1 - assert_equal [r hlen h4] 4 - assert_equal [r hlen h18] 18 - assert_equal [r hlen h20] 20 - # Restore to support active expire - r debug set-active-expire 1 - } - - test "Lazy Expire - HSCAN does not report expired fields ($type)" { - # Enforce only lazy expire - r debug set-active-expire 0 - - r del h1 h20 h4 h18 h20 - r hset h1 01 01 - r hpexpire h1 1 NX FIELDS 1 01 - - r hset h4 01 01 02 02 03 03 04 04 - r hpexpire h4 1 NX FIELDS 3 01 03 04 - - # beyond 16 fields hash-field expiration DS (ebuckets) converts from list to rax - - r hset h18 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 - r hpexpire h18 1 NX FIELDS 18 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 - - r hset h20 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20 - r hpexpire h20 1 NX FIELDS 2 01 02 - - after 10 - - # Verify SCAN does not report expired fields - assert_equal [lsort -unique [lindex [r hscan h1 0 COUNT 10] 1]] "" - assert_equal [lsort -unique [lindex [r hscan h4 0 COUNT 10] 1]] "02" - assert_equal [lsort -unique [lindex [r hscan h18 0 COUNT 10] 1]] "" - assert_equal [lsort -unique [lindex [r hscan h20 0 COUNT 100] 1]] "03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20" - # Restore to support active expire - r debug set-active-expire 1 - } - - test "Test HSCAN with mostly expired fields return empty result ($type)" { - r debug set-active-expire 0 - - # Create hash with 1000 fields and 999 of them will be expired - r del myhash - for {set i 1} {$i <= 1000} {incr i} { - r hset myhash field$i value$i - if {$i > 1} { - r hpexpire myhash 1 NX FIELDS 1 field$i - } - } - after 3 - - # Verify iterative HSCAN returns either empty result or only the first field - set countEmptyResult 0 - set cur 0 - while 1 { - set res [r hscan myhash $cur] - set cur [lindex $res 0] - # if the result is not empty, it should contain only the first field - if {[llength [lindex $res 1]] > 0} { - assert_equal [lindex $res 1] "field1 value1" - } else { - incr countEmptyResult - } - if {$cur == 0} break - } - assert {$countEmptyResult > 0} - r debug set-active-expire 1 - } - - test "Lazy Expire - verify various HASH commands handling expired fields ($type)" { - # Enforce only lazy expire - r debug set-active-expire 0 - r del h1 h2 h3 h4 h5 h18 - r hset h1 01 01 - r hset h2 01 01 02 02 - r hset h3 01 01 02 02 03 03 - r hset h4 1 99 2 99 3 99 4 99 - r hset h5 1 1 2 22 3 333 4 4444 5 55555 - r hset h6 01 01 02 02 03 03 04 04 05 05 06 06 - r hset h18 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 - r hpexpire h1 1 NX FIELDS 1 01 - r hpexpire h2 1 NX FIELDS 1 01 - r hpexpire h2 1 NX FIELDS 1 02 - r hpexpire h3 1 NX FIELDS 1 01 - r hpexpire h4 1 NX FIELDS 1 2 - r hpexpire h5 1 NX FIELDS 1 3 - r hpexpire h6 1 NX FIELDS 1 05 - r hpexpire h18 1 NX FIELDS 17 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 - - after 5 - - # Verify HDEL not ignore expired field. It is too much overhead to check - # if the field is expired before deletion. - assert_equal [r HDEL h1 01] "1" - - # Verify HGET ignore expired field - r config resetstat - assert_equal [r HGET h2 01] "" - assert_equal [s expired_subkeys] 1 - assert_equal [r HGET h2 02] "" - assert_equal [s expired_subkeys] 2 - assert_equal [r HGET h3 01] "" - assert_equal [r HGET h3 02] "02" - assert_equal [r HGET h3 03] "03" - assert_equal [s expired_subkeys] 3 - # Verify HINCRBY ignore expired field - assert_equal [r HINCRBY h4 2 1] "1" - assert_equal [s expired_subkeys] 4 - assert_equal [r HINCRBY h4 3 1] "100" - # Verify HSTRLEN ignore expired field - assert_equal [r HSTRLEN h5 3] "0" - assert_equal [s expired_subkeys] 5 - assert_equal [r HSTRLEN h5 4] "4" - assert_equal [lsort [r HKEYS h6]] "01 02 03 04 06" - assert_equal [s expired_subkeys] 5 - # Verify HEXISTS ignore expired field - assert_equal [r HEXISTS h18 07] "0" - assert_equal [s expired_subkeys] 6 - assert_equal [r HEXISTS h18 18] "1" - # Verify HVALS ignore expired field - assert_equal [lsort [r HVALS h18]] "18" - assert_equal [s expired_subkeys] 6 - # Restore to support active expire - r debug set-active-expire 1 - } - - test "A field with TTL overridden with another value (TTL discarded) ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - r hpexpire myhash 10000 NX FIELDS 1 field1 - r hpexpire myhash 1 NX FIELDS 1 field2 - - # field2 TTL will be discarded - r hset myhash field2 value4 - after 5 - # Expected TTL will be discarded - assert_equal [r hget myhash field2] "value4" - assert_equal [r httl myhash FIELDS 2 field2 field3] "$T_NO_EXPIRY $T_NO_EXPIRY" - assert_not_equal [r httl myhash FIELDS 1 field1] "$T_NO_EXPIRY" - } - - test "Modify TTL of a field ($type)" { - r del myhash - r hset myhash field1 value1 - r hpexpire myhash 200000 NX FIELDS 1 field1 - r hpexpire myhash 1000000 XX FIELDS 1 field1 - after 15 - assert_equal [r hget myhash field1] "value1" - assert_range [r hpttl myhash FIELDS 1 field1] 900000 1000000 - } - - test "Test return value of set operation ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - r hexpire myhash 100000 FIELDS 1 f1 - assert_equal [r hset myhash f2 v2] 0 - assert_equal [r hset myhash f3 v3] 1 - assert_equal [r hset myhash f3 v3 f4 v4] 1 - assert_equal [r hset myhash f3 v3 f5 v5 f6 v6] 2 - } - - test "Test HGETALL not return expired fields ($type)" { - # Test with small hash - r debug set-active-expire 0 - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6 - r hpexpire myhash 1 NX FIELDS 3 f2 f4 f6 - after 10 - assert_equal [lsort [r hgetall myhash]] "f1 f3 f5 v1 v3 v5" - - # Test with large hash - r del myhash - for {set i 1} {$i <= 600} {incr i} { - r hset myhash f$i v$i - if {$i > 3} { r hpexpire myhash 1 NX FIELDS 1 f$i } - } - after 10 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 f2 f3 v1 v2 v3"] - - # hash that all fields are expired return empty result - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6 - r hpexpire myhash 1 FIELDS 6 f1 f2 f3 f4 f5 f6 - after 10 - assert_equal [r hgetall myhash] "" - r debug set-active-expire 1 - } - - test "Test RENAME hash with fields to be expired ($type)" { - r debug set-active-expire 0 - r del myhash - r hset myhash field1 value1 - r hpexpire myhash 20 NX FIELDS 1 field1 - r rename myhash myhash2 - assert_equal [r exists myhash] 0 - assert_range [r hpttl myhash2 FIELDS 1 field1] 1 20 - after 25 - # Verify the renamed key exists - assert_equal [r exists myhash2] 1 - r debug set-active-expire 1 - # Only active expire will delete the key - wait_for_condition 30 10 { [r exists myhash2] == 0 } else { fail "`myhash2` should be expired" } - } - - test "Test RENAME hash that had HFEs but not during the rename ($type)" { - r del h1 - r hset h1 f1 v1 f2 v2 - r hpexpire h1 1 FIELDS 1 f1 - after 20 - r rename h1 h1_renamed - assert_equal [r exists h1] 0 - assert_equal [r exists h1_renamed] 1 - assert_equal [r hgetall h1_renamed] {f2 v2} - r hpexpire h1_renamed 1 FIELDS 1 f2 - # Only active expire will delete the key - wait_for_condition 30 10 { [r exists h1_renamed] == 0 } else { fail "`h1_renamed` should be expired" } - } - - test "MOVE to another DB hash with fields to be expired ($type)" { - r select 9 - r flushall - r hset myhash field1 value1 - r expireat myhash 2000000000000 ;# Force kvobj reallocation during move command - r hpexpire myhash 100 NX FIELDS 1 field1 - r move myhash 10 - assert_equal [r exists myhash] 0 - assert_equal [r dbsize] 0 - - # Verify the key and its field exists in the target DB - r select 10 - assert_equal [r hget myhash field1] "value1" - assert_equal [r exists myhash] 1 - - # Eventually the field will be expired and the key will be deleted - wait_for_condition 40 10 { [r hget myhash field1] == "" } else { fail "`field1` should be expired" } - wait_for_condition 40 10 { [r exists myhash] == 0 } else { fail "db should be empty" } - } {} {singledb:skip} - - test "Test COPY hash with fields to be expired ($type)" { - r flushall - r hset h1 f1 v1 f2 v2 - r hset h2 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6 f7 v7 f8 v8 f9 v9 f10 v10 f11 v11 f12 v12 f13 v13 f14 v14 f15 v15 f16 v16 f17 v17 f18 v18 - r hpexpire h1 100 NX FIELDS 1 f1 - r hpexpire h2 100 NX FIELDS 18 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18 - r COPY h1 h1copy - r COPY h2 h2copy - assert_equal [r hget h1 f1] "v1" - assert_equal [r hget h1copy f1] "v1" - assert_equal [r exists h2] 1 - assert_equal [r exists h2copy] 1 - after 105 - - # Verify lazy expire of field in h1 and its copy - assert_equal [r hget h1 f1] "" - assert_equal [r hget h1copy f1] "" - - # Verify lazy expire of field in h2 and its copy. Verify the key deleted as well. - wait_for_condition 40 10 { [r exists h2] == 0 } else { fail "`h2` should be expired" } - wait_for_condition 40 10 { [r exists h2copy] == 0 } else { fail "`h2copy` should be expired" } - - } {} {singledb:skip} - - test "Test COPY hash that had HFEs but not during the copy ($type)" { - r del h1 - r hset h1 f1 v1 f2 v2 - r hpexpire h1 1 FIELDS 1 f1 - after 20 - r COPY h1 h1_copy - assert_equal [r exists h1] 1 - assert_equal [r exists h1_copy] 1 - assert_equal [r hgetall h1_copy] {f2 v2} - r hpexpire h1_copy 1 FIELDS 1 f2 - # Only active expire will delete the key - wait_for_condition 30 10 { [r exists h1_copy] == 0 } else { fail "`h1_copy` should be expired" } - } - - test "Test SWAPDB hash-fields to be expired ($type)" { - r select 9 - r flushall - r hset myhash field1 value1 - r hpexpire myhash 50 NX FIELDS 1 field1 - - r swapdb 9 10 - - # Verify the key and its field doesn't exist in the source DB - assert_equal [r exists myhash] 0 - assert_equal [r dbsize] 0 - - # Verify the key and its field exists in the target DB - r select 10 - assert_equal [r hget myhash field1] "value1" - assert_equal [r dbsize] 1 - - # Eventually the field will be expired and the key will be deleted - wait_for_condition 20 10 { [r exists myhash] == 0 } else { fail "'myhash' should be expired" } - } {} {singledb:skip} - - test "Test SWAPDB hash that had HFEs but not during the swap ($type)" { - r select 9 - r flushall - r hset myhash f1 v1 f2 v2 - r hpexpire myhash 1 NX FIELDS 1 f1 - after 10 - - r swapdb 9 10 - - # Verify the key and its field doesn't exist in the source DB - assert_equal [r exists myhash] 0 - assert_equal [r dbsize] 0 - - # Verify the key and its field exists in the target DB - r select 10 - assert_equal [r hgetall myhash] {f2 v2} - assert_equal [r dbsize] 1 - r hpexpire myhash 1 NX FIELDS 1 f2 - - # Eventually the field will be expired and the key will be deleted - wait_for_condition 20 10 { [r exists myhash] == 0 } else { fail "'myhash' should be expired" } - } {} {singledb:skip} - - test "HMGET - returns empty entries if fields or hash expired ($type)" { - r debug set-active-expire 0 - r del h1 h2 - r hset h1 f1 v1 f2 v2 f3 v3 - r hset h2 f1 v1 f2 v2 f3 v3 - r hpexpire h1 10000000 NX FIELDS 1 f1 - r hpexpire h1 1 NX FIELDS 2 f2 f3 - r hpexpire h2 1 NX FIELDS 3 f1 f2 f3 - after 5 - assert_equal [r hmget h1 f1 f2 f3] {v1 {} {}} - assert_equal [r hmget h2 f1 f2 f3] {{} {} {}} - r debug set-active-expire 1 - } - - test "HPERSIST - Returns array if the key does not exist ($type)" { - r del myhash - assert_equal [r HPERSIST myhash FIELDS 1 a] [list $P_NO_FIELD] - assert_equal [r HPERSIST myhash FIELDS 2 a b] [list $P_NO_FIELD $P_NO_FIELD] - } - - test "HPERSIST - input validation ($type)" { - # HPERSIST key - r del myhash - r hset myhash f1 v1 f2 v2 - r hexpire myhash 1000 NX FIELDS 1 f1 - assert_error {*wrong number of arguments*} {r hpersist myhash} - assert_error {*wrong number of arguments*} {r hpersist myhash FIELDS 1} - assert_equal [r hpersist myhash FIELDS 2 f1 not-exists-field] "$P_OK $P_NO_FIELD" - assert_equal [r hpersist myhash FIELDS 1 f2] "$P_NO_EXPIRY" - # not match with actual number of fields - assert_error {*numfields* parameter must match the number of arguments*} {r hpersist myhash FIELDS 2 f1 f2 f3} - assert_error {*numfields* parameter must match the number of arguments*} {r hpersist myhash FIELDS 4 f1 f2 f3} - } - - test "HPERSIST - verify fields with TTL are persisted ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - r hexpire myhash 20 NX FIELDS 2 f1 f2 - r hpersist myhash FIELDS 2 f1 f2 - after 25 - assert_equal [r hget myhash f1] "v1" - assert_equal [r hget myhash f2] "v2" - assert_equal [r HTTL myhash FIELDS 2 f1 f2] "$T_NO_EXPIRY $T_NO_EXPIRY" - } - - test "HTTL/HPERSIST - Test expiry commands with non-volatile hash ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r httl myhash FIELDS 1 field1] $T_NO_EXPIRY - assert_equal [r httl myhash FIELDS 1 fieldnonexist] $E_NO_FIELD - - assert_equal [r hpersist myhash FIELDS 1 field1] $P_NO_EXPIRY - assert_equal [r hpersist myhash FIELDS 1 fieldnonexist] $P_NO_FIELD - } - - test {DUMP / RESTORE are able to serialize / unserialize a hash} { - r config set sanitize-dump-payload yes - r del myhash - r hmset myhash a 1 b 2 c 3 - r hexpireat myhash 2524600800 fields 1 a - r hexpireat myhash 2524600801 fields 1 b - set encoded [r dump myhash] - r del myhash - r restore myhash 0 $encoded - assert_equal [lsort [r hgetall myhash]] "1 2 3 a b c" - assert_equal [r hexpiretime myhash FIELDS 3 a b c] {2524600800 2524600801 -1} - } - - test {RESTORE hash that had in the past HFEs but not during the dump} { - r config set sanitize-dump-payload yes - r del myhash - r hmset myhash a 1 b 2 c 3 - r hpexpire myhash 1 fields 1 a - after 10 - set encoded [r dump myhash] - r del myhash - r restore myhash 0 $encoded - assert_equal [lsort [r hgetall myhash]] "2 3 b c" - r hpexpire myhash 1 fields 2 b c - wait_for_condition 30 10 { [r exists myhash] == 0 } else { fail "`myhash` should be expired" } - } - - test {DUMP / RESTORE are able to serialize / unserialize a hash with TTL 0 for all fields} { - r config set sanitize-dump-payload yes - r del myhash - r hmset myhash a 1 b 2 c 3 - r hexpire myhash 9999999 fields 1 a ;# make all TTLs of fields to 0 - r hpersist myhash fields 1 a - assert_encoding $type myhash - set encoded [r dump myhash] - r del myhash - r restore myhash 0 $encoded - assert_equal [lsort [r hgetall myhash]] "1 2 3 a b c" - assert_equal [r hexpiretime myhash FIELDS 3 a b c] {-1 -1 -1} - } - - test {HINCRBY - discards pending expired field and reset its value} { - r debug set-active-expire 0 - r del h1 h2 - r hset h1 f1 10 f2 2 - r hset h2 f1 10 - assert_equal [r HINCRBY h1 f1 2] 12 - assert_equal [r HINCRBY h2 f1 2] 12 - r HPEXPIRE h1 10 FIELDS 1 f1 - r HPEXPIRE h2 10 FIELDS 1 f1 - after 15 - assert_equal [r HINCRBY h1 f1 1] 1 - assert_equal [r HINCRBY h2 f1 1] 1 - r debug set-active-expire 1 - } - - test {HINCRBY - preserve expiration time of the field} { - r del h1 - r hset h1 f1 10 - r hpexpire h1 20 FIELDS 1 f1 - assert_equal [r HINCRBY h1 f1 2] 12 - assert_range [r HPTTL h1 FIELDS 1 f1] 1 20 - } - - - test {HINCRBYFLOAT - discards pending expired field and reset its value} { - r debug set-active-expire 0 - r del h1 h2 - r hset h1 f1 10 f2 2 - r hset h2 f1 10 - assert_equal [r HINCRBYFLOAT h1 f1 2] 12 - assert_equal [r HINCRBYFLOAT h2 f1 2] 12 - r HPEXPIRE h1 10 FIELDS 1 f1 - r HPEXPIRE h2 10 FIELDS 1 f1 - after 15 - assert_equal [r HINCRBYFLOAT h1 f1 1] 1 - assert_equal [r HINCRBYFLOAT h2 f1 1] 1 - r debug set-active-expire 1 - } - - test {HINCRBYFLOAT - preserve expiration time of the field} { - r del h1 - r hset h1 f1 10 - r hpexpire h1 20 FIELDS 1 f1 - assert_equal [r HINCRBYFLOAT h1 f1 2.5] 12.5 - assert_range [r HPTTL h1 FIELDS 1 f1] 1 20 - } - - test "HGETDEL - delete field with ttl ($type)" { - r debug set-active-expire 0 - r del h1 - - # Test deleting only field in a hash. Due to lazy expiry, - # reply will be null and the field and the key will be deleted. - r hsetex h1 PX 5 FIELDS 1 f1 10 - after 15 - assert_equal [r hgetdel h1 fields 1 f1] "{}" - assert_equal [r exists h1] 0 - - # Test deleting one field among many. f2 will lazily expire - r hsetex h1 FIELDS 3 f1 10 f2 20 f3 value3 - r hpexpire h1 5 FIELDS 1 f2 - after 15 - assert_equal [r hgetdel h1 fields 2 f2 f3] "{} value3" - assert_equal [lsort [r hgetall h1]] [lsort "f1 10"] - - # Try to delete the last field, along with non-existing fields - assert_equal [r hgetdel h1 fields 4 f1 f2 f3 f4] "10 {} {} {}" - r debug set-active-expire 1 - } - - test "HGETEX - input validation ($type)" { - r del h1 - assert_error "*wrong number of arguments*" {r HGETEX} - assert_error "*wrong number of arguments*" {r HGETEX h1} - assert_error "*wrong number of arguments*" {r HGETEX h1 FIELDS} - assert_error "*wrong number of arguments*" {r HGETEX h1 FIELDS 0} - assert_error "*wrong number of arguments*" {r HGETEX h1 FIELDS 1} - assert_error "*unknown argument*" {r HGETEX h1 XFIELDX 1 a} - assert_error "*unknown argument*" {r HGETEX h1 PXAT 1 1} - assert_error "*wrong number of arguments*" {r HGETEX h1 FIELDS 2 a} - assert_error "*invalid number of fields*" {r HGETEX h1 FIELDS 0 a} - assert_error "*invalid number of fields*" {r HGETEX h1 FIELDS -1 a} - assert_error "*invalid number of fields*" {r HGETEX h1 FIELDS 9223372036854775808 a} - } - - test "HGETEX - input validation (expire time) ($type)" { - assert_error "*value is not an integer or out of range*" {r HGETEX h1 EX bla FIELDS 1 a} - assert_error "*value is not an integer or out of range*" {r HGETEX h1 EX 9223372036854775808 FIELDS 1 a} - assert_error "*value is not an integer or out of range*" {r HGETEX h1 EXAT 9223372036854775808 FIELDS 1 a} - assert_error "*invalid expire time, must be >= 0*" {r HGETEX h1 PX -1 FIELDS 1 a} - assert_error "*invalid expire time, must be >= 0*" {r HGETEX h1 PXAT -1 FIELDS 1 a} - assert_error "*invalid expire time*" {r HGETEX h1 EX -1 FIELDS 1 a} - assert_error "*invalid expire time*" {r HGETEX h1 EX [expr (1<<48)] FIELDS 1 a} - assert_error "*invalid expire time*" {r HGETEX h1 EX [expr (1<<46) - [clock seconds] + 100 ] FIELDS 1 a} - assert_error "*invalid expire time*" {r HGETEX h1 EXAT [expr (1<<46) + 100 ] FIELDS 1 a} - assert_error "*invalid expire time*" {r HGETEX h1 PX [expr (1<<46) - [clock milliseconds] + 100 ] FIELDS 1 a} - assert_error "*invalid expire time*" {r HGETEX h1 PXAT [expr (1<<46) + 100 ] FIELDS 1 a} - assert_error "*wrong number of arguments*" {r HGETEX missingkey EX 100 FIELDS} - assert_error "*wrong number of arguments*" {r EVAL "return redis.call('HGETEX', 'missingkey', 'EX', '100', 'FIELDS')" 0} - } - - test "HGETEX - get without setting ttl ($type)" { - r del h1 - r hset h1 a 1 b 2 c strval - assert_equal [r hgetex h1 fields 1 a] "1" - assert_equal [r hgetex h1 fields 2 a b] "1 2" - assert_equal [r hgetex h1 fields 3 a b c] "1 2 strval" - assert_equal [r HTTL h1 FIELDS 3 a b c] "$T_NO_EXPIRY $T_NO_EXPIRY $T_NO_EXPIRY" - } - - test "HGETEX - get and set the ttl ($type)" { - r del h1 - r hset h1 a 1 b 2 c strval - assert_equal [r hgetex h1 EX 10000 fields 1 a] "1" - assert_range [r HTTL h1 FIELDS 1 a] 9000 10000 - assert_equal [r hgetex h1 EX 10000 fields 1 c] "strval" - assert_range [r HTTL h1 FIELDS 1 c] 9000 10000 - } - - test "HGETEX - Test 'EX' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hgetex myhash EX 1000 FIELDS 1 field1] [list "value1"] - assert_range [r httl myhash FIELDS 1 field1] 1 1000 - } - - test "HGETEX - Test 'EXAT' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hgetex myhash EXAT [expr [clock seconds] + 10] FIELDS 1 field2] [list "value2"] - assert_range [r httl myhash FIELDS 1 field2] 5 10 - } - - test "HGETEX - Test 'PX' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hgetex myhash PX 1000000 FIELDS 1 field3] [list "value3"] - assert_range [r httl myhash FIELDS 1 field3] 900 1000 - } - - test "HGETEX - Test 'PXAT' flag ($type)" { - r del myhash - r hset myhash field1 value1 field2 value2 field3 value3 - assert_equal [r hgetex myhash PXAT [expr [clock milliseconds] + 10000] FIELDS 1 field3] [list "value3"] - assert_range [r httl myhash FIELDS 1 field3] 5 10 - } - - test "HGETEX - Test 'PERSIST' flag ($type)" { - r del myhash - r debug set-active-expire 0 - - r hsetex myhash PX 5000 FIELDS 3 f1 v1 f2 v2 f3 v3 - assert_not_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY" - assert_not_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY" - assert_not_equal [r httl myhash FIELDS 1 f3] "$T_NO_EXPIRY" - - # Persist f1 and verify it does not have TTL anymore - assert_equal [r hgetex myhash PERSIST FIELDS 1 f1] "v1" - assert_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY" - - # Persist rest of the fields - assert_equal [r hgetex myhash PERSIST FIELDS 2 f2 f3] "v2 v3" - assert_equal [r httl myhash FIELDS 2 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY" - - # Redo the operation. It should be noop as fields are persisted already. - assert_equal [r hgetex myhash PERSIST FIELDS 2 f2 f3] "v2 v3" - assert_equal [r httl myhash FIELDS 2 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY" - - # Final sanity, fields exist and have no attached ttl. - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2 f3 v3"] - assert_equal [r httl myhash FIELDS 3 f1 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY $T_NO_EXPIRY" - r debug set-active-expire 1 - } - - test "HGETEX - Test setting ttl in the past will delete the key ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - - # hgetex without setting ttl - assert_equal [lsort [r hgetex myhash fields 3 f1 f2 f3]] [lsort "v1 v2 v3"] - assert_equal [r httl myhash FIELDS 3 f1 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY $T_NO_EXPIRY" - - # set an expired ttl and verify the key is deleted - r hgetex myhash PXAT 1 fields 3 f1 f2 f3 - assert_equal [r exists myhash] 0 - } - - test "HGETEX - Test active expiry ($type)" { - r del myhash - r debug set-active-expire 0 - - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - assert_equal [lsort [r hgetex myhash PXAT 1 FIELDS 5 f1 f2 f3 f4 f5]] [lsort "v1 v2 v3 v4 v5"] - - r debug set-active-expire 1 - wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" } - } - - test "HGETEX - A field with TTL overridden with another value (TTL discarded) ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - r hgetex myhash PX 10000 FIELDS 1 f1 - r hgetex myhash EX 100 FIELDS 1 f2 - - # f2 ttl will be discarded - r hset myhash f2 v22 - assert_equal [r hget myhash f2] "v22" - assert_equal [r httl myhash FIELDS 2 f2 f3] "$T_NO_EXPIRY $T_NO_EXPIRY" - - # Other field is not affected (still has TTL) - assert_not_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY" - } - - test "HGETEX - Test with lazy expiry ($type)" { - r del myhash - r debug set-active-expire 0 - - r hsetex myhash PX 1 FIELDS 2 f1 v1 f2 v2 - after 5 - assert_equal [r hgetex myhash FIELDS 2 f1 f2] "{} {}" - assert_equal [r exists myhash] 0 - - r debug set-active-expire 1 - } - - - test "HSETEX - input validation ($type)" { - assert_error {*wrong number of arguments*} {r hsetex myhash} - assert_error {*wrong number of arguments*} {r hsetex myhash fields} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 1} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 2 a b} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 2 a b c} - assert_error {*unknown argument*} {r hsetex myhash fields 2 a b c d e} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 3 a b c d} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 3 a b c d e} - assert_error {*unknown argument*} {r hsetex myhash fields 3 a b c d e f g} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 3 a b} - assert_error {*unknown argument*} {r hsetex myhash fields 1 a b c} - assert_error {*unknown argument*} {r hsetex myhash nx fields 1 a b} - assert_error {*unknown argument*} {r hsetex myhash 1 fields 1 a b} - assert_error {*wrong number of arguments*} {r hsetex myhash fields 1 a} - - # Only one of FNX or FXX - assert_error {*Only one of FXX or FNX arguments *} {r hsetex myhash fxx fxx EX 100 fields 1 a b} - assert_error {*Only one of FXX or FNX arguments *} {r hsetex myhash fxx fnx EX 100 fields 1 a b} - assert_error {*Only one of FXX or FNX arguments *} {r hsetex myhash fnx fxx EX 100 fields 1 a b} - assert_error {*Only one of FXX or FNX arguments *} {r hsetex myhash fnx fnx EX 100 fields 1 a b} - assert_error {*Only one of FXX or FNX arguments *} {r hsetex myhash fxx fnx fxx EX 100 fields 1 a b} - assert_error {*Only one of FXX or FNX arguments *} {r hsetex myhash fnx fxx fnx EX 100 fields 1 a b} - - # Only one of EX, PX, EXAT, PXAT or KEEPTTL can be specified - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EX 100 PX 1000 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EX 100 EXAT 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EXAT 100 EX 1000 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EXAT 100 PX 1000 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash PX 100 EXAT 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash PX 100 PXAT 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash PXAT 100 EX 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash PXAT 100 EXAT 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EX 100 KEEPTTL fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash KEEPTTL EX 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EX 100 EX 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash EXAT 100 EXAT 100 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash PX 10 PX 10 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash PXAT 10 PXAT 10 fields 1 a b} - assert_error {*Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments*} {r hsetex myhash KEEPTTL KEEPTTL fields 1 a b} - - # missing expire time - assert_error {*not an integer or out of range*} {r hsetex myhash ex fields 1 a b} - assert_error {*not an integer or out of range*} {r hsetex myhash px fields 1 a b} - assert_error {*not an integer or out of range*} {r hsetex myhash exat fields 1 a b} - assert_error {*not an integer or out of range*} {r hsetex myhash pxat fields 1 a b} - - # expire time more than 2 ^ 48 - assert_error {*invalid expire time*} {r hsetex myhash EXAT [expr (1<<48)] 1 a b} - assert_error {*invalid expire time*} {r hsetex myhash PXAT [expr (1<<48)] 1 a b} - assert_error {*invalid expire time*} {r hsetex myhash EX [expr (1<<48) - [clock seconds] + 1000 ] 1 a b} - assert_error {*invalid expire time*} {r hsetex myhash PX [expr (1<<48) - [clock milliseconds] + 1000 ] 1 a b} - - # invalid expire time - assert_error {*invalid expire time*} {r hsetex myhash EXAT -1 1 a b} - assert_error {*not an integer or out of range*} {r hsetex myhash EXAT 9223372036854775808 1 a b} - assert_error {*not an integer or out of range*} {r hsetex myhash EXAT x 1 a b} - - # invalid numfields arg - assert_error {*invalid number of fields*} {r hsetex myhash fields x a b} - assert_error {*invalid number of fields*} {r hsetex myhash fields 9223372036854775808 a b} - assert_error {*invalid number of fields*} {r hsetex myhash fields 0 a b} - assert_error {*invalid number of fields*} {r hsetex myhash fields -1 a b} - } - - test "HSETEX - Basic test ($type)" { - r del myhash - - # set field - assert_equal [r hsetex myhash FIELDS 1 f1 v1] 1 - assert_equal [r hget myhash f1] "v1" - - # override - assert_equal [r hsetex myhash FIELDS 1 f1 v11] 1 - assert_equal [r hget myhash f1] "v11" - - # set multiple - assert_equal [r hsetex myhash FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2"] - assert_equal [r hsetex myhash FIELDS 3 f1 v111 f2 v222 f3 v333] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v111 f2 v222 f3 v333"] - } - - test "HSETEX - Test FXX flag ($type)" { - r del myhash - - # Key is empty, command fails due to FXX - assert_equal [r hsetex myhash FXX FIELDS 2 f1 v1 f2 v2] 0 - # Verify it did not leave the key empty - assert_equal [r exists myhash] 0 - - # Command fails and no change on fields - r hset myhash f1 v1 - assert_equal [r hsetex myhash FXX FIELDS 2 f1 v1 f2 v2] 0 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1"] - - # Command executed successfully - assert_equal [r hsetex myhash FXX FIELDS 1 f1 v11] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v11"] - - # Try with multiple fields - r hset myhash f2 v2 - assert_equal [r hsetex myhash FXX FIELDS 2 f1 v111 f2 v222] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v111 f2 v222"] - - # Try with expiry - assert_equal [r hsetex myhash FXX EX 100 FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2"] - assert_range [r httl myhash FIELDS 1 f1] 80 100 - assert_range [r httl myhash FIELDS 1 f2] 80 100 - - # Try with expiry, FXX arg comes after TTL - assert_equal [r hsetex myhash PX 5000 FXX FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2"] - assert_range [r hpttl myhash FIELDS 1 f1] 4500 5000 - assert_range [r hpttl myhash FIELDS 1 f2] 4500 5000 - } - - test "HSETEX - Test FXX flag with lazy expire ($type)" { - r del myhash - r debug set-active-expire 0 - - r hsetex myhash PX 10 FIELDS 1 f1 v1 - after 15 - assert_equal [r hsetex myhash FXX FIELDS 1 f1 v11] 0 - assert_equal [r exists myhash] 0 - r debug set-active-expire 1 - } - - test "HSETEX - Test FNX flag ($type)" { - r del myhash - - # Command successful on an empty key - assert_equal [r hsetex myhash FNX FIELDS 1 f1 v1] 1 - - # Command fails and no change on fields - assert_equal [r hsetex myhash FNX FIELDS 2 f1 v1 f2 v2] 0 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1"] - - # Command executed successfully - assert_equal [r hsetex myhash FNX FIELDS 2 f2 v2 f3 v3] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2 f3 v3"] - assert_equal [r hsetex myhash FXX FIELDS 3 f1 v11 f2 v22 f3 v33] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v11 f2 v22 f3 v33"] - - # Try with expiry - r del myhash - assert_equal [r hsetex myhash FNX EX 100 FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2"] - assert_range [r httl myhash FIELDS 1 f1] 80 100 - assert_range [r httl myhash FIELDS 1 f2] 80 100 - - # Try with expiry, FNX arg comes after TTL - assert_equal [r hsetex myhash PX 5000 FNX FIELDS 1 f3 v3] 1 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v1 f2 v2 f3 v3"] - assert_range [r hpttl myhash FIELDS 1 f3] 4500 5000 - } - - test "HSETEX - Test 'EX' flag ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - assert_equal [r hsetex myhash EX 1000 FIELDS 1 f3 v3 ] 1 - assert_range [r httl myhash FIELDS 1 f3] 900 1000 - } - - test "HSETEX - Test 'EXAT' flag ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - assert_equal [r hsetex myhash EXAT [expr [clock seconds] + 10] FIELDS 1 f3 v3] 1 - assert_range [r httl myhash FIELDS 1 f3] 5 10 - } - - test "HSETEX - Test 'PX' flag ($type)" { - r del myhash - assert_equal [r hsetex myhash PX 1000000 FIELDS 1 f3 v3] 1 - assert_range [r httl myhash FIELDS 1 f3] 990 1000 - } - - test "HSETEX - Test 'PXAT' flag ($type)" { - r del myhash - r hset myhash f1 v2 f2 v2 f3 v3 - assert_equal [r hsetex myhash PXAT [expr [clock milliseconds] + 10000] FIELDS 1 f2 v2] 1 - assert_range [r httl myhash FIELDS 1 f2] 5 10 - } - - test "HSETEX - Test 'KEEPTTL' flag ($type)" { - r del myhash - - r hsetex myhash FIELDS 2 f1 v1 f2 v2 - r hsetex myhash PX 20000 FIELDS 1 f2 v2 - - # f1 does not have ttl - assert_equal [r httl myhash FIELDS 1 f1] "$T_NO_EXPIRY" - - # f2 has ttl - assert_not_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY" - - # Validate KEEPTTL preserves the TTL - assert_equal [r hsetex myhash KEEPTTL FIELDS 1 f2 v22] 1 - assert_equal [r hget myhash f2] "v22" - assert_not_equal [r httl myhash FIELDS 1 f2] "$T_NO_EXPIRY" - - # Try with multiple fields. First, set fields and TTL - r hsetex myhash EX 10000 FIELDS 3 f1 v1 f2 v2 f3 v3 - - # Update fields with KEEPTTL flag - r hsetex myhash KEEPTTL FIELDS 3 f1 v111 f2 v222 f3 v333 - - # Verify values are set, ttls are untouched - assert_equal [lsort [r hgetall myhash]] [lsort "f1 v111 f2 v222 f3 v333"] - assert_range [r httl myhash FIELDS 1 f1] 9000 10000 - assert_range [r httl myhash FIELDS 1 f2] 9000 10000 - assert_range [r httl myhash FIELDS 1 f3] 9000 10000 - } - - test "HSETEX - Test no expiry flag discards TTL ($type)" { - r del myhash - - r hsetex myhash FIELDS 1 f1 v1 - r hsetex myhash PX 100000 FIELDS 1 f2 v2 - assert_range [r hpttl myhash FIELDS 1 f2] 1 100000 - - assert_equal [r hsetex myhash FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [r httl myhash FIELDS 2 f1 f2] "$T_NO_EXPIRY $T_NO_EXPIRY" - } - - test "HSETEX - Test with active expiry" { - r del myhash - r debug set-active-expire 0 - - r hsetex myhash PX 10 FIELDS 5 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r debug set-active-expire 1 - wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" } - } - - test "HSETEX - Set time in the past ($type)" { - r del myhash - - # Try on an empty key - assert_equal [r hsetex myhash EXAT [expr {[clock seconds] - 1}] FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [r hexists myhash field1] 0 - - # Try with existing fields - r hset myhash fields 2 f1 v1 f2 v2 - assert_equal [r hsetex myhash EXAT [expr {[clock seconds] - 1}] FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [r hexists myhash field1] 0 - } - - test "Hash field expire - test allow-access-expired parameter enabled" { - r debug set-active-expire 0 - r debug set-allow-access-expired 1 - r del H1 - r hset H1 f1 1 - r hpexpire H1 1 FIELDS 1 f1 - after 2 - # With allow-access-expired 1, expired fields should be accessible - assert_equal {1} [r hexists H1 f1] - # Test hget with allow-access-expired enabled - assert_equal {1} [r hget H1 f1] - # Test hscan with allow-access-expired enabled - assert_equal {1 f1} [lsort [lindex [r hscan H1 0] 1]] - # Test hgetall with allow-access-expired enabled - assert_equal {f1 1} [r hgetall H1] - # Test hlen with allow-access-expired enabled - assert_equal {1} [r hlen H1] - # Test hmget with allow-access-expired enabled - assert_equal {1} [r hmget H1 f1] - # Test hkeys and hvals with allow-access-expired enabled - assert_equal {f1} [r hkeys H1] - assert_equal {1} [r hvals H1] - # Test hincrby with allow-access-expired enabled - assert_equal {2} [r hincrby H1 f1 1] - # Test hincrbyfloat with allow-access-expired enabled - assert_equal {3.5} [r hincrbyfloat H1 f1 1.5] - # Test hrandfield with allow-access-expired enabled - assert_equal {f1} [r hrandfield H1] - assert_equal {f1 3.5} [r hrandfield H1 1 withvalues] - # Reset to default - r debug set-allow-access-expired 0 - r debug set-active-expire 1 - } - } - - test "Statistics - Hashes with HFEs ($type)" { - r config resetstat - r flushall - - # hash1: 5 fields, 3 with TTL. subexpiry incr +1 - r hset myhash1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hpexpire myhash1 150 FIELDS 3 f1 f2 f3 - assert_match [get_stat_subexpiry r] 1 - # Update hash1, f3 field with earlier TTL. subexpiry no change. - r hpexpire myhash1 100 FIELDS 1 f3 - assert_match [get_stat_subexpiry r] 1 - - # hash2: 5 fields, 3 with TTL. subexpiry incr +1 - r hset myhash2 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - assert_match [get_stat_subexpiry r] 1 - r hpexpire myhash2 100 FIELDS 3 f1 f2 f3 - assert_match [get_stat_subexpiry r] 2 - # Update hash2, f3 field with later TTL. subexpiry no change. - r hpexpire myhash2 150 FIELDS 1 f3 - assert_match [get_stat_subexpiry r] 2 - - # hash3: 2 fields, 1 with TTL. HDEL field with TTL. subexpiry decr -1 - r hset myhash3 f1 v1 f2 v2 - r hpexpire myhash3 100 FIELDS 1 f2 - assert_match [get_stat_subexpiry r] 3 - r hdel myhash3 f2 - assert_match [get_stat_subexpiry r] 2 - - # hash4: 2 fields, 1 with TTL. HGETDEL field with TTL. subexpiry decr -1 - r hset myhash4 f1 v1 f2 v2 - r hpexpire myhash4 100 FIELDS 1 f2 - assert_match [get_stat_subexpiry r] 3 - r hgetdel myhash4 FIELDS 1 f2 - assert_match [get_stat_subexpiry r] 2 - - # Expired fields of hash1 and hash2. subexpiry decr -2 - wait_for_condition 50 50 { - [get_stat_subexpiry r] == 0 - } else { - fail "Hash field expiry statistics failed" - } - } - - test "HFE commands against wrong type" { - r set wrongtype somevalue - assert_error "WRONGTYPE Operation against a key*" {r hexpire wrongtype 10 fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hexpireat wrongtype 10 fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hpexpire wrongtype 10 fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hpexpireat wrongtype 10 fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hexpiretime wrongtype fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hpexpiretime wrongtype fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r httl wrongtype fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hpttl wrongtype fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hpersist wrongtype fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hgetex wrongtype fields 1 f1} - assert_error "WRONGTYPE Operation against a key*" {r hsetex wrongtype fields 1 f1 v1} - } - - r config set hash-max-listpack-entries 512 -} - -start_server {tags {"external:skip needs:debug"}} { - - # Tests that only applies to listpack - - test "Test listpack memory usage" { - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hpexpire myhash 5 FIELDS 2 f2 f4 - - # Just to have code coverage for the new listpack encoding - r memory usage myhash - } - - test "Test listpack object encoding" { - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hpexpire myhash 5 FIELDS 2 f2 f4 - - # Just to have code coverage for the listpackex encoding - assert_equal [r object encoding myhash] "listpackex" - } - - test "Test listpack debug listpack" { - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - - # Just to have code coverage for the listpackex encoding - r debug listpack myhash - } - - test "Test listpack converts to ht and passive expiry works" { - set prev [lindex [r config get hash-max-listpack-entries] 1] - r config set hash-max-listpack-entries 10 - r debug set-active-expire 0 - - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hpexpire myhash 5 FIELDS 2 f2 f4 - - for {set i 6} {$i < 11} {incr i} { - r hset myhash f$i v$i - } - after 50 - assert_equal [lsort [r hgetall myhash]] [lsort "f1 f3 f5 f6 f7 f8 f9 f10 v1 v3 v5 v6 v7 v8 v9 v10"] - r config set hash-max-listpack-entries $prev - r debug set-active-expire 1 - } - - test "Test listpack converts to ht with allow-access-expired enabled" { - r debug set-active-expire 0 - r debug set-allow-access-expired 1 - set prev [config_get_set hash-max-listpack-entries 5] - r del myhash - - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 - r hpexpire myhash 1 FIELDS 2 f1 f2 - after 2 - - assert_equal {f1 f2 f3 f4 v1 v2 v3 v4} [lsort [r hgetall myhash]] - assert_equal {1} [r hexists myhash f1] - - for {set i 5} {$i <= 10} {incr i} { - r hset myhash f$i v$i - } - - assert_equal {hashtable} [r object encoding myhash] - assert_equal {v1} [r hget myhash f1] - assert_equal {10} [r hlen myhash] - - r config set hash-max-listpack-entries $prev - r debug set-allow-access-expired 0 - r debug set-active-expire 1 - } - - test "Test listpack converts to ht and active expiry works" { - r del myhash - r debug set-active-expire 0 - - r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hpexpire myhash 10 FIELDS 1 f1 - - for {set i 0} {$i < 2048} {incr i} { - r hset myhash f$i v$i - } - - for {set i 0} {$i < 2048} {incr i} { - r hpexpire myhash 10 FIELDS 1 f$i - } - - r debug set-active-expire 1 - wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" } - } - - test "Test listpack converts to ht and active expiry works" { - r del myhash - r debug set-active-expire 0 - - # Check expiry works after listpack converts to ht - for {set i 0} {$i < 1024} {incr i} { - r hset myhash f1_$i v1_$i f2_$i v2_$i f3_$i v3_$i f4_$i v4_$i - r hpexpire myhash 10 FIELDS 4 f1_$i f2_$i f3_$i f4_$i - } - - assert_encoding hashtable myhash - assert_equal [r hlen myhash] 4096 - - r debug set-active-expire 1 - wait_for_condition 50 20 { [r EXISTS myhash] == 0 } else { fail "'myhash' should be expired" } - } - - test "HPERSIST/HEXPIRE - Test listpack with large values" { - r del myhash - - # Test with larger values to verify we successfully move fields in - # listpack when we are ordering according to TTL. This config change - # will make code to use temporary heap allocation when moving fields. - # See listpackExUpdateExpiry() for details. - r config set hash-max-listpack-value 2048 - - set payload1 [string repeat v3 1024] - set payload2 [string repeat v1 1024] - - # Test with single item list - r hset myhash f1 $payload1 - r hexpire myhash 2000 FIELDS 1 f1 - assert_equal [r hget myhash f1] $payload1 - r del myhash - - # Test with multiple items - r hset myhash f1 $payload2 f2 v2 f3 $payload1 f4 v4 - r hexpire myhash 100000 FIELDS 1 f3 - r hpersist myhash FIELDS 1 f3 - assert_equal [r hpersist myhash FIELDS 1 f3] $P_NO_EXPIRY - - r hpexpire myhash 10 FIELDS 1 f1 - after 20 - assert_equal [lsort [r hgetall myhash]] [lsort "f2 f3 f4 v2 $payload1 v4"] - - r config set hash-max-listpack-value 64 - } - - test {Test HEXPIRE coexists with EXPIRE} { - # Verify HEXPIRE & EXPIRE coexists. When setting EXPIRE a new kvobj might be - # created whereas the old one can be ref by hash field expiration DS. - # Take care to set hexpire before expire. Verify all combinations of - # which expired first. - # Another point to verify is that whether hexpire deletes the last field - # and in turn the key (See f2). - foreach etime {10 1000} htime {10 1000} f2 {0 1} { - r del myhash - r hset myhash f1 v1 - if {$f2} { r hset myhash f2 v2 } - r hpexpire myhash $etime FIELDS 1 f1 - r pexpire myhash $htime - after 20 - # If EXPIRE is shorter, it should delete the key. - if {$etime == 10} { - assert_equal [r httl myhash FIELDS 1 f1] $T_NO_FIELD - assert_equal [r exists myhash] 0 - } else { - if {$htime == 10} { - assert_equal [r httl myhash FIELDS 1 f1] $T_NO_FIELD - assert_range [r pttl myhash] 500 1000 - } else { - assert_range [r httl myhash FIELDS 1 f1] 1 1000 - assert_range [r pttl myhash] 500 1000 - } - } - } - } -} - -start_server {tags {"external:skip needs:debug"}} { - foreach type {listpack ht} { - if {$type eq "ht"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "Test Command propagated to replica as expected ($type)" { - start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} { - - set aof [get_last_incr_aof_path r] - r debug set-active-expire 0 ;# Prevent fields from being expired during data preparation - - # Time is in the past so it should propagate HDELs to replica - # and delete the fields - r hset h0 x1 y1 x2 y2 - r hexpireat h0 1 fields 3 x1 x2 non_exists_field - - r hset h1 f1 v1 f2 v2 - - # Next command won't be propagated to replica - # because XX condition not met or field not exists - r hexpire h1 10 XX FIELDS 3 f1 f2 non_exists_field - - r hpexpire h1 20 FIELDS 1 f1 - - # Next command will be propagate with only field 'f2' - # because NX condition not met for field 'f1' - r hpexpire h1 30 NX FIELDS 2 f1 f2 - - # Non exists field should be ignored - r hpexpire h1 30 FIELDS 1 non_exists_field - r hset h2 f1 v1 f2 v2 f3 v3 f4 v4 - r hpexpire h2 40 FIELDS 2 f1 non_exists_field - r hpexpire h2 50 FIELDS 1 f2 - r hpexpireat h2 [expr [clock seconds]*1000+100000] LT FIELDS 1 f3 - r hexpireat h2 [expr [clock seconds]+10] NX FIELDS 1 f4 - - r debug set-active-expire 1 - wait_for_condition 50 100 { - [r hlen h2] eq 2 - } else { - fail "Field f2 of hash h2 wasn't deleted" - } - - # HSETEX - r hsetex h3 FIELDS 1 f1 v1 - r hsetex h3 FXX FIELDS 1 f1 v11 - r hsetex h3 FNX FIELDS 1 f2 v22 - r hsetex h3 KEEPTTL FIELDS 1 f2 v22 - - # Next one will fail due to FNX arg and it won't be replicated - r hsetex h3 FNX FIELDS 2 f1 v1 f2 v2 - - # Commands with EX/PX/PXAT/EXAT will be replicated as PXAT - r hsetex h3 EX 10000 FIELDS 1 f1 v111 - r hsetex h3 PX 10000 FIELDS 1 f1 v111 - r hsetex h3 PXAT [expr [clock milliseconds]+100000] FIELDS 1 f1 v111 - r hsetex h3 EXAT [expr [clock seconds]+100000] FIELDS 1 f1 v111 - - # Following commands will set and then delete the fields because - # of TTL in the past. HDELs will be propagated. - r hsetex h3 PX 0 FIELDS 1 f1 v111 - r hsetex h3 PX 0 FIELDS 3 f1 v2 f2 v2 f3 v3 - - # HGETEX - r hsetex h4 FIELDS 3 f1 v1 f2 v2 f3 v3 - # No change on expiry, it won't be replicated. - r hgetex h4 FIELDS 1 f1 - - # Commands with EX/PX/PXAT/EXAT will be replicated as - # HPEXPIREAT command. - r hgetex h4 EX 10000 FIELDS 1 f1 - r hgetex h4 PX 10000 FIELDS 1 f1 - r hgetex h4 PXAT [expr [clock milliseconds]+100000] FIELDS 1 f1 - r hgetex h4 EXAT [expr [clock seconds]+100000] FIELDS 1 f1 - - # Following commands will delete the fields because of TTL in - # the past. HDELs will be propagated. - r hgetex h4 PX 0 FIELDS 1 f1 - # HDELs will be propagated for f2 and f3 as only those exist. - r hgetex h4 PX 0 FIELDS 3 f1 f2 f3 - - # HGETEX with PERSIST flag will be replicated as HPERSIST - r hsetex h4 EX 1000 FIELDS 1 f4 v4 - r hgetex h4 PERSIST FIELDS 1 f4 - - # Nothing will be replicated as f4 is persisted already. - r hgetex h4 PERSIST FIELDS 1 f4 - - # Replicated as hdel - r hgetdel h4 FIELDS 1 f4 - - # Assert that each TTL-related command are persisted with absolute timestamps in AOF - assert_aof_content $aof { - {select *} - {hset h0 x1 y1 x2 y2} - {multi} - {hdel h0 x1} - {hdel h0 x2} - {exec} - {hset h1 f1 v1 f2 v2} - {hpexpireat h1 * FIELDS 1 f1} - {hpexpireat h1 * FIELDS 1 f2} - {hset h2 f1 v1 f2 v2 f3 v3 f4 v4} - {hpexpireat h2 * FIELDS 1 f1} - {hpexpireat h2 * FIELDS 1 f2} - {hpexpireat h2 * FIELDS 1 f3} - {hpexpireat h2 * FIELDS 1 f4} - {hdel h1 f1} - {hdel h1 f2} - {hdel h2 f1} - {hdel h2 f2} - {hsetex h3 FIELDS 1 f1 v1} - {hsetex h3 FXX FIELDS 1 f1 v11} - {hsetex h3 FNX FIELDS 1 f2 v22} - {hsetex h3 KEEPTTL FIELDS 1 f2 v22} - {hsetex h3 PXAT * 1 f1 v111} - {hsetex h3 PXAT * 1 f1 v111} - {hsetex h3 PXAT * 1 f1 v111} - {hsetex h3 PXAT * 1 f1 v111} - {hdel h3 f1} - {multi} - {hdel h3 f1} - {hdel h3 f2} - {hdel h3 f3} - {exec} - {hsetex h4 FIELDS 3 f1 v1 f2 v2 f3 v3} - {hpexpireat h4 * FIELDS 1 f1} - {hpexpireat h4 * FIELDS 1 f1} - {hpexpireat h4 * FIELDS 1 f1} - {hpexpireat h4 * FIELDS 1 f1} - {hdel h4 f1} - {multi} - {hdel h4 f2} - {hdel h4 f3} - {exec} - {hsetex h4 PXAT * FIELDS 1 f4 v4} - {hpersist h4 FIELDS 1 f4} - {hdel h4 f4} - } - } - } {} {needs:debug} - - test "Lazy Expire - fields are lazy deleted and propagated to replicas ($type)" { - start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} { - r debug set-active-expire 0 - set aof [get_last_incr_aof_path r] - - r del myhash - - r hset myhash f1 v1 f2 v2 f3 v3 - r hpexpire myhash 1 NX FIELDS 3 f1 f2 f3 - after 5 - - # Verify that still exists even if all fields are expired - assert_equal 1 [r EXISTS myhash] - - # Verify that len counts also expired fields - assert_equal 3 [r HLEN myhash] - - # Trying access to expired field should delete it. Len should be updated - assert_equal 0 [r hexists myhash f1] - assert_equal 2 [r HLEN myhash] - - # Trying access another expired field should delete it. Len should be updated - assert_equal "" [r hget myhash f2] - assert_equal 1 [r HLEN myhash] - - # Trying access last expired field should delete it. hash shouldn't exists afterward. - assert_equal 0 [r hstrlen myhash f3] - assert_equal 0 [r HLEN myhash] - assert_equal 0 [r EXISTS myhash] - - wait_for_condition 50 100 { [r exists h1] == 0 } else { fail "hash h1 wasn't deleted" } - - # HDEL are propagated as expected - assert_aof_content $aof { - {select *} - {hset myhash f1 v1 f2 v2 f3 v3} - {hpexpireat myhash * NX FIELDS 3 f1 f2 f3} - {hdel myhash f1} - {hdel myhash f2} - {hdel myhash f3} - } - r debug set-active-expire 1 - } - } - - # Start a new server with empty data and AOF file. - start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} { - - # Based on test at expire.tcl: " All time-to-live(TTL) in commands are propagated as absolute ..." - test {All TTLs in commands are propagated as absolute timestamp in milliseconds in AOF} { - - set aof [get_last_incr_aof_path r] - - r hset h1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6 - r hexpireat h1 [expr [clock seconds]+100] NX FIELDS 1 f1 - r hpexpireat h1 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2 - r hpexpire h1 100000 NX FIELDS 3 f3 f4 f5 - r hexpire h1 100000 FIELDS 1 f6 - - r hset h2 f1 v1 f2 v2 - r hpexpire h2 1 FIELDS 2 f1 f2 - after 200 - - r hsetex h3 EX 100000 FIELDS 2 f1 v1 f2 v2 - r hsetex h3 EXAT [expr [clock seconds] + 1000] FIELDS 2 f1 v1 f2 v2 - r hsetex h3 PX 100000 FIELDS 2 f1 v1 f2 v2 - r hsetex h3 PXAT [expr [clock milliseconds]+100000] FIELDS 2 f1 v1 f2 v2 - - r hgetex h3 EX 100000 FIELDS 2 f1 f2 - r hgetex h3 EXAT [expr [clock seconds] + 1000] FIELDS 2 f1 f2 - r hgetex h3 PX 100000 FIELDS 2 f1 f2 - r hgetex h3 PXAT [expr [clock milliseconds]+100000] FIELDS 2 f1 f2 - - assert_aof_content $aof { - {select *} - {hset h1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6} - {hpexpireat h1 * FIELDS 1 f1} - {hpexpireat h1 * FIELDS 1 f2} - {hpexpireat h1 * NX FIELDS 3 f3 f4 f5} - {hpexpireat h1 * FIELDS 1 f6} - {hset h2 f1 v1 f2 v2} - {hpexpireat h2 * FIELDS 2 f1 f2} - {hdel h2 *} - {hdel h2 *} - {hsetex h3 PXAT * FIELDS 2 f1 v1 f2 v2} - {hsetex h3 PXAT * FIELDS 2 f1 v1 f2 v2} - {hsetex h3 PXAT * FIELDS 2 f1 v1 f2 v2} - {hsetex h3 PXAT * FIELDS 2 f1 v1 f2 v2} - {hpexpireat h3 * FIELDS 2 f1 f2} - {hpexpireat h3 * FIELDS 2 f1 f2} - {hpexpireat h3 * FIELDS 2 f1 f2} - {hpexpireat h3 * FIELDS 2 f1 f2} - } - - array set keyAndFields1 [dumpAllHashes r] - r debug loadaof - array set keyAndFields2 [dumpAllHashes r] - - # Assert that absolute TTLs are the same - assert_equal [array get keyAndFields1] [array get keyAndFields2] - - } {} {needs:debug} - } - - # Based on test, with same name, at expire.tcl: - test {All TTL in commands are propagated as absolute timestamp in replication stream} { - # Make sure that both relative and absolute expire commands are propagated - # Consider also comment of the test, with same name, at expire.tcl - - r flushall ; # Clean up keyspace to avoid interference by keys from other tests - set repl [attach_to_replication_stream] - - # HEXPIRE/HPEXPIRE should be translated into HPEXPIREAT - r hset h1 f1 v1 - r hexpireat h1 [expr [clock seconds]+100] NX FIELDS 1 f1 - r hset h2 f2 v2 - r hpexpireat h2 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2 - r hset h3 f3 v3 f4 v4 f5 v5 - # hpersist does nothing here. Verify it is not propagated. - r hpersist h3 FIELDS 1 f5 - r hexpire h3 100 FIELDS 3 f3 f4 non_exists_field - r hpersist h3 FIELDS 1 f3 - - assert_replication_stream $repl { - {select *} - {hset h1 f1 v1} - {hpexpireat h1 * NX FIELDS 1 f1} - {hset h2 f2 v2} - {hpexpireat h2 * NX FIELDS 1 f2} - {hset h3 f3 v3 f4 v4 f5 v5} - {hpexpireat h3 * FIELDS 2 f3 f4} - {hpersist h3 FIELDS 1 f3} - } - close_replication_stream $repl - } {} {needs:repl} - - test {HRANDFIELD delete expired fields and propagate DELs to replica} { - r debug set-active-expire 0 - r flushall - set repl [attach_to_replication_stream] - - # HRANDFIELD delete expired fields and propagate MULTI-EXEC DELs. Reply none. - r hset h1 f1 v1 f2 v2 - r hpexpire h1 1 FIELDS 2 f1 f2 - after 5 - assert_equal [r hrandfield h1 2] "" - - # HRANDFIELD delete expired field and propagate DEL. Reply non-expired field. - r hset h2 f1 v1 f2 v2 - r hpexpire h2 1 FIELDS 1 f1 - after 5 - assert_equal [r hrandfield h2 2] "f2" - - # HRANDFIELD delete expired field and propagate DEL. Reply none. - r hset h3 f1 v1 - r hpexpire h3 1 FIELDS 1 f1 - after 5 - assert_equal [r hrandfield h3 2] "" - - assert_replication_stream $repl { - {select *} - {hset h1 f1 v1 f2 v2} - {hpexpireat h1 * FIELDS 2 f1 f2} - {multi} - {hdel h1 *} - {hdel h1 *} - {exec} - {hset h2 f1 v1 f2 v2} - {hpexpireat h2 * FIELDS 1 f1} - {hdel h2 f1} - {hset h3 f1 v1} - {hpexpireat h3 * FIELDS 1 f1} - {hdel h3 f1} - } - close_replication_stream $repl - r debug set-active-expire 1 - } {OK} {needs:repl} - - # Start another server to test replication of TTLs - start_server {tags {needs:repl external:skip}} { - # Set the outer layer server as primary - set primary [srv -1 client] - set primary_host [srv -1 host] - set primary_port [srv -1 port] - # Set this inner layer server as replica - set replica [srv 0 client] - - # Server should have role slave - $replica replicaof $primary_host $primary_port - wait_for_condition 50 100 { - [s 0 role] eq {slave} - } else { - fail "Replication not started." - } - - # Based on test, with same name, at expire.tcl - test {For all replicated TTL-related commands, absolute expire times are identical on primary and replica} { - # Apply each TTL-related command to a unique key on primary - $primary flushall - $primary hset h1 f v - $primary hexpireat h1 [expr [clock seconds]+10000] FIELDS 1 f - $primary hset h2 f v - $primary hpexpireat h2 [expr [clock milliseconds]+100000] FIELDS 1 f - $primary hset h3 f v - $primary hexpire h3 100 NX FIELDS 1 f - $primary hset h4 f v - $primary hpexpire h4 100000 NX FIELDS 1 f - $primary hset h5 f v - $primary hpexpireat h5 [expr [clock milliseconds]-100000] FIELDS 1 f - $primary hset h9 f v - - $primary hsetex h10 EX 100000 FIELDS 1 f v - $primary hsetex h11 EXAT [expr [clock seconds] + 1000] FIELDS 1 f v - $primary hsetex h12 PX 100000 FIELDS 1 f v - $primary hsetex h13 PXAT [expr [clock milliseconds]+100000] FIELDS 1 f v - $primary hsetex h14 PXAT 1 FIELDS 1 f v - - $primary hsetex h15 FIELDS 1 f v - $primary hgetex h15 EX 100000 FIELDS 1 f - $primary hsetex h16 FIELDS 1 f v - $primary hgetex h16 EXAT [expr [clock seconds] + 1000] FIELDS 1 f - $primary hsetex h17 FIELDS 1 f v - $primary hgetex h17 PX 100000 FIELDS 1 f - $primary hsetex h18 FIELDS 1 f v - $primary hgetex h18 PXAT [expr [clock milliseconds]+100000] FIELDS 1 f - $primary hsetex h19 FIELDS 1 f v - $primary hgetex h19 PXAT 1 FIELDS 1 f - - # Wait for replica to get the keys and TTLs - assert {[$primary wait 1 0] == 1} - - # Verify absolute TTLs are identical on primary and replica for all keys - # This is because TTLs are always replicated as absolute values - assert_equal [dumpAllHashes $primary] [dumpAllHashes $replica] - } - } - - test "Test HSETEX command replication" { - r flushall - set repl [attach_to_replication_stream] - - # Create a field and delete it in a single command due to timestamp - # being in the past. It will be propagated as HDEL. - r hsetex h1 PXAT 1 FIELDS 1 f1 v1 - - # Following ones will be propagated with PXAT arg - r hsetex h1 EX 100000 FIELDS 1 f1 v1 - r hsetex h1 EXAT [expr [clock seconds] + 1000] FIELDS 1 f1 v1 - r hsetex h1 PX 100000 FIELDS 1 f1 v1 - r hsetex h1 PXAT [expr [clock milliseconds]+100000] FIELDS 1 f1 v1 - - # Propagate with KEEPTTL flag - r hsetex h1 KEEPTTL FIELDS 1 f1 v1 - - # Following commands will fail and won't be propagated - r hsetex h1 FNX FIELDS 1 f1 v11 - r hsetex h1 FXX FIELDS 1 f2 v2 - - # Propagate with FNX and FXX flags - r hsetex h1 FNX FIELDS 1 f2 v2 - r hsetex h1 FXX FIELDS 1 f2 v22 - - assert_replication_stream $repl { - {select *} - {hdel h1 f1} - {hsetex h1 PXAT * FIELDS 1 f1 v1} - {hsetex h1 PXAT * FIELDS 1 f1 v1} - {hsetex h1 PXAT * FIELDS 1 f1 v1} - {hsetex h1 PXAT * FIELDS 1 f1 v1} - {hsetex h1 KEEPTTL FIELDS 1 f1 v1} - {hsetex h1 FNX FIELDS 1 f2 v2} - {hsetex h1 FXX FIELDS 1 f2 v22} - } - close_replication_stream $repl - } {} {needs:repl} - - test "Test HGETEX command replication" { - r flushall - r debug set-active-expire 0 - set repl [attach_to_replication_stream] - - # If no fields are found, command won't be replicated - r hgetex h1 EX 10000 FIELDS 1 f0 - r hgetex h1 PERSIST FIELDS 1 f0 - - # Get without setting expiry will not be replicated - r hsetex h1 FIELDS 1 f0 v0 - r hgetex h1 FIELDS 1 f0 - - # Lazy expired field will be replicated as HDEL - r hsetex h1 PX 10 FIELDS 1 f1 v1 - after 15 - r hgetex h1 EX 1000 FIELDS 1 f1 - - # If new TTL is in the past, it will be replicated as HDEL - r hsetex h1 EX 10000 FIELDS 1 f2 v2 - r hgetex h1 EXAT 1 FIELDS 1 f2 - - # A field will expire lazily and other field will be deleted due to - # TTL is being in the past. It'll be propagated as two HDEL's. - r hsetex h1 PX 10 FIELDS 1 f3 v3 - after 15 - r hsetex h1 FIELDS 1 f4 v4 - r hgetex h1 EXAT 1 FIELDS 2 f3 f4 - - # TTL update, it will be replicated as HPEXPIREAT - r hsetex h1 FIELDS 1 f5 v5 - r hgetex h1 EX 10000 FIELDS 1 f5 - - # If PERSIST flag is used, it will be replicated as HPERSIST - r hsetex h1 EX 10000 FIELDS 1 f6 v6 - r hgetex h1 PERSIST FIELDS 1 f6 - - assert_replication_stream $repl { - {select *} - {hsetex h1 FIELDS 1 f0 v0} - {hsetex h1 PXAT * FIELDS 1 f1 v1} - {hdel h1 f1} - {hsetex h1 PXAT * FIELDS 1 f2 v2} - {hdel h1 f2} - {hsetex h1 PXAT * FIELDS 1 f3 v3} - {hsetex h1 FIELDS 1 f4 v4} - {multi} - {hdel h1 f3} - {hdel h1 f4} - {exec} - {hsetex h1 FIELDS 1 f5 v5} - {hpexpireat h1 * FIELDS 1 f5} - {hsetex h1 PXAT * FIELDS 1 f6 v6} - {hpersist h1 FIELDS 1 f6} - } - close_replication_stream $repl - } {} {needs:repl} - - test "HINCRBYFLOAT command won't remove field expiration on replica ($type)" { - r flushall - set repl [attach_to_replication_stream] - - r hsetex h1 EX 100 FIELDS 1 f1 1 - r hset h1 f2 1 - r hincrbyfloat h1 f1 1.1 - r hincrbyfloat h1 f2 1.1 - - # HINCRBYFLOAT will be replicated as HSETEX with KEEPTTL flag - assert_replication_stream $repl { - {select *} - {hsetex h1 PXAT * FIELDS 1 f1 1} - {hset h1 f2 1} - {hsetex h1 KEEPTTL FIELDS 1 f1 *} - {hsetex h1 KEEPTTL FIELDS 1 f2 *} - } - close_replication_stream $repl - - start_server {tags {external:skip}} { - r -1 flushall - r slaveof [srv -1 host] [srv -1 port] - wait_for_sync r - - r -1 hsetex h1 EX 100 FIELDS 1 f1 1 - r -1 hset h1 f2 1 - wait_for_ofs_sync [srv -1 client] [srv 0 client] - assert_range [r httl h1 FIELDS 1 f1] 90 100 - assert_equal {-1} [r httl h1 FIELDS 1 f2] - - r -1 hincrbyfloat h1 f1 1.1 - r -1 hincrbyfloat h1 f2 1.1 - - # Expiration time should not be removed on replica and the value - # should be equal to the master. - wait_for_ofs_sync [srv -1 client] [srv 0 client] - assert_range [r httl h1 FIELDS 1 f1] 90 100 - assert_equal [r -1 hget h1 f1] [r hget h1 f1] - - # The field f2 should not have any expiration on replica either even - # though it was set using HSET with KEEPTTL flag. - assert_equal {-1} [r httl h1 FIELDS 1 f2] - assert_equal [r -1 hget h1 f2] [r hget h1 f2] - } - } {} {needs:repl external:skip} - } -} - -# Comprehensive tests for flexible parsing improvements and field validation fixes -start_server {tags {"hash"}} { - foreach type {listpackex hashtable} { - if {$type eq "hashtable"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "HEXPIRE FAMILY - Rigid expiration time positioning ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - - # Test 1: Traditional order - assert_equal [r HEXPIRE myhash 60 FIELDS 2 f1 f2] [list $E_OK $E_OK] - - # Test 2: Mixed order with condition flags - r del myhash - r hset myhash f1 v1 f2 v2 - assert_equal [r HEXPIRE myhash 120 NX FIELDS 2 f1 f2] [list $E_OK $E_OK] - assert_equal [r HEXPIRE myhash 180 XX FIELDS 1 f1] [list $E_OK] - - # Test 3: All condition flags with flexible ordering - r del myhash - r hset myhash f1 v1 f2 v2 - # Set initial expiry - assert_equal [r HEXPIRE myhash 100 FIELDS 1 f1] [list $E_OK] - assert_equal [r HEXPIRE myhash 200 GT FIELDS 1 f1] [list $E_OK] - assert_equal [r HEXPIRE myhash 50 LT FIELDS 1 f1] [list $E_OK] - - # Test 4: Flexible positioning should FAIL (expiration time not at position 2) - assert_error {*value is not an integer or out of range*} {r HEXPIRE myhash FIELDS 1 f1 60} - assert_error {*value is not an integer or out of range*} {r HPEXPIRE myhash FIELDS 1 f2 5000} - assert_error {*value is not an integer or out of range*} {r HEXPIRE myhash NX FIELDS 1 f1} - } - - test "HEXPIREAT/HPEXPIREAT - Flexible keyword ordering ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - - set future_sec [expr {[clock seconds] + 300}] - set future_ms [expr {[clock milliseconds] + 300000}] - - # Test rigid ordering with absolute timestamps - assert_equal [r HEXPIREAT myhash $future_sec FIELDS 1 f1] [list $E_OK] - assert_equal [r HPEXPIREAT myhash $future_ms NX FIELDS 1 f2] [list $E_OK] - assert_equal [r HPEXPIREAT myhash $future_ms XX FIELDS 1 f2] [list $E_OK] - } - - test "HSETEX - Flexible argument parsing and validation ($type)" { - r del myhash - - # Test 1: Traditional order (expiration first, FIELDS last) - assert_equal [r HSETEX myhash EX 60 FIELDS 2 f1 v1 f2 v2] 1 - set ttl [r HTTL myhash FIELDS 2 f1 f2] - assert {[lindex $ttl 0] > 0 && [lindex $ttl 0] <= 60} - assert {[lindex $ttl 1] > 0 && [lindex $ttl 1] <= 60} - - r del myhash - - # Test 2: Flexible order (FIELDS first, expiration last) - assert_equal [r HSETEX myhash FIELDS 2 f1 v1 f2 v2 EX 60] 1 - set ttl [r HTTL myhash FIELDS 2 f1 f2] - assert {[lindex $ttl 0] > 0 && [lindex $ttl 0] <= 60} - assert {[lindex $ttl 1] > 0 && [lindex $ttl 1] <= 60} - - # Test 3: With condition flags in flexible order - assert_equal [r HSETEX myhash FXX FIELDS 1 f1 v1_updated KEEPTTL] 1 - assert_equal [r HGET myhash f1] "v1_updated" - } - - test "HGETEX - Flexible argument parsing and validation ($type)" { - r del myhash - r HSET myhash f1 v1 f2 v2 f3 v3 - - # Test 1: Traditional order (expiration first, FIELDS last) - assert_equal [r HGETEX myhash EX 60 FIELDS 2 f1 f2] [list "v1" "v2"] - set ttl [r HTTL myhash FIELDS 2 f1 f2] - assert {[lindex $ttl 0] > 0 && [lindex $ttl 0] <= 60} - assert {[lindex $ttl 1] > 0 && [lindex $ttl 1] <= 60} - - r del myhash - r HSET myhash f1 v1 f2 v2 f3 v3 - - # Test 2: Flexible order (FIELDS first, expiration last) - assert_equal [r HGETEX myhash FIELDS 2 f1 f2 EX 60] [list "v1" "v2"] - set ttl [r HTTL myhash FIELDS 2 f1 f2] - assert {[lindex $ttl 0] > 0 && [lindex $ttl 0] <= 60} - assert {[lindex $ttl 1] > 0 && [lindex $ttl 1] <= 60} - - # Test 3: PERSIST with flexible order - assert_equal [r HGETEX myhash FIELDS 1 f3 PERSIST] [list "v3"] - set ttl [r HTTL myhash FIELDS 1 f3] - assert_equal [lindex $ttl 0] -1 - } - } -} - -# Field validation and error handling improvements tests -start_server {tags {"hash"}} { - foreach type {listpackex hashtable} { - if {$type eq "hashtable"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "Field count validation - HEXPIRE family ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - - # Test field count mismatches (too few fields specified) - assert_error {*value is not an integer or out of range*} {r HEXPIRE myhash FIELDS 60 1 f1 f2 f3} - - # Test with numeric field names (should work) - r del myhash - r hset myhash 01 v1 02 v2 03 v3 - assert_equal [r HEXPIRE myhash 60 FIELDS 3 01 02 03] [list $E_OK $E_OK $E_OK] - } - - test "Field count validation - HSETEX ($type)" { - r del myhash - - # Test field-value pair mismatches - assert_error {*wrong number of arguments*} {r HSETEX myhash FIELDS 2 f1 v1} - assert_error {*unknown argument*} {r HSETEX myhash FIELDS 1 f1 v1 f2 v2} - assert_error {*wrong number of arguments*} {r HSETEX myhash FIELDS 3 f1 v1 f2 v2} - - # Test valid field-value pairs - assert_equal [r HSETEX myhash FIELDS 2 f1 v1 f2 v2 EX 60] 1 - assert_equal [r HGET myhash f1] "v1" - assert_equal [r HGET myhash f2] "v2" - } - - test "Field count validation - HGETEX ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - - # Test field count mismatches - assert_error {*wrong number of arguments*} {r HGETEX myhash FIELDS 2 f1} - assert_error {*unknown argument*} {r HGETEX myhash FIELDS 1 f1 f2 f3} - - # Test valid field counts - assert_equal [r HGETEX myhash FIELDS 2 f1 f2 EX 60] [list "v1" "v2"] - } - - test "Error message consistency and validation ($type)" { - r del myhash - r hset myhash f1 v1 - - # Test invalid numfields values - assert_error {*Parameter*numFields*should be greater than 0*} {r HEXPIRE myhash 60 FIELDS 0 f1} - assert_error {*Parameter*numFields*should be greater than 0*} {r HEXPIRE myhash 60 FIELDS -1 f1} - assert_error {*invalid number of fields*} {r HSETEX myhash FIELDS 0 f1 v1 EX 60} - assert_error {*invalid number of fields*} {r HGETEX myhash FIELDS 0 f1 EX 60} - - # Test missing FIELDS keyword - assert_error {*unknown argument*} {r HEXPIRE myhash 60 2 f1 f2} - assert_error {*unknown argument*} {r HSETEX myhash EX 60 2 f1 v1 f2 v2} - - # Test missing expire time - assert_error {*value is not an integer or out of range*} {r HEXPIRE myhash NX FIELDS 1 f1} - assert_error {*value is not an integer or out of range*} {r HPEXPIRE myhash FIELDS 1 f1 XX} - } - - test "Numeric field names validation ($type)" { - r del myhash - r hset myhash 01 v1 02 v2 999 v999 1000 v1000 - - # Small numbers should work as field names - assert_equal [r HEXPIRE myhash 60 FIELDS 3 01 02 999] [list $E_OK $E_OK $E_OK] - - # Large numbers should also work as field names - assert_equal [r HPEXPIRE myhash 5000 FIELDS 1 1000] [list $E_OK] - - # Verify the fields still exist and have expiry - set ttl [r HTTL myhash FIELDS 4 01 02 999 1000] - assert {[lindex $ttl 0] > 0} - assert {[lindex $ttl 1] > 0} - assert {[lindex $ttl 2] > 0} - assert {[lindex $ttl 3] > 0} - } - } -} - -# Advanced flexible parsing and edge case tests -start_server {tags {"hash"}} { - foreach type {listpackex hashtable} { - if {$type eq "hashtable"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "Multiple condition flags error handling ($type)" { - r del myhash - r hset myhash f1 v1 - - # Test multiple condition flags (should fail) - assert_error {*Multiple condition flags specified*} {r HEXPIRE myhash 60 NX XX FIELDS 1 f1} - assert_error {*Multiple condition flags specified*} {r HPEXPIRE myhash 5000 GT LT FIELDS 1 f1} - assert_error {*Multiple condition flags specified*} {r HEXPIRE myhash 60 FIELDS 1 f1 NX XX} - } - - test "Multiple FIELDS keywords error handling ($type)" { - r del myhash - r hset myhash f1 v1 - - # Test multiple FIELDS keywords (should fail) - assert_error {*value is not an integer or out of range*} {r HEXPIRE myhash FIELDS 1 f1 60 FIELDS 1 f2} - assert_error {*FIELDS keyword specified multiple times*} {r HPEXPIRE myhash 5000 FIELDS 1 f1 FIELDS 1 f2} - } - - test "Boundary conditions and edge cases ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - - # Test with maximum reasonable field count - r del myhash - for {set i 1} {$i <= 100} {incr i} { - r hset myhash f$i v$i - } - - # Build field list for 50 fields - set field_list {} - for {set i 1} {$i <= 50} {incr i} { - lappend field_list f$i - } - - # Test rigid parsing with many fields - set result [r HEXPIRE myhash 300 FIELDS 50 {*}$field_list] - assert_equal [llength $result] 50 - - # Verify all fields got expiry set - set ttl_result [r HTTL myhash FIELDS 50 {*}$field_list] - foreach ttl $ttl_result { - assert {$ttl > 0 && $ttl <= 300} - } - } - - test "Field names that look like keywords or numbers ($type)" { - r del myhash - r hset myhash EX value1 PX value2 FIELDS value3 NX value4 60 value5 - - # Test that field names that look like keywords work correctly - assert_equal [r HEXPIRE myhash 120 FIELDS 5 EX PX FIELDS NX 60] [list $E_OK $E_OK $E_OK $E_OK $E_OK] - - # Verify the fields exist and have expiry - set ttl [r HTTL myhash FIELDS 5 EX PX FIELDS NX 60] - foreach t $ttl { - assert {$t > 0 && $t <= 120} - } - - # Test HSETEX with keyword-like field names - r del myhash - assert_equal [r HSETEX myhash FIELDS 3 EX val1 PX val2 FIELDS val3 EX 60] 1 - assert_equal [r HGET myhash EX] "val1" - assert_equal [r HGET myhash PX] "val2" - assert_equal [r HGET myhash FIELDS] "val3" - } - } -} - -# Regression tests for specific fixes made during development -start_server {tags {"hash"}} { - foreach type {listpackex hashtable} { - if {$type eq "hashtable"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "Parser state consistency ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 - - # Test that parser correctly handles all argument positions - # without corrupting internal state - - # Test 1: Multiple valid commands in sequence - assert_equal [r HEXPIRE myhash 60 FIELDS 1 f1] [list $E_OK] - assert_equal [r HPEXPIRE myhash 5000 FIELDS 1 f2] [list $E_OK] - # Should fail due to NX (field already has expiration) - assert_equal [r HEXPIRE myhash 120 NX FIELDS 1 f1] [list $E_FAIL] - # Should succeed due to XX (field has expiration) - assert_equal [r HEXPIRE myhash 180 XX FIELDS 1 f1] [list $E_OK] - - # Test 2: Verify TTL values are correct - set ttl [r HTTL myhash FIELDS 2 f1 f2] - assert {[lindex $ttl 0] > 0 && [lindex $ttl 0] <= 180} - assert {[lindex $ttl 1] > 0} - } - } -} - -# Integration tests - verify all improvements work together -start_server {tags {"hash"}} { - foreach type {listpackex hashtable} { - if {$type eq "hashtable"} { - r config set hash-max-listpack-entries 0 - } else { - r config set hash-max-listpack-entries 512 - } - - test "Stress test - complex scenarios with all features ($type)" { - r del myhash - - # Create a hash with many fields - for {set i 1} {$i <= 20} {incr i} { - r hset myhash field$i value$i - } - - # Test 1: Flexible parsing with large field counts - set field_list {} - for {set i 1} {$i <= 10} {incr i} { - lappend field_list field$i - } - assert_equal [llength [r HEXPIRE myhash 3600 NX FIELDS 10 {*}$field_list]] 10 - - # Test 2: Mixed operations with rigid ordering - # First set expiration on field11-field15 so XX condition can succeed - assert_equal [r HPEXPIRE myhash 3600000 NX FIELDS 5 field11 field12 field13 field14 field15] [list $E_OK $E_OK $E_OK $E_OK $E_OK] - # Now XX should succeed since these fields have expiration - assert_equal [r HPEXPIRE myhash 7200000 XX FIELDS 5 field11 field12 field13 field14 field15] [list $E_OK $E_OK $E_OK $E_OK $E_OK] - assert_equal [r HEXPIRE myhash 7200 GT FIELDS 3 field1 field2 field3] [list $E_OK $E_OK $E_OK] - - # Test 3: Verify field count validation still works with complex scenarios - assert_error {*wrong number of arguments*} {r HEXPIRE myhash 3600 FIELDS 15 field1 field2 field3} - assert_error {*unknown argument*} {r HPEXPIRE myhash 7200000 FIELDS 3 field1 field2 field3 field4 field5} - - # Test 4: Verify all fields have correct expiry states - set ttl_result [r HTTL myhash FIELDS 15 field1 field2 field3 field4 field5 field6 field7 field8 field9 field10 field11 field12 field13 field14 field15] - for {set i 0} {$i < 15} {incr i} { - set ttl_val [lindex $ttl_result $i] - assert {$ttl_val > 0} - } - } - - test "Backward compatibility verification ($type)" { - r del myhash - r hset myhash f1 v1 f2 v2 f3 v3 - - # Verify that traditional syntax still works exactly as before - assert_equal [r HEXPIRE myhash 60 FIELDS 2 f1 f2] [list $E_OK $E_OK] - assert_equal [r HPEXPIRE myhash 5000 NX FIELDS 1 f3] [list $E_OK] - assert_equal [r HEXPIREAT myhash [expr {[clock seconds] + 300}] XX FIELDS 1 f1] [list $E_OK] - - # Verify HSETEX/HGETEX traditional syntax - r del myhash - assert_equal [r HSETEX myhash EX 60 FIELDS 2 f1 v1 f2 v2] 1 - assert_equal [r HGETEX myhash PX 5000 FIELDS 2 f1 f2] [list "v1" "v2"] - - # Verify error messages are consistent with expectations - assert_error {*Parameter*numFields*should be greater than 0*} {r HEXPIRE myhash 60 FIELDS 0 f1} - assert_error {*unknown argument*} {r HEXPIRE myhash 60 2 f1 f2} - } - } -} diff --git a/examples/redis-unstable/tests/unit/type/hash.tcl b/examples/redis-unstable/tests/unit/type/hash.tcl deleted file mode 100644 index aa1f818..0000000 --- a/examples/redis-unstable/tests/unit/type/hash.tcl +++ /dev/null @@ -1,962 +0,0 @@ -start_server {tags {"hash"}} { - r config set hash-max-listpack-value 64 - r config set hash-max-listpack-entries 512 - - test {HSET/HLEN - Small hash creation} { - array set smallhash {} - for {set i 0} {$i < 8} {incr i} { - set key __avoid_collisions__[randstring 0 8 alpha] - set val __avoid_collisions__[randstring 0 8 alpha] - if {[info exists smallhash($key)]} { - incr i -1 - continue - } - r hset smallhash $key $val - set smallhash($key) $val - } - list [r hlen smallhash] - } {8} - - test {Is the small hash encoded with a listpack?} { - assert_encoding listpack smallhash - } - - proc create_hash {key entries} { - r del $key - foreach entry $entries { - r hset $key [lindex $entry 0] [lindex $entry 1] - } - } - - proc get_keys {l} { - set res {} - foreach entry $l { - set key [lindex $entry 0] - lappend res $key - } - return $res - } - - foreach {type contents} "listpack {{a 1} {b 2} {c 3}} hashtable {{a 1} {b 2} {[randstring 70 90 alpha] 3}}" { - set original_max_value [lindex [r config get hash-max-ziplist-value] 1] - r config set hash-max-ziplist-value 10 - create_hash myhash $contents - assert_encoding $type myhash - - # coverage for kvobjComputeSize - assert_morethan [memory_usage myhash] 0 - - test "HRANDFIELD - $type" { - unset -nocomplain myhash - array set myhash {} - for {set i 0} {$i < 100} {incr i} { - set key [r hrandfield myhash] - set myhash($key) 1 - } - assert_equal [lsort [get_keys $contents]] [lsort [array names myhash]] - } - r config set hash-max-ziplist-value $original_max_value - } - - test "HRANDFIELD with RESP3" { - r hello 3 - set res [r hrandfield myhash 3 withvalues] - assert_equal [llength $res] 3 - assert_equal [llength [lindex $res 1]] 2 - - set res [r hrandfield myhash 3] - assert_equal [llength $res] 3 - assert_equal [llength [lindex $res 1]] 1 - r hello 2 - } - - test "HRANDFIELD count of 0 is handled correctly" { - r hrandfield myhash 0 - } {} - - test "HRANDFIELD count overflow" { - r hmset myhash a 1 - assert_error {*value is out of range*} {r hrandfield myhash -9223372036854770000 withvalues} - assert_error {*value is out of range*} {r hrandfield myhash -9223372036854775808 withvalues} - assert_error {*value is out of range*} {r hrandfield myhash -9223372036854775808} - } {} - - test "HRANDFIELD with against non existing key" { - r hrandfield nonexisting_key 100 - } {} - - # Make sure we can distinguish between an empty array and a null response - r readraw 1 - - test "HRANDFIELD count of 0 is handled correctly - emptyarray" { - r hrandfield myhash 0 - } {*0} - - test "HRANDFIELD with against non existing key - emptyarray" { - r hrandfield nonexisting_key 100 - } {*0} - - r readraw 0 - - foreach {type contents} " - hashtable {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {[randstring 70 90 alpha] 10}} - listpack {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {10 j}} " { - test "HRANDFIELD with - $type" { - set original_max_value [lindex [r config get hash-max-ziplist-value] 1] - r config set hash-max-ziplist-value 10 - create_hash myhash $contents - assert_encoding $type myhash - - # create a dict for easy lookup - set mydict [dict create {*}[r hgetall myhash]] - - # We'll stress different parts of the code, see the implementation - # of HRANDFIELD for more information, but basically there are - # four different code paths. - - # PATH 1: Use negative count. - - # 1) Check that it returns repeated elements with and without values. - set res [r hrandfield myhash -20] - assert_equal [llength $res] 20 - set res [r hrandfield myhash -1001] - assert_equal [llength $res] 1001 - # again with WITHVALUES - set res [r hrandfield myhash -20 withvalues] - assert_equal [llength $res] 40 - set res [r hrandfield myhash -1001 withvalues] - assert_equal [llength $res] 2002 - - # Test random uniform distribution - # df = 9, 40 means 0.00001 probability - set res [r hrandfield myhash -1000] - assert_lessthan [chi_square_value $res] 40 - - # 2) Check that all the elements actually belong to the original hash. - foreach {key val} $res { - assert {[dict exists $mydict $key]} - } - - # 3) Check that eventually all the elements are returned. - # Use both WITHVALUES and without - unset -nocomplain auxset - set iterations 1000 - while {$iterations != 0} { - incr iterations -1 - if {[expr {$iterations % 2}] == 0} { - set res [r hrandfield myhash -3 withvalues] - foreach {key val} $res { - dict append auxset $key $val - } - } else { - set res [r hrandfield myhash -3] - foreach key $res { - dict append auxset $key $val - } - } - if {[lsort [dict keys $mydict]] eq - [lsort [dict keys $auxset]]} { - break; - } - } - assert {$iterations != 0} - - # PATH 2: positive count (unique behavior) with requested size - # equal or greater than set size. - foreach size {10 20} { - set res [r hrandfield myhash $size] - assert_equal [llength $res] 10 - assert_equal [lsort $res] [lsort [dict keys $mydict]] - - # again with WITHVALUES - set res [r hrandfield myhash $size withvalues] - assert_equal [llength $res] 20 - assert_equal [lsort $res] [lsort $mydict] - } - - # PATH 3: Ask almost as elements as there are in the set. - # In this case the implementation will duplicate the original - # set and will remove random elements up to the requested size. - # - # PATH 4: Ask a number of elements definitely smaller than - # the set size. - # - # We can test both the code paths just changing the size but - # using the same code. - foreach size {8 2} { - set res [r hrandfield myhash $size] - assert_equal [llength $res] $size - # again with WITHVALUES - set res [r hrandfield myhash $size withvalues] - assert_equal [llength $res] [expr {$size * 2}] - - # 1) Check that all the elements actually belong to the - # original set. - foreach ele [dict keys $res] { - assert {[dict exists $mydict $ele]} - } - - # 2) Check that eventually all the elements are returned. - # Use both WITHVALUES and without - unset -nocomplain auxset - unset -nocomplain allkey - set iterations [expr {1000 / $size}] - set all_ele_return false - while {$iterations != 0} { - incr iterations -1 - if {[expr {$iterations % 2}] == 0} { - set res [r hrandfield myhash $size withvalues] - foreach {key value} $res { - dict append auxset $key $value - lappend allkey $key - } - } else { - set res [r hrandfield myhash $size] - foreach key $res { - dict append auxset $key - lappend allkey $key - } - } - if {[lsort [dict keys $mydict]] eq - [lsort [dict keys $auxset]]} { - set all_ele_return true - } - } - assert_equal $all_ele_return true - # df = 9, 40 means 0.00001 probability - assert_lessthan [chi_square_value $allkey] 40 - } - } - r config set hash-max-ziplist-value $original_max_value - } - - - test {HSET/HLEN - Big hash creation} { - array set bighash {} - for {set i 0} {$i < 1024} {incr i} { - set key __avoid_collisions__[randstring 0 8 alpha] - set val __avoid_collisions__[randstring 0 8 alpha] - if {[info exists bighash($key)]} { - incr i -1 - continue - } - r hset bighash $key $val - set bighash($key) $val - } - list [r hlen bighash] - } {1024} - - test {Is the big hash encoded with an hash table?} { - assert_encoding hashtable bighash - } - - test {HGET against the small hash} { - set err {} - foreach k [array names smallhash *] { - if {$smallhash($k) ne [r hget smallhash $k]} { - set err "$smallhash($k) != [r hget smallhash $k]" - break - } - } - set _ $err - } {} - - test {HGET against the big hash} { - set err {} - foreach k [array names bighash *] { - if {$bighash($k) ne [r hget bighash $k]} { - set err "$bighash($k) != [r hget bighash $k]" - break - } - } - set _ $err - } {} - - test {HGET against non existing key} { - set rv {} - lappend rv [r hget smallhash __123123123__] - lappend rv [r hget bighash __123123123__] - set _ $rv - } {{} {}} - - test {HSET in update and insert mode} { - set rv {} - set k [lindex [array names smallhash *] 0] - lappend rv [r hset smallhash $k newval1] - set smallhash($k) newval1 - lappend rv [r hget smallhash $k] - lappend rv [r hset smallhash __foobar123__ newval] - set k [lindex [array names bighash *] 0] - lappend rv [r hset bighash $k newval2] - set bighash($k) newval2 - lappend rv [r hget bighash $k] - lappend rv [r hset bighash __foobar123__ newval] - lappend rv [r hdel smallhash __foobar123__] - lappend rv [r hdel bighash __foobar123__] - set _ $rv - } {0 newval1 1 0 newval2 1 1 1} - - test {HSETNX target key missing - small hash} { - r hsetnx smallhash __123123123__ foo - r hget smallhash __123123123__ - } {foo} - - test {HSETNX target key exists - small hash} { - r hsetnx smallhash __123123123__ bar - set result [r hget smallhash __123123123__] - r hdel smallhash __123123123__ - set _ $result - } {foo} - - test {HSETNX target key missing - big hash} { - r hsetnx bighash __123123123__ foo - r hget bighash __123123123__ - } {foo} - - test {HSETNX target key exists - big hash} { - r hsetnx bighash __123123123__ bar - set result [r hget bighash __123123123__] - r hdel bighash __123123123__ - set _ $result - } {foo} - - test {HSET/HMSET wrong number of args} { - assert_error {*wrong number of arguments for 'hset' command} {r hset smallhash key1 val1 key2} - assert_error {*wrong number of arguments for 'hmset' command} {r hmset smallhash key1 val1 key2} - } - - test {HMSET - small hash} { - set args {} - foreach {k v} [array get smallhash] { - set newval [randstring 0 8 alpha] - set smallhash($k) $newval - lappend args $k $newval - } - r hmset smallhash {*}$args - } {OK} - - test {HMSET - big hash} { - set args {} - foreach {k v} [array get bighash] { - set newval [randstring 0 8 alpha] - set bighash($k) $newval - lappend args $k $newval - } - r hmset bighash {*}$args - } {OK} - - test {HMGET against non existing key and fields} { - set rv {} - lappend rv [r hmget doesntexist __123123123__ __456456456__] - lappend rv [r hmget smallhash __123123123__ __456456456__] - lappend rv [r hmget bighash __123123123__ __456456456__] - set _ $rv - } {{{} {}} {{} {}} {{} {}}} - - test {Hash commands against wrong type} { - r set wrongtype somevalue - assert_error "WRONGTYPE Operation against a key*" {r hmget wrongtype field1 field2} - assert_error "WRONGTYPE Operation against a key*" {r hrandfield wrongtype} - assert_error "WRONGTYPE Operation against a key*" {r hget wrongtype field1} - assert_error "WRONGTYPE Operation against a key*" {r hgetall wrongtype} - assert_error "WRONGTYPE Operation against a key*" {r hdel wrongtype field1} - assert_error "WRONGTYPE Operation against a key*" {r hincrby wrongtype field1 2} - assert_error "WRONGTYPE Operation against a key*" {r hincrbyfloat wrongtype field1 2.5} - assert_error "WRONGTYPE Operation against a key*" {r hstrlen wrongtype field1} - assert_error "WRONGTYPE Operation against a key*" {r hvals wrongtype} - assert_error "WRONGTYPE Operation against a key*" {r hkeys wrongtype} - assert_error "WRONGTYPE Operation against a key*" {r hexists wrongtype field1} - assert_error "WRONGTYPE Operation against a key*" {r hset wrongtype field1 val1} - assert_error "WRONGTYPE Operation against a key*" {r hmset wrongtype field1 val1 field2 val2} - assert_error "WRONGTYPE Operation against a key*" {r hsetnx wrongtype field1 val1} - assert_error "WRONGTYPE Operation against a key*" {r hlen wrongtype} - assert_error "WRONGTYPE Operation against a key*" {r hscan wrongtype 0} - assert_error "WRONGTYPE Operation against a key*" {r hgetdel wrongtype fields 1 a} - } - - test {HMGET - small hash} { - set keys {} - set vals {} - foreach {k v} [array get smallhash] { - lappend keys $k - lappend vals $v - } - set err {} - set result [r hmget smallhash {*}$keys] - if {$vals ne $result} { - set err "$vals != $result" - break - } - set _ $err - } {} - - test {HMGET - big hash} { - set keys {} - set vals {} - foreach {k v} [array get bighash] { - lappend keys $k - lappend vals $v - } - set err {} - set result [r hmget bighash {*}$keys] - if {$vals ne $result} { - set err "$vals != $result" - break - } - set _ $err - } {} - - test {HKEYS - small hash} { - lsort [r hkeys smallhash] - } [lsort [array names smallhash *]] - - test {HKEYS - big hash} { - lsort [r hkeys bighash] - } [lsort [array names bighash *]] - - test {HVALS - small hash} { - set vals {} - foreach {k v} [array get smallhash] { - lappend vals $v - } - set _ [lsort $vals] - } [lsort [r hvals smallhash]] - - test {HVALS - big hash} { - set vals {} - foreach {k v} [array get bighash] { - lappend vals $v - } - set _ [lsort $vals] - } [lsort [r hvals bighash]] - - test {HGETALL - small hash} { - lsort [r hgetall smallhash] - } [lsort [array get smallhash]] - - test {HGETALL - big hash} { - lsort [r hgetall bighash] - } [lsort [array get bighash]] - - test {HGETALL against non-existing key} { - r del htest - r hgetall htest - } {} - - test {HDEL and return value} { - set rv {} - lappend rv [r hdel smallhash nokey] - lappend rv [r hdel bighash nokey] - set k [lindex [array names smallhash *] 0] - lappend rv [r hdel smallhash $k] - lappend rv [r hdel smallhash $k] - lappend rv [r hget smallhash $k] - unset smallhash($k) - set k [lindex [array names bighash *] 0] - lappend rv [r hdel bighash $k] - lappend rv [r hdel bighash $k] - lappend rv [r hget bighash $k] - unset bighash($k) - set _ $rv - } {0 0 1 0 {} 1 0 {}} - - test {HDEL - more than a single value} { - set rv {} - r del myhash - r hmset myhash a 1 b 2 c 3 - assert_equal 0 [r hdel myhash x y] - assert_equal 2 [r hdel myhash a c f] - r hgetall myhash - } {b 2} - - test {HDEL - hash becomes empty before deleting all specified fields} { - r del myhash - r hmset myhash a 1 b 2 c 3 - assert_equal 3 [r hdel myhash a b c d e] - assert_equal 0 [r exists myhash] - } - - test {HEXISTS} { - set rv {} - set k [lindex [array names smallhash *] 0] - lappend rv [r hexists smallhash $k] - lappend rv [r hexists smallhash nokey] - set k [lindex [array names bighash *] 0] - lappend rv [r hexists bighash $k] - lappend rv [r hexists bighash nokey] - } {1 0 1 0} - - test {Is a ziplist encoded Hash promoted on big payload?} { - r hset smallhash foo [string repeat a 1024] - r debug object smallhash - } {*hashtable*} {needs:debug} - - test {HINCRBY against non existing database key} { - r del htest - list [r hincrby htest foo 2] - } {2} - - test {HINCRBY HINCRBYFLOAT against non-integer increment value} { - r del incrhash - r hset incrhash field 5 - assert_error "*value is not an integer*" {r hincrby incrhash field v} - assert_error "*value is not a*" {r hincrbyfloat incrhash field v} - } - - test {HINCRBY against non existing hash key} { - set rv {} - r hdel smallhash tmp - r hdel bighash tmp - lappend rv [r hincrby smallhash tmp 2] - lappend rv [r hget smallhash tmp] - lappend rv [r hincrby bighash tmp 2] - lappend rv [r hget bighash tmp] - } {2 2 2 2} - - test {HINCRBY against hash key created by hincrby itself} { - set rv {} - lappend rv [r hincrby smallhash tmp 3] - lappend rv [r hget smallhash tmp] - lappend rv [r hincrby bighash tmp 3] - lappend rv [r hget bighash tmp] - } {5 5 5 5} - - test {HINCRBY against hash key originally set with HSET} { - r hset smallhash tmp 100 - r hset bighash tmp 100 - list [r hincrby smallhash tmp 2] [r hincrby bighash tmp 2] - } {102 102} - - test {HINCRBY over 32bit value} { - r hset smallhash tmp 17179869184 - r hset bighash tmp 17179869184 - list [r hincrby smallhash tmp 1] [r hincrby bighash tmp 1] - } {17179869185 17179869185} - - test {HINCRBY over 32bit value with over 32bit increment} { - r hset smallhash tmp 17179869184 - r hset bighash tmp 17179869184 - list [r hincrby smallhash tmp 17179869184] [r hincrby bighash tmp 17179869184] - } {34359738368 34359738368} - - test {HINCRBY fails against hash value with spaces (left)} { - r hset smallhash str " 11" - r hset bighash str " 11" - catch {r hincrby smallhash str 1} smallerr - catch {r hincrby bighash str 1} bigerr - set rv {} - lappend rv [string match "ERR *not an integer*" $smallerr] - lappend rv [string match "ERR *not an integer*" $bigerr] - } {1 1} - - test {HINCRBY fails against hash value with spaces (right)} { - r hset smallhash str "11 " - r hset bighash str "11 " - catch {r hincrby smallhash str 1} smallerr - catch {r hincrby bighash str 1} bigerr - set rv {} - lappend rv [string match "ERR *not an integer*" $smallerr] - lappend rv [string match "ERR *not an integer*" $bigerr] - } {1 1} - - test {HINCRBY can detect overflows} { - set e {} - r hset hash n -9223372036854775484 - assert {[r hincrby hash n -1] == -9223372036854775485} - catch {r hincrby hash n -10000} e - set e - } {*overflow*} - - test {HINCRBYFLOAT against non existing database key} { - r del htest - list [r hincrbyfloat htest foo 2.5] - } {2.5} - - test {HINCRBYFLOAT against non existing hash key} { - set rv {} - r hdel smallhash tmp - r hdel bighash tmp - lappend rv [roundFloat [r hincrbyfloat smallhash tmp 2.5]] - lappend rv [roundFloat [r hget smallhash tmp]] - lappend rv [roundFloat [r hincrbyfloat bighash tmp 2.5]] - lappend rv [roundFloat [r hget bighash tmp]] - } {2.5 2.5 2.5 2.5} - - test {HINCRBYFLOAT against hash key created by hincrby itself} { - set rv {} - lappend rv [roundFloat [r hincrbyfloat smallhash tmp 3.5]] - lappend rv [roundFloat [r hget smallhash tmp]] - lappend rv [roundFloat [r hincrbyfloat bighash tmp 3.5]] - lappend rv [roundFloat [r hget bighash tmp]] - } {6 6 6 6} - - test {HINCRBYFLOAT against hash key originally set with HSET} { - r hset smallhash tmp 100 - r hset bighash tmp 100 - list [roundFloat [r hincrbyfloat smallhash tmp 2.5]] \ - [roundFloat [r hincrbyfloat bighash tmp 2.5]] - } {102.5 102.5} - - test {HINCRBYFLOAT over 32bit value} { - r hset smallhash tmp 17179869184 - r hset bighash tmp 17179869184 - list [r hincrbyfloat smallhash tmp 1] \ - [r hincrbyfloat bighash tmp 1] - } {17179869185 17179869185} - - test {HINCRBYFLOAT over 32bit value with over 32bit increment} { - r hset smallhash tmp 17179869184 - r hset bighash tmp 17179869184 - list [r hincrbyfloat smallhash tmp 17179869184] \ - [r hincrbyfloat bighash tmp 17179869184] - } {34359738368 34359738368} - - test {HINCRBYFLOAT fails against hash value with spaces (left)} { - r hset smallhash str " 11" - r hset bighash str " 11" - catch {r hincrbyfloat smallhash str 1} smallerr - catch {r hincrbyfloat bighash str 1} bigerr - set rv {} - lappend rv [string match "ERR *not*float*" $smallerr] - lappend rv [string match "ERR *not*float*" $bigerr] - } {1 1} - - test {HINCRBYFLOAT fails against hash value with spaces (right)} { - r hset smallhash str "11 " - r hset bighash str "11 " - catch {r hincrbyfloat smallhash str 1} smallerr - catch {r hincrbyfloat bighash str 1} bigerr - set rv {} - lappend rv [string match "ERR *not*float*" $smallerr] - lappend rv [string match "ERR *not*float*" $bigerr] - } {1 1} - - test {HINCRBYFLOAT fails against hash value that contains a null-terminator in the middle} { - r hset h f "1\x002" - catch {r hincrbyfloat h f 1} err - set rv {} - lappend rv [string match "ERR *not*float*" $err] - } {1} - - test {HSTRLEN against the small hash} { - set err {} - foreach k [array names smallhash *] { - if {[string length $smallhash($k)] ne [r hstrlen smallhash $k]} { - set err "[string length $smallhash($k)] != [r hstrlen smallhash $k]" - break - } - } - set _ $err - } {} - - test {HSTRLEN against the big hash} { - set err {} - foreach k [array names bighash *] { - if {[string length $bighash($k)] ne [r hstrlen bighash $k]} { - set err "[string length $bighash($k)] != [r hstrlen bighash $k]" - puts "HSTRLEN and logical length mismatch:" - puts "key: $k" - puts "Logical content: $bighash($k)" - puts "Server content: [r hget bighash $k]" - } - } - set _ $err - } {} - - test {HSTRLEN against non existing field} { - set rv {} - lappend rv [r hstrlen smallhash __123123123__] - lappend rv [r hstrlen bighash __123123123__] - set _ $rv - } {0 0} - - test {HSTRLEN corner cases} { - set vals { - -9223372036854775808 9223372036854775807 9223372036854775808 - {} 0 -1 x - } - foreach v $vals { - r hmset smallhash field $v - r hmset bighash field $v - set len1 [string length $v] - set len2 [r hstrlen smallhash field] - set len3 [r hstrlen bighash field] - assert {$len1 == $len2} - assert {$len2 == $len3} - } - } - - test {HINCRBYFLOAT over hash-max-listpack-value encoded with a listpack} { - set original_max_value [lindex [r config get hash-max-ziplist-value] 1] - r config set hash-max-listpack-value 8 - - # hash's value exceeds hash-max-listpack-value - r del smallhash - r del bighash - r hset smallhash tmp 0 - r hset bighash tmp 0 - r hincrbyfloat smallhash tmp 0.000005 - r hincrbyfloat bighash tmp 0.0000005 - assert_encoding listpack smallhash - assert_encoding hashtable bighash - - # hash's field exceeds hash-max-listpack-value - r del smallhash - r del bighash - r hincrbyfloat smallhash abcdefgh 1 - r hincrbyfloat bighash abcdefghi 1 - assert_encoding listpack smallhash - assert_encoding hashtable bighash - - r config set hash-max-listpack-value $original_max_value - } - - test {HGETDEL input validation} { - r del key1 - assert_error "*wrong number of arguments*" {r hgetdel} - assert_error "*wrong number of arguments*" {r hgetdel key1} - assert_error "*wrong number of arguments*" {r hgetdel key1 FIELDS} - assert_error "*wrong number of arguments*" {r hgetdel key1 FIELDS 0} - assert_error "*wrong number of arguments*" {r hgetdel key1 FIELDX} - assert_error "*argument FIELDS is missing*" {r hgetdel key1 XFIELDX 1 a} - assert_error "*numfields*parameter*must match*number of arguments*" {r hgetdel key1 FIELDS 2 a} - assert_error "*numfields*parameter*must match*number of arguments*" {r hgetdel key1 FIELDS 2 a b c} - assert_error "*Number of fields must be a positive integer*" {r hgetdel key1 FIELDS 0 a} - assert_error "*Number of fields must be a positive integer*" {r hgetdel key1 FIELDS -1 a} - assert_error "*Number of fields must be a positive integer*" {r hgetdel key1 FIELDS b a} - assert_error "*Number of fields must be a positive integer*" {r hgetdel key1 FIELDS 9223372036854775808 a} - } - - foreach type {listpack ht} { - set orig_config [lindex [r config get hash-max-listpack-entries] 1] - r del key1 - - if {$type == "listpack"} { - r config set hash-max-listpack-entries $orig_config - r hset key1 f1 1 f2 2 f3 3 strfield strval - assert_encoding listpack key1 - } else { - r config set hash-max-listpack-entries 0 - r hset key1 f1 1 f2 2 f3 3 strfield strval - assert_encoding hashtable key1 - } - - test {HGETDEL basic test} { - r del key1 - r hset key1 f1 1 f2 2 f3 3 strfield strval - assert_equal [r hgetdel key1 fields 1 f2] 2 - assert_equal [r hlen key1] 3 - assert_equal [r hget key1 f1] 1 - assert_equal [r hget key1 f2] "" - assert_equal [r hget key1 f3] 3 - assert_equal [r hget key1 strfield] strval - - assert_equal [r hgetdel key1 fields 1 f1] 1 - assert_equal [lsort [r hgetall key1]] [lsort "f3 3 strfield strval"] - assert_equal [r hgetdel key1 fields 1 f3] 3 - assert_equal [r hgetdel key1 fields 1 strfield] strval - assert_equal [r hgetall key1] "" - assert_equal [r exists key1] 0 - } - - test {HGETDEL test with non existing fields} { - r del key1 - r hset key1 f1 1 f2 2 f3 3 - assert_equal [r hgetdel key1 fields 4 x1 x2 x3 x4] "{} {} {} {}" - assert_equal [r hgetdel key1 fields 4 x1 x2 f3 x4] "{} {} 3 {}" - assert_equal [lsort [r hgetall key1]] [lsort "f1 1 f2 2"] - assert_equal [r hgetdel key1 fields 3 f1 f2 f3] "1 2 {}" - assert_equal [r hgetdel key1 fields 3 f1 f2 f3] "{} {} {}" - } - - r config set hash-max-listpack-entries $orig_config - } - - test {HGETDEL propagated as HDEL command to replica} { - set repl [attach_to_replication_stream] - r hset key1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 - r hgetdel key1 fields 1 f1 - r hgetdel key1 fields 2 f2 f3 - - # make sure non-existing fields are not replicated - r hgetdel key1 fields 2 f7 f8 - - # delete more - r hgetdel key1 fields 3 f4 f5 f6 - - assert_replication_stream $repl { - {select *} - {hset key1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5} - {hdel key1 f1} - {hdel key1 f2 f3} - {hdel key1 f4 f5 f6} - } - close_replication_stream $repl - } {} {needs:repl} - - test {Hash ziplist regression test for large keys} { - r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk a - r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk b - r hget hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk - } {b} - - foreach size {10 512} { - test "Hash fuzzing #1 - $size fields" { - for {set times 0} {$times < 10} {incr times} { - catch {unset hash} - array set hash {} - r del hash - - # Create - for {set j 0} {$j < $size} {incr j} { - set field [randomValue] - set value [randomValue] - r hset hash $field $value - set hash($field) $value - } - - # Verify - foreach {k v} [array get hash] { - assert_equal $v [r hget hash $k] - } - assert_equal [array size hash] [r hlen hash] - } - } - - test "Hash fuzzing #2 - $size fields" { - for {set times 0} {$times < 10} {incr times} { - catch {unset hash} - array set hash {} - r del hash - - # Create - for {set j 0} {$j < $size} {incr j} { - randpath { - set field [randomValue] - set value [randomValue] - r hset hash $field $value - set hash($field) $value - } { - set field [randomSignedInt 512] - set value [randomSignedInt 512] - r hset hash $field $value - set hash($field) $value - } { - randpath { - set field [randomValue] - } { - set field [randomSignedInt 512] - } - r hdel hash $field - unset -nocomplain hash($field) - } - } - - # Verify - foreach {k v} [array get hash] { - assert_equal $v [r hget hash $k] - } - assert_equal [array size hash] [r hlen hash] - } - } - } - - test {Stress test the hash ziplist -> hashtable encoding conversion} { - r config set hash-max-ziplist-entries 32 - for {set j 0} {$j < 100} {incr j} { - r del myhash - for {set i 0} {$i < 64} {incr i} { - r hset myhash [randomValue] [randomValue] - } - assert_encoding hashtable myhash - } - } - - # The following test can only be executed if we don't use Valgrind, and if - # we are using x86_64 architecture, because: - # - # 1) Valgrind has floating point limitations, no support for 80 bits math. - # 2) Other archs may have the same limits. - # - # 1.23 cannot be represented correctly with 64 bit doubles, so we skip - # the test, since we are only testing pretty printing here and is not - # a bug if the program outputs things like 1.299999... - if {!$::valgrind && [string match *x86_64* [exec uname -a]]} { - test {Test HINCRBYFLOAT for correct float representation (issue #2846)} { - r del myhash - assert {[r hincrbyfloat myhash float 1.23] eq {1.23}} - assert {[r hincrbyfloat myhash float 0.77] eq {2}} - assert {[r hincrbyfloat myhash float -0.1] eq {1.9}} - } - } - - test {Hash ziplist of various encodings} { - r del k - config_set hash-max-ziplist-entries 1000000000 - config_set hash-max-ziplist-value 1000000000 - r hset k ZIP_INT_8B 127 - r hset k ZIP_INT_16B 32767 - r hset k ZIP_INT_32B 2147483647 - r hset k ZIP_INT_64B 9223372036854775808 - r hset k ZIP_INT_IMM_MIN 0 - r hset k ZIP_INT_IMM_MAX 12 - r hset k ZIP_STR_06B [string repeat x 31] - r hset k ZIP_STR_14B [string repeat x 8191] - r hset k ZIP_STR_32B [string repeat x 65535] - set k [r hgetall k] - set dump [r dump k] - - # will be converted to dict at RESTORE - config_set hash-max-ziplist-entries 2 - config_set sanitize-dump-payload no mayfail - r restore kk 0 $dump - set kk [r hgetall kk] - - # make sure the values are right - assert_equal [lsort $k] [lsort $kk] - assert_equal [dict get $k ZIP_STR_06B] [string repeat x 31] - set k [dict remove $k ZIP_STR_06B] - assert_equal [dict get $k ZIP_STR_14B] [string repeat x 8191] - set k [dict remove $k ZIP_STR_14B] - assert_equal [dict get $k ZIP_STR_32B] [string repeat x 65535] - set k [dict remove $k ZIP_STR_32B] - set _ $k - } {ZIP_INT_8B 127 ZIP_INT_16B 32767 ZIP_INT_32B 2147483647 ZIP_INT_64B 9223372036854775808 ZIP_INT_IMM_MIN 0 ZIP_INT_IMM_MAX 12} - - test {Hash ziplist of various encodings - sanitize dump} { - config_set sanitize-dump-payload yes mayfail - r restore kk 0 $dump replace - set k [r hgetall k] - set kk [r hgetall kk] - - # make sure the values are right - assert_equal [lsort $k] [lsort $kk] - assert_equal [dict get $k ZIP_STR_06B] [string repeat x 31] - set k [dict remove $k ZIP_STR_06B] - assert_equal [dict get $k ZIP_STR_14B] [string repeat x 8191] - set k [dict remove $k ZIP_STR_14B] - assert_equal [dict get $k ZIP_STR_32B] [string repeat x 65535] - set k [dict remove $k ZIP_STR_32B] - set _ $k - } {ZIP_INT_8B 127 ZIP_INT_16B 32767 ZIP_INT_32B 2147483647 ZIP_INT_64B 9223372036854775808 ZIP_INT_IMM_MIN 0 ZIP_INT_IMM_MAX 12} - - test {KEYS command return expired keys when allow_access_expired is 1} { - r flushall - r debug set-allow-access-expired 1 - r debug set-active-expire 0 - r set key1 value1 - r pexpire key1 1 - after 2 - assert_equal {key1} [r keys *] - r debug set-allow-access-expired 0 - r debug set-active-expire 1 - } {OK} {needs:debug} - - # On some platforms strtold("+inf") with valgrind returns a non-inf result - if {!$::valgrind} { - test {HINCRBYFLOAT does not allow NaN or Infinity} { - assert_error "*value is NaN or Infinity*" {r hincrbyfloat hfoo field +inf} - assert_equal 0 [r exists hfoo] - } - } -} diff --git a/examples/redis-unstable/tests/unit/type/incr.tcl b/examples/redis-unstable/tests/unit/type/incr.tcl deleted file mode 100644 index 16c2531..0000000 --- a/examples/redis-unstable/tests/unit/type/incr.tcl +++ /dev/null @@ -1,225 +0,0 @@ -start_server {tags {"incr"}} { - test {INCR against non existing key} { - set res {} - append res [r incr novar] - append res [r get novar] - } {11} - - test {INCR against key created by incr itself} { - r incr novar - } {2} - - test {DECR against key created by incr} { - r decr novar - } {1} - - test {DECR against key is not exist and incr} { - r del novar_not_exist - assert_equal {-1} [r decr novar_not_exist] - assert_equal {0} [r incr novar_not_exist] - } - - test {INCR against key originally set with SET} { - r set novar 100 - r incr novar - } {101} - - test {INCR over 32bit value} { - r set novar 17179869184 - r incr novar - } {17179869185} - - test {INCRBY over 32bit value with over 32bit increment} { - r set novar 17179869184 - r incrby novar 17179869184 - } {34359738368} - - test {INCR fails against key with spaces (left)} { - r set novar " 11" - catch {r incr novar} err - format $err - } {ERR*} - - test {INCR fails against key with spaces (right)} { - r set novar "11 " - catch {r incr novar} err - format $err - } {ERR*} - - test {INCR fails against key with spaces (both)} { - r set novar " 11 " - catch {r incr novar} err - format $err - } {ERR*} - - test {DECRBY negation overflow} { - r set x 0 - catch {r decrby x -9223372036854775808} err - format $err - } {ERR*} - - test {INCR fails against a key holding a list} { - r rpush mylist 1 - catch {r incr mylist} err - r rpop mylist - format $err - } {WRONGTYPE*} - - test {DECRBY over 32bit value with over 32bit increment, negative res} { - r set novar 17179869184 - r decrby novar 17179869185 - } {-1} - - test {DECRBY against key is not exist} { - r del key_not_exist - assert_equal {-1} [r decrby key_not_exist 1] - } - - test {INCR does not use shared objects} { - r set foo -1 - r incr foo - assert_refcount 1 foo - r set foo 9998 - r incr foo - assert_refcount 1 foo - r incr foo - assert_refcount 1 foo - } - - test {INCR can modify objects in-place} { - r set foo 20000 - r incr foo - assert_refcount 1 foo - set old [lindex [split [r debug object foo]] 1] - r incr foo - set new [lindex [split [r debug object foo]] 1] - assert {[string range $old 0 2] eq "at:"} - assert {[string range $new 0 2] eq "at:"} - assert {$old eq $new} - } {} {needs:debug debug_defrag:skip} - - test {INCRBYFLOAT against non existing key} { - r del novar - list [roundFloat [r incrbyfloat novar 1]] \ - [roundFloat [r get novar]] \ - [roundFloat [r incrbyfloat novar 0.25]] \ - [roundFloat [r get novar]] - } {1 1 1.25 1.25} - - test {INCRBYFLOAT against key originally set with SET} { - r set novar 1.5 - roundFloat [r incrbyfloat novar 1.5] - } {3} - - test {INCRBYFLOAT over 32bit value} { - r set novar 17179869184 - r incrbyfloat novar 1.5 - } {17179869185.5} - - test {INCRBYFLOAT over 32bit value with over 32bit increment} { - r set novar 17179869184 - r incrbyfloat novar 17179869184 - } {34359738368} - - test {INCRBYFLOAT fails against key with spaces (left)} { - set err {} - r set novar " 11" - catch {r incrbyfloat novar 1.0} err - format $err - } {ERR *valid*} - - test {INCRBYFLOAT fails against key with spaces (right)} { - set err {} - r set novar "11 " - catch {r incrbyfloat novar 1.0} err - format $err - } {ERR *valid*} - - test {INCRBYFLOAT fails against key with spaces (both)} { - set err {} - r set novar " 11 " - catch {r incrbyfloat novar 1.0} err - format $err - } {ERR *valid*} - - test {INCRBYFLOAT fails against a key holding a list} { - r del mylist - set err {} - r rpush mylist 1 - catch {r incrbyfloat mylist 1.0} err - r del mylist - format $err - } {WRONGTYPE*} - - # On some platforms strtold("+inf") with valgrind returns a non-inf result - if {!$::valgrind} { - test {INCRBYFLOAT does not allow NaN or Infinity} { - r set foo 0 - set err {} - catch {r incrbyfloat foo +inf} err - set err - # p.s. no way I can force NaN to test it from the API because - # there is no way to increment / decrement by infinity nor to - # perform divisions. - } {ERR *would produce*} - } - - test {INCRBYFLOAT decrement} { - r set foo 1 - roundFloat [r incrbyfloat foo -1.1] - } {-0.1} - - test {string to double with null terminator} { - r set foo 1 - r setrange foo 2 2 - catch {r incrbyfloat foo 1} err - format $err - } {ERR *valid*} - - test {No negative zero} { - r del foo - r incrbyfloat foo [expr double(1)/41] - r incrbyfloat foo [expr double(-1)/41] - r get foo - } {0} - - test {INCRBY INCRBYFLOAT DECRBY against unhappy path} { - r del mykeyincr - assert_error "*ERR wrong number of arguments*" {r incr mykeyincr v} - assert_error "*ERR wrong number of arguments*" {r decr mykeyincr v} - assert_error "*value is not an integer or out of range*" {r incrby mykeyincr v} - assert_error "*value is not an integer or out of range*" {r incrby mykeyincr 1.5} - assert_error "*value is not an integer or out of range*" {r decrby mykeyincr v} - assert_error "*value is not an integer or out of range*" {r decrby mykeyincr 1.5} - assert_error "*value is not a valid float*" {r incrbyfloat mykeyincr v} - } - - foreach cmd {"incr" "decr" "incrby" "decrby"} { - test "$cmd operation should update encoding from raw to int" { - set res {} - set expected {1 12} - if {[string match {*incr*} $cmd]} { - lappend expected 13 - } else { - lappend expected 11 - } - - r set foo 1 - assert_encoding "int" foo - lappend res [r get foo] - - r append foo 2 - assert_encoding "raw" foo - lappend res [r get foo] - - if {[string match {*by*} $cmd]} { - r $cmd foo 1 - } else { - r $cmd foo - } - assert_encoding "int" foo - lappend res [r get foo] - assert_equal $res $expected - } - } -} diff --git a/examples/redis-unstable/tests/unit/type/list-2.tcl b/examples/redis-unstable/tests/unit/type/list-2.tcl deleted file mode 100644 index ac3bec8..0000000 --- a/examples/redis-unstable/tests/unit/type/list-2.tcl +++ /dev/null @@ -1,47 +0,0 @@ -start_server { - tags {"list"} - overrides { - "list-max-ziplist-size" 4 - } -} { - array set largevalue [generate_largevalue_test_array] - - foreach {type large} [array get largevalue] { - tags {"slow"} { - test "LTRIM stress testing - $type" { - set mylist {} - set startlen 32 - r del mylist - - # Start with the large value to ensure the - # right encoding is used. - r rpush mylist $large - lappend mylist $large - - for {set i 0} {$i < $startlen} {incr i} { - set str [randomInt 9223372036854775807] - r rpush mylist $str - lappend mylist $str - } - - for {set i 0} {$i < 1000} {incr i} { - set min [expr {int(rand()*$startlen)}] - set max [expr {$min+int(rand()*$startlen)}] - set before_len [llength $mylist] - set before_len_r [r llen mylist] - assert_equal $before_len $before_len_r - set mylist [lrange $mylist $min $max] - r ltrim mylist $min $max - assert_equal $mylist [r lrange mylist 0 -1] "failed trim" - - for {set j [r llen mylist]} {$j < $startlen} {incr j} { - set str [randomInt 9223372036854775807] - r rpush mylist $str - lappend mylist $str - assert_equal $mylist [r lrange mylist 0 -1] "failed append match" - } - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/type/list-3.tcl b/examples/redis-unstable/tests/unit/type/list-3.tcl deleted file mode 100644 index 45df593..0000000 --- a/examples/redis-unstable/tests/unit/type/list-3.tcl +++ /dev/null @@ -1,232 +0,0 @@ -proc generate_cmd_on_list_key {key} { - set op [randomInt 7] - set small_signed_count [expr 5-[randomInt 10]] - if {[randomInt 2] == 0} { - set ele [randomInt 1000] - } else { - set ele [string repeat x [randomInt 10000]][randomInt 1000] - } - switch $op { - 0 {return "lpush $key $ele"} - 1 {return "rpush $key $ele"} - 2 {return "lpop $key"} - 3 {return "rpop $key"} - 4 { - return "lset $key $small_signed_count $ele" - } - 5 { - set otherele [randomInt 1000] - if {[randomInt 2] == 0} { - set where before - } else { - set where after - } - return "linsert $key $where $otherele $ele" - } - 6 { - set otherele "" - catch { - set index [randomInt [r llen $key]] - set otherele [r lindex $key $index] - } - return "lrem $key 1 $otherele" - } - } -} - -start_server { - tags {"list ziplist"} - overrides { - "list-max-ziplist-size" 16 - } -} { - test {Explicit regression for a list bug} { - set mylist {49376042582 {BkG2o\pIC]4YYJa9cJ4GWZalG[4tin;1D2whSkCOW`mX;SFXGyS8sedcff3fQI^tgPCC@^Nu1J6o]meM@Lko]t_jRyotK?tH[\EvWqS]b`o2OCtjg:?nUTwdjpcUm]y:pg5q24q7LlCOwQE^}} - r del l - r rpush l [lindex $mylist 0] - r rpush l [lindex $mylist 1] - assert_equal [r lindex l 0] [lindex $mylist 0] - assert_equal [r lindex l 1] [lindex $mylist 1] - } - - test {Regression for quicklist #3343 bug} { - r del mylist - r lpush mylist 401 - r lpush mylist 392 - r rpush mylist [string repeat x 5105]"799" - r lset mylist -1 [string repeat x 1014]"702" - r lpop mylist - r lset mylist -1 [string repeat x 4149]"852" - r linsert mylist before 401 [string repeat x 9927]"12" - r lrange mylist 0 -1 - r ping ; # It's enough if the server is still alive - } {PONG} - - test {Check compression with recompress} { - r del key - config_set list-compress-depth 1 - config_set list-max-ziplist-size 16 - r rpush key a - r rpush key [string repeat b 50000] - r rpush key c - r lset key 1 d - r rpop key - r rpush key [string repeat e 5000] - r linsert key before f 1 - r rpush key g - r ping - } - - test {Crash due to wrongly recompress after lrem} { - r del key - config_set list-compress-depth 2 - r lpush key a - r lpush key [string repeat a 5000] - r lpush key [string repeat b 5000] - r lpush key [string repeat c 5000] - r rpush key [string repeat x 10000]"969" - r rpush key b - r lrem key 1 a - r rpop key - r lrem key 1 [string repeat x 10000]"969" - r rpush key crash - r ping - } - - test {LINSERT correctly recompress full quicklistNode after inserting a element before it} { - r del key - config_set list-compress-depth 1 - r rpush key b - r rpush key c - r lset key -1 [string repeat x 8192]"969" - r lpush key a - r rpush key d - r linsert key before b f - r rpop key - r ping - } - - test {LINSERT correctly recompress full quicklistNode after inserting a element after it} { - r del key - config_set list-compress-depth 1 - r rpush key b - r rpush key c - r lset key 0 [string repeat x 8192]"969" - r lpush key a - r rpush key d - r linsert key after c f - r lpop key - r ping - } - -foreach comp {2 1 0} { - set cycles 1000 - if {$::accurate} { set cycles 10000 } - config_set list-compress-depth $comp - - test "Stress tester for #3343-alike bugs comp: $comp" { - r del key - set sent {} - for {set j 0} {$j < $cycles} {incr j} { - catch { - set cmd [generate_cmd_on_list_key key] - lappend sent $cmd - - # execute the command, we expect commands to fail on syntax errors - r {*}$cmd - } - } - - set print_commands false - set crash false - if {[catch {r ping}]} { - puts "Server crashed" - set print_commands true - set crash true - } - - if {!$::external} { - # check valgrind and asan report for invalid reads after execute - # command so that we have a report that is easier to reproduce - set valgrind_errors [find_valgrind_errors [srv 0 stderr] false] - set asan_errors [sanitizer_errors_from_file [srv 0 stderr]] - if {$valgrind_errors != "" || $asan_errors != ""} { - puts "valgrind or asan found an issue" - set print_commands true - } - } - - if {$print_commands} { - puts "violating commands:" - foreach cmd $sent { - puts $cmd - } - } - - assert_equal $crash false - } -} ;# foreach comp - - tags {slow} { - test {ziplist implementation: value encoding and backlink} { - if {$::accurate} {set iterations 100} else {set iterations 10} - for {set j 0} {$j < $iterations} {incr j} { - r del l - set l {} - for {set i 0} {$i < 200} {incr i} { - randpath { - set data [string repeat x [randomInt 100000]] - } { - set data [randomInt 65536] - } { - set data [randomInt 4294967296] - } { - set data [randomInt 18446744073709551616] - } { - set data -[randomInt 65536] - if {$data eq {-0}} {set data 0} - } { - set data -[randomInt 4294967296] - if {$data eq {-0}} {set data 0} - } { - set data -[randomInt 18446744073709551616] - if {$data eq {-0}} {set data 0} - } - lappend l $data - r rpush l $data - } - assert_equal [llength $l] [r llen l] - # Traverse backward - for {set i 199} {$i >= 0} {incr i -1} { - if {[lindex $l $i] ne [r lindex l $i]} { - assert_equal [lindex $l $i] [r lindex l $i] - } - } - } - } - - test {ziplist implementation: encoding stress testing} { - for {set j 0} {$j < 200} {incr j} { - r del l - set l {} - set len [randomInt 400] - for {set i 0} {$i < $len} {incr i} { - set rv [randomValue] - randpath { - lappend l $rv - r rpush l $rv - } { - set l [concat [list $rv] $l] - r lpush l $rv - } - } - assert_equal [llength $l] [r llen l] - for {set i 0} {$i < $len} {incr i} { - if {[lindex $l $i] ne [r lindex l $i]} { - assert_equal [lindex $l $i] [r lindex l $i] - } - } - } - } - } -} diff --git a/examples/redis-unstable/tests/unit/type/list.tcl b/examples/redis-unstable/tests/unit/type/list.tcl deleted file mode 100644 index 8d17c5c..0000000 --- a/examples/redis-unstable/tests/unit/type/list.tcl +++ /dev/null @@ -1,2500 +0,0 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -# check functionality compression of plain and packed nodes -start_server [list overrides [list save ""] ] { - r config set list-compress-depth 2 - r config set list-max-ziplist-size 1 - - # 3 test to check compression with plain and packed nodes - # 1. using push + insert - # 2. using push + insert + trim - # 3. using push + insert + set - - foreach {container size} {packed 500 plain 8193} { - test "$container node check compression with insert and pop" { - r flushdb - r lpush list1 [string repeat a $size] - r lpush list1 [string repeat b $size] - r lpush list1 [string repeat c $size] - r lpush list1 [string repeat d $size] - r linsert list1 after [string repeat d $size] [string repeat e $size] - r linsert list1 after [string repeat d $size] [string repeat f $size] - r linsert list1 after [string repeat d $size] [string repeat g $size] - r linsert list1 after [string repeat d $size] [string repeat j $size] - assert_equal [r lpop list1] [string repeat d $size] - assert_equal [r lpop list1] [string repeat j $size] - assert_equal [r lpop list1] [string repeat g $size] - assert_equal [r lpop list1] [string repeat f $size] - assert_equal [r lpop list1] [string repeat e $size] - assert_equal [r lpop list1] [string repeat c $size] - assert_equal [r lpop list1] [string repeat b $size] - assert_equal [r lpop list1] [string repeat a $size] - }; - - test "$container node check compression combined with trim" { - r flushdb - r lpush list2 [string repeat a $size] - r linsert list2 after [string repeat a $size] [string repeat b $size] - r rpush list2 [string repeat c $size] - assert_equal [string repeat b $size] [r lindex list2 1] - r LTRIM list2 1 -1 - r llen list2 - } {2} - - test "$container node check compression with lset" { - r flushdb - r lpush list3 [string repeat a $size] - r LSET list3 0 [string repeat b $size] - assert_equal [string repeat b $size] [r lindex list3 0] - r lpush list3 [string repeat c $size] - r LSET list3 0 [string repeat d $size] - assert_equal [string repeat d $size] [r lindex list3 0] - } - } ;# foreach - - # revert config for external mode tests. - r config set list-compress-depth 0 -} - -# check functionality of plain nodes using low packed-threshold -start_server [list overrides [list save ""] ] { -foreach type {listpack quicklist} { - if {$type eq "listpack"} { - r config set list-max-listpack-size -2 - } else { - r config set list-max-listpack-size 1 - } - - # basic command check for plain nodes - "LPUSH & LPOP" - test {Test LPUSH and LPOP on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst 9 - r lpush lst xxxxxxxxxx - r lpush lst xxxxxxxxxx - assert_encoding $type lst - set s0 [s used_memory] - assert {$s0 > 10} - assert {[r llen lst] == 3} - set s0 [r rpop lst] - set s1 [r rpop lst] - assert {$s0 eq "9"} - assert {[r llen lst] == 1} - r lpop lst - assert {[string length $s1] == 10} - # check rdb - r lpush lst xxxxxxxxxx - r lpush lst bb - r debug reload - assert_equal [r rpop lst] "xxxxxxxxxx" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LINDEX & LINSERT" - test {Test LINDEX and LINSERT on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst xxxxxxxxxxx - r lpush lst 9 - r lpush lst xxxxxxxxxxx - assert_encoding $type lst - r linsert lst before "9" "8" - assert {[r lindex lst 1] eq "8"} - r linsert lst BEFORE "9" "7" - r linsert lst BEFORE "9" "xxxxxxxxxxx" - assert {[r lindex lst 3] eq "xxxxxxxxxxx"} - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LTRIM" - test {Test LTRIM on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst1 9 - r lpush lst1 xxxxxxxxxxx - r lpush lst1 9 - assert_encoding $type lst1 - r LTRIM lst1 1 -1 - assert_equal [r llen lst1] 2 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LREM" - test {Test LREM on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst one - r lpush lst xxxxxxxxxxx - assert_encoding $type lst - set s0 [s used_memory] - assert {$s0 > 10} - r lpush lst 9 - r LREM lst -2 "one" - assert_equal [r llen lst] 2 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LPOS" - test {Test LPOS on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r RPUSH lst "aa" - r RPUSH lst "bb" - r RPUSH lst "cc" - assert_encoding $type lst - r LSET lst 0 "xxxxxxxxxxx" - assert_equal [r LPOS lst "xxxxxxxxxxx"] 0 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LMOVE" - test {Test LMOVE on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r RPUSH lst2{t} "aa" - r RPUSH lst2{t} "bb" - assert_encoding $type lst2{t} - r LSET lst2{t} 0 xxxxxxxxxxx - r RPUSH lst2{t} "cc" - r RPUSH lst2{t} "dd" - r LMOVE lst2{t} lst{t} RIGHT LEFT - r LMOVE lst2{t} lst{t} LEFT RIGHT - assert_equal [r llen lst{t}] 2 - assert_equal [r llen lst2{t}] 2 - assert_equal [r lpop lst2{t}] "bb" - assert_equal [r lpop lst2{t}] "cc" - assert_equal [r lpop lst{t}] "dd" - assert_equal [r lpop lst{t}] "xxxxxxxxxxx" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # testing LSET with combinations of node types - # plain->packed , packed->plain, plain->plain, packed->packed - test {Test LSET with packed / plain combinations} { - r debug quicklist-packed-threshold 5b - r RPUSH lst "aa" - r RPUSH lst "bb" - assert_encoding $type lst - r lset lst 0 [string repeat d 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat d 50001] - r RPUSH lst [string repeat f 50001] - r lset lst 0 [string repeat e 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat e 50001] - r RPUSH lst [string repeat m 50001] - r lset lst 0 "bb" - set s1 [r lpop lst] - assert_equal $s1 "bb" - r RPUSH lst "bb" - r lset lst 0 "cc" - set s1 [r lpop lst] - assert_equal $s1 "cc" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # checking LSET in case ziplist needs to be split - test {Test LSET with packed is split in the middle} { - set original_config [config_get_set list-max-listpack-size 4] - r flushdb - r debug quicklist-packed-threshold 5b - r RPUSH lst "aa" - r RPUSH lst "bb" - r RPUSH lst "cc" - r RPUSH lst "dd" - r RPUSH lst "ee" - assert_encoding quicklist lst - r lset lst 2 [string repeat e 10] - assert_equal [r lpop lst] "aa" - assert_equal [r lpop lst] "bb" - assert_equal [r lpop lst] [string repeat e 10] - assert_equal [r lpop lst] "dd" - assert_equal [r lpop lst] "ee" - r debug quicklist-packed-threshold 0 - r config set list-max-listpack-size $original_config - } {OK} {needs:debug} - - - # repeating "plain check LSET with combinations" - # but now with single item in each ziplist - test {Test LSET with packed consist only one item} { - r flushdb - set original_config [config_get_set list-max-ziplist-size 1] - r debug quicklist-packed-threshold 1b - r RPUSH lst "aa" - r RPUSH lst "bb" - r lset lst 0 [string repeat d 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat d 50001] - r RPUSH lst [string repeat f 50001] - r lset lst 0 [string repeat e 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat e 50001] - r RPUSH lst [string repeat m 50001] - r lset lst 0 "bb" - set s1 [r lpop lst] - assert_equal $s1 "bb" - r RPUSH lst "bb" - r lset lst 0 "cc" - set s1 [r lpop lst] - assert_equal $s1 "cc" - r debug quicklist-packed-threshold 0 - r config set list-max-ziplist-size $original_config - } {OK} {needs:debug} - - test {Crash due to delete entry from a compress quicklist node} { - r flushdb - r debug quicklist-packed-threshold 100b - set original_config [config_get_set list-compress-depth 1] - - set small_ele [string repeat x 32] - set large_ele [string repeat x 100] - - # Push a large element - r RPUSH lst $large_ele - - # Insert two elements and keep them in the same node - r RPUSH lst $small_ele - r RPUSH lst $small_ele - assert_encoding $type lst - - # When setting the position of -1 to a large element, we first insert - # a large element at the end and then delete its previous element. - r LSET lst -1 $large_ele - assert_equal "$large_ele $small_ele $large_ele" [r LRANGE lst 0 -1] - - r debug quicklist-packed-threshold 0 - r config set list-compress-depth $original_config - } {OK} {needs:debug} - - test {Crash due to split quicklist node wrongly} { - r flushdb - r debug quicklist-packed-threshold 10b - - r LPUSH lst "aa" - r LPUSH lst "bb" - assert_encoding $type lst - r LSET lst -2 [string repeat x 10] - r RPOP lst - assert_equal [string repeat x 10] [r LRANGE lst 0 -1] - - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} -} -} - -run_solo {list-large-memory} { -start_server [list overrides [list save ""] ] { - -# test if the server supports such large configs (avoid 32 bit builds) -catch { - r config set proto-max-bulk-len 10000000000 ;#10gb - r config set client-query-buffer-limit 10000000000 ;#10gb -} -if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { - - set str_length 5000000000 - - # repeating all the plain nodes basic checks with 5gb values - test {Test LPUSH and LPOP on plain nodes over 4GB} { - r flushdb - r lpush lst 9 - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - set s0 [s used_memory] - assert {$s0 > $str_length} - assert {[r llen lst] == 3} - assert_equal [r rpop lst] "9" - assert_equal [read_big_bulk {r rpop lst}] $str_length - assert {[r llen lst] == 1} - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LINDEX and LINSERT on plain nodes over 4GB} { - r flushdb - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r lpush lst 9 - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r linsert lst before "9" "8" - assert_equal [r lindex lst 1] "8" - r LINSERT lst BEFORE "9" "7" - r write "*5\r\n\$7\r\nLINSERT\r\n\$3\r\nlst\r\n\$6\r\nBEFORE\r\n\$3\r\n\"9\"\r\n" - write_big_bulk 10; - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LTRIM on plain nodes over 4GB} { - r flushdb - r lpush lst 9 - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r lpush lst 9 - r LTRIM lst 1 -1 - assert_equal [r llen lst] 2 - assert_equal [r rpop lst] 9 - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LREM on plain nodes over 4GB} { - r flushdb - r lpush lst one - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r lpush lst 9 - r LREM lst -2 "one" - assert_equal [read_big_bulk {r rpop lst}] $str_length - r llen lst - } {1} {large-memory} - - test {Test LSET on plain nodes over 4GB} { - r flushdb - r RPUSH lst "aa" - r RPUSH lst "bb" - r RPUSH lst "cc" - r write "*4\r\n\$4\r\nLSET\r\n\$3\r\nlst\r\n\$1\r\n0\r\n" - write_big_bulk $str_length; - assert_equal [r rpop lst] "cc" - assert_equal [r rpop lst] "bb" - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LSET on plain nodes with large elements under packed_threshold over 4GB} { - r flushdb - r rpush lst a b c d e - for {set i 0} {$i < 5} {incr i} { - r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" - write_big_bulk 1000000000 - } - r ping - } {PONG} {large-memory} - - test {Test LSET splits a quicklist node, and then merge} { - # Test when a quicklist node can't be inserted and is split, the split - # node merges with the node before it and the `before` node is kept. - r flushdb - r rpush lst [string repeat "x" 4096] - r lpush lst a b c d e f g - r lpush lst [string repeat "y" 4096] - # now: [y...] [g f e d c b a x...] - # (node0) (node1) - # Keep inserting elements into node1 until node1 is split into two - # nodes([g] [...]), eventually node0 will merge with the [g] node. - # Since node0 is larger, after the merge node0 will be kept and - # the [g] node will be deleted. - for {set i 7} {$i >= 3} {incr i -1} { - r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" - write_big_bulk 1000000000 - } - assert_equal "g" [r lindex lst 1] - r ping - } {PONG} {large-memory} - - test {Test LSET splits a LZF compressed quicklist node, and then merge} { - # Test when a LZF compressed quicklist node can't be inserted and is split, - # the split node merges with the node before it and the split node is kept. - r flushdb - r config set list-compress-depth 1 - r lpush lst [string repeat "x" 2000] - r rpush lst [string repeat "y" 7000] - r rpush lst a b c d e f g - r rpush lst [string repeat "z" 8000] - r lset lst 0 h - # now: [h] [y... a b c d e f g] [z...] - # node0 node1(LZF) - # Keep inserting elements into node1 until node1 is split into two - # nodes([y...] [...]), eventually node0 will merge with the [y...] node. - # Since [y...] node is larger, after the merge node0 will be deleted and - # the [y...] node will be kept. - for {set i 7} {$i >= 3} {incr i -1} { - r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" - write_big_bulk 1000000000 - } - assert_equal "h" [r lindex lst 0] - r config set list-compress-depth 0 - r ping - } {PONG} {large-memory} - - test {Test LMOVE on plain nodes over 4GB} { - r flushdb - r RPUSH lst2{t} "aa" - r RPUSH lst2{t} "bb" - r write "*4\r\n\$4\r\nLSET\r\n\$7\r\nlst2{t}\r\n\$1\r\n0\r\n" - write_big_bulk $str_length; - r RPUSH lst2{t} "cc" - r RPUSH lst2{t} "dd" - r LMOVE lst2{t} lst{t} RIGHT LEFT - assert_equal [read_big_bulk {r LMOVE lst2{t} lst{t} LEFT RIGHT}] $str_length - assert_equal [r llen lst{t}] 2 - assert_equal [r llen lst2{t}] 2 - assert_equal [r lpop lst2{t}] "bb" - assert_equal [r lpop lst2{t}] "cc" - assert_equal [r lpop lst{t}] "dd" - assert_equal [read_big_bulk {r rpop lst{t}}] $str_length - } {} {large-memory} - - # restore defaults - r config set proto-max-bulk-len 536870912 - r config set client-query-buffer-limit 1073741824 - -} ;# skip 32bit builds -} -} ;# run_solo - -start_server { - tags {"list"} - overrides { - "list-max-ziplist-size" -1 - } -} { - array set largevalue [generate_largevalue_test_array] - - # A helper function to execute either B*POP or BLMPOP* with one input key. - proc bpop_command {rd pop key timeout} { - if {$pop == "BLMPOP_LEFT"} { - $rd blmpop $timeout 1 $key left count 1 - } elseif {$pop == "BLMPOP_RIGHT"} { - $rd blmpop $timeout 1 $key right count 1 - } else { - $rd $pop $key $timeout - } - } - - # A helper function to execute either B*POP or BLMPOP* with two input keys. - proc bpop_command_two_key {rd pop key key2 timeout} { - if {$pop == "BLMPOP_LEFT"} { - $rd blmpop $timeout 2 $key $key2 left count 1 - } elseif {$pop == "BLMPOP_RIGHT"} { - $rd blmpop $timeout 2 $key $key2 right count 1 - } else { - $rd $pop $key $key2 $timeout - } - } - - proc create_listpack {key entries} { - r del $key - foreach entry $entries { r rpush $key $entry } - assert_encoding listpack $key - } - - proc create_quicklist {key entries} { - r del $key - foreach entry $entries { r rpush $key $entry } - assert_encoding quicklist $key - } - -foreach {type large} [array get largevalue] { - test "LPOS basic usage - $type" { - r DEL mylist - r RPUSH mylist a b c $large 2 3 c c - assert {[r LPOS mylist a] == 0} - assert {[r LPOS mylist c] == 2} - } - - test {LPOS RANK (positive, negative and zero rank) option} { - assert {[r LPOS mylist c RANK 1] == 2} - assert {[r LPOS mylist c RANK 2] == 6} - assert {[r LPOS mylist c RANK 4] eq ""} - assert {[r LPOS mylist c RANK -1] == 7} - assert {[r LPOS mylist c RANK -2] == 6} - assert_error "*RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start*" {r LPOS mylist c RANK 0} - assert_error "*value is out of range*" {r LPOS mylist c RANK -9223372036854775808} - } - - test {LPOS COUNT option} { - assert {[r LPOS mylist c COUNT 0] == {2 6 7}} - assert {[r LPOS mylist c COUNT 1] == {2}} - assert {[r LPOS mylist c COUNT 2] == {2 6}} - assert {[r LPOS mylist c COUNT 100] == {2 6 7}} - } - - test {LPOS COUNT + RANK option} { - assert {[r LPOS mylist c COUNT 0 RANK 2] == {6 7}} - assert {[r LPOS mylist c COUNT 2 RANK -1] == {7 6}} - } - - test {LPOS non existing key} { - assert {[r LPOS mylistxxx c COUNT 0 RANK 2] eq {}} - } - - test {LPOS no match} { - assert {[r LPOS mylist x COUNT 2 RANK -1] eq {}} - assert {[r LPOS mylist x RANK -1] eq {}} - } - - test {LPOS MAXLEN} { - assert {[r LPOS mylist a COUNT 0 MAXLEN 1] == {0}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 1] == {}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 3] == {2}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 3 RANK -1] == {7 6}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 7 RANK 2] == {6}} - } - - test {LPOS when RANK is greater than matches} { - r DEL mylist - r LPUSH mylist a - assert {[r LPOS mylist b COUNT 10 RANK 5] eq {}} - } - - test "LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - $type" { - # first lpush then rpush - r del mylist1 - assert_equal 1 [r lpush mylist1 $large] - assert_encoding $type mylist1 - assert_equal 2 [r rpush mylist1 b] - assert_equal 3 [r rpush mylist1 c] - assert_equal 3 [r llen mylist1] - assert_equal $large [r lindex mylist1 0] - assert_equal b [r lindex mylist1 1] - assert_equal c [r lindex mylist1 2] - assert_equal {} [r lindex mylist1 3] - assert_equal c [r rpop mylist1] - assert_equal $large [r lpop mylist1] - - # first rpush then lpush - r del mylist2 - assert_equal 1 [r rpush mylist2 $large] - assert_equal 2 [r lpush mylist2 b] - assert_equal 3 [r lpush mylist2 c] - assert_encoding $type mylist2 - assert_equal 3 [r llen mylist2] - assert_equal c [r lindex mylist2 0] - assert_equal b [r lindex mylist2 1] - assert_equal $large [r lindex mylist2 2] - assert_equal {} [r lindex mylist2 3] - assert_equal $large [r rpop mylist2] - assert_equal c [r lpop mylist2] - } - - test "LPOP/RPOP with wrong number of arguments" { - assert_error {*wrong number of arguments for 'lpop' command} {r lpop key 1 1} - assert_error {*wrong number of arguments for 'rpop' command} {r rpop key 2 2} - } - - test "RPOP/LPOP with the optional count argument - $type" { - assert_equal 7 [r lpush listcount aa $large cc dd ee ff gg] - assert_equal {gg} [r lpop listcount 1] - assert_equal {ff ee} [r lpop listcount 2] - assert_equal "aa $large" [r rpop listcount 2] - assert_equal {cc} [r rpop listcount 1] - assert_equal {dd} [r rpop listcount 123] - assert_error "*ERR*range*" {r lpop forbarqaz -123} - } -} - - proc verify_resp_response {resp response resp2_response resp3_response} { - if {$resp == 2} { - assert_equal $response $resp2_response - } elseif {$resp == 3} { - assert_equal $response $resp3_response - } - } - - foreach resp {3 2} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$resp == 3} {continue} - } elseif {$::force_resp3} { - if {$resp == 2} {continue} - } - r hello $resp - - # Make sure we can distinguish between an empty array and a null response - r readraw 1 - - test "LPOP/RPOP with the count 0 returns an empty array in RESP$resp" { - r lpush listcount zero - assert_equal {*0} [r lpop listcount 0] - assert_equal {*0} [r rpop listcount 0] - } - - test "LPOP/RPOP against non existing key in RESP$resp" { - r del non_existing_key - - verify_resp_response $resp [r lpop non_existing_key] {$-1} {_} - verify_resp_response $resp [r rpop non_existing_key] {$-1} {_} - } - - test "LPOP/RPOP with against non existing key in RESP$resp" { - r del non_existing_key - - verify_resp_response $resp [r lpop non_existing_key 0] {*-1} {_} - verify_resp_response $resp [r lpop non_existing_key 1] {*-1} {_} - - verify_resp_response $resp [r rpop non_existing_key 0] {*-1} {_} - verify_resp_response $resp [r rpop non_existing_key 1] {*-1} {_} - } - - r readraw 0 - r hello 2 - } - - test {Variadic RPUSH/LPUSH} { - r del mylist - assert_equal 4 [r lpush mylist a b c d] - assert_equal 8 [r rpush mylist 0 1 2 3] - assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1] - } - - test {DEL a list} { - assert_equal 1 [r del mylist2] - assert_equal 0 [r exists mylist2] - assert_equal 0 [r llen mylist2] - } - - foreach {type large} [array get largevalue] { - foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop: single existing list - $type" { - set rd [redis_deferring_client] - create_$type blist "a b $large c d" - - bpop_command $rd $pop blist 1 - assert_equal {blist a} [$rd read] - if {$pop == "BLPOP"} { - bpop_command $rd BRPOP blist 1 - } else { - bpop_command $rd BLMPOP_RIGHT blist 1 - } - assert_equal {blist d} [$rd read] - - bpop_command $rd $pop blist 1 - assert_equal {blist b} [$rd read] - if {$pop == "BLPOP"} { - bpop_command $rd BRPOP blist 1 - } else { - bpop_command $rd BLMPOP_RIGHT blist 1 - } - assert_equal {blist c} [$rd read] - - assert_equal 1 [r llen blist] - $rd close - } - - test "$pop: multiple existing lists - $type" { - set rd [redis_deferring_client] - create_$type blist1{t} "a $large c" - create_$type blist2{t} "d $large f" - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - assert_equal {blist1{t} a} [$rd read] - if {$pop == "BLPOP"} { - bpop_command_two_key $rd BRPOP blist1{t} blist2{t} 1 - } else { - bpop_command_two_key $rd BLMPOP_RIGHT blist1{t} blist2{t} 1 - } - assert_equal {blist1{t} c} [$rd read] - assert_equal 1 [r llen blist1{t}] - assert_equal 3 [r llen blist2{t}] - - bpop_command_two_key $rd $pop blist2{t} blist1{t} 1 - assert_equal {blist2{t} d} [$rd read] - if {$pop == "BLPOP"} { - bpop_command_two_key $rd BRPOP blist2{t} blist1{t} 1 - } else { - bpop_command_two_key $rd BLMPOP_RIGHT blist2{t} blist1{t} 1 - } - assert_equal {blist2{t} f} [$rd read] - assert_equal 1 [r llen blist1{t}] - assert_equal 1 [r llen blist2{t}] - $rd close - } - - test "$pop: second list has an entry - $type" { - set rd [redis_deferring_client] - r del blist1{t} - create_$type blist2{t} "d $large f" - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - assert_equal {blist2{t} d} [$rd read] - if {$pop == "BLPOP"} { - bpop_command_two_key $rd BRPOP blist1{t} blist2{t} 1 - } else { - bpop_command_two_key $rd BLMPOP_RIGHT blist1{t} blist2{t} 1 - } - assert_equal {blist2{t} f} [$rd read] - assert_equal 0 [r llen blist1{t}] - assert_equal 1 [r llen blist2{t}] - $rd close - } - } - - test "BRPOPLPUSH - $type" { - r del target{t} - r rpush target{t} bar - - set rd [redis_deferring_client] - create_$type blist{t} "a b $large c d" - - $rd brpoplpush blist{t} target{t} 1 - assert_equal d [$rd read] - - assert_equal d [r lpop target{t}] - assert_equal "a b $large c" [r lrange blist{t} 0 -1] - $rd close - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "BLMOVE $wherefrom $whereto - $type" { - r del target{t} - r rpush target{t} bar - - set rd [redis_deferring_client] - create_$type blist{t} "a b $large c d" - - $rd blmove blist{t} target{t} $wherefrom $whereto 1 - set poppedelement [$rd read] - - if {$wherefrom eq "right"} { - assert_equal d $poppedelement - assert_equal "a b $large c" [r lrange blist{t} 0 -1] - } else { - assert_equal a $poppedelement - assert_equal "b $large c d" [r lrange blist{t} 0 -1] - } - - if {$whereto eq "right"} { - assert_equal $poppedelement [r rpop target{t}] - } else { - assert_equal $poppedelement [r lpop target{t}] - } - $rd close - } - } - } - } - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop, LPUSH + DEL should not awake blocked client" { - set rd [redis_deferring_client] - r del list - - bpop_command $rd $pop list 0 - wait_for_blocked_client - - r multi - r lpush list a - r del list - r exec - r del list - r lpush list b - assert_equal {list b} [$rd read] - $rd close - } - - test "$pop, LPUSH + DEL + SET should not awake blocked client" { - set rd [redis_deferring_client] - r del list - - bpop_command $rd $pop list 0 - wait_for_blocked_client - - r multi - r lpush list a - r del list - r set list foo - r exec - r del list - r lpush list b - assert_equal {list b} [$rd read] - $rd close - } -} - - test "BLPOP with same key multiple times should work (issue #801)" { - set rd [redis_deferring_client] - r del list1{t} list2{t} - - # Data arriving after the BLPOP. - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - wait_for_blocked_client - r lpush list1{t} a - assert_equal [$rd read] {list1{t} a} - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - wait_for_blocked_client - r lpush list2{t} b - assert_equal [$rd read] {list2{t} b} - - # Data already there. - r lpush list1{t} a - r lpush list2{t} b - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - assert_equal [$rd read] {list1{t} a} - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - assert_equal [$rd read] {list2{t} b} - $rd close - } - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "MULTI/EXEC is isolated from the point of view of $pop" { - set rd [redis_deferring_client] - r del list - - bpop_command $rd $pop list 0 - wait_for_blocked_client - - r multi - r lpush list a - r lpush list b - r lpush list c - r exec - assert_equal {list c} [$rd read] - $rd close - } - - test "$pop with variadic LPUSH" { - set rd [redis_deferring_client] - r del blist - bpop_command $rd $pop blist 0 - wait_for_blocked_client - assert_equal 2 [r lpush blist foo bar] - assert_equal {blist bar} [$rd read] - assert_equal foo [lindex [r lrange blist 0 -1] 0] - $rd close - } -} - - test "BRPOPLPUSH with zero timeout should block indefinitely" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r rpush target{t} bar - $rd brpoplpush blist{t} target{t} 0 - wait_for_blocked_clients_count 1 - r rpush blist{t} foo - assert_equal foo [$rd read] - assert_equal {foo bar} [r lrange target{t} 0 -1] - $rd close - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "BLMOVE $wherefrom $whereto with zero timeout should block indefinitely" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r rpush target{t} bar - $rd blmove blist{t} target{t} $wherefrom $whereto 0 - wait_for_blocked_clients_count 1 - r rpush blist{t} foo - assert_equal foo [$rd read] - if {$whereto eq "right"} { - assert_equal {bar foo} [r lrange target{t} 0 -1] - } else { - assert_equal {foo bar} [r lrange target{t} 0 -1] - } - $rd close - } - } - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "BLMOVE ($wherefrom, $whereto) with a client BLPOPing the target list" { - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - r del blist{t} target{t} - $rd2 blpop target{t} 0 - wait_for_blocked_clients_count 1 - $rd blmove blist{t} target{t} $wherefrom $whereto 0 - wait_for_blocked_clients_count 2 - r rpush blist{t} foo - assert_equal foo [$rd read] - assert_equal {target{t} foo} [$rd2 read] - assert_equal 0 [r exists target{t}] - $rd close - $rd2 close - } - } - } - - test "BRPOPLPUSH with wrong source type" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r set blist{t} nolist - $rd brpoplpush blist{t} target{t} 1 - assert_error "WRONGTYPE*" {$rd read} - $rd close - } - - test "BRPOPLPUSH with wrong destination type" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r set target{t} nolist - r lpush blist{t} foo - $rd brpoplpush blist{t} target{t} 1 - assert_error "WRONGTYPE*" {$rd read} - $rd close - - set rd [redis_deferring_client] - r del blist{t} target{t} - r set target{t} nolist - $rd brpoplpush blist{t} target{t} 0 - wait_for_blocked_clients_count 1 - r rpush blist{t} foo - assert_error "WRONGTYPE*" {$rd read} - assert_equal {foo} [r lrange blist{t} 0 -1] - $rd close - } - - test "BRPOPLPUSH maintains order of elements after failure" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r set target{t} nolist - $rd brpoplpush blist{t} target{t} 0 - wait_for_blocked_client - r rpush blist{t} a b c - assert_error "WRONGTYPE*" {$rd read} - $rd close - r lrange blist{t} 0 -1 - } {a b c} - - test "BRPOPLPUSH with multiple blocked clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - r del blist{t} target1{t} target2{t} - r set target1{t} nolist - $rd1 brpoplpush blist{t} target1{t} 0 - wait_for_blocked_clients_count 1 - $rd2 brpoplpush blist{t} target2{t} 0 - wait_for_blocked_clients_count 2 - r lpush blist{t} foo - - assert_error "WRONGTYPE*" {$rd1 read} - assert_equal {foo} [$rd2 read] - assert_equal {foo} [r lrange target2{t} 0 -1] - $rd1 close - $rd2 close - } - - test "BLMPOP with multiple blocked clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - set rd4 [redis_deferring_client] - r del blist{t} blist2{t} - - $rd1 blmpop 0 2 blist{t} blist2{t} left count 1 - wait_for_blocked_clients_count 1 - $rd2 blmpop 0 2 blist{t} blist2{t} right count 10 - wait_for_blocked_clients_count 2 - $rd3 blmpop 0 2 blist{t} blist2{t} left count 10 - wait_for_blocked_clients_count 3 - $rd4 blmpop 0 2 blist{t} blist2{t} right count 1 - wait_for_blocked_clients_count 4 - - r multi - r lpush blist{t} a b c d e - r lpush blist2{t} 1 2 3 4 5 - r exec - - assert_equal {blist{t} e} [$rd1 read] - assert_equal {blist{t} {a b c d}} [$rd2 read] - assert_equal {blist2{t} {5 4 3 2 1}} [$rd3 read] - - r lpush blist2{t} 1 2 3 - assert_equal {blist2{t} 1} [$rd4 read] - $rd1 close - $rd2 close - $rd3 close - $rd4 close - } - - test "Linked LMOVEs" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - r del list1{t} list2{t} list3{t} - - $rd1 blmove list1{t} list2{t} right left 0 - wait_for_blocked_clients_count 1 - $rd2 blmove list2{t} list3{t} left right 0 - wait_for_blocked_clients_count 2 - - r rpush list1{t} foo - - assert_equal {} [r lrange list1{t} 0 -1] - assert_equal {} [r lrange list2{t} 0 -1] - assert_equal {foo} [r lrange list3{t} 0 -1] - $rd1 close - $rd2 close - } - - test "Circular BRPOPLPUSH" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - r del list1{t} list2{t} - - $rd1 brpoplpush list1{t} list2{t} 0 - wait_for_blocked_clients_count 1 - $rd2 brpoplpush list2{t} list1{t} 0 - wait_for_blocked_clients_count 2 - - r rpush list1{t} foo - - assert_equal {foo} [r lrange list1{t} 0 -1] - assert_equal {} [r lrange list2{t} 0 -1] - $rd1 close - $rd2 close - } - - test "Self-referential BRPOPLPUSH" { - set rd [redis_deferring_client] - - r del blist{t} - - $rd brpoplpush blist{t} blist{t} 0 - wait_for_blocked_client - - r rpush blist{t} foo - - assert_equal {foo} [r lrange blist{t} 0 -1] - $rd close - } - - test "BRPOPLPUSH inside a transaction" { - r del xlist{t} target{t} - r lpush xlist{t} foo - r lpush xlist{t} bar - - r multi - r brpoplpush xlist{t} target{t} 0 - r brpoplpush xlist{t} target{t} 0 - r brpoplpush xlist{t} target{t} 0 - r lrange xlist{t} 0 -1 - r lrange target{t} 0 -1 - r exec - } {foo bar {} {} {bar foo}} - - test "PUSH resulting from BRPOPLPUSH affect WATCH" { - set blocked_client [redis_deferring_client] - set watching_client [redis_deferring_client] - r del srclist{t} dstlist{t} somekey{t} - r set somekey{t} somevalue - $blocked_client brpoplpush srclist{t} dstlist{t} 0 - wait_for_blocked_client - $watching_client watch dstlist{t} - $watching_client read - $watching_client multi - $watching_client read - $watching_client get somekey{t} - $watching_client read - r lpush srclist{t} element - $watching_client exec - set res [$watching_client read] - $blocked_client close - $watching_client close - set _ $res - } {} - - test "BRPOPLPUSH does not affect WATCH while still blocked" { - set blocked_client [redis_deferring_client] - set watching_client [redis_deferring_client] - r del srclist{t} dstlist{t} somekey{t} - r set somekey{t} somevalue - $blocked_client brpoplpush srclist{t} dstlist{t} 0 - wait_for_blocked_client - $watching_client watch dstlist{t} - $watching_client read - $watching_client multi - $watching_client read - $watching_client get somekey{t} - $watching_client read - $watching_client exec - wait_for_condition 100 10 { - [regexp {cmd=exec} [r client list]] eq 1 - } else { - fail "exec did not arrive" - } - # Blocked BLPOPLPUSH may create problems, unblock it. - r lpush srclist{t} element - set res [$watching_client read] - $blocked_client close - $watching_client close - set _ $res - } {somevalue} - - test {BRPOPLPUSH timeout} { - set rd [redis_deferring_client] - - $rd brpoplpush foo_list{t} bar_list{t} 1 - wait_for_blocked_clients_count 1 - wait_for_blocked_clients_count 0 500 10 - set res [$rd read] - $rd close - set _ $res - } {} - - test {SWAPDB awakes blocked client} { - r flushall - r select 1 - r rpush k hello - r select 9 - set rd [redis_deferring_client] - $rd brpop k 5 - wait_for_blocked_clients_count 1 - r swapdb 1 9 - $rd read - } {k hello} {singledb:skip} - - test {SWAPDB wants to wake blocked client, but the key already expired} { - set repl [attach_to_replication_stream] - r flushall - r debug set-active-expire 0 - r select 1 - r rpush k hello - r pexpire k 100 - set rd [redis_deferring_client] - $rd deferred 0 - $rd select 9 - set id [$rd client id] - $rd deferred 1 - $rd brpop k 1 - wait_for_blocked_clients_count 1 - after 101 - r swapdb 1 9 - # The SWAPDB command tries to awake the blocked client, but it remains - # blocked because the key is expired. Check that the deferred client is - # still blocked. Then unblock it. - assert_match "*flags=b*" [r client list id $id] - r client unblock $id - assert_equal {} [$rd read] - $rd deferred 0 - # We want to force key deletion to be propagated to the replica - # in order to verify it was expired on the replication stream. - $rd set somekey1 someval1 - $rd exists k - r set somekey2 someval2 - - assert_replication_stream $repl { - {select *} - {flushall} - {select 1} - {rpush k hello} - {pexpireat k *} - {swapdb 1 9} - {select 9} - {set somekey1 someval1} - {del k} - {select 1} - {set somekey2 someval2} - } - close_replication_stream $repl - r debug set-active-expire 1 - # Restore server and client state - r select 9 - } {OK} {singledb:skip needs:debug} - - test {MULTI + LPUSH + EXPIRE + DEBUG SLEEP on blocked client, key already expired} { - set repl [attach_to_replication_stream] - r flushall - r debug set-active-expire 0 - - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - $rd brpop k 0 - wait_for_blocked_clients_count 1 - - r multi - r rpush k hello - r pexpire k 100 - r debug sleep 0.2 - r exec - - # The EXEC command tries to awake the blocked client, but it remains - # blocked because the key is expired. Check that the deferred client is - # still blocked. Then unblock it. - assert_match "*flags=b*" [r client list id $id] - r client unblock $id - assert_equal {} [$rd read] - # We want to force key deletion to be propagated to the replica - # in order to verify it was expired on the replication stream. - $rd exists k - assert_equal {0} [$rd read] - assert_replication_stream $repl { - {select *} - {flushall} - {multi} - {rpush k hello} - {pexpireat k *} - {exec} - {del k} - } - close_replication_stream $repl - # Restore server and client state - r debug set-active-expire 1 - r select 9 - } {OK} {singledb:skip needs:debug} - - test {BLPOP unblock but the key is expired and then block again - reprocessing command} { - r flushall - r debug set-active-expire 0 - set rd [redis_deferring_client] - - set start [clock milliseconds] - $rd blpop mylist 1 - wait_for_blocked_clients_count 1 - - # The exec will try to awake the blocked client, but the key is expired, - # so the client will be blocked again during the command reprocessing. - r multi - r rpush mylist a - r pexpire mylist 100 - r debug sleep 0.2 - r exec - - assert_equal {} [$rd read] - set end [clock milliseconds] - - # Before the fix in #13004, this time would have been 1200+ (i.e. more than 1200ms), - # now it should be 1000, but in order to avoid timing issues, we increase the range a bit. - assert_range [expr $end-$start] 1000 1150 - - r debug set-active-expire 1 - $rd close - } {0} {needs:debug} - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop when new key is moved into place" { - set rd [redis_deferring_client] - r del foo{t} - - bpop_command $rd $pop foo{t} 0 - wait_for_blocked_client - r lpush bob{t} abc def hij - r rename bob{t} foo{t} - set res [$rd read] - $rd close - set _ $res - } {foo{t} hij} - - test "$pop when result key is created by SORT..STORE" { - set rd [redis_deferring_client] - - # zero out list from previous test without explicit delete - r lpop foo{t} - r lpop foo{t} - r lpop foo{t} - - bpop_command $rd $pop foo{t} 5 - wait_for_blocked_client - r lpush notfoo{t} hello hola aguacate konichiwa zanzibar - r sort notfoo{t} ALPHA store foo{t} - set res [$rd read] - $rd close - set _ $res - } {foo{t} aguacate} -} - - test "BLPOP: timeout value out of range" { - # Timeout is parsed as float and multiplied by 1000, added mstime() - # and stored in long-long which might lead to out-of-range value. - # (Even though given timeout is smaller than LLONG_MAX, the result - # will be bigger) - assert_error "ERR *is out of range*" {r BLPOP blist1 0x7FFFFFFFFFFFFF} - } - - foreach {pop} {BLPOP BRPOP BLMPOP_LEFT BLMPOP_RIGHT} { - test "$pop: with single empty list argument" { - set rd [redis_deferring_client] - r del blist1 - bpop_command $rd $pop blist1 1 - wait_for_blocked_client - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - assert_equal 0 [r exists blist1] - $rd close - } - - test "$pop: with negative timeout" { - set rd [redis_deferring_client] - bpop_command $rd $pop blist1 -1 - assert_error "ERR *is negative*" {$rd read} - $rd close - } - - test "$pop: with non-integer timeout" { - set rd [redis_deferring_client] - r del blist1 - bpop_command $rd $pop blist1 0.1 - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - assert_equal 0 [r exists blist1] - $rd close - } - - test "$pop: with zero timeout should block indefinitely" { - # To test this, use a timeout of 0 and wait a second. - # The blocking pop should still be waiting for a push. - set rd [redis_deferring_client] - bpop_command $rd $pop blist1 0 - wait_for_blocked_client - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - $rd close - } - - test "$pop: with 0.001 timeout should not block indefinitely" { - # Use a timeout of 0.001 and wait for the number of blocked clients to equal 0. - # Validate the empty read from the deferring client. - set rd [redis_deferring_client] - bpop_command $rd $pop blist1 0.001 - wait_for_blocked_clients_count 0 - assert_equal {} [$rd read] - $rd close - } - - test "$pop: second argument is not a list" { - set rd [redis_deferring_client] - r del blist1{t} blist2{t} - r set blist2{t} nolist{t} - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - assert_error "WRONGTYPE*" {$rd read} - $rd close - } - - test "$pop: timeout" { - set rd [redis_deferring_client] - r del blist1{t} blist2{t} - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - wait_for_blocked_client - assert_equal {} [$rd read] - $rd close - } - - test "$pop: arguments are empty" { - set rd [redis_deferring_client] - r del blist1{t} blist2{t} - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - wait_for_blocked_client - r rpush blist1{t} foo - assert_equal {blist1{t} foo} [$rd read] - assert_equal 0 [r exists blist1{t}] - assert_equal 0 [r exists blist2{t}] - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - wait_for_blocked_client - r rpush blist2{t} foo - assert_equal {blist2{t} foo} [$rd read] - assert_equal 0 [r exists blist1{t}] - assert_equal 0 [r exists blist2{t}] - $rd close - } - } - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop inside a transaction" { - r del xlist - r lpush xlist foo - r lpush xlist bar - r multi - - bpop_command r $pop xlist 0 - bpop_command r $pop xlist 0 - bpop_command r $pop xlist 0 - r exec - } {{xlist bar} {xlist foo} {}} -} - - test {BLMPOP propagate as pop with count command to replica} { - set rd [redis_deferring_client] - set repl [attach_to_replication_stream] - - # BLMPOP without being blocked. - r lpush mylist{t} a b c - r rpush mylist2{t} 1 2 3 - r blmpop 0 1 mylist{t} left count 1 - r blmpop 0 2 mylist{t} mylist2{t} right count 10 - r blmpop 0 2 mylist{t} mylist2{t} right count 10 - - # BLMPOP that gets blocked. - $rd blmpop 0 1 mylist{t} left count 1 - wait_for_blocked_client - r lpush mylist{t} a - $rd blmpop 0 2 mylist{t} mylist2{t} left count 5 - wait_for_blocked_client - r lpush mylist{t} a b c - $rd blmpop 0 2 mylist{t} mylist2{t} right count 10 - wait_for_blocked_client - r rpush mylist2{t} a b c - - # Released on timeout. - assert_equal {} [r blmpop 0.01 1 mylist{t} left count 10] - r set foo{t} bar ;# something else to propagate after, so we can make sure the above pop didn't. - - $rd close - - assert_replication_stream $repl { - {select *} - {lpush mylist{t} a b c} - {rpush mylist2{t} 1 2 3} - {lpop mylist{t} 1} - {rpop mylist{t} 2} - {rpop mylist2{t} 3} - {lpush mylist{t} a} - {lpop mylist{t} 1} - {lpush mylist{t} a b c} - {lpop mylist{t} 3} - {rpush mylist2{t} a b c} - {rpop mylist2{t} 3} - {set foo{t} bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test {LPUSHX, RPUSHX - generic} { - r del xlist - assert_equal 0 [r lpushx xlist a] - assert_equal 0 [r llen xlist] - assert_equal 0 [r rpushx xlist a] - assert_equal 0 [r llen xlist] - } - - foreach {type large} [array get largevalue] { - test "LPUSHX, RPUSHX - $type" { - create_$type xlist "$large c" - assert_equal 3 [r rpushx xlist d] - assert_equal 4 [r lpushx xlist a] - assert_equal 6 [r rpushx xlist 42 x] - assert_equal 9 [r lpushx xlist y3 y2 y1] - assert_equal "y1 y2 y3 a $large c d 42 x" [r lrange xlist 0 -1] - } - - test "LINSERT - $type" { - create_$type xlist "a $large c d" - assert_equal 5 [r linsert xlist before c zz] "before c" - assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA" - assert_equal 6 [r linsert xlist after c yy] "after c" - assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB" - assert_equal 7 [r linsert xlist after d dd] "after d" - assert_equal -1 [r linsert xlist after bad ddd] "after bad" - assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC" - assert_equal 8 [r linsert xlist before a aa] "before a" - assert_equal -1 [r linsert xlist before bad aaa] "before bad" - assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD" - - # check inserting integer encoded value - assert_equal 9 [r linsert xlist before aa 42] "before aa" - assert_equal 42 [r lrange xlist 0 0] "lrangeE" - } - } - - test {LINSERT raise error on bad syntax} { - catch {[r linsert xlist aft3r aa 42]} e - set e - } {*ERR*syntax*error*} - - test {LINSERT against non-list value error} { - r set k1 v1 - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r linsert k1 after 0 0} - } - - test {LINSERT against non existing key} { - assert_equal 0 [r linsert not-a-key before 0 0] - } - -foreach type {listpack quicklist} { - foreach {num} {250 500} { - if {$type == "quicklist"} { - set origin_config [config_get_set list-max-listpack-size 5] - } else { - set origin_config [config_get_set list-max-listpack-size -1] - } - - proc check_numbered_list_consistency {key} { - set len [r llen $key] - for {set i 0} {$i < $len} {incr i} { - assert_equal $i [r lindex $key $i] - assert_equal [expr $len-1-$i] [r lindex $key [expr (-$i)-1]] - } - } - - proc check_random_access_consistency {key} { - set len [r llen $key] - for {set i 0} {$i < $len} {incr i} { - set rint [expr int(rand()*$len)] - assert_equal $rint [r lindex $key $rint] - assert_equal [expr $len-1-$rint] [r lindex $key [expr (-$rint)-1]] - } - } - - test "LINDEX consistency test - $type" { - r del mylist - for {set i 0} {$i < $num} {incr i} { - r rpush mylist $i - } - assert_encoding $type mylist - check_numbered_list_consistency mylist - } - - test "LINDEX random access - $type" { - assert_encoding $type mylist - check_random_access_consistency mylist - } - - test "Check if list is still ok after a DEBUG RELOAD - $type" { - r debug reload - assert_encoding $type mylist - check_numbered_list_consistency mylist - check_random_access_consistency mylist - } {} {needs:debug} - - config_set list-max-listpack-size $origin_config - } -} - - test {LLEN against non-list value error} { - r del mylist - r set mylist foobar - assert_error WRONGTYPE* {r llen mylist} - } - - test {LLEN against non existing key} { - assert_equal 0 [r llen not-a-key] - } - - test {LINDEX against non-list value error} { - assert_error WRONGTYPE* {r lindex mylist 0} - } - - test {LINDEX against non existing key} { - assert_equal "" [r lindex not-a-key 10] - } - - test {LPUSH against non-list value error} { - assert_error WRONGTYPE* {r lpush mylist 0} - } - - test {RPUSH against non-list value error} { - assert_error WRONGTYPE* {r rpush mylist 0} - } - - foreach {type large} [array get largevalue] { - test "RPOPLPUSH base case - $type" { - r del mylist1{t} mylist2{t} - create_$type mylist1{t} "a $large c d" - assert_equal d [r rpoplpush mylist1{t} mylist2{t}] - assert_equal c [r rpoplpush mylist1{t} mylist2{t}] - assert_equal $large [r rpoplpush mylist1{t} mylist2{t}] - assert_equal "a" [r lrange mylist1{t} 0 -1] - assert_equal "$large c d" [r lrange mylist2{t} 0 -1] - assert_encoding listpack mylist1{t} ;# converted to listpack after shrinking - assert_encoding $type mylist2{t} - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "LMOVE $wherefrom $whereto base case - $type" { - r del mylist1{t} mylist2{t} - - if {$wherefrom eq "right"} { - create_$type mylist1{t} "c d $large a" - } else { - create_$type mylist1{t} "a $large c d" - } - assert_equal a [r lmove mylist1{t} mylist2{t} $wherefrom $whereto] - assert_equal $large [r lmove mylist1{t} mylist2{t} $wherefrom $whereto] - assert_equal "c d" [r lrange mylist1{t} 0 -1] - if {$whereto eq "right"} { - assert_equal "a $large" [r lrange mylist2{t} 0 -1] - } else { - assert_equal "$large a" [r lrange mylist2{t} 0 -1] - } - assert_encoding $type mylist2{t} - } - } - } - - test "RPOPLPUSH with the same list as src and dst - $type" { - create_$type mylist{t} "a $large c" - assert_equal "a $large c" [r lrange mylist{t} 0 -1] - assert_equal c [r rpoplpush mylist{t} mylist{t}] - assert_equal "c a $large" [r lrange mylist{t} 0 -1] - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "LMOVE $wherefrom $whereto with the same list as src and dst - $type" { - if {$wherefrom eq "right"} { - create_$type mylist{t} "a $large c" - assert_equal "a $large c" [r lrange mylist{t} 0 -1] - } else { - create_$type mylist{t} "c a $large" - assert_equal "c a $large" [r lrange mylist{t} 0 -1] - } - assert_equal c [r lmove mylist{t} mylist{t} $wherefrom $whereto] - if {$whereto eq "right"} { - assert_equal "a $large c" [r lrange mylist{t} 0 -1] - } else { - assert_equal "c a $large" [r lrange mylist{t} 0 -1] - } - } - } - } - - foreach {othertype otherlarge} [array get largevalue] { - test "RPOPLPUSH with $type source and existing target $othertype" { - create_$type srclist{t} "a b c $large" - create_$othertype dstlist{t} "$otherlarge" - assert_equal $large [r rpoplpush srclist{t} dstlist{t}] - assert_equal c [r rpoplpush srclist{t} dstlist{t}] - assert_equal "a b" [r lrange srclist{t} 0 -1] - assert_equal "c $large $otherlarge" [r lrange dstlist{t} 0 -1] - - # When we rpoplpush'ed a large value, dstlist should be - # converted to the same encoding as srclist. - if {$type eq "quicklist"} { - assert_encoding quicklist dstlist{t} - } - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "LMOVE $wherefrom $whereto with $type source and existing target $othertype" { - create_$othertype dstlist{t} "$otherlarge" - - if {$wherefrom eq "right"} { - create_$type srclist{t} "a b c $large" - } else { - create_$type srclist{t} "$large c a b" - } - assert_equal $large [r lmove srclist{t} dstlist{t} $wherefrom $whereto] - assert_equal c [r lmove srclist{t} dstlist{t} $wherefrom $whereto] - assert_equal "a b" [r lrange srclist{t} 0 -1] - - if {$whereto eq "right"} { - assert_equal "$otherlarge $large c" [r lrange dstlist{t} 0 -1] - } else { - assert_equal "c $large $otherlarge" [r lrange dstlist{t} 0 -1] - } - - # When we lmoved a large value, dstlist should be - # converted to the same encoding as srclist. - if {$type eq "quicklist"} { - assert_encoding quicklist dstlist{t} - } - } - } - } - } - } - - test {RPOPLPUSH against non existing key} { - r del srclist{t} dstlist{t} - assert_equal {} [r rpoplpush srclist{t} dstlist{t}] - assert_equal 0 [r exists srclist{t}] - assert_equal 0 [r exists dstlist{t}] - } - - test {RPOPLPUSH against non list src key} { - r del srclist{t} dstlist{t} - r set srclist{t} x - assert_error WRONGTYPE* {r rpoplpush srclist{t} dstlist{t}} - assert_type string srclist{t} - assert_equal 0 [r exists newlist{t}] - } - -foreach {type large} [array get largevalue] { - test "RPOPLPUSH against non list dst key - $type" { - create_$type srclist{t} "a $large c d" - r set dstlist{t} x - assert_error WRONGTYPE* {r rpoplpush srclist{t} dstlist{t}} - assert_type string dstlist{t} - assert_equal "a $large c d" [r lrange srclist{t} 0 -1] - } -} - - test {RPOPLPUSH against non existing src key} { - r del srclist{t} dstlist{t} - assert_equal {} [r rpoplpush srclist{t} dstlist{t}] - } {} - - foreach {type large} [array get largevalue] { - test "Basic LPOP/RPOP/LMPOP - $type" { - create_$type mylist "$large 1 2" - assert_equal $large [r lpop mylist] - assert_equal 2 [r rpop mylist] - assert_equal 1 [r lpop mylist] - assert_equal 0 [r llen mylist] - - create_$type mylist "$large 1 2" - assert_equal "mylist $large" [r lmpop 1 mylist left count 1] - assert_equal {mylist {2 1}} [r lmpop 2 mylist mylist right count 2] - } - } - - test {LPOP/RPOP/LMPOP against empty list} { - r del non-existing-list{t} non-existing-list2{t} - - assert_equal {} [r lpop non-existing-list{t}] - assert_equal {} [r rpop non-existing-list2{t}] - - assert_equal {} [r lmpop 1 non-existing-list{t} left count 1] - assert_equal {} [r lmpop 1 non-existing-list{t} left count 10] - assert_equal {} [r lmpop 2 non-existing-list{t} non-existing-list2{t} right count 1] - assert_equal {} [r lmpop 2 non-existing-list{t} non-existing-list2{t} right count 10] - } - - test {LPOP/RPOP/LMPOP NON-BLOCK or BLOCK against non list value} { - r set notalist{t} foo - assert_error WRONGTYPE* {r lpop notalist{t}} - assert_error WRONGTYPE* {r blpop notalist{t} 0} - assert_error WRONGTYPE* {r rpop notalist{t}} - assert_error WRONGTYPE* {r brpop notalist{t} 0} - - r del notalist2{t} - assert_error "WRONGTYPE*" {r lmpop 2 notalist{t} notalist2{t} left count 1} - assert_error "WRONGTYPE*" {r blmpop 0 2 notalist{t} notalist2{t} left count 1} - - r del notalist{t} - r set notalist2{t} nolist - assert_error "WRONGTYPE*" {r lmpop 2 notalist{t} notalist2{t} right count 10} - assert_error "WRONGTYPE*" {r blmpop 0 2 notalist{t} notalist2{t} left count 1} - } - - foreach {num} {250 500} { - test "Mass RPOP/LPOP - $type" { - r del mylist - set sum1 0 - for {set i 0} {$i < $num} {incr i} { - if {$i == [expr $num/2]} { - r lpush mylist $large - } - r lpush mylist $i - incr sum1 $i - } - assert_encoding $type mylist - set sum2 0 - for {set i 0} {$i < [expr $num/2]} {incr i} { - incr sum2 [r lpop mylist] - incr sum2 [r rpop mylist] - } - assert_equal $sum1 $sum2 - } - } - - test {LMPOP with illegal argument} { - assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop} - assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop 1} - assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop 1 mylist{t}} - - assert_error "ERR numkeys*" {r lmpop 0 mylist{t} LEFT} - assert_error "ERR numkeys*" {r lmpop a mylist{t} LEFT} - assert_error "ERR numkeys*" {r lmpop -1 mylist{t} RIGHT} - - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} bad_where} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} LEFT bar_arg} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} RIGHT LEFT} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} COUNT} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} LEFT COUNT 1 COUNT 2} - assert_error "ERR syntax error*" {r lmpop 2 mylist{t} mylist2{t} bad_arg} - - assert_error "ERR count*" {r lmpop 1 mylist{t} LEFT COUNT 0} - assert_error "ERR count*" {r lmpop 1 mylist{t} RIGHT COUNT a} - assert_error "ERR count*" {r lmpop 1 mylist{t} LEFT COUNT -1} - assert_error "ERR count*" {r lmpop 2 mylist{t} mylist2{t} RIGHT COUNT -1} - } - -foreach {type large} [array get largevalue] { - test "LMPOP single existing list - $type" { - # Same key multiple times. - create_$type mylist{t} "a b $large d e f" - assert_equal {mylist{t} {a b}} [r lmpop 2 mylist{t} mylist{t} left count 2] - assert_equal {mylist{t} {f e}} [r lmpop 2 mylist{t} mylist{t} right count 2] - assert_equal 2 [r llen mylist{t}] - - # First one exists, second one does not exist. - create_$type mylist{t} "a b $large d e" - r del mylist2{t} - assert_equal {mylist{t} a} [r lmpop 2 mylist{t} mylist2{t} left count 1] - assert_equal 4 [r llen mylist{t}] - assert_equal "mylist{t} {e d $large b}" [r lmpop 2 mylist{t} mylist2{t} right count 10] - assert_equal {} [r lmpop 2 mylist{t} mylist2{t} right count 1] - - # First one does not exist, second one exists. - r del mylist{t} - create_$type mylist2{t} "1 2 $large 4 5" - assert_equal {mylist2{t} 5} [r lmpop 2 mylist{t} mylist2{t} right count 1] - assert_equal 4 [r llen mylist2{t}] - assert_equal "mylist2{t} {1 2 $large 4}" [r lmpop 2 mylist{t} mylist2{t} left count 10] - - assert_equal 0 [r exists mylist{t} mylist2{t}] - } - - test "LMPOP multiple existing lists - $type" { - create_$type mylist{t} "a b $large d e" - create_$type mylist2{t} "1 2 $large 4 5" - - # Pop up from the first key. - assert_equal {mylist{t} {a b}} [r lmpop 2 mylist{t} mylist2{t} left count 2] - assert_equal 3 [r llen mylist{t}] - assert_equal "mylist{t} {e d $large}" [r lmpop 2 mylist{t} mylist2{t} right count 3] - assert_equal 0 [r exists mylist{t}] - - # Pop up from the second key. - assert_equal "mylist2{t} {1 2 $large}" [r lmpop 2 mylist{t} mylist2{t} left count 3] - assert_equal 2 [r llen mylist2{t}] - assert_equal {mylist2{t} {5 4}} [r lmpop 2 mylist{t} mylist2{t} right count 2] - assert_equal 0 [r exists mylist{t}] - - # Pop up all elements. - create_$type mylist{t} "a $large c" - create_$type mylist2{t} "1 $large 3" - assert_equal "mylist{t} {a $large c}" [r lmpop 2 mylist{t} mylist2{t} left count 10] - assert_equal 0 [r llen mylist{t}] - assert_equal "mylist2{t} {3 $large 1}" [r lmpop 2 mylist{t} mylist2{t} right count 10] - assert_equal 0 [r llen mylist2{t}] - assert_equal 0 [r exists mylist{t} mylist2{t}] - } -} - - test {LMPOP propagate as pop with count command to replica} { - set repl [attach_to_replication_stream] - - # left/right propagate as lpop/rpop with count - r lpush mylist{t} a b c - - # Pop elements from one list. - r lmpop 1 mylist{t} left count 1 - r lmpop 1 mylist{t} right count 1 - - # Now the list have only one element - r lmpop 2 mylist{t} mylist2{t} left count 10 - - # No elements so we don't propagate. - r lmpop 2 mylist{t} mylist2{t} left count 10 - - # Pop elements from the second list. - r rpush mylist2{t} 1 2 3 - r lmpop 2 mylist{t} mylist2{t} left count 2 - r lmpop 2 mylist{t} mylist2{t} right count 1 - - # Pop all elements. - r rpush mylist{t} a b c - r rpush mylist2{t} 1 2 3 - r lmpop 2 mylist{t} mylist2{t} left count 10 - r lmpop 2 mylist{t} mylist2{t} right count 10 - - assert_replication_stream $repl { - {select *} - {lpush mylist{t} a b c} - {lpop mylist{t} 1} - {rpop mylist{t} 1} - {lpop mylist{t} 1} - {rpush mylist2{t} 1 2 3} - {lpop mylist2{t} 2} - {rpop mylist2{t} 1} - {rpush mylist{t} a b c} - {rpush mylist2{t} 1 2 3} - {lpop mylist{t} 3} - {rpop mylist2{t} 3} - } - close_replication_stream $repl - } {} {needs:repl} - - foreach {type large} [array get largevalue] { - test "LRANGE basics - $type" { - create_$type mylist "$large 1 2 3 4 5 6 7 8 9" - assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2] - assert_equal {7 8 9} [r lrange mylist -3 -1] - assert_equal {4} [r lrange mylist 4 4] - } - - test "LRANGE inverted indexes - $type" { - create_$type mylist "$large 1 2 3 4 5 6 7 8 9" - assert_equal {} [r lrange mylist 6 2] - } - - test "LRANGE out of range indexes including the full list - $type" { - create_$type mylist "$large 1 2 3" - assert_equal "$large 1 2 3" [r lrange mylist -1000 1000] - } - - test "LRANGE out of range negative end index - $type" { - create_$type mylist "$large 1 2 3" - assert_equal $large [r lrange mylist 0 -4] - assert_equal {} [r lrange mylist 0 -5] - } - } - - test {LRANGE against non existing key} { - assert_equal {} [r lrange nosuchkey 0 1] - } - - test {LRANGE with start > end yields an empty array for backward compatibility} { - create_$type mylist "1 $large 3" - assert_equal {} [r lrange mylist 1 0] - assert_equal {} [r lrange mylist -1 -2] - } - - foreach {type large} [array get largevalue] { - proc trim_list {type min max} { - upvar 1 large large - r del mylist - create_$type mylist "1 2 3 4 $large" - r ltrim mylist $min $max - r lrange mylist 0 -1 - } - - test "LTRIM basics - $type" { - assert_equal "1" [trim_list $type 0 0] - assert_equal "1 2" [trim_list $type 0 1] - assert_equal "1 2 3" [trim_list $type 0 2] - assert_equal "2 3" [trim_list $type 1 2] - assert_equal "2 3 4 $large" [trim_list $type 1 -1] - assert_equal "2 3 4" [trim_list $type 1 -2] - assert_equal "4 $large" [trim_list $type -2 -1] - assert_equal "$large" [trim_list $type -1 -1] - assert_equal "1 2 3 4 $large" [trim_list $type -5 -1] - assert_equal "1 2 3 4 $large" [trim_list $type -10 10] - assert_equal "1 2 3 4 $large" [trim_list $type 0 5] - assert_equal "1 2 3 4 $large" [trim_list $type 0 10] - } - - test "LTRIM out of range negative end index - $type" { - assert_equal {1} [trim_list $type 0 -5] - assert_equal {} [trim_list $type 0 -6] - } - - test "LSET - $type" { - create_$type mylist "99 98 $large 96 95" - r lset mylist 1 foo - r lset mylist -1 bar - assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1] - } - - test "LSET out of range index - $type" { - assert_error ERR*range* {r lset mylist 10 foo} - } - } - - test {LSET against non existing key} { - assert_error ERR*key* {r lset nosuchkey 10 foo} - } - - test {LSET against non list value} { - r set nolist foobar - assert_error WRONGTYPE* {r lset nolist 0 foo} - } - - foreach {type e} [array get largevalue] { - test "LREM remove all the occurrences - $type" { - create_$type mylist "$e foo bar foobar foobared zap bar test foo" - assert_equal 2 [r lrem mylist 0 bar] - assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1] - } - - test "LREM remove the first occurrence - $type" { - assert_equal 1 [r lrem mylist 1 foo] - assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1] - } - - test "LREM remove non existing element - $type" { - assert_equal 0 [r lrem mylist 1 nosuchelement] - assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1] - } - - test "LREM starting from tail with negative count - $type" { - create_$type mylist "$e foo bar foobar foobared zap bar test foo foo" - assert_equal 1 [r lrem mylist -1 bar] - assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1] - } - - test "LREM starting from tail with negative count (2) - $type" { - assert_equal 2 [r lrem mylist -2 foo] - assert_equal "$e foo bar foobar foobared zap test" [r lrange mylist 0 -1] - } - - test "LREM deleting objects that may be int encoded - $type" { - create_$type myotherlist "$e 1 2 3" - assert_equal 1 [r lrem myotherlist 1 2] - assert_equal 3 [r llen myotherlist] - } - } - - test "Regression for bug 593 - chaining BRPOPLPUSH with other blocking cmds" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - $rd1 brpoplpush a{t} b{t} 0 - $rd1 brpoplpush a{t} b{t} 0 - wait_for_blocked_clients_count 1 - $rd2 brpoplpush b{t} c{t} 0 - wait_for_blocked_clients_count 2 - r lpush a{t} data - $rd1 close - $rd2 close - r ping - } {PONG} - - test "BLPOP/BLMOVE should increase dirty" { - r del lst{t} lst1{t} - set rd [redis_deferring_client] - - set dirty [s rdb_changes_since_last_save] - $rd blpop lst{t} 0 - wait_for_blocked_client - r lpush lst{t} a - assert_equal {lst{t} a} [$rd read] - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 2} - - set dirty [s rdb_changes_since_last_save] - $rd blmove lst{t} lst1{t} left left 0 - wait_for_blocked_client - r lpush lst{t} a - assert_equal {a} [$rd read] - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 2} - - $rd close - } - -foreach {pop} {BLPOP BLMPOP_RIGHT} { - test "client unblock tests" { - r del l - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - - # test default args - bpop_command $rd $pop l 0 - wait_for_blocked_client - r client unblock $id - assert_equal {} [$rd read] - - # test with timeout - bpop_command $rd $pop l 0 - wait_for_blocked_client - r client unblock $id TIMEOUT - assert_equal {} [$rd read] - - # test with error - bpop_command $rd $pop l 0 - wait_for_blocked_client - r client unblock $id ERROR - catch {[$rd read]} e - assert_equal $e "UNBLOCKED client unblocked via CLIENT UNBLOCK" - - # test with invalid client id - catch {[r client unblock asd]} e - assert_equal $e "ERR value is not an integer or out of range" - - # test with non blocked client - set myid [r client id] - catch {[r client unblock $myid]} e - assert_equal $e {invalid command name "0"} - - # finally, see the this client and list are still functional - bpop_command $rd $pop l 0 - wait_for_blocked_client - r lpush l foo - assert_equal {l foo} [$rd read] - $rd close - } -} - - foreach {max_lp_size large} "3 $largevalue(listpack) -1 $largevalue(quicklist)" { - test "List listpack -> quicklist encoding conversion" { - set origin_conf [config_get_set list-max-listpack-size $max_lp_size] - - # RPUSH - create_listpack lst "a b c" - r RPUSH lst $large - assert_encoding quicklist lst - - # LINSERT - create_listpack lst "a b c" - r LINSERT lst after b $large - assert_encoding quicklist lst - - # LSET - create_listpack lst "a b c" - r LSET lst 0 $large - assert_encoding quicklist lst - - # LMOVE - create_quicklist lsrc{t} "a b c $large" - create_listpack ldes{t} "d e f" - r LMOVE lsrc{t} ldes{t} right right - assert_encoding quicklist ldes{t} - - r config set list-max-listpack-size $origin_conf - } - } - - test "List quicklist -> listpack encoding conversion" { - set origin_conf [config_get_set list-max-listpack-size 3] - - # RPOP - create_quicklist lst "a b c d" - r RPOP lst 3 - assert_encoding listpack lst - - # LREM - create_quicklist lst "a a a d" - r LREM lst 3 a - assert_encoding listpack lst - - # LTRIM - create_quicklist lst "a b c d" - r LTRIM lst 1 1 - assert_encoding listpack lst - - r config set list-max-listpack-size -1 - - # RPOP - create_quicklist lst "a b c $largevalue(quicklist)" - r RPOP lst 1 - assert_encoding listpack lst - - # LREM - create_quicklist lst "a $largevalue(quicklist)" - r LREM lst 1 $largevalue(quicklist) - assert_encoding listpack lst - - # LTRIM - create_quicklist lst "a b $largevalue(quicklist)" - r LTRIM lst 0 1 - assert_encoding listpack lst - - # LSET - create_quicklist lst "$largevalue(quicklist) a b" - r RPOP lst 2 - assert_encoding quicklist lst - r LSET lst -1 c - assert_encoding listpack lst - - r config set list-max-listpack-size $origin_conf - } - - test "List encoding conversion when RDB loading" { - set origin_conf [config_get_set list-max-listpack-size 3] - create_listpack lst "a b c" - - # list is still a listpack after DEBUG RELOAD - r DEBUG RELOAD - assert_encoding listpack lst - - # list is still a quicklist after DEBUG RELOAD - r RPUSH lst d - r DEBUG RELOAD - assert_encoding quicklist lst - - # when a quicklist has only one packed node, it will be - # converted to listpack during rdb loading - r RPOP lst - assert_encoding quicklist lst - r DEBUG RELOAD - assert_encoding listpack lst - - r config set list-max-listpack-size $origin_conf - } {OK} {needs:debug} - - test "List invalid list-max-listpack-size config" { - # ​When list-max-listpack-size is 0 we treat it as 1 and it'll - # still be listpack if there's a single element in the list. - r config set list-max-listpack-size 0 - r DEL lst - r RPUSH lst a - assert_encoding listpack lst - r RPUSH lst b - assert_encoding quicklist lst - - # When list-max-listpack-size < -5 we treat it as -5. - r config set list-max-listpack-size -6 - r DEL lst - r RPUSH lst [string repeat "x" 60000] - assert_encoding listpack lst - # Converted to quicklist when the size of listpack exceed 65536 - r RPUSH lst [string repeat "x" 5536] - assert_encoding quicklist lst - } - - test "List of various encodings" { - r del k - r lpush k 127 ;# ZIP_INT_8B - r lpush k 32767 ;# ZIP_INT_16B - r lpush k 2147483647 ;# ZIP_INT_32B - r lpush k 9223372036854775808 ;# ZIP_INT_64B - r lpush k 0 ;# ZIP_INT_IMM_MIN - r lpush k 12 ;# ZIP_INT_IMM_MAX - r lpush k [string repeat x 31] ;# ZIP_STR_06B - r lpush k [string repeat x 8191] ;# ZIP_STR_14B - r lpush k [string repeat x 65535] ;# ZIP_STR_32B - assert_encoding quicklist k ;# exceeds the size limit of quicklist node - set k [r lrange k 0 -1] - set dump [r dump k] - - # coverage for kvobjComputeSize - assert_morethan [memory_usage k] 0 - - config_set sanitize-dump-payload no mayfail - r restore kk 0 $dump replace - assert_encoding quicklist kk - set kk [r lrange kk 0 -1] - - # try some forward and backward searches to make sure all encodings - # can be traversed - assert_equal [r lindex kk 5] {9223372036854775808} - assert_equal [r lindex kk -5] {0} - assert_equal [r lpos kk foo rank 1] {} - assert_equal [r lpos kk foo rank -1] {} - - # make sure the values are right - assert_equal $k $kk - assert_equal [lpop k] [string repeat x 65535] - assert_equal [lpop k] [string repeat x 8191] - assert_equal [lpop k] [string repeat x 31] - set _ $k - } {12 0 9223372036854775808 2147483647 32767 127} - - test "List of various encodings - sanitize dump" { - config_set sanitize-dump-payload yes mayfail - r restore kk 0 $dump replace - assert_encoding quicklist kk - set k [r lrange k 0 -1] - set kk [r lrange kk 0 -1] - - # make sure the values are right - assert_equal $k $kk - assert_equal [lpop k] [string repeat x 65535] - assert_equal [lpop k] [string repeat x 8191] - assert_equal [lpop k] [string repeat x 31] - set _ $k - } {12 0 9223372036854775808 2147483647 32767 127} - - test "Unblock fairness is kept while pipelining" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - # delete the list in case already exists - r del mylist - - # block a client on the list - $rd1 BLPOP mylist 0 - wait_for_blocked_clients_count 1 - - # pipeline on other client a list push and a blocking pop - # we should expect the fairness to be kept and have $rd1 - # being unblocked - set buf "" - append buf "LPUSH mylist 1\r\n" - append buf "BLPOP mylist 0\r\n" - $rd2 write $buf - $rd2 flush - - # we check that we still have 1 blocked client - # and that the first blocked client has been served - assert_equal [$rd1 read] {mylist 1} - assert_equal [$rd2 read] {1} - wait_for_blocked_clients_count 1 - - # We no unblock the last client and verify it was served last - r LPUSH mylist 2 - wait_for_blocked_clients_count 0 - assert_equal [$rd2 read] {mylist 2} - - $rd1 close - $rd2 close - } - - test "Unblock fairness is kept during nested unblock" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - - # delete the list in case already exists - r del l1{t} l2{t} l3{t} - - # block a client on the list - $rd1 BRPOPLPUSH l1{t} l3{t} 0 - wait_for_blocked_clients_count 1 - - $rd2 BLPOP l2{t} 0 - wait_for_blocked_clients_count 2 - - $rd3 BLMPOP 0 2 l2{t} l3{t} LEFT COUNT 1 - wait_for_blocked_clients_count 3 - - r multi - r lpush l1{t} 1 - r lpush l2{t} 2 - r exec - - wait_for_blocked_clients_count 0 - - assert_equal [$rd1 read] {1} - assert_equal [$rd2 read] {l2{t} 2} - assert_equal [$rd3 read] {l3{t} 1} - - $rd1 close - $rd2 close - $rd3 close - } - - test "Blocking command accounted only once in commandstats" { - # cleanup first - r del mylist - - # create a test client - set rd [redis_deferring_client] - - # reset the server stats - r config resetstat - - # block a client on the list - $rd BLPOP mylist 0 - wait_for_blocked_clients_count 1 - - # unblock the list - r LPUSH mylist 1 - wait_for_blocked_clients_count 0 - - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdrstat blpop r] - - $rd close - } - - test "Blocking command accounted only once in commandstats after timeout" { - # cleanup first - r del mylist - - # create a test client - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - - # reset the server stats - r config resetstat - - # block a client on the list - $rd BLPOP mylist 0 - wait_for_blocked_clients_count 1 - - # unblock the client on timeout - r client unblock $id timeout - - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdrstat blpop r] - - $rd close - } - - test {Command being unblocked cause another command to get unblocked execution order test} { - r del src{t} dst{t} key1{t} key2{t} key3{t} - set repl [attach_to_replication_stream] - - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - - $rd1 blmove src{t} dst{t} left right 0 - wait_for_blocked_clients_count 1 - - $rd2 blmove dst{t} src{t} right left 0 - wait_for_blocked_clients_count 2 - - # Create a pipeline of commands that will be processed in one socket read. - # Insert two set commands before and after lpush to observe the execution order. - set buf "" - append buf "set key1{t} value1\r\n" - append buf "lpush src{t} dummy\r\n" - append buf "set key2{t} value2\r\n" - $rd3 write $buf - $rd3 flush - - wait_for_blocked_clients_count 0 - - r set key3{t} value3 - - # If a command being unblocked causes another command to get unblocked, like a BLMOVE would do, - # then the new unblocked command will get processed right away rather than wait for later. - # If the set command occurs between two lmove commands, the results are not as expected. - assert_replication_stream $repl { - {select *} - {set key1{t} value1} - {lpush src{t} dummy} - {lmove src{t} dst{t} left right} - {lmove dst{t} src{t} right left} - {set key2{t} value2} - {set key3{t} value3} - } - - $rd1 close - $rd2 close - $rd3 close - - close_replication_stream $repl - } {} {needs:repl} - - test "Blocking timeout following PAUSE should honor the timeout" { - # cleanup first - r del mylist - - # create a test client - set rd [redis_deferring_client] - - # first PAUSE all writes for a very long time - r client pause 10000000000000 write - - # block a client on the list - $rd BLPOP mylist 1 - wait_for_blocked_clients_count 1 - - # now unpause the writes - r client unpause - - # client should time-out - wait_for_blocked_clients_count 0 - - $rd close - } - - test "CLIENT NO-TOUCH with BRPOP and RPUSH regression test" { - # Test scenario: - # 1. Client 1: CLIENT NO-TOUCH on - # 2. Client 2: BRPOP mylist 0 - # 3. Client 1: RPUSH mylist elem - - # cleanup first - r del mylist - - # Create two test clients - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - # Client 1: Enable CLIENT NO-TOUCH - $rd1 client no-touch on - assert_equal {OK} [$rd1 read] - - # Client 2: Block waiting for elements in mylist - $rd2 brpop mylist 0 - wait_for_blocked_client - - # Client 1: Push an element to mylist - $rd1 rpush mylist elem - assert_equal {1} [$rd1 read] - - # Verify Client 2 received the element - assert_equal {mylist elem} [$rd2 read] - - $rd1 close - $rd2 close - } - -} ;# stop servers diff --git a/examples/redis-unstable/tests/unit/type/set.tcl b/examples/redis-unstable/tests/unit/type/set.tcl deleted file mode 100644 index 054c978..0000000 --- a/examples/redis-unstable/tests/unit/type/set.tcl +++ /dev/null @@ -1,1314 +0,0 @@ -start_server { - tags {"set"} - overrides { - "set-max-intset-entries" 512 - "set-max-listpack-entries" 128 - "set-max-listpack-value" 32 - } -} { - proc create_set {key entries} { - r del $key - foreach entry $entries { r sadd $key $entry } - } - - # Values for initialing sets, per encoding. - array set initelems {listpack {foo} hashtable {foo}} - for {set i 0} {$i < 130} {incr i} { - lappend initelems(hashtable) [format "i%03d" $i] - } - - foreach type {listpack hashtable} { - test "SADD, SCARD, SISMEMBER, SMISMEMBER, SMEMBERS basics - $type" { - create_set myset $initelems($type) - assert_encoding $type myset - assert_equal 1 [r sadd myset bar] - assert_equal 0 [r sadd myset bar] - assert_equal [expr [llength $initelems($type)] + 1] [r scard myset] - assert_equal 1 [r sismember myset foo] - assert_equal 1 [r sismember myset bar] - assert_equal 0 [r sismember myset bla] - assert_equal {1} [r smismember myset foo] - assert_equal {1 1} [r smismember myset foo bar] - assert_equal {1 0} [r smismember myset foo bla] - assert_equal {0 1} [r smismember myset bla foo] - assert_equal {0} [r smismember myset bla] - assert_equal "bar $initelems($type)" [lsort [r smembers myset]] - } - } - - test {SADD, SCARD, SISMEMBER, SMISMEMBER, SMEMBERS basics - intset} { - create_set myset {17} - assert_encoding intset myset - assert_equal 1 [r sadd myset 16] - assert_equal 0 [r sadd myset 16] - assert_equal 2 [r scard myset] - assert_equal 1 [r sismember myset 16] - assert_equal 1 [r sismember myset 17] - assert_equal 0 [r sismember myset 18] - assert_equal {1} [r smismember myset 16] - assert_equal {1 1} [r smismember myset 16 17] - assert_equal {1 0} [r smismember myset 16 18] - assert_equal {0 1} [r smismember myset 18 16] - assert_equal {0} [r smismember myset 18] - assert_equal {16 17} [lsort [r smembers myset]] - } - - test {SMISMEMBER SMEMBERS SCARD against non set} { - r lpush mylist foo - assert_error WRONGTYPE* {r smismember mylist bar} - assert_error WRONGTYPE* {r smembers mylist} - assert_error WRONGTYPE* {r scard mylist} - } - - test {SMISMEMBER SMEMBERS SCARD against non existing key} { - assert_equal {0} [r smismember myset1 foo] - assert_equal {0 0} [r smismember myset1 foo bar] - assert_equal {} [r smembers myset1] - assert_equal {0} [r scard myset1] - } - - test {SMISMEMBER requires one or more members} { - r del zmscoretest - r zadd zmscoretest 10 x - r zadd zmscoretest 20 y - - catch {r smismember zmscoretest} e - assert_match {*ERR*wrong*number*arg*} $e - } - - test {SADD against non set} { - r lpush mylist foo - assert_error WRONGTYPE* {r sadd mylist bar} - } - - test "SADD a non-integer against a small intset" { - create_set myset {1 2 3} - assert_encoding intset myset - assert_equal 1 [r sadd myset a] - assert_encoding listpack myset - } - - test "SADD a non-integer against a large intset" { - create_set myset {0} - for {set i 1} {$i < 130} {incr i} {r sadd myset $i} - assert_encoding intset myset - assert_equal 1 [r sadd myset a] - assert_encoding hashtable myset - } - - test "SADD an integer larger than 64 bits" { - create_set myset {213244124402402314402033402} - assert_encoding listpack myset - assert_equal 1 [r sismember myset 213244124402402314402033402] - assert_equal {1} [r smismember myset 213244124402402314402033402] - } - - test "SADD an integer larger than 64 bits to a large intset" { - create_set myset {0} - for {set i 1} {$i < 130} {incr i} {r sadd myset $i} - assert_encoding intset myset - r sadd myset 213244124402402314402033402 - assert_encoding hashtable myset - assert_equal 1 [r sismember myset 213244124402402314402033402] - assert_equal {1} [r smismember myset 213244124402402314402033402] - } - -foreach type {single multiple single_multiple} { - test "SADD overflows the maximum allowed integers in an intset - $type" { - r del myset - - if {$type == "single"} { - # All are single sadd commands. - for {set i 0} {$i < 512} {incr i} { r sadd myset $i } - } elseif {$type == "multiple"} { - # One sadd command to add all elements. - set args {} - for {set i 0} {$i < 512} {incr i} { lappend args $i } - r sadd myset {*}$args - } elseif {$type == "single_multiple"} { - # First one sadd adds an element (creates a key) and then one sadd adds all elements. - r sadd myset 1 - set args {} - for {set i 0} {$i < 512} {incr i} { lappend args $i } - r sadd myset {*}$args - } - - assert_encoding intset myset - assert_equal 512 [r scard myset] - assert_equal 1 [r sadd myset 512] - assert_encoding hashtable myset - } - - test "SADD overflows the maximum allowed elements in a listpack - $type" { - r del myset - - if {$type == "single"} { - # All are single sadd commands. - r sadd myset a - for {set i 0} {$i < 127} {incr i} { r sadd myset $i } - } elseif {$type == "multiple"} { - # One sadd command to add all elements. - set args {} - lappend args a - for {set i 0} {$i < 127} {incr i} { lappend args $i } - r sadd myset {*}$args - } elseif {$type == "single_multiple"} { - # First one sadd adds an element (creates a key) and then one sadd adds all elements. - r sadd myset a - set args {} - lappend args a - for {set i 0} {$i < 127} {incr i} { lappend args $i } - r sadd myset {*}$args - } - - assert_encoding listpack myset - assert_equal 128 [r scard myset] - assert_equal 1 [r sadd myset b] - assert_encoding hashtable myset - } -} - - test {Variadic SADD} { - r del myset - assert_equal 3 [r sadd myset a b c] - assert_equal 2 [r sadd myset A a b c B] - assert_equal [lsort {A a b c B}] [lsort [r smembers myset]] - } - - test "Set encoding after DEBUG RELOAD" { - r del myintset - r del myhashset - r del mylargeintset - r del mysmallset - for {set i 0} {$i < 100} {incr i} { r sadd myintset $i } - for {set i 0} {$i < 1280} {incr i} { r sadd mylargeintset $i } - for {set i 0} {$i < 50} {incr i} { r sadd mysmallset [format "i%03d" $i] } - for {set i 0} {$i < 256} {incr i} { r sadd myhashset [format "i%03d" $i] } - assert_encoding intset myintset - assert_encoding hashtable mylargeintset - assert_encoding listpack mysmallset - assert_encoding hashtable myhashset - - r debug reload - assert_encoding intset myintset - assert_encoding hashtable mylargeintset - assert_encoding listpack mysmallset - assert_encoding hashtable myhashset - } {} {needs:debug} - - foreach type {listpack hashtable} { - test {SREM basics - $type} { - create_set myset $initelems($type) - r sadd myset ciao - assert_encoding $type myset - assert_equal 0 [r srem myset qux] - assert_equal 1 [r srem myset ciao] - assert_equal $initelems($type) [lsort [r smembers myset]] - } - } - - test {SREM basics - intset} { - create_set myset {3 4 5} - assert_encoding intset myset - assert_equal 0 [r srem myset 6] - assert_equal 1 [r srem myset 4] - assert_equal {3 5} [lsort [r smembers myset]] - } - - test {SREM with multiple arguments} { - r del myset - r sadd myset a b c d - assert_equal 0 [r srem myset k k k] - assert_equal 2 [r srem myset b d x y] - lsort [r smembers myset] - } {a c} - - test {SREM variadic version with more args needed to destroy the key} { - r del myset - r sadd myset 1 2 3 - r srem myset 1 2 3 4 5 6 7 8 - } {3} - - test "SINTERCARD with illegal arguments" { - assert_error "ERR wrong number of arguments for 'sintercard' command" {r sintercard} - assert_error "ERR wrong number of arguments for 'sintercard' command" {r sintercard 1} - - assert_error "ERR numkeys*" {r sintercard 0 myset{t}} - assert_error "ERR numkeys*" {r sintercard a myset{t}} - - assert_error "ERR Number of keys*" {r sintercard 2 myset{t}} - assert_error "ERR Number of keys*" {r sintercard 3 myset{t} myset2{t}} - - assert_error "ERR syntax error*" {r sintercard 1 myset{t} myset2{t}} - assert_error "ERR syntax error*" {r sintercard 1 myset{t} bar_arg} - assert_error "ERR syntax error*" {r sintercard 1 myset{t} LIMIT} - - assert_error "ERR LIMIT*" {r sintercard 1 myset{t} LIMIT -1} - assert_error "ERR LIMIT*" {r sintercard 1 myset{t} LIMIT a} - } - - test "SINTERCARD against non-set should throw error" { - r del set{t} - r sadd set{t} a b c - r set key1{t} x - - assert_error "WRONGTYPE*" {r sintercard 1 key1{t}} - assert_error "WRONGTYPE*" {r sintercard 2 set{t} key1{t}} - assert_error "WRONGTYPE*" {r sintercard 2 key1{t} noset{t}} - } - - test "SINTERCARD against non-existing key" { - assert_equal 0 [r sintercard 1 non-existing-key] - assert_equal 0 [r sintercard 1 non-existing-key limit 0] - assert_equal 0 [r sintercard 1 non-existing-key limit 10] - } - - foreach {type} {regular intset} { - # Create sets setN{t} where N = 1..5 - if {$type eq "regular"} { - set smallenc listpack - set bigenc hashtable - } else { - set smallenc intset - set bigenc intset - } - # Sets 1, 2 and 4 are big; sets 3 and 5 are small. - array set encoding "1 $bigenc 2 $bigenc 3 $smallenc 4 $bigenc 5 $smallenc" - - for {set i 1} {$i <= 5} {incr i} { - r del [format "set%d{t}" $i] - } - for {set i 0} {$i < 200} {incr i} { - r sadd set1{t} $i - r sadd set2{t} [expr $i+195] - } - foreach i {199 195 1000 2000} { - r sadd set3{t} $i - } - for {set i 5} {$i < 200} {incr i} { - r sadd set4{t} $i - } - r sadd set5{t} 0 - - # To make sure the sets are encoded as the type we are testing -- also - # when the VM is enabled and the values may be swapped in and out - # while the tests are running -- an extra element is added to every - # set that determines its encoding. - set large 200 - if {$type eq "regular"} { - set large foo - } - - for {set i 1} {$i <= 5} {incr i} { - r sadd [format "set%d{t}" $i] $large - } - - test "Generated sets must be encoded correctly - $type" { - for {set i 1} {$i <= 5} {incr i} { - assert_encoding $encoding($i) [format "set%d{t}" $i] - } - } - - test "SINTER with two sets - $type" { - assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter set1{t} set2{t}]] - } - - test "SINTERCARD with two sets - $type" { - assert_equal 6 [r sintercard 2 set1{t} set2{t}] - assert_equal 6 [r sintercard 2 set1{t} set2{t} limit 0] - assert_equal 3 [r sintercard 2 set1{t} set2{t} limit 3] - assert_equal 6 [r sintercard 2 set1{t} set2{t} limit 10] - } - - test "SINTERSTORE with two sets - $type" { - r sinterstore setres{t} set1{t} set2{t} - assert_encoding $smallenc setres{t} - assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres{t}]] - } - - test "SINTERSTORE with two sets, after a DEBUG RELOAD - $type" { - r debug reload - r sinterstore setres{t} set1{t} set2{t} - assert_encoding $smallenc setres{t} - assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres{t}]] - } {} {needs:debug} - - test "SUNION with two sets - $type" { - set expected [lsort -uniq "[r smembers set1{t}] [r smembers set2{t}]"] - assert_equal $expected [lsort [r sunion set1{t} set2{t}]] - } - - test "SUNIONSTORE with two sets - $type" { - r sunionstore setres{t} set1{t} set2{t} - assert_encoding $bigenc setres{t} - set expected [lsort -uniq "[r smembers set1{t}] [r smembers set2{t}]"] - assert_equal $expected [lsort [r smembers setres{t}]] - } - - test "SINTER against three sets - $type" { - assert_equal [list 195 199 $large] [lsort [r sinter set1{t} set2{t} set3{t}]] - } - - test "SINTERCARD against three sets - $type" { - assert_equal 3 [r sintercard 3 set1{t} set2{t} set3{t}] - assert_equal 3 [r sintercard 3 set1{t} set2{t} set3{t} limit 0] - assert_equal 2 [r sintercard 3 set1{t} set2{t} set3{t} limit 2] - assert_equal 3 [r sintercard 3 set1{t} set2{t} set3{t} limit 10] - } - - test "SINTERSTORE with three sets - $type" { - r sinterstore setres{t} set1{t} set2{t} set3{t} - assert_equal [list 195 199 $large] [lsort [r smembers setres{t}]] - } - - test "SUNION with non existing keys - $type" { - set expected [lsort -uniq "[r smembers set1{t}] [r smembers set2{t}]"] - assert_equal $expected [lsort [r sunion nokey1{t} set1{t} set2{t} nokey2{t}]] - } - - test "SDIFF with two sets - $type" { - assert_equal {0 1 2 3 4} [lsort [r sdiff set1{t} set4{t}]] - } - - test "SDIFF with three sets - $type" { - assert_equal {1 2 3 4} [lsort [r sdiff set1{t} set4{t} set5{t}]] - } - - test "SDIFFSTORE with three sets - $type" { - r sdiffstore setres{t} set1{t} set4{t} set5{t} - # When we start with intsets, we should always end with intsets. - if {$type eq {intset}} { - assert_encoding intset setres{t} - } - assert_equal {1 2 3 4} [lsort [r smembers setres{t}]] - } - - test "SINTER/SUNION/SDIFF with three same sets - $type" { - set expected [lsort "[r smembers set1{t}]"] - assert_equal $expected [lsort [r sinter set1{t} set1{t} set1{t}]] - assert_equal $expected [lsort [r sunion set1{t} set1{t} set1{t}]] - assert_equal {} [lsort [r sdiff set1{t} set1{t} set1{t}]] - } - } - - test "SINTERSTORE with two listpack sets where result is intset" { - r del setres{t} set1{t} set2{t} - r sadd set1{t} a b c 1 3 6 x y z - r sadd set2{t} e f g 1 2 3 u v w - assert_encoding listpack set1{t} - assert_encoding listpack set2{t} - r sinterstore setres{t} set1{t} set2{t} - assert_equal [list 1 3] [lsort [r smembers setres{t}]] - assert_encoding intset setres{t} - } - - test "SINTERSTORE with two hashtable sets where result is intset" { - r del setres{t} set1{t} set2{t} - r sadd set1{t} a b c 444 555 666 - r sadd set2{t} e f g 111 222 333 - set expected {} - for {set i 1} {$i < 130} {incr i} { - r sadd set1{t} $i - r sadd set2{t} $i - lappend expected $i - } - assert_encoding hashtable set1{t} - assert_encoding hashtable set2{t} - r sinterstore setres{t} set1{t} set2{t} - assert_equal [lsort $expected] [lsort [r smembers setres{t}]] - assert_encoding intset setres{t} - } - - test "SUNION hashtable and listpack" { - # This adds code coverage for adding a non-sds string to a hashtable set - # which already contains the string. - r del set1{t} set2{t} - set union {abcdefghijklmnopqrstuvwxyz1234567890 a b c 1 2 3} - create_set set1{t} $union - create_set set2{t} {a b c} - assert_encoding hashtable set1{t} - assert_encoding listpack set2{t} - assert_equal [lsort $union] [lsort [r sunion set1{t} set2{t}]] - } - - test "SDIFF with first set empty" { - r del set1{t} set2{t} set3{t} - r sadd set2{t} 1 2 3 4 - r sadd set3{t} a b c d - r sdiff set1{t} set2{t} set3{t} - } {} - - test "SDIFF with same set two times" { - r del set1 - r sadd set1 a b c 1 2 3 4 5 6 - r sdiff set1 set1 - } {} - - test "SDIFF fuzzing" { - for {set j 0} {$j < 100} {incr j} { - unset -nocomplain s - array set s {} - set args {} - set num_sets [expr {[randomInt 10]+1}] - for {set i 0} {$i < $num_sets} {incr i} { - set num_elements [randomInt 100] - r del set_$i{t} - lappend args set_$i{t} - while {$num_elements} { - set ele [randomValue] - r sadd set_$i{t} $ele - if {$i == 0} { - set s($ele) x - } else { - unset -nocomplain s($ele) - } - incr num_elements -1 - } - } - set result [lsort [r sdiff {*}$args]] - assert_equal $result [lsort [array names s]] - } - } - - test "SDIFF against non-set should throw error" { - # with an empty set - r set key1{t} x - assert_error "WRONGTYPE*" {r sdiff key1{t} noset{t}} - # different order - assert_error "WRONGTYPE*" {r sdiff noset{t} key1{t}} - - # with a legal set - r del set1{t} - r sadd set1{t} a b c - assert_error "WRONGTYPE*" {r sdiff key1{t} set1{t}} - # different order - assert_error "WRONGTYPE*" {r sdiff set1{t} key1{t}} - } - - test "SDIFF should handle non existing key as empty" { - r del set1{t} set2{t} set3{t} - - r sadd set1{t} a b c - r sadd set2{t} b c d - assert_equal {a} [lsort [r sdiff set1{t} set2{t} set3{t}]] - assert_equal {} [lsort [r sdiff set3{t} set2{t} set1{t}]] - } - - test "SDIFFSTORE against non-set should throw error" { - r del set1{t} set2{t} set3{t} key1{t} - r set key1{t} x - - # with en empty dstkey - assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} key1{t} noset{t}} - assert_equal 0 [r exists set3{t}] - assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} noset{t} key1{t}} - assert_equal 0 [r exists set3{t}] - - # with a legal dstkey - r sadd set1{t} a b c - r sadd set2{t} b c d - r sadd set3{t} e - assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} key1{t} set1{t} noset{t}} - assert_equal 1 [r exists set3{t}] - assert_equal {e} [lsort [r smembers set3{t}]] - - assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} set1{t} key1{t} set2{t}} - assert_equal 1 [r exists set3{t}] - assert_equal {e} [lsort [r smembers set3{t}]] - } - - test "SDIFFSTORE should handle non existing key as empty" { - r del set1{t} set2{t} set3{t} - - r set setres{t} xxx - assert_equal 0 [r sdiffstore setres{t} foo111{t} bar222{t}] - assert_equal 0 [r exists setres{t}] - - # with a legal dstkey, should delete dstkey - r sadd set3{t} a b c - assert_equal 0 [r sdiffstore set3{t} set1{t} set2{t}] - assert_equal 0 [r exists set3{t}] - - r sadd set1{t} a b c - assert_equal 3 [r sdiffstore set3{t} set1{t} set2{t}] - assert_equal 1 [r exists set3{t}] - assert_equal {a b c} [lsort [r smembers set3{t}]] - - # with a legal dstkey and empty set2, should delete the dstkey - r sadd set3{t} a b c - assert_equal 0 [r sdiffstore set3{t} set2{t} set1{t}] - assert_equal 0 [r exists set3{t}] - } - - test "SINTER against non-set should throw error" { - r set key1{t} x - assert_error "WRONGTYPE*" {r sinter key1{t} noset{t}} - # different order - assert_error "WRONGTYPE*" {r sinter noset{t} key1{t}} - - r sadd set1{t} a b c - assert_error "WRONGTYPE*" {r sinter key1{t} set1{t}} - # different order - assert_error "WRONGTYPE*" {r sinter set1{t} key1{t}} - } - - test "SINTER should handle non existing key as empty" { - r del set1{t} set2{t} set3{t} - r sadd set1{t} a b c - r sadd set2{t} b c d - r sinter set1{t} set2{t} set3{t} - } {} - - test "SINTER with same integer elements but different encoding" { - r del set1{t} set2{t} - r sadd set1{t} 1 2 3 - r sadd set2{t} 1 2 3 a - r srem set2{t} a - assert_encoding intset set1{t} - assert_encoding listpack set2{t} - lsort [r sinter set1{t} set2{t}] - } {1 2 3} - - test "SINTERSTORE against non-set should throw error" { - r del set1{t} set2{t} set3{t} key1{t} - r set key1{t} x - - # with en empty dstkey - assert_error "WRONGTYPE*" {r sinterstore set3{t} key1{t} noset{t}} - assert_equal 0 [r exists set3{t}] - assert_error "WRONGTYPE*" {r sinterstore set3{t} noset{t} key1{t}} - assert_equal 0 [r exists set3{t}] - - # with a legal dstkey - r sadd set1{t} a b c - r sadd set2{t} b c d - r sadd set3{t} e - assert_error "WRONGTYPE*" {r sinterstore set3{t} key1{t} set2{t} noset{t}} - assert_equal 1 [r exists set3{t}] - assert_equal {e} [lsort [r smembers set3{t}]] - - assert_error "WRONGTYPE*" {r sinterstore set3{t} noset{t} key1{t} set2{t}} - assert_equal 1 [r exists set3{t}] - assert_equal {e} [lsort [r smembers set3{t}]] - } - - test "SINTERSTORE against non existing keys should delete dstkey" { - r del set1{t} set2{t} set3{t} - - r set setres{t} xxx - assert_equal 0 [r sinterstore setres{t} foo111{t} bar222{t}] - assert_equal 0 [r exists setres{t}] - - # with a legal dstkey - r sadd set3{t} a b c - assert_equal 0 [r sinterstore set3{t} set1{t} set2{t}] - assert_equal 0 [r exists set3{t}] - - r sadd set1{t} a b c - assert_equal 0 [r sinterstore set3{t} set1{t} set2{t}] - assert_equal 0 [r exists set3{t}] - - assert_equal 0 [r sinterstore set3{t} set2{t} set1{t}] - assert_equal 0 [r exists set3{t}] - } - - test "SUNION against non-set should throw error" { - r set key1{t} x - assert_error "WRONGTYPE*" {r sunion key1{t} noset{t}} - # different order - assert_error "WRONGTYPE*" {r sunion noset{t} key1{t}} - - r del set1{t} - r sadd set1{t} a b c - assert_error "WRONGTYPE*" {r sunion key1{t} set1{t}} - # different order - assert_error "WRONGTYPE*" {r sunion set1{t} key1{t}} - } - - test "SUNION should handle non existing key as empty" { - r del set1{t} set2{t} set3{t} - - r sadd set1{t} a b c - r sadd set2{t} b c d - assert_equal {a b c d} [lsort [r sunion set1{t} set2{t} set3{t}]] - } - - test "SUNIONSTORE against non-set should throw error" { - r del set1{t} set2{t} set3{t} key1{t} - r set key1{t} x - - # with en empty dstkey - assert_error "WRONGTYPE*" {r sunionstore set3{t} key1{t} noset{t}} - assert_equal 0 [r exists set3{t}] - assert_error "WRONGTYPE*" {r sunionstore set3{t} noset{t} key1{t}} - assert_equal 0 [r exists set3{t}] - - # with a legal dstkey - r sadd set1{t} a b c - r sadd set2{t} b c d - r sadd set3{t} e - assert_error "WRONGTYPE*" {r sunionstore set3{t} key1{t} key2{t} noset{t}} - assert_equal 1 [r exists set3{t}] - assert_equal {e} [lsort [r smembers set3{t}]] - - assert_error "WRONGTYPE*" {r sunionstore set3{t} noset{t} key1{t} key2{t}} - assert_equal 1 [r exists set3{t}] - assert_equal {e} [lsort [r smembers set3{t}]] - } - - test "SUNIONSTORE should handle non existing key as empty" { - r del set1{t} set2{t} set3{t} - - r set setres{t} xxx - assert_equal 0 [r sunionstore setres{t} foo111{t} bar222{t}] - assert_equal 0 [r exists setres{t}] - - # set1 set2 both empty, should delete the dstkey - r sadd set3{t} a b c - assert_equal 0 [r sunionstore set3{t} set1{t} set2{t}] - assert_equal 0 [r exists set3{t}] - - r sadd set1{t} a b c - r sadd set3{t} e f - assert_equal 3 [r sunionstore set3{t} set1{t} set2{t}] - assert_equal 1 [r exists set3{t}] - assert_equal {a b c} [lsort [r smembers set3{t}]] - - r sadd set3{t} d - assert_equal 3 [r sunionstore set3{t} set2{t} set1{t}] - assert_equal 1 [r exists set3{t}] - assert_equal {a b c} [lsort [r smembers set3{t}]] - } - - test "SUNIONSTORE against non existing keys should delete dstkey" { - r set setres{t} xxx - assert_equal 0 [r sunionstore setres{t} foo111{t} bar222{t}] - assert_equal 0 [r exists setres{t}] - } - - foreach {type contents} {listpack {a b c} intset {1 2 3}} { - test "SPOP basics - $type" { - create_set myset $contents - assert_encoding $type myset - assert_equal $contents [lsort [list [r spop myset] [r spop myset] [r spop myset]]] - assert_equal 0 [r scard myset] - } - - test "SPOP with =1 - $type" { - create_set myset $contents - assert_encoding $type myset - assert_equal $contents [lsort [list [r spop myset 1] [r spop myset 1] [r spop myset 1]]] - assert_equal 0 [r scard myset] - } - - test "SRANDMEMBER - $type" { - create_set myset $contents - unset -nocomplain myset - array set myset {} - for {set i 0} {$i < 100} {incr i} { - set myset([r srandmember myset]) 1 - } - assert_equal $contents [lsort [array names myset]] - } - } - - test "SPOP integer from listpack set" { - create_set myset {a 1 2 3 4 5 6 7} - assert_encoding listpack myset - set a [r spop myset] - set b [r spop myset] - assert {[string is digit $a] || [string is digit $b]} - } - - foreach {type contents} { - listpack {a b c d e f g h i j k l m n o p q r s t u v w x y z} - intset {1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23 24 25 26 3 4 5 6 7 8 9} - hashtable {ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 b c d e f g h i j k l m n o p q r s t u v w x y z} - } { - test "SPOP with - $type" { - create_set myset $contents - assert_encoding $type myset - assert_equal $contents [lsort [concat [r spop myset 11] [r spop myset 9] [r spop myset 0] [r spop myset 4] [r spop myset 1] [r spop myset 0] [r spop myset 1] [r spop myset 0]]] - assert_equal 0 [r scard myset] - } - } - - # As seen in intsetRandomMembers - test "SPOP using integers, testing Knuth's and Floyd's algorithm" { - create_set myset {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} - assert_encoding intset myset - assert_equal 20 [r scard myset] - r spop myset 1 - assert_equal 19 [r scard myset] - r spop myset 2 - assert_equal 17 [r scard myset] - r spop myset 3 - assert_equal 14 [r scard myset] - r spop myset 10 - assert_equal 4 [r scard myset] - r spop myset 10 - assert_equal 0 [r scard myset] - r spop myset 1 - assert_equal 0 [r scard myset] - } {} - - test "SPOP using integers with Knuth's algorithm" { - r spop nonexisting_key 100 - } {} - - foreach {type content} { - intset {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} - listpack {a 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} - } { - test "SPOP new implementation: code path #1 $type" { - create_set myset $content - assert_encoding $type myset - set res [r spop myset 30] - assert {[lsort $content] eq [lsort $res]} - assert_equal {0} [r exists myset] - } - - test "SPOP new implementation: code path #2 $type" { - create_set myset $content - assert_encoding $type myset - set res [r spop myset 2] - assert {[llength $res] == 2} - assert {[r scard myset] == 18} - set union [concat [r smembers myset] $res] - assert {[lsort $union] eq [lsort $content]} - } - - test "SPOP new implementation: code path #3 $type" { - create_set myset $content - assert_encoding $type myset - set res [r spop myset 18] - assert {[llength $res] == 18} - assert {[r scard myset] == 2} - set union [concat [r smembers myset] $res] - assert {[lsort $union] eq [lsort $content]} - } - } - - test "SPOP new implementation: code path #1 propagate as DEL or UNLINK" { - r del myset1{t} myset2{t} - r sadd myset1{t} 1 2 3 4 5 - r sadd myset2{t} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 - - set repl [attach_to_replication_stream] - - r config set lazyfree-lazy-server-del no - r spop myset1{t} [r scard myset1{t}] - r config set lazyfree-lazy-server-del yes - r spop myset2{t} [r scard myset2{t}] - assert_equal {0} [r exists myset1{t} myset2{t}] - - # Verify the propagate of DEL and UNLINK. - assert_replication_stream $repl { - {select *} - {del myset1{t}} - {unlink myset2{t}} - } - - close_replication_stream $repl - } {} {needs:repl} - - test "SRANDMEMBER count of 0 is handled correctly" { - r srandmember myset 0 - } {} - - test "SRANDMEMBER with against non existing key" { - r srandmember nonexisting_key 100 - } {} - - test "SRANDMEMBER count overflow" { - r sadd myset a - assert_error {*value is out of range*} {r srandmember myset -9223372036854775808} - } {} - - # Make sure we can distinguish between an empty array and a null response - r readraw 1 - - test "SRANDMEMBER count of 0 is handled correctly - emptyarray" { - r srandmember myset 0 - } {*0} - - test "SRANDMEMBER with against non existing key - emptyarray" { - r srandmember nonexisting_key 100 - } {*0} - - r readraw 0 - - foreach {type contents} { - listpack { - 1 5 10 50 125 50000 33959417 4775547 65434162 - 12098459 427716 483706 2726473884 72615637475 - MARY PATRICIA LINDA BARBARA ELIZABETH JENNIFER MARIA - SUSAN MARGARET DOROTHY LISA NANCY KAREN BETTY HELEN - SANDRA DONNA CAROL RUTH SHARON MICHELLE LAURA SARAH - KIMBERLY DEBORAH JESSICA SHIRLEY CYNTHIA ANGELA MELISSA - BRENDA AMY ANNA REBECCA VIRGINIA KATHLEEN - } - intset { - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - 20 21 22 23 24 25 26 27 28 29 - 30 31 32 33 34 35 36 37 38 39 - 40 41 42 43 44 45 46 47 48 49 - } - hashtable { - ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 - 1 5 10 50 125 50000 33959417 4775547 65434162 - 12098459 427716 483706 2726473884 72615637475 - MARY PATRICIA LINDA BARBARA ELIZABETH JENNIFER MARIA - SUSAN MARGARET DOROTHY LISA NANCY KAREN BETTY HELEN - SANDRA DONNA CAROL RUTH SHARON MICHELLE LAURA SARAH - KIMBERLY DEBORAH JESSICA SHIRLEY CYNTHIA ANGELA MELISSA - BRENDA AMY ANNA REBECCA VIRGINIA - } - } { - test "SRANDMEMBER with - $type" { - create_set myset $contents - assert_encoding $type myset - unset -nocomplain myset - array set myset {} - foreach ele [r smembers myset] { - set myset($ele) 1 - } - assert_equal [lsort $contents] [lsort [array names myset]] - - # Make sure that a count of 0 is handled correctly. - assert_equal [r srandmember myset 0] {} - - # We'll stress different parts of the code, see the implementation - # of SRANDMEMBER for more information, but basically there are - # four different code paths. - # - # PATH 1: Use negative count. - # - # 1) Check that it returns repeated elements. - set res [r srandmember myset -100] - assert_equal [llength $res] 100 - - # 2) Check that all the elements actually belong to the - # original set. - foreach ele $res { - assert {[info exists myset($ele)]} - } - - # 3) Check that eventually all the elements are returned. - unset -nocomplain auxset - set iterations 1000 - while {$iterations != 0} { - incr iterations -1 - set res [r srandmember myset -10] - foreach ele $res { - set auxset($ele) 1 - } - if {[lsort [array names myset]] eq - [lsort [array names auxset]]} { - break; - } - } - assert {$iterations != 0} - - # PATH 2: positive count (unique behavior) with requested size - # equal or greater than set size. - foreach size {50 100} { - set res [r srandmember myset $size] - assert_equal [llength $res] 50 - assert_equal [lsort $res] [lsort [array names myset]] - } - - # PATH 3: Ask almost as elements as there are in the set. - # In this case the implementation will duplicate the original - # set and will remove random elements up to the requested size. - # - # PATH 4: Ask a number of elements definitely smaller than - # the set size. - # - # We can test both the code paths just changing the size but - # using the same code. - - foreach size {45 5} { - set res [r srandmember myset $size] - assert_equal [llength $res] $size - - # 1) Check that all the elements actually belong to the - # original set. - foreach ele $res { - assert {[info exists myset($ele)]} - } - - # 2) Check that eventually all the elements are returned. - unset -nocomplain auxset - set iterations 1000 - while {$iterations != 0} { - incr iterations -1 - set res [r srandmember myset $size] - foreach ele $res { - set auxset($ele) 1 - } - if {[lsort [array names myset]] eq - [lsort [array names auxset]]} { - break; - } - } - assert {$iterations != 0} - } - } - } - - foreach {type contents} { - listpack { - 1 5 10 50 125 - MARY PATRICIA LINDA BARBARA ELIZABETH - } - intset { - 0 1 2 3 4 5 6 7 8 9 - } - hashtable { - ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 - 1 5 10 50 125 - MARY PATRICIA LINDA BARBARA - } - } { - test "SRANDMEMBER histogram distribution - $type" { - create_set myset $contents - assert_encoding $type myset - unset -nocomplain myset - array set myset {} - foreach ele [r smembers myset] { - set myset($ele) 1 - } - - # Use negative count (PATH 1). - # df = 9, 40 means 0.00001 probability - set res [r srandmember myset -1000] - assert_lessthan [chi_square_value $res] 40 - - # Use positive count (both PATH 3 and PATH 4). - foreach size {8 2} { - unset -nocomplain allkey - set iterations [expr {1000 / $size}] - while {$iterations != 0} { - incr iterations -1 - set res [r srandmember myset $size] - foreach ele $res { - lappend allkey $ele - } - } - # df = 9, 40 means 0.00001 probability - assert_lessthan [chi_square_value $allkey] 40 - } - } - } - - proc is_rehashing {myset} { - set htstats [r debug HTSTATS-KEY $myset] - return [string match {*rehashing target*} $htstats] - } - - proc rem_hash_set_top_N {myset n} { - set cursor 0 - set members {} - set enough 0 - while 1 { - set res [r sscan $myset $cursor] - set cursor [lindex $res 0] - set k [lindex $res 1] - foreach m $k { - lappend members $m - if {[llength $members] >= $n} { - set enough 1 - break - } - } - if {$enough || $cursor == 0} { - break - } - } - r srem $myset {*}$members - } - - proc verify_rehashing_completed_key {myset table_size keys} { - set htstats [r debug HTSTATS-KEY $myset] - assert {![string match {*rehashing target*} $htstats]} - return {[string match {*table size: $table_size*number of elements: $keys*} $htstats]} - } - - test "SRANDMEMBER with a dict containing long chain" { - set origin_save [config_get_set save ""] - set origin_max_lp [config_get_set set-max-listpack-entries 0] - set origin_save_delay [config_get_set rdb-key-save-delay 2147483647] - - # 1) Create a hash set with 100000 members. - set members {} - for {set i 0} {$i < 100000} {incr i} { - lappend members [format "m:%d" $i] - } - create_set myset $members - - # 2) Wait for the hash set rehashing to finish. - while {[is_rehashing myset]} { - r srandmember myset 100 - } - - # 3) Turn off the rehashing of this set, and remove the members to 500. - r bgsave - rem_hash_set_top_N myset [expr {[r scard myset] - 500}] - assert_equal [r scard myset] 500 - - # 4) Kill RDB child process to restart rehashing. - set pid1 [get_child_pid 0] - catch {exec kill -9 $pid1} - waitForBgsave r - - # 5) Let the set hash to start rehashing - r spop myset 1 - assert [is_rehashing myset] - - # 6) Verify that when rdb saving is in progress, rehashing will still be performed (because - # the ratio is extreme) by waiting for it to finish during an active bgsave. - r bgsave - - while {[is_rehashing myset]} { - r srandmember myset 1 - } - if {$::verbose} { - puts [r debug HTSTATS-KEY myset full] - } - - set pid1 [get_child_pid 0] - catch {exec kill -9 $pid1} - waitForBgsave r - - # 7) Check that eventually, SRANDMEMBER returns all elements. - array set allmyset {} - foreach ele [r smembers myset] { - set allmyset($ele) 1 - } - unset -nocomplain auxset - set iterations 1000 - while {$iterations != 0} { - incr iterations -1 - set res [r srandmember myset -10] - foreach ele $res { - set auxset($ele) 1 - } - if {[lsort [array names allmyset]] eq - [lsort [array names auxset]]} { - break; - } - } - assert {$iterations != 0} - - # 8) Remove the members to 30 in order to calculate the value of Chi-Square Distribution, - # otherwise we would need more iterations. - rem_hash_set_top_N myset [expr {[r scard myset] - 30}] - assert_equal [r scard myset] 30 - - # Hash set rehashing would be completed while removing members from the `myset` - # We also check the size and members in the hash table. - verify_rehashing_completed_key myset 64 30 - - # Now that we have a hash set with only one long chain bucket. - set htstats [r debug HTSTATS-KEY myset full] - assert {[regexp {different slots: ([0-9]+)} $htstats - different_slots]} - assert {[regexp {max chain length: ([0-9]+)} $htstats - max_chain_length]} - assert {$different_slots == 1 && $max_chain_length == 30} - - # 9) Use positive count (PATH 4) to get 10 elements (out of 30) each time. - unset -nocomplain allkey - set iterations 1000 - while {$iterations != 0} { - incr iterations -1 - set res [r srandmember myset 10] - foreach ele $res { - lappend allkey $ele - } - } - # validate even distribution of random sampling (df = 29, 73 means 0.00001 probability) - assert_lessthan [chi_square_value $allkey] 73 - - r config set save $origin_save - r config set set-max-listpack-entries $origin_max_lp - r config set rdb-key-save-delay $origin_save_delay - } {OK} {needs:debug slow debug_defrag:skip} - - proc setup_move {} { - r del myset3{t} myset4{t} - create_set myset1{t} {1 a b} - create_set myset2{t} {2 3 4} - assert_encoding listpack myset1{t} - assert_encoding intset myset2{t} - } - - test "SMOVE basics - from regular set to intset" { - # move a non-integer element to an intset should convert encoding - setup_move - assert_equal 1 [r smove myset1{t} myset2{t} a] - assert_equal {1 b} [lsort [r smembers myset1{t}]] - assert_equal {2 3 4 a} [lsort [r smembers myset2{t}]] - assert_encoding listpack myset2{t} - - # move an integer element should not convert the encoding - setup_move - assert_equal 1 [r smove myset1{t} myset2{t} 1] - assert_equal {a b} [lsort [r smembers myset1{t}]] - assert_equal {1 2 3 4} [lsort [r smembers myset2{t}]] - assert_encoding intset myset2{t} - } - - test "SMOVE basics - from intset to regular set" { - setup_move - assert_equal 1 [r smove myset2{t} myset1{t} 2] - assert_equal {1 2 a b} [lsort [r smembers myset1{t}]] - assert_equal {3 4} [lsort [r smembers myset2{t}]] - } - - test "SMOVE non existing key" { - setup_move - assert_equal 0 [r smove myset1{t} myset2{t} foo] - assert_equal 0 [r smove myset1{t} myset1{t} foo] - assert_equal {1 a b} [lsort [r smembers myset1{t}]] - assert_equal {2 3 4} [lsort [r smembers myset2{t}]] - } - - test "SMOVE non existing src set" { - setup_move - assert_equal 0 [r smove noset{t} myset2{t} foo] - assert_equal {2 3 4} [lsort [r smembers myset2{t}]] - } - - test "SMOVE from regular set to non existing destination set" { - setup_move - assert_equal 1 [r smove myset1{t} myset3{t} a] - assert_equal {1 b} [lsort [r smembers myset1{t}]] - assert_equal {a} [lsort [r smembers myset3{t}]] - assert_encoding listpack myset3{t} - } - - test "SMOVE from intset to non existing destination set" { - setup_move - assert_equal 1 [r smove myset2{t} myset3{t} 2] - assert_equal {3 4} [lsort [r smembers myset2{t}]] - assert_equal {2} [lsort [r smembers myset3{t}]] - assert_encoding intset myset3{t} - } - - test "SMOVE wrong src key type" { - r set x{t} 10 - assert_error "WRONGTYPE*" {r smove x{t} myset2{t} foo} - } - - test "SMOVE wrong dst key type" { - r set x{t} 10 - assert_error "WRONGTYPE*" {r smove myset2{t} x{t} foo} - } - - test "SMOVE with identical source and destination" { - r del set{t} - r sadd set{t} a b c - r smove set{t} set{t} b - lsort [r smembers set{t}] - } {a b c} - - test "SMOVE only notify dstset when the addition is successful" { - r del srcset{t} - r del dstset{t} - - r sadd srcset{t} a b - r sadd dstset{t} a - - r watch dstset{t} - - r multi - r sadd dstset{t} c - - set r2 [redis_client] - $r2 smove srcset{t} dstset{t} a - - # The dstset is actually unchanged, multi should success - r exec - set res [r scard dstset{t}] - assert_equal $res 2 - $r2 close - } - - tags {slow} { - test {intsets implementation stress testing} { - for {set j 0} {$j < 20} {incr j} { - unset -nocomplain s - array set s {} - r del s - set len [randomInt 1024] - for {set i 0} {$i < $len} {incr i} { - randpath { - set data [randomInt 65536] - } { - set data [randomInt 4294967296] - } { - set data [randomInt 18446744073709551616] - } - set s($data) {} - r sadd s $data - } - assert_equal [lsort [r smembers s]] [lsort [array names s]] - set len [array size s] - for {set i 0} {$i < $len} {incr i} { - set e [r spop s] - if {![info exists s($e)]} { - puts "Can't find '$e' on local array" - puts "Local array: [lsort [r smembers s]]" - puts "Remote array: [lsort [array names s]]" - error "exception" - } - array unset s $e - } - assert_equal [r scard s] 0 - assert_equal [array size s] 0 - } - } - } -} - -run_solo {set-large-memory} { -start_server [list overrides [list save ""] ] { - -# test if the server supports such large configs (avoid 32 bit builds) -catch { - r config set proto-max-bulk-len 10000000000 ;#10gb - r config set client-query-buffer-limit 10000000000 ;#10gb -} -if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { - - set str_length 4400000000 ;#~4.4GB - - test {SADD, SCARD, SISMEMBER - large data} { - r flushdb - r write "*3\r\n\$4\r\nSADD\r\n\$5\r\nmyset\r\n" - assert_equal 1 [write_big_bulk $str_length "aaa"] - r write "*3\r\n\$4\r\nSADD\r\n\$5\r\nmyset\r\n" - assert_equal 1 [write_big_bulk $str_length "bbb"] - r write "*3\r\n\$4\r\nSADD\r\n\$5\r\nmyset\r\n" - assert_equal 0 [write_big_bulk $str_length "aaa"] - assert_encoding hashtable myset - set s0 [s used_memory] - assert {$s0 > [expr $str_length * 2]} - assert_equal 2 [r scard myset] - - r write "*3\r\n\$9\r\nSISMEMBER\r\n\$5\r\nmyset\r\n" - assert_equal 1 [write_big_bulk $str_length "aaa"] - r write "*3\r\n\$9\r\nSISMEMBER\r\n\$5\r\nmyset\r\n" - assert_equal 0 [write_big_bulk $str_length "ccc"] - r write "*3\r\n\$4\r\nSREM\r\n\$5\r\nmyset\r\n" - assert_equal 1 [write_big_bulk $str_length "bbb"] - assert_equal [read_big_bulk {r spop myset} yes "aaa"] $str_length - } {} {large-memory} - - # restore defaults - r config set proto-max-bulk-len 536870912 - r config set client-query-buffer-limit 1073741824 - -} ;# skip 32bit builds -} -} ;# run_solo diff --git a/examples/redis-unstable/tests/unit/type/stream-cgroups.tcl b/examples/redis-unstable/tests/unit/type/stream-cgroups.tcl deleted file mode 100644 index 4990275..0000000 --- a/examples/redis-unstable/tests/unit/type/stream-cgroups.tcl +++ /dev/null @@ -1,3293 +0,0 @@ -start_server { - tags {"stream"} -} { - test {XGROUP CREATE: creation and duplicate group name detection} { - r DEL mystream - r XADD mystream * foo bar - r XGROUP CREATE mystream mygroup $ - catch {r XGROUP CREATE mystream mygroup $} err - set err - } {BUSYGROUP*} - - test {XGROUP CREATE: with ENTRIESREAD parameter} { - r DEL mystream - r XADD mystream 1-1 a 1 - r XADD mystream 1-2 b 2 - r XADD mystream 1-3 c 3 - r XADD mystream 1-4 d 4 - assert_error "*value for ENTRIESREAD must be positive or -1*" {r XGROUP CREATE mystream mygroup $ ENTRIESREAD -3} - - r XGROUP CREATE mystream mygroup1 $ ENTRIESREAD 0 - r XGROUP CREATE mystream mygroup2 $ ENTRIESREAD 3 - - set reply [r xinfo groups mystream] - foreach group_info $reply { - set group_name [dict get $group_info name] - set entries_read [dict get $group_info entries-read] - if {$group_name == "mygroup1"} { - assert_equal $entries_read 0 - } else { - assert_equal $entries_read 3 - } - } - } - - test {XGROUP CREATE: automatic stream creation fails without MKSTREAM} { - r DEL mystream - catch {r XGROUP CREATE mystream mygroup $} err - set err - } {ERR*} - - test {XGROUP CREATE: automatic stream creation works with MKSTREAM} { - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - } {OK} - - test {XREADGROUP basic argument count validation} { - # Too few arguments - assert_error "*wrong number of arguments*" {r XREADGROUP} - assert_error "*wrong number of arguments*" {r XREADGROUP GROUP} - assert_error "*wrong number of arguments*" {r XREADGROUP GROUP mygroup} - assert_error "*wrong number of arguments*" {r XREADGROUP GROUP mygroup consumer} - assert_error "*wrong number of arguments*" {r XREADGROUP GROUP mygroup consumer STREAMS} - } - - test {XREADGROUP GROUP keyword validation} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Missing GROUP keyword entirely - wrong syntax - assert_error "*wrong number of arguments*" {r XREADGROUP mygroup consumer STREAMS mystream >} - - # Wrong keyword instead of GROUP - assert_error "*syntax error*" {r XREADGROUP GROUPS mygroup consumer STREAMS mystream >} - } - - test {XREADGROUP empty group name handling} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Empty group name should give NOGROUP error - assert_error "*NOGROUP*" {r XREADGROUP GROUP "" consumer STREAMS mystream >} - } - - test {XREADGROUP STREAMS keyword validation} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Missing STREAMS keyword - assert_error "*wrong number of arguments*" {r XREADGROUP GROUP mygroup consumer mystream >} - - # Wrong keyword - assert_error "*syntax error*" {r XREADGROUP GROUP mygroup consumer STREAM mystream >} - } - - test {XREADGROUP stream and ID pairing} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Missing stream ID - assert_error "*wrong number of arguments*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream} - - # Unbalanced streams and IDs - r DEL stream2 - r XADD stream2 * field value - r XGROUP CREATE stream2 mygroup $ - - assert_error "*Unbalanced*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream > stream2} - assert_error "*Unbalanced*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream stream2 >} - - r DEL stream2 - } - - test {XREADGROUP COUNT parameter validation} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Non-numeric count - assert_error "*not an integer*" {r XREADGROUP GROUP mygroup consumer COUNT abc STREAMS mystream >} - assert_error "*not an integer*" {r XREADGROUP GROUP mygroup consumer COUNT 1.5 STREAMS mystream >} - } - - test {XREADGROUP BLOCK parameter validation} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Non-numeric block timeout - assert_error "*not an integer*" {r XREADGROUP GROUP mygroup consumer BLOCK abc STREAMS mystream >} - assert_error "*not an integer*" {r XREADGROUP GROUP mygroup consumer BLOCK 1.5 STREAMS mystream >} - - # Missing BLOCK value - assert_error "*ERR timeout is not an integer or out of range*" {r XREADGROUP GROUP mygroup consumer BLOCK STREAMS mystream >} - - # Negative timeout (typically not allowed) - assert_error "*ERR timeout is negative*" {r XREADGROUP GROUP mygroup consumer BLOCK -1 STREAMS mystream >} - } - - test {XREADGROUP stream ID format validation} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Invalid ID formats should error - assert_error "*Invalid stream ID*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream invalid-id} - assert_error "*Invalid stream ID*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream 123-} - assert_error "*Invalid stream ID*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream -123} - assert_error "*Invalid stream ID*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream abc-def} - assert_error "*Invalid stream ID*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream --} - assert_error "*Invalid stream ID*" {r XREADGROUP GROUP mygroup consumer STREAMS mystream 123-abc} - } - - test {XREADGROUP nonexistent group} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - assert_error "*NOGROUP*" {r XREADGROUP GROUP nonexistent consumer STREAMS mystream >} - } - - test {XREADGROUP nonexistent stream with existing group} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Group doesn't exist on the nonexistent stream - assert_error "*NOGROUP*" {r XREADGROUP GROUP mygroup consumer STREAMS nonexistent >} - } - - test {XREADGROUP wrong key type} { - r SET wrongtype "not a stream" - assert_error "*WRONGTYPE*" {r XREADGROUP GROUP mygroup consumer STREAMS wrongtype >} - r DEL wrongtype - } - - test {XREADGROUP boundary value validation} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Test COUNT boundaries - values that are too large - assert_error "*value is not an integer or out of range*" {r XREADGROUP GROUP mygroup consumer COUNT 18446744073709551616 STREAMS mystream >} - - # Test BLOCK timeout boundaries - values that are too large - assert_error "*timeout is not an integer or out of range*" {r XREADGROUP GROUP mygroup consumer BLOCK 18446744073709551616 STREAMS mystream >} - } - - test {XREADGROUP malformed parameter syntax} { - r DEL mystream - r XADD mystream * field value - r XGROUP CREATE mystream mygroup $ - - # Unknown parameters - assert_error "*syntax error*" {r XREADGROUP GROUP mygroup consumer INVALID param STREAMS mystream >} - assert_error "*syntax error*" {r XREADGROUP GROUP mygroup consumer TIMEOUT 1000 STREAMS mystream >} - } - - test {XREADGROUP will return only new elements} { - r XADD mystream * a 1 - r XADD mystream * b 2 - - # Verify XPENDING returns empty results when no messages are in the PEL. - assert_equal {0 {} {} {}} [r XPENDING mystream mygroup] - assert_equal {} [r XPENDING mystream mygroup - + 10] - - # XREADGROUP should return only the new elements "a 1" "b 1" - # and not the element "foo bar" which was pre existing in the - # stream (see previous test) - set reply [ - r XREADGROUP GROUP mygroup consumer-1 STREAMS mystream ">" - ] - assert {[llength [lindex $reply 0 1]] == 2} - lindex $reply 0 1 0 1 - } {a 1} - - test {XREADGROUP can read the history of the elements we own} { - # Add a few more elements - r XADD mystream * c 3 - r XADD mystream * d 4 - # Read a few elements using a different consumer name - set reply [ - r XREADGROUP GROUP mygroup consumer-2 STREAMS mystream ">" - ] - assert {[llength [lindex $reply 0 1]] == 2} - assert {[lindex $reply 0 1 0 1] eq {c 3}} - - set r1 [r XREADGROUP GROUP mygroup consumer-1 COUNT 10 STREAMS mystream 0] - set r2 [r XREADGROUP GROUP mygroup consumer-2 COUNT 10 STREAMS mystream 0] - assert {[lindex $r1 0 1 0 1] eq {a 1}} - assert {[lindex $r2 0 1 0 1] eq {c 3}} - } - - test {XPENDING is able to return pending items} { - set pending [r XPENDING mystream mygroup - + 10] - assert {[llength $pending] == 4} - for {set j 0} {$j < 4} {incr j} { - set item [lindex $pending $j] - if {$j < 2} { - set owner consumer-1 - } else { - set owner consumer-2 - } - assert {[lindex $item 1] eq $owner} - assert {[lindex $item 1] eq $owner} - } - } - - test {XPENDING can return single consumer items} { - set pending [r XPENDING mystream mygroup - + 10 consumer-1] - assert {[llength $pending] == 2} - } - - test {XPENDING only group} { - set pending [r XPENDING mystream mygroup] - assert {[llength $pending] == 4} - } - - test {XPENDING with IDLE} { - after 20 - set pending [r XPENDING mystream mygroup IDLE 99999999 - + 10 consumer-1] - assert {[llength $pending] == 0} - set pending [r XPENDING mystream mygroup IDLE 1 - + 10 consumer-1] - assert {[llength $pending] == 2} - set pending [r XPENDING mystream mygroup IDLE 99999999 - + 10] - assert {[llength $pending] == 0} - set pending [r XPENDING mystream mygroup IDLE 1 - + 10] - assert {[llength $pending] == 4} - } - - test {XPENDING with exclusive range intervals works as expected} { - set pending [r XPENDING mystream mygroup - + 10] - assert {[llength $pending] == 4} - set startid [lindex [lindex $pending 0] 0] - set endid [lindex [lindex $pending 3] 0] - set expending [r XPENDING mystream mygroup ($startid ($endid 10] - assert {[llength $expending] == 2} - for {set j 0} {$j < 2} {incr j} { - set itemid [lindex [lindex $expending $j] 0] - assert {$itemid ne $startid} - assert {$itemid ne $endid} - } - } - - test {XACK is able to remove items from the consumer/group PEL} { - set pending [r XPENDING mystream mygroup - + 10 consumer-1] - set id1 [lindex $pending 0 0] - set id2 [lindex $pending 1 0] - assert {[r XACK mystream mygroup $id1] eq 1} - set pending [r XPENDING mystream mygroup - + 10 consumer-1] - assert {[llength $pending] == 1} - set id [lindex $pending 0 0] - assert {$id eq $id2} - set global_pel [r XPENDING mystream mygroup - + 10] - assert {[llength $global_pel] == 3} - } - - test {XACK can't remove the same item multiple times} { - assert {[r XACK mystream mygroup $id1] eq 0} - } - - test {XACK is able to accept multiple arguments} { - # One of the IDs was already removed, so it should ack - # just ID2. - assert {[r XACK mystream mygroup $id1 $id2] eq 1} - } - - test {XACK should fail if got at least one invalid ID} { - r del mystream - r xgroup create s g $ MKSTREAM - r xadd s * f1 v1 - set c [llength [lindex [r xreadgroup group g c streams s >] 0 1]] - assert {$c == 1} - set pending [r xpending s g - + 10 c] - set id1 [lindex $pending 0 0] - assert_error "*Invalid stream ID specified*" {r xack s g $id1 invalid-id} - assert {[r xack s g $id1] eq 1} - } - - test {PEL NACK reassignment after XGROUP SETID event} { - r del events - r xadd events * f1 v1 - r xadd events * f1 v1 - r xadd events * f1 v1 - r xadd events * f1 v1 - r xgroup create events g1 $ - r xadd events * f1 v1 - set c [llength [lindex [r xreadgroup group g1 c1 streams events >] 0 1]] - assert {$c == 1} - r xgroup setid events g1 - - set c [llength [lindex [r xreadgroup group g1 c2 streams events >] 0 1]] - assert {$c == 5} - } - - test {XREADGROUP will not report data on empty history. Bug #5577} { - r del events - r xadd events * a 1 - r xadd events * b 2 - r xadd events * c 3 - r xgroup create events mygroup 0 - - # Current local PEL should be empty - set res [r xpending events mygroup - + 10] - assert {[llength $res] == 0} - - # So XREADGROUP should read an empty history as well - set res [r xreadgroup group mygroup myconsumer count 3 streams events 0] - assert {[llength [lindex $res 0 1]] == 0} - - # We should fetch all the elements in the stream asking for > - set res [r xreadgroup group mygroup myconsumer count 3 streams events >] - assert {[llength [lindex $res 0 1]] == 3} - - # Now the history is populated with three not acked entries - set res [r xreadgroup group mygroup myconsumer count 3 streams events 0] - assert {[llength [lindex $res 0 1]] == 3} - } - - test {XREADGROUP history reporting of deleted entries. Bug #5570} { - r del mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream 1 field1 A - r XREADGROUP GROUP mygroup myconsumer STREAMS mystream > - r XADD mystream MAXLEN 1 2 field1 B - r XREADGROUP GROUP mygroup myconsumer STREAMS mystream > - - # Now we have two pending entries, however one should be deleted - # and one should be ok (we should only see "B") - set res [r XREADGROUP GROUP mygroup myconsumer STREAMS mystream 0-1] - assert {[lindex $res 0 1 0] == {1-0 {}}} - assert {[lindex $res 0 1 1] == {2-0 {field1 B}}} - } - - test {Blocking XREADGROUP will not reply with an empty array} { - r del mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream 666 f v - set res [r XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"] - assert {[lindex $res 0 1 0] == {666-0 {f v}}} - r XADD mystream 667 f2 v2 - r XDEL mystream 667 - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">" - wait_for_blocked_clients_count 0 - assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}} - $rd close - } - - test {Blocking XREADGROUP: key deleted} { - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r DEL mystream - assert_error "NOGROUP*" {$rd read} - $rd close - } - - test {Blocking XREADGROUP: key type changed with SET} { - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r SET mystream val1 - assert_error "*WRONGTYPE*" {$rd read} - $rd close - } - - test {Blocking XREADGROUP: key type changed with transaction} { - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r MULTI - r DEL mystream - r SADD mystream e1 - r EXEC - assert_error "*WRONGTYPE*" {$rd read} - $rd close - } - - test {Blocking XREADGROUP: flushed DB} { - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r FLUSHALL - assert_error "*NOGROUP*" {$rd read} - $rd close - } - - test {Blocking XREADGROUP: swapped DB, key doesn't exist} { - r SELECT 4 - r FLUSHDB - r SELECT 9 - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - set rd [redis_deferring_client] - $rd SELECT 9 - $rd read - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r SWAPDB 4 9 - assert_error "*NOGROUP*" {$rd read} - $rd close - } {0} {external:skip} - - test {Blocking XREADGROUP: swapped DB, key is not a stream} { - r SELECT 4 - r FLUSHDB - r LPUSH mystream e1 - r SELECT 9 - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - set rd [redis_deferring_client] - $rd SELECT 9 - $rd read - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r SWAPDB 4 9 - assert_error "*WRONGTYPE*" {$rd read} - $rd close - } {0} {external:skip} - - test {XREAD and XREADGROUP against wrong parameter} { - r DEL mystream - r XADD mystream 666 f v - r XGROUP CREATE mystream mygroup $ - assert_error "ERR Unbalanced 'xreadgroup' list of streams: for each stream key an ID or '>' must be specified." {r XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream } - assert_error "ERR Unbalanced 'xread' list of streams: for each stream key an ID, '+', or '$' must be specified." {r XREAD COUNT 1 STREAMS mystream } - } - - test {Blocking XREAD: key deleted} { - r DEL mystream - r XADD mystream 666 f v - set rd [redis_deferring_client] - $rd XREAD BLOCK 0 STREAMS mystream "$" - wait_for_blocked_clients_count 1 - r DEL mystream - - r XADD mystream 667 f v - set res [$rd read] - assert_equal [lindex $res 0 1 0] {667-0 {f v}} - $rd close - } - - test {Blocking XREAD: key type changed with SET} { - r DEL mystream - r XADD mystream 666 f v - set rd [redis_deferring_client] - $rd XREAD BLOCK 0 STREAMS mystream "$" - wait_for_blocked_clients_count 1 - r SET mystream val1 - - r DEL mystream - r XADD mystream 667 f v - set res [$rd read] - assert_equal [lindex $res 0 1 0] {667-0 {f v}} - $rd close - } - - test {Blocking XREADGROUP for stream that ran dry (issue #5299)} { - set rd [redis_deferring_client] - - # Add a entry then delete it, now stream's last_id is 666. - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream 666 key value - r XDEL mystream 666 - - # Pass a special `>` ID but without new entry, released on timeout. - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 10 STREAMS mystream > - assert_equal [$rd read] {} - - # Throw an error if the ID equal or smaller than the last_id. - assert_error ERR*equal*smaller* {r XADD mystream 665 key value} - assert_error ERR*equal*smaller* {r XADD mystream 666 key value} - - # Entered blocking state and then release because of the new entry. - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream > - wait_for_blocked_clients_count 1 - r XADD mystream 667 key value - assert_equal [$rd read] {{mystream {{667-0 {key value}}}}} - - $rd close - } - - test "Blocking XREADGROUP will ignore BLOCK if ID is not >" { - set rd [redis_deferring_client] - - # Add a entry then delete it, now stream's last_id is 666. - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream 666 key value - r XDEL mystream 666 - - # Return right away instead of blocking, return the stream with an - # empty list instead of NIL if the ID specified is not the special `>` ID. - foreach id {0 600 666 700} { - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream $id - assert_equal [$rd read] {{mystream {}}} - } - - # After adding a new entry, `XREADGROUP BLOCK` still return the stream - # with an empty list because the pending list is empty. - r XADD mystream 667 key value - foreach id {0 600 666 667 700} { - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream $id - assert_equal [$rd read] {{mystream {}}} - } - - # After we read it once, the pending list is not empty at this time, - # pass any ID smaller than 667 will return one of the pending entry. - set res [r XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream >] - assert_equal $res {{mystream {{667-0 {key value}}}}} - foreach id {0 600 666} { - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream $id - assert_equal [$rd read] {{mystream {{667-0 {key value}}}}} - } - - # Pass ID equal or greater than 667 will return the stream with an empty list. - foreach id {667 700} { - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream $id - assert_equal [$rd read] {{mystream {}}} - } - - # After we ACK the pending entry, return the stream with an empty list. - r XACK mystream mygroup 667 - foreach id {0 600 666 667 700} { - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream $id - assert_equal [$rd read] {{mystream {}}} - } - - $rd close - } - - test {Blocking XREADGROUP for stream key that has clients blocked on list} { - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - - # First delete the stream - r DEL mystream - - # now place a client blocked on non-existing key as list - $rd2 BLPOP mystream 0 - - # wait until we verify the client is blocked - wait_for_blocked_clients_count 1 - - # verify we only have 1 regular blocking key - assert_equal 1 [getInfoProperty [r info clients] total_blocking_keys] - assert_equal 0 [getInfoProperty [r info clients] total_blocking_keys_on_nokey] - - # now write mystream as stream - r XADD mystream 666 key value - r XGROUP CREATE mystream mygroup $ MKSTREAM - - # block another client on xreadgroup - $rd XREADGROUP GROUP mygroup myconsumer BLOCK 0 STREAMS mystream ">" - - # wait until we verify we have 2 blocked clients (one for the list and one for the stream) - wait_for_blocked_clients_count 2 - - # verify we have 1 blocking key which also have clients blocked on nokey condition - assert_equal 1 [getInfoProperty [r info clients] total_blocking_keys] - assert_equal 1 [getInfoProperty [r info clients] total_blocking_keys_on_nokey] - - # now delete the key and verify we have no clients blocked on nokey condition - r DEL mystream - assert_error "NOGROUP*" {$rd read} - assert_equal 1 [getInfoProperty [r info clients] total_blocking_keys] - assert_equal 0 [getInfoProperty [r info clients] total_blocking_keys_on_nokey] - - # close the only left client and make sure we have no more blocking keys - $rd2 close - - # wait until we verify we have no more blocked clients - wait_for_blocked_clients_count 0 - - assert_equal 0 [getInfoProperty [r info clients] total_blocking_keys] - assert_equal 0 [getInfoProperty [r info clients] total_blocking_keys_on_nokey] - - $rd close - } - - test {Blocking XREADGROUP for stream key that has clients blocked on stream - avoid endless loop} { - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - - $rd1 xreadgroup GROUP mygroup myuser COUNT 10 BLOCK 10000 STREAMS mystream > - $rd2 xreadgroup GROUP mygroup myuser COUNT 10 BLOCK 10000 STREAMS mystream > - $rd3 xreadgroup GROUP mygroup myuser COUNT 10 BLOCK 10000 STREAMS mystream > - - wait_for_blocked_clients_count 3 - - r xadd mystream MAXLEN 5000 * field1 value1 field2 value2 field3 value3 - - $rd1 close - $rd2 close - $rd3 close - - assert_equal [r ping] {PONG} - } - - test {Blocking XREADGROUP for stream key that has clients blocked on stream - reprocessing command} { - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - $rd1 xreadgroup GROUP mygroup myuser BLOCK 0 STREAMS mystream > - wait_for_blocked_clients_count 1 - - set start [clock milliseconds] - $rd2 xreadgroup GROUP mygroup myuser BLOCK 1000 STREAMS mystream > - wait_for_blocked_clients_count 2 - - # After a while call xadd and let rd2 re-process the command. - after 200 - r xadd mystream * field value - assert_equal {} [$rd2 read] - set end [clock milliseconds] - - # Before the fix in #13004, this time would have been 1200+ (i.e. more than 1200ms), - # now it should be 1000, but in order to avoid timing issues, we increase the range a bit. - assert_range [expr $end-$start] 1000 1150 - - $rd1 close - $rd2 close - } - - test {XGROUP DESTROY should unblock XREADGROUP with -NOGROUP} { - r config resetstat - r del mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r XGROUP DESTROY mystream mygroup - assert_error "NOGROUP*" {$rd read} - $rd close - - # verify command stats, error stats and error counter work on failed blocked command - assert_match {*count=1*} [errorrstat NOGROUP r] - assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdrstat xreadgroup r] - assert_equal [s total_error_replies] 1 - } - - test {XGROUP DESTROY removes all consumer group references} { - r DEL mystream - for {set j 0} {$j < 5} {incr j} { - r XADD mystream $j-1 item $j - } - - r XGROUP CREATE mystream mygroup 0 - r XREADGROUP GROUP mygroup consumer1 STREAMS mystream > - assert {[lindex [r XPENDING mystream mygroup] 0] == 5} - - # Try to delete a message with ACKED - should fail because both groups have references - assert_equal {2 2 2 2 2} [r XDELEX mystream ACKED IDS 5 0-1 1-1 2-1 3-1 4-1] - - # Destroy one consumer group, and then we can delete all the entries with ACKED. - r XGROUP DESTROY mystream mygroup - assert_equal {1 1 1 1 1} [r XDELEX mystream ACKED IDS 5 0-1 1-1 2-1 3-1 4-1] - assert_equal 0 [r XLEN mystream] - } - - test {XGROUP DESTROY correctly manage min_cgroup_last_id cache} { - r DEL mystream - # Add some entries - r XADD mystream 1-0 f1 v1 - r XADD mystream 2-0 f2 v2 - r XADD mystream 3-0 f3 v3 - r XADD mystream 4-0 f4 v4 - r XADD mystream 5-0 f5 v5 - - # Create two consumer groups - r XGROUP CREATE mystream group1 1-0 ;# min_cgroup_last_id is 1-0 now - r XGROUP CREATE mystream group2 3-0 - - # Entry 1-0 should be deletable (1-0 <= min_cgroup_last_id and not in any PEL) - assert_equal {1} [r XDELEX mystream ACKED IDS 1 1-0] - - # Entry 2-0 should be referenced (2-0 > 1-0, not yet consumed by all consume groups) - assert_equal {2} [r XDELEX mystream ACKED IDS 1 2-0] - - # Destroy group1 - # min_cgroup_last_id is 3-0 now - r XGROUP DESTROY mystream group1 - - # Entry 2-0 should now be deletable (2-0 < 3-0 and not in any PEL) - assert_equal {1} [r XDELEX mystream ACKED IDS 1 2-0] - } - - test {RENAME can unblock XREADGROUP with data} { - r del mystream{t} - r XGROUP CREATE mystream{t} mygroup $ MKSTREAM - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream{t} ">" - wait_for_blocked_clients_count 1 - r XGROUP CREATE mystream2{t} mygroup $ MKSTREAM - r XADD mystream2{t} 100 f1 v1 - r RENAME mystream2{t} mystream{t} - assert_equal "{mystream{t} {{100-0 {f1 v1}}}}" [$rd read] ;# mystream2{t} had mygroup before RENAME - $rd close - } - - test {RENAME can unblock XREADGROUP with -NOGROUP} { - r del mystream{t} - r XGROUP CREATE mystream{t} mygroup $ MKSTREAM - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream{t} ">" - wait_for_blocked_clients_count 1 - r XADD mystream2{t} 100 f1 v1 - r RENAME mystream2{t} mystream{t} - assert_error "*NOGROUP*" {$rd read} ;# mystream2{t} didn't have mygroup before RENAME - $rd close - } - - test {XCLAIM can claim PEL items from another consumer} { - # Add 3 items into the stream, and create a consumer group - r del mystream - set id1 [r XADD mystream * a 1] - set id2 [r XADD mystream * b 2] - set id3 [r XADD mystream * c 3] - r XGROUP CREATE mystream mygroup 0 - - # Consumer 1 reads item 1 from the stream without acknowledgements. - # Consumer 2 then claims pending item 1 from the PEL of consumer 1 - set reply [ - r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream > - ] - assert {[llength [lindex $reply 0 1 0 1]] == 2} - assert {[lindex $reply 0 1 0 1] eq {a 1}} - - # make sure the entry is present in both the group, and the right consumer - assert {[llength [r XPENDING mystream mygroup - + 10]] == 1} - assert {[llength [r XPENDING mystream mygroup - + 10 consumer1]] == 1} - assert {[llength [r XPENDING mystream mygroup - + 10 consumer2]] == 0} - - after 200 - set reply [ - r XCLAIM mystream mygroup consumer2 10 $id1 - ] - assert {[llength [lindex $reply 0 1]] == 2} - assert {[lindex $reply 0 1] eq {a 1}} - - # make sure the entry is present in both the group, and the right consumer - assert {[llength [r XPENDING mystream mygroup - + 10]] == 1} - assert {[llength [r XPENDING mystream mygroup - + 10 consumer1]] == 0} - assert {[llength [r XPENDING mystream mygroup - + 10 consumer2]] == 1} - - # Consumer 1 reads another 2 items from stream - r XREADGROUP GROUP mygroup consumer1 count 2 STREAMS mystream > - after 200 - - # Delete item 2 from the stream. Now consumer 1 has PEL that contains - # only item 3. Try to use consumer 2 to claim the deleted item 2 - # from the PEL of consumer 1, this should be NOP - r XDEL mystream $id2 - set reply [ - r XCLAIM mystream mygroup consumer2 10 $id2 - ] - assert {[llength $reply] == 0} - - # Delete item 3 from the stream. Now consumer 1 has PEL that is empty. - # Try to use consumer 2 to claim the deleted item 3 from the PEL - # of consumer 1, this should be NOP - after 200 - r XDEL mystream $id3 - set reply [ - r XCLAIM mystream mygroup consumer2 10 $id3 - ] - assert {[llength $reply] == 0} - } - - test {XCLAIM without JUSTID increments delivery count} { - # Add 3 items into the stream, and create a consumer group - r del mystream - set id1 [r XADD mystream * a 1] - set id2 [r XADD mystream * b 2] - set id3 [r XADD mystream * c 3] - r XGROUP CREATE mystream mygroup 0 - - # Consumer 1 reads item 1 from the stream without acknowledgements. - # Consumer 2 then claims pending item 1 from the PEL of consumer 1 - set reply [ - r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream > - ] - assert {[llength [lindex $reply 0 1 0 1]] == 2} - assert {[lindex $reply 0 1 0 1] eq {a 1}} - after 200 - set reply [ - r XCLAIM mystream mygroup consumer2 10 $id1 - ] - assert {[llength [lindex $reply 0 1]] == 2} - assert {[lindex $reply 0 1] eq {a 1}} - - set reply [ - r XPENDING mystream mygroup - + 10 - ] - assert {[llength [lindex $reply 0]] == 4} - assert {[lindex $reply 0 3] == 2} - - # Consumer 3 then claims pending item 1 from the PEL of consumer 2 using JUSTID - after 200 - set reply [ - r XCLAIM mystream mygroup consumer3 10 $id1 JUSTID - ] - assert {[llength $reply] == 1} - assert {[lindex $reply 0] eq $id1} - - set reply [ - r XPENDING mystream mygroup - + 10 - ] - assert {[llength [lindex $reply 0]] == 4} - assert {[lindex $reply 0 3] == 2} - } - - test {XCLAIM same consumer} { - # Add 3 items into the stream, and create a consumer group - r del mystream - set id1 [r XADD mystream * a 1] - set id2 [r XADD mystream * b 2] - set id3 [r XADD mystream * c 3] - r XGROUP CREATE mystream mygroup 0 - - set reply [r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream >] - assert {[llength [lindex $reply 0 1 0 1]] == 2} - assert {[lindex $reply 0 1 0 1] eq {a 1}} - after 200 - # re-claim with the same consumer that already has it - assert {[llength [r XCLAIM mystream mygroup consumer1 10 $id1]] == 1} - - # make sure the entry is still in the PEL - set reply [r XPENDING mystream mygroup - + 10] - assert {[llength $reply] == 1} - assert {[lindex $reply 0 1] eq {consumer1}} - } - - test {XAUTOCLAIM can claim PEL items from another consumer} { - # Add 3 items into the stream, and create a consumer group - r del mystream - set id1 [r XADD mystream * a 1] - set id2 [r XADD mystream * b 2] - set id3 [r XADD mystream * c 3] - set id4 [r XADD mystream * d 4] - r XGROUP CREATE mystream mygroup 0 - - # Consumer 1 reads item 1 from the stream without acknowledgements. - # Consumer 2 then claims pending item 1 from the PEL of consumer 1 - set reply [r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream >] - assert_equal [llength [lindex $reply 0 1 0 1]] 2 - assert_equal [lindex $reply 0 1 0 1] {a 1} - after 200 - set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 1] - assert_equal [llength $reply] 3 - assert_equal [lindex $reply 0] "0-0" - assert_equal [llength [lindex $reply 1]] 1 - assert_equal [llength [lindex $reply 1 0]] 2 - assert_equal [llength [lindex $reply 1 0 1]] 2 - assert_equal [lindex $reply 1 0 1] {a 1} - - # Consumer 1 reads another 2 items from stream - r XREADGROUP GROUP mygroup consumer1 count 3 STREAMS mystream > - - # For min-idle-time - after 200 - - # Delete item 2 from the stream. Now consumer 1 has PEL that contains - # only item 3. Try to use consumer 2 to claim the deleted item 2 - # from the PEL of consumer 1, this should return nil - r XDEL mystream $id2 - - # id1 and id3 are self-claimed here but not id2 ('count' was set to 3) - # we make sure id2 is indeed skipped (the cursor points to id4) - set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 3] - - assert_equal [llength $reply] 3 - assert_equal [lindex $reply 0] $id4 - assert_equal [llength [lindex $reply 1]] 2 - assert_equal [llength [lindex $reply 1 0]] 2 - assert_equal [llength [lindex $reply 1 0 1]] 2 - assert_equal [lindex $reply 1 0 1] {a 1} - assert_equal [lindex $reply 1 1 1] {c 3} - assert_equal [llength [lindex $reply 2]] 1 - assert_equal [llength [lindex $reply 2 0]] 1 - - # Delete item 3 from the stream. Now consumer 1 has PEL that is empty. - # Try to use consumer 2 to claim the deleted item 3 from the PEL - # of consumer 1, this should return nil - after 200 - - r XDEL mystream $id4 - - # id1 and id3 are self-claimed here but not id2 and id4 ('count' is default 100) - set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - JUSTID] - - # we also test the JUSTID modifier here. note that, when using JUSTID, - # deleted entries are returned in reply (consistent with XCLAIM). - - assert_equal [llength $reply] 3 - assert_equal [lindex $reply 0] {0-0} - assert_equal [llength [lindex $reply 1]] 2 - assert_equal [lindex $reply 1 0] $id1 - assert_equal [lindex $reply 1 1] $id3 - } - - test {XAUTOCLAIM as an iterator} { - # Add 5 items into the stream, and create a consumer group - r del mystream - set id1 [r XADD mystream * a 1] - set id2 [r XADD mystream * b 2] - set id3 [r XADD mystream * c 3] - set id4 [r XADD mystream * d 4] - set id5 [r XADD mystream * e 5] - r XGROUP CREATE mystream mygroup 0 - - # Read 5 messages into consumer1 - r XREADGROUP GROUP mygroup consumer1 count 90 STREAMS mystream > - - # For min-idle-time - after 200 - - # Claim 2 entries - set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 2] - assert_equal [llength $reply] 3 - set cursor [lindex $reply 0] - assert_equal $cursor $id3 - assert_equal [llength [lindex $reply 1]] 2 - assert_equal [llength [lindex $reply 1 0 1]] 2 - assert_equal [lindex $reply 1 0 1] {a 1} - - # Claim 2 more entries - set reply [r XAUTOCLAIM mystream mygroup consumer2 10 $cursor COUNT 2] - assert_equal [llength $reply] 3 - set cursor [lindex $reply 0] - assert_equal $cursor $id5 - assert_equal [llength [lindex $reply 1]] 2 - assert_equal [llength [lindex $reply 1 0 1]] 2 - assert_equal [lindex $reply 1 0 1] {c 3} - - # Claim last entry - set reply [r XAUTOCLAIM mystream mygroup consumer2 10 $cursor COUNT 1] - assert_equal [llength $reply] 3 - set cursor [lindex $reply 0] - assert_equal $cursor {0-0} - assert_equal [llength [lindex $reply 1]] 1 - assert_equal [llength [lindex $reply 1 0 1]] 2 - assert_equal [lindex $reply 1 0 1] {e 5} - } - - test {XAUTOCLAIM COUNT must be > 0} { - assert_error "ERR COUNT must be > 0" {r XAUTOCLAIM key group consumer 1 1 COUNT 0} - } - - test {XCLAIM with XDEL} { - r DEL x - r XADD x 1-0 f v - r XADD x 2-0 f v - r XADD x 3-0 f v - r XGROUP CREATE x grp 0 - assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}} - r XDEL x 2-0 - assert_equal [r XCLAIM x grp Bob 0 1-0 2-0 3-0] {{1-0 {f v}} {3-0 {f v}}} - assert_equal [r XPENDING x grp - + 10 Alice] {} - } - - test {XCLAIM with trimming} { - r DEL x - r config set stream-node-max-entries 2 - r XADD x 1-0 f v - r XADD x 2-0 f v - r XADD x 3-0 f v - r XGROUP CREATE x grp 0 - assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}} - r XTRIM x MAXLEN 1 - assert_equal [r XCLAIM x grp Bob 0 1-0 2-0 3-0] {{3-0 {f v}}} - assert_equal [r XPENDING x grp - + 10 Alice] {} - } - - test {XAUTOCLAIM with XDEL} { - r DEL x - r XADD x 1-0 f v - r XADD x 2-0 f v - r XADD x 3-0 f v - r XGROUP CREATE x grp 0 - assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}} - r XDEL x 2-0 - assert_equal [r XAUTOCLAIM x grp Bob 0 0-0] {0-0 {{1-0 {f v}} {3-0 {f v}}} 2-0} - assert_equal [r XPENDING x grp - + 10 Alice] {} - } - - test {XAUTOCLAIM with XDEL and count} { - r DEL x - r XADD x 1-0 f v - r XADD x 2-0 f v - r XADD x 3-0 f v - r XGROUP CREATE x grp 0 - assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}} - r XDEL x 1-0 - r XDEL x 2-0 - assert_equal [r XAUTOCLAIM x grp Bob 0 0-0 COUNT 1] {2-0 {} 1-0} - assert_equal [r XAUTOCLAIM x grp Bob 0 2-0 COUNT 1] {3-0 {} 2-0} - assert_equal [r XAUTOCLAIM x grp Bob 0 3-0 COUNT 1] {0-0 {{3-0 {f v}}} {}} - assert_equal [r XPENDING x grp - + 10 Alice] {} - } - - test {XAUTOCLAIM with out of range count} { - assert_error {ERR COUNT*} {r XAUTOCLAIM x grp Bob 0 3-0 COUNT 8070450532247928833} - } - - test {XCLAIM with trimming} { - r DEL x - r config set stream-node-max-entries 2 - r XADD x 1-0 f v - r XADD x 2-0 f v - r XADD x 3-0 f v - r XGROUP CREATE x grp 0 - assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}} - r XTRIM x MAXLEN 1 - assert_equal [r XAUTOCLAIM x grp Bob 0 0-0] {0-0 {{3-0 {f v}}} {1-0 2-0}} - assert_equal [r XPENDING x grp - + 10 Alice] {} - } - - test {XINFO FULL output} { - r del x - r XADD x 100 a 1 - r XADD x 101 b 1 - r XADD x 102 c 1 - r XADD x 103 e 1 - r XADD x 104 f 1 - r XGROUP CREATE x g1 0 - r XGROUP CREATE x g2 0 - r XREADGROUP GROUP g1 Alice COUNT 1 STREAMS x > - r XREADGROUP GROUP g1 Bob COUNT 1 STREAMS x > - r XREADGROUP GROUP g1 Bob NOACK COUNT 1 STREAMS x > - r XREADGROUP GROUP g2 Charlie COUNT 4 STREAMS x > - r XDEL x 103 - - set reply [r XINFO STREAM x FULL] - assert_equal [llength $reply] 30 - assert_equal [dict get $reply length] 4 - assert_equal [dict get $reply entries] "{100-0 {a 1}} {101-0 {b 1}} {102-0 {c 1}} {104-0 {f 1}}" - - # First consumer group - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group name] "g1" - assert_equal [lindex [dict get $group pending] 0 0] "100-0" - set consumer [lindex [dict get $group consumers] 0] - assert_equal [dict get $consumer name] "Alice" - assert_equal [lindex [dict get $consumer pending] 0 0] "100-0" ;# first entry in first consumer's PEL - - # Second consumer group - set group [lindex [dict get $reply groups] 1] - assert_equal [dict get $group name] "g2" - set consumer [lindex [dict get $group consumers] 0] - assert_equal [dict get $consumer name] "Charlie" - assert_equal [lindex [dict get $consumer pending] 0 0] "100-0" ;# first entry in first consumer's PEL - assert_equal [lindex [dict get $consumer pending] 1 0] "101-0" ;# second entry in first consumer's PEL - - set reply [r XINFO STREAM x FULL COUNT 1] - assert_equal [llength $reply] 30 - assert_equal [dict get $reply length] 4 - assert_equal [dict get $reply entries] "{100-0 {a 1}}" - } - - test {Consumer seen-time and active-time} { - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > - after 100 - set reply [r xinfo consumers mystream mygroup] - set consumer_info [lindex $reply 0] - assert {[dict get $consumer_info idle] >= 100} ;# consumer idle (seen-time) - assert_equal [dict get $consumer_info inactive] "-1" ;# consumer inactive (active-time) - - r XADD mystream * f v - r XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > - set reply [r xinfo consumers mystream mygroup] - set consumer_info [lindex $reply 0] - assert_equal [lindex $consumer_info 1] "Alice" ;# consumer name - assert {[dict get $consumer_info idle] < 80} ;# consumer idle (seen-time) - assert {[dict get $consumer_info inactive] < 80} ;# consumer inactive (active-time) - - after 100 - r XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > - set reply [r xinfo consumers mystream mygroup] - set consumer_info [lindex $reply 0] - assert {[dict get $consumer_info idle] < 80} ;# consumer idle (seen-time) - assert {[dict get $consumer_info inactive] >= 100} ;# consumer inactive (active-time) - - - # Simulate loading from RDB - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - set consumer [lindex [dict get $group consumers] 0] - set prev_seen [dict get $consumer seen-time] - set prev_active [dict get $consumer active-time] - - set dump [r DUMP mystream] - r DEL mystream - r RESTORE mystream 0 $dump - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - set consumer [lindex [dict get $group consumers] 0] - assert_equal $prev_seen [dict get $consumer seen-time] - assert_equal $prev_active [dict get $consumer active-time] - } - - test {XGROUP CREATECONSUMER: create consumer if does not exist} { - r del mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream * f v - - set reply [r xinfo groups mystream] - set group_info [lindex $reply 0] - set n_consumers [lindex $group_info 3] - assert_equal $n_consumers 0 ;# consumers number in cg - - # create consumer using XREADGROUP - r XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > - - set reply [r xinfo groups mystream] - set group_info [lindex $reply 0] - set n_consumers [lindex $group_info 3] - assert_equal $n_consumers 1 ;# consumers number in cg - - set reply [r xinfo consumers mystream mygroup] - set consumer_info [lindex $reply 0] - assert_equal [lindex $consumer_info 1] "Alice" ;# consumer name - - # create group using XGROUP CREATECONSUMER when Alice already exists - set created [r XGROUP CREATECONSUMER mystream mygroup Alice] - assert_equal $created 0 - - # create group using XGROUP CREATECONSUMER when Bob does not exist - set created [r XGROUP CREATECONSUMER mystream mygroup Bob] - assert_equal $created 1 - - set reply [r xinfo groups mystream] - set group_info [lindex $reply 0] - set n_consumers [lindex $group_info 3] - assert_equal $n_consumers 2 ;# consumers number in cg - - set reply [r xinfo consumers mystream mygroup] - set consumer_info [lindex $reply 0] - assert_equal [lindex $consumer_info 1] "Alice" ;# consumer name - set consumer_info [lindex $reply 1] - assert_equal [lindex $consumer_info 1] "Bob" ;# consumer name - } - - test {XGROUP CREATECONSUMER: group must exist} { - r del mystream - r XADD mystream * f v - assert_error "*NOGROUP*" {r XGROUP CREATECONSUMER mystream mygroup consumer} - } - - test {XREADGROUP of multiple entries changes dirty by one} { - r DEL x - r XADD x 1-0 data a - r XADD x 2-0 data b - r XADD x 3-0 data c - r XADD x 4-0 data d - r XGROUP CREATE x g1 0 - r XGROUP CREATECONSUMER x g1 Alice - - set dirty [s rdb_changes_since_last_save] - set res [r XREADGROUP GROUP g1 Alice COUNT 2 STREAMS x ">"] - assert_equal $res {{x {{1-0 {data a}} {2-0 {data b}}}}} - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 1} - - set dirty [s rdb_changes_since_last_save] - set res [r XREADGROUP GROUP g1 Alice NOACK COUNT 2 STREAMS x ">"] - assert_equal $res {{x {{3-0 {data c}} {4-0 {data d}}}}} - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 1} - } - - test {XREADGROUP from PEL does not change dirty} { - # Techinally speaking, XREADGROUP from PEL should cause propagation - # because it change the delivery count/time - # It was decided that this metadata changes are too insiginificant - # to justify propagation - # This test covers that. - r DEL x - r XADD x 1-0 data a - r XADD x 2-0 data b - r XADD x 3-0 data c - r XADD x 4-0 data d - r XGROUP CREATE x g1 0 - r XGROUP CREATECONSUMER x g1 Alice - - set res [r XREADGROUP GROUP g1 Alice COUNT 2 STREAMS x ">"] - assert_equal $res {{x {{1-0 {data a}} {2-0 {data b}}}}} - - set dirty [s rdb_changes_since_last_save] - set res [r XREADGROUP GROUP g1 Alice COUNT 2 STREAMS x 0] - assert_equal $res {{x {{1-0 {data a}} {2-0 {data b}}}}} - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty} - - set dirty [s rdb_changes_since_last_save] - set res [r XREADGROUP GROUP g1 Alice COUNT 2 STREAMS x 9000] - assert_equal $res {{x {}}} - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty} - - # The current behavior is that we create the consumer (causes dirty++) even - # if we onlyneed to read from PEL. - # It feels like we shouldn't create the consumer in that case, but I added - # this test just for coverage of current behavior - set dirty [s rdb_changes_since_last_save] - set res [r XREADGROUP GROUP g1 noconsumer COUNT 2 STREAMS x 0] - assert_equal $res {{x {}}} - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 1} - } - - start_server {tags {"stream needs:debug"} overrides {appendonly yes aof-use-rdb-preamble no appendfsync always}} { - test {XREADGROUP with NOACK creates consumer} { - r del mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream * f1 v1 - r XREADGROUP GROUP mygroup Alice NOACK STREAMS mystream ">" - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Bob BLOCK 0 NOACK STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r XADD mystream * f2 v2 - set grpinfo [r xinfo groups mystream] - - r debug loadaof - assert_equal [r xinfo groups mystream] $grpinfo - set reply [r xinfo consumers mystream mygroup] - set consumer_info [lindex $reply 0] - assert_equal [lindex $consumer_info 1] "Alice" ;# consumer name - set consumer_info [lindex $reply 1] - assert_equal [lindex $consumer_info 1] "Bob" ;# consumer name - $rd close - } - - test {Consumer without PEL is present in AOF after AOFRW} { - r del mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - r XADD mystream * f v - r XREADGROUP GROUP mygroup Alice NOACK STREAMS mystream ">" - set rd [redis_deferring_client] - $rd XREADGROUP GROUP mygroup Bob BLOCK 0 NOACK STREAMS mystream ">" - wait_for_blocked_clients_count 1 - r XGROUP CREATECONSUMER mystream mygroup Charlie - set grpinfo [lindex [r xinfo groups mystream] 0] - - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - - set curr_grpinfo [lindex [r xinfo groups mystream] 0] - assert {$curr_grpinfo == $grpinfo} - set n_consumers [lindex $grpinfo 3] - - # All consumers are created via XREADGROUP, regardless of whether they managed - # to read any entries ot not - assert_equal $n_consumers 3 - $rd close - } - } - - test {Consumer group read counter and lag in empty streams} { - r DEL x - r XGROUP CREATE x g1 0 MKSTREAM - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $reply max-deleted-entry-id] "0-0" - assert_equal [dict get $reply entries-added] 0 - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] 0 - - r XADD x 1-0 data a - r XDEL x 1-0 - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $reply max-deleted-entry-id] "1-0" - assert_equal [dict get $reply entries-added] 1 - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] 0 - } - - test {Consumer group read counter and lag sanity} { - r DEL x - r XADD x 1-0 data a - r XADD x 2-0 data b - r XADD x 3-0 data c - r XADD x 4-0 data d - r XADD x 5-0 data e - r XGROUP CREATE x g1 0 - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] 5 - - r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 1 - assert_equal [dict get $group lag] 4 - - r XREADGROUP GROUP g1 c12 COUNT 10 STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 0 - - r XADD x 6-0 data f - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 1 - } - - test {Consumer group lag with XDELs} { - r DEL x - r XADD x 1-0 data a - r XADD x 2-0 data b - r XADD x 3-0 data c - r XADD x 4-0 data d - r XADD x 5-0 data e - r XDEL x 3-0 - r XGROUP CREATE x g1 0 - r XGROUP CREATE x g2 0 - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] {} - - r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] {} - - r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] {} - - r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] {} - - r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 0 - - r XADD x 6-0 data f - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 1 - - r XTRIM x MINID = 3-0 - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 1 - set group [lindex [dict get $reply groups] 1] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] 3 - - r XTRIM x MINID = 5-0 - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 1 - set group [lindex [dict get $reply groups] 1] - assert_equal [dict get $group entries-read] {} - assert_equal [dict get $group lag] 2 - } - - test {Consumer Group Lag with XDELs and tombstone after the last_id of consume group} { - r DEL x - r XGROUP CREATE x g1 $ MKSTREAM - r XADD x 1-0 data a - r XREADGROUP GROUP g1 alice STREAMS x > ;# Read one entry - r XADD x 2-0 data c - r XADD x 3-0 data d - r XDEL x 2-0 - - # Now the latest tombstone(2-0) is before the first entry(3-0), but there is still - # a tombstone(2-0) after the last_id(1-0) of the consume group. - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 1 - assert_equal [dict get $group lag] {} - - r XDEL x 1-0 - # Although there is a tombstone(2-0) after the consumer group's last_id(1-0), all - # entries before the maximal tombstone have been deleted. This means that both the - # last_id and the largest tombstone are behind the first entry. Therefore, tombstones - # no longer affect the lag, which now reflects the remaining entries in the stream. - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 1 - assert_equal [dict get $group lag] 1 - - # Now there is a tombstone(2-0) after the last_id of the consume group, so after consuming - # entry(3-0), the group's counter will be invalid. - r XREADGROUP GROUP g1 alice STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 3 - assert_equal [dict get $group lag] 0 - } - - test {Consumer group lag with XTRIM} { - r DEL x - r XGROUP CREATE x mygroup $ MKSTREAM - r XADD x 1-0 data a - r XADD x 2-0 data b - r XADD x 3-0 data c - r XADD x 4-0 data d - r XADD x 5-0 data e - r XREADGROUP GROUP mygroup alice COUNT 1 STREAMS x > - - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 1 - assert_equal [dict get $group lag] 4 - - # Although XTRIM doesn't update the `max-deleted-entry-id`, it always updates the - # position of the first entry. When trimming causes the first entry to be behind - # the consumer group's last_id, the consumer group's lag will always be equal to - # the number of remainin entries in the stream. - r XTRIM x MAXLEN 1 - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $reply max-deleted-entry-id] "0-0" - assert_equal [dict get $group entries-read] 1 - assert_equal [dict get $group lag] 1 - - # When all the entries are read, the lag is always 0. - r XREADGROUP GROUP mygroup alice STREAMS x > - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 0 - - r XADD x 6-0 data f - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 5 - assert_equal [dict get $group lag] 1 - - # When all the entries were deleted, the lag is always 0. - r XTRIM x MAXLEN 0 - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group lag] 0 - } - - test {Loading from legacy (Redis <= v6.2.x, rdb_ver < 10) persistence} { - # The payload was DUMPed from a v5 instance after: - # XADD x 1-0 data a - # XADD x 2-0 data b - # XADD x 3-0 data c - # XADD x 4-0 data d - # XADD x 5-0 data e - # XADD x 6-0 data f - # XDEL x 3-0 - # XGROUP CREATE x g1 0 - # XGROUP CREATE x g2 0 - # XREADGROUP GROUP g1 c11 COUNT 4 STREAMS x > - # XTRIM x MAXLEN = 2 - - r DEL x - r RESTORE x 0 "\x0F\x01\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4A\x40\x57\x16\x57\x00\x00\x00\x23\x00\x02\x01\x04\x01\x01\x01\x84\x64\x61\x74\x61\x05\x00\x01\x03\x01\x00\x20\x01\x03\x81\x61\x02\x04\x20\x0A\x00\x01\x40\x0A\x00\x62\x60\x0A\x00\x02\x40\x0A\x00\x63\x60\x0A\x40\x22\x01\x81\x64\x20\x0A\x40\x39\x20\x0A\x00\x65\x60\x0A\x00\x05\x40\x0A\x00\x66\x20\x0A\x00\xFF\x02\x06\x00\x02\x02\x67\x31\x05\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x01\x03\x63\x31\x31\x3E\xF7\x83\x43\x7A\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x02\x67\x32\x00\x00\x00\x00\x09\x00\x3D\x52\xEF\x68\x67\x52\x1D\xFA" - - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply max-deleted-entry-id] "0-0" - assert_equal [dict get $reply entries-added] 2 - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 1 - assert_equal [dict get $group lag] 1 - set group [lindex [dict get $reply groups] 1] - assert_equal [dict get $group entries-read] 0 - assert_equal [dict get $group lag] 2 - } - - test {Loading from legacy (Redis <= v7.0.x, rdb_ver < 11) persistence} { - # The payload was DUMPed from a v7 instance after: - # XGROUP CREATE x g $ MKSTREAM - # XADD x 1-1 f v - # XREADGROUP GROUP g Alice STREAMS x > - - r DEL x - r RESTORE x 0 "\x13\x01\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x1D\x1D\x00\x00\x00\x0A\x00\x01\x01\x00\x01\x01\x01\x81\x66\x02\x00\x01\x02\x01\x00\x01\x00\x01\x81\x76\x02\x04\x01\xFF\x01\x01\x01\x01\x01\x00\x00\x01\x01\x01\x67\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\xF5\x5A\x71\xC7\x84\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\xF5\x5A\x71\xC7\x84\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x0B\x00\xA7\xA9\x14\xA5\x27\xFF\x9B\x9B" - set reply [r XINFO STREAM x FULL] - set group [lindex [dict get $reply groups] 0] - set consumer [lindex [dict get $group consumers] 0] - assert_equal [dict get $consumer seen-time] [dict get $consumer active-time] - } - - start_server {tags {"external:skip"}} { - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - set slave [srv 0 client] - - foreach noack {0 1} { - test "Consumer group last ID propagation to slave (NOACK=$noack)" { - $slave slaveof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - - $master del stream - $master xadd stream * a 1 - $master xadd stream * a 2 - $master xadd stream * a 3 - $master xgroup create stream mygroup 0 - - # Consume the first two items on the master - for {set j 0} {$j < 2} {incr j} { - if {$noack} { - set item [$master xreadgroup group mygroup \ - myconsumer COUNT 1 NOACK STREAMS stream >] - } else { - set item [$master xreadgroup group mygroup \ - myconsumer COUNT 1 STREAMS stream >] - } - set id [lindex $item 0 1 0 0] - if {$noack == 0} { - assert {[$master xack stream mygroup $id] eq "1"} - } - } - - wait_for_ofs_sync $master $slave - - # Turn slave into master - $slave slaveof no one - - set item [$slave xreadgroup group mygroup myconsumer \ - COUNT 1 STREAMS stream >] - - # The consumed entry should be the third - set myentry [lindex $item 0 1 0 1] - assert {$myentry eq {a 3}} - } - } - } - - start_server {tags {"external:skip"}} { - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - set replica [srv 0 client] - - foreach autoclaim {0 1} { - test "Replication tests of XCLAIM with deleted entries (autoclaim=$autoclaim)" { - $replica replicaof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - - $master DEL x - $master XADD x 1-0 f v - $master XADD x 2-0 f v - $master XADD x 3-0 f v - $master XADD x 4-0 f v - $master XADD x 5-0 f v - $master XGROUP CREATE x grp 0 - assert_equal [$master XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}} {4-0 {f v}} {5-0 {f v}}}}} - wait_for_ofs_sync $master $replica - assert_equal [llength [$replica XPENDING x grp - + 10 Alice]] 5 - $master XDEL x 2-0 - $master XDEL x 4-0 - if {$autoclaim} { - assert_equal [$master XAUTOCLAIM x grp Bob 0 0-0] {0-0 {{1-0 {f v}} {3-0 {f v}} {5-0 {f v}}} {2-0 4-0}} - wait_for_ofs_sync $master $replica - assert_equal [llength [$replica XPENDING x grp - + 10 Alice]] 0 - } else { - assert_equal [$master XCLAIM x grp Bob 0 1-0 2-0 3-0 4-0] {{1-0 {f v}} {3-0 {f v}}} - wait_for_ofs_sync $master $replica - assert_equal [llength [$replica XPENDING x grp - + 10 Alice]] 1 - } - } - } - - test {XREADGROUP ACK would propagate entries-read} { - $master del mystream - $master xadd mystream * a b c d e f - $master xgroup create mystream mygroup $ - $master xreadgroup group mygroup ryan count 1 streams mystream > - $master xadd mystream * a1 b1 a1 b2 - $master xadd mystream * name v1 name v1 - $master xreadgroup group mygroup ryan count 1 streams mystream > - $master xreadgroup group mygroup ryan count 1 streams mystream > - - set reply [$master XINFO STREAM mystream FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 3 - assert_equal [dict get $group lag] 0 - - wait_for_ofs_sync $master $replica - - set reply [$replica XINFO STREAM mystream FULL] - set group [lindex [dict get $reply groups] 0] - assert_equal [dict get $group entries-read] 3 - assert_equal [dict get $group lag] 0 - } - - test {XREADGROUP from PEL inside MULTI} { - # This scenario used to cause propagation of EXEC without MULTI in 6.2 - $replica config set propagation-error-behavior panic - $master del mystream - $master xadd mystream 1-0 a b c d e f - $master xgroup create mystream mygroup 0 - assert_equal [$master xreadgroup group mygroup ryan count 1 streams mystream >] {{mystream {{1-0 {a b c d e f}}}}} - $master multi - $master xreadgroup group mygroup ryan count 1 streams mystream 0 - $master exec - } - } - - start_server {tags {"stream needs:debug"} overrides {appendonly yes aof-use-rdb-preamble no}} { - test {Empty stream with no lastid can be rewrite into AOF correctly} { - r XGROUP CREATE mystream group-name $ MKSTREAM - assert {[dict get [r xinfo stream mystream] length] == 0} - set grpinfo [r xinfo groups mystream] - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - assert {[dict get [r xinfo stream mystream] length] == 0} - assert_equal [r xinfo groups mystream] $grpinfo - } - } - - start_server {} { - test "XACKDEL wrong number of args" { - assert_error {*wrong number of arguments for 'xackdel' command} {r XACKDEL} - assert_error {*wrong number of arguments for 'xackdel' command} {r XACKDEL s} - assert_error {*wrong number of arguments for 'xackdel' command} {r XACKDEL s g} - } - - test "XACKDEL should return empty array when key doesn't exist or group doesn't exist" { - r DEL s - assert_equal {-1 -1} [r XACKDEL s g IDS 2 1-1 2-2] ;# the key doesn't exist - - r XADD s 1-0 f v - assert_equal {-1 -1} [r XACKDEL s g IDS 2 1-1 2-2] ;# the key exists but the group doesn't exist - } - - test "XACKDEL IDS parameter validation" { - r DEL s - r XADD s 1-0 f v - r XGROUP CREATE s g 0 - - # Test invalid numids - assert_error {*Number of IDs must be a positive integer*} {r XACKDEL s g IDS abc 1-1} - assert_error {*Number of IDs must be a positive integer*} {r XACKDEL s g IDS 0 1-1} - assert_error {*Number of IDs must be a positive integer*} {r XACKDEL s g IDS -5 1-1} - - # Test whether numids is equal to the number of IDs provided - assert_error {*The `numids` parameter must match the number of arguments*} {r XACKDEL s g IDS 3 1-1 2-2} - assert_error {*syntax error*} {r XACKDEL s g IDS 1 1-1 2-2} - } - - test "XACKDEL KEEPREF/DELREF/ACKED parameter validation" { - # Test mutually exclusive options - assert_error {*syntax error*} {r XACKDEL s g KEEPREF DELREF IDS 1 1-1} - assert_error {*syntax error*} {r XACKDEL s g KEEPREF ACKED IDS 1 1-1} - assert_error {*syntax error*} {r XACKDEL s g DELREF ACKED IDS 1 1-1} - } - - test "XACKDEL with DELREF option acknowledges will remove entry from all PELs" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - - # Create two consumer groups - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer2 STREAMS mystream > - - # Verify the message was removed from both groups' PELs when with DELREF - assert_equal {1 1} [r XACKDEL mystream group1 DELREF IDS 2 1-0 2-0] - assert_equal 0 [r XLEN mystream] - assert_equal {0 {} {} {}} [r XPENDING mystream group1] - assert_equal {0 {} {} {}} [r XPENDING mystream group2] - assert_equal {-1 -1} [r XACKDEL mystream group2 DELREF IDS 2 1-0 2-0] - } - - test "XACKDEL with ACKED option only deletes messages acknowledged by all groups" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - - # Create two consumer groups - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer2 STREAMS mystream > - - # The message is referenced by two groups. - # Even after one of them is ack, it still can't be deleted. - assert_equal {2 2} [r XACKDEL mystream group1 ACKED IDS 2 1-0 2-0] - assert_equal 2 [r XLEN mystream] - assert_equal {0 {} {} {}} [r XPENDING mystream group1] - assert_equal {2 1-0 2-0 {{consumer2 2}}} [r XPENDING mystream group2] - - # When these messages are dereferenced by all groups, they can be deleted. - assert_equal {1 1} [r XACKDEL mystream group2 ACKED IDS 2 1-0 2-0] - assert_equal 0 [r XLEN mystream] - assert_equal {0 {} {} {}} [r XPENDING mystream group1] - assert_equal {0 {} {} {}} [r XPENDING mystream group2] - } - - test "XACKDEL with KEEPREF" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - - # Create two consumer groups - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer2 STREAMS mystream > - - # Test XACKDEL with KEEPREF - # XACKDEL only deletes the message from the stream - # but does not clean up references in consumer groups' PELs - assert_equal {1 1} [r XACKDEL mystream group1 KEEPREF IDS 2 1-0 2-0] - assert_equal 0 [r XLEN mystream] - assert_equal {0 {} {} {}} [r XPENDING mystream group1] - assert_equal {2 1-0 2-0 {{consumer2 2}}} [r XPENDING mystream group2] - - # Acknowledge remaining messages in group2 - assert_equal {1 1} [r XACKDEL mystream group2 KEEPREF IDS 2 1-0 2-0] - assert_equal {0 {} {} {}} [r XPENDING mystream group1] - assert_equal {0 {} {} {}} [r XPENDING mystream group2] - } - - test "XGROUP CREATE with ENTRIESREAD larger than stream entries should cap the value" { - r DEL mystream - r xadd mystream * field value - r xgroup create mystream mygroup $ entriesread 9999 - - set reply [r XINFO STREAM mystream FULL] - set group [lindex [dict get $reply groups] 0] - - # Lag must be 0 and entries-read must be 1. - assert_equal [dict get $group lag] 0 - assert_equal [dict get $group entries-read] 1 - } - - test "XGROUP SETID with ENTRIESREAD larger than stream entries should cap the value" { - r DEL mystream - r xadd mystream * field value - r xgroup create mystream mygroup $ - - r xgroup setid mystream mygroup $ entriesread 9999 - - set reply [r XINFO STREAM mystream FULL] - set group [lindex [dict get $reply groups] 0] - - # Lag must be 0 and entries-read must be 1. - assert_equal [dict get $group lag] 0 - assert_equal [dict get $group entries-read] 1 - } - - test "XACKDEL with IDs exceeding STREAMID_STATIC_VECTOR_LEN for heap allocation" { - r DEL mystream - r XGROUP CREATE mystream mygroup $ MKSTREAM - - # Generate IDs exceeding STREAMID_STATIC_VECTOR_LEN (8) to force heap allocation - # instead of using the static vector cache, ensuring proper memory allocation. - set ids {} - for {set i 0} {$i < 50} {incr i} { - lappend ids "$i-1" - } - set result [r XACKDEL mystream mygroup IDS 50 {*}$ids] - assert {[llength $result] == 50} - r PING - } - } - - start_server {tags {"repl external:skip"}} { - test "XREADGROUP CLAIM delivery count increments replicated correctly" { - start_server {tags {"stream"}} { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - - start_server {tags {"stream"}} { - set replica [srv 0 client] - - # Setup replication - $replica replicaof $master_host $master_port - wait_for_sync $replica - - # Setup stream and consumer group on master - $master DEL mystream - $master XADD mystream 1-0 f v1 - $master XGROUP CREATE mystream group1 0 - - # Wait for replication - wait_for_ofs_sync $master $replica - - # First read on master - $master XREADGROUP GROUP group1 consumer1 STREAMS mystream > - wait_for_ofs_sync $master $replica - - # Check initial delivery count on replica - set replica_pending [$replica XPENDING mystream group1 - + 1] - assert_equal [llength $replica_pending] 1 - set delivery_count [lindex [lindex $replica_pending 0] 3] - assert_equal $delivery_count 1 - - # First claim on master - after 100 - set claim_result1 [$master XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - wait_for_ofs_sync $master $replica - - # Check delivery count after first claim - set replica_pending [$replica XPENDING mystream group1 - + 1] - set delivery_count [lindex [lindex $replica_pending 0] 3] - assert_equal $delivery_count 2 - - # Second claim on master - after 100 - set claim_result2 [$master XREADGROUP GROUP group1 consumer3 CLAIM 50 STREAMS mystream >] - wait_for_ofs_sync $master $replica - - # Check final delivery count on replica - set replica_pending [$replica XPENDING mystream group1 - + 1] - assert_equal [llength $replica_pending] 1 - set delivery_count [lindex [lindex $replica_pending 0] 3] - assert_equal $delivery_count 3 - } - } - } - } - - start_server {} { - if {!$::force_resp3} { - test "XREADGROUP CLAIM field types are correct" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - # Read the message with XREADGROUP - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Wait to allow claiming - after 100 - - # Read again with CLAIM using readraw to check field types - r readraw 1 - r deferred 1 - - r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream > - - # Check the response format line by line - # Response structure: *1 (outer array) -> *2 (stream name + messages array) - assert_equal [r read] {*1} ;# Outer array (1 stream) - assert_equal [r read] {*2} ;# Stream data (2 elements: stream name + messages) - assert_equal [r read] {$8} ;# Stream name length - assert_equal [r read] {mystream} ;# Stream name - assert_equal [r read] {*1} ;# Messages array (1 message) - assert_equal [r read] {*4} ;# Message with 4 fields - assert_equal [r read] {$3} ;# Field 1: Message ID length - assert_equal [r read] {1-0} ;# Field 1: Message ID value - assert_equal [r read] {*2} ;# Field 2: Field-value pairs array - assert_equal [r read] {$1} ;# Field-value pair: key length - assert_equal [r read] {f} ;# Field-value pair: key - assert_equal [r read] {$2} ;# Field-value pair: value length - assert_equal [r read] {v1} ;# Field-value pair: value - - # Field 3: Delivery count - should be integer type (:) - set delivery_count_type [r read] - assert_match {:*} $delivery_count_type "Expected delivery count to be integer type (:), got: $delivery_count_type" - - # Field 4: Idle time - should be integer type (:) - set idle_time_type [r read] - assert_match {:*} $idle_time_type "Expected idle time to be integer type (:), got: $idle_time_type" - } - } - - # Restore connection state - r readraw 0 - r deferred 0 - - test "XREADGROUP CLAIM returns unacknowledged messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Verify we got 1 message without acknowledgment - set read_result [r XREADGROUP GROUP group1 consumer1 STREAMS mystream >] - assert_equal [llength [lindex [lindex $read_result 0] 1]] 2 - - # Verify the messages are now in pending state - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 2 - - after 100 - - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - - # Check first message - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 1] [list f v1] - assert {[lindex $messages 0 2] >= 50} - assert_equal [lindex $messages 0 3] 1 - - # Check second message - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 1] [list f v2] - assert {[lindex $messages 1 2] >= 50} - assert_equal [lindex $messages 1 3] 1 - - # Verify pending list now shows messages belong to consumer2 - set pending_range [r XPENDING mystream group1 - + 10] - assert_equal [llength $pending_range] 2 - - # Check that messages are now assigned to consumer2 - assert_equal [lindex [lindex $pending_range 0] 1] "consumer2" - assert_equal [lindex [lindex $pending_range 1] 1] "consumer2" - - # Verify delivery count was incremented in pending list - assert_equal [lindex [lindex $pending_range 0] 3] 2 - assert_equal [lindex [lindex $pending_range 1] 3] 2 - } - - test "XREADGROUP CLAIM respects min-idle-time threshold" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Verify we got 1 message without acknowledgment - set read_result [r XREADGROUP GROUP group1 consumer1 STREAMS mystream >] - assert_equal [llength [lindex [lindex $read_result 0] 1]] 2 - - # Verify the messages are now in pending state - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 2 - - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 100 STREAMS mystream >] - - assert_equal [llength $claim_result] 0 - } - - test "XREADGROUP CLAIM with COUNT limit" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Verify we got 1 message without acknowledgment - set read_result [r XREADGROUP GROUP group1 consumer1 STREAMS mystream >] - assert_equal [llength [lindex [lindex $read_result 0] 1]] 3 - - # Verify the messages are now in pending state - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 3 - - after 100 - - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer2 COUNT 2 CLAIM 50 STREAMS mystream >] - - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - - # Check first message - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 1] [list f v1] - assert {[lindex $messages 0 2] >= 50} - assert_equal [lindex $messages 0 3] 1 - - # Check second message - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 1] [list f v2] - assert {[lindex $messages 1 2] >= 50} - assert_equal [lindex $messages 1 3] 1 - } - - test "XREADGROUP CLAIM without messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XDEL mystream 1-0 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 100 STREAMS mystream >] - - assert_equal [llength $claim_result] 0 - } - - test "XREADGROUP CLAIM without pending messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 100 STREAMS mystream >] - - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - - # Check first message - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 1] [list f v1] - assert_equal [lindex $messages 0 2] 0 - assert_equal [lindex $messages 0 3] 0 - - # Check second message - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 1] [list f v2] - assert_equal [lindex $messages 1 2] 0 - assert_equal [lindex $messages 1 3] 0 - } - - test "XREADGROUP CLAIM message response format" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Verify we got 1 message without acknowledgment - set read_result [r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream >] - assert_equal [llength [lindex [lindex $read_result 0] 1]] 1 - lassign [lindex $read_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength [lindex $messages 0]] 2 - - # Verify the messages are now in pending state - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 1 - - after 100 - - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - - # Check claimed message - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 1] [list f v1] - assert {[lindex $messages 0 2] >= 50 } - assert_equal [lindex $messages 0 3] 1 - - # Check stream message - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 1] [list f v2] - assert_equal [lindex $messages 1 2] 0 - assert_equal [lindex $messages 1 3] 0 - } - - test "XREADGROUP CLAIM delivery count" { - r DEL mystream - r XADD mystream 1-0 f v1 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read message - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - # Claim pending messages one time - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - - # Check delivery count - lassign [lindex $claim_result 0] stream_name messages - assert_equal [lindex $messages 0 3] 1 - - # Claim pending messages with same consumer three times - after 100 - r XREADGROUP GROUP group1 consumer3 CLAIM 50 STREAMS mystream > - - after 100 - r XREADGROUP GROUP group1 consumer3 CLAIM 50 STREAMS mystream > - - after 100 - set claim_result [r XREADGROUP GROUP group1 consumer3 CLAIM 50 STREAMS mystream >] - - # Check delivery count - lassign [lindex $claim_result 0] stream_name messages - assert_equal [lindex $messages 0 3] 4 - - # Claim pending messages with different consumer two times - after 100 - r XREADGROUP GROUP group1 consumer4 CLAIM 50 STREAMS mystream > - - after 100 - set claim_result [r XREADGROUP GROUP group1 consumer5 CLAIM 50 STREAMS mystream >] - - # Check delivery count - lassign [lindex $claim_result 0] stream_name messages - assert_equal [lindex $messages 0 3] 6 - } - - test "XREADGROUP CLAIM idle time" { - r DEL mystream - r XADD mystream 1-0 f v1 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read message - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - # Check idle time - lassign [lindex $claim_result 0] stream_name messages - assert {[lindex $messages 0 2] >= 50} - - # Claim pending messages - after 70 - set claim_result [r XREADGROUP GROUP group1 consumer3 CLAIM 60 STREAMS mystream >] - # Check idle time - lassign [lindex $claim_result 0] stream_name messages - assert {[lindex $messages 0 2] >= 60} - - after 80 - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer3 CLAIM 70 STREAMS mystream >] - # Check idle time - lassign [lindex $claim_result 0] stream_name messages - assert {[lindex $messages 0 2] >= 70} - - after 20 - # Claim pending messages - set claim_result [r XREADGROUP GROUP group1 consumer3 CLAIM 10 STREAMS mystream >] - # Check idle time - lassign [lindex $claim_result 0] stream_name messages - assert {[lindex $messages 0 2] >= 10} - } - - test "XREADGROUP CLAIM with NOACK" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - after 100 - - # Claim with NOACK - set claim_result [r XREADGROUP GROUP group1 consumer1 NOACK CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal [llength $messages] 2 - - # Verify there is no pending messages - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 0 - - # Claim again with NOACK - set claim_result [r XREADGROUP GROUP group1 consumer1 NOACK CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal [llength $messages] 0 - } - - test "XREADGROUP CLAIM with NOACK and pending messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - - # Verify there is one pending message - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 1 - - after 100 - - # Claim with NOACK. We expect one pending message and one from the stream - set claim_result [r XREADGROUP GROUP group1 consumer1 NOACK CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal [llength $messages] 2 - - # Verify there is one pending messages - set pending_info [r XPENDING mystream group1] - assert_equal [lindex $pending_info 0] 1 - - after 100 - - # Claim again with NOACK. We expect only the pending message. - set claim_result [r XREADGROUP GROUP group1 consumer1 NOACK CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal [llength $messages] 1 - assert_equal [lindex $messages 0 0] 1-0 - } - - test "XREADGROUP CLAIM with multiple streams" { - r DEL mystream{t}1 - r XADD mystream{t}1 1-0 f v1 - r XADD mystream{t}1 2-0 f v2 - - r DEL mystream{t}2 - r XADD mystream{t}2 3-0 f v1 - r XADD mystream{t}2 4-0 f v2 - - r DEL mystream{t}3 - r XADD mystream{t}3 5-0 f v1 - r XADD mystream{t}3 6-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream{t}1 group1 0 - r XGROUP CREATE mystream{t}2 group1 0 - r XGROUP CREATE mystream{t}3 group1 0 - - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream{t}1 mystream{t}2 mystream{t}3 > > > - - after 100 - - # Claim messages from multiply streams. - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 50 STREAMS mystream{t}1 mystream{t}2 mystream{t}3 > > >] - - # We expect two messages from the first stream. One pending and one new. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream{t}1" - assert_equal [llength $messages] 2 - # Pending message. - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 1 - # New message - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 0 - - # We expect two messages from the second stream. One pending and one new. - lassign [lindex $claim_result 1] stream_name messages - assert_equal $stream_name "mystream{t}2" - assert_equal [llength $messages] 2 - # Pending message. - assert_equal [lindex $messages 0 0] 3-0 - assert_equal [lindex $messages 0 3] 1 - # New message - assert_equal [lindex $messages 1 0] 4-0 - assert_equal [lindex $messages 1 3] 0 - - # We expect two messages from the third stream. One pending and one new. - lassign [lindex $claim_result 2] stream_name messages - assert_equal $stream_name "mystream{t}3" - assert_equal [llength $messages] 2 - # Pending message. - assert_equal [lindex $messages 0 0] 5-0 - assert_equal [lindex $messages 0 3] 1 - # New message - assert_equal [lindex $messages 1 0] 6-0 - assert_equal [lindex $messages 1 3] 0 - } - - test "XREADGROUP CLAIM with min-idle-time equal to zero" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - - # Claim one message with min-idle-time=0 - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 0 STREAMS mystream >] - - # We expect two messages. One pending and one new. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - # Pending message. - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 1 - # New message - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 0 - } - - test "XREADGROUP CLAIM with large min-idle-time" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - - after 100 - - # Claim one message with large min-idle-time - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 9223372036854775807 STREAMS mystream >] - - # We expect only the new message. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - # New message - assert_equal [lindex $messages 0 0] 2-0 - assert_equal [lindex $messages 0 3] 0 - } - - test "XREADGROUP CLAIM with not integer for min-idle-time" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM test STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM 5.5 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM 5,5 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM "10e" STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM +10 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM *10 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM 10/2 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM 10*2 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM 10€ STREAMS mystream >} - } - - test "XREADGROUP CLAIM with negative integer for min-idle-time" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - - assert_error "*ERR min-idle-time must be a positive integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -10 STREAMS mystream >} - assert_error "*ERR min-idle-time must be a positive integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -42 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -0 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -5.5 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -5,5 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM "-10e" STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -10/2 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM -10*2 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM (-10)*2 STREAMS mystream >} - } - - test "XREADGROUP CLAIM with different position" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - - after 100 - - # Claim one message with CLAIM option after COUNT - set claim_result [r XREADGROUP GROUP group1 consumer1 COUNT 1 CLAIM 50 STREAMS mystream >] - # We expect only the claimed message. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 1 - - after 100 - - # Claim one message with CLAIM option before COUNT - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 50 COUNT 1 STREAMS mystream >] - # We expect only the claimed message. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 2 - - after 100 - - # Claim one message with multiple CLAIM options - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 50 COUNT 1 CLAIM 60 STREAMS mystream >] - # We expect only the claimed message. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 3 - - after 100 - - # Claim one message with CLAIM option before GROUP - set claim_result [r XREADGROUP CLAIM 50 GROUP group1 consumer1 COUNT 1 STREAMS mystream >] - # We expect only the claimed message. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 4 - - # Test error cases with invalid CLAIM syntax - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM 10 CLAIM COUNT 1 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP CLAIM GROUP group1 consumer1 COUNT 1 STREAMS mystream >} - assert_error "*NOGROUP No such key*" {r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream CLAIM 50 >} - assert_error "*ERR Unbalanced*" {r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream CLAIM >} - assert_error "*ERR Invalid stream ID*" {r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > CLAIM 50} - assert_error "*ERR Unbalanced*" {r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > CLAIM} - assert_error "*ERR syntax error*" {r XREADGROUP GROUP group1 CLAIM 50 consumer1 STREAMS mystream >} - assert_error "*ERR min-idle-time is not an integer*" {r XREADGROUP GROUP group1 consumer1 CLAIM STREAMS mystream >} - } {} {external:skip} - - test "XREADGROUP CLAIM with specific ID" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - # Claim option is ignored when we specify ID different than >. - set claim_result [r XREADGROUP GROUP group1 consumer1 CLAIM 1000 STREAMS mystream 0] - - # We expect only the new message. - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - assert_equal [llength [lindex $messages 0]] 2 - } - - test "XREADGROUP CLAIM on non-existing consumer group" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read all messages - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - # We expect error. Group does not exists. - assert_error "*NOGROUP No such key*" {r XREADGROUP GROUP not_existing_group consumer1 CLAIM 50 STREAMS mystream >} - } - - test "XREADGROUP CLAIM on non-existing consumer" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read all messages - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - # We expect 3 messages. Consumer is created if not exist. - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - } - - test "XREADGROUP CLAIM verify ownership transfer and delivery count updates" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer groups - r XGROUP CREATE mystream group1 0 - - # Read one message - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - # Transfer ownership to consumer2 - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - - # Verify ownership transfer and delivery count updates - set pending_info [r XPENDING mystream group1 - + 10] - - assert_equal [llength $pending_info] 3 - - # Check first message entry - assert_equal [lindex $pending_info 0 0] "1-0" - assert_equal [lindex $pending_info 0 1] "consumer2" - assert_equal [lindex $pending_info 0 3] 2 - - # Check second message entry - assert_equal [lindex $pending_info 1 0] "2-0" - assert_equal [lindex $pending_info 1 1] "consumer2" - assert_equal [lindex $pending_info 1 3] 2 - - # Check third message entry - assert_equal [lindex $pending_info 2 0] "3-0" - assert_equal [lindex $pending_info 2 1] "consumer2" - assert_equal [lindex $pending_info 2 3] 2 - } - - test "XREADGROUP CLAIM verify XACK removes messages from CLAIM pool" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - # Read all three messages with consumer1 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Acknowledge messages 1-0 and 3-0, leaving 2-0 pending - r XACK mystream group1 1-0 3-0 - - after 100 - - # Claim pending messages older than 50ms for consumer2 - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - - # Should claim only message 2-0 (the unacknowledged one) - assert_equal [llength $messages] 1 - assert_equal [lindex $messages 0 0] 2-0 - - # Acknowledge message 2-0 - r XACK mystream group1 2-0 - - after 100 - - # Attempt to claim again - should return nothing since all messages are acknowledged - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - assert_equal [llength $claim_result] 0 - } - - test "XREADGROUP CLAIM verify that XCLAIM updates delivery count" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - # Read all three messages with consumer1 (delivery count becomes 1 for all) - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - # This increments delivery count to 2 for these messages - r XCLAIM mystream group1 consumer3 50 2-0 3-0 - - after 100 - - # This should claim all three messages and increment their delivery counts - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - - # Message 1-0: only claimed once via XREADGROUP (delivery count = 1) - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 1 - - # Message 2-0: claimed via XCLAIM then XREADGROUP (delivery count = 2) - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 2 - - # Message 3-0: claimed via XCLAIM then XREADGROUP (delivery count = 2) - assert_equal [lindex $messages 2 0] 3-0 - assert_equal [lindex $messages 2 3] 2 - } - - test "XREADGROUP CLAIM verify forced entries are claimable" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - r XCLAIM mystream group1 consumer3 0 1-0 2-0 FORCE JUSTID - - # This should claim all three messages and increment their delivery counts - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 0 COUNT 2 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - - # Message 1-0: only claimed once via XREADGROUP (delivery count = 1) - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 1 - - # Message 2-0: claimed via XCLAIM then XREADGROUP (delivery count = 2) - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 1 - } - - test "XREADGROUP CLAIM with BLOCK zero" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - # Read all three messages with consumer1 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - set claim_result [r XREADGROUP GROUP group1 consumer1 BLOCK 1 STREAMS mystream >] - assert_equal [llength $claim_result] 0 - - set claim_result [r XREADGROUP GROUP group1 consumer1 BLOCK 100 CLAIM 500 STREAMS mystream >] - assert_equal [llength $claim_result] 0 - - after 100 - - set claim_result [r XREADGROUP GROUP group1 consumer1 BLOCK 10000 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - - after 100 - - set claim_result [r XREADGROUP GROUP group1 consumer1 BLOCK 0 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - } - - test "XREADGROUP CLAIM with two blocked clients" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XDEL mystream 1-0 - - # Create consumer group - r XGROUP CREATE mystream group1 0 MKSTREAM - - # Create two deferring clients for blocking reads - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - # Both clients issue blocking XREADGROUP commands - $rd1 XREADGROUP GROUP group1 consumer1 BLOCK 0 CLAIM 100 STREAMS mystream ">" - $rd2 XREADGROUP GROUP group1 consumer2 BLOCK 0 CLAIM 100 STREAMS mystream ">" - - # Wait for both clients to be blocked - wait_for_blocked_clients_count 2 - - r XADD mystream 2-0 f v2 - - set result1 [$rd1 read] - assert_equal [llength $result1] 1 - - set result2 [$rd2 read] - assert_equal [llength $result2] 1 - - # Clean up - $rd1 close - $rd2 close - } - - test "XREADGROUP CLAIM messages become claimable during block" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - # Consumer1 reads but doesn't ack - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Consumer2 blocks with CLAIM - message not yet claimable - set rd [redis_deferring_client] - $rd XREADGROUP GROUP group1 consumer2 BLOCK 5000 CLAIM 1000 STREAMS mystream > - - wait_for_blocked_client - - # Wait for message to become claimable (>1000ms) - after 1500 - - # Should unblock and return the now-claimable message - set result [$rd read] - assert_equal [llength $result] 1 - - $rd close - } - - test "XREADGROUP CLAIM block times out with no claimable messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - # Read and immediately try to claim (not idle enough) - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - set start [clock milliseconds] - set result [r XREADGROUP GROUP group1 consumer2 BLOCK 100 CLAIM 500 STREAMS mystream >] - set elapsed [expr {[clock milliseconds] - $start}] - - # Should timeout and return empty - assert_equal [llength $result] 0 - assert_range $elapsed 100 300 - } - - test "XREADGROUP CLAIM block with multiple streams, mixed claimable" { - r DEL stream{t}1 stream{t}2 - r XADD stream{t}1 1-0 f v1 - r XADD stream{t}2 2-0 f v2 - - r XGROUP CREATE stream{t}1 group1 0 - r XGROUP CREATE stream{t}2 group1 0 - - # Reads from both - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS stream{t}1 stream{t}2 > > - - after 100 - - # Blocks with CLAIM - should get all messages - set result [r XREADGROUP GROUP group1 consumer2 BLOCK 1000 CLAIM 50 STREAMS stream{t}1 stream{t}2 > >] - - assert_equal [llength $result] 2 - # stream1 should have claimable message - lassign [lindex $result 0] stream_name messages - assert_equal $stream_name "stream{t}1" - assert_equal [llength $messages] 1 - - # stream2 should be empty (message not yet read) - lassign [lindex $result 1] stream_name messages - assert_equal $stream_name "stream{t}2" - assert_equal [llength $messages] 1 - } - - test "XREADGROUP CLAIM claims all pending immediately" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - # Consumer1 reads - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Consumer2 immediately tries to claim with min-idle-time=0 - set result [r XREADGROUP GROUP group1 consumer2 BLOCK 1000 CLAIM 0 STREAMS mystream >] - - # Should immediately return without blocking - lassign [lindex $result 0] stream_name messages - assert_equal [llength $messages] 1 - } - - test "XREADGROUP CLAIM with BLOCK and NOACK" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - # Consumer1 reads without ack - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - # Consumer2 tries to claim with NOACK - set result [r XREADGROUP GROUP group1 consumer2 BLOCK 1000 CLAIM 50 NOACK STREAMS mystream >] - - lassign [lindex $result 0] stream_name messages - assert_equal [llength $messages] 1 - - # Verify message still pending - set pending [r XPENDING mystream group1 - + 10] - assert_equal [llength $pending] 1 - } - - test "XREADGROUP CLAIM BLOCK wakes on new message before min-idle-time reached" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - set rd [redis_deferring_client] - $rd XREADGROUP GROUP group1 consumer2 BLOCK 5000 CLAIM 1000 STREAMS mystream > - - wait_for_blocked_client - - after 100 # Before min-idle-time - r XADD mystream 2-0 f v2 - - set result [$rd read] - - # Unblock with new message immediately, not wait for CLAIM threshold - lassign [lindex $result 0] stream_name messages - assert_equal [llength $messages] 1 - - $rd close - } - - test "XREADGROUP CLAIM verify claiming order" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - r XADD mystream 4-0 f v4 - r XADD mystream 5-0 f v5 - r XADD mystream 6-0 f v6 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - # Read all messages with consumer1 to make them pending - r XREADGROUP GROUP group1 consumer1 COUNT 10 STREAMS mystream > - - # Now use XCLAIM to explicitly set different delivery times for each message - # We'll set the delivery time backwards in time by different amounts - # to create known idle time differences without actually waiting - set current_time [r TIME] - set current_ms [expr {[lindex $current_time 0] * 1000 + [lindex $current_time 1] / 1000}] - - # Set delivery times: 1-0 is oldest (5000ms ago), 6-0 is newest (100ms ago) - # Use larger values for robustness against timing variations - r XCLAIM mystream group1 consumer1 0 1-0 TIME [expr {$current_ms - 50000}] JUSTID - r XCLAIM mystream group1 consumer1 0 2-0 TIME [expr {$current_ms - 40000}] JUSTID - r XCLAIM mystream group1 consumer1 0 3-0 TIME [expr {$current_ms - 30000}] JUSTID - r XCLAIM mystream group1 consumer1 0 4-0 TIME [expr {$current_ms - 20000}] JUSTID - r XCLAIM mystream group1 consumer1 0 5-0 TIME [expr {$current_ms - 2000}] JUSTID - r XCLAIM mystream group1 consumer1 0 6-0 TIME [expr {$current_ms - 1000}] JUSTID - - # Now claim with threshold of 250ms - should get 1-0, 2-0, 3-0, 4-0 in that order - # (idle times: 50000, 40000, 30000, 20000ms all >= 10000ms) - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 10000 COUNT 10 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 4 - - # Verify order: oldest first - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 2 0] 3-0 - assert_equal [lindex $messages 3 0] 4-0 - - # Claim with threshold of 1500ms - should get remaining 5-0 - # (idle time: 200ms >= 1500ms, but 6-0 with 1000ms < 1500ms) - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 1500 COUNT 10 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - - assert_equal [lindex $messages 0 0] 5-0 - - # Claim with threshold of 500ms - should get last one (6-0) - # (idle time: 100ms >= 500ms) - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 500 COUNT 10 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 1 - - assert_equal [lindex $messages 0 0] 6-0 - } - - test "XREADGROUP CLAIM after consumer deleted with pending messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XGROUP DELCONSUMER mystream group1 consumer1 - - set pending [r XPENDING mystream group1 - + 10] - assert_equal [llength $pending] 0 - - after 100 - - # Orphaned pending messages are deleted. - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - assert_equal [llength $claim_result] 0 - } - - test "XREADGROUP CLAIM after XGROUP SETID moves past pending messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XGROUP SETID mystream group1 2-0 - - after 100 - - # Pending messages are still claimable - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - } - - test "XREADGROUP CLAIM after XGROUP SETID moves before pending messages" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group1 consumer2 CLAIM 0 STREAMS mystream > - r XGROUP SETID mystream group1 0 - - after 100 - - # Pending messages are still claimable - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 4 - - # Message 1-0: claimed by consumer2 (delivery count = 2) - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 2 - - # Message 2-0: claimed by consumer2 (delivery count = 2) - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 2 - - # Message 1-0: claimed by consumer2 (delivery count = 0) - assert_equal [lindex $messages 2 0] 1-0 - assert_equal [lindex $messages 2 3] 0 - - # Message 2-0: claimed by consumer2 (delivery count = 0) - assert_equal [lindex $messages 3 0] 2-0 - assert_equal [lindex $messages 3 3] 0 - - after 100 - - # Verify that pending messages are not doubled - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 2 - - # Message 1-0: claimed by consumer2 (delivery count = 1) - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 1 - - # Message 2-0: claimed by consumer2 (delivery count = 1) - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 1 - } - - test "XREADGROUP CLAIM when pending messages get trimmed" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - # Create consumer group - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Trim away the pending messages - r XTRIM mystream MAXLEN 0 - - after 100 - - # Pending list still references trimmed messages but they don't exist. We can't return them. - set claim_result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream >] - assert_equal [llength $claim_result] 0 - } - - test "XREADGROUP CLAIM state persists across RDB save/load" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - r XADD mystream 3-0 f v3 - - r XGROUP CREATE mystream group1 0 - - # Read messages to create pending entries - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - # Claim some messages to increment delivery count - r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream > - - # Trigger RDB save and restart - r SAVE - r DEBUG RELOAD - - # Verify pending state restored - set pending_info [r XPENDING mystream group1 - + 10] - assert_equal [llength $pending_info] 3 - - # Check first message entry - assert_equal [lindex $pending_info 0 0] "1-0" - assert_equal [lindex $pending_info 0 1] "consumer2" - assert_equal [lindex $pending_info 0 3] 2 - - # Check second message entry - assert_equal [lindex $pending_info 1 0] "2-0" - assert_equal [lindex $pending_info 1 1] "consumer2" - assert_equal [lindex $pending_info 1 3] 2 - - # Check third message entry - assert_equal [lindex $pending_info 2 0] "3-0" - assert_equal [lindex $pending_info 2 1] "consumer2" - assert_equal [lindex $pending_info 2 3] 2 - - # Verify can still claim after reload - after 100 - set claim_result [r XREADGROUP GROUP group1 consumer3 CLAIM 50 STREAMS mystream >] - lassign [lindex $claim_result 0] stream_name messages - assert_equal $stream_name "mystream" - assert_equal [llength $messages] 3 - - # Message 1-0: claimed by consumer3 (delivery count = 2) - assert_equal [lindex $messages 0 0] 1-0 - assert_equal [lindex $messages 0 3] 2 - - # Message 2-0: claimed by consumer3 (delivery count = 2) - assert_equal [lindex $messages 1 0] 2-0 - assert_equal [lindex $messages 1 3] 2 - - # Message 2-0: claimed by consumer3 (delivery count = 2) - assert_equal [lindex $messages 2 0] 3-0 - assert_equal [lindex $messages 2 3] 2 - } {} {external:skip needs:debug} - - test "XREADGROUP CLAIM idle time resets after RDB reload" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 1000 - - # Before reload: message should be claimable - set claim_before [r XREADGROUP GROUP group1 consumer2 CLAIM 500 STREAMS mystream >] - assert_equal [llength [lindex $claim_before 0 1]] 1 - - r SAVE - r DEBUG RELOAD - - # After reload: idle time resets, message not immediately claimable - set claim_after [r XREADGROUP GROUP group1 consumer3 CLAIM 500 STREAMS mystream >] - assert_equal [llength $claim_after] 0 - - } {} {external:skip needs:debug} - - test "XREADGROUP CLAIM multiple groups persist correctly" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XADD mystream 2-0 f v2 - - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - - r XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer1 STREAMS mystream > - - after 100 - r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream > - - r SAVE - r DEBUG RELOAD - - # Verify both groups maintained separately - set pending1 [r XPENDING mystream group1] - set pending2 [r XPENDING mystream group2] - - assert_equal [lindex $pending1 0] 2 ;# group1 has 2 pending - assert_equal [lindex $pending2 0] 2 ;# group2 has 2 pending - } {} {external:skip needs:debug} - - test "XREADGROUP CLAIM NOACK state not persisted" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - after 100 - r XREADGROUP GROUP group1 consumer1 NOACK CLAIM 50 STREAMS mystream > - - set pending_before [r XPENDING mystream group1] - assert_equal [lindex $pending_before 0] 0 - - r SAVE - r DEBUG RELOAD - - set pending_after [r XPENDING mystream group1] - assert_equal [lindex $pending_after 0] 0 - } {} {external:skip needs:debug} - - test "XREADGROUP CLAIM high delivery counts persist in RDB" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Claim multiple times to increase delivery count - for {set i 0} {$i < 10} {incr i} { - after 20 - r XREADGROUP GROUP group1 consumer2 CLAIM 10 STREAMS mystream > - } - - set pending_before [r XPENDING mystream group1 - + 1] - set delivery_before [lindex $pending_before 0 3] - - r SAVE - r DEBUG RELOAD - - set pending_after [r XPENDING mystream group1 - + 1] - set delivery_after [lindex $pending_after 0 3] - - assert_equal $delivery_before $delivery_after - } {} {external:skip needs:debug} - - test "XREADGROUP CLAIM usage stability with repeated claims" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # Claim same message many times between consumers - for {set i 0} {$i < 1000} {incr i} { - after 2 - set consumer_id [expr {$i % 10 + 1}] - r XREADGROUP GROUP group1 consumer$consumer_id CLAIM 1 STREAMS mystream > - } - - # Verify no memory leaks - PEL should still have only 1 message - set pending [r XPENDING mystream group1] - assert_equal [lindex $pending 0] 1 - } - - test "XREADGROUP CLAIM with large number of PEL messages" { - r DEL mystream - r XGROUP CREATE mystream group1 0 MKSTREAM - - # Create large PEL - for {set i 0} {$i < 10000} {incr i} { - r XADD mystream * field $i - } - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - set result [r XREADGROUP GROUP group1 consumer2 CLAIM 50 COUNT 1000 STREAMS mystream >] - assert_equal [llength [lindex $result 0 1]] 1000 - } - - test "XREADGROUP CLAIM within MULTI/EXEC transaction" { - r DEL mystream - r XADD mystream 1-0 f v1 - r XGROUP CREATE mystream group1 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - after 100 - - r MULTI - r XREADGROUP GROUP group1 consumer2 CLAIM 50 STREAMS mystream > - r XPENDING mystream group1 - set results [r EXEC] - - # Verify transaction atomicity - assert_equal [lindex $results 1 0] 1 - } - - test "XREAD with CLAIM option" { - r DEL mystream - r XADD mystream 1-0 f v1 - - assert_error "*ERR The CLAIM option is only supported*" {r XREAD COUNT 2 CLAIM 10 STREAMS mystream 0-0} - } - } -} diff --git a/examples/redis-unstable/tests/unit/type/stream.tcl b/examples/redis-unstable/tests/unit/type/stream.tcl deleted file mode 100644 index da9d340..0000000 --- a/examples/redis-unstable/tests/unit/type/stream.tcl +++ /dev/null @@ -1,3283 +0,0 @@ -# return value is like strcmp() and similar. -proc streamCompareID {a b} { - if {$a eq $b} {return 0} - lassign [split $a -] a_ms a_seq - lassign [split $b -] b_ms b_seq - if {$a_ms > $b_ms} {return 1} - if {$a_ms < $b_ms} {return -1} - # Same ms case, compare seq. - if {$a_seq > $b_seq} {return 1} - if {$a_seq < $b_seq} {return -1} -} - -# return the ID immediately greater than the specified one. -# Note that this function does not care to handle 'seq' overflow -# since it's a 64 bit value. -proc streamNextID {id} { - lassign [split $id -] ms seq - incr seq - join [list $ms $seq] - -} - -# Generate a random stream entry ID with the ms part between min and max -# and a low sequence number (0 - 999 range), in order to stress test -# XRANGE against a Tcl implementation implementing the same concept -# with Tcl-only code in a linear array. -proc streamRandomID {min_id max_id} { - lassign [split $min_id -] min_ms min_seq - lassign [split $max_id -] max_ms max_seq - set delta [expr {$max_ms-$min_ms+1}] - set ms [expr {$min_ms+[randomInt $delta]}] - set seq [randomInt 1000] - return $ms-$seq -} - -# Tcl-side implementation of XRANGE to perform fuzz testing in the Redis -# XRANGE implementation. -proc streamSimulateXRANGE {items start end} { - set res {} - foreach i $items { - set this_id [lindex $i 0] - if {[streamCompareID $this_id $start] >= 0} { - if {[streamCompareID $this_id $end] <= 0} { - lappend res $i - } - } - } - return $res -} - -set content {} ;# Will be populated with Tcl side copy of the stream content. - -start_server { - tags {"stream"} -} { - test "XADD wrong number of args" { - assert_error {*wrong number of arguments for 'xadd' command} {r XADD mystream} - assert_error {*wrong number of arguments for 'xadd' command} {r XADD mystream *} - assert_error {*wrong number of arguments for 'xadd' command} {r XADD mystream * field} - } - - test {XADD can add entries into a stream that XRANGE can fetch} { - r XADD mystream * item 1 value a - r XADD mystream * item 2 value b - assert_equal 2 [r XLEN mystream] - set items [r XRANGE mystream - +] - assert_equal [lindex $items 0 1] {item 1 value a} - assert_equal [lindex $items 1 1] {item 2 value b} - } - - test {XADD IDs are incremental} { - set id1 [r XADD mystream * item 1 value a] - set id2 [r XADD mystream * item 2 value b] - set id3 [r XADD mystream * item 3 value c] - assert {[streamCompareID $id1 $id2] == -1} - assert {[streamCompareID $id2 $id3] == -1} - } - - test {XADD IDs are incremental when ms is the same as well} { - r multi - r XADD mystream * item 1 value a - r XADD mystream * item 2 value b - r XADD mystream * item 3 value c - lassign [r exec] id1 id2 id3 - assert {[streamCompareID $id1 $id2] == -1} - assert {[streamCompareID $id2 $id3] == -1} - } - - test {XADD IDs correctly report an error when overflowing} { - r DEL mystream - r xadd mystream 18446744073709551615-18446744073709551615 a b - assert_error ERR* {r xadd mystream * c d} - } - - test {XADD auto-generated sequence is incremented for last ID} { - r DEL mystream - set id1 [r XADD mystream 123-456 item 1 value a] - set id2 [r XADD mystream 123-* item 2 value b] - lassign [split $id2 -] _ seq - assert {$seq == 457} - assert {[streamCompareID $id1 $id2] == -1} - } - - test {XADD auto-generated sequence is zero for future timestamp ID} { - r DEL mystream - set id1 [r XADD mystream 123-456 item 1 value a] - set id2 [r XADD mystream 789-* item 2 value b] - lassign [split $id2 -] _ seq - assert {$seq == 0} - assert {[streamCompareID $id1 $id2] == -1} - } - - test {XADD auto-generated sequence can't be smaller than last ID} { - r DEL mystream - r XADD mystream 123-456 item 1 value a - assert_error ERR* {r XADD mystream 42-* item 2 value b} - } - - test {XADD auto-generated sequence can't overflow} { - r DEL mystream - r xadd mystream 1-18446744073709551615 a b - assert_error ERR* {r xadd mystream 1-* c d} - } - - test {XADD 0-* should succeed} { - r DEL mystream - set id [r xadd mystream 0-* a b] - lassign [split $id -] _ seq - assert {$seq == 1} - } - - test {XADD with MAXLEN option} { - r DEL mystream - for {set j 0} {$j < 1000} {incr j} { - if {rand() < 0.9} { - r XADD mystream MAXLEN 5 * xitem $j - } else { - r XADD mystream MAXLEN 5 * yitem $j - } - } - assert {[r xlen mystream] == 5} - set res [r xrange mystream - +] - set expected 995 - foreach r $res { - assert {[lindex $r 1 1] == $expected} - incr expected - } - } - - test {XADD with MAXLEN option and the '=' argument} { - r DEL mystream - for {set j 0} {$j < 1000} {incr j} { - if {rand() < 0.9} { - r XADD mystream MAXLEN = 5 * xitem $j - } else { - r XADD mystream MAXLEN = 5 * yitem $j - } - } - assert {[r XLEN mystream] == 5} - } - - test {XADD with MAXLEN option and the '~' argument} { - r DEL mystream - r config set stream-node-max-entries 100 - for {set j 0} {$j < 1000} {incr j} { - if {rand() < 0.9} { - r XADD mystream MAXLEN ~ 555 * xitem $j - } else { - r XADD mystream MAXLEN ~ 555 * yitem $j - } - } - assert {[r XLEN mystream] == 600} - } - - test {XADD with NOMKSTREAM option} { - r DEL mystream - assert_equal "" [r XADD mystream NOMKSTREAM * item 1 value a] - assert_equal 0 [r EXISTS mystream] - r XADD mystream * item 1 value a - r XADD mystream NOMKSTREAM * item 2 value b - assert_equal 2 [r XLEN mystream] - set items [r XRANGE mystream - +] - assert_equal [lindex $items 0 1] {item 1 value a} - assert_equal [lindex $items 1 1] {item 2 value b} - } - - test {XADD with MINID option} { - r DEL mystream - for {set j 1} {$j < 1001} {incr j} { - set minid 1000 - if {$j >= 5} { - set minid [expr {$j-5}] - } - if {rand() < 0.9} { - r XADD mystream MINID $minid $j xitem $j - } else { - r XADD mystream MINID $minid $j yitem $j - } - } - assert {[r xlen mystream] == 6} - set res [r xrange mystream - +] - set expected 995 - foreach r $res { - assert {[lindex $r 1 1] == $expected} - incr expected - } - } - - test {XADD with MAXLEN option and ACKED option} { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - r XADD mystream 3-0 f v - r XADD mystream 4-0 f v - r XADD mystream 5-0 f v - assert {[r XLEN mystream] == 5} - - # Create a consumer group but don't read any messages yet - # ACKED option should preserve all messages since none are acked. - r XGROUP CREATE mystream mygroup 0 - r XADD mystream MAXLEN = 1 ACKED 6-0 f v - assert {[r XLEN mystream] == 6} ;# All messages preserved + the new one - - # Read 1 messages and acknowledge them - # This leaves 5 messages still unacked - set records [r XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >] - r XACK mystream mygroup [lindex [lindex [lindex [lindex $records 0] 1] 0] 0] - assert {[lindex [r XPENDING mystream mygroup] 0] == 0} - - # With 5 messages still unacked, ACKED option should preserve them - r XADD mystream MAXLEN = 1 ACKED 7-0 f v - assert {[r XLEN mystream] == 6} ;# 6 - 1 acked + 1 new - - # Acknowledge all remaining messages - set records [r XREADGROUP GROUP mygroup consumer1 STREAMS mystream >] - set ids {} - foreach entry [lindex [lindex $records 0] 1] { - lappend ids [lindex $entry 0] - } - r XACK mystream mygroup {*}$ids - assert {[lindex [r XPENDING mystream mygroup] 0] == 0} ;# All messages acked - - # Now ACKED should trim to MAXLEN since all messages are acked - r XADD mystream MAXLEN = 1 ACKED * f v - assert {[r XLEN mystream] == 1} ;# Successfully trimmed to 1 entries - } - - test {XADD with ACKED option doesn't crash after DEBUG RELOAD} { - r DEL mystream - r XADD mystream 1-0 f v - - # Create a consumer group and read one message - r XGROUP CREATE mystream mygroup 0 - set records [r XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >] - assert_equal [lindex [r XPENDING mystream mygroup] 0] 1 - - # After reload, the reference relationship between consumer groups and messages - # is correctly rebuilt, so the previously read but unacked message still cannot be deleted. - r DEBUG RELOAD - r XADD mystream MAXLEN = 1 ACKED 2-0 f v - assert_equal [r XLEN mystream] 2 - - # Acknowledge the read message so the PEL becomes empty - r XACK mystream mygroup [lindex [lindex [lindex [lindex $records 0] 1] 0] 0] - assert {[lindex [r XPENDING mystream mygroup] 0] == 0} - - # After reload, since PEL is empty, no cgroup references will be recreated. - r DEBUG RELOAD - - # ACKED option should work correctly even without cgroup references. - r XADD mystream MAXLEN = 1 ACKED 3-0 f v - assert_equal [r XLEN mystream] 2 - } {} {needs:debug} - - test {XADD with MAXLEN option and DELREF option} { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - r XADD mystream 3-0 f v - r XADD mystream 4-0 f v - r XADD mystream 5-0 f v - - r XGROUP CREATE mystream mygroup 0 - r XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream > - - # XADD with MAXLEN and DELREF should trim and remove all references - r XADD mystream MAXLEN = 1 DELREF * f v - assert {[r XLEN mystream] == 1} - - # All PEL entries should be cleaned up - assert {[lindex [r XPENDING mystream mygroup] 0] == 0} - } - - test {XADD IDMP with invalid syntax} { - r DEL mystream - assert_error "*ERR Invalid stream ID specified*" {r XADD mystream IDMP p1 * f v} - assert_error "*IDMP/IDMPAUTO can be used only with auto-generated IDs*" {r XADD mystream IDMP p1 iid1 1-1 f v} - assert_error "*IDMP/IDMPAUTO specified multiple times*" {r XADD mystream IDMP p1 iid1 IDMP p2 iid2 * f v} - assert_error "*IDMP/IDMPAUTO specified multiple times*" {r XADD mystream IDMPAUTO p1 IDMP p2 iid2 * f v} - assert_error "*IDMP requires a non-empty producer ID*" {r XADD mystream IDMP "" iid1 * f v} - assert_error "*IDMP requires a non-empty idempotent ID*" {r XADD mystream IDMP p1 "" * f v} - assert_error "*IDMPAUTO requires a non-empty producer ID*" {r XADD mystream IDMPAUTO "" * f v} - } - - test {XADD IDMP basic addition} { - r DEL mystream - - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 1 * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 A * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 B * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 - * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 + * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 * * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 ^ * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 $ * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 # * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 @ * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 ? * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 \\ * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 IDMP * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 123-456 * f v]]} - - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 9999999999999-9999999999999 * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "hello世界" * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "héllo" * f v]]} - - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "line1\nline2" * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "tab\there" * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "quote\"test" * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "with spaces" * f v]]} - - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 [string repeat "long" 100] * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 [string repeat "x" 1000] * f v]]} - - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "special!@#$%^&*()" * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "path/to/file" * f v]]} - assert {[regexp {^[0-9]+-[0-9]+$} [r XADD mystream IDMP p1 "key:value" * f v]]} - - assert_equal 26 [r XLEN mystream] - } - - test "XADD IDMP duplicate request returns same ID" { - r DEL mystream - - # First XADD with IDMP - set id1 [r XADD mystream IDMP p1 "payment-abc" * amount "100" currency "USD"] - - # Second XADD with same iid but different fields - set id2 [r XADD mystream IDMP p1 "payment-abc" * amount "200" currency "EUR"] - - # Verify both IDs are identical - assert_equal $id1 $id2 - - # Verify only one entry exists - assert_equal 1 [r XLEN mystream] - - # Verify original fields are preserved - set entries [r XRANGE mystream - +] - set fields [lindex [lindex $entries 0] 1] - assert_equal "100" [dict get $fields amount] - assert_equal "USD" [dict get $fields currency] - } - - test {XADD IDMP multiple different IIDs create multiple entries} { - r DEL mystream - - # Add entries with different iids - set id1 [r XADD mystream IDMP p1 "req-1" * user "alice"] - set id2 [r XADD mystream IDMP p1 "req-2" * user "bob"] - set id3 [r XADD mystream IDMP p1 "req-3" * user "charlie"] - - # Verify all IDs are different - assert {$id1 != $id2} - assert {$id2 != $id3} - assert {$id1 != $id3} - - # Verify all entries exist - assert_equal 3 [r XLEN mystream] - - # Verify each entry has correct data - set entries [r XRANGE mystream - +] - assert_equal "alice" [dict get [lindex [lindex $entries 0] 1] user] - assert_equal "bob" [dict get [lindex [lindex $entries 1] 1] user] - assert_equal "charlie" [dict get [lindex [lindex $entries 2] 1] user] - } - - test {XADD IDMP with binary-safe iid} { - r DEL mystream - - # Test with null bytes and binary data - set binary_iid "\x00\x01\x02\xff" - set id1 [r XADD mystream IDMP p1 $binary_iid * field "value"] - set id2 [r XADD mystream IDMP p1 $binary_iid * field "dup"] - assert_equal $id1 $id2 - } - - test {XADD IDMP with maximum length iid} { - r DEL mystream - - # Test with very long iid (e.g., 64KB) - set long_iid [string repeat "x" 65536] - set id [r XADD mystream IDMP p1 $long_iid * field "value"] - assert_match {*-*} $id - } - - test {XADD IDMP with MAXLEN option} { - r DEL mystream - - # Add entries with IDMP and MAXLEN - set id1 [r XADD mystream IDMP p1 "req-1" MAXLEN ~ 100 * field "value1"] - set id2 [r XADD mystream IDMP p1 "req-2" MAXLEN ~ 100 * field "value2"] - - # Attempt duplicate - set id1_dup [r XADD mystream IDMP p1 "req-1" MAXLEN ~ 100 * field "value3"] - - # Verify deduplication works - assert_equal $id1 $id1_dup - - # Verify only 2 entries exist - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMP with MINID option} { - r DEL mystream - - # Add entry with IDMP and MINID - set id1 [r XADD mystream IDMP p1 "req-1" MINID ~ 1000000000-0 * field "value1"] - - # Attempt duplicate with MINID - set id2 [r XADD mystream IDMP p1 "req-1" MINID ~ 1000000000-0 * field "value2"] - - # Verify deduplication works - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - } - - test {XADD IDMP with NOMKSTREAM option} { - r DEL mystream - - # Attempt XADD with NOMKSTREAM on non-existent stream - set result [r XADD mystream NOMKSTREAM IDMP p1 "req-1" * field "value"] - assert_equal {} $result - - # Create stream normally - r XADD mystream IDMP p1 "req-2" * field "value" - - # Now NOMKSTREAM should work - set id [r XADD mystream NOMKSTREAM IDMP p1 "req-3" * field "value"] - assert_match {*-*} $id - - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMP with KEEPREF option} { - r DEL mystream - - # Add entry with IDMP and KEEPREF - set id1 [r XADD mystream IDMP p1 "req-1" KEEPREF * field "value1"] - - # Attempt duplicate with KEEPREF - set id2 [r XADD mystream IDMP p1 "req-1" KEEPREF * field "value2"] - - # Verify deduplication works - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - } - - test {XADD IDMP with combined options} { - r DEL mystream - - # Add entry with all options - set id1 [r XADD mystream IDMP p1 "req-1" KEEPREF MAXLEN ~ 1000 LIMIT 10 * field1 "value1" field2 "value2"] - - # Attempt duplicate with all options - set id2 [r XADD mystream IDMP p1 "req-1" KEEPREF MAXLEN ~ 1000 LIMIT 10 * field3 "value3"] - - # Verify deduplication works - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - - # Verify original fields preserved - set entries [r XRANGE mystream - +] - set fields [lindex [lindex $entries 0] 1] - assert_equal "value1" [dict get $fields field1] - assert_equal "value2" [dict get $fields field2] - } - - test {XADD IDMP argument order variations} { - r DEL mystream - - # IDMP before MAXLEN - set id1 [r XADD mystream IDMP p1 "req-1" MAXLEN ~ 100 * field "value1"] - - # IDMP after MAXLEN - set id2 [r XADD mystream MAXLEN ~ 100 IDMP p1 "req-2" * field "value2"] - - # Multiple options in different order - set id3 [r XADD mystream NOMKSTREAM IDMP p1 "req-3" MAXLEN ~ 100 * field "value3"] - - # All should succeed - assert_match {*-*} $id1 - assert_match {*-*} $id2 - assert_match {*-*} $id3 - - assert_equal 3 [r XLEN mystream] - } - - test {XADD IDMP concurrent duplicate requests} { - r DEL mystream - - # Create multiple clients - set client1 [redis_client] - set client2 [redis_client] - set client3 [redis_client] - - # Send same IDMP request from all clients concurrently - set id1 [$client1 XADD mystream IDMP p1 "concurrent-req" * client "1"] - set id2 [$client2 XADD mystream IDMP p1 "concurrent-req" * client "2"] - set id3 [$client3 XADD mystream IDMP p1 "concurrent-req" * client "3"] - - # All should return the same ID - assert_equal $id1 $id2 - assert_equal $id2 $id3 - - # Only one entry should exist - assert_equal 1 [r XLEN mystream] - - # Cleanup - $client1 close - $client2 close - $client3 close - } - - test {XADD IDMP pipelined requests} { - r DEL mystream - - # Send pipelined requests - r MULTI - r XADD mystream IDMP p1 "req-1" * field "value1" - r XADD mystream IDMP p1 "req-2" * field "value2" - r XADD mystream IDMP p1 "req-1" * field "value3" # Duplicate - r XADD mystream IDMP p1 "req-3" * field "value4" - set results [r EXEC] - - # Extract IDs - set id1 [lindex $results 0] - set id2 [lindex $results 1] - set id1_dup [lindex $results 2] - set id3 [lindex $results 3] - - # Verify deduplication - assert_equal $id1 $id1_dup - - # Verify all IDs are different (except duplicate) - assert {$id1 != $id2} - assert {$id2 != $id3} - assert {$id1 != $id3} - - # Verify only 3 entries exist - assert_equal 3 [r XLEN mystream] - } - - test {XADD IDMP with consumer groups} { - r DEL mystream - - # Add entries with IDMP - set id1 [r XADD mystream IDMP p1 "cg-1" * field "value1"] - set id2 [r XADD mystream IDMP p1 "cg-2" * field "value2"] - - # Create consumer group - r XGROUP CREATE mystream mygroup 0 - - # Read entries - set entries [r XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream >] - - # Verify both entries are readable - set stream_entries [lindex [lindex $entries 0] 1] - assert_equal 2 [llength $stream_entries] - - # ACK entries - assert_equal 2 [r XACK mystream mygroup $id1 $id2] - - # Verify deduplication still works - set id1_dup [r XADD mystream IDMP p1 "cg-1" * field "dup"] - assert_equal $id1 $id1_dup - } - - test {XADD IDMP persists in RDB} { - r DEL mystream - - # Add entries with IDMP - set id1 [r XADD mystream IDMP p1 "persist-1" * field "value1"] - r XADD mystream IDMP p1 "persist-2" * field "value2" - - # Force RDB save - r SAVE - r DEBUG RELOAD - - # Verify stream still exists - assert_equal 2 [r XLEN mystream] - - # Verify deduplication still works after restart - set id1_dup [r XADD mystream IDMP p1 "persist-1" * field "new"] - assert_equal $id1 $id1_dup - - # Should still have only 2 entries - assert_equal 2 [r XLEN mystream] - } {} {external:skip needs:debug} - - test {XADD IDMP set in AOF} { - r DEL mystream - r config set appendonly yes - - # Wait for the automatic AOF rewrite triggered by enabling AOF - waitForBgrewriteaof r - - # Add entries with IDMP - set id1 [r XADD mystream IDMP p1 "aof-1" * field "value1"] - r XADD mystream IDMP p1 "aof-2" * field "value2" - - # Add duplicate - set id1_dup [r XADD mystream IDMP p1 "aof-1" * field "dup"] - assert_equal $id1 $id1_dup - - # Restart with AOF - r DEBUG RELOAD - - # Verify stream exists - assert_equal 2 [r XLEN mystream] - - # Verify deduplication still works - set id1_dup2 [r XADD mystream IDMP p1 "aof-1" * field "new"] - assert_equal $id1 $id1_dup2 - } {} {external:skip needs:debug} - - test {XADD IDMP multiple producers have isolated namespaces} { - r DEL mystream - - # Add entry with producer p1 - set id1 [r XADD mystream IDMP producer1 "req-123" * field "from-p1"] - - # Add entry with same IID but different producer p2 - should create NEW entry - set id2 [r XADD mystream IDMP producer2 "req-123" * field "from-p2"] - - # IDs should be different since producers are isolated - assert {$id1 ne $id2} - - # Both entries should exist - assert_equal 2 [r XLEN mystream] - - # Verify each entry has correct data - set entries [r XRANGE mystream - +] - assert_equal "from-p1" [dict get [lindex [lindex $entries 0] 1] field] - assert_equal "from-p2" [dict get [lindex [lindex $entries 1] 1] field] - - # Duplicate within same producer should still deduplicate - set id1_dup [r XADD mystream IDMP producer1 "req-123" * field "dup-p1"] - assert_equal $id1 $id1_dup - - set id2_dup [r XADD mystream IDMP producer2 "req-123" * field "dup-p2"] - assert_equal $id2 $id2_dup - - # Still only 2 entries - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMP multiple producers each have their own MAXSIZE limit} { - r DEL mystream - - # Create stream and set global MAXSIZE - r XADD mystream IDMP p1 "init" * field "init" - r XCFGSET mystream IDMP-MAXSIZE 3 IDMP-DURATION 60 - - # Add entries for producer p1 (will have 3: init, req-1, req-2, then req-3 evicts init) - set p1_id1 [r XADD mystream IDMP p1 "req-1" * field "p1-v1"] - set p1_id2 [r XADD mystream IDMP p1 "req-2" * field "p1-v2"] - set p1_id3 [r XADD mystream IDMP p1 "req-3" * field "p1-v3"] - - # Add entries for producer p2 (separate tracking) - set p2_id1 [r XADD mystream IDMP p2 "req-1" * field "p2-v1"] - set p2_id2 [r XADD mystream IDMP p2 "req-2" * field "p2-v2"] - set p2_id3 [r XADD mystream IDMP p2 "req-3" * field "p2-v3"] - - # p1's oldest entries should be evicted, but p1 req-1,2,3 should still work - assert_equal $p1_id1 [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $p1_id2 [r XADD mystream IDMP p1 "req-2" * field "dup"] - assert_equal $p1_id3 [r XADD mystream IDMP p1 "req-3" * field "dup"] - - # p2's entries should also still work (each producer has own MAXSIZE tracking) - assert_equal $p2_id1 [r XADD mystream IDMP p2 "req-1" * field "dup"] - assert_equal $p2_id2 [r XADD mystream IDMP p2 "req-2" * field "dup"] - assert_equal $p2_id3 [r XADD mystream IDMP p2 "req-3" * field "dup"] - - # Verify pids-tracked - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply pids-tracked] - } - - test {XADD IDMP multiple producers with binary producer IDs} { - r DEL mystream - - # Test with binary producer IDs - set bin_pid1 "\x00\x01\x02" - set bin_pid2 "\x03\x04\x05" - - set id1 [r XADD mystream IDMP $bin_pid1 "req-1" * field "v1"] - set id2 [r XADD mystream IDMP $bin_pid2 "req-1" * field "v2"] - - # Different binary PIDs should be isolated - assert {$id1 ne $id2} - assert_equal 2 [r XLEN mystream] - - # Verify deduplication within same binary PID - set id1_dup [r XADD mystream IDMP $bin_pid1 "req-1" * field "dup"] - assert_equal $id1 $id1_dup - } - - test {XADD IDMP multiple producers with unicode producer IDs} { - r DEL mystream - - # Test with unicode producer IDs - set id1 [r XADD mystream IDMP "producer-世界" "req-1" * field "v1"] - set id2 [r XADD mystream IDMP "producer-héllo" "req-1" * field "v2"] - set id3 [r XADD mystream IDMP "producer-日本" "req-1" * field "v3"] - - # All should be separate entries - assert {$id1 ne $id2} - assert {$id2 ne $id3} - assert_equal 3 [r XLEN mystream] - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - } - - test {XADD IDMP multiple producers with long producer IDs} { - r DEL mystream - - # Test with very long producer IDs - set long_pid1 [string repeat "a" 1000] - set long_pid2 [string repeat "b" 1000] - - set id1 [r XADD mystream IDMP $long_pid1 "req-1" * field "v1"] - set id2 [r XADD mystream IDMP $long_pid2 "req-1" * field "v2"] - - # Different long PIDs should be isolated - assert {$id1 ne $id2} - assert_equal 2 [r XLEN mystream] - - # Verify deduplication - set id1_dup [r XADD mystream IDMP $long_pid1 "req-1" * field "dup"] - assert_equal $id1 $id1_dup - } - - test {XADD IDMP multiple producers persistence in RDB} { - r DEL mystream - - # Add entries with multiple producers - set id1 [r XADD mystream IDMP p1 "req-1" * field "v1"] - set id2 [r XADD mystream IDMP p2 "req-1" * field "v2"] - set id3 [r XADD mystream IDMP p3 "req-1" * field "v3"] - - # Verify before save - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - assert_equal 3 [dict get $reply iids-tracked] - - # Save and reload - r SAVE - restart_server 0 true false - - # Verify after reload - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - assert_equal 3 [dict get $reply iids-tracked] - - # Verify deduplication still works for all producers - assert_equal $id1 [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $id2 [r XADD mystream IDMP p2 "req-1" * field "dup"] - assert_equal $id3 [r XADD mystream IDMP p3 "req-1" * field "dup"] - } {} {external:skip} - - test {XADD IDMP multiple producers concurrent access} { - r DEL mystream - - # Create multiple clients - set client1 [redis_client] - set client2 [redis_client] - set client3 [redis_client] - - # Each client acts as a different producer - set id1 [$client1 XADD mystream IDMP service-a "order-123" * data "from-a"] - set id2 [$client2 XADD mystream IDMP service-b "order-123" * data "from-b"] - set id3 [$client3 XADD mystream IDMP service-c "order-123" * data "from-c"] - - # All should be different entries - assert {$id1 ne $id2} - assert {$id2 ne $id3} - assert_equal 3 [r XLEN mystream] - - # Duplicate from same service should return same ID - set id1_dup [$client1 XADD mystream IDMP service-a "order-123" * data "retry"] - assert_equal $id1 $id1_dup - - # Verify pids-tracked - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - - # Cleanup - $client1 close - $client2 close - $client3 close - } - - test {XADD IDMP multiple producers pipelined requests} { - r DEL mystream - - # Send pipelined requests from multiple producers - r MULTI - r XADD mystream IDMP p1 "req-1" * field "v1" - r XADD mystream IDMP p2 "req-1" * field "v2" - r XADD mystream IDMP p1 "req-1" * field "dup" - r XADD mystream IDMP p2 "req-2" * field "v3" - r XADD mystream IDMP p3 "req-1" * field "v4" - set results [r EXEC] - - set id_p1_r1 [lindex $results 0] - set id_p2_r1 [lindex $results 1] - set id_p1_r1_dup [lindex $results 2] - set id_p2_r2 [lindex $results 3] - set id_p3_r1 [lindex $results 4] - - # p1 req-1 and its duplicate should match - assert_equal $id_p1_r1 $id_p1_r1_dup - - # Different producers or different IIDs should be different - assert {$id_p1_r1 ne $id_p2_r1} - assert {$id_p2_r1 ne $id_p2_r2} - assert {$id_p2_r1 ne $id_p3_r1} - - # 4 unique entries: p1/req-1, p2/req-1, p2/req-2, p3/req-1 - assert_equal 4 [r XLEN mystream] - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - } - - test {XADD IDMP multiple producers with mixed IDMP and IDMPAUTO} { - r DEL mystream - - # Mix of IDMP and IDMPAUTO from different producers - set id1 [r XADD mystream IDMP p1 "explicit-iid" * field "v1"] - set id2 [r XADD mystream IDMPAUTO p2 * field "v1"] - set id3 [r XADD mystream IDMP p3 "another-iid" * field "v1"] - set id4 [r XADD mystream IDMPAUTO p4 * field "v1"] - - # All should be different entries - assert {$id1 ne $id2} - assert {$id2 ne $id3} - assert {$id3 ne $id4} - assert_equal 4 [r XLEN mystream] - - # Duplicates should work for each type - set id1_dup [r XADD mystream IDMP p1 "explicit-iid" * field "dup"] - set id2_dup [r XADD mystream IDMPAUTO p2 * field "v1"] - - assert_equal $id1 $id1_dup - assert_equal $id2 $id2_dup - - set reply [r XINFO STREAM mystream] - assert_equal 4 [dict get $reply pids-tracked] - } - - test {XADD IDMP multiple producers stress test} { - r DEL mystream - - # Create many producers - set num_producers 50 - set ids {} - - for {set i 0} {$i < $num_producers} {incr i} { - lappend ids [r XADD mystream IDMP "producer-$i" "request-1" * field "value-$i"] - } - - # Verify all entries exist - assert_equal $num_producers [r XLEN mystream] - - # Verify pids-tracked - set reply [r XINFO STREAM mystream] - assert_equal $num_producers [dict get $reply pids-tracked] - assert_equal $num_producers [dict get $reply iids-tracked] - - # Verify deduplication for each producer - for {set i 0} {$i < $num_producers} {incr i} { - set original_id [lindex $ids $i] - set dup_id [r XADD mystream IDMP "producer-$i" "request-1" * field "dup"] - assert_equal $original_id $dup_id - } - - # No new entries should have been added - assert_equal $num_producers [r XLEN mystream] - } - - test {XADD IDMPAUTO with invalid syntax} { - r DEL mystream - assert_error "*IDMP/IDMPAUTO specified multiple times*" {r XADD mystream IDMPAUTO p1 IDMPAUTO p2 * f v} - assert_error "*IDMP/IDMPAUTO specified multiple times*" {r XADD mystream IDMPAUTO p1 IDMP p2 iid1 * f v} - assert_error "*IDMP/IDMPAUTO specified multiple times*" {r XADD mystream IDMP p1 iid1 IDMPAUTO p2 * f v} - assert_error "*IDMP/IDMPAUTO can be used only with auto-generated IDs*" {r XADD mystream IDMPAUTO p1 1-1 f v} - } - - test {XADD IDMPAUTO basic deduplication based on field-value pairs} { - r DEL mystream - - # First XADD with IDMPAUTO - set id1 [r XADD mystream IDMPAUTO p1 * amount "100" currency "USD"] - assert {[regexp {^[0-9]+-[0-9]+$} $id1]} - - # Second XADD with same fields and values should deduplicate - set id2 [r XADD mystream IDMPAUTO p1 * amount "100" currency "USD"] - assert_equal $id1 $id2 - - # Verify only one entry exists - assert_equal 1 [r XLEN mystream] - - # Third XADD with different values should create new entry - set id3 [r XADD mystream IDMPAUTO p1 * amount "200" currency "USD"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO deduplicates regardless of field order} { - r DEL mystream - - # Add entry with fields in one order - set id1 [r XADD mystream IDMPAUTO p1 * field1 "a" field2 "b" field3 "c"] - - # Add entry with same fields in different order (should deduplicate) - set id2 [r XADD mystream IDMPAUTO p1 * field2 "b" field3 "c" field1 "a"] - assert_equal $id1 $id2 - - # Verify only one entry exists - assert_equal 1 [r XLEN mystream] - - # Add entry with different order but different values (should be new) - set id3 [r XADD mystream IDMPAUTO p1 * field3 "c" field1 "x" field2 "b"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO different field-value pairs create different entries} { - r DEL mystream - - # Add different entries - set id1 [r XADD mystream IDMPAUTO p1 * user "alice" action "login"] - set id2 [r XADD mystream IDMPAUTO p1 * user "bob" action "login"] - set id3 [r XADD mystream IDMPAUTO p1 * user "alice" action "logout"] - - # Verify all IDs are different - assert {$id1 != $id2} - assert {$id2 != $id3} - assert {$id1 != $id3} - - # Verify all entries exist - assert_equal 3 [r XLEN mystream] - } - - test {XADD IDMPAUTO with single field-value pair} { - r DEL mystream - - # Add entry with single field - set id1 [r XADD mystream IDMPAUTO p1 * status "active"] - set id2 [r XADD mystream IDMPAUTO p1 * status "active"] - assert_equal $id1 $id2 - - # Different value should create new entry - set id3 [r XADD mystream IDMPAUTO p1 * status "inactive"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO with many field-value pairs} { - r DEL mystream - - # Add entry with many fields - set id1 [r XADD mystream IDMPAUTO p1 * f1 "v1" f2 "v2" f3 "v3" f4 "v4" f5 "v5" f6 "v6" f7 "v7" f8 "v8"] - set id2 [r XADD mystream IDMPAUTO p1 * f1 "v1" f2 "v2" f3 "v3" f4 "v4" f5 "v5" f6 "v6" f7 "v7" f8 "v8"] - assert_equal $id1 $id2 - - # Change one value should create new entry - set id3 [r XADD mystream IDMPAUTO p1 * f1 "v1" f2 "v2" f3 "v3" f4 "v4" f5 "v5" f6 "v6" f7 "v7" f8 "different"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO with binary-safe values} { - r DEL mystream - - # Test with null bytes and binary data - set binary_val "\x00\x01\x02\xff" - set id1 [r XADD mystream IDMPAUTO p1 * field $binary_val] - set id2 [r XADD mystream IDMPAUTO p1 * field $binary_val] - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - } - - test {XADD IDMPAUTO with unicode values} { - r DEL mystream - - # Test with unicode characters - set id1 [r XADD mystream IDMPAUTO p1 * message "hello世界"] - set id2 [r XADD mystream IDMPAUTO p1 * message "hello世界"] - assert_equal $id1 $id2 - - # Different unicode should create new entry - set id3 [r XADD mystream IDMPAUTO p1 * message "héllo"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO with long values} { - r DEL mystream - - # Test with very long values - set long_val [string repeat "x" 10000] - set id1 [r XADD mystream IDMPAUTO p1 * data $long_val] - set id2 [r XADD mystream IDMPAUTO p1 * data $long_val] - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - } - - test {XADD IDMPAUTO with empty string values} { - r DEL mystream - - # Test with empty string values - set id1 [r XADD mystream IDMPAUTO p1 * field ""] - set id2 [r XADD mystream IDMPAUTO p1 * field ""] - assert_equal $id1 $id2 - - # Non-empty should be different - set id3 [r XADD mystream IDMPAUTO p1 * field "value"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO with MAXLEN option} { - r DEL mystream - - # Add entries with IDMPAUTO and MAXLEN - set id1 [r XADD mystream IDMPAUTO p1 MAXLEN ~ 100 * field "value1"] - set id2 [r XADD mystream IDMPAUTO p1 MAXLEN ~ 100 * field "value2"] - - # Attempt duplicate - set id1_dup [r XADD mystream IDMPAUTO p1 MAXLEN ~ 100 * field "value1"] - - # Verify deduplication works - assert_equal $id1 $id1_dup - - # Verify only 2 entries exist - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO with MINID option} { - r DEL mystream - - # Add entry with IDMPAUTO and MINID - set id1 [r XADD mystream IDMPAUTO p1 MINID ~ 1000000000-0 * field "value1"] - - # Attempt duplicate with MINID - set id2 [r XADD mystream IDMPAUTO p1 MINID ~ 1000000000-0 * field "value1"] - - # Verify deduplication works - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - } - - test {XADD IDMPAUTO with NOMKSTREAM option} { - r DEL mystream - - # Attempt XADD with NOMKSTREAM on non-existent stream - set result [r XADD mystream NOMKSTREAM IDMPAUTO p1 * field "value"] - assert_equal {} $result - - # Create stream first - r XADD mystream * field "initial" - - # Now NOMKSTREAM with IDMPAUTO should work - set id [r XADD mystream NOMKSTREAM IDMPAUTO p1 * field "test"] - assert {[regexp {^[0-9]+-[0-9]+$} $id]} - } - - test {XADD IDMPAUTO with KEEPREF option} { - r DEL mystream - - # Add entries with IDMPAUTO and KEEPREF - set id1 [r XADD mystream KEEPREF IDMPAUTO p1 * field "value1"] - set id2 [r XADD mystream KEEPREF IDMPAUTO p1 * field "value1"] - - # Verify deduplication works with KEEPREF - assert_equal $id1 $id2 - assert_equal 1 [r XLEN mystream] - } - - test {XADD IDMPAUTO argument order variations} { - r DEL mystream - - # Test different argument orders - set id1 [r XADD mystream IDMPAUTO p1 * field "test"] - set id2 [r XADD mystream IDMPAUTO p1 MAXLEN ~ 100 * field "test2"] - set id3 [r XADD mystream MAXLEN ~ 100 IDMPAUTO p1 * field "test3"] - set id4 [r XADD mystream KEEPREF IDMPAUTO p1 * field "test4"] - set id5 [r XADD mystream IDMPAUTO p1 KEEPREF * field "test5"] - - # All should be valid stream IDs - assert {[regexp {^[0-9]+-[0-9]+$} $id1]} - assert {[regexp {^[0-9]+-[0-9]+$} $id2]} - assert {[regexp {^[0-9]+-[0-9]+$} $id3]} - assert {[regexp {^[0-9]+-[0-9]+$} $id4]} - assert {[regexp {^[0-9]+-[0-9]+$} $id5]} - - # Verify all entries exist - assert_equal 5 [r XLEN mystream] - } - - test {XADD IDMPAUTO persists in RDB} { - r DEL mystream - - # Add entries with IDMPAUTO - set id1 [r XADD mystream IDMPAUTO p1 * field "value1"] - set id2 [r XADD mystream IDMPAUTO p1 * field "value2"] - - # Save and reload - r DEBUG RELOAD - - # Verify stream exists - assert_equal 2 [r XLEN mystream] - - # Verify deduplication still works after restart - set id1_dup [r XADD mystream IDMPAUTO p1 * field "value1"] - assert_equal $id1 $id1_dup - - # Should still have only 2 entries - assert_equal 2 [r XLEN mystream] - } {} {external:skip needs:debug} - - test {XADD IDMPAUTO with consumer groups} { - r DEL mystream - - # Create consumer group - r XADD mystream * initial "value" - r XGROUP CREATE mystream mygroup 0 - - # Add entries with IDMPAUTO - set id1 [r XADD mystream IDMPAUTO p1 * event "login" user "alice"] - set id2 [r XADD mystream IDMPAUTO p1 * event "logout" user "bob"] - - # Attempt duplicate - set id1_dup [r XADD mystream IDMPAUTO p1 * event "login" user "alice"] - assert_equal $id1 $id1_dup - - # Read from consumer group (should get 3 new entries, not 4) - set messages [r XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream >] - set stream_data [lindex $messages 0 1] - assert_equal 3 [llength $stream_data] - } - - test {XADD IDMPAUTO field names matter} { - r DEL mystream - - # Different field names should create different entries - set id1 [r XADD mystream IDMPAUTO p1 * field1 "value"] - set id2 [r XADD mystream IDMPAUTO p1 * field2 "value"] - - assert {$id1 != $id2} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO with numeric field names and values} { - r DEL mystream - - # Test with numeric field names - set id1 [r XADD mystream IDMPAUTO p1 * 123 "456" 789 "012"] - set id2 [r XADD mystream IDMPAUTO p1 * 123 "456" 789 "012"] - assert_equal $id1 $id2 - - # Different numeric values - set id3 [r XADD mystream IDMPAUTO p1 * 123 "999" 789 "012"] - assert {$id3 != $id1} - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO multiple producers have isolated namespaces} { - r DEL mystream - - # Same field-value pairs with different producers should create separate entries - set id1 [r XADD mystream IDMPAUTO producer1 * amount "100" currency "USD"] - set id2 [r XADD mystream IDMPAUTO producer2 * amount "100" currency "USD"] - - # Different producers = different entries - assert {$id1 ne $id2} - assert_equal 2 [r XLEN mystream] - - # Same producer with same fields should deduplicate - set id1_dup [r XADD mystream IDMPAUTO producer1 * amount "100" currency "USD"] - set id2_dup [r XADD mystream IDMPAUTO producer2 * amount "100" currency "USD"] - - assert_equal $id1 $id1_dup - assert_equal $id2 $id2_dup - assert_equal 2 [r XLEN mystream] - } - - test {XADD IDMPAUTO multiple producers} { - r DEL mystream - - # Different producers with same content should create separate entries - set id1 [r XADD mystream IDMPAUTO app1 * event "login" user "alice"] - set id2 [r XADD mystream IDMPAUTO app2 * event "login" user "alice"] - set id3 [r XADD mystream IDMPAUTO app3 * event "login" user "alice"] - - # All should be different (different producers) - assert {$id1 ne $id2} - assert {$id2 ne $id3} - assert_equal 3 [r XLEN mystream] - - # Same producer with same content should deduplicate - set id1_dup [r XADD mystream IDMPAUTO app1 * event "login" user "alice"] - assert_equal $id1 $id1_dup - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - } - - test {XIDMP entries expire after DURATION seconds} { - r DEL mystream - r XADD mystream IDMP p1 "req-1" * field "value1" - r XCFGSET mystream IDMP-DURATION 1 - - # Immediate duplicate should be detected - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - set id2 [r XADD mystream IDMP p1 "req-1" * field "value2"] - assert_equal $id1 $id2 - - # Wait for expiration (1 second + margin) - after 2500 - - # Now should create new entry - set id3 [r XADD mystream IDMP p1 "req-1" * field "value3"] - assert {$id1 ne $id3} - } - - test {XIDMP set evicts entries when MAXSIZE is reached} { - r DEL mystream - - # First add an entry to create the stream, then set config - r XADD mystream IDMP p1 "init" * field "init" - r XCFGSET mystream IDMP-MAXSIZE 3 IDMP-DURATION 60 - - # Add 3 unique entries - set id1 [r XADD mystream IDMP p1 "req-1" * field "v1"] - set id2 [r XADD mystream IDMP p1 "req-2" * field "v2"] - set id3 [r XADD mystream IDMP p1 "req-3" * field "v3"] - - # All duplicates should still work (IDMP set has: req-1, req-2, req-3) - assert_equal $id1 [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $id2 [r XADD mystream IDMP p1 "req-2" * field "dup"] - assert_equal $id3 [r XADD mystream IDMP p1 "req-3" * field "dup"] - - # Add 4th entry - should evict oldest (req-1) - set id4 [r XADD mystream IDMP p1 "req-4" * field "v4"] - - # req-1 should be evicted, so it should create new entry - set result [r XADD mystream IDMP p1 "req-1" * field "new"] - assert {$result ne $id1} - - # req-2 is also eveicted but req-3 should still be in the set - assert_equal $id3 [r XADD mystream IDMP p1 "req-3" * field "dup2"] - - # Stream should have: init, req-1, req-2, req-3, req-4, req-1(new) = 6 entries - assert_equal 6 [r XLEN mystream] - } - - test {XCFGSET set IDMP-DURATION successfully} { - r DEL mystream - - # Create stream with IDMP entry - r XADD mystream IDMP p1 "req-1" * field "value" - - # Set IDMP-DURATION to 5s - assert_equal "OK" [r XCFGSET mystream IDMP-DURATION 5] - - # Verify IDMP-DURATION was set - set reply [r XINFO STREAM mystream] - assert_equal 5 [dict get $reply idmp-duration] - } - - test {XCFGSET set IDMP-MAXSIZE successfully} { - r DEL mystream - - # Create stream with IDMP entry - r XADD mystream IDMP p1 "req-1" * field "value" - - # Set IDMP-MAXSIZE to 5000 - assert_equal "OK" [r XCFGSET mystream IDMP-MAXSIZE 5000] - - # Verify IDMP-MAXSIZE was set - set reply [r XINFO STREAM mystream] - assert_equal 5000 [dict get $reply idmp-maxsize] - } - - test {XCFGSET set both IDMP-DURATION and IDMP-MAXSIZE} { - r DEL mystream - - # Create stream with IDMP entry - r XADD mystream IDMP p1 "req-1" * field "value" - - # Set both IDMP-DURATION and IDMP-MAXSIZE - assert_equal "OK" [r XCFGSET mystream IDMP-DURATION 3 IDMP-MAXSIZE 10000] - - # Verify both were set - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply idmp-duration] - assert_equal 10000 [dict get $reply idmp-maxsize] - } - - test {XINFO STREAM shows IDMP configuration parameters} { - r DEL mystream - - # Create stream with IDMP entry - r XADD mystream IDMP p1 "req-1" * field "value" - - # Set both IDMP-DURATION and IDMP-MAXSIZE - assert_equal "OK" [r XCFGSET mystream IDMP-DURATION 3 IDMP-MAXSIZE 10000] - - # Verify both were set - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply idmp-duration] - assert_equal 10000 [dict get $reply idmp-maxsize] - } - - test {XINFO STREAM shows default IDMP parameters} { - r DEL mystream - - # Create stream with IDMP entry - r XADD mystream IDMP p1 "req-1" * field "value" - - # Verify default parameters - set reply [r XINFO STREAM mystream] - assert_equal 100 [dict get $reply idmp-duration] - assert_equal 100 [dict get $reply idmp-maxsize] - } - - test {XCFGSET error on non-existent stream} { - r DEL mystream - - # Attempt to set config on non-existent stream - assert_error "*no such key*" {r XCFGSET mystream IDMP-DURATION 5} - } - - test {XCFGSET IDMP-DURATION maximum value validation} { - r DEL mystream - - # Create stream with IDMP - r XADD mystream IDMP p1 "req-1" * field "value" - - # Set IDMP-DURATION to maximum allowed (86400 seconds = 24 hours) - assert_equal "OK" [r XCFGSET mystream IDMP-DURATION 86400] - - # Verify it was set - set reply [r XINFO STREAM mystream] - assert_equal 86400 [dict get $reply idmp-duration] - - # Attempt to set IDMP-DURATION above maximum - assert_error "*ERR IDMP-DURATION must be*" {r XCFGSET mystream IDMP-DURATION 86401} - - # Verify IDMP-DURATION wasn't changed - set reply [r XINFO STREAM mystream] - assert_equal 86400 [dict get $reply idmp-duration] - } - - test {XCFGSET IDMP-DURATION minimum value validation} { - r DEL mystream - - # Create stream with IDMP - r XADD mystream IDMP p1 "req-1" * field "value" - - # Attempt to set IDMP-DURATION to 0 - assert_error "*ERR IDMP-DURATION must be between*" {r XCFGSET mystream IDMP-DURATION 0} - - # Attempt to set IDMP-DURATION to negative value - assert_error "*ERR IDMP-DURATION must be between*" {r XCFGSET mystream IDMP-DURATION -100} - - # Set IDMP-DURATION to minimum valid value (1 second) - assert_equal "OK" [r XCFGSET mystream IDMP-DURATION 1] - - # Verify it was set - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply idmp-duration] - } - - test {XCFGSET IDMP-MAXSIZE maximum value validation} { - r DEL mystream - - # Create stream with IDMP - r XADD mystream IDMP p1 "req-1" * field "value" - - # Set IDMP-MAXSIZE to maximum allowed (10000) - assert_equal "OK" [r XCFGSET mystream IDMP-MAXSIZE 10000] - - # Verify it was set - set reply [r XINFO STREAM mystream] - assert_equal 10000 [dict get $reply idmp-maxsize] - - # Attempt to set IDMP-MAXSIZE above maximum - assert_error "*ERR IDMP-MAXSIZE must be between*" {r XCFGSET mystream IDMP-MAXSIZE 10001} - - # Verify IDMP-MAXSIZE wasn't changed - set reply [r XINFO STREAM mystream] - assert_equal 10000 [dict get $reply idmp-maxsize] - } - - test {XCFGSET IDMP-MAXSIZE minimum value validation} { - r DEL mystream - - # Create stream with IDMP - r XADD mystream IDMP p1 "req-1" * field "value" - - # Attempt to set IDMP-MAXSIZE to 0 - assert_error "*ERR IDMP-MAXSIZE must be between*" {r XCFGSET mystream IDMP-MAXSIZE 0} - - # Attempt to set IDMP-MAXSIZE to negative value - assert_error "*ERR IDMP-MAXSIZE must be between*" {r XCFGSET mystream IDMP-MAXSIZE -50} - - # Set IDMP-MAXSIZE to minimum valid value (1) - assert_equal "OK" [r XCFGSET mystream IDMP-MAXSIZE 1] - - # Verify it was set - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply idmp-maxsize] - } - - test {XCFGSET invalid syntax} { - r DEL mystream - - # Create stream with IDMP - r XADD mystream IDMP p1 "req-1" * field "value" - - # Attempt CFGSET with invalid syntax - assert_error "*ERR At least one parameter*" {r XCFGSET mystream} - assert_error "*syntax*" {r XCFGSET mystream IDMP-DURATION} - assert_error "*syntax*" {r XCFGSET mystream IDMP-MAXSIZE} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION A} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION AAA} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION *} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION -} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION +} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION 120-5} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION 3.14} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION 000000000} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION IDMP-DURATION} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION IDMP-DURATION IDMP-DURATION} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE A} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE AAA} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE *} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE -} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE +} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE 120-5} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE 3.14} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE 000000000} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE IDMP-MAXSIZE} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE IDMP-MAXSIZE IDMP-MAXSIZE} - - assert_error "*syntax*" {r XCFGSET mystream INVALID} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-DURATION INVALID IDMP-MAXSIZE} - assert_error "*ERR value is not an integer*" {r XCFGSET mystream IDMP-MAXSIZE INVALID IDMP-DURATION} - } - - test {XCFGSET multiple configuration changes} { - r DEL mystream - - # Create stream with IDMP - r XADD mystream IDMP p1 "req-1" * field "value" - - # Change DURATION multiple times - r XCFGSET mystream IDMP-DURATION 1 - r XCFGSET mystream IDMP-DURATION 2 - r XCFGSET mystream IDMP-DURATION 3 - - # Change MAXSIZE - r XCFGSET mystream IDMP-MAXSIZE 100 - r XCFGSET mystream IDMP-MAXSIZE 200 - - # Verify latest values - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply idmp-duration] - assert_equal 200 [dict get $reply idmp-maxsize] - } - - test {XCFGSET configuration persists in RDB} { - r DEL mystream - - # Create stream and set configuration - r XADD mystream IDMP p1 "req-1" * field "value" - r XCFGSET mystream IDMP-DURATION 75 IDMP-MAXSIZE 7500 - - # Save and restart - r SAVE - - # Restart Redis - restart_server 0 true false - - # Verify configuration persisted - set reply [r XINFO STREAM mystream] - assert_equal 75 [dict get $reply idmp-duration] - assert_equal 7500 [dict get $reply idmp-maxsize] - } {} {external:skip} - - test {XCFGSET configuration in AOF} { - r DEL mystream - r config set appendonly yes - - # Wait for the automatic AOF rewrite triggered by enabling AOF - waitForBgrewriteaof r - - # Create stream and set configuration - r XADD mystream IDMP p1 "req-1" * field "value" - r XCFGSET mystream IDMP-DURATION 45 IDMP-MAXSIZE 4500 - - # Force AOF rewrite - r BGREWRITEAOF - waitForBgrewriteaof r - - # Restart with AOF - r DEBUG RELOAD - - # Verify configuration - set reply [r XINFO STREAM mystream] - assert_equal 45 [dict get $reply idmp-duration] - assert_equal 4500 [dict get $reply idmp-maxsize] - - assert_equal "OK" [r config set appendonly no] - } {} {external:skip needs:debug} - - test {XCFGSET changing IDMP-DURATION clears all iids history} { - r DEL mystream - - # Create stream and add entries with IDMP - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - set id2 [r XADD mystream IDMP p1 "req-2" * field "value2"] - - # Verify deduplication works before config change - set dup_id [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $id1 $dup_id - - # Change DURATION - should clear iids history - r XCFGSET mystream IDMP-DURATION 5 - - # Now req-1 should create a new entry (history was cleared) - set new_id1 [r XADD mystream IDMP p1 "req-1" * field "new1"] - assert {$id1 ne $new_id1} - - # Should have 3 entries total (2 original + 1 new) - assert_equal 3 [r XLEN mystream] - } - - test {XCFGSET changing IDMP-MAXSIZE clears all iids history} { - r DEL mystream - - # Create stream and add entries with IDMP - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - set id2 [r XADD mystream IDMP p1 "req-2" * field "value2"] - - # Verify deduplication works before config change - set dup_id [r XADD mystream IDMP p1 "req-2" * field "dup"] - assert_equal $id2 $dup_id - - # Change MAXSIZE - should clear iids history - r XCFGSET mystream IDMP-MAXSIZE 5000 - - # Now req-2 should create a new entry (history was cleared) - set new_id2 [r XADD mystream IDMP p1 "req-2" * field "new2"] - assert {$id2 ne $new_id2} - - # Should have 3 entries total (2 original + 1 new) - assert_equal 3 [r XLEN mystream] - } - - test {XCFGSET history cleared then new deduplication works} { - r DEL mystream - - # Create stream and add entries - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - - # Change configuration to clear history - r XCFGSET mystream IDMP-DURATION 6 - - # Add new entry with same iid - set new_id1 [r XADD mystream IDMP p1 "req-1" * field "new1"] - assert {$id1 ne $new_id1} - - # Now deduplication should work with new history - set dup_id1 [r XADD mystream IDMP p1 "req-1" * field "dup1"] - assert_equal $new_id1 $dup_id1 - - # Should have 2 entries (1 original + 1 new) - assert_equal 2 [r XLEN mystream] - } - - test {XCFGSET history cleared preserves stream entries} { - r DEL mystream - - # Create stream with entries - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1" data "data1"] - set id2 [r XADD mystream IDMP p1 "req-2" * field "value2" data "data2"] - - # Verify entries exist with correct data - set entries [r XRANGE mystream - +] - assert_equal 2 [llength $entries] - - # Change configuration to clear iids history - r XCFGSET mystream IDMP-DURATION 7 - - # Stream entries should still exist unchanged - set entries_after [r XRANGE mystream - +] - assert_equal 2 [llength $entries_after] - - # Verify original entries have correct data - set entry1_fields [lindex [lindex $entries_after 0] 1] - assert_equal "value1" [dict get $entry1_fields field] - assert_equal "data1" [dict get $entry1_fields data] - - # But iids history is cleared, so can add new entries - set new_id1 [r XADD mystream IDMP p1 "req-1" * field "new1"] - assert {$id1 ne $new_id1} - } - - test {XCFGSET setting same IDMP-DURATION does not clear iids history} { - r DEL mystream - - # Create stream and add entries with IDMP - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - set id2 [r XADD mystream IDMP p1 "req-2" * field "value2"] - - # Verify deduplication works before config - set dup_id [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $id1 $dup_id - - # Get current DURATION (default is 100) - set reply [r XINFO STREAM mystream] - set current_duration [dict get $reply idmp-duration] - assert_equal 100 $current_duration - - # Set IDMP-DURATION to same value - should NOT clear iids history - r XCFGSET mystream IDMP-DURATION 100 - - # Deduplication should still work (history was NOT cleared) - set dup_id2 [r XADD mystream IDMP p1 "req-1" * field "dup2"] - assert_equal $id1 $dup_id2 - - set dup_id3 [r XADD mystream IDMP p1 "req-2" * field "dup3"] - assert_equal $id2 $dup_id3 - - # Should still have 2 entries (no new entries added) - assert_equal 2 [r XLEN mystream] - } - - test {XCFGSET setting same IDMP-MAXSIZE does not clear iids history} { - r DEL mystream - - # Create stream and add entries with IDMP - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - set id2 [r XADD mystream IDMP p1 "req-2" * field "value2"] - - # Verify deduplication works - set dup_id [r XADD mystream IDMP p1 "req-2" * field "dup"] - assert_equal $id2 $dup_id - - # Get current MAXSIZE (default is 100) - set reply [r XINFO STREAM mystream] - set current_maxsize [dict get $reply idmp-maxsize] - assert_equal 100 $current_maxsize - - # Set IDMP-MAXSIZE to same value - should NOT clear iids history - r XCFGSET mystream IDMP-MAXSIZE 100 - - # Deduplication should still work (history was NOT cleared) - set dup_id2 [r XADD mystream IDMP p1 "req-1" * field "dup2"] - assert_equal $id1 $dup_id2 - - set dup_id3 [r XADD mystream IDMP p1 "req-2" * field "dup3"] - assert_equal $id2 $dup_id3 - - # Should still have 2 entries (no new entries added) - assert_equal 2 [r XLEN mystream] - } - - test {XCFGSET repeated same-value calls preserve IDMP history} { - r DEL mystream - - # Set configuration first - r XADD mystream * field "init" - r XCFGSET mystream IDMP-DURATION 10 IDMP-MAXSIZE 5000 - - # Create stream with initial entry after config is set - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - - # Call XCFGSET multiple times with same values - # (common pattern for configuration initialization) - r XCFGSET mystream IDMP-DURATION 10 IDMP-MAXSIZE 5000 - r XCFGSET mystream IDMP-DURATION 10 IDMP-MAXSIZE 5000 - r XCFGSET mystream IDMP-DURATION 10 IDMP-MAXSIZE 5000 - - # Deduplication should still work (history not cleared by same-value sets) - set dup_id [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $id1 $dup_id - - # Add new entry from second producer - set id2 [r XADD mystream IDMP p2 "req-2" * field "value2"] - - # Both producers should work with deduplication - set dup_id2 [r XADD mystream IDMP p2 "req-2" * field "dup2"] - assert_equal $id2 $dup_id2 - - # Should have 3 entries total (init + 2 IDMP entries) - assert_equal 3 [r XLEN mystream] - } - - test {XCFGSET changing value after same-value sets still clears history} { - r DEL mystream - - # Create stream with entries - set id1 [r XADD mystream IDMP p1 "req-1" * field "value1"] - - # Set to same value multiple times (doesn't clear) - r XCFGSET mystream IDMP-DURATION 100 - r XCFGSET mystream IDMP-DURATION 100 - - # Verify deduplication still works - set dup_id [r XADD mystream IDMP p1 "req-1" * field "dup"] - assert_equal $id1 $dup_id - - # Now change to different value (should clear) - r XCFGSET mystream IDMP-DURATION 50 - - # Deduplication should not work anymore (history cleared) - set new_id [r XADD mystream IDMP p1 "req-1" * field "new"] - assert {$id1 ne $new_id} - - # Should have 2 entries now - assert_equal 2 [r XLEN mystream] - } - - test {XCFGSET setting same value preserves iids-tracked count} { - r DEL mystream - - # Add entries with IDMP - r XADD mystream IDMP p1 "req-1" * field "value1" - r XADD mystream IDMP p1 "req-2" * field "value2" - r XADD mystream IDMP p2 "req-3" * field "value3" - - # Verify counts - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-tracked] - assert_equal 3 [dict get $reply iids-added] - - # Set to same value - should preserve counts - r XCFGSET mystream IDMP-DURATION 100 IDMP-MAXSIZE 100 - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-tracked] - assert_equal 3 [dict get $reply iids-added] - } - - test {XINFO STREAM returns iids-tracked and iids-added fields} { - r DEL mystream - - # Create stream without IDMP first - r XADD mystream * field "value" - - # Verify initial values: no IDMP entries yet - set reply [r XINFO STREAM mystream] - assert_equal 0 [dict get $reply iids-tracked] - assert_equal 0 [dict get $reply iids-added] - - # Add entries with IDMP - r XADD mystream IDMP p1 "req-1" * field "value1" - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply iids-tracked] - assert_equal 1 [dict get $reply iids-added] - - r XADD mystream IDMP p1 "req-2" * field "value2" - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply iids-tracked] - assert_equal 2 [dict get $reply iids-added] - - # Duplicate IDMP should NOT increment counters - r XADD mystream IDMP p1 "req-1" * field "duplicate" - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply iids-tracked] - assert_equal 2 [dict get $reply iids-added] - - # Also verify FULL mode returns the same fields - set reply_full [r XINFO STREAM mystream FULL] - assert_equal 2 [dict get $reply_full iids-tracked] - assert_equal 2 [dict get $reply_full iids-added] - } - - test {XINFO STREAM iids-added is lifetime counter even after eviction} { - r DEL mystream - - # Set small MAXSIZE to trigger eviction - r XADD mystream IDMP p1 "init" * field "init" - r XCFGSET mystream IDMP-MAXSIZE 3 - - # Add 3 more entries (total 4, but MAXSIZE=3 so oldest evicted) - r XADD mystream IDMP p1 "req-1" * field "v1" - r XADD mystream IDMP p1 "req-2" * field "v2" - r XADD mystream IDMP p1 "req-3" * field "v3" - - set reply [r XINFO STREAM mystream] - # iids-tracked should be capped at MAXSIZE (3) - assert_equal 3 [dict get $reply iids-tracked] - # iids-added should be lifetime count (4) - assert_equal 4 [dict get $reply iids-added] - - # Add more entries to verify lifetime counter keeps growing - r XADD mystream IDMP p1 "req-4" * field "v4" - r XADD mystream IDMP p1 "req-5" * field "v5" - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-tracked] - assert_equal 6 [dict get $reply iids-added] - } - - test {XINFO STREAM iids-duplicates is lifetime counter} { - r DEL mystream - - # Add initial entry with unique IID - r XADD mystream IDMP p1 "req-1" * field "v1" - - set reply [r XINFO STREAM mystream] - # No duplicates yet - assert_equal 0 [dict get $reply iids-duplicates] - assert_equal 1 [dict get $reply iids-added] - - # Try to add duplicate IID - should be rejected and increment counter - set dup_id [r XADD mystream IDMP p1 "req-1" * field "v1-dup"] - - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply iids-duplicates] - assert_equal 1 [dict get $reply iids-added] ;# Still 1 successful add - - # Try same duplicate again - r XADD mystream IDMP p1 "req-1" * field "v1-dup2" - - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply iids-duplicates] - assert_equal 1 [dict get $reply iids-added] - - # Add a different IID (should succeed, duplicates unchanged) - r XADD mystream IDMP p1 "req-2" * field "v2" - - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply iids-duplicates] - assert_equal 2 [dict get $reply iids-added] - - # Try the first IID again - r XADD mystream IDMP p1 "req-1" * field "v1-dup3" - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-duplicates] - assert_equal 2 [dict get $reply iids-added] - } - - test {XINFO STREAM iids-duplicates persists after eviction} { - r DEL mystream - - # Add initial entry and configure MAXSIZE - r XADD mystream IDMP p1 "init" * field "init" - r XCFGSET mystream IDMP-MAXSIZE 3 - # Note: CFGSET clears IID history, so "init" is no longer tracked - - # Add entries and create some duplicates - r XADD mystream IDMP p1 "req-1" * field "v1" - r XADD mystream IDMP p1 "req-1" * field "v1-dup" ;# Duplicate - r XADD mystream IDMP p1 "req-2" * field "v2" - r XADD mystream IDMP p1 "req-2" * field "v2-dup" ;# Duplicate - - set reply [r XINFO STREAM mystream] - # iids-tracked should be 2 (req-1, req-2) - "init" was cleared by CFGSET - assert_equal 2 [dict get $reply iids-tracked] - # iids-added should be 3 (init, req-1, req-2) - lifetime counter includes "init" - assert_equal 3 [dict get $reply iids-added] - # iids-duplicates should be 2 - assert_equal 2 [dict get $reply iids-duplicates] - - # Add more entries to trigger eviction of old IIDs - r XADD mystream IDMP p1 "req-3" * field "v3" - r XADD mystream IDMP p1 "req-4" * field "v4" - - # Now we have: req-2, req-3, req-4 (MAXSIZE=3, so req-1 was evicted) - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-tracked] ;# Now capped at MAXSIZE (3) - assert_equal 5 [dict get $reply iids-added] ;# 5 successful adds total - assert_equal 2 [dict get $reply iids-duplicates] ;# Still 2 (lifetime counter) - - # Try to duplicate one of the currently tracked IIDs - r XADD mystream IDMP p1 "req-3" * field "v3-dup" - r XADD mystream IDMP p1 "req-4" * field "v4-dup" - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-tracked] - assert_equal 5 [dict get $reply iids-added] - assert_equal 4 [dict get $reply iids-duplicates] ;# Incremented by 2 - } - - test {XINFO STREAM iids-duplicates with multiple producers} { - r DEL mystream - - # Add entries from different producers with same IID - # (same IID but different producer = NOT a duplicate) - r XADD mystream IDMP p1 "req-1" * field "v1-p1" - r XADD mystream IDMP p2 "req-1" * field "v1-p2" - - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply pids-tracked] - assert_equal 2 [dict get $reply iids-added] - assert_equal 0 [dict get $reply iids-duplicates] ;# No duplicates - - # Now add actual duplicates (same IID, same producer) - r XADD mystream IDMP p1 "req-1" * field "v1-p1-dup" - r XADD mystream IDMP p2 "req-1" * field "v1-p2-dup" - - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply pids-tracked] - assert_equal 2 [dict get $reply iids-added] - assert_equal 2 [dict get $reply iids-duplicates] ;# 2 duplicates (one per producer) - } - - test {XINFO STREAM iids counters after CFGSET clears history} { - r DEL mystream - - # Add entries with IDMP and create some duplicates - r XADD mystream IDMP p1 "req-1" * field "v1" - r XADD mystream IDMP p1 "req-2" * field "v2" - r XADD mystream IDMP p1 "req-3" * field "v3" - r XADD mystream IDMP p1 "req-1" * field "v1-dup" ;# Duplicate - r XADD mystream IDMP p1 "req-2" * field "v2-dup" ;# Duplicate - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply iids-tracked] - assert_equal 3 [dict get $reply iids-added] - assert_equal 2 [dict get $reply iids-duplicates] - - # CFGSET clears IID history - r XCFGSET mystream IDMP-DURATION 60 - - set reply [r XINFO STREAM mystream] - # iids-tracked should be 0 after history cleared - assert_equal 0 [dict get $reply iids-tracked] - # iids-added should still be preserved (lifetime counter) - assert_equal 3 [dict get $reply iids-added] - # iids-duplicates should still be preserved (lifetime counter) - assert_equal 2 [dict get $reply iids-duplicates] - - # Add new entry and verify counters - r XADD mystream IDMP p1 "req-4" * field "v4" - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply iids-tracked] - assert_equal 4 [dict get $reply iids-added] - } - - test {XINFO STREAM iids-added persists in RDB} { - r DEL mystream - - # Add entries with IDMP to build up iids-added counter - r XADD mystream IDMP p1 "req-1" * field "v1" - r XADD mystream IDMP p1 "req-2" * field "v2" - r XADD mystream IDMP p1 "req-3" * field "v3" - - # Set small MAXSIZE to cause eviction - r XCFGSET mystream IDMP-MAXSIZE 2 - - # Add more to trigger eviction (iids-tracked will be 2, but iids-added=5) - r XADD mystream IDMP p1 "req-4" * field "v4" - r XADD mystream IDMP p1 "req-5" * field "v5" - - # Verify values before save - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply iids-tracked] - assert_equal 5 [dict get $reply iids-added] - - # Save and restart - r SAVE - restart_server 0 true false - - # Verify iids-added persisted after restart - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply iids-tracked] - assert_equal 5 [dict get $reply iids-added] - } {} {external:skip} - - test {XINFO STREAM returns pids-tracked field} { - r DEL mystream - - # Create stream without IDMP - r XADD mystream * field "value" - - # Verify initial pids-tracked is 0 - set reply [r XINFO STREAM mystream] - assert_equal 0 [dict get $reply pids-tracked] - - # Add entry with first producer - r XADD mystream IDMP p1 "req-1" * field "v1" - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply pids-tracked] - - # Add entry with same producer - pids-tracked should stay 1 - r XADD mystream IDMP p1 "req-2" * field "v2" - set reply [r XINFO STREAM mystream] - assert_equal 1 [dict get $reply pids-tracked] - - # Add entry with second producer - r XADD mystream IDMP p2 "req-1" * field "v3" - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply pids-tracked] - - # Add entry with third producer - r XADD mystream IDMP producer3 "req-1" * field "v4" - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - } - - test {XINFO STREAM FULL returns pids-tracked field} { - r DEL mystream - - # Add entries with multiple producers - r XADD mystream IDMP prod-a "req-1" * field "v1" - r XADD mystream IDMP prod-b "req-1" * field "v2" - r XADD mystream IDMP prod-c "req-1" * field "v3" - - # Verify FULL mode also returns pids-tracked - set reply [r XINFO STREAM mystream FULL] - assert_equal 3 [dict get $reply pids-tracked] - } - - test {XINFO STREAM iids-tracked counts across all producers} { - r DEL mystream - - # Add entries with multiple producers - r XADD mystream IDMP p1 "req-1" * field "v1" - r XADD mystream IDMP p1 "req-2" * field "v2" - r XADD mystream IDMP p2 "req-1" * field "v3" - r XADD mystream IDMP p2 "req-2" * field "v4" - r XADD mystream IDMP p2 "req-3" * field "v5" - - # iids-tracked should count all IIDs across all producers (2 + 3 = 5) - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply pids-tracked] - assert_equal 5 [dict get $reply iids-tracked] - assert_equal 5 [dict get $reply iids-added] - - # Duplicates should not increment counters - r XADD mystream IDMP p1 "req-1" * field "dup" - r XADD mystream IDMP p2 "req-2" * field "dup" - - set reply [r XINFO STREAM mystream] - assert_equal 2 [dict get $reply pids-tracked] - assert_equal 5 [dict get $reply iids-tracked] - assert_equal 5 [dict get $reply iids-added] - } - - test {XINFO STREAM returns idmp-duration and idmp-maxsize fields} { - r DEL mystream - - # Create stream with default IDMP config - r XADD mystream IDMP p1 "req-1" * field "value1" - - # Verify default values - set reply [r XINFO STREAM mystream] - assert [dict exists $reply idmp-duration] - assert [dict exists $reply idmp-maxsize] - - # Get default values from server config - set default_duration [lindex [r CONFIG GET stream-idmp-duration] 1] - set default_maxsize [lindex [r CONFIG GET stream-idmp-maxsize] 1] - - assert_equal $default_duration [dict get $reply idmp-duration] - assert_equal $default_maxsize [dict get $reply idmp-maxsize] - - # Change IDMP config - r XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 50 - - set reply [r XINFO STREAM mystream] - assert_equal 300 [dict get $reply idmp-duration] - assert_equal 50 [dict get $reply idmp-maxsize] - - # Also verify FULL mode returns the same fields - set reply_full [r XINFO STREAM mystream FULL] - assert_equal 300 [dict get $reply_full idmp-duration] - assert_equal 50 [dict get $reply_full idmp-maxsize] - - # Change only DURATION - r XCFGSET mystream IDMP-DURATION 600 - set reply [r XINFO STREAM mystream] - assert_equal 600 [dict get $reply idmp-duration] - assert_equal 50 [dict get $reply idmp-maxsize] - - # Change only MAXSIZE - r XCFGSET mystream IDMP-MAXSIZE 100 - set reply [r XINFO STREAM mystream] - assert_equal 600 [dict get $reply idmp-duration] - assert_equal 100 [dict get $reply idmp-maxsize] - } - - test {XCFGSET IDMP-MAXSIZE wraparound keeps last 8 entries} { - r DEL mystream - - # Create stream and set MAXSIZE to 8 - r XADD mystream IDMP p1 "init" * field "init" - r XCFGSET mystream IDMP-MAXSIZE 8 IDMP-DURATION 60 - - # Add 100 unique entries and store their IDs in a list - set id_list {} - for {set i 1} {$i <= 100} {incr i} { - lappend id_list [r XADD mystream IDMP p1 "req-$i" * field "v$i"] - } - - # Verify the last 8 entries (93-100) still deduplicate - for {set i 93} {$i <= 100} {incr i} { - set idx [expr {$i - 1}] - set original_id [lindex $id_list $idx] - set dup_id [r XADD mystream IDMP p1 "req-$i" * field "dup"] - assert_equal $original_id $dup_id - } - - # Verify earlier entries (1-92) are evicted and create new entries - for {set i 1} {$i <= 92} {incr i} { - set idx [expr {$i - 1}] - set original_id [lindex $id_list $idx] - set new_id [r XADD mystream IDMP p1 "req-$i" * field "new"] - assert {$new_id ne $original_id} - } - - # Total entries: init + 100 original + 92 new = 193 - assert_equal 193 [r XLEN mystream] - } - - test {XCFGSET clears all producer histories} { - r DEL mystream - - # Add entries with multiple producers - set id1 [r XADD mystream IDMP p1 "req-1" * field "v1"] - set id2 [r XADD mystream IDMP p2 "req-1" * field "v2"] - set id3 [r XADD mystream IDMP p3 "req-1" * field "v3"] - - set reply [r XINFO STREAM mystream] - assert_equal 3 [dict get $reply pids-tracked] - assert_equal 3 [dict get $reply iids-tracked] - - # CFGSET clears all histories - r XCFGSET mystream IDMP-DURATION 60 - - set reply [r XINFO STREAM mystream] - # pids-tracked should be 0 after clearing - assert_equal 0 [dict get $reply pids-tracked] - assert_equal 0 [dict get $reply iids-tracked] - # iids-added is lifetime counter, should persist - assert_equal 3 [dict get $reply iids-added] - - # Can now add "duplicates" since history is cleared - set new_id1 [r XADD mystream IDMP p1 "req-1" * field "new"] - assert {$id1 ne $new_id1} - } - - test {CONFIG SET stream-idmp-duration and stream-idmp-maxsize validation} { - # Test maximum value rejection for duration (max: 86400) - assert_error "*must be between*" {r CONFIG SET stream-idmp-duration 86401} - assert_error "*must be between*" {r CONFIG SET stream-idmp-duration 100000} - - # Test maximum value rejection for maxsize (max: 10000) - assert_error "*must be between*" {r CONFIG SET stream-idmp-maxsize 10001} - assert_error "*must be between*" {r CONFIG SET stream-idmp-maxsize 50000} - - # Test minimum value rejection for duration (min: 1) - assert_error "*must be between*" {r CONFIG SET stream-idmp-duration 0} - assert_error "*must be between*" {r CONFIG SET stream-idmp-duration -1} - assert_error "*must be between*" {r CONFIG SET stream-idmp-duration -100} - - # Test minimum value rejection for maxsize (min: 1) - assert_error "*must be between*" {r CONFIG SET stream-idmp-maxsize 0} - assert_error "*must be between*" {r CONFIG SET stream-idmp-maxsize -1} - assert_error "*must be between*" {r CONFIG SET stream-idmp-maxsize -100} - - # Test exact boundary values work correctly - assert_equal "OK" [r CONFIG SET stream-idmp-duration 86400] - assert_equal "86400" [lindex [r CONFIG GET stream-idmp-duration] 1] - - assert_equal "OK" [r CONFIG SET stream-idmp-maxsize 10000] - assert_equal "10000" [lindex [r CONFIG GET stream-idmp-maxsize] 1] - - # Test minimum boundary values work (min: 1) - assert_equal "OK" [r CONFIG SET stream-idmp-duration 1] - assert_equal "1" [lindex [r CONFIG GET stream-idmp-duration] 1] - - assert_equal "OK" [r CONFIG SET stream-idmp-maxsize 1] - assert_equal "1" [lindex [r CONFIG GET stream-idmp-maxsize] 1] - - # Test valid intermediate values - assert_equal "OK" [r CONFIG SET stream-idmp-duration 100] - assert_equal "OK" [r CONFIG SET stream-idmp-maxsize 100] - - # Reset to defaults - assert_equal "OK" [r CONFIG SET stream-idmp-duration 100] - assert_equal "OK" [r CONFIG SET stream-idmp-maxsize 100] - } - - test {XTRIM with MINID option} { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - r XADD mystream 3-0 f v - r XADD mystream 4-0 f v - r XADD mystream 5-0 f v - r XTRIM mystream MINID = 3-0 - assert_equal [r XRANGE mystream - +] {{3-0 {f v}} {4-0 {f v}} {5-0 {f v}}} - } - - test {XTRIM with MINID option, big delta from master record} { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 1641544570597-0 f v - r XADD mystream 1641544570597-1 f v - r XTRIM mystream MINID 1641544570597-0 - assert_equal [r XRANGE mystream - +] {{1641544570597-0 {f v}} {1641544570597-1 {f v}}} - } - - proc insert_into_stream_key {key {count 10000}} { - r multi - for {set j 0} {$j < $count} {incr j} { - # From time to time insert a field with a different set - # of fields in order to stress the stream compression code. - if {rand() < 0.9} { - r XADD $key * item $j - } else { - r XADD $key * item $j otherfield foo - } - } - r exec - } - - test {XADD mass insertion and XLEN} { - r DEL mystream - insert_into_stream_key mystream - - set items [r XRANGE mystream - +] - for {set j 0} {$j < 10000} {incr j} { - assert {[lrange [lindex $items $j 1] 0 1] eq [list item $j]} - } - assert {[r xlen mystream] == $j} - } - - test {XADD with ID 0-0} { - r DEL otherstream - catch {r XADD otherstream 0-0 k v} err - assert {[r EXISTS otherstream] == 0} - } - - test {XADD with LIMIT delete entries no more than limit} { - r del yourstream - for {set j 0} {$j < 3} {incr j} { - r XADD yourstream * xitem v - } - r XADD yourstream MAXLEN ~ 0 limit 1 * xitem v - assert {[r XLEN yourstream] == 4} - } - - test {XRANGE COUNT works as expected} { - assert {[llength [r xrange mystream - + COUNT 10]] == 10} - } - - test {XREVRANGE COUNT works as expected} { - assert {[llength [r xrevrange mystream + - COUNT 10]] == 10} - } - - test {XRANGE can be used to iterate the whole stream} { - set last_id "-" - set j 0 - while 1 { - set elements [r xrange mystream $last_id + COUNT 100] - if {[llength $elements] == 0} break - foreach e $elements { - assert {[lrange [lindex $e 1] 0 1] eq [list item $j]} - incr j; - } - set last_id [streamNextID [lindex $elements end 0]] - } - assert {$j == 10000} - } - - test {XREVRANGE returns the reverse of XRANGE} { - assert {[r xrange mystream - +] == [lreverse [r xrevrange mystream + -]]} - } - - test {XRANGE exclusive ranges} { - set ids {0-1 0-18446744073709551615 1-0 42-0 42-42 - 18446744073709551615-18446744073709551614 - 18446744073709551615-18446744073709551615} - set total [llength $ids] - r multi - r DEL vipstream - foreach id $ids { - r XADD vipstream $id foo bar - } - r exec - assert {[llength [r xrange vipstream - +]] == $total} - assert {[llength [r xrange vipstream ([lindex $ids 0] +]] == $total-1} - assert {[llength [r xrange vipstream - ([lindex $ids $total-1]]] == $total-1} - assert {[llength [r xrange vipstream (0-1 (1-0]] == 1} - assert {[llength [r xrange vipstream (1-0 (42-42]] == 1} - catch {r xrange vipstream (- +} e - assert_match {ERR*} $e - catch {r xrange vipstream - (+} e - assert_match {ERR*} $e - catch {r xrange vipstream (18446744073709551615-18446744073709551615 +} e - assert_match {ERR*} $e - catch {r xrange vipstream - (0-0} e - assert_match {ERR*} $e - } - - test {XREAD with non empty stream} { - set res [r XREAD COUNT 1 STREAMS mystream 0-0] - assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {item 0}} - } - - test {Non blocking XREAD with empty streams} { - set res [r XREAD STREAMS s1{t} s2{t} 0-0 0-0] - assert {$res eq {}} - } - - test {XREAD with non empty second stream} { - insert_into_stream_key mystream{t} - set res [r XREAD COUNT 1 STREAMS nostream{t} mystream{t} 0-0 0-0] - assert {[lindex $res 0 0] eq {mystream{t}}} - assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {item 0}} - } - - test {Blocking XREAD waiting new data} { - r XADD s2{t} * old abcd1234 - set rd [redis_deferring_client] - $rd XREAD BLOCK 20000 STREAMS s1{t} s2{t} s3{t} $ $ $ - wait_for_blocked_client - r XADD s2{t} * new abcd1234 - set res [$rd read] - assert {[lindex $res 0 0] eq {s2{t}}} - assert {[lindex $res 0 1 0 1] eq {new abcd1234}} - $rd close - } - - test {Blocking XREAD waiting old data} { - set rd [redis_deferring_client] - $rd XREAD BLOCK 20000 STREAMS s1{t} s2{t} s3{t} $ 0-0 $ - r XADD s2{t} * foo abcd1234 - set res [$rd read] - assert {[lindex $res 0 0] eq {s2{t}}} - assert {[lindex $res 0 1 0 1] eq {old abcd1234}} - $rd close - } - - test {Blocking XREAD will not reply with an empty array} { - r del s1 - r XADD s1 666 f v - r XADD s1 667 f2 v2 - r XDEL s1 667 - set rd [redis_deferring_client] - $rd XREAD BLOCK 10 STREAMS s1 666 - after 20 - assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {s1 {}} - $rd close - } - - test "Blocking XREAD for stream that ran dry (issue #5299)" { - set rd [redis_deferring_client] - - # Add a entry then delete it, now stream's last_id is 666. - r DEL mystream - r XADD mystream 666 key value - r XDEL mystream 666 - - # Pass a ID smaller than stream's last_id, released on timeout. - $rd XREAD BLOCK 10 STREAMS mystream 665 - assert_equal [$rd read] {} - - # Throw an error if the ID equal or smaller than the last_id. - assert_error ERR*equal*smaller* {r XADD mystream 665 key value} - assert_error ERR*equal*smaller* {r XADD mystream 666 key value} - - # Entered blocking state and then release because of the new entry. - $rd XREAD BLOCK 0 STREAMS mystream 665 - wait_for_blocked_clients_count 1 - r XADD mystream 667 key value - assert_equal [$rd read] {{mystream {{667-0 {key value}}}}} - - $rd close - } - - test {XREAD last element from non-empty stream} { - # should return last entry - - # add 3 entries to a stream - r DEL lestream - r XADD lestream 1-0 k1 v1 - r XADD lestream 2-0 k2 v2 - r XADD lestream 3-0 k3 v3 - - # read the last entry - set res [r XREAD STREAMS lestream +] - - # verify it's the last entry - assert_equal $res {{lestream {{3-0 {k3 v3}}}}} - - # two more entries, with MAX_UINT64 for sequence number for the last one - r XADD lestream 3-18446744073709551614 k4 v4 - r XADD lestream 3-18446744073709551615 k5 v5 - - # read the new last entry - set res [r XREAD STREAMS lestream +] - - # verify it's the last entry - assert_equal $res {{lestream {{3-18446744073709551615 {k5 v5}}}}} - } - - test {XREAD last element from empty stream} { - # should return nil - - # make sure the stream is empty - r DEL lestream - - # read last entry and verify nil is received - assert_equal [r XREAD STREAMS lestream +] {} - - # add an element to the stream, than delete it - r XADD lestream 1-0 k1 v1 - r XDEL lestream 1-0 - - # verify nil is still received when reading last entry - assert_equal [r XREAD STREAMS lestream +] {} - - # case when stream created empty - - # make sure the stream is not initialized - r DEL lestream - - # create empty stream with XGROUP CREATE - r XGROUP CREATE lestream legroup $ MKSTREAM - - # verify nil is received when reading last entry - assert_equal [r XREAD STREAMS lestream +] {} - } - - test {XREAD last element blocking from empty stream} { - # should block until a new entry is available - - # make sure there is no stream - r DEL lestream - - # read last entry from stream, blocking - set rd [redis_deferring_client] - $rd XREAD BLOCK 20000 STREAMS lestream + - wait_for_blocked_client - - # add an entry to the stream - r XADD lestream 1-0 k1 v1 - - # read and verify result - set res [$rd read] - assert_equal $res {{lestream {{1-0 {k1 v1}}}}} - $rd close - } - - test {XREAD last element blocking from non-empty stream} { - # should return last element immediately, w/o blocking - - # add 3 entries to a stream - r DEL lestream - r XADD lestream 1-0 k1 v1 - r XADD lestream 2-0 k2 v2 - r XADD lestream 3-0 k3 v3 - - # read the last entry - set res [r XREAD BLOCK 1000000 STREAMS lestream +] - - # verify it's the last entry - assert_equal $res {{lestream {{3-0 {k3 v3}}}}} - } - - test {XREAD last element from multiple streams} { - # should return last element only from non-empty streams - - # add 3 entries to one stream - r DEL "\{lestream\}1" - r XADD "\{lestream\}1" 1-0 k1 v1 - r XADD "\{lestream\}1" 2-0 k2 v2 - r XADD "\{lestream\}1" 3-0 k3 v3 - - # add 3 entries to another stream - r DEL "\{lestream\}2" - r XADD "\{lestream\}2" 1-0 k1 v4 - r XADD "\{lestream\}2" 2-0 k2 v5 - r XADD "\{lestream\}2" 3-0 k3 v6 - - # read last element from 3 streams (2 with enetries, 1 non-existent) - # verify the last element from the two existing streams were returned - set res [r XREAD STREAMS "\{lestream\}1" "\{lestream\}2" "\{lestream\}3" + + +] - assert_equal $res {{{{lestream}1} {{3-0 {k3 v3}}}} {{{lestream}2} {{3-0 {k3 v6}}}}} - } - - test {XREAD last element with count > 1} { - # Should return only the last element - count has no affect here - - # add 3 entries to a stream - r DEL lestream - r XADD lestream 1-0 k1 v1 - r XADD lestream 2-0 k2 v2 - r XADD lestream 3-0 k3 v3 - - # read the last entry - set res [r XREAD COUNT 3 STREAMS lestream +] - - # verify only last entry was read, even though COUNT > 1 - assert_equal $res {{lestream {{3-0 {k3 v3}}}}} - } - - test "XREAD: read last element after XDEL (issue #13628)" { - # Should return actual last element after XDEL of current last element - - # Add 2 entries to a stream and delete last one - r DEL stream - r XADD stream 1-0 f 1 - r XADD stream 2-0 f 2 - r XDEL stream 2-0 - - # Read last entry - set res [r XREAD STREAMS stream +] - - # Verify the last entry was read - assert_equal $res {{stream {{1-0 {f 1}}}}} - } - - test "XREAD: XADD + DEL should not awake client" { - set rd [redis_deferring_client] - r del s1 - $rd XREAD BLOCK 20000 STREAMS s1 $ - wait_for_blocked_clients_count 1 - r multi - r XADD s1 * old abcd1234 - r DEL s1 - r exec - r XADD s1 * new abcd1234 - set res [$rd read] - assert {[lindex $res 0 0] eq {s1}} - assert {[lindex $res 0 1 0 1] eq {new abcd1234}} - $rd close - } - - test "XREAD: XADD + DEL + LPUSH should not awake client" { - set rd [redis_deferring_client] - r del s1 - $rd XREAD BLOCK 20000 STREAMS s1 $ - wait_for_blocked_clients_count 1 - r multi - r XADD s1 * old abcd1234 - r DEL s1 - r LPUSH s1 foo bar - r exec - r DEL s1 - r XADD s1 * new abcd1234 - set res [$rd read] - assert {[lindex $res 0 0] eq {s1}} - assert {[lindex $res 0 1 0 1] eq {new abcd1234}} - $rd close - } - - test {XREAD with same stream name multiple times should work} { - r XADD s2 * old abcd1234 - set rd [redis_deferring_client] - $rd XREAD BLOCK 20000 STREAMS s2 s2 s2 $ $ $ - wait_for_blocked_clients_count 1 - r XADD s2 * new abcd1234 - set res [$rd read] - assert {[lindex $res 0 0] eq {s2}} - assert {[lindex $res 0 1 0 1] eq {new abcd1234}} - $rd close - } - - test {XREAD + multiple XADD inside transaction} { - r XADD s2 * old abcd1234 - set rd [redis_deferring_client] - $rd XREAD BLOCK 20000 STREAMS s2 s2 s2 $ $ $ - wait_for_blocked_clients_count 1 - r MULTI - r XADD s2 * field one - r XADD s2 * field two - r XADD s2 * field three - r EXEC - set res [$rd read] - assert {[lindex $res 0 0] eq {s2}} - assert {[lindex $res 0 1 0 1] eq {field one}} - assert {[lindex $res 0 1 1 1] eq {field two}} - $rd close - } - - test {XDEL basic test} { - r del somestream - r xadd somestream * foo value0 - set id [r xadd somestream * foo value1] - r xadd somestream * foo value2 - r xdel somestream $id - assert {[r xlen somestream] == 2} - set result [r xrange somestream - +] - assert {[lindex $result 0 1 1] eq {value0}} - assert {[lindex $result 1 1 1] eq {value2}} - } - - test {XDEL multiply id test} { - r del somestream - r xadd somestream 1-1 a 1 - r xadd somestream 1-2 b 2 - r xadd somestream 1-3 c 3 - r xadd somestream 1-4 d 4 - r xadd somestream 1-5 e 5 - assert {[r xlen somestream] == 5} - assert {[r xdel somestream 1-1 1-4 1-5 2-1] == 3} - assert {[r xlen somestream] == 2} - set result [r xrange somestream - +] - assert {[dict get [lindex $result 0 1] b] eq {2}} - assert {[dict get [lindex $result 1 1] c] eq {3}} - } - # Here the idea is to check the consistency of the stream data structure - # as we remove all the elements down to zero elements. - test {XDEL fuzz test} { - r del somestream - set ids {} - set x 0; # Length of the stream - while 1 { - lappend ids [r xadd somestream * item $x] - incr x - # Add enough elements to have a few radix tree nodes inside the stream. - if {[dict get [r xinfo stream somestream] radix-tree-keys] > 20} break - } - - # Now remove all the elements till we reach an empty stream - # and after every deletion, check that the stream is sane enough - # to report the right number of elements with XRANGE: this will also - # force accessing the whole data structure to check sanity. - assert {[r xlen somestream] == $x} - - # We want to remove elements in random order to really test the - # implementation in a better way. - set ids [lshuffle $ids] - foreach id $ids { - assert {[r xdel somestream $id] == 1} - incr x -1 - assert {[r xlen somestream] == $x} - # The test would be too slow calling XRANGE for every iteration. - # Do it every 100 removal. - if {$x % 100 == 0} { - set res [r xrange somestream - +] - assert {[llength $res] == $x} - } - } - } - - test {XRANGE fuzzing} { - set items [r XRANGE mystream{t} - +] - set low_id [lindex $items 0 0] - set high_id [lindex $items end 0] - for {set j 0} {$j < 100} {incr j} { - set start [streamRandomID $low_id $high_id] - set end [streamRandomID $low_id $high_id] - set range [r xrange mystream{t} $start $end] - set tcl_range [streamSimulateXRANGE $items $start $end] - if {$range ne $tcl_range} { - puts "*** WARNING *** - XRANGE fuzzing mismatch: $start - $end" - puts "---" - puts "XRANGE: '$range'" - puts "---" - puts "TCL: '$tcl_range'" - puts "---" - fail "XRANGE fuzzing failed, check logs for details" - } - } - } - - test {XREVRANGE regression test for issue #5006} { - # Add non compressed entries - r xadd teststream 1234567891230 key1 value1 - r xadd teststream 1234567891240 key2 value2 - r xadd teststream 1234567891250 key3 value3 - - # Add SAMEFIELD compressed entries - r xadd teststream2 1234567891230 key1 value1 - r xadd teststream2 1234567891240 key1 value2 - r xadd teststream2 1234567891250 key1 value3 - - assert_equal [r xrevrange teststream 1234567891245 -] {{1234567891240-0 {key2 value2}} {1234567891230-0 {key1 value1}}} - - assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}} - } - - test {XREAD streamID edge (no-blocking)} { - r del x - r XADD x 1-1 f v - r XADD x 1-18446744073709551615 f v - r XADD x 2-1 f v - set res [r XREAD BLOCK 0 STREAMS x 1-18446744073709551615] - assert {[lindex $res 0 1 0] == {2-1 {f v}}} - } - - test {XREAD streamID edge (blocking)} { - r del x - set rd [redis_deferring_client] - $rd XREAD BLOCK 0 STREAMS x 1-18446744073709551615 - wait_for_blocked_clients_count 1 - r XADD x 1-1 f v - r XADD x 1-18446744073709551615 f v - r XADD x 2-1 f v - set res [$rd read] - assert {[lindex $res 0 1 0] == {2-1 {f v}}} - $rd close - } - - test {XADD streamID edge} { - r del x - r XADD x 2577343934890-18446744073709551615 f v ;# we need the timestamp to be in the future - r XADD x * f2 v2 - assert_equal [r XRANGE x - +] {{2577343934890-18446744073709551615 {f v}} {2577343934891-0 {f2 v2}}} - } - - test {XTRIM with MAXLEN option basic test} { - r DEL mystream - for {set j 0} {$j < 1000} {incr j} { - if {rand() < 0.9} { - r XADD mystream * xitem $j - } else { - r XADD mystream * yitem $j - } - } - r XTRIM mystream MAXLEN 666 - assert {[r XLEN mystream] == 666} - r XTRIM mystream MAXLEN = 555 - assert {[r XLEN mystream] == 555} - r XTRIM mystream MAXLEN ~ 444 - assert {[r XLEN mystream] == 500} - r XTRIM mystream MAXLEN ~ 400 - assert {[r XLEN mystream] == 400} - } - - test {XADD with LIMIT consecutive calls} { - r del mystream - r config set stream-node-max-entries 10 - for {set j 0} {$j < 100} {incr j} { - r XADD mystream * xitem v - } - r XADD mystream MAXLEN ~ 55 LIMIT 30 * xitem v - assert {[r xlen mystream] == 71} - r XADD mystream MAXLEN ~ 55 LIMIT 30 * xitem v - assert {[r xlen mystream] == 62} - r config set stream-node-max-entries 100 - } - - test {XTRIM with ~ is limited} { - r del mystream - r config set stream-node-max-entries 1 - for {set j 0} {$j < 102} {incr j} { - r XADD mystream * xitem v - } - r XTRIM mystream MAXLEN ~ 1 - assert {[r xlen mystream] == 2} - r config set stream-node-max-entries 100 - } - - test {XTRIM without ~ is not limited} { - r del mystream - r config set stream-node-max-entries 1 - for {set j 0} {$j < 102} {incr j} { - r XADD mystream * xitem v - } - r XTRIM mystream MAXLEN 1 - assert {[r xlen mystream] == 1} - r config set stream-node-max-entries 100 - } - - test {XTRIM without ~ and with LIMIT} { - r del mystream - r config set stream-node-max-entries 1 - for {set j 0} {$j < 102} {incr j} { - r XADD mystream * xitem v - } - assert_error ERR* {r XTRIM mystream MAXLEN 1 LIMIT 30} - } - - test {XTRIM with LIMIT delete entries no more than limit} { - r del mystream - r config set stream-node-max-entries 2 - for {set j 0} {$j < 3} {incr j} { - r XADD mystream * xitem v - } - assert {[r XTRIM mystream MAXLEN ~ 0 LIMIT 1] == 0} - assert {[r XTRIM mystream MAXLEN ~ 0 LIMIT 2] == 2} - } - - test {XTRIM with approx and ACKED deletes entries correctly} { - # This test verifies that when using approx trim (~) with ACKED strategy, - # if the first node cannot be removed (has unacked messages), we should - # continue to check subsequent nodes that might be eligible for removal. - r DEL mystream - set origin_max_entries [config_get_set stream-node-max-entries 2] - - # Create 5 entries in 3 nodes (2 entries per node) - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - r XADD mystream 3-0 f v - r XADD mystream 4-0 f v - r XADD mystream 5-0 f v - - # Create a consumer group and read all messages - r XGROUP CREATE mystream mygroup 0 - r XREADGROUP GROUP mygroup consumer1 STREAMS mystream > - - # Acknowledge messages: 1-0, 2-0 (first node), and 4-0 (second node) - r XACK mystream mygroup 1-0 2-0 4-0 - - # XTRIM MINID ~ 6-0 ACKED should remove: - # Total 3 entries removed (1-0, 2-0, 4-0), 2 unacked entries remain (3-0, 5-0) - assert_equal 3 [r XTRIM mystream MINID ~ 6-0 ACKED] - assert_equal 2 [r XLEN mystream] - assert_equal {{3-0 {f v}} {5-0 {f v}}} [r XRANGE mystream - +] - - r config set stream-node-max-entries $origin_max_entries - } - - test {XTRIM with approx and DELREF deletes entries correctly} { - # Similar test but with DELREF strategy - r DEL mystream - set origin_max_entries [config_get_set stream-node-max-entries 2] - - # Create 4 entries in 2 nodes - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - r XADD mystream 3-0 f v - r XADD mystream 4-0 f v - - # Create a consumer group and read all messages - r XGROUP CREATE mystream mygroup 0 - r XREADGROUP GROUP mygroup consumer1 STREAMS mystream > - - # With XTRIM MINID ~ 5-0 DELREF, all eligible nodes should be trimmed - # and PEL entries should be cleaned up - assert_equal 4 [r XTRIM mystream MINID ~ 5-0 DELREF] - assert_equal 0 [r XLEN mystream] - # PEL should be empty after DELREF - assert_equal {0 {} {} {}} [r XPENDING mystream mygroup] - - r config set stream-node-max-entries $origin_max_entries - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes}} { - test {XADD with MAXLEN > xlen can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - r XADD mystream * xitem v - } - r XADD mystream MAXLEN 200 * xitem v - incr j - assert {[r xlen mystream] == $j} - r debug loadaof - r XADD mystream * xitem v - incr j - assert {[r xlen mystream] == $j} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes}} { - test {XADD with MINID > lastid can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - set id [expr {$j+1}] - r XADD mystream $id xitem v - } - r XADD mystream MINID 1 * xitem v - incr j - assert {[r xlen mystream] == $j} - r debug loadaof - r XADD mystream * xitem v - incr j - assert {[r xlen mystream] == $j} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes stream-node-max-entries 100}} { - test {XADD with ~ MAXLEN can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - r XADD mystream * xitem v - } - r XADD mystream MAXLEN ~ $j * xitem v - incr j - assert {[r xlen mystream] == $j} - r config set stream-node-max-entries 1 - r debug loadaof - r XADD mystream * xitem v - incr j - assert {[r xlen mystream] == $j} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes stream-node-max-entries 10}} { - test {XADD with ~ MAXLEN and LIMIT can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - r XADD mystream * xitem v - } - r XADD mystream MAXLEN ~ 55 LIMIT 30 * xitem v - assert {[r xlen mystream] == 71} - r config set stream-node-max-entries 1 - r debug loadaof - r XADD mystream * xitem v - assert {[r xlen mystream] == 72} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes stream-node-max-entries 100}} { - test {XADD with ~ MINID can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - set id [expr {$j+1}] - r XADD mystream $id xitem v - } - r XADD mystream MINID ~ $j * xitem v - incr j - assert {[r xlen mystream] == $j} - r config set stream-node-max-entries 1 - r debug loadaof - r XADD mystream * xitem v - incr j - assert {[r xlen mystream] == $j} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes stream-node-max-entries 10}} { - test {XADD with ~ MINID and LIMIT can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - set id [expr {$j+1}] - r XADD mystream $id xitem v - } - r XADD mystream MINID ~ 55 LIMIT 30 * xitem v - assert {[r xlen mystream] == 71} - r config set stream-node-max-entries 1 - r debug loadaof - r XADD mystream * xitem v - assert {[r xlen mystream] == 72} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes stream-node-max-entries 10}} { - test {XTRIM with ~ MAXLEN can propagate correctly} { - for {set j 0} {$j < 100} {incr j} { - r XADD mystream * xitem v - } - r XTRIM mystream MAXLEN ~ 85 - assert {[r xlen mystream] == 90} - r config set stream-node-max-entries 1 - r debug loadaof - r XADD mystream * xitem v - incr j - assert {[r xlen mystream] == 91} - } -} - -start_server {tags {"stream"}} { - test {XADD can CREATE an empty stream} { - r XADD mystream MAXLEN 0 * a b - assert {[dict get [r xinfo stream mystream] length] == 0} - } - - test {XSETID can set a specific ID} { - r XSETID mystream "200-0" - set reply [r XINFO stream mystream] - assert_equal [dict get $reply last-generated-id] "200-0" - assert_equal [dict get $reply entries-added] 1 - } - - test {XSETID cannot SETID with smaller ID} { - r XADD mystream * a b - catch {r XSETID mystream "1-1"} err - r XADD mystream MAXLEN 0 * a b - set err - } {ERR *smaller*} - - test {XSETID cannot SETID on non-existent key} { - catch {r XSETID stream 1-1} err - set _ $err - } {ERR no such key} - - test {XSETID cannot run with an offset but without a maximal tombstone} { - catch {r XSETID stream 1-1 0} err - set _ $err - } {ERR syntax error} - - test {XSETID cannot run with a maximal tombstone but without an offset} { - catch {r XSETID stream 1-1 0-0} err - set _ $err - } {ERR syntax error} - - test {XSETID errors on negstive offset} { - catch {r XSETID stream 1-1 ENTRIESADDED -1 MAXDELETEDID 0-0} err - set _ $err - } {ERR *must be positive} - - test {XSETID cannot set the maximal tombstone with larger ID} { - r DEL x - r XADD x 1-0 a b - - catch {r XSETID x "1-0" ENTRIESADDED 1 MAXDELETEDID "2-0" } err - r XADD mystream MAXLEN 0 * a b - set err - } {ERR *smaller*} - - test {XSETID cannot set the offset to less than the length} { - r DEL x - r XADD x 1-0 a b - - catch {r XSETID x "1-0" ENTRIESADDED 0 MAXDELETEDID "0-0" } err - r XADD mystream MAXLEN 0 * a b - set err - } {ERR *smaller*} - - test {XSETID cannot set smaller ID than current MAXDELETEDID} { - r DEL x - r XADD x 1-1 a 1 - r XADD x 1-2 b 2 - r XADD x 1-3 c 3 - r XDEL x 1-2 - r XDEL x 1-3 - set reply [r XINFO stream x] - assert_equal [dict get $reply max-deleted-entry-id] "1-3" - catch {r XSETID x "1-2" } err - set err - } {ERR *smaller*} -} - -start_server {tags {"stream"}} { - test {XADD advances the entries-added counter and sets the recorded-first-entry-id} { - r DEL x - r XADD x 1-0 data a - - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply entries-added] 1 - assert_equal [dict get $reply recorded-first-entry-id] "1-0" - - r XADD x 2-0 data a - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply entries-added] 2 - assert_equal [dict get $reply recorded-first-entry-id] "1-0" - } - - test {XDEL/TRIM are reflected by recorded first entry} { - r DEL x - r XADD x 1-0 data a - r XADD x 2-0 data a - r XADD x 3-0 data a - r XADD x 4-0 data a - r XADD x 5-0 data a - - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply entries-added] 5 - assert_equal [dict get $reply recorded-first-entry-id] "1-0" - - r XDEL x 2-0 - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply recorded-first-entry-id] "1-0" - - r XDEL x 1-0 - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply recorded-first-entry-id] "3-0" - - r XTRIM x MAXLEN = 2 - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply recorded-first-entry-id] "4-0" - } - - test {Maximum XDEL ID behaves correctly} { - r DEL x - r XADD x 1-0 data a - r XADD x 2-0 data b - r XADD x 3-0 data c - - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply max-deleted-entry-id] "0-0" - - r XDEL x 2-0 - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply max-deleted-entry-id] "2-0" - - r XDEL x 1-0 - set reply [r XINFO STREAM x FULL] - assert_equal [dict get $reply max-deleted-entry-id] "2-0" - } - - test {XADD with artial ID with maximal seq} { - r DEL x - r XADD x 1-18446744073709551615 f1 v1 - assert_error {*The ID specified in XADD is equal or smaller*} {r XADD x 1-* f2 v2} - } -} - -start_server {tags {"stream needs:debug"} overrides {appendonly yes aof-use-rdb-preamble no}} { - test {Empty stream can be rewrite into AOF correctly} { - r XADD mystream MAXLEN 0 * a b - assert {[dict get [r xinfo stream mystream] length] == 0} - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - assert {[dict get [r xinfo stream mystream] length] == 0} - } - - test {Stream can be rewrite into AOF correctly after XDEL lastid} { - r XSETID mystream 0-0 - r XADD mystream 1-1 a b - r XADD mystream 2-2 a b - assert {[dict get [r xinfo stream mystream] length] == 2} - r XDEL mystream 2-2 - r bgrewriteaof - waitForBgrewriteaof r - r debug loadaof - assert {[dict get [r xinfo stream mystream] length] == 1} - assert_equal [dict get [r xinfo stream mystream] last-generated-id] "2-2" - } -} - -start_server {tags {"stream"}} { - test {XGROUP HELP should not have unexpected options} { - catch {r XGROUP help xxx} e - assert_match "*wrong number of arguments for 'xgroup|help' command" $e - } - - test {XINFO HELP should not have unexpected options} { - catch {r XINFO help xxx} e - assert_match "*wrong number of arguments for 'xinfo|help' command" $e - } -} - -start_server {tags {"stream"}} { - test "XDELEX wrong number of args" { - assert_error {*wrong number of arguments for 'xdelex' command} {r XDELEX s DELREF} - } - - test "XDELEX should return empty array when key doesn't exist" { - r DEL nonexist - assert_equal {-1 -1} [r XDELEX nonexist IDS 2 1-1 2-2] - } - - test "XDELEX IDS parameter validation" { - r DEL s - r XADD s 1-0 f v - r XGROUP CREATE s g 0 - - # Test invalid numids - assert_error {*Number of IDs must be a positive integer*} {r XDELEX s IDS abc 1-1} - assert_error {*Number of IDs must be a positive integer*} {r XDELEX s IDS 0 1-1} - assert_error {*Number of IDs must be a positive integer*} {r XDELEX s IDS -5 1-1} - - # Test whether numids is equal to the number of IDs provided - assert_error {*The `numids` parameter must match the number of arguments*} {r XDELEX s IDS 3 1-1 2-2} - assert_error {*syntax error*} {r XDELEX s IDS 1 1-1 2-2} - - # Delete non-existent ids - assert_equal {-1 -1} [r XDELEX s IDS 2 1-1 2-2] - } - - test "XDELEX KEEPREF/DELREF/ACKED parameter validation" { - # Test mutually exclusive options - assert_error {*syntax error*} {r XDELEX s KEEPREF DELREF IDS 1 1-1} - assert_error {*syntax error*} {r XDELEX s KEEPREF ACKED IDS 1 1-1} - assert_error {*syntax error*} {r XDELEX s ACKED DELREF IDS 1 1-1} - } - - test "XDELEX with DELREF option acknowledges will remove entry from all PELs" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - - # Create two consumer groups - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer2 STREAMS mystream > - - # Verify the message was removed from both groups' PELs when with DELREF - assert_equal {1 1} [r XDELEX mystream DELREF IDS 2 1-0 2-0] - assert_equal 0 [r XLEN mystream] - assert_equal {0 {} {} {}} [r XPENDING mystream group1] - assert_equal {0 {} {} {}} [r XPENDING mystream group2] - } - - test "XDELEX with ACKED option only deletes messages acknowledged by all groups" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - - # Create two consumer groups - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - r XGROUP CREATE mystream group3 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer2 STREAMS mystream > - - # The message is referenced by three consumer groups: - # - group1 and group2 have read the messages - # - group3 hasn't read the messages yet (not delivered) - # Even after group1 acknowledges the messages, they still can't be deleted - r XACK mystream group1 1-0 2-0 - assert_equal {2 2} [r XDELEX mystream ACKED IDS 2 1-0 2-0] - assert_equal 2 [r XLEN mystream] - - # Even after both group1 and group2 acknowledge the messages, these entries - # still can't be deleted because group3 hasn't even read them yet. - r XACK mystream group2 1-0 2-0 - assert_equal {2 2} [r XDELEX mystream ACKED IDS 2 1-0 2-0] - assert_equal 2 [r XLEN mystream] - - # Now group3 reads the messages, but hasn't acknowledged them yet. - # these entries still can't be deleted because group3 hasn't acknowledged them. - r XREADGROUP GROUP group3 consumer3 STREAMS mystream > - assert_equal {2 2} [r XDELEX mystream ACKED IDS 2 1-0 2-0] - assert_equal 2 [r XLEN mystream] - - # Now group3 acknowledges the messages. These entries can now be deleted. - r XACK mystream group3 1-0 2-0 - r XDELEX mystream ACKED IDS 2 1-0 2-0 - assert_equal 0 [r XLEN mystream] - } - - test "XDELEX with ACKED option won't delete messages when new consumer groups are created" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - r XADD mystream 3-0 f v - - r XGROUP CREATE mystream group1 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - - # When the group1 ack message, the message can be deleted with ACK option. - assert_equal {3} [r XACK mystream group1 1-0 2-0 3-0] - assert_equal {1} [r XDELEX mystream ACKED IDS 1 1-0] - - # Create a new consumer group that hasn't read the messages yet. - # Even if group1 ack the message, we still can't delete the message. - r XGROUP CREATE mystream group2 0 - assert_equal {2} [r XDELEX mystream ACKED IDS 1 2-0] - - # Now group2 reads and acknowledges the messages, - # so we can be successfully deleted with the ACKED option. - r XREADGROUP GROUP group2 consumer1 STREAMS mystream > - assert_equal {2} [r XACK mystream group2 2-0 3-0] - assert_equal {1 1} [r XDELEX mystream ACKED IDS 2 2-0 3-0] - } - - test "XDELEX with KEEPREF" { - r DEL mystream - r XADD mystream 1-0 f v - r XADD mystream 2-0 f v - - # Create two consumer groups - r XGROUP CREATE mystream group1 0 - r XGROUP CREATE mystream group2 0 - r XREADGROUP GROUP group1 consumer1 STREAMS mystream > - r XREADGROUP GROUP group2 consumer2 STREAMS mystream > - - # Test XDELEX with KEEPREF - # XDELEX only deletes the message from the stream - # but does not clean up references in consumer groups' PELs - assert_equal {1 1} [r XDELEX mystream KEEPREF IDS 2 1-0 2-0] - assert_equal 0 [r XLEN mystream] - assert_equal {2 1-0 2-0 {{consumer1 2}}} [r XPENDING mystream group1] - assert_equal {2 1-0 2-0 {{consumer2 2}}} [r XPENDING mystream group2] - } -} diff --git a/examples/redis-unstable/tests/unit/type/string.tcl b/examples/redis-unstable/tests/unit/type/string.tcl deleted file mode 100644 index d0a9135..0000000 --- a/examples/redis-unstable/tests/unit/type/string.tcl +++ /dev/null @@ -1,1556 +0,0 @@ -start_server {tags {"string"}} { - test {SET and GET an item} { - r set x foobar - r get x - } {foobar} - - test {SET and GET an empty item} { - r set x {} - r get x - } {} - - test {Very big payload in GET/SET} { - set buf [string repeat "abcd" 1000000] - r set foo $buf - r get foo - } [string repeat "abcd" 1000000] - - tags {"slow"} { - test {Very big payload random access} { - set err {} - array set payload {} - for {set j 0} {$j < 100} {incr j} { - set size [expr 1+[randomInt 100000]] - set buf [string repeat "pl-$j" $size] - set payload($j) $buf - r set bigpayload_$j $buf - } - for {set j 0} {$j < 1000} {incr j} { - set index [randomInt 100] - set buf [r get bigpayload_$index] - if {$buf != $payload($index)} { - set err "Values differ: I set '$payload($index)' but I read back '$buf'" - break - } - } - unset payload - set _ $err - } {} - - test {SET 10000 numeric keys and access all them in reverse order} { - r flushdb - set err {} - for {set x 0} {$x < 10000} {incr x} { - r set $x $x - } - set sum 0 - for {set x 9999} {$x >= 0} {incr x -1} { - set val [r get $x] - if {$val ne $x} { - set err "Element at position $x is $val instead of $x" - break - } - } - set _ $err - } {} - - test {DBSIZE should be 10000 now} { - r dbsize - } {10000} - } - - test "SETNX target key missing" { - r del novar - assert_equal 1 [r setnx novar foobared] - assert_equal "foobared" [r get novar] - } - - test "SETNX target key exists" { - r set novar foobared - assert_equal 0 [r setnx novar blabla] - assert_equal "foobared" [r get novar] - } - - test "SETNX against not-expired volatile key" { - r set x 10 - r expire x 10000 - assert_equal 0 [r setnx x 20] - assert_equal 10 [r get x] - } - - test "SETNX against expired volatile key" { - # Make it very unlikely for the key this test uses to be expired by the - # active expiry cycle. This is tightly coupled to the implementation of - # active expiry and dbAdd() but currently the only way to test that - # SETNX expires a key when it should have been. - for {set x 0} {$x < 9999} {incr x} { - r setex key-$x 3600 value - } - - # This will be one of 10000 expiring keys. A cycle is executed every - # 100ms, sampling 10 keys for being expired or not. This key will be - # expired for at most 1s when we wait 2s, resulting in a total sample - # of 100 keys. The probability of the success of this test being a - # false positive is therefore approx. 1%. - r set x 10 - r expire x 1 - - # Wait for the key to expire - after 2000 - - assert_equal 1 [r setnx x 20] - assert_equal 20 [r get x] - } {} {debug_defrag:skip} - - test "GETEX EX option" { - r del foo - r set foo bar - r getex foo ex 10 - assert_range [r ttl foo] 5 10 - } - - test "GETEX PX option" { - r del foo - r set foo bar - r getex foo px 10000 - assert_range [r pttl foo] 5000 10000 - } - - test "GETEX EXAT option" { - r del foo - r set foo bar - r getex foo exat [expr [clock seconds] + 10] - assert_range [r ttl foo] 5 10 - } - - test "GETEX PXAT option" { - r del foo - r set foo bar - r getex foo pxat [expr [clock milliseconds] + 10000] - assert_range [r pttl foo] 5000 10000 - } - - test "GETEX PERSIST option" { - r del foo - r set foo bar ex 10 - assert_range [r ttl foo] 5 10 - r getex foo persist - assert_equal -1 [r ttl foo] - } - - test "GETEX no option" { - r del foo - r set foo bar - r getex foo - assert_equal bar [r getex foo] - } - - test "GETEX syntax errors" { - set ex {} - catch {r getex foo non-existent-option} ex - set ex - } {*syntax*} - - test "GETEX and GET expired key or not exist" { - r del foo - r set foo bar px 1 - after 2 - assert_equal {} [r getex foo] - assert_equal {} [r get foo] - } - - test "GETEX no arguments" { - set ex {} - catch {r getex} ex - set ex - } {*wrong number of arguments for 'getex' command} - - test "GETDEL command" { - r del foo - r set foo bar - assert_equal bar [r getdel foo ] - assert_equal {} [r getdel foo ] - } - - test {GETDEL propagate as DEL command to replica} { - set repl [attach_to_replication_stream] - r set foo bar - r getdel foo - assert_replication_stream $repl { - {select *} - {set foo bar} - {del foo} - } - close_replication_stream $repl - } {} {needs:repl} - - test {GETEX without argument does not propagate to replica} { - set repl [attach_to_replication_stream] - r set foo bar - r getex foo - r del foo - assert_replication_stream $repl { - {select *} - {set foo bar} - {del foo} - } - close_replication_stream $repl - } {} {needs:repl} - - test {MGET} { - r flushdb - r set foo{t} BAR - r set bar{t} FOO - r mget foo{t} bar{t} - } {BAR FOO} - - test {MGET against non existing key} { - r mget foo{t} baazz{t} bar{t} - } {BAR {} FOO} - - test {MGET against non-string key} { - r sadd myset{t} ciao - r sadd myset{t} bau - r mget foo{t} baazz{t} bar{t} myset{t} - } {BAR {} FOO {}} - - test {GETSET (set new value)} { - r del foo - list [r getset foo xyz] [r get foo] - } {{} xyz} - - test {GETSET (replace old value)} { - r set foo bar - list [r getset foo xyz] [r get foo] - } {bar xyz} - - test {MSET base case} { - r mset x{t} 10 y{t} "foo bar" z{t} "x x x x x x x\n\n\r\n" - r mget x{t} y{t} z{t} - } [list 10 {foo bar} "x x x x x x x\n\n\r\n"] - - test {MSET/MSETNX wrong number of args} { - assert_error {*wrong number of arguments for 'mset' command} {r mset x{t} 10 y{t} "foo bar" z{t}} - assert_error {*wrong number of arguments for 'msetnx' command} {r msetnx x{t} 20 y{t} "foo bar" z{t}} - } - - test {MSET with already existing - same key twice} { - r set x{t} x - list [r mset x{t} xxx x{t} yyy] [r get x{t}] - } {OK yyy} - - test {MSETNX with already existent key} { - list [r msetnx x1{t} xxx y2{t} yyy x{t} 20] [r exists x1{t}] [r exists y2{t}] - } {0 0 0} - - test {MSETNX with not existing keys} { - list [r msetnx x1{t} xxx y2{t} yyy] [r get x1{t}] [r get y2{t}] - } {1 xxx yyy} - - test {MSETNX with not existing keys - same key twice} { - r del x1{t} - list [r msetnx x1{t} xxx x1{t} yyy] [r get x1{t}] - } {1 yyy} - - test {MSETNX with already existing keys - same key twice} { - list [r msetnx x1{t} xxx x1{t} zzz] [r get x1{t}] - } {0 yyy} - - test {MSETEX - all expiration flags} { - # Test each expiration type separately (EX, PX, EXAT, PXAT) - set future_sec [expr [clock seconds] + 10] - set future_ms [expr [clock milliseconds] + 10000] - - # Test EX and PX with separate commands (each applies to all keys in that command) - r msetex 2 ex:key1{t} val1 ex:key2{t} val2 ex 5 - r msetex 2 px:key1{t} val1 px:key2{t} val2 px 5000 - - # Test EXAT and PXAT with separate commands - r msetex 2 exat:key1{t} val3 exat:key2{t} val4 exat $future_sec - r msetex 2 pxat:key1{t} val3 pxat:key2{t} val4 pxat $future_ms - - assert_morethan [r ttl ex:key1{t}] 0 - assert_morethan [r pttl px:key1{t}] 0 - assert_morethan [r ttl exat:key1{t}] 0 - assert_morethan [r pttl pxat:key1{t}] 0 - } - - test {MSETEX - KEEPTTL preserves existing TTL} { - r setex keepttl:key{t} 100 oldval - set old_ttl [r ttl keepttl:key{t}] - r msetex 1 keepttl:key{t} newval keepttl - assert_equal [r get keepttl:key{t}] "newval" - assert_morethan [r ttl keepttl:key{t}] [expr $old_ttl - 5] - } - - test {MSETEX - NX/XX conditions and return values} { - r del nx:new{t} nx:new2{t} xx:existing{t} xx:nonexist{t} - r set xx:existing{t} oldval - - assert_equal [r msetex 2 nx:new{t} val1 nx:new2{t} val2 nx ex 10] 1 - assert_equal [r msetex 1 xx:existing{t} newval nx ex 10] 0 - assert_equal [r msetex 1 xx:nonexist{t} newval xx ex 10] 0 - assert_equal [r msetex 1 xx:existing{t} newval xx ex 10] 1 - assert_equal [r get nx:new{t}] "val1" - assert_equal [r get xx:existing{t}] "newval" - } - - test {MSETEX - flexible argument parsing} { - r del flex:1{t} flex:2{t} - # Test flags before and after KEYS - r msetex 2 flex:1{t} val1 flex:2{t} val2 ex 3 nx - r msetex 2 flex:3{t} val3 flex:4{t} val4 px 3000 xx - - assert_equal [r get flex:1{t}] "val1" - assert_equal [r get flex:2{t}] "val2" - assert_morethan [r ttl flex:1{t}] 0 - assert_equal [r exists flex:3{t}] 0 - assert_equal [r exists flex:4{t}] 0 - } - - test {MSETEX - error cases} { - assert_error {*wrong number of arguments*} {r msetex} - assert_error {*invalid numkeys value*} {r msetex key1 val1 ex 10} - assert_error {*wrong number of key-value pairs*} {r msetex 2 key1{t} val1 key2{t}} - assert_error {*syntax error*} {r msetex 1 key1 val1 invalid_flag} - } - - test {MSETEX - overflow protection in numkeys} { - # Test that large numkeys values don't cause integer overflow - # This tests the fix for potential overflow in kv_count_long * 2 - assert_error {*invalid numkeys value*} {r msetex 2147483648 key1 val1 ex 10} - assert_error {*wrong number of key-value pairs*} {r msetex 2147483647 key1 val1 ex 10} - } - - test {MSETEX - mutually exclusive flags} { - # NX and XX are mutually exclusive - assert_error {*syntax error*} {r msetex 2 key1{t} val1 key2{t} val2 nx xx ex 10} - - # Multiple expiration flags are mutually exclusive - assert_error {*syntax error*} {r msetex 2 key1{t} val1 key2{t} val2 ex 10 px 5000} - assert_error {*syntax error*} {r msetex 2 key1{t} val1 key2{t} val2 exat 1735689600 pxat 1735689600000} - - # KEEPTTL conflicts with expiration flags - assert_error {*syntax error*} {r msetex 2 key1{t} val1 key2{t} val2 keepttl ex 10} - assert_error {*syntax error*} {r msetex 2 key1{t} val1 key2{t} val2 keepttl px 5000} - } - - test "STRLEN against non-existing key" { - assert_equal 0 [r strlen notakey] - } - - test "STRLEN against integer-encoded value" { - r set myinteger -555 - assert_equal 4 [r strlen myinteger] - } - - test "STRLEN against plain string" { - r set mystring "foozzz0123456789 baz" - assert_equal 20 [r strlen mystring] - } - - test "SETBIT against non-existing key" { - r del mykey - assert_equal 0 [r setbit mykey 1 1] - assert_equal [binary format B* 01000000] [r get mykey] - } - - test "SETBIT against string-encoded key" { - # Ascii "@" is integer 64 = 01 00 00 00 - r set mykey "@" - - assert_equal 0 [r setbit mykey 2 1] - assert_equal [binary format B* 01100000] [r get mykey] - assert_equal 1 [r setbit mykey 1 0] - assert_equal [binary format B* 00100000] [r get mykey] - } - - test "SETBIT against integer-encoded key" { - # Ascii "1" is integer 49 = 00 11 00 01 - r set mykey 1 - assert_encoding int mykey - - assert_equal 0 [r setbit mykey 6 1] - assert_equal [binary format B* 00110011] [r get mykey] - assert_equal 1 [r setbit mykey 2 0] - assert_equal [binary format B* 00010011] [r get mykey] - } - - test "SETBIT against key with wrong type" { - r del mykey - r lpush mykey "foo" - assert_error "WRONGTYPE*" {r setbit mykey 0 1} - } - - test "SETBIT with out of range bit offset" { - r del mykey - assert_error "*out of range*" {r setbit mykey [expr 4*1024*1024*1024] 1} - assert_error "*out of range*" {r setbit mykey -1 1} - } - - test "SETBIT with non-bit argument" { - r del mykey - assert_error "*out of range*" {r setbit mykey 0 -1} - assert_error "*out of range*" {r setbit mykey 0 2} - assert_error "*out of range*" {r setbit mykey 0 10} - assert_error "*out of range*" {r setbit mykey 0 20} - } - - test "SETBIT fuzzing" { - set str "" - set len [expr 256*8] - r del mykey - - for {set i 0} {$i < 2000} {incr i} { - set bitnum [randomInt $len] - set bitval [randomInt 2] - set fmt [format "%%-%ds%%d%%-s" $bitnum] - set head [string range $str 0 $bitnum-1] - set tail [string range $str $bitnum+1 end] - set str [string map {" " 0} [format $fmt $head $bitval $tail]] - - r setbit mykey $bitnum $bitval - assert_equal [binary format B* $str] [r get mykey] - } - } - - test "GETBIT against non-existing key" { - r del mykey - assert_equal 0 [r getbit mykey 0] - } - - test "GETBIT against string-encoded key" { - # Single byte with 2nd and 3rd bit set - r set mykey "`" - - # In-range - assert_equal 0 [r getbit mykey 0] - assert_equal 1 [r getbit mykey 1] - assert_equal 1 [r getbit mykey 2] - assert_equal 0 [r getbit mykey 3] - - # Out-range - assert_equal 0 [r getbit mykey 8] - assert_equal 0 [r getbit mykey 100] - assert_equal 0 [r getbit mykey 10000] - } - - test "GETBIT against integer-encoded key" { - r set mykey 1 - assert_encoding int mykey - - # Ascii "1" is integer 49 = 00 11 00 01 - assert_equal 0 [r getbit mykey 0] - assert_equal 0 [r getbit mykey 1] - assert_equal 1 [r getbit mykey 2] - assert_equal 1 [r getbit mykey 3] - - # Out-range - assert_equal 0 [r getbit mykey 8] - assert_equal 0 [r getbit mykey 100] - assert_equal 0 [r getbit mykey 10000] - } - - test "SETRANGE against non-existing key" { - r del mykey - assert_equal 3 [r setrange mykey 0 foo] - assert_equal "foo" [r get mykey] - - r del mykey - assert_equal 0 [r setrange mykey 0 ""] - assert_equal 0 [r exists mykey] - - r del mykey - assert_equal 4 [r setrange mykey 1 foo] - assert_equal "\000foo" [r get mykey] - } - - test "SETRANGE against string-encoded key" { - r set mykey "foo" - assert_equal 3 [r setrange mykey 0 b] - assert_equal "boo" [r get mykey] - - r set mykey "foo" - assert_equal 3 [r setrange mykey 0 ""] - assert_equal "foo" [r get mykey] - - r set mykey "foo" - assert_equal 3 [r setrange mykey 1 b] - assert_equal "fbo" [r get mykey] - - r set mykey "foo" - assert_equal 7 [r setrange mykey 4 bar] - assert_equal "foo\000bar" [r get mykey] - } - - test "SETRANGE against integer-encoded key" { - r set mykey 1234 - assert_encoding int mykey - assert_equal 4 [r setrange mykey 0 2] - assert_encoding raw mykey - assert_equal 2234 [r get mykey] - - # Shouldn't change encoding when nothing is set - r set mykey 1234 - assert_encoding int mykey - assert_equal 4 [r setrange mykey 0 ""] - assert_encoding int mykey - assert_equal 1234 [r get mykey] - - r set mykey 1234 - assert_encoding int mykey - assert_equal 4 [r setrange mykey 1 3] - assert_encoding raw mykey - assert_equal 1334 [r get mykey] - - r set mykey 1234 - assert_encoding int mykey - assert_equal 6 [r setrange mykey 5 2] - assert_encoding raw mykey - assert_equal "1234\0002" [r get mykey] - } - - test "SETRANGE against key with wrong type" { - r del mykey - r lpush mykey "foo" - assert_error "WRONGTYPE*" {r setrange mykey 0 bar} - } - - test "SETRANGE with out of range offset" { - r del mykey - assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world} - - r set mykey "hello" - assert_error "*out of range*" {r setrange mykey -1 world} - assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world} - } - - test "GETRANGE against non-existing key" { - r del mykey - assert_equal "" [r getrange mykey 0 -1] - } - - test "GETRANGE against wrong key type" { - r lpush lkey1 "list" - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r getrange lkey1 0 -1} - } - - test "GETRANGE against string value" { - r set mykey "Hello World" - assert_equal "Hell" [r getrange mykey 0 3] - assert_equal "Hello World" [r getrange mykey 0 -1] - assert_equal "orld" [r getrange mykey -4 -1] - assert_equal "" [r getrange mykey 5 3] - assert_equal " World" [r getrange mykey 5 5000] - assert_equal "Hello World" [r getrange mykey -5000 10000] - assert_equal "H" [r getrange mykey 0 -100] - assert_equal "" [r getrange mykey 1 -100] - assert_equal "" [r getrange mykey -1 -100] - assert_equal "H" [r getrange mykey -100 -99] - assert_equal "H" [r getrange mykey -100 -100] - assert_equal "" [r getrange mykey -100 -101] - } - - test "GETRANGE against integer-encoded value" { - r set mykey 1234 - assert_equal "123" [r getrange mykey 0 2] - assert_equal "1234" [r getrange mykey 0 -1] - assert_equal "234" [r getrange mykey -3 -1] - assert_equal "" [r getrange mykey 5 3] - assert_equal "4" [r getrange mykey 3 5000] - assert_equal "1234" [r getrange mykey -5000 10000] - assert_equal "1" [r getrange mykey 0 -100] - assert_equal "" [r getrange mykey 1 -100] - assert_equal "" [r getrange mykey -1 -100] - assert_equal "1" [r getrange mykey -100 -99] - assert_equal "1" [r getrange mykey -100 -100] - assert_equal "" [r getrange mykey -100 -101] - } - - test "GETRANGE fuzzing" { - for {set i 0} {$i < 1000} {incr i} { - r set bin [set bin [randstring 0 1024 binary]] - set _start [set start [randomInt 1500]] - set _end [set end [randomInt 1500]] - if {$_start < 0} {set _start "end-[abs($_start)-1]"} - if {$_end < 0} {set _end "end-[abs($_end)-1]"} - assert_equal [string range $bin $_start $_end] [r getrange bin $start $end] - } - } - - test "Coverage: SUBSTR" { - r set key abcde - assert_equal "a" [r substr key 0 0] - assert_equal "abcd" [r substr key 0 3] - assert_equal "bcde" [r substr key -4 -1] - assert_equal "" [r substr key -1 -3] - assert_equal "" [r substr key 7 8] - assert_equal "" [r substr nokey 0 1] - } - -if {[string match {*jemalloc*} [s mem_allocator]]} { - test {trim on SET with big value} { - # set a big value to trigger increasing the query buf - r set key [string repeat A 100000] - # set a smaller value but > PROTO_MBULK_BIG_ARG (32*1024) Redis will try to save the query buf itself on the DB. - r set key [string repeat A 33000] - # asset the value was trimmed - assert {[r memory usage key] < 42000}; # 42K to count for Jemalloc's additional memory overhead. - } -} ;# if jemalloc - - test {Extended SET can detect syntax errors} { - set e {} - catch {r set foo bar non-existing-option} e - set e - } {*syntax*} - - test {Extended SET NX option} { - r del foo - set v1 [r set foo 1 nx] - set v2 [r set foo 2 nx] - list $v1 $v2 [r get foo] - } {OK {} 1} - - test {Extended SET XX option} { - r del foo - set v1 [r set foo 1 xx] - r set foo bar - set v2 [r set foo 2 xx] - list $v1 $v2 [r get foo] - } {{} OK 2} - - test {Extended SET GET option} { - r del foo - r set foo bar - set old_value [r set foo bar2 GET] - set new_value [r get foo] - list $old_value $new_value - } {bar bar2} - - test {Extended SET GET option with no previous value} { - r del foo - set old_value [r set foo bar GET] - set new_value [r get foo] - list $old_value $new_value - } {{} bar} - - test {Extended SET GET option with XX} { - r del foo - r set foo bar - set old_value [r set foo baz GET XX] - set new_value [r get foo] - list $old_value $new_value - } {bar baz} - - test {Extended SET GET option with XX and no previous value} { - r del foo - set old_value [r set foo bar GET XX] - set new_value [r get foo] - list $old_value $new_value - } {{} {}} - - test {Extended SET GET option with NX} { - r del foo - set old_value [r set foo bar GET NX] - set new_value [r get foo] - list $old_value $new_value - } {{} bar} - - test {Extended SET GET option with NX and previous value} { - r del foo - r set foo bar - set old_value [r set foo baz GET NX] - set new_value [r get foo] - list $old_value $new_value - } {bar bar} - - test {Extended SET GET with incorrect type should result in wrong type error} { - r del foo - r rpush foo waffle - catch {r set foo bar GET} err1 - assert_equal "waffle" [r rpop foo] - set err1 - } {*WRONGTYPE*} - - test {Extended SET EX option} { - r del foo - r set foo bar ex 10 - set ttl [r ttl foo] - assert {$ttl <= 10 && $ttl > 5} - } - - test {Extended SET PX option} { - r del foo - r set foo bar px 10000 - set ttl [r ttl foo] - assert {$ttl <= 10 && $ttl > 5} - } - - test "Extended SET EXAT option" { - r del foo - r set foo bar exat [expr [clock seconds] + 10] - assert_range [r ttl foo] 5 10 - } - - test "Extended SET PXAT option" { - r del foo - r set foo bar pxat [expr [clock milliseconds] + 10000] - assert_range [r ttl foo] 5 10 - } - test {Extended SET using multiple options at once} { - r set foo val - assert {[r set foo bar xx px 10000] eq {OK}} - set ttl [r ttl foo] - assert {$ttl <= 10 && $ttl > 5} - } - - test {GETRANGE with huge ranges, Github issue #1844} { - r set foo bar - r getrange foo 0 4294967297 - } {bar} - - set rna1 {CACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTCGTCCGGGTGTG} - set rna2 {ATTAAAGGTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT} - set rnalcs {ACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT} - - test {LCS basic} { - r set virus1{t} $rna1 - r set virus2{t} $rna2 - r LCS virus1{t} virus2{t} - } $rnalcs - - test {LCS len} { - r set virus1{t} $rna1 - r set virus2{t} $rna2 - r LCS virus1{t} virus2{t} LEN - } [string length $rnalcs] - - test {LCS indexes} { - dict get [r LCS virus1{t} virus2{t} IDX] matches - } {{{238 238} {239 239}} {{236 236} {238 238}} {{229 230} {236 237}} {{224 224} {235 235}} {{1 222} {13 234}}} - - test {LCS indexes with match len} { - dict get [r LCS virus1{t} virus2{t} IDX WITHMATCHLEN] matches - } {{{238 238} {239 239} 1} {{236 236} {238 238} 1} {{229 230} {236 237} 2} {{224 224} {235 235} 1} {{1 222} {13 234} 222}} - - test {LCS indexes with match len and minimum match len} { - dict get [r LCS virus1{t} virus2{t} IDX WITHMATCHLEN MINMATCHLEN 5] matches - } {{{1 222} {13 234} 222}} - - test {SETRANGE with huge offset} { - foreach value {9223372036854775807 2147483647} { - catch {[r setrange K $value A]} res - # expecting a different error on 32 and 64 bit systems - if {![string match "*string exceeds maximum allowed size*" $res] && ![string match "*out of range*" $res]} { - assert_equal $res "expecting an error" - } - } - } - - test {APPEND modifies the encoding from int to raw} { - r del foo - r set foo 1 - assert_encoding "int" foo - r append foo 2 - - set res {} - lappend res [r get foo] - assert_encoding "raw" foo - - r set bar 12 - assert_encoding "int" bar - lappend res [r get bar] - } {12 12} - - # coverage for kvobjComputeSize - test {MEMORY USAGE - STRINGS} { - set sizes {1 5 8 15 16 17 31 32 33 63 64 65 127 128 129 255 256 257} - set hdrsize [expr {[s arch_bits] == 32 ? 12 : 16}] - - foreach ksize $sizes { - set key [string repeat "k" $ksize] - # OBJ_ENCODING_EMBSTR, OBJ_ENCODING_RAW - foreach vsize $sizes { - set value [string repeat "v" $vsize] - r set $key $value - set memory_used [r memory usage $key] - set min [expr $hdrsize + $ksize + $vsize] - assert_lessthan_equal $min $memory_used - set max [expr {32 > $min ? 64 : [expr $min * 2]}] - assert_morethan_equal $max $memory_used - } - - # OBJ_ENCODING_INT - foreach value {1 100 10000 10000000} { - r set $key $value - set min [expr $hdrsize + $ksize] - assert_lessthan_equal $min [r memory usage $key] - } - } - } - - if {[string match {*jemalloc*} [s mem_allocator]]} { - test {Check MEMORY USAGE for embedded key strings with jemalloc} { - - proc expected_mem {key val with_expire exp_mem_usage exp_debug_sdslen} { - r del $key - r set $key $val - if {$with_expire} { r expire $key 5678315 } - assert_equal $exp_mem_usage [r memory usage $key] - assert_equal $exp_debug_sdslen [r debug sdslen $key] - } - - if {[s arch_bits] == 64} { - # 16 (kvobj) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 5 (val) + 1 (\0) = 32bytes - expected_mem x234 y2345 0 32 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 32, val_sds_len:5, val_sds_avail:0, val_zmalloc: 0" - # 16 (kvobj) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 6 (val) + 1 (\0) = 33bytes - expected_mem x234 y23456 0 40 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 40, val_sds_len:6, val_sds_avail:7, val_zmalloc: 0" - # 16 (kvobj) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 13 (val) + 1 (\0) = 40bytes - expected_mem x234 y234561234567 0 40 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 40, val_sds_len:13, val_sds_avail:0, val_zmalloc: 0" - # 16 (kvobj) + 8 (expiry) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 13 (val) + 1 (\0) = 48bytes - expected_mem x234 y234561234567 1 48 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 48, val_sds_len:13, val_sds_avail:0, val_zmalloc: 0" - } else { - # 12 (kvobj) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 9 (val) + 1 (\0) = 32bytes - expected_mem x234 y23456789 0 32 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 32, val_sds_len:9, val_sds_avail:0, val_zmalloc: 0" - # 12 (kvobj) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 10 (val) + 1 (\0) = 33bytes - expected_mem x234 y234567890 0 40 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 40, val_sds_len:10, val_sds_avail:7, val_zmalloc: 0" - # 12 (kvobj) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 17 (val) + 1 (\0) = 40bytes - expected_mem x234 y2345678901234567 0 40 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 40, val_sds_len:17, val_sds_avail:0, val_zmalloc: 0" - # 12 (kvobj) + 8 (expiry) + 1 (key-hdr-size) + 1 (sdshdr5) + 4 (key) + 1 (\0) + 3 (sdshdr8) + 17 (val) + 1 (\0) = 48bytes - expected_mem x234 y2345678901234567 1 48 "key_sds_len:4, key_sds_avail:0, key_zmalloc: 48, val_sds_len:17, val_sds_avail:0, val_zmalloc: 0" - } - } {} {needs:debug} - } - - test {DIGEST basic usage with plain string} { - r set mykey "hello world" - set digest [r digest mykey] - # Ensure reply is hex string - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST with empty string} { - r set mykey "" - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST with integer-encoded value} { - r set mykey 12345 - assert_encoding int mykey - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST with negative integer} { - r set mykey -999 - assert_encoding int mykey - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST returns consistent hash for same value} { - r set mykey "test string" - set digest1 [r digest mykey] - set digest2 [r digest mykey] - assert_equal $digest1 $digest2 - } - - test {DIGEST returns same hash for same content in different keys} { - r set key1 "identical" - r set key2 "identical" - set digest1 [r digest key1] - set digest2 [r digest key2] - assert_equal $digest1 $digest2 - } - - test {DIGEST returns different hash for different values} { - r set key1 "value1" - r set key2 "value2" - set digest1 [r digest key1] - set digest2 [r digest key2] - assert {$digest1 != $digest2} - } - - test {DIGEST with binary data} { - r set mykey "\x00\x01\x02\x03\xff\xfe" - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST with unicode characters} { - r set mykey "Hello 世界" - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST with very long string} { - set longstring [string repeat "Lorem ipsum dolor sit amet. " 1000] - r set mykey $longstring - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST against non-existing key} { - r del nonexistent - assert_equal {} [r digest nonexistent] - } - - test {DIGEST against wrong type (list)} { - r del mylist - r lpush mylist "element" - assert_error "*WRONGTYPE*" {r digest mylist} - } - - test {DIGEST against wrong type (hash)} { - r del myhash - r hset myhash field value - assert_error "*WRONGTYPE*" {r digest myhash} - } - - test {DIGEST against wrong type (set)} { - r del myset - r sadd myset member - assert_error "*WRONGTYPE*" {r digest myset} - } - - test {DIGEST against wrong type (zset)} { - r del myzset - r zadd myzset 1 member - assert_error "*WRONGTYPE*" {r digest myzset} - } - - test {DIGEST wrong number of arguments} { - assert_error "*wrong number of arguments*" {r digest} - assert_error "*wrong number of arguments*" {r digest key1 key2} - } - - test {DIGEST with special characters and whitespace} { - r set mykey " spaces \t\n\r" - set digest [r digest mykey] - assert {[string is wideinteger -strict "0x$digest"]} - } - - test {DIGEST consistency across SET operations} { - r set mykey "original" - set digest1 [r digest mykey] - - r set mykey "changed" - set digest2 [r digest mykey] - assert {$digest1 != $digest2} - - r set mykey "original" - set digest3 [r digest mykey] - assert_equal $digest1 $digest3 - } - - test {DELEX basic usage without conditions} { - r set mykey "hello" - assert_equal 1 [r delex mykey] - - r hset myhash f v - assert_equal 1 [r delex myhash] - - r zadd mystr 1 m - assert_equal 1 [r delex mystr] - } - - test {DELEX basic usage with IFEQ} { - r set mykey "hello" - assert_equal 1 [r delex mykey IFEQ "hello"] - assert_equal 0 [r exists mykey] - - r set mykey "hello" - assert_equal 0 [r delex mykey IFEQ "world"] - assert_equal 1 [r exists mykey] - assert_equal "hello" [r get mykey] - } - - test {DELEX basic usage with IFNE} { - r set mykey "hello" - assert_equal 1 [r delex mykey IFNE "world"] - assert_equal 0 [r exists mykey] - - r set mykey "hello" - assert_equal 0 [r delex mykey IFNE "hello"] - assert_equal 1 [r exists mykey] - assert_equal "hello" [r get mykey] - } - - test {DELEX basic usage with IFDEQ} { - r set mykey "hello" - set digest [r digest mykey] - assert_equal 1 [r delex mykey IFDEQ $digest] - assert_equal 0 [r exists mykey] - - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal 0 [r delex mykey IFDEQ $wrong_digest] - assert_equal 1 [r exists mykey] - assert_equal "hello" [r get mykey] - } - - test {DELEX basic usage with IFDNE} { - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal 1 [r delex mykey IFDNE $wrong_digest] - assert_equal 0 [r exists mykey] - - r set mykey "hello" - set digest [r digest mykey] - assert_equal 0 [r delex mykey IFDNE $digest] - assert_equal 1 [r exists mykey] - assert_equal "hello" [r get mykey] - } - - test {DELEX with non-existing key} { - r del nonexistent - assert_equal 0 [r delex nonexistent IFEQ "hello"] - assert_equal 0 [r delex nonexistent IFNE "hello"] - assert_equal 0 [r delex nonexistent IFDEQ 1234567890] - assert_equal 0 [r delex nonexistent IFDNE 1234567890] - } - - test {DELEX with empty string value} { - r set mykey "" - assert_equal 1 [r delex mykey IFEQ ""] - assert_equal 0 [r exists mykey] - - r set mykey "" - assert_equal 0 [r delex mykey IFEQ "notempty"] - assert_equal 1 [r exists mykey] - } - - test {DELEX with integer-encoded value} { - r set mykey 12345 - assert_encoding int mykey - assert_equal 1 [r delex mykey IFEQ "12345"] - assert_equal 0 [r exists mykey] - - r set mykey 12345 - assert_encoding int mykey - assert_equal 0 [r delex mykey IFEQ "54321"] - assert_equal 1 [r exists mykey] - } - - test {DELEX with negative integer} { - r set mykey -999 - assert_encoding int mykey - assert_equal 1 [r delex mykey IFEQ "-999"] - assert_equal 0 [r exists mykey] - } - - test {DELEX with binary data} { - r set mykey "\x00\x01\x02\x03\xff\xfe" - assert_equal 1 [r delex mykey IFEQ "\x00\x01\x02\x03\xff\xfe"] - assert_equal 0 [r exists mykey] - - r set mykey "\x00\x01\x02\x03\xff\xfe" - assert_equal 0 [r delex mykey IFEQ "\x00\x01\x02\x03\xff\xff"] - assert_equal 1 [r exists mykey] - } - - test {DELEX with unicode characters} { - r set mykey "Hello 世界" - assert_equal 1 [r delex mykey IFEQ "Hello 世界"] - assert_equal 0 [r exists mykey] - - r set mykey "Hello 世界" - assert_equal 0 [r delex mykey IFEQ "Hello World"] - assert_equal 1 [r exists mykey] - } - - test {DELEX with very long string} { - set longstring [string repeat "Lorem ipsum dolor sit amet. " 1000] - r set mykey $longstring - assert_equal 1 [r delex mykey IFEQ $longstring] - assert_equal 0 [r exists mykey] - } - - test {DELEX against wrong type} { - r del mylist - r lpush mylist "element" - assert_error "*ERR*" {r delex mylist IFEQ "element"} - - r del myhash - r hset myhash field value - assert_error "*ERR*" {r delex myhash IFEQ "value"} - - r del myset - r sadd myset member - assert_error "*ERR*" {r delex myset IFEQ "member"} - - r del myzset - r zadd myzset 1 member - assert_error "*ERR*" {r delex myzset IFEQ "member"} - } - - test {DELEX wrong number of arguments} { - r del key1 - assert_error "*wrong number of arguments*" {r delex key1 IFEQ} - - r set key1 x - assert_error "*wrong number of arguments*" {r delex key1 IFEQ} - assert_error "*wrong number of arguments*" {r delex key1 IFEQ value1 extra} - } - - test {DELEX invalid condition} { - r set mykey "hello" - assert_error "*Invalid condition*" {r delex mykey INVALID "hello"} - assert_error "*Invalid condition*" {r delex mykey IF "hello"} - assert_error "*Invalid condition*" {r delex mykey EQ "hello"} - } - - test {DELEX with special characters and whitespace} { - r set mykey " spaces \t\n\r" - assert_equal 1 [r delex mykey IFEQ " spaces \t\n\r"] - assert_equal 0 [r exists mykey] - } - - test {DELEX digest consistency with same content} { - r set key1 "identical" - r set key2 "identical" - set digest1 [r digest key1] - set digest2 [r digest key2] - assert_equal $digest1 $digest2 - - # Both should be deletable with the same digest - assert_equal 1 [r delex key1 IFDEQ $digest2] - assert_equal 1 [r delex key2 IFDEQ $digest1] - } - - test {DELEX digest with different content} { - r set key1 "value1" - r set key2 "value2" - set digest1 [r digest key1] - set digest2 [r digest key2] - assert {$digest1 != $digest2} - - # Should not be able to delete with wrong digest - assert_equal 0 [r delex key1 IFDEQ $digest2] - assert_equal 0 [r delex key2 IFDEQ $digest1] - - # Should be able to delete with correct digest - assert_equal 1 [r delex key1 IFDEQ $digest1] - assert_equal 1 [r delex key2 IFDEQ $digest2] - } - - test {DELEX propagate as DEL command to replica} { - r flushall - set repl [attach_to_replication_stream] - r set foo bar - r delex foo IFEQ bar - assert_replication_stream $repl { - {select *} - {set foo bar} - {del foo} - } - close_replication_stream $repl - } {} {needs:repl} - - test {DELEX does not propagate when condition not met} { - r flushall - set repl [attach_to_replication_stream] - r set foo bar - r delex foo IFEQ baz - r set foo bar2 - assert_replication_stream $repl { - {select *} - {set foo bar} - {set foo bar2} - } - close_replication_stream $repl - } {} {needs:repl} - - test {DELEX with integer that looks like string} { - # Set as integer - r set key1 123 - assert_encoding int key1 - assert_equal 1 [r delex key1 IFEQ "123"] - assert_equal 0 [r exists key1] - - # Set as string - r set key2 "123" - assert_equal 1 [r delex key2 IFEQ "123"] - assert_equal 0 [r exists key2] - } - - test {Extended SET with IFEQ - key exists and matches} { - r set mykey "hello" - assert_equal "OK" [r set mykey "world" IFEQ "hello"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFEQ - key exists but doesn't match} { - r set mykey "hello" - assert_equal {} [r set mykey "world" IFEQ "different"] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFEQ - key doesn't exist} { - r del mykey - assert_equal {} [r set mykey "world" IFEQ "hello"] - assert_equal 0 [r exists mykey] - } - - test {Extended SET with IFNE - key exists and doesn't match} { - r set mykey "hello" - assert_equal "OK" [r set mykey "world" IFNE "different"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFNE - key exists and matches} { - r set mykey "hello" - assert_equal {} [r set mykey "world" IFNE "hello"] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFNE - key doesn't exist} { - r del mykey - assert_equal "OK" [r set mykey "world" IFNE "hello"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFDEQ - key exists and digest matches} { - r set mykey "hello" - set digest [r digest mykey] - puts $digest - assert_equal "OK" [r set mykey "world" IFDEQ $digest] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFDEQ - key exists but digest doesn't match} { - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal {} [r set mykey "world" IFDEQ $wrong_digest] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFDEQ - key doesn't exist} { - r del mykey - set digest 1234567890 - assert_equal {} [r set mykey "world" IFDEQ $digest] - assert_equal 0 [r exists mykey] - } - - test {Extended SET with IFDNE - key exists and digest doesn't match} { - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal "OK" [r set mykey "world" IFDNE $wrong_digest] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFDNE - key exists and digest matches} { - r set mykey "hello" - set digest [r digest mykey] - assert_equal {} [r set mykey "world" IFDNE $digest] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFDNE - key doesn't exist} { - r del mykey - set digest 1234567890 - assert_equal "OK" [r set mykey "world" IFDNE $digest] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFEQ and GET - key exists and matches} { - r set mykey "hello" - assert_equal "hello" [r set mykey "world" IFEQ "hello" GET] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFEQ and GET - key exists but doesn't match} { - r set mykey "hello" - assert_equal "hello" [r set mykey "world" IFEQ "different" GET] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFEQ and GET - key doesn't exist} { - r del mykey - assert_equal {} [r set mykey "world" IFEQ "hello" GET] - assert_equal 0 [r exists mykey] - } - - test {Extended SET with IFNE and GET - key exists and doesn't match} { - r set mykey "hello" - assert_equal "hello" [r set mykey "world" IFNE "different" GET] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFNE and GET - key exists and matches} { - r set mykey "hello" - assert_equal "hello" [r set mykey "world" IFNE "hello" GET] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFNE and GET - key doesn't exist} { - r del mykey - assert_equal {} [r set mykey "world" IFNE "hello" GET] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFDEQ and GET - key exists and digest matches} { - r set mykey "hello" - set digest [r digest mykey] - assert_equal "hello" [r set mykey "world" IFDEQ $digest GET] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFDEQ and GET - key exists but digest doesn't match} { - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal "hello" [r set mykey "world" IFDEQ $wrong_digest GET] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFDEQ and GET - key doesn't exist} { - r del mykey - set digest 1234567890 - assert_equal {} [r set mykey "world" IFDEQ $digest GET] - assert_equal 0 [r exists mykey] - } - - test {Extended SET with IFDNE and GET - key exists and digest doesn't match} { - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal "hello" [r set mykey "world" IFDNE $wrong_digest GET] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFDNE and GET - key exists and digest matches} { - r set mykey "hello" - set digest [r digest mykey] - assert_equal "hello" [r set mykey "world" IFDNE $digest GET] - assert_equal "hello" [r get mykey] - } - - test {Extended SET with IFDNE and GET - key doesn't exist} { - r del mykey - set digest 1234567890 - assert_equal {} [r set mykey "world" IFDNE $digest GET] - assert_equal "world" [r get mykey] - } - - test {Extended SET with IFEQ and expiration} { - r set mykey "hello" - assert_equal "OK" [r set mykey "world" IFEQ "hello" EX 10] - assert_equal "world" [r get mykey] - assert_range [r ttl mykey] 5 10 - } - - test {Extended SET with IFNE and expiration} { - r set mykey "hello" - assert_equal "OK" [r set mykey "world" IFNE "different" EX 10] - assert_equal "world" [r get mykey] - assert_range [r ttl mykey] 5 10 - } - - test {Extended SET with IFDEQ and expiration} { - r set mykey "hello" - set digest [r digest mykey] - assert_equal "OK" [r set mykey "world" IFDEQ $digest EX 10] - assert_equal "world" [r get mykey] - assert_range [r ttl mykey] 5 10 - } - - test {Extended SET with IFDNE and expiration} { - r set mykey "hello" - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal "OK" [r set mykey "world" IFDNE $wrong_digest EX 10] - assert_equal "world" [r get mykey] - assert_range [r ttl mykey] 5 10 - } - - test {Extended SET with IFEQ against wrong type} { - r del mylist - r lpush mylist "element" - assert_error "*WRONGTYPE*" {r set mylist "value" IFEQ "element"} - } - - test {Extended SET with IFNE against wrong type} { - r del myhash - r hset myhash field value - assert_error "*WRONGTYPE*" {r set myhash "value" IFNE "value"} - } - - test {Extended SET with IFDEQ against wrong type} { - r del myset - r sadd myset member - assert_error "*WRONGTYPE*" {r set myset "value" IFDEQ 1234567890} - } - - test {Extended SET with IFDNE against wrong type} { - r del myzset - r zadd myzset 1 member - assert_error "*WRONGTYPE*" {r set myzset "value" IFDNE 1234567890} - } - - test {Extended SET with integer-encoded value and IFEQ} { - r set mykey 12345 - assert_encoding int mykey - assert_equal "OK" [r set mykey "world" IFEQ "12345"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with integer-encoded value and IFNE} { - r set mykey 12345 - assert_encoding int mykey - assert_equal "OK" [r set mykey "world" IFNE "54321"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with binary data and IFEQ} { - r set mykey "\x00\x01\x02\x03\xff\xfe" - assert_equal "OK" [r set mykey "world" IFEQ "\x00\x01\x02\x03\xff\xfe"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with unicode characters and IFEQ} { - r set mykey "Hello 世界" - assert_equal "OK" [r set mykey "world" IFEQ "Hello 世界"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with empty string and IFEQ} { - r set mykey "" - assert_equal "OK" [r set mykey "world" IFEQ ""] - assert_equal "world" [r get mykey] - } - - test {Extended SET with empty string and IFNE} { - r set mykey "" - assert_equal {} [r set mykey "world" IFNE ""] - assert_equal "" [r get mykey] - } - - test {Extended SET case insensitive conditions} { - r set mykey "hello" - assert_equal "OK" [r set mykey "world" ifeq "hello"] - assert_equal "world" [r get mykey] - - r set mykey "hello" - assert_equal "OK" [r set mykey "world" IfEq "hello"] - assert_equal "world" [r get mykey] - - r set mykey "hello" - assert_equal "OK" [r set mykey "world" IFEQ "hello"] - assert_equal "world" [r get mykey] - } - - test {Extended SET with special characters and IFEQ} { - r set mykey " spaces \t\n\r" - assert_equal "OK" [r set mykey "world" IFEQ " spaces \t\n\r"] - assert_equal "world" [r get mykey] - } - - test {Extended SET digest consistency with same content} { - r set key1 "identical" - r set key2 "identical" - set digest1 [r digest key1] - set digest2 [r digest key2] - assert_equal $digest1 $digest2 - - # Both should be settable with the same digest - assert_equal "OK" [r set key1 "new1" IFDEQ $digest1] - assert_equal "OK" [r set key2 "new2" IFDEQ $digest2] - assert_equal "new1" [r get key1] - assert_equal "new2" [r get key2] - } - - test {Extended SET digest with different content} { - r set key1 "value1" - r set key2 "value2" - set digest1 [r digest key1] - set digest2 [r digest key2] - assert {$digest1 != $digest2} - - # Should not be able to set with wrong digest - assert_equal {} [r set key1 "new1" IFDEQ $digest2] - assert_equal {} [r set key2 "new2" IFDEQ $digest1] - assert_equal "value1" [r get key1] - assert_equal "value2" [r get key2] - - # Should be able to set with correct digest - assert_equal "OK" [r set key1 "new1" IFDEQ $digest1] - assert_equal "OK" [r set key2 "new2" IFDEQ $digest2] - assert_equal "new1" [r get key1] - assert_equal "new2" [r get key2] - } - - test {Extended SET with very long string and IFEQ} { - set longstring [string repeat "Lorem ipsum dolor sit amet. " 1000] - r set mykey $longstring - assert_equal "OK" [r set mykey "world" IFEQ $longstring] - assert_equal "world" [r get mykey] - } - - test {Extended SET with negative digest} { - r set mykey "test" - set digest [r digest mykey] - set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]] - assert_equal "OK" [r set mykey "world" IFDNE $wrong_digest] - assert_equal "world" [r get mykey] - } - - test {DIGEST always returns exactly 16 hex characters with leading zeros} { - # Test with a value that produces a digest with leading zeros - r set foo "v8lf0c11xh8ymlqztfd3eeq16kfn4sspw7fqmnuuq3k3t75em5wdizgcdw7uc26nnf961u2jkfzkjytls2kwlj7626sd" - # Verify it matches the expected value with leading zeros - assert_equal "00006c38adf31777" [r digest foo] - } - - test {IFDEQ/IFDNE reject digest with incorrect format} { - r set mykey "test" - set digest [r digest mykey] - - # Test with too short digest (15 chars) - set short_digest [string range $digest 1 end] - assert_error "*must be exactly 16 hexadecimal characters*" {r set mykey "new" IFDEQ $short_digest} - assert_error "*must be exactly 16 hexadecimal characters*" {r set mykey "new" IFDNE $short_digest} - assert_error "*must be exactly 16 hexadecimal characters*" {r delex mykey IFDEQ $short_digest} - assert_error "*must be exactly 16 hexadecimal characters*" {r delex mykey IFDNE $short_digest} - - # Test with too long digest (17 chars) - set long_digest "0${digest}" - assert_error "*must be exactly 16 hexadecimal characters*" {r set mykey "new" IFDEQ $long_digest} - assert_error "*must be exactly 16 hexadecimal characters*" {r set mykey "new" IFDNE $long_digest} - assert_error "*must be exactly 16 hexadecimal characters*" {r delex mykey IFDEQ $long_digest} - assert_error "*must be exactly 16 hexadecimal characters*" {r delex mykey IFDNE $long_digest} - - # Test with empty digest - assert_error "*must be exactly 16 hexadecimal characters*" {r set mykey "new" IFDEQ ""} - assert_error "*must be exactly 16 hexadecimal characters*" {r set mykey "new" IFDNE ""} - assert_error "*must be exactly 16 hexadecimal characters*" {r delex mykey IFDEQ ""} - assert_error "*must be exactly 16 hexadecimal characters*" {r delex mykey IFDNE ""} - } - - test {IFDEQ/IFDNE accepts uppercase hex digits (case-insensitive)} { - # Test SET IFDEQ with uppercase - r set mykey "hello" - set digest [r digest mykey] - set upper_digest [string toupper $digest] - assert_equal "OK" [r set mykey "world" IFDEQ $upper_digest] - assert_equal "world" [r get mykey] - - # Test SET IFDEQ with uppercase - r set mykey "hello" - set digest [r digest mykey] - set upper_digest [string toupper $digest] - assert_equal "" [r set mykey "world" IFDNE $upper_digest] - assert_equal "hello" [r get mykey] - - # Test DELEX IFDEQ with uppercase - r set mykey "hello" - set upper_digest [string toupper [r digest mykey]] - assert_equal 1 [r delex mykey IFDEQ $upper_digest] - assert_equal 0 [r exists mykey] - - # Test DELEX IFDNE with uppercase - r set mykey "hello" - set upper_digest [string toupper [r digest mykey]] - assert_equal 0 [r delex mykey IFDNE $upper_digest] - assert_equal 1 [r exists mykey] - } -} diff --git a/examples/redis-unstable/tests/unit/type/zset.tcl b/examples/redis-unstable/tests/unit/type/zset.tcl deleted file mode 100644 index 9f3e1f3..0000000 --- a/examples/redis-unstable/tests/unit/type/zset.tcl +++ /dev/null @@ -1,2761 +0,0 @@ -start_server {tags {"zset"}} { - proc create_zset {key items} { - r del $key - foreach {score entry} $items { - r zadd $key $score $entry - } - } - - # A helper function to verify either ZPOP* or ZMPOP* response. - proc verify_pop_response {pop res zpop_expected_response zmpop_expected_response} { - if {[string match "*ZM*" $pop]} { - assert_equal $res $zmpop_expected_response - } else { - assert_equal $res $zpop_expected_response - } - } - - # A helper function to verify either ZPOP* or ZMPOP* response when given one input key. - proc verify_zpop_response {rd pop key count zpop_expected_response zmpop_expected_response} { - if {[string match "ZM*" $pop]} { - lassign [split $pop "_"] pop where - - if {$count == 0} { - set res [$rd $pop 1 $key $where] - } else { - set res [$rd $pop 1 $key $where COUNT $count] - } - } else { - if {$count == 0} { - set res [$rd $pop $key] - } else { - set res [$rd $pop $key $count] - } - } - verify_pop_response $pop $res $zpop_expected_response $zmpop_expected_response - } - - # A helper function to verify either BZPOP* or BZMPOP* response when given one input key. - proc verify_bzpop_response {rd pop key timeout count bzpop_expected_response bzmpop_expected_response} { - if {[string match "BZM*" $pop]} { - lassign [split $pop "_"] pop where - - if {$count == 0} { - $rd $pop $timeout 1 $key $where - } else { - $rd $pop $timeout 1 $key $where COUNT $count - } - } else { - $rd $pop $key $timeout - } - verify_pop_response $pop [$rd read] $bzpop_expected_response $bzmpop_expected_response - } - - # A helper function to verify either ZPOP* or ZMPOP* response when given two input keys. - proc verify_bzpop_two_key_response {rd pop key key2 timeout count bzpop_expected_response bzmpop_expected_response} { - if {[string match "BZM*" $pop]} { - lassign [split $pop "_"] pop where - - if {$count == 0} { - $rd $pop $timeout 2 $key $key2 $where - } else { - $rd $pop $timeout 2 $key $key2 $where COUNT $count - } - } else { - $rd $pop $key $key2 $timeout - } - verify_pop_response $pop [$rd read] $bzpop_expected_response $bzmpop_expected_response - } - - # A helper function to execute either BZPOP* or BZMPOP* with one input key. - proc bzpop_command {rd pop key timeout} { - if {[string match "BZM*" $pop]} { - lassign [split $pop "_"] pop where - $rd $pop $timeout 1 $key $where COUNT 1 - } else { - $rd $pop $key $timeout - } - } - - # A helper function to verify nil response in readraw base on RESP version. - proc verify_nil_response {resp nil_response} { - if {$resp == 2} { - assert_equal $nil_response {*-1} - } elseif {$resp == 3} { - assert_equal $nil_response {_} - } - } - - # A helper function to verify zset score response in readraw base on RESP version. - proc verify_score_response {rd resp score} { - if {$resp == 2} { - assert_equal [$rd read] {$1} - assert_equal [$rd read] $score - } elseif {$resp == 3} { - assert_equal [$rd read] ",$score" - } - } - - proc basics {encoding} { - set original_max_entries [lindex [r config get zset-max-ziplist-entries] 1] - set original_max_value [lindex [r config get zset-max-ziplist-value] 1] - if {$encoding == "listpack"} { - r config set zset-max-ziplist-entries 128 - r config set zset-max-ziplist-value 64 - } elseif {$encoding == "skiplist"} { - r config set zset-max-ziplist-entries 0 - r config set zset-max-ziplist-value 0 - } else { - puts "Unknown sorted set encoding" - exit - } - - test "Check encoding - $encoding" { - r del ztmp - r zadd ztmp 10 x - assert_encoding $encoding ztmp - } - - test "ZSET basic ZADD and score update - $encoding" { - r del ztmp - r zadd ztmp 10 x - r zadd ztmp 20 y - r zadd ztmp 30 z - assert_equal {x y z} [r zrange ztmp 0 -1] - - r zadd ztmp 1 y - assert_equal {y x z} [r zrange ztmp 0 -1] - } - - test "ZSET element can't be set to NaN with ZADD - $encoding" { - assert_error "*not*float*" {r zadd myzset nan abc} - } - - test "ZSET element can't be set to NaN with ZINCRBY - $encoding" { - assert_error "*not*float*" {r zincrby myzset nan abc} - } - - test "ZADD with options syntax error with incomplete pair - $encoding" { - r del ztmp - catch {r zadd ztmp xx 10 x 20} err - set err - } {ERR*} - - test "ZADD XX option without key - $encoding" { - r del ztmp - assert {[r zadd ztmp xx 10 x] == 0} - assert {[r type ztmp] eq {none}} - } - - test "ZADD XX existing key - $encoding" { - r del ztmp - r zadd ztmp 10 x - assert {[r zadd ztmp xx 20 y] == 0} - assert {[r zcard ztmp] == 1} - } - - test "ZADD XX returns the number of elements actually added - $encoding" { - r del ztmp - r zadd ztmp 10 x - set retval [r zadd ztmp 10 x 20 y 30 z] - assert {$retval == 2} - } - - test "ZADD XX updates existing elements score - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - r zadd ztmp xx 5 foo 11 x 21 y 40 zap - assert {[r zcard ztmp] == 3} - assert {[r zscore ztmp x] == 11} - assert {[r zscore ztmp y] == 21} - } - - test "ZADD GT updates existing elements when new scores are greater - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - assert {[r zadd ztmp gt ch 5 foo 11 x 21 y 29 z] == 3} - assert {[r zcard ztmp] == 4} - assert {[r zscore ztmp x] == 11} - assert {[r zscore ztmp y] == 21} - assert {[r zscore ztmp z] == 30} - } - - test "ZADD LT updates existing elements when new scores are lower - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - assert {[r zadd ztmp lt ch 5 foo 11 x 21 y 29 z] == 2} - assert {[r zcard ztmp] == 4} - assert {[r zscore ztmp x] == 10} - assert {[r zscore ztmp y] == 20} - assert {[r zscore ztmp z] == 29} - } - - test "ZADD GT XX updates existing elements when new scores are greater and skips new elements - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - assert {[r zadd ztmp gt xx ch 5 foo 11 x 21 y 29 z] == 2} - assert {[r zcard ztmp] == 3} - assert {[r zscore ztmp x] == 11} - assert {[r zscore ztmp y] == 21} - assert {[r zscore ztmp z] == 30} - } - - test "ZADD LT XX updates existing elements when new scores are lower and skips new elements - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - assert {[r zadd ztmp lt xx ch 5 foo 11 x 21 y 29 z] == 1} - assert {[r zcard ztmp] == 3} - assert {[r zscore ztmp x] == 10} - assert {[r zscore ztmp y] == 20} - assert {[r zscore ztmp z] == 29} - } - - test "ZADD XX and NX are not compatible - $encoding" { - r del ztmp - catch {r zadd ztmp xx nx 10 x} err - set err - } {ERR*} - - test "ZADD NX with non existing key - $encoding" { - r del ztmp - r zadd ztmp nx 10 x 20 y 30 z - assert {[r zcard ztmp] == 3} - } - - test "ZADD NX only add new elements without updating old ones - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - assert {[r zadd ztmp nx 11 x 21 y 100 a 200 b] == 2} - assert {[r zscore ztmp x] == 10} - assert {[r zscore ztmp y] == 20} - assert {[r zscore ztmp a] == 100} - assert {[r zscore ztmp b] == 200} - } - - test "ZADD GT and NX are not compatible - $encoding" { - r del ztmp - catch {r zadd ztmp gt nx 10 x} err - set err - } {ERR*} - - test "ZADD LT and NX are not compatible - $encoding" { - r del ztmp - catch {r zadd ztmp lt nx 10 x} err - set err - } {ERR*} - - test "ZADD LT and GT are not compatible - $encoding" { - r del ztmp - catch {r zadd ztmp lt gt 10 x} err - set err - } {ERR*} - - test "ZADD INCR LT/GT replies with nill if score not updated - $encoding" { - r del ztmp - r zadd ztmp 28 x - assert {[r zadd ztmp lt incr 1 x] eq {}} - assert {[r zscore ztmp x] == 28} - assert {[r zadd ztmp gt incr -1 x] eq {}} - assert {[r zscore ztmp x] == 28} - } - - test "ZADD INCR LT/GT with inf - $encoding" { - r del ztmp - r zadd ztmp +inf x -inf y - - assert {[r zadd ztmp lt incr 1 x] eq {}} - assert {[r zscore ztmp x] == inf} - assert {[r zadd ztmp gt incr -1 x] eq {}} - assert {[r zscore ztmp x] == inf} - assert {[r zadd ztmp lt incr -1 x] eq {}} - assert {[r zscore ztmp x] == inf} - assert {[r zadd ztmp gt incr 1 x] eq {}} - assert {[r zscore ztmp x] == inf} - - assert {[r zadd ztmp lt incr 1 y] eq {}} - assert {[r zscore ztmp y] == -inf} - assert {[r zadd ztmp gt incr -1 y] eq {}} - assert {[r zscore ztmp y] == -inf} - assert {[r zadd ztmp lt incr -1 y] eq {}} - assert {[r zscore ztmp y] == -inf} - assert {[r zadd ztmp gt incr 1 y] eq {}} - assert {[r zscore ztmp y] == -inf} - } - - test "ZADD INCR works like ZINCRBY - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - r zadd ztmp INCR 15 x - assert {[r zscore ztmp x] == 25} - } - - test "ZADD INCR works with a single score-elemenet pair - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - catch {r zadd ztmp INCR 15 x 10 y} err - set err - } {ERR*} - - test "ZADD CH option changes return value to all changed elements - $encoding" { - r del ztmp - r zadd ztmp 10 x 20 y 30 z - assert {[r zadd ztmp 11 x 21 y 30 z] == 0} - assert {[r zadd ztmp ch 12 x 22 y 30 z] == 2} - } - - test "ZINCRBY calls leading to NaN result in error - $encoding" { - r zincrby myzset +inf abc - assert_error "*NaN*" {r zincrby myzset -inf abc} - } - - test "ZINCRBY accepts hexadecimal inputs - $encoding" { - r del zhexa - - # Add some hexadecimal values to the sorted set 'zhexa' - r zadd zhexa 0x0p+0 "zero" - r zadd zhexa 0x1p+0 "one" - - # Increment them - # 0 + 0 = 0 - r zincrby zhexa 0x0p+0 "zero" - # 1 + 1 = 2 - r zincrby zhexa 0x1p+0 "one" - - assert_equal 0 [r zscore zhexa "zero"] - assert_equal 2 [r zscore zhexa "one"] - } - - test "ZINCRBY against invalid incr value - $encoding" { - r del zincr - r zadd zincr 1 "one" - assert_error "*value is not a valid*" {r zincrby zincr v "one"} - assert_error "*value is not a valid float" {r zincrby zincr 23456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789 "one"} - } - - test "ZADD - Variadic version base case - $encoding" { - r del myzset - list [r zadd myzset 10 a 20 b 30 c] [r zrange myzset 0 -1 withscores] - } {3 {a 10 b 20 c 30}} - - test "ZADD - Return value is the number of actually added items - $encoding" { - list [r zadd myzset 5 x 20 b 30 c] [r zrange myzset 0 -1 withscores] - } {1 {x 5 a 10 b 20 c 30}} - - test "ZADD - Variadic version does not add nothing on single parsing err - $encoding" { - r del myzset - catch {r zadd myzset 10 a 20 b 30.badscore c} e - assert_match {*ERR*not*float*} $e - r exists myzset - } {0} - - test "ZADD - Variadic version will raise error on missing arg - $encoding" { - r del myzset - catch {r zadd myzset 10 a 20 b 30 c 40} e - assert_match {*ERR*syntax*} $e - } - - test "ZINCRBY does not work variadic even if shares ZADD implementation - $encoding" { - r del myzset - catch {r zincrby myzset 10 a 20 b 30 c} e - assert_match {*ERR*wrong*number*arg*} $e - } - - test "ZCARD basics - $encoding" { - r del ztmp - r zadd ztmp 10 a 20 b 30 c - assert_equal 3 [r zcard ztmp] - assert_equal 0 [r zcard zdoesntexist] - } - - test "ZREM removes key after last element is removed - $encoding" { - r del ztmp - r zadd ztmp 10 x - r zadd ztmp 20 y - - assert_equal 1 [r exists ztmp] - assert_equal 0 [r zrem ztmp z] - assert_equal 1 [r zrem ztmp y] - assert_equal 1 [r zrem ztmp x] - assert_equal 0 [r exists ztmp] - } - - test "ZREM variadic version - $encoding" { - r del ztmp - r zadd ztmp 10 a 20 b 30 c - assert_equal 2 [r zrem ztmp x y a b k] - assert_equal 0 [r zrem ztmp foo bar] - assert_equal 1 [r zrem ztmp c] - r exists ztmp - } {0} - - test "ZREM variadic version -- remove elements after key deletion - $encoding" { - r del ztmp - r zadd ztmp 10 a 20 b 30 c - r zrem ztmp a b c d e f g - } {3} - - test "ZRANGE basics - $encoding" { - r del ztmp - r zadd ztmp 1 a - r zadd ztmp 2 b - r zadd ztmp 3 c - r zadd ztmp 4 d - - assert_equal {a b c d} [r zrange ztmp 0 -1] - assert_equal {a b c} [r zrange ztmp 0 -2] - assert_equal {b c d} [r zrange ztmp 1 -1] - assert_equal {b c} [r zrange ztmp 1 -2] - assert_equal {c d} [r zrange ztmp -2 -1] - assert_equal {c} [r zrange ztmp -2 -2] - - # out of range start index - assert_equal {a b c} [r zrange ztmp -5 2] - assert_equal {a b} [r zrange ztmp -5 1] - assert_equal {} [r zrange ztmp 5 -1] - assert_equal {} [r zrange ztmp 5 -2] - - # out of range end index - assert_equal {a b c d} [r zrange ztmp 0 5] - assert_equal {b c d} [r zrange ztmp 1 5] - assert_equal {} [r zrange ztmp 0 -5] - assert_equal {} [r zrange ztmp 1 -5] - - # withscores - assert_equal {a 1 b 2 c 3 d 4} [r zrange ztmp 0 -1 withscores] - } - - test "ZREVRANGE basics - $encoding" { - r del ztmp - r zadd ztmp 1 a - r zadd ztmp 2 b - r zadd ztmp 3 c - r zadd ztmp 4 d - - assert_equal {d c b a} [r zrevrange ztmp 0 -1] - assert_equal {d c b} [r zrevrange ztmp 0 -2] - assert_equal {c b a} [r zrevrange ztmp 1 -1] - assert_equal {c b} [r zrevrange ztmp 1 -2] - assert_equal {b a} [r zrevrange ztmp -2 -1] - assert_equal {b} [r zrevrange ztmp -2 -2] - - # out of range start index - assert_equal {d c b} [r zrevrange ztmp -5 2] - assert_equal {d c} [r zrevrange ztmp -5 1] - assert_equal {} [r zrevrange ztmp 5 -1] - assert_equal {} [r zrevrange ztmp 5 -2] - - # out of range end index - assert_equal {d c b a} [r zrevrange ztmp 0 5] - assert_equal {c b a} [r zrevrange ztmp 1 5] - assert_equal {} [r zrevrange ztmp 0 -5] - assert_equal {} [r zrevrange ztmp 1 -5] - - # withscores - assert_equal {d 4 c 3 b 2 a 1} [r zrevrange ztmp 0 -1 withscores] - } - - test "ZRANK/ZREVRANK basics - $encoding" { - set nullres {$-1} - if {$::force_resp3} { - set nullres {_} - } - r del zranktmp - r zadd zranktmp 10 x - r zadd zranktmp 20 y - r zadd zranktmp 30 z - assert_equal 0 [r zrank zranktmp x] - assert_equal 1 [r zrank zranktmp y] - assert_equal 2 [r zrank zranktmp z] - assert_equal 2 [r zrevrank zranktmp x] - assert_equal 1 [r zrevrank zranktmp y] - assert_equal 0 [r zrevrank zranktmp z] - r readraw 1 - assert_equal $nullres [r zrank zranktmp foo] - assert_equal $nullres [r zrevrank zranktmp foo] - r readraw 0 - - # withscore - set nullres {*-1} - if {$::force_resp3} { - set nullres {_} - } - assert_equal {0 10} [r zrank zranktmp x withscore] - assert_equal {1 20} [r zrank zranktmp y withscore] - assert_equal {2 30} [r zrank zranktmp z withscore] - assert_equal {2 10} [r zrevrank zranktmp x withscore] - assert_equal {1 20} [r zrevrank zranktmp y withscore] - assert_equal {0 30} [r zrevrank zranktmp z withscore] - r readraw 1 - assert_equal $nullres [r zrank zranktmp foo withscore] - assert_equal $nullres [r zrevrank zranktmp foo withscore] - r readraw 0 - } - - test "ZRANK - after deletion - $encoding" { - r zrem zranktmp y - assert_equal 0 [r zrank zranktmp x] - assert_equal 1 [r zrank zranktmp z] - assert_equal {0 10} [r zrank zranktmp x withscore] - assert_equal {1 30} [r zrank zranktmp z withscore] - } - - test "ZINCRBY - can create a new sorted set - $encoding" { - r del zset - r zincrby zset 1 foo - assert_equal {foo} [r zrange zset 0 -1] - assert_equal 1 [r zscore zset foo] - } - - test "ZINCRBY - increment and decrement - $encoding" { - r zincrby zset 2 foo - r zincrby zset 1 bar - assert_equal {bar foo} [r zrange zset 0 -1] - - r zincrby zset 10 bar - r zincrby zset -5 foo - r zincrby zset -5 bar - assert_equal {foo bar} [r zrange zset 0 -1] - - assert_equal -2 [r zscore zset foo] - assert_equal 6 [r zscore zset bar] - } - - test "ZINCRBY return value - $encoding" { - r del ztmp - set retval [r zincrby ztmp 1.0 x] - assert {$retval == 1.0} - } - - proc create_default_zset {} { - create_zset zset {-inf a 1 b 2 c 3 d 4 e 5 f +inf g} - } - - proc create_long_zset {key length} { - r del $key - for {set i 0} {$i < $length} {incr i 1} { - r zadd $key $i i$i - } - } - - test "ZRANGEBYSCORE/ZREVRANGEBYSCORE/ZCOUNT basics - $encoding" { - create_default_zset - - # inclusive range - assert_equal {a b c} [r zrangebyscore zset -inf 2] - assert_equal {b c d} [r zrangebyscore zset 0 3] - assert_equal {d e f} [r zrangebyscore zset 3 6] - assert_equal {e f g} [r zrangebyscore zset 4 +inf] - assert_equal {c b a} [r zrevrangebyscore zset 2 -inf] - assert_equal {d c b} [r zrevrangebyscore zset 3 0] - assert_equal {f e d} [r zrevrangebyscore zset 6 3] - assert_equal {g f e} [r zrevrangebyscore zset +inf 4] - assert_equal 3 [r zcount zset 0 3] - - # exclusive range - assert_equal {b} [r zrangebyscore zset (-inf (2] - assert_equal {b c} [r zrangebyscore zset (0 (3] - assert_equal {e f} [r zrangebyscore zset (3 (6] - assert_equal {f} [r zrangebyscore zset (4 (+inf] - assert_equal {b} [r zrevrangebyscore zset (2 (-inf] - assert_equal {c b} [r zrevrangebyscore zset (3 (0] - assert_equal {f e} [r zrevrangebyscore zset (6 (3] - assert_equal {f} [r zrevrangebyscore zset (+inf (4] - assert_equal 2 [r zcount zset (0 (3] - - # test empty ranges - r zrem zset a - r zrem zset g - - # inclusive - assert_equal {} [r zrangebyscore zset 4 2] - assert_equal {} [r zrangebyscore zset 6 +inf] - assert_equal {} [r zrangebyscore zset -inf -6] - assert_equal {} [r zrevrangebyscore zset +inf 6] - assert_equal {} [r zrevrangebyscore zset -6 -inf] - - # exclusive - assert_equal {} [r zrangebyscore zset (4 (2] - assert_equal {} [r zrangebyscore zset 2 (2] - assert_equal {} [r zrangebyscore zset (2 2] - assert_equal {} [r zrangebyscore zset (6 (+inf] - assert_equal {} [r zrangebyscore zset (-inf (-6] - assert_equal {} [r zrevrangebyscore zset (+inf (6] - assert_equal {} [r zrevrangebyscore zset (-6 (-inf] - - # empty inner range - assert_equal {} [r zrangebyscore zset 2.4 2.6] - assert_equal {} [r zrangebyscore zset (2.4 2.6] - assert_equal {} [r zrangebyscore zset 2.4 (2.6] - assert_equal {} [r zrangebyscore zset (2.4 (2.6] - } - - test "ZRANGEBYSCORE with WITHSCORES - $encoding" { - create_default_zset - assert_equal {b 1 c 2 d 3} [r zrangebyscore zset 0 3 withscores] - assert_equal {d 3 c 2 b 1} [r zrevrangebyscore zset 3 0 withscores] - } - - test "ZRANGEBYSCORE with LIMIT - $encoding" { - create_default_zset - assert_equal {b c} [r zrangebyscore zset 0 10 LIMIT 0 2] - assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 3] - assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 10] - assert_equal {} [r zrangebyscore zset 0 10 LIMIT 20 10] - assert_equal {} [r zrangebyscore zset 0 10 LIMIT -1 2] - assert_equal {f e} [r zrevrangebyscore zset 10 0 LIMIT 0 2] - assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 3] - assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 10] - assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT 20 10] - assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT -1 2] - # zrangebyscore uses different logic when offset > ZSKIPLIST_MAX_SEARCH - create_long_zset zset 30 - assert_equal {i12 i13 i14} [r zrangebyscore zset 0 20 LIMIT 12 3] - assert_equal {i14 i15} [r zrangebyscore zset 0 20 LIMIT 14 2] - assert_equal {i19 i20 i21} [r zrangebyscore zset 0 30 LIMIT 19 3] - assert_equal {i29} [r zrangebyscore zset 10 30 LIMIT 19 2] - assert_equal {} [r zrangebyscore zset 0 20 LIMIT -1 3] - assert_equal {i17 i16 i15} [r zrevrangebyscore zset 30 10 LIMIT 12 3] - assert_equal {i6 i5} [r zrevrangebyscore zset 20 0 LIMIT 14 2] - assert_equal {i2 i1 i0} [r zrevrangebyscore zset 20 0 LIMIT 18 5] - assert_equal {i0} [r zrevrangebyscore zset 20 0 LIMIT 20 5] - assert_equal {} [r zrevrangebyscore zset 30 10 LIMIT -1 3] - } - - test "ZRANGEBYSCORE with LIMIT and WITHSCORES - $encoding" { - create_default_zset - assert_equal {e 4 f 5} [r zrangebyscore zset 2 5 LIMIT 2 3 WITHSCORES] - assert_equal {d 3 c 2} [r zrevrangebyscore zset 5 2 LIMIT 2 3 WITHSCORES] - assert_equal {} [r zrangebyscore zset 2 5 LIMIT 12 13 WITHSCORES] - } - - test "ZRANGEBYSCORE with non-value min or max - $encoding" { - assert_error "*not*float*" {r zrangebyscore fooz str 1} - assert_error "*not*float*" {r zrangebyscore fooz 1 str} - assert_error "*not*float*" {r zrangebyscore fooz 1 NaN} - } - - proc create_default_lex_zset {} { - create_zset zset {0 alpha 0 bar 0 cool 0 down - 0 elephant 0 foo 0 great 0 hill - 0 omega} - } - - proc create_long_lex_zset {} { - create_zset zset {0 alpha 0 bar 0 cool 0 down - 0 elephant 0 foo 0 great 0 hill - 0 island 0 jacket 0 key 0 lip - 0 max 0 null 0 omega 0 point - 0 query 0 result 0 sea 0 tree} - } - - test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics - $encoding" { - create_default_lex_zset - - # inclusive range - assert_equal {alpha bar cool} [r zrangebylex zset - \[cool] - assert_equal {bar cool down} [r zrangebylex zset \[bar \[down] - assert_equal {great hill omega} [r zrangebylex zset \[g +] - assert_equal {cool bar alpha} [r zrevrangebylex zset \[cool -] - assert_equal {down cool bar} [r zrevrangebylex zset \[down \[bar] - assert_equal {omega hill great foo elephant down} [r zrevrangebylex zset + \[d] - assert_equal 3 [r zlexcount zset \[ele \[h] - - # exclusive range - assert_equal {alpha bar} [r zrangebylex zset - (cool] - assert_equal {cool} [r zrangebylex zset (bar (down] - assert_equal {hill omega} [r zrangebylex zset (great +] - assert_equal {bar alpha} [r zrevrangebylex zset (cool -] - assert_equal {cool} [r zrevrangebylex zset (down (bar] - assert_equal {omega hill} [r zrevrangebylex zset + (great] - assert_equal 2 [r zlexcount zset (ele (great] - - # inclusive and exclusive - assert_equal {} [r zrangebylex zset (az (b] - assert_equal {} [r zrangebylex zset (z +] - assert_equal {} [r zrangebylex zset - \[aaaa] - assert_equal {} [r zrevrangebylex zset \[elez \[elex] - assert_equal {} [r zrevrangebylex zset (hill (omega] - } - - test "ZLEXCOUNT advanced - $encoding" { - create_default_lex_zset - - assert_equal 9 [r zlexcount zset - +] - assert_equal 0 [r zlexcount zset + -] - assert_equal 0 [r zlexcount zset + \[c] - assert_equal 0 [r zlexcount zset \[c -] - assert_equal 8 [r zlexcount zset \[bar +] - assert_equal 5 [r zlexcount zset \[bar \[foo] - assert_equal 4 [r zlexcount zset \[bar (foo] - assert_equal 4 [r zlexcount zset (bar \[foo] - assert_equal 3 [r zlexcount zset (bar (foo] - assert_equal 5 [r zlexcount zset - (foo] - assert_equal 1 [r zlexcount zset (maxstring +] - } - - test "ZRANGEBYLEX with LIMIT - $encoding" { - create_default_lex_zset - assert_equal {alpha bar} [r zrangebylex zset - \[cool LIMIT 0 2] - assert_equal {bar cool} [r zrangebylex zset - \[cool LIMIT 1 2] - assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 0 0] - assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 2 0] - assert_equal {bar} [r zrangebylex zset \[bar \[down LIMIT 0 1] - assert_equal {cool} [r zrangebylex zset \[bar \[down LIMIT 1 1] - assert_equal {bar cool down} [r zrangebylex zset \[bar \[down LIMIT 0 100] - assert_equal {} [r zrangebylex zset - \[cool LIMIT -1 2] - assert_equal {omega hill great foo elephant} [r zrevrangebylex zset + \[d LIMIT 0 5] - assert_equal {omega hill great foo} [r zrevrangebylex zset + \[d LIMIT 0 4] - assert_equal {great foo elephant} [r zrevrangebylex zset + \[d LIMIT 2 3] - assert_equal {} [r zrevrangebylex zset + \[d LIMIT -1 5] - # zrangebylex uses different logic when offset > ZSKIPLIST_MAX_SEARCH - create_long_lex_zset - assert_equal {max null} [r zrangebylex zset - \[tree LIMIT 12 2] - assert_equal {point query} [r zrangebylex zset - \[tree LIMIT 15 2] - assert_equal {} [r zrangebylex zset \[max \[tree LIMIT 10 0] - assert_equal {} [r zrangebylex zset \[max \[tree LIMIT 12 0] - assert_equal {max} [r zrangebylex zset \[max \[null LIMIT 0 1] - assert_equal {null} [r zrangebylex zset \[max \[null LIMIT 1 1] - assert_equal {max null omega point} [r zrangebylex zset \[max \[point LIMIT 0 100] - assert_equal {} [r zrangebylex zset - \[tree LIMIT -1 2] - assert_equal {tree sea result query point} [r zrevrangebylex zset + \[o LIMIT 0 5] - assert_equal {tree sea result query} [r zrevrangebylex zset + \[o LIMIT 0 4] - assert_equal {omega null max lip} [r zrevrangebylex zset + \[l LIMIT 5 4] - assert_equal {elephant down} [r zrevrangebylex zset + \[a LIMIT 15 2] - assert_equal {bar alpha} [r zrevrangebylex zset + - LIMIT 18 6] - assert_equal {hill great foo} [r zrevrangebylex zset + \[c LIMIT 12 3] - assert_equal {} [r zrevrangebylex zset + \[o LIMIT -1 5] - } - - test "ZRANGEBYLEX with invalid lex range specifiers - $encoding" { - assert_error "*not*string*" {r zrangebylex fooz foo bar} - assert_error "*not*string*" {r zrangebylex fooz \[foo bar} - assert_error "*not*string*" {r zrangebylex fooz foo \[bar} - assert_error "*not*string*" {r zrangebylex fooz +x \[bar} - assert_error "*not*string*" {r zrangebylex fooz -x \[bar} - } - - test "ZREMRANGEBYSCORE basics - $encoding" { - proc remrangebyscore {min max} { - create_zset zset {1 a 2 b 3 c 4 d 5 e} - assert_equal 1 [r exists zset] - r zremrangebyscore zset $min $max - } - - # inner range - assert_equal 3 [remrangebyscore 2 4] - assert_equal {a e} [r zrange zset 0 -1] - - # start underflow - assert_equal 1 [remrangebyscore -10 1] - assert_equal {b c d e} [r zrange zset 0 -1] - - # end overflow - assert_equal 1 [remrangebyscore 5 10] - assert_equal {a b c d} [r zrange zset 0 -1] - - # switch min and max - assert_equal 0 [remrangebyscore 4 2] - assert_equal {a b c d e} [r zrange zset 0 -1] - - # -inf to mid - assert_equal 3 [remrangebyscore -inf 3] - assert_equal {d e} [r zrange zset 0 -1] - - # mid to +inf - assert_equal 3 [remrangebyscore 3 +inf] - assert_equal {a b} [r zrange zset 0 -1] - - # -inf to +inf - assert_equal 5 [remrangebyscore -inf +inf] - assert_equal {} [r zrange zset 0 -1] - - # exclusive min - assert_equal 4 [remrangebyscore (1 5] - assert_equal {a} [r zrange zset 0 -1] - assert_equal 3 [remrangebyscore (2 5] - assert_equal {a b} [r zrange zset 0 -1] - - # exclusive max - assert_equal 4 [remrangebyscore 1 (5] - assert_equal {e} [r zrange zset 0 -1] - assert_equal 3 [remrangebyscore 1 (4] - assert_equal {d e} [r zrange zset 0 -1] - - # exclusive min and max - assert_equal 3 [remrangebyscore (1 (5] - assert_equal {a e} [r zrange zset 0 -1] - - # destroy when empty - assert_equal 5 [remrangebyscore 1 5] - assert_equal 0 [r exists zset] - } - - test "ZREMRANGEBYSCORE with non-value min or max - $encoding" { - assert_error "*not*float*" {r zremrangebyscore fooz str 1} - assert_error "*not*float*" {r zremrangebyscore fooz 1 str} - assert_error "*not*float*" {r zremrangebyscore fooz 1 NaN} - } - - test "ZREMRANGEBYRANK basics - $encoding" { - proc remrangebyrank {min max} { - create_zset zset {1 a 2 b 3 c 4 d 5 e} - assert_equal 1 [r exists zset] - r zremrangebyrank zset $min $max - } - - # inner range - assert_equal 3 [remrangebyrank 1 3] - assert_equal {a e} [r zrange zset 0 -1] - - # start underflow - assert_equal 1 [remrangebyrank -10 0] - assert_equal {b c d e} [r zrange zset 0 -1] - - # start overflow - assert_equal 0 [remrangebyrank 10 -1] - assert_equal {a b c d e} [r zrange zset 0 -1] - - # end underflow - assert_equal 0 [remrangebyrank 0 -10] - assert_equal {a b c d e} [r zrange zset 0 -1] - - # end overflow - assert_equal 5 [remrangebyrank 0 10] - assert_equal {} [r zrange zset 0 -1] - - # destroy when empty - assert_equal 5 [remrangebyrank 0 4] - assert_equal 0 [r exists zset] - } - - test "ZREMRANGEBYLEX basics - $encoding" { - proc remrangebylex {min max} { - create_default_lex_zset - assert_equal 1 [r exists zset] - r zremrangebylex zset $min $max - } - - # inclusive range - assert_equal 3 [remrangebylex - \[cool] - assert_equal {down elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 3 [remrangebylex \[bar \[down] - assert_equal {alpha elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 3 [remrangebylex \[g +] - assert_equal {alpha bar cool down elephant foo} [r zrange zset 0 -1] - assert_equal 6 [r zcard zset] - - # exclusive range - assert_equal 2 [remrangebylex - (cool] - assert_equal {cool down elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 1 [remrangebylex (bar (down] - assert_equal {alpha bar down elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 2 [remrangebylex (great +] - assert_equal {alpha bar cool down elephant foo great} [r zrange zset 0 -1] - assert_equal 7 [r zcard zset] - - # inclusive and exclusive - assert_equal 0 [remrangebylex (az (b] - assert_equal {alpha bar cool down elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 0 [remrangebylex (z +] - assert_equal {alpha bar cool down elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 0 [remrangebylex - \[aaaa] - assert_equal {alpha bar cool down elephant foo great hill omega} [r zrange zset 0 -1] - assert_equal 9 [r zcard zset] - - # destroy when empty - assert_equal 9 [remrangebylex - +] - assert_equal 0 [r zcard zset] - assert_equal 0 [r exists zset] - } - - test "ZUNIONSTORE against non-existing key doesn't set destination - $encoding" { - r del zseta{t} - assert_equal 0 [r zunionstore dst_key{t} 1 zseta{t}] - assert_equal 0 [r exists dst_key{t}] - } - - test "ZUNION/ZINTER/ZINTERCARD/ZDIFF against non-existing key - $encoding" { - r del zseta - assert_equal {} [r zunion 1 zseta] - assert_equal {} [r zinter 1 zseta] - assert_equal 0 [r zintercard 1 zseta] - assert_equal 0 [r zintercard 1 zseta limit 0] - assert_equal {} [r zdiff 1 zseta] - } - - test "ZUNIONSTORE with empty set - $encoding" { - r del zseta{t} zsetb{t} - r zadd zseta{t} 1 a - r zadd zseta{t} 2 b - r zunionstore zsetc{t} 2 zseta{t} zsetb{t} - r zrange zsetc{t} 0 -1 withscores - } {a 1 b 2} - - test "ZUNION/ZINTER/ZINTERCARD/ZDIFF with empty set - $encoding" { - r del zseta{t} zsetb{t} - r zadd zseta{t} 1 a - r zadd zseta{t} 2 b - assert_equal {a 1 b 2} [r zunion 2 zseta{t} zsetb{t} withscores] - assert_equal {} [r zinter 2 zseta{t} zsetb{t} withscores] - assert_equal 0 [r zintercard 2 zseta{t} zsetb{t}] - assert_equal 0 [r zintercard 2 zseta{t} zsetb{t} limit 0] - assert_equal {a 1 b 2} [r zdiff 2 zseta{t} zsetb{t} withscores] - } - - test "ZUNIONSTORE basics - $encoding" { - r del zseta{t} zsetb{t} zsetc{t} - r zadd zseta{t} 1 a - r zadd zseta{t} 2 b - r zadd zseta{t} 3 c - r zadd zsetb{t} 1 b - r zadd zsetb{t} 2 c - r zadd zsetb{t} 3 d - - assert_equal 4 [r zunionstore zsetc{t} 2 zseta{t} zsetb{t}] - assert_equal {a 1 b 3 d 3 c 5} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZUNION/ZINTER/ZINTERCARD/ZDIFF with integer members - $encoding" { - r del zsetd{t} zsetf{t} - r zadd zsetd{t} 1 1 - r zadd zsetd{t} 2 2 - r zadd zsetd{t} 3 3 - r zadd zsetf{t} 1 1 - r zadd zsetf{t} 3 3 - r zadd zsetf{t} 4 4 - - assert_equal {1 2 2 2 4 4 3 6} [r zunion 2 zsetd{t} zsetf{t} withscores] - assert_equal {1 2 3 6} [r zinter 2 zsetd{t} zsetf{t} withscores] - assert_equal 2 [r zintercard 2 zsetd{t} zsetf{t}] - assert_equal 2 [r zintercard 2 zsetd{t} zsetf{t} limit 0] - assert_equal {2 2} [r zdiff 2 zsetd{t} zsetf{t} withscores] - } - - test "ZUNIONSTORE with weights - $encoding" { - assert_equal 4 [r zunionstore zsetc{t} 2 zseta{t} zsetb{t} weights 2 3] - assert_equal {a 2 b 7 d 9 c 12} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZUNION with weights - $encoding" { - assert_equal {a 2 b 7 d 9 c 12} [r zunion 2 zseta{t} zsetb{t} weights 2 3 withscores] - assert_equal {b 7 c 12} [r zinter 2 zseta{t} zsetb{t} weights 2 3 withscores] - } - - test "ZUNIONSTORE with a regular set and weights - $encoding" { - r del seta{t} - r sadd seta{t} a - r sadd seta{t} b - r sadd seta{t} c - - assert_equal 4 [r zunionstore zsetc{t} 2 seta{t} zsetb{t} weights 2 3] - assert_equal {a 2 b 5 c 8 d 9} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZUNIONSTORE with AGGREGATE MIN - $encoding" { - assert_equal 4 [r zunionstore zsetc{t} 2 zseta{t} zsetb{t} aggregate min] - assert_equal {a 1 b 1 c 2 d 3} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZUNION/ZINTER with AGGREGATE MIN - $encoding" { - assert_equal {a 1 b 1 c 2 d 3} [r zunion 2 zseta{t} zsetb{t} aggregate min withscores] - assert_equal {b 1 c 2} [r zinter 2 zseta{t} zsetb{t} aggregate min withscores] - } - - test "ZUNIONSTORE with AGGREGATE MAX - $encoding" { - assert_equal 4 [r zunionstore zsetc{t} 2 zseta{t} zsetb{t} aggregate max] - assert_equal {a 1 b 2 c 3 d 3} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZUNION/ZINTER with AGGREGATE MAX - $encoding" { - assert_equal {a 1 b 2 c 3 d 3} [r zunion 2 zseta{t} zsetb{t} aggregate max withscores] - assert_equal {b 2 c 3} [r zinter 2 zseta{t} zsetb{t} aggregate max withscores] - } - - test "ZINTERSTORE basics - $encoding" { - assert_equal 2 [r zinterstore zsetc{t} 2 zseta{t} zsetb{t}] - assert_equal {b 3 c 5} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZINTER basics - $encoding" { - assert_equal {b 3 c 5} [r zinter 2 zseta{t} zsetb{t} withscores] - } - - test "ZINTERCARD with illegal arguments" { - assert_error "ERR syntax error*" {r zintercard 1 zseta{t} zseta{t}} - assert_error "ERR syntax error*" {r zintercard 1 zseta{t} bar_arg} - assert_error "ERR syntax error*" {r zintercard 1 zseta{t} LIMIT} - - assert_error "ERR LIMIT*" {r zintercard 1 myset{t} LIMIT -1} - assert_error "ERR LIMIT*" {r zintercard 1 myset{t} LIMIT a} - } - - test "ZINTERCARD basics - $encoding" { - assert_equal 2 [r zintercard 2 zseta{t} zsetb{t}] - assert_equal 2 [r zintercard 2 zseta{t} zsetb{t} limit 0] - assert_equal 1 [r zintercard 2 zseta{t} zsetb{t} limit 1] - assert_equal 2 [r zintercard 2 zseta{t} zsetb{t} limit 10] - } - - test "ZINTER RESP3 - $encoding" { - r hello 3 - assert_equal {{b 3.0} {c 5.0}} [r zinter 2 zseta{t} zsetb{t} withscores] - r hello 2 - } - - test "ZINTERSTORE with weights - $encoding" { - assert_equal 2 [r zinterstore zsetc{t} 2 zseta{t} zsetb{t} weights 2 3] - assert_equal {b 7 c 12} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZINTER with weights - $encoding" { - assert_equal {b 7 c 12} [r zinter 2 zseta{t} zsetb{t} weights 2 3 withscores] - } - - test "ZINTERSTORE with a regular set and weights - $encoding" { - r del seta{t} - r sadd seta{t} a - r sadd seta{t} b - r sadd seta{t} c - assert_equal 2 [r zinterstore zsetc{t} 2 seta{t} zsetb{t} weights 2 3] - assert_equal {b 5 c 8} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZINTERSTORE with AGGREGATE MIN - $encoding" { - assert_equal 2 [r zinterstore zsetc{t} 2 zseta{t} zsetb{t} aggregate min] - assert_equal {b 1 c 2} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZINTERSTORE with AGGREGATE MAX - $encoding" { - assert_equal 2 [r zinterstore zsetc{t} 2 zseta{t} zsetb{t} aggregate max] - assert_equal {b 2 c 3} [r zrange zsetc{t} 0 -1 withscores] - } - - foreach cmd {ZUNIONSTORE ZINTERSTORE} { - test "$cmd with +inf/-inf scores - $encoding" { - r del zsetinf1{t} zsetinf2{t} - - r zadd zsetinf1{t} +inf key - r zadd zsetinf2{t} +inf key - r $cmd zsetinf3{t} 2 zsetinf1{t} zsetinf2{t} - assert_equal inf [r zscore zsetinf3{t} key] - - r zadd zsetinf1{t} -inf key - r zadd zsetinf2{t} +inf key - r $cmd zsetinf3{t} 2 zsetinf1{t} zsetinf2{t} - assert_equal 0 [r zscore zsetinf3{t} key] - - r zadd zsetinf1{t} +inf key - r zadd zsetinf2{t} -inf key - r $cmd zsetinf3{t} 2 zsetinf1{t} zsetinf2{t} - assert_equal 0 [r zscore zsetinf3{t} key] - - r zadd zsetinf1{t} -inf key - r zadd zsetinf2{t} -inf key - r $cmd zsetinf3{t} 2 zsetinf1{t} zsetinf2{t} - assert_equal -inf [r zscore zsetinf3{t} key] - } - - test "$cmd with NaN weights - $encoding" { - r del zsetinf1{t} zsetinf2{t} - - r zadd zsetinf1{t} 1.0 key - r zadd zsetinf2{t} 1.0 key - assert_error "*weight*not*float*" { - r $cmd zsetinf3{t} 2 zsetinf1{t} zsetinf2{t} weights nan nan - } - } - } - - test "ZDIFFSTORE basics - $encoding" { - assert_equal 1 [r zdiffstore zsetc{t} 2 zseta{t} zsetb{t}] - assert_equal {a 1} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZDIFF basics - $encoding" { - assert_equal {a 1} [r zdiff 2 zseta{t} zsetb{t} withscores] - } - - test "ZDIFFSTORE with a regular set - $encoding" { - r del seta{t} - r sadd seta{t} a - r sadd seta{t} b - r sadd seta{t} c - assert_equal 1 [r zdiffstore zsetc{t} 2 seta{t} zsetb{t}] - assert_equal {a 1} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZDIFF subtracting set from itself - $encoding" { - assert_equal 0 [r zdiffstore zsetc{t} 2 zseta{t} zseta{t}] - assert_equal {} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZDIFF algorithm 1 - $encoding" { - r del zseta{t} zsetb{t} zsetc{t} - r zadd zseta{t} 1 a - r zadd zseta{t} 2 b - r zadd zseta{t} 3 c - r zadd zsetb{t} 1 b - r zadd zsetb{t} 2 c - r zadd zsetb{t} 3 d - assert_equal 1 [r zdiffstore zsetc{t} 2 zseta{t} zsetb{t}] - assert_equal {a 1} [r zrange zsetc{t} 0 -1 withscores] - } - - test "ZDIFF algorithm 2 - $encoding" { - r del zseta{t} zsetb{t} zsetc{t} zsetd{t} zsete{t} - r zadd zseta{t} 1 a - r zadd zseta{t} 2 b - r zadd zseta{t} 3 c - r zadd zseta{t} 5 e - r zadd zsetb{t} 1 b - r zadd zsetc{t} 1 c - r zadd zsetd{t} 1 d - assert_equal 2 [r zdiffstore zsete{t} 4 zseta{t} zsetb{t} zsetc{t} zsetd{t}] - assert_equal {a 1 e 5} [r zrange zsete{t} 0 -1 withscores] - } - - test "ZDIFF fuzzing - $encoding" { - for {set j 0} {$j < 100} {incr j} { - unset -nocomplain s - array set s {} - set args {} - set num_sets [expr {[randomInt 10]+1}] - for {set i 0} {$i < $num_sets} {incr i} { - set num_elements [randomInt 100] - r del zset_$i{t} - lappend args zset_$i{t} - while {$num_elements} { - set ele [randomValue] - r zadd zset_$i{t} [randomInt 100] $ele - if {$i == 0} { - set s($ele) x - } else { - unset -nocomplain s($ele) - } - incr num_elements -1 - } - } - set result [lsort [r zdiff [llength $args] {*}$args]] - assert_equal $result [lsort [array names s]] - } - } - - foreach {pop} {ZPOPMIN ZPOPMAX} { - test "$pop with the count 0 returns an empty array" { - r del zset - r zadd zset 1 a 2 b 3 c - assert_equal {} [r $pop zset 0] - - # Make sure we can distinguish between an empty array and a null response - r readraw 1 - assert_equal {*0} [r $pop zset 0] - r readraw 0 - - assert_equal 3 [r zcard zset] - } - - test "$pop with negative count" { - r set zset foo - assert_error "ERR *must be positive" {r $pop zset -1} - - r del zset - assert_error "ERR *must be positive" {r $pop zset -2} - - r zadd zset 1 a 2 b 3 c - assert_error "ERR *must be positive" {r $pop zset -3} - } - } - - foreach {popmin popmax} {ZPOPMIN ZPOPMAX ZMPOP_MIN ZMPOP_MAX} { - test "Basic $popmin/$popmax with a single key - $encoding" { - r del zset - verify_zpop_response r $popmin zset 0 {} {} - - create_zset zset {-1 a 1 b 2 c 3 d 4 e} - verify_zpop_response r $popmin zset 0 {a -1} {zset {{a -1}}} - verify_zpop_response r $popmin zset 0 {b 1} {zset {{b 1}}} - verify_zpop_response r $popmax zset 0 {e 4} {zset {{e 4}}} - verify_zpop_response r $popmax zset 0 {d 3} {zset {{d 3}}} - verify_zpop_response r $popmin zset 0 {c 2} {zset {{c 2}}} - assert_equal 0 [r exists zset] - } - - test "$popmin/$popmax with count - $encoding" { - r del z1 - verify_zpop_response r $popmin z1 2 {} {} - - create_zset z1 {0 a 1 b 2 c 3 d} - verify_zpop_response r $popmin z1 2 {a 0 b 1} {z1 {{a 0} {b 1}}} - verify_zpop_response r $popmax z1 2 {d 3 c 2} {z1 {{d 3} {c 2}}} - } - } - - foreach {popmin popmax} {BZPOPMIN BZPOPMAX BZMPOP_MIN BZMPOP_MAX} { - test "$popmin/$popmax with a single existing sorted set - $encoding" { - set rd [redis_deferring_client] - create_zset zset {0 a 1 b 2 c 3 d} - - verify_bzpop_response $rd $popmin zset 5 0 {zset a 0} {zset {{a 0}}} - verify_bzpop_response $rd $popmax zset 5 0 {zset d 3} {zset {{d 3}}} - verify_bzpop_response $rd $popmin zset 5 0 {zset b 1} {zset {{b 1}}} - verify_bzpop_response $rd $popmax zset 5 0 {zset c 2} {zset {{c 2}}} - assert_equal 0 [r exists zset] - $rd close - } - - test "$popmin/$popmax with multiple existing sorted sets - $encoding" { - set rd [redis_deferring_client] - create_zset z1{t} {0 a 1 b 2 c} - create_zset z2{t} {3 d 4 e 5 f} - - verify_bzpop_two_key_response $rd $popmin z1{t} z2{t} 5 0 {z1{t} a 0} {z1{t} {{a 0}}} - verify_bzpop_two_key_response $rd $popmax z1{t} z2{t} 5 0 {z1{t} c 2} {z1{t} {{c 2}}} - assert_equal 1 [r zcard z1{t}] - assert_equal 3 [r zcard z2{t}] - - verify_bzpop_two_key_response $rd $popmax z2{t} z1{t} 5 0 {z2{t} f 5} {z2{t} {{f 5}}} - verify_bzpop_two_key_response $rd $popmin z2{t} z1{t} 5 0 {z2{t} d 3} {z2{t} {{d 3}}} - assert_equal 1 [r zcard z1{t}] - assert_equal 1 [r zcard z2{t}] - $rd close - } - - test "$popmin/$popmax second sorted set has members - $encoding" { - set rd [redis_deferring_client] - r del z1{t} - create_zset z2{t} {3 d 4 e 5 f} - - verify_bzpop_two_key_response $rd $popmax z1{t} z2{t} 5 0 {z2{t} f 5} {z2{t} {{f 5}}} - verify_bzpop_two_key_response $rd $popmin z1{t} z2{t} 5 0 {z2{t} d 3} {z2{t} {{d 3}}} - assert_equal 0 [r zcard z1{t}] - assert_equal 1 [r zcard z2{t}] - $rd close - } - } - - foreach {popmin popmax} {ZPOPMIN ZPOPMAX ZMPOP_MIN ZMPOP_MAX} { - test "Basic $popmin/$popmax - $encoding RESP3" { - r hello 3 - create_zset z1 {0 a 1 b 2 c 3 d} - verify_zpop_response r $popmin z1 0 {a 0.0} {z1 {{a 0.0}}} - verify_zpop_response r $popmax z1 0 {d 3.0} {z1 {{d 3.0}}} - r hello 2 - } - - test "$popmin/$popmax with count - $encoding RESP3" { - r hello 3 - create_zset z1 {0 a 1 b 2 c 3 d} - verify_zpop_response r $popmin z1 2 {{a 0.0} {b 1.0}} {z1 {{a 0.0} {b 1.0}}} - verify_zpop_response r $popmax z1 2 {{d 3.0} {c 2.0}} {z1 {{d 3.0} {c 2.0}}} - r hello 2 - } - } - - foreach {popmin popmax} {BZPOPMIN BZPOPMAX BZMPOP_MIN BZMPOP_MAX} { - test "$popmin/$popmax - $encoding RESP3" { - r hello 3 - set rd [redis_deferring_client] - create_zset zset {0 a 1 b 2 c 3 d} - - verify_bzpop_response $rd $popmin zset 5 0 {zset a 0} {zset {{a 0}}} - verify_bzpop_response $rd $popmax zset 5 0 {zset d 3} {zset {{d 3}}} - verify_bzpop_response $rd $popmin zset 5 0 {zset b 1} {zset {{b 1}}} - verify_bzpop_response $rd $popmax zset 5 0 {zset c 2} {zset {{c 2}}} - - assert_equal 0 [r exists zset] - r hello 2 - $rd close - } - } - - r config set zset-max-ziplist-entries $original_max_entries - r config set zset-max-ziplist-value $original_max_value - } - - basics listpack - basics skiplist - - test "ZPOP/ZMPOP against wrong type" { - r set foo{t} bar - assert_error "*WRONGTYPE*" {r zpopmin foo{t}} - assert_error "*WRONGTYPE*" {r zpopmin foo{t} 0} - assert_error "*WRONGTYPE*" {r zpopmax foo{t}} - assert_error "*WRONGTYPE*" {r zpopmax foo{t} 0} - assert_error "*WRONGTYPE*" {r zpopmin foo{t} 2} - - assert_error "*WRONGTYPE*" {r zmpop 1 foo{t} min} - assert_error "*WRONGTYPE*" {r zmpop 1 foo{t} max} - assert_error "*WRONGTYPE*" {r zmpop 1 foo{t} max count 200} - - r del foo{t} - r set foo2{t} bar - assert_error "*WRONGTYPE*" {r zmpop 2 foo{t} foo2{t} min} - assert_error "*WRONGTYPE*" {r zmpop 2 foo2{t} foo1{t} max count 1} - } - - test "ZMPOP with illegal argument" { - assert_error "ERR wrong number of arguments for 'zmpop' command" {r zmpop} - assert_error "ERR wrong number of arguments for 'zmpop' command" {r zmpop 1} - assert_error "ERR wrong number of arguments for 'zmpop' command" {r zmpop 1 myzset{t}} - - assert_error "ERR numkeys*" {r zmpop 0 myzset{t} MIN} - assert_error "ERR numkeys*" {r zmpop a myzset{t} MIN} - assert_error "ERR numkeys*" {r zmpop -1 myzset{t} MAX} - - assert_error "ERR syntax error*" {r zmpop 1 myzset{t} bad_where} - assert_error "ERR syntax error*" {r zmpop 1 myzset{t} MIN bar_arg} - assert_error "ERR syntax error*" {r zmpop 1 myzset{t} MAX MIN} - assert_error "ERR syntax error*" {r zmpop 1 myzset{t} COUNT} - assert_error "ERR syntax error*" {r zmpop 1 myzset{t} MAX COUNT 1 COUNT 2} - assert_error "ERR syntax error*" {r zmpop 2 myzset{t} myzset2{t} bad_arg} - - assert_error "ERR count*" {r zmpop 1 myzset{t} MIN COUNT 0} - assert_error "ERR count*" {r zmpop 1 myzset{t} MAX COUNT a} - assert_error "ERR count*" {r zmpop 1 myzset{t} MIN COUNT -1} - assert_error "ERR count*" {r zmpop 2 myzset{t} myzset2{t} MAX COUNT -1} - } - - test "ZMPOP propagate as pop with count command to replica" { - set repl [attach_to_replication_stream] - - # ZMPOP min/max propagate as ZPOPMIN/ZPOPMAX with count - r zadd myzset{t} 1 one 2 two 3 three - - # Pop elements from one zset. - r zmpop 1 myzset{t} min - r zmpop 1 myzset{t} max count 1 - - # Now the zset have only one element - r zmpop 2 myzset{t} myzset2{t} min count 10 - - # No elements so we don't propagate. - r zmpop 2 myzset{t} myzset2{t} max count 10 - - # Pop elements from the second zset. - r zadd myzset2{t} 1 one 2 two 3 three - r zmpop 2 myzset{t} myzset2{t} min count 2 - r zmpop 2 myzset{t} myzset2{t} max count 1 - - # Pop all elements. - r zadd myzset{t} 1 one 2 two 3 three - r zadd myzset2{t} 4 four 5 five 6 six - r zmpop 2 myzset{t} myzset2{t} min count 10 - r zmpop 2 myzset{t} myzset2{t} max count 10 - - assert_replication_stream $repl { - {select *} - {zadd myzset{t} 1 one 2 two 3 three} - {zpopmin myzset{t} 1} - {zpopmax myzset{t} 1} - {zpopmin myzset{t} 1} - {zadd myzset2{t} 1 one 2 two 3 three} - {zpopmin myzset2{t} 2} - {zpopmax myzset2{t} 1} - {zadd myzset{t} 1 one 2 two 3 three} - {zadd myzset2{t} 4 four 5 five 6 six} - {zpopmin myzset{t} 3} - {zpopmax myzset2{t} 3} - } - close_replication_stream $repl - } {} {needs:repl} - - foreach resp {3 2} { - set rd [redis_deferring_client] - - if {[lsearch $::denytags "resp3"] >= 0} { - if {$resp == 3} {continue} - } elseif {$::force_resp3} { - if {$resp == 2} {continue} - } - r hello $resp - $rd hello $resp - $rd read - - test "ZPOPMIN/ZPOPMAX readraw in RESP$resp" { - r del zset{t} - create_zset zset2{t} {1 a 2 b 3 c 4 d 5 e} - - r readraw 1 - - # ZPOP against non existing key. - assert_equal {*0} [r zpopmin zset{t}] - assert_equal {*0} [r zpopmin zset{t} 1] - - # ZPOP without COUNT option. - assert_equal {*2} [r zpopmin zset2{t}] - assert_equal [r read] {$1} - assert_equal [r read] {a} - verify_score_response r $resp 1 - - # ZPOP with COUNT option. - if {$resp == 2} { - assert_equal {*2} [r zpopmax zset2{t} 1] - assert_equal [r read] {$1} - assert_equal [r read] {e} - } elseif {$resp == 3} { - assert_equal {*1} [r zpopmax zset2{t} 1] - assert_equal [r read] {*2} - assert_equal [r read] {$1} - assert_equal [r read] {e} - } - verify_score_response r $resp 5 - - r readraw 0 - } - - test "BZPOPMIN/BZPOPMAX readraw in RESP$resp" { - r del zset{t} - create_zset zset2{t} {1 a 2 b 3 c 4 d 5 e} - - $rd readraw 1 - - # BZPOP released on timeout. - $rd bzpopmin zset{t} 0.01 - verify_nil_response $resp [$rd read] - $rd bzpopmax zset{t} 0.01 - verify_nil_response $resp [$rd read] - - # BZPOP non-blocking path. - $rd bzpopmin zset1{t} zset2{t} 0.1 - assert_equal [$rd read] {*3} - assert_equal [$rd read] {$8} - assert_equal [$rd read] {zset2{t}} - assert_equal [$rd read] {$1} - assert_equal [$rd read] {a} - verify_score_response $rd $resp 1 - - # BZPOP blocking path. - $rd bzpopmin zset{t} 5 - wait_for_blocked_client - r zadd zset{t} 1 a - assert_equal [$rd read] {*3} - assert_equal [$rd read] {$7} - assert_equal [$rd read] {zset{t}} - assert_equal [$rd read] {$1} - assert_equal [$rd read] {a} - verify_score_response $rd $resp 1 - - $rd readraw 0 - } - - test "ZMPOP readraw in RESP$resp" { - r del zset{t} zset2{t} - create_zset zset3{t} {1 a} - create_zset zset4{t} {1 a 2 b 3 c 4 d 5 e} - - r readraw 1 - - # ZMPOP against non existing key. - verify_nil_response $resp [r zmpop 1 zset{t} min] - verify_nil_response $resp [r zmpop 1 zset{t} max count 1] - verify_nil_response $resp [r zmpop 2 zset{t} zset2{t} min] - verify_nil_response $resp [r zmpop 2 zset{t} zset2{t} max count 1] - - # ZMPOP with one input key. - assert_equal {*2} [r zmpop 1 zset3{t} max] - assert_equal [r read] {$8} - assert_equal [r read] {zset3{t}} - assert_equal [r read] {*1} - assert_equal [r read] {*2} - assert_equal [r read] {$1} - assert_equal [r read] {a} - verify_score_response r $resp 1 - - # ZMPOP with COUNT option. - assert_equal {*2} [r zmpop 2 zset3{t} zset4{t} min count 2] - assert_equal [r read] {$8} - assert_equal [r read] {zset4{t}} - assert_equal [r read] {*2} - assert_equal [r read] {*2} - assert_equal [r read] {$1} - assert_equal [r read] {a} - verify_score_response r $resp 1 - assert_equal [r read] {*2} - assert_equal [r read] {$1} - assert_equal [r read] {b} - verify_score_response r $resp 2 - - r readraw 0 - } - - test "BZMPOP readraw in RESP$resp" { - r del zset{t} zset2{t} - create_zset zset3{t} {1 a 2 b 3 c 4 d 5 e} - - $rd readraw 1 - - # BZMPOP released on timeout. - $rd bzmpop 0.01 1 zset{t} min - verify_nil_response $resp [$rd read] - $rd bzmpop 0.01 2 zset{t} zset2{t} max - verify_nil_response $resp [$rd read] - - # BZMPOP non-blocking path. - $rd bzmpop 0.1 2 zset3{t} zset4{t} min - - assert_equal [$rd read] {*2} - assert_equal [$rd read] {$8} - assert_equal [$rd read] {zset3{t}} - assert_equal [$rd read] {*1} - assert_equal [$rd read] {*2} - assert_equal [$rd read] {$1} - assert_equal [$rd read] {a} - verify_score_response $rd $resp 1 - - # BZMPOP blocking path with COUNT option. - $rd bzmpop 5 2 zset{t} zset2{t} max count 2 - wait_for_blocked_client - r zadd zset2{t} 1 a 2 b 3 c - - assert_equal [$rd read] {*2} - assert_equal [$rd read] {$8} - assert_equal [$rd read] {zset2{t}} - assert_equal [$rd read] {*2} - assert_equal [$rd read] {*2} - assert_equal [$rd read] {$1} - assert_equal [$rd read] {c} - verify_score_response $rd $resp 3 - assert_equal [$rd read] {*2} - assert_equal [$rd read] {$1} - assert_equal [$rd read] {b} - verify_score_response $rd $resp 2 - - } - - $rd close - r hello 2 - } - - test {ZINTERSTORE regression with two sets, intset+hashtable} { - r del seta{t} setb{t} setc{t} - r sadd set1{t} a - r sadd set2{t} 10 - r zinterstore set3{t} 2 set1{t} set2{t} - } {0} - - test {ZUNIONSTORE regression, should not create NaN in scores} { - r zadd z{t} -inf neginf - r zunionstore out{t} 1 z{t} weights 0 - r zrange out{t} 0 -1 withscores - } {neginf 0} - - test {ZINTERSTORE #516 regression, mixed sets and ziplist zsets} { - r sadd one{t} 100 101 102 103 - r sadd two{t} 100 200 201 202 - r zadd three{t} 1 500 1 501 1 502 1 503 1 100 - r zinterstore to_here{t} 3 one{t} two{t} three{t} WEIGHTS 0 0 1 - r zrange to_here{t} 0 -1 - } {100} - - test {ZUNIONSTORE result is sorted} { - # Create two sets with common and not common elements, perform - # the UNION, check that elements are still sorted. - r del one{t} two{t} dest{t} - set cmd1 [list r zadd one{t}] - set cmd2 [list r zadd two{t}] - for {set j 0} {$j < 1000} {incr j} { - lappend cmd1 [expr rand()] [randomInt 1000] - lappend cmd2 [expr rand()] [randomInt 1000] - } - {*}$cmd1 - {*}$cmd2 - assert {[r zcard one{t}] > 100} - assert {[r zcard two{t}] > 100} - r zunionstore dest{t} 2 one{t} two{t} - set oldscore 0 - foreach {ele score} [r zrange dest{t} 0 -1 withscores] { - assert {$score >= $oldscore} - set oldscore $score - } - } - - test "ZUNIONSTORE/ZINTERSTORE/ZDIFFSTORE error if using WITHSCORES " { - assert_error "*ERR*syntax*" {r zunionstore foo{t} 2 zsetd{t} zsetf{t} withscores} - assert_error "*ERR*syntax*" {r zinterstore foo{t} 2 zsetd{t} zsetf{t} withscores} - assert_error "*ERR*syntax*" {r zdiffstore foo{t} 2 zsetd{t} zsetf{t} withscores} - } - - test {ZMSCORE retrieve} { - r del zmscoretest - r zadd zmscoretest 10 x - r zadd zmscoretest 20 y - - r zmscore zmscoretest x y - } {10 20} - - test {ZMSCORE retrieve from empty set} { - r del zmscoretest - - r zmscore zmscoretest x y - } {{} {}} - - test {ZMSCORE retrieve with missing member} { - r del zmscoretest - r zadd zmscoretest 10 x - - r zmscore zmscoretest x y - } {10 {}} - - test {ZMSCORE retrieve single member} { - r del zmscoretest - r zadd zmscoretest 10 x - r zadd zmscoretest 20 y - - r zmscore zmscoretest x - } {10} - - test {ZMSCORE retrieve requires one or more members} { - r del zmscoretest - r zadd zmscoretest 10 x - r zadd zmscoretest 20 y - - catch {r zmscore zmscoretest} e - assert_match {*ERR*wrong*number*arg*} $e - } - - test "ZSET commands don't accept the empty strings as valid score" { - assert_error "*not*float*" {r zadd myzset "" abc} - } - - test "zunionInterDiffGenericCommand at least 1 input key" { - assert_error {*at least 1 input key * 'zunion' command} {r zunion 0 key{t}} - assert_error {*at least 1 input key * 'zunionstore' command} {r zunionstore dst_key{t} 0 key{t}} - assert_error {*at least 1 input key * 'zinter' command} {r zinter 0 key{t}} - assert_error {*at least 1 input key * 'zinterstore' command} {r zinterstore dst_key{t} 0 key{t}} - assert_error {*at least 1 input key * 'zdiff' command} {r zdiff 0 key{t}} - assert_error {*at least 1 input key * 'zdiffstore' command} {r zdiffstore dst_key{t} 0 key{t}} - assert_error {*at least 1 input key * 'zintercard' command} {r zintercard 0 key{t}} - } - - proc stressers {encoding} { - set original_max_entries [lindex [r config get zset-max-ziplist-entries] 1] - set original_max_value [lindex [r config get zset-max-ziplist-value] 1] - if {$encoding == "listpack"} { - # Little extra to allow proper fuzzing in the sorting stresser - r config set zset-max-ziplist-entries 256 - r config set zset-max-ziplist-value 64 - set elements 128 - } elseif {$encoding == "skiplist"} { - r config set zset-max-ziplist-entries 0 - r config set zset-max-ziplist-value 0 - if {$::accurate} {set elements 1000} else {set elements 100} - } else { - puts "Unknown sorted set encoding" - exit - } - - test "ZSCORE - $encoding" { - r del zscoretest - set aux {} - for {set i 0} {$i < $elements} {incr i} { - set score [expr rand()] - lappend aux $score - r zadd zscoretest $score $i - } - - assert_encoding $encoding zscoretest - for {set i 0} {$i < $elements} {incr i} { - # If an IEEE 754 double-precision number is converted to a decimal string with at - # least 17 significant digits (reply of zscore), and then converted back to double-precision representation, - # the final result replied via zscore command must match the original number present on the $aux list. - # Given Tcl is mostly very relaxed about types (everything is a string) we need to use expr to convert a string to float. - assert_equal [expr [lindex $aux $i]] [expr [r zscore zscoretest $i]] - } - } - - test "ZMSCORE - $encoding" { - r del zscoretest - set aux {} - for {set i 0} {$i < $elements} {incr i} { - set score [expr rand()] - lappend aux $score - r zadd zscoretest $score $i - } - - assert_encoding $encoding zscoretest - for {set i 0} {$i < $elements} {incr i} { - # Check above notes on IEEE 754 double-precision comparison - assert_equal [expr [lindex $aux $i]] [expr [r zscore zscoretest $i]] - } - } - - test "ZSCORE after a DEBUG RELOAD - $encoding" { - r del zscoretest - set aux {} - for {set i 0} {$i < $elements} {incr i} { - set score [expr rand()] - lappend aux $score - r zadd zscoretest $score $i - } - - r debug reload - assert_encoding $encoding zscoretest - for {set i 0} {$i < $elements} {incr i} { - # Check above notes on IEEE 754 double-precision comparison - assert_equal [expr [lindex $aux $i]] [expr [r zscore zscoretest $i]] - } - } {} {needs:debug} - - test "ZSET sorting stresser - $encoding" { - set delta 0 - for {set test 0} {$test < 2} {incr test} { - unset -nocomplain auxarray - array set auxarray {} - set auxlist {} - r del myzset - for {set i 0} {$i < $elements} {incr i} { - if {$test == 0} { - set score [expr rand()] - } else { - set score [expr int(rand()*10)] - } - set auxarray($i) $score - r zadd myzset $score $i - # Random update - if {[expr rand()] < .2} { - set j [expr int(rand()*1000)] - if {$test == 0} { - set score [expr rand()] - } else { - set score [expr int(rand()*10)] - } - set auxarray($j) $score - r zadd myzset $score $j - } - } - foreach {item score} [array get auxarray] { - lappend auxlist [list $score $item] - } - set sorted [lsort -command zlistAlikeSort $auxlist] - set auxlist {} - foreach x $sorted { - lappend auxlist [lindex $x 1] - } - - assert_encoding $encoding myzset - set fromredis [r zrange myzset 0 -1] - set delta 0 - for {set i 0} {$i < [llength $fromredis]} {incr i} { - if {[lindex $fromredis $i] != [lindex $auxlist $i]} { - incr delta - } - } - } - assert_equal 0 $delta - } - - test "ZRANGEBYSCORE fuzzy test, 100 ranges in $elements element sorted set - $encoding" { - set err {} - r del zset - for {set i 0} {$i < $elements} {incr i} { - r zadd zset [expr rand()] $i - } - - assert_encoding $encoding zset - for {set i 0} {$i < 100} {incr i} { - set min [expr rand()] - set max [expr rand()] - if {$min > $max} { - set aux $min - set min $max - set max $aux - } - set low [r zrangebyscore zset -inf $min] - set ok [r zrangebyscore zset $min $max] - set high [r zrangebyscore zset $max +inf] - set lowx [r zrangebyscore zset -inf ($min] - set okx [r zrangebyscore zset ($min ($max] - set highx [r zrangebyscore zset ($max +inf] - - if {[r zcount zset -inf $min] != [llength $low]} { - append err "Error, len does not match zcount\n" - } - if {[r zcount zset $min $max] != [llength $ok]} { - append err "Error, len does not match zcount\n" - } - if {[r zcount zset $max +inf] != [llength $high]} { - append err "Error, len does not match zcount\n" - } - if {[r zcount zset -inf ($min] != [llength $lowx]} { - append err "Error, len does not match zcount\n" - } - if {[r zcount zset ($min ($max] != [llength $okx]} { - append err "Error, len does not match zcount\n" - } - if {[r zcount zset ($max +inf] != [llength $highx]} { - append err "Error, len does not match zcount\n" - } - - foreach x $low { - set score [r zscore zset $x] - if {$score > $min} { - append err "Error, score for $x is $score > $min\n" - } - } - foreach x $lowx { - set score [r zscore zset $x] - if {$score >= $min} { - append err "Error, score for $x is $score >= $min\n" - } - } - foreach x $ok { - set score [r zscore zset $x] - if {$score < $min || $score > $max} { - append err "Error, score for $x is $score outside $min-$max range\n" - } - } - foreach x $okx { - set score [r zscore zset $x] - if {$score <= $min || $score >= $max} { - append err "Error, score for $x is $score outside $min-$max open range\n" - } - } - foreach x $high { - set score [r zscore zset $x] - if {$score < $max} { - append err "Error, score for $x is $score < $max\n" - } - } - foreach x $highx { - set score [r zscore zset $x] - if {$score <= $max} { - append err "Error, score for $x is $score <= $max\n" - } - } - } - assert_equal {} $err - } - - test "ZRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" { - set lexset {} - r del zset - for {set j 0} {$j < $elements} {incr j} { - set e [randstring 0 30 alpha] - lappend lexset $e - r zadd zset 0 $e - } - set lexset [lsort -unique $lexset] - for {set j 0} {$j < 100} {incr j} { - set min [randstring 0 30 alpha] - set max [randstring 0 30 alpha] - set mininc [randomInt 2] - set maxinc [randomInt 2] - if {$mininc} {set cmin "\[$min"} else {set cmin "($min"} - if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"} - set rev [randomInt 2] - if {$rev} { - set cmd zrevrangebylex - } else { - set cmd zrangebylex - } - - # Make sure data is the same in both sides - assert {[r zrange zset 0 -1] eq $lexset} - - # Get the Redis output - set output [r $cmd zset $cmin $cmax] - if {$rev} { - set outlen [r zlexcount zset $cmax $cmin] - } else { - set outlen [r zlexcount zset $cmin $cmax] - } - - # Compute the same output via Tcl - set o {} - set copy $lexset - if {(!$rev && [string compare $min $max] > 0) || - ($rev && [string compare $max $min] > 0)} { - # Empty output when ranges are inverted. - } else { - if {$rev} { - # Invert the Tcl array using Redis itself. - set copy [r zrevrange zset 0 -1] - # Invert min / max as well - lassign [list $min $max $mininc $maxinc] \ - max min maxinc mininc - } - foreach e $copy { - set mincmp [string compare $e $min] - set maxcmp [string compare $e $max] - if { - ($mininc && $mincmp >= 0 || !$mininc && $mincmp > 0) - && - ($maxinc && $maxcmp <= 0 || !$maxinc && $maxcmp < 0) - } { - lappend o $e - } - } - } - assert {$o eq $output} - assert {$outlen eq [llength $output]} - } - } - - test "ZREMRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" { - set lexset {} - r del zset{t} zsetcopy{t} - for {set j 0} {$j < $elements} {incr j} { - set e [randstring 0 30 alpha] - lappend lexset $e - r zadd zset{t} 0 $e - } - set lexset [lsort -unique $lexset] - for {set j 0} {$j < 100} {incr j} { - # Copy... - r zunionstore zsetcopy{t} 1 zset{t} - set lexsetcopy $lexset - - set min [randstring 0 30 alpha] - set max [randstring 0 30 alpha] - set mininc [randomInt 2] - set maxinc [randomInt 2] - if {$mininc} {set cmin "\[$min"} else {set cmin "($min"} - if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"} - - # Make sure data is the same in both sides - assert {[r zrange zset{t} 0 -1] eq $lexset} - - # Get the range we are going to remove - set torem [r zrangebylex zset{t} $cmin $cmax] - set toremlen [r zlexcount zset{t} $cmin $cmax] - r zremrangebylex zsetcopy{t} $cmin $cmax - set output [r zrange zsetcopy{t} 0 -1] - - # Remove the range with Tcl from the original list - if {$toremlen} { - set first [lsearch -exact $lexsetcopy [lindex $torem 0]] - set last [expr {$first+$toremlen-1}] - set lexsetcopy [lreplace $lexsetcopy $first $last] - } - assert {$lexsetcopy eq $output} - } - } - - test "ZSETs skiplist implementation backlink consistency test - $encoding" { - set diff 0 - for {set j 0} {$j < $elements} {incr j} { - r zadd myzset [expr rand()] "Element-$j" - r zrem myzset "Element-[expr int(rand()*$elements)]" - } - - assert_encoding $encoding myzset - set l1 [r zrange myzset 0 -1] - set l2 [r zrevrange myzset 0 -1] - for {set j 0} {$j < [llength $l1]} {incr j} { - if {[lindex $l1 $j] ne [lindex $l2 end-$j]} { - incr diff - } - } - assert_equal 0 $diff - } - - test "ZSETs ZRANK augmented skip list stress testing - $encoding" { - set err {} - r del myzset - for {set k 0} {$k < 2000} {incr k} { - set i [expr {$k % $elements}] - if {[expr rand()] < .2} { - r zrem myzset $i - } else { - set score [expr rand()] - r zadd myzset $score $i - assert_encoding $encoding myzset - } - - set card [r zcard myzset] - if {$card > 0} { - set index [randomInt $card] - set ele [lindex [r zrange myzset $index $index] 0] - set rank [r zrank myzset $ele] - if {$rank != $index} { - set err "$ele RANK is wrong! ($rank != $index)" - break - } - } - } - assert_equal {} $err - } - - foreach {pop} {BZPOPMIN BZMPOP_MIN} { - test "$pop, ZADD + DEL should not awake blocked client" { - set rd [redis_deferring_client] - r del zset - - bzpop_command $rd $pop zset 0 - wait_for_blocked_client - - r multi - r zadd zset 0 foo - r del zset - r exec - r del zset - r zadd zset 1 bar - - verify_pop_response $pop [$rd read] {zset bar 1} {zset {{bar 1}}} - $rd close - } - - test "$pop, ZADD + DEL + SET should not awake blocked client" { - set rd [redis_deferring_client] - r del zset - - bzpop_command $rd $pop zset 0 - wait_for_blocked_client - - r multi - r zadd zset 0 foo - r del zset - r set zset foo - r exec - r del zset - r zadd zset 1 bar - - verify_pop_response $pop [$rd read] {zset bar 1} {zset {{bar 1}}} - $rd close - } - } - - test {BZPOPMIN unblock but the key is expired and then block again - reprocessing command} { - r flushall - r debug set-active-expire 0 - set rd [redis_deferring_client] - - set start [clock milliseconds] - $rd bzpopmin zset{t} 1 - wait_for_blocked_clients_count 1 - - # The exec will try to awake the blocked client, but the key is expired, - # so the client will be blocked again during the command reprocessing. - r multi - r zadd zset{t} 1 one - r pexpire zset{t} 100 - r debug sleep 0.2 - r exec - - assert_equal {} [$rd read] - set end [clock milliseconds] - - # Before the fix in #13004, this time would have been 1200+ (i.e. more than 1200ms), - # now it should be 1000, but in order to avoid timing issues, we increase the range a bit. - assert_range [expr $end-$start] 1000 1150 - - r debug set-active-expire 1 - $rd close - } {0} {needs:debug} - - test "BZPOPMIN with same key multiple times should work" { - set rd [redis_deferring_client] - r del z1{t} z2{t} - - # Data arriving after the BZPOPMIN. - $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 - wait_for_blocked_client - r zadd z1{t} 0 a - assert_equal [$rd read] {z1{t} a 0} - $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 - wait_for_blocked_client - r zadd z2{t} 1 b - assert_equal [$rd read] {z2{t} b 1} - - # Data already there. - r zadd z1{t} 0 a - r zadd z2{t} 1 b - $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 - assert_equal [$rd read] {z1{t} a 0} - $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 - assert_equal [$rd read] {z2{t} b 1} - $rd close - } - - foreach {pop} {BZPOPMIN BZMPOP_MIN} { - test "MULTI/EXEC is isolated from the point of view of $pop" { - set rd [redis_deferring_client] - r del zset - - bzpop_command $rd $pop zset 0 - wait_for_blocked_client - - r multi - r zadd zset 0 a - r zadd zset 1 b - r zadd zset 2 c - r exec - - verify_pop_response $pop [$rd read] {zset a 0} {zset {{a 0}}} - $rd close - } - - test "$pop with variadic ZADD" { - set rd [redis_deferring_client] - r del zset - if {$::valgrind} {after 100} - bzpop_command $rd $pop zset 0 - wait_for_blocked_client - if {$::valgrind} {after 100} - assert_equal 2 [r zadd zset -1 foo 1 bar] - if {$::valgrind} {after 100} - verify_pop_response $pop [$rd read] {zset foo -1} {zset {{foo -1}}} - assert_equal {bar} [r zrange zset 0 -1] - $rd close - } - - test "$pop with zero timeout should block indefinitely" { - set rd [redis_deferring_client] - r del zset - bzpop_command $rd $pop zset 0 - wait_for_blocked_client - after 1000 - r zadd zset 0 foo - verify_pop_response $pop [$rd read] {zset foo 0} {zset {{foo 0}}} - $rd close - } - } - - r config set zset-max-ziplist-entries $original_max_entries - r config set zset-max-ziplist-value $original_max_value - } - - tags {"slow"} { - stressers listpack - stressers skiplist - } - - test "BZPOP/BZMPOP against wrong type" { - r set foo{t} bar - assert_error "*WRONGTYPE*" {r bzpopmin foo{t} 1} - assert_error "*WRONGTYPE*" {r bzpopmax foo{t} 1} - - assert_error "*WRONGTYPE*" {r bzmpop 1 1 foo{t} min} - assert_error "*WRONGTYPE*" {r bzmpop 1 1 foo{t} max} - assert_error "*WRONGTYPE*" {r bzmpop 1 1 foo{t} min count 10} - - r del foo{t} - r set foo2{t} bar - assert_error "*WRONGTYPE*" {r bzmpop 1 2 foo{t} foo2{t} min} - assert_error "*WRONGTYPE*" {r bzmpop 1 2 foo2{t} foo{t} max count 1} - } - - test "BZMPOP with illegal argument" { - assert_error "ERR wrong number of arguments for 'bzmpop' command" {r bzmpop} - assert_error "ERR wrong number of arguments for 'bzmpop' command" {r bzmpop 0 1} - assert_error "ERR wrong number of arguments for 'bzmpop' command" {r bzmpop 0 1 myzset{t}} - - assert_error "ERR numkeys*" {r bzmpop 1 0 myzset{t} MIN} - assert_error "ERR numkeys*" {r bzmpop 1 a myzset{t} MIN} - assert_error "ERR numkeys*" {r bzmpop 1 -1 myzset{t} MAX} - - assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} bad_where} - assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} MIN bar_arg} - assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} MAX MIN} - assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} COUNT} - assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} MIN COUNT 1 COUNT 2} - assert_error "ERR syntax error*" {r bzmpop 1 2 myzset{t} myzset2{t} bad_arg} - - assert_error "ERR count*" {r bzmpop 1 1 myzset{t} MIN COUNT 0} - assert_error "ERR count*" {r bzmpop 1 1 myzset{t} MAX COUNT a} - assert_error "ERR count*" {r bzmpop 1 1 myzset{t} MIN COUNT -1} - assert_error "ERR count*" {r bzmpop 1 2 myzset{t} myzset2{t} MAX COUNT -1} - } - - test "BZMPOP with multiple blocked clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - set rd4 [redis_deferring_client] - r del myzset{t} myzset2{t} - - $rd1 bzmpop 0 2 myzset{t} myzset2{t} min count 1 - wait_for_blocked_clients_count 1 - $rd2 bzmpop 0 2 myzset{t} myzset2{t} max count 10 - wait_for_blocked_clients_count 2 - $rd3 bzmpop 0 2 myzset{t} myzset2{t} min count 10 - wait_for_blocked_clients_count 3 - $rd4 bzmpop 0 2 myzset{t} myzset2{t} max count 1 - wait_for_blocked_clients_count 4 - - r multi - r zadd myzset{t} 1 a 2 b 3 c 4 d 5 e - r zadd myzset2{t} 1 a 2 b 3 c 4 d 5 e - r exec - - assert_equal {myzset{t} {{a 1}}} [$rd1 read] - assert_equal {myzset{t} {{e 5} {d 4} {c 3} {b 2}}} [$rd2 read] - assert_equal {myzset2{t} {{a 1} {b 2} {c 3} {d 4} {e 5}}} [$rd3 read] - - r zadd myzset2{t} 1 a 2 b 3 c - assert_equal {myzset2{t} {{c 3}}} [$rd4 read] - - r del myzset{t} myzset2{t} - $rd1 close - $rd2 close - $rd3 close - $rd4 close - } - - test "BZMPOP propagate as pop with count command to replica" { - set rd [redis_deferring_client] - set repl [attach_to_replication_stream] - - # BZMPOP without being blocked. - r zadd myzset{t} 1 one 2 two 3 three - r zadd myzset2{t} 4 four 5 five 6 six - r bzmpop 0 1 myzset{t} min - r bzmpop 0 2 myzset{t} myzset2{t} max count 10 - r bzmpop 0 2 myzset{t} myzset2{t} max count 10 - - # BZMPOP that gets blocked. - $rd bzmpop 0 1 myzset{t} min count 1 - wait_for_blocked_client - r zadd myzset{t} 1 one - $rd bzmpop 0 2 myzset{t} myzset2{t} min count 5 - wait_for_blocked_client - r zadd myzset{t} 1 one 2 two 3 three - $rd bzmpop 0 2 myzset{t} myzset2{t} max count 10 - wait_for_blocked_client - r zadd myzset2{t} 4 four 5 five 6 six - - # Released on timeout. - assert_equal {} [r bzmpop 0.01 1 myzset{t} max count 10] - r set foo{t} bar ;# something else to propagate after, so we can make sure the above pop didn't. - - $rd close - - assert_replication_stream $repl { - {select *} - {zadd myzset{t} 1 one 2 two 3 three} - {zadd myzset2{t} 4 four 5 five 6 six} - {zpopmin myzset{t} 1} - {zpopmax myzset{t} 2} - {zpopmax myzset2{t} 3} - {zadd myzset{t} 1 one} - {zpopmin myzset{t} 1} - {zadd myzset{t} 1 one 2 two 3 three} - {zpopmin myzset{t} 3} - {zadd myzset2{t} 4 four 5 five 6 six} - {zpopmax myzset2{t} 3} - {set foo{t} bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test "BZMPOP should not blocks on non key arguments - #10762" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - r del myzset myzset2 myzset3 - - $rd1 bzmpop 0 1 myzset min count 10 - wait_for_blocked_clients_count 1 - $rd2 bzmpop 0 2 myzset2 myzset3 max count 10 - wait_for_blocked_clients_count 2 - - # These non-key keys will not unblock the clients. - r zadd 0 100 timeout_value - r zadd 1 200 numkeys_value - r zadd min 300 min_token - r zadd max 400 max_token - r zadd count 500 count_token - r zadd 10 600 count_value - - r zadd myzset 1 zset - r zadd myzset3 1 zset3 - assert_equal {myzset {{zset 1}}} [$rd1 read] - assert_equal {myzset3 {{zset3 1}}} [$rd2 read] - - $rd1 close - $rd2 close - } {0} {cluster:skip} - - test {ZSET skiplist order consistency when elements are moved} { - set original_max [lindex [r config get zset-max-ziplist-entries] 1] - r config set zset-max-ziplist-entries 0 - for {set times 0} {$times < 10} {incr times} { - r del zset - for {set j 0} {$j < 1000} {incr j} { - r zadd zset [randomInt 50] ele-[randomInt 10] - } - - # Make sure that element ordering is correct - set prev_element {} - set prev_score -1 - foreach {element score} [r zrange zset 0 -1 WITHSCORES] { - # Assert that elements are in increasing ordering - assert { - $prev_score < $score || - ($prev_score == $score && - [string compare $prev_element $element] == -1) - } - set prev_element $element - set prev_score $score - } - } - r config set zset-max-ziplist-entries $original_max - } - - test {ZRANGESTORE basic} { - r flushall - r zadd z1{t} 1 a 2 b 3 c 4 d - set res [r zrangestore z2{t} z1{t} 0 -1] - assert_equal $res 4 - r zrange z2{t} 0 -1 withscores - } {a 1 b 2 c 3 d 4} - - test {ZRANGESTORE RESP3} { - r hello 3 - assert_equal [r zrange z2{t} 0 -1 withscores] {{a 1.0} {b 2.0} {c 3.0} {d 4.0}} - r hello 2 - } - - test {ZRANGESTORE range} { - set res [r zrangestore z2{t} z1{t} 1 2] - assert_equal $res 2 - r zrange z2{t} 0 -1 withscores - } {b 2 c 3} - - test {ZRANGESTORE BYLEX} { - set res [r zrangestore z3{t} z1{t} \[b \[c BYLEX] - assert_equal $res 2 - assert_encoding listpack z3{t} - set res [r zrangestore z2{t} z1{t} \[b \[c BYLEX] - assert_equal $res 2 - r zrange z2{t} 0 -1 withscores - } {b 2 c 3} - - test {ZRANGESTORE BYSCORE} { - set res [r zrangestore z4{t} z1{t} 1 2 BYSCORE] - assert_equal $res 2 - assert_encoding listpack z4{t} - set res [r zrangestore z2{t} z1{t} 1 2 BYSCORE] - assert_equal $res 2 - r zrange z2{t} 0 -1 withscores - } {a 1 b 2} - - test {ZRANGESTORE BYSCORE LIMIT} { - set res [r zrangestore z2{t} z1{t} 0 5 BYSCORE LIMIT 0 2] - assert_equal $res 2 - r zrange z2{t} 0 -1 withscores - } {a 1 b 2} - - test {ZRANGESTORE BYSCORE REV LIMIT} { - set res [r zrangestore z2{t} z1{t} 5 0 BYSCORE REV LIMIT 0 2] - assert_equal $res 2 - r zrange z2{t} 0 -1 withscores - } {c 3 d 4} - - test {ZRANGE BYSCORE REV LIMIT} { - r zrange z1{t} 5 0 BYSCORE REV LIMIT 0 2 WITHSCORES - } {d 4 c 3} - - test {ZRANGESTORE - src key missing} { - set res [r zrangestore z2{t} missing{t} 0 -1] - assert_equal $res 0 - r exists z2{t} - } {0} - - test {ZRANGESTORE - src key wrong type} { - r zadd z2{t} 1 a - r set foo{t} bar - assert_error "*WRONGTYPE*" {r zrangestore z2{t} foo{t} 0 -1} - r zrange z2{t} 0 -1 - } {a} - - test {ZRANGESTORE - empty range} { - set res [r zrangestore z2{t} z1{t} 5 6] - assert_equal $res 0 - r exists z2{t} - } {0} - - test {ZRANGESTORE BYLEX - empty range} { - set res [r zrangestore z2{t} z1{t} \[f \[g BYLEX] - assert_equal $res 0 - r exists z2{t} - } {0} - - test {ZRANGESTORE BYSCORE - empty range} { - set res [r zrangestore z2{t} z1{t} 5 6 BYSCORE] - assert_equal $res 0 - r exists z2{t} - } {0} - - test {ZRANGE BYLEX} { - r zrange z1{t} \[b \[c BYLEX - } {b c} - - test {ZRANGESTORE invalid syntax} { - catch {r zrangestore z2{t} z1{t} 0 -1 limit 1 2} err - assert_match "*syntax*" $err - catch {r zrangestore z2{t} z1{t} 0 -1 WITHSCORES} err - assert_match "*syntax*" $err - } - - test {ZRANGESTORE with zset-max-listpack-entries 0 #10767 case} { - set original_max [lindex [r config get zset-max-listpack-entries] 1] - r config set zset-max-listpack-entries 0 - r del z1{t} z2{t} - r zadd z1{t} 1 a - assert_encoding skiplist z1{t} - assert_equal 1 [r zrangestore z2{t} z1{t} 0 -1] - assert_encoding skiplist z2{t} - r config set zset-max-listpack-entries $original_max - } - - test {ZRANGESTORE with zset-max-listpack-entries 1 dst key should use skiplist encoding} { - set original_max [lindex [r config get zset-max-listpack-entries] 1] - r config set zset-max-listpack-entries 1 - r del z1{t} z2{t} z3{t} - r zadd z1{t} 1 a 2 b - assert_equal 1 [r zrangestore z2{t} z1{t} 0 0] - assert_encoding listpack z2{t} - assert_equal 2 [r zrangestore z3{t} z1{t} 0 1] - assert_encoding skiplist z3{t} - r config set zset-max-listpack-entries $original_max - } - - test {ZRANGE invalid syntax} { - catch {r zrange z1{t} 0 -1 limit 1 2} err - assert_match "*syntax*" $err - catch {r zrange z1{t} 0 -1 BYLEX WITHSCORES} err - assert_match "*syntax*" $err - catch {r zrevrange z1{t} 0 -1 BYSCORE} err - assert_match "*syntax*" $err - catch {r zrangebyscore z1{t} 0 -1 REV} err - assert_match "*syntax*" $err - } - - proc get_keys {l} { - set res {} - foreach {score key} $l { - lappend res $key - } - return $res - } - - # Check whether the zset members belong to the zset - proc check_member {mydict res} { - foreach ele $res { - assert {[dict exists $mydict $ele]} - } - } - - # Check whether the zset members and score belong to the zset - proc check_member_and_score {mydict res} { - foreach {key val} $res { - assert_equal $val [dict get $mydict $key] - } - } - - foreach {type contents} "listpack {1 a 2 b 3 c} skiplist {1 a 2 b 3 [randstring 70 90 alpha]}" { - set original_max_value [lindex [r config get zset-max-ziplist-value] 1] - r config set zset-max-ziplist-value 10 - create_zset myzset $contents - assert_encoding $type myzset - - test "ZRANDMEMBER - $type" { - unset -nocomplain myzset - array set myzset {} - for {set i 0} {$i < 100} {incr i} { - set key [r zrandmember myzset] - set myzset($key) 1 - } - assert_equal [lsort [get_keys $contents]] [lsort [array names myzset]] - } - r config set zset-max-ziplist-value $original_max_value - } - - test "ZRANDMEMBER with RESP3" { - r hello 3 - set res [r zrandmember myzset 3 withscores] - assert_equal [llength $res] 3 - assert_equal [llength [lindex $res 1]] 2 - - set res [r zrandmember myzset 3] - assert_equal [llength $res] 3 - assert_equal [llength [lindex $res 1]] 1 - r hello 2 - } - - test "ZRANDMEMBER count of 0 is handled correctly" { - r zrandmember myzset 0 - } {} - - test "ZRANDMEMBER with against non existing key" { - r zrandmember nonexisting_key 100 - } {} - - test "ZRANDMEMBER count overflow" { - r zadd myzset 0 a - assert_error {*value is out of range*} {r zrandmember myzset -9223372036854770000 withscores} - assert_error {*value is out of range*} {r zrandmember myzset -9223372036854775808 withscores} - assert_error {*value is out of range*} {r zrandmember myzset -9223372036854775808} - } {} - - # Make sure we can distinguish between an empty array and a null response - r readraw 1 - - test "ZRANDMEMBER count of 0 is handled correctly - emptyarray" { - r zrandmember myzset 0 - } {*0} - - test "ZRANDMEMBER with against non existing key - emptyarray" { - r zrandmember nonexisting_key 100 - } {*0} - - r readraw 0 - - foreach {type contents} " - skiplist {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 [randstring 70 90 alpha]} - listpack {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 j} " { - test "ZRANDMEMBER with - $type" { - set original_max_value [lindex [r config get zset-max-ziplist-value] 1] - r config set zset-max-ziplist-value 10 - create_zset myzset $contents - assert_encoding $type myzset - - # create a dict for easy lookup - set mydict [dict create {*}[r zrange myzset 0 -1 withscores]] - - # We'll stress different parts of the code, see the implementation - # of ZRANDMEMBER for more information, but basically there are - # four different code paths. - - # PATH 1: Use negative count. - - # 1) Check that it returns repeated elements with and without values. - # 2) Check that all the elements actually belong to the original zset. - set res [r zrandmember myzset -20] - assert_equal [llength $res] 20 - check_member $mydict $res - - set res [r zrandmember myzset -1001] - assert_equal [llength $res] 1001 - check_member $mydict $res - - # again with WITHSCORES - set res [r zrandmember myzset -20 withscores] - assert_equal [llength $res] 40 - check_member_and_score $mydict $res - - set res [r zrandmember myzset -1001 withscores] - assert_equal [llength $res] 2002 - check_member_and_score $mydict $res - - # Test random uniform distribution - # df = 9, 40 means 0.00001 probability - set res [r zrandmember myzset -1000] - assert_lessthan [chi_square_value $res] 40 - check_member $mydict $res - - # 3) Check that eventually all the elements are returned. - # Use both WITHSCORES and without - unset -nocomplain auxset - set iterations 1000 - while {$iterations != 0} { - incr iterations -1 - if {[expr {$iterations % 2}] == 0} { - set res [r zrandmember myzset -3 withscores] - foreach {key val} $res { - dict append auxset $key $val - } - } else { - set res [r zrandmember myzset -3] - foreach key $res { - dict append auxset $key - } - } - if {[lsort [dict keys $mydict]] eq - [lsort [dict keys $auxset]]} { - break; - } - } - assert {$iterations != 0} - - # PATH 2: positive count (unique behavior) with requested size - # equal or greater than set size. - foreach size {10 20} { - set res [r zrandmember myzset $size] - assert_equal [llength $res] 10 - assert_equal [lsort $res] [lsort [dict keys $mydict]] - check_member $mydict $res - - # again with WITHSCORES - set res [r zrandmember myzset $size withscores] - assert_equal [llength $res] 20 - assert_equal [lsort $res] [lsort $mydict] - check_member_and_score $mydict $res - } - - # PATH 3: Ask almost as elements as there are in the set. - # In this case the implementation will duplicate the original - # set and will remove random elements up to the requested size. - # - # PATH 4: Ask a number of elements definitely smaller than - # the set size. - # - # We can test both the code paths just changing the size but - # using the same code. - foreach size {1 2 8} { - # 1) Check that all the elements actually belong to the - # original set. - set res [r zrandmember myzset $size] - assert_equal [llength $res] $size - check_member $mydict $res - - # again with WITHSCORES - set res [r zrandmember myzset $size withscores] - assert_equal [llength $res] [expr {$size * 2}] - check_member_and_score $mydict $res - - # 2) Check that eventually all the elements are returned. - # Use both WITHSCORES and without - unset -nocomplain auxset - unset -nocomplain allkey - set iterations [expr {1000 / $size}] - set all_ele_return false - while {$iterations != 0} { - incr iterations -1 - if {[expr {$iterations % 2}] == 0} { - set res [r zrandmember myzset $size withscores] - foreach {key value} $res { - dict append auxset $key $value - lappend allkey $key - } - } else { - set res [r zrandmember myzset $size] - foreach key $res { - dict append auxset $key - lappend allkey $key - } - } - if {[lsort [dict keys $mydict]] eq - [lsort [dict keys $auxset]]} { - set all_ele_return true - } - } - assert_equal $all_ele_return true - # df = 9, 40 means 0.00001 probability - assert_lessthan [chi_square_value $allkey] 40 - } - } - r config set zset-max-ziplist-value $original_max_value - } - - test {zset score double range} { - set dblmax 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.00000000000000000 - r del zz - r zadd zz $dblmax dblmax - assert_encoding listpack zz - r zscore zz dblmax - } {1.7976931348623157e+308} - - test {zunionInterDiffGenericCommand acts on SET and ZSET} { - r del set_small{t} set_big{t} zset_small{t} zset_big{t} zset_dest{t} - - foreach set_type {intset listpack hashtable} { - # Restore all default configurations before each round of testing. - r config set set-max-intset-entries 512 - r config set set-max-listpack-entries 128 - r config set zset-max-listpack-entries 128 - - r del set_small{t} set_big{t} - - if {$set_type == "intset"} { - r sadd set_small{t} 1 2 3 - r sadd set_big{t} 1 2 3 4 5 - assert_encoding intset set_small{t} - assert_encoding intset set_big{t} - } elseif {$set_type == "listpack"} { - # Add an "a" and then remove it, make sure the set is listpack encoding. - r sadd set_small{t} a 1 2 3 - r sadd set_big{t} a 1 2 3 4 5 - r srem set_small{t} a - r srem set_big{t} a - assert_encoding listpack set_small{t} - assert_encoding listpack set_big{t} - } elseif {$set_type == "hashtable"} { - r config set set-max-intset-entries 0 - r config set set-max-listpack-entries 0 - r sadd set_small{t} 1 2 3 - r sadd set_big{t} 1 2 3 4 5 - assert_encoding hashtable set_small{t} - assert_encoding hashtable set_big{t} - } - - foreach zset_type {listpack skiplist} { - r del zset_small{t} zset_big{t} - - if {$zset_type == "listpack"} { - r zadd zset_small{t} 1 1 2 2 3 3 - r zadd zset_big{t} 1 1 2 2 3 3 4 4 5 5 - assert_encoding listpack zset_small{t} - assert_encoding listpack zset_big{t} - } elseif {$zset_type == "skiplist"} { - r config set zset-max-listpack-entries 0 - r zadd zset_small{t} 1 1 2 2 3 3 - r zadd zset_big{t} 1 1 2 2 3 3 4 4 5 5 - assert_encoding skiplist zset_small{t} - assert_encoding skiplist zset_big{t} - } - - # Test one key is big and one key is small separately. - # The reason for this is because we will sort the sets from smallest to largest. - # So set one big key and one small key, then the test can cover more code paths. - foreach {small_or_big set_key zset_key} { - small set_small{t} zset_big{t} - big set_big{t} zset_small{t} - } { - # The result of these commands are not related to the order of the keys. - assert_equal {1 2 3 4 5} [lsort [r zunion 2 $set_key $zset_key]] - assert_equal {5} [r zunionstore zset_dest{t} 2 $set_key $zset_key] - assert_equal {1 2 3} [lsort [r zinter 2 $set_key $zset_key]] - assert_equal {3} [r zinterstore zset_dest{t} 2 $set_key $zset_key] - assert_equal {3} [r zintercard 2 $set_key $zset_key] - - # The result of sdiff is related to the order of the keys. - if {$small_or_big == "small"} { - assert_equal {} [r zdiff 2 $set_key $zset_key] - assert_equal {0} [r zdiffstore zset_dest{t} 2 $set_key $zset_key] - } else { - assert_equal {4 5} [lsort [r zdiff 2 $set_key $zset_key]] - assert_equal {2} [r zdiffstore zset_dest{t} 2 $set_key $zset_key] - } - } - } - } - - r config set set-max-intset-entries 512 - r config set set-max-listpack-entries 128 - r config set zset-max-listpack-entries 128 - } - - foreach type {single multiple single_multiple} { - test "ZADD overflows the maximum allowed elements in a listpack - $type" { - r del myzset - - set max_entries 64 - set original_max [lindex [r config get zset-max-listpack-entries] 1] - r config set zset-max-listpack-entries $max_entries - - if {$type == "single"} { - # All are single zadd commands. - for {set i 0} {$i < $max_entries} {incr i} { r zadd myzset $i $i } - } elseif {$type == "multiple"} { - # One zadd command to add all elements. - set args {} - for {set i 0} {$i < $max_entries * 2} {incr i} { lappend args $i } - r zadd myzset {*}$args - } elseif {$type == "single_multiple"} { - # First one zadd adds an element (creates a key) and then one zadd adds all elements. - r zadd myzset 1 1 - set args {} - for {set i 0} {$i < $max_entries * 2} {incr i} { lappend args $i } - r zadd myzset {*}$args - } - - assert_encoding listpack myzset - assert_equal $max_entries [r zcard myzset] - assert_equal 1 [r zadd myzset 1 b] - assert_encoding skiplist myzset - - r config set zset-max-listpack-entries $original_max - } - } -} diff --git a/examples/redis-unstable/tests/unit/violations.tcl b/examples/redis-unstable/tests/unit/violations.tcl deleted file mode 100644 index 783f306..0000000 --- a/examples/redis-unstable/tests/unit/violations.tcl +++ /dev/null @@ -1,103 +0,0 @@ -# One XADD with one huge 5GB field -# Expected to fail resulting in an empty stream -run_solo {violations} { -start_server [list overrides [list save ""] ] { - test {XADD one huge field} { - r config set proto-max-bulk-len 10000000000 ;#10gb - r config set client-query-buffer-limit 10000000000 ;#10gb - r write "*5\r\n\$4\r\nXADD\r\n\$2\r\nS1\r\n\$1\r\n*\r\n" - r write "\$1\r\nA\r\n" - catch { - write_big_bulk 5000000000 ;#5gb - } err - assert_match {*too large*} $err - r xlen S1 - } {0} {large-memory} -} - -# One XADD with one huge (exactly nearly) 4GB field -# This uncovers the overflow in lpEncodeGetType -# Expected to fail resulting in an empty stream -start_server [list overrides [list save ""] ] { - test {XADD one huge field - 1} { - r config set proto-max-bulk-len 10000000000 ;#10gb - r config set client-query-buffer-limit 10000000000 ;#10gb - r write "*5\r\n\$4\r\nXADD\r\n\$2\r\nS1\r\n\$1\r\n*\r\n" - r write "\$1\r\nA\r\n" - catch { - write_big_bulk 4294967295 ;#4gb-1 - } err - assert_match {*too large*} $err - r xlen S1 - } {0} {large-memory} -} - -# Gradually add big stream fields using repeated XADD calls -start_server [list overrides [list save ""] ] { - test {several XADD big fields} { - r config set stream-node-max-bytes 0 - for {set j 0} {$j<10} {incr j} { - r xadd stream * 1 $::str500 2 $::str500 - } - r ping - r xlen stream - } {10} {large-memory} -} - -# Add over 4GB to a single stream listpack (one XADD command) -# Expected to fail resulting in an empty stream -start_server [list overrides [list save ""] ] { - test {single XADD big fields} { - r write "*23\r\n\$4\r\nXADD\r\n\$1\r\nS\r\n\$1\r\n*\r\n" - for {set j 0} {$j<10} {incr j} { - r write "\$1\r\n$j\r\n" - write_big_bulk 500000000 "" yes ;#500mb - } - r flush - catch {r read} err - assert_match {*too large*} $err - r xlen S - } {0} {large-memory} -} - -# Gradually add big hash fields using repeated HSET calls -# This reproduces the overflow in the call to ziplistResize -# Object will be converted to hashtable encoding -start_server [list overrides [list save ""] ] { - r config set hash-max-ziplist-value 1000000000 ;#1gb - test {hash with many big fields} { - for {set j 0} {$j<10} {incr j} { - r hset h $j $::str500 - } - r object encoding h - } {hashtable} {large-memory} -} - -# Add over 4GB to a single hash field (one HSET command) -# Object will be converted to hashtable encoding -start_server [list overrides [list save ""] ] { - test {hash with one huge field} { - catch {r config set hash-max-ziplist-value 10000000000} ;#10gb - r config set proto-max-bulk-len 10000000000 ;#10gb - r config set client-query-buffer-limit 10000000000 ;#10gb - r write "*4\r\n\$4\r\nHSET\r\n\$2\r\nH1\r\n" - r write "\$1\r\nA\r\n" - write_big_bulk 5000000000 ;#5gb - r object encoding H1 - } {hashtable} {large-memory} -} -} ;# run_solo - -# SORT which stores an integer encoded element into a list. -# Just for coverage, no news here. -start_server [list overrides [list save ""] ] { - test {SORT adds integer field to list} { - r set S1 asdf - r set S2 123 ;# integer encoded - assert_encoding "int" S2 - r sadd myset 1 2 - r mset D1 1 D2 2 - r sort myset by D* get S* store mylist - r llen mylist - } {2} {cluster:skip} -} diff --git a/examples/redis-unstable/tests/unit/wait.tcl b/examples/redis-unstable/tests/unit/wait.tcl deleted file mode 100644 index 7cf849b..0000000 --- a/examples/redis-unstable/tests/unit/wait.tcl +++ /dev/null @@ -1,528 +0,0 @@ -source tests/support/cli.tcl - -start_server {tags {"wait network external:skip"}} { -start_server {} { - set slave [srv 0 client] - set slave_host [srv 0 host] - set slave_port [srv 0 port] - set slave_pid [srv 0 pid] - set master [srv -1 client] - set master_host [srv -1 host] - set master_port [srv -1 port] - - test {Setup slave} { - $slave slaveof $master_host $master_port - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - } - - test {WAIT out of range timeout (milliseconds)} { - # Timeout is parsed as milliseconds by getLongLongFromObjectOrReply(). - # Verify we get out of range message if value is behind LLONG_MAX - # (decimal value equals to 0x8000000000000000) - assert_error "*or out of range*" {$master wait 2 9223372036854775808} - - # expected to fail by later overflow condition after addition - # of mstime(). (decimal value equals to 0x7FFFFFFFFFFFFFFF) - assert_error "*timeout is out of range*" {$master wait 2 9223372036854775807} - - assert_error "*timeout is negative*" {$master wait 2 -1} - } - - test {WAIT should acknowledge 1 additional copy of the data} { - $master set foo 0 - $master incr foo - $master incr foo - $master incr foo - assert {[$master wait 1 5000] == 1} - assert {[$slave get foo] == 3} - } - - test {WAIT should not acknowledge 2 additional copies of the data} { - $master incr foo - assert {[$master wait 2 1000] <= 1} - } - - test {WAIT should not acknowledge 1 additional copy if slave is blocked} { - pause_process $slave_pid - $master set foo 0 - $master incr foo - $master incr foo - $master incr foo - assert {[$master wait 1 1000] == 0} - resume_process $slave_pid - assert {[$master wait 1 1000] == 1} - } - - test {WAIT implicitly blocks on client pause since ACKs aren't sent} { - pause_process $slave_pid - $master multi - $master incr foo - $master client pause 10000 write - $master exec - assert {[$master wait 1 1000] == 0} - $master client unpause - resume_process $slave_pid - assert {[$master wait 1 1000] == 1} - } - - test {WAIT replica multiple clients unblock - reuse last result} { - set rd [redis_deferring_client -1] - set rd2 [redis_deferring_client -1] - - pause_process $slave_pid - - $rd incr foo - $rd read - - $rd2 incr foo - $rd2 read - - $rd wait 1 0 - $rd2 wait 1 0 - wait_for_blocked_clients_count 2 100 10 -1 - - resume_process $slave_pid - - assert_equal [$rd read] {1} - assert_equal [$rd2 read] {1} - - $rd ping - assert_equal [$rd read] {PONG} - $rd2 ping - assert_equal [$rd2 read] {PONG} - - $rd close - $rd2 close - } -}} - - -tags {"wait aof network external:skip"} { - start_server {overrides {appendonly {yes} auto-aof-rewrite-percentage {0}}} { - set master [srv 0 client] - - test {WAITAOF local copy before fsync} { - r config set appendfsync no - $master incr foo - assert_equal [$master waitaof 1 0 50] {0 0} ;# exits on timeout - r config set appendfsync everysec - } - - test {WAITAOF local copy everysec} { - $master incr foo - assert_equal [$master waitaof 1 0 0] {1 0} - } - - test {WAITAOF local copy with appendfsync always} { - r config set appendfsync always - $master incr foo - assert_equal [$master waitaof 1 0 0] {1 0} - } - - test {WAITAOF local wait and then stop aof} { - r config set appendfsync no - set rd [redis_deferring_client] - $rd incr foo - $rd read - $rd waitaof 1 0 0 - wait_for_blocked_client - r config set appendonly no ;# this should release the blocked client as an error - assert_error {ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.} {$rd read} - $rd close - } - - test {WAITAOF local on server with aof disabled} { - $master incr foo - assert_error {ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.} {$master waitaof 1 0 0} - } - - test {WAITAOF local client unblock with timeout and error} { - r config set appendonly yes - r config set appendfsync no - set rd [redis_deferring_client] - $rd client id - set client_id [$rd read] - - # Test unblock with timeout - $rd incr foo - $rd read - $rd waitaof 1 0 0 - wait_for_blocked_client - assert_equal 1 [r client unblock $client_id timeout] - - # Test unblock with error - $rd incr foo - $rd read - $rd waitaof 1 0 0 - wait_for_blocked_client - assert_equal 1 [r client unblock $client_id error] - $rd close - } - - test {WAITAOF local if AOFRW was postponed} { - r config set appendfsync everysec - - # turn off AOF - r config set appendonly no - - # create an RDB child that takes a lot of time to run - r set x y - r config set rdb-key-save-delay 100000000 ;# 100 seconds - r bgsave - assert_equal [s rdb_bgsave_in_progress] 1 - - # turn on AOF - r config set appendonly yes - assert_equal [s aof_rewrite_scheduled] 1 - - # create a write command (to increment master_repl_offset) - r set x y - - # reset save_delay and kill RDB child - r config set rdb-key-save-delay 0 - catch {exec kill -9 [get_child_pid 0]} - - # wait for AOF (will unblock after AOFRW finishes) - assert_equal [r waitaof 1 0 10000] {1 0} - - # make sure AOFRW finished - assert_equal [s aof_rewrite_in_progress] 0 - assert_equal [s aof_rewrite_scheduled] 0 - } - - $master config set appendonly yes - waitForBgrewriteaof $master - - start_server {overrides {appendonly {yes} auto-aof-rewrite-percentage {0}}} { - set master_host [srv -1 host] - set master_port [srv -1 port] - set replica [srv 0 client] - set replica_host [srv 0 host] - set replica_port [srv 0 port] - set replica_pid [srv 0 pid] - - # make sure the master always fsyncs first (easier to test) - $master config set appendfsync always - $replica config set appendfsync no - - test {WAITAOF on demoted master gets unblocked with an error} { - set rd [redis_deferring_client] - $rd incr foo - $rd read - $rd waitaof 0 1 0 - wait_for_blocked_client - $replica replicaof $master_host $master_port - assert_error {UNBLOCKED force unblock from blocking operation,*} {$rd read} - $rd close - } - - wait_for_ofs_sync $master $replica - - test {WAITAOF replica copy before fsync} { - $master incr foo - assert_equal [$master waitaof 0 1 50] {1 0} ;# exits on timeout - } - $replica config set appendfsync everysec - - test {WAITAOF replica copy everysec} { - $replica config set appendfsync everysec - waitForBgrewriteaof $replica ;# Make sure there is no AOFRW - - $master incr foo - assert_equal [$master waitaof 0 1 0] {1 1} - } - - test {WAITAOF replica copy everysec with AOFRW} { - $replica config set appendfsync everysec - - # When we trigger an AOFRW, a fsync is triggered when closing the old INCR file, - # so with the everysec, we will skip that second of fsync, and in the next second - # after that, we will eventually do the fsync. - $replica bgrewriteaof - waitForBgrewriteaof $replica - - $master incr foo - assert_equal [$master waitaof 0 1 0] {1 1} - } - - test {WAITAOF replica copy everysec with slow AOFRW} { - $replica config set appendfsync everysec - $replica config set rdb-key-save-delay 1000000 ;# 1 sec - - $replica bgrewriteaof - - $master incr foo - assert_equal [$master waitaof 0 1 0] {1 1} - - $replica config set rdb-key-save-delay 0 - waitForBgrewriteaof $replica - } - - test {WAITAOF replica copy everysec->always with AOFRW} { - $replica config set appendfsync everysec - - # Try to fit all of them in the same round second, although there's no way to guarantee - # that, it can be done on fast machine. In any case, the test shouldn't fail either. - $replica bgrewriteaof - $master incr foo - waitForBgrewriteaof $replica - $replica config set appendfsync always - - assert_equal [$master waitaof 0 1 0] {1 1} - } - - test {WAITAOF replica copy appendfsync always} { - $replica config set appendfsync always - $master incr foo - assert_equal [$master waitaof 0 1 0] {1 1} - $replica config set appendfsync everysec - } - - test {WAITAOF replica copy if replica is blocked} { - pause_process $replica_pid - $master incr foo - assert_equal [$master waitaof 0 1 50] {1 0} ;# exits on timeout - resume_process $replica_pid - assert_equal [$master waitaof 0 1 0] {1 1} - } - - test {WAITAOF replica multiple clients unblock - reuse last result} { - set rd [redis_deferring_client -1] - set rd2 [redis_deferring_client -1] - - pause_process $replica_pid - - $rd incr foo - $rd read - - $rd2 incr foo - $rd2 read - - $rd waitaof 0 1 0 - $rd2 waitaof 0 1 0 - wait_for_blocked_clients_count 2 100 10 -1 - - resume_process $replica_pid - - assert_equal [$rd read] {1 1} - assert_equal [$rd2 read] {1 1} - - $rd ping - assert_equal [$rd read] {PONG} - $rd2 ping - assert_equal [$rd2 read] {PONG} - - $rd close - $rd2 close - } - - test {WAITAOF on promoted replica} { - $replica replicaof no one - $replica incr foo - assert_equal [$replica waitaof 1 0 0] {1 0} - } - - test {WAITAOF master that loses a replica and backlog is dropped} { - $master config set repl-backlog-ttl 1 - after 2000 ;# wait for backlog to expire - $master incr foo - assert_equal [$master waitaof 1 0 0] {1 0} - } - - test {WAITAOF master without backlog, wait is released when the replica finishes full-sync} { - set rd [redis_deferring_client -1] - $rd incr foo - $rd read - $rd waitaof 0 1 0 - wait_for_blocked_client -1 - $replica replicaof $master_host $master_port - assert_equal [$rd read] {1 1} - $rd close - } - - test {WAITAOF master isn't configured to do AOF} { - $master config set appendonly no - $master incr foo - assert_equal [$master waitaof 0 1 0] {0 1} - } - - test {WAITAOF replica isn't configured to do AOF} { - $master config set appendonly yes - waitForBgrewriteaof $master - $replica config set appendonly no - $master incr foo - assert_equal [$master waitaof 1 0 0] {1 0} - } - - test {WAITAOF both local and replica got AOF enabled at runtime} { - $replica config set appendonly yes - waitForBgrewriteaof $replica - $master incr foo - assert_equal [$master waitaof 1 1 0] {1 1} - } - - test {WAITAOF master sends PING after last write} { - $master config set repl-ping-replica-period 1 - $master incr foo - after 1200 ;# wait for PING - $master get foo - assert_equal [$master waitaof 1 1 0] {1 1} - $master config set repl-ping-replica-period 10 - } - - test {WAITAOF master client didn't send any write command} { - $master config set repl-ping-replica-period 1 - set client [redis_client -1] - after 1200 ;# wait for PING - assert_equal [$master waitaof 1 1 0] {1 1} - $client close - $master config set repl-ping-replica-period 10 - } - - test {WAITAOF master client didn't send any command} { - $master config set repl-ping-replica-period 1 - set client [redis [srv -1 "host"] [srv -1 "port"] 0 $::tls] - after 1200 ;# wait for PING - assert_equal [$master waitaof 1 1 0] {1 1} - $client close - $master config set repl-ping-replica-period 10 - } - - foreach fsync {no everysec always} { - test "WAITAOF when replica switches between masters, fsync: $fsync" { - # test a case where a replica is moved from one master to the other - # between two replication streams with different offsets that should - # not be mixed. done to smoke-test race conditions with bio thread. - start_server {overrides {appendonly {yes} auto-aof-rewrite-percentage {0}}} { - start_server {overrides {appendonly {yes} auto-aof-rewrite-percentage {0}}} { - set master2 [srv -1 client] - set master2_host [srv -1 host] - set master2_port [srv -1 port] - set replica2 [srv 0 client] - set replica2_host [srv 0 host] - set replica2_port [srv 0 port] - set replica2_pid [srv 0 pid] - - $replica2 replicaof $master2_host $master2_port - wait_for_ofs_sync $master2 $replica2 - - $master config set appendfsync $fsync - $master2 config set appendfsync $fsync - $replica config set appendfsync $fsync - $replica2 config set appendfsync $fsync - if {$fsync eq "no"} { - after 2000 ;# wait for any previous fsync to finish - # can't afford "no" on the masters - $master config set appendfsync always - $master2 config set appendfsync always - } elseif {$fsync eq "everysec"} { - after 990 ;# hoping to hit a race - } - - # add some writes and block a client on each master - set rd [redis_deferring_client -3] - set rd2 [redis_deferring_client -1] - $rd set boo 11 - $rd2 set boo 22 - $rd read - $rd2 read - $rd waitaof 1 1 0 - $rd2 waitaof 1 1 0 - - if {$fsync eq "no"} { - # since appendfsync is disabled in the replicas, the client - # will get released only with full sync - wait_for_blocked_client -1 - wait_for_blocked_client -3 - } - # switch between the two replicas - $replica2 replicaof $master_host $master_port - $replica replicaof $master2_host $master2_port - assert_equal [$rd read] {1 1} - assert_equal [$rd2 read] {1 1} - $rd close - $rd2 close - - assert_equal [$replica get boo] 22 - assert_equal [$replica2 get boo] 11 - } - } - } - } - } - } -} - -start_server {tags {"failover external:skip"}} { -start_server {} { -start_server {} { - set master [srv 0 client] - set master_host [srv 0 host] - set master_port [srv 0 port] - - set replica1 [srv -1 client] - set replica1_pid [srv -1 pid] - - set replica2 [srv -2 client] - - test {setup replication for following tests} { - $replica1 replicaof $master_host $master_port - $replica2 replicaof $master_host $master_port - wait_for_sync $replica1 - wait_for_sync $replica2 - } - - test {WAIT and WAITAOF replica multiple clients unblock - reuse last result} { - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - - $master config set appendonly yes - $replica1 config set appendonly yes - $replica2 config set appendonly yes - - $master config set appendfsync always - $replica1 config set appendfsync no - $replica2 config set appendfsync no - - waitForBgrewriteaof $master - waitForBgrewriteaof $replica1 - waitForBgrewriteaof $replica2 - - pause_process $replica1_pid - - $rd incr foo - $rd read - $rd waitaof 0 1 0 - - # rd2 has a newer repl_offset - $rd2 incr foo - $rd2 read - $rd2 wait 2 0 - - wait_for_blocked_clients_count 2 - - resume_process $replica1_pid - - # WAIT will unblock the client first. - assert_equal [$rd2 read] {2} - - # Make $replica1 catch up the repl_aof_off, then WAITAOF will unblock the client. - $replica1 config set appendfsync always - $master incr foo - assert_equal [$rd read] {1 1} - - $rd ping - assert_equal [$rd read] {PONG} - $rd2 ping - assert_equal [$rd2 read] {PONG} - - $rd close - $rd2 close - } -} -} -} -- cgit v1.2.3