diff options
Diffstat (limited to 'examples/redis-unstable/tests/unit/pubsub.tcl')
| -rw-r--r-- | examples/redis-unstable/tests/unit/pubsub.tcl | 1016 |
1 files changed, 1016 insertions, 0 deletions
diff --git a/examples/redis-unstable/tests/unit/pubsub.tcl b/examples/redis-unstable/tests/unit/pubsub.tcl new file mode 100644 index 0000000..24f779f --- /dev/null +++ b/examples/redis-unstable/tests/unit/pubsub.tcl @@ -0,0 +1,1016 @@ +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} + +} |
