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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
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}
}
|