summaryrefslogtreecommitdiffstats
path: root/tests/unit/type
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:40:54 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:40:54 +0000
commit317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch)
treec417b3d25c86b775989cb5ac042f37611b626c8a /tests/unit/type
parentInitial commit. (diff)
downloadredis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz
redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip
Adding upstream version 5:7.2.4.upstream/5%7.2.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/unit/type')
-rw-r--r--tests/unit/type/hash.tcl846
-rw-r--r--tests/unit/type/incr.tcl214
-rw-r--r--tests/unit/type/list-2.tcl47
-rw-r--r--tests/unit/type/list-3.tcl232
-rw-r--r--tests/unit/type/list-common.tcl4
-rw-r--r--tests/unit/type/list.tcl2363
-rw-r--r--tests/unit/type/set.tcl1305
-rw-r--r--tests/unit/type/stream-cgroups.tcl1297
-rw-r--r--tests/unit/type/stream.tcl940
-rw-r--r--tests/unit/type/string.tcl674
-rw-r--r--tests/unit/type/zset.tcl2654
11 files changed, 10576 insertions, 0 deletions
diff --git a/tests/unit/type/hash.tcl b/tests/unit/type/hash.tcl
new file mode 100644
index 0000000..2a26f44
--- /dev/null
+++ b/tests/unit/type/hash.tcl
@@ -0,0 +1,846 @@
+start_server {tags {"hash"}} {
+ 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 objectComputeSize
+ 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 <count> 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 <count> 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 <count> - $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}
+ }
+
+ 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 {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 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 {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}
+
+ # 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/tests/unit/type/incr.tcl b/tests/unit/type/incr.tcl
new file mode 100644
index 0000000..2319b2c
--- /dev/null
+++ b/tests/unit/type/incr.tcl
@@ -0,0 +1,214 @@
+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 uses shared objects in the 0-9999 range} {
+ r set foo -1
+ r incr foo
+ assert_refcount_morethan foo 1
+ r set foo 9998
+ r incr foo
+ assert_refcount_morethan foo 1
+ 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}
+
+ 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}
+
+ 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/tests/unit/type/list-2.tcl b/tests/unit/type/list-2.tcl
new file mode 100644
index 0000000..5874a90
--- /dev/null
+++ b/tests/unit/type/list-2.tcl
@@ -0,0 +1,47 @@
+start_server {
+ tags {"list"}
+ overrides {
+ "list-max-ziplist-size" 4
+ }
+} {
+ source "tests/unit/type/list-common.tcl"
+
+ 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/tests/unit/type/list-3.tcl b/tests/unit/type/list-3.tcl
new file mode 100644
index 0000000..45df593
--- /dev/null
+++ b/tests/unit/type/list-3.tcl
@@ -0,0 +1,232 @@
+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_jRyo<xSJ1oObDYd`ppZuW6P@fS278YaOx=s6lvdFlMbP0[SbkI^Kr\HBXtuFaA^mDx:yzS4a[skiiPWhT<nNfAf=aQVfclcuwDrfe;iVuKdNvB9kbfq>tK?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/tests/unit/type/list-common.tcl b/tests/unit/type/list-common.tcl
new file mode 100644
index 0000000..b393737
--- /dev/null
+++ b/tests/unit/type/list-common.tcl
@@ -0,0 +1,4 @@
+# We need a value to make sure the list has the right encoding when it is inserted.
+array set largevalue {}
+set largevalue(listpack) "hello"
+set largevalue(quicklist) [string repeat "x" 8192]
diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl
new file mode 100644
index 0000000..993b6d1
--- /dev/null
+++ b/tests/unit/type/list.tcl
@@ -0,0 +1,2363 @@
+# check functionality compression of plain and zipped 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 regular ziplist nodes
+ # 1. using push + insert
+ # 2. using push + insert + trim
+ # 3. using push + insert + set
+
+ test {reg node check compression with insert and pop} {
+ r lpush list1 [string repeat a 500]
+ r lpush list1 [string repeat b 500]
+ r lpush list1 [string repeat c 500]
+ r lpush list1 [string repeat d 500]
+ r linsert list1 after [string repeat d 500] [string repeat e 500]
+ r linsert list1 after [string repeat d 500] [string repeat f 500]
+ r linsert list1 after [string repeat d 500] [string repeat g 500]
+ r linsert list1 after [string repeat d 500] [string repeat j 500]
+ assert_equal [r lpop list1] [string repeat d 500]
+ assert_equal [r lpop list1] [string repeat j 500]
+ assert_equal [r lpop list1] [string repeat g 500]
+ assert_equal [r lpop list1] [string repeat f 500]
+ assert_equal [r lpop list1] [string repeat e 500]
+ assert_equal [r lpop list1] [string repeat c 500]
+ assert_equal [r lpop list1] [string repeat b 500]
+ assert_equal [r lpop list1] [string repeat a 500]
+ };
+
+ test {reg node check compression combined with trim} {
+ r lpush list2 [string repeat a 500]
+ r linsert list2 after [string repeat a 500] [string repeat b 500]
+ r rpush list2 [string repeat c 500]
+ assert_equal [string repeat b 500] [r lindex list2 1]
+ r LTRIM list2 1 -1
+ r llen list2
+ } {2}
+
+ test {reg node check compression with lset} {
+ r lpush list3 [string repeat a 500]
+ r LSET list3 0 [string repeat b 500]
+ assert_equal [string repeat b 500] [r lindex list3 0]
+ r lpush list3 [string repeat c 500]
+ r LSET list3 0 [string repeat d 500]
+ assert_equal [string repeat d 500] [r lindex list3 0]
+ }
+
+ # repeating the 3 tests with plain nodes
+ # (by adjusting quicklist-packed-threshold)
+
+ test {plain node check compression} {
+ r debug quicklist-packed-threshold 1b
+ r lpush list4 [string repeat a 500]
+ r lpush list4 [string repeat b 500]
+ r lpush list4 [string repeat c 500]
+ r lpush list4 [string repeat d 500]
+ r linsert list4 after [string repeat d 500] [string repeat e 500]
+ r linsert list4 after [string repeat d 500] [string repeat f 500]
+ r linsert list4 after [string repeat d 500] [string repeat g 500]
+ r linsert list4 after [string repeat d 500] [string repeat j 500]
+ assert_equal [r lpop list4] [string repeat d 500]
+ assert_equal [r lpop list4] [string repeat j 500]
+ assert_equal [r lpop list4] [string repeat g 500]
+ assert_equal [r lpop list4] [string repeat f 500]
+ assert_equal [r lpop list4] [string repeat e 500]
+ assert_equal [r lpop list4] [string repeat c 500]
+ assert_equal [r lpop list4] [string repeat b 500]
+ assert_equal [r lpop list4] [string repeat a 500]
+ r debug quicklist-packed-threshold 0
+ } {OK} {needs:debug}
+
+ test {plain node check compression with ltrim} {
+ r debug quicklist-packed-threshold 1b
+ r lpush list5 [string repeat a 500]
+ r linsert list5 after [string repeat a 500] [string repeat b 500]
+ r rpush list5 [string repeat c 500]
+ assert_equal [string repeat b 500] [r lindex list5 1]
+ r LTRIM list5 1 -1
+ assert_equal [r llen list5] 2
+ r debug quicklist-packed-threshold 0
+ } {OK} {needs:debug}
+
+ test {plain node check compression using lset} {
+ r debug quicklist-packed-threshold 1b
+ r lpush list6 [string repeat a 500]
+ r LSET list6 0 [string repeat b 500]
+ assert_equal [string repeat b 500] [r lindex list6 0]
+ r lpush list6 [string repeat c 500]
+ r LSET list6 0 [string repeat d 500]
+ assert_equal [string repeat d 500] [r lindex list6 0]
+ r debug quicklist-packed-threshold 0
+ } {OK} {needs:debug}
+
+ # 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 ""] ] {
+ # 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
+ 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
+ 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
+ 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
+ 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"
+ 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"
+ 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"
+ 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} {
+ 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"
+ 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
+ } {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
+
+ # 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"
+ 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 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
+ }
+} {
+ source "tests/unit/type/list-common.tcl"
+
+ # 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 <count> 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
+ # 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 expiered 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 expiered 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}
+
+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 objectComputeSize
+ 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}
+
+} ;# stop servers
diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl
new file mode 100644
index 0000000..2927562
--- /dev/null
+++ b/tests/unit/type/set.tcl
@@ -0,0 +1,1305 @@
+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 <count>=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 <count> - $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 <count> 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 <count> 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 <count> - $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
+ }
+
+ 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
+ assert {[is_rehashing myset]}
+
+ # 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}
+
+ 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/tests/unit/type/stream-cgroups.tcl b/tests/unit/type/stream-cgroups.tcl
new file mode 100644
index 0000000..a6cc5da
--- /dev/null
+++ b/tests/unit/type/stream-cgroups.tcl
@@ -0,0 +1,1297 @@
+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 will return only new elements} {
+ r XADD mystream * a 1
+ r XADD mystream * b 2
+ # 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 list - 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 {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 {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] 18
+ 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] 18
+ 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}
+ }
+
+ 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 {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 (autclaim=$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
+ }
+ }
+ }
+ }
+
+ 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
+ }
+ }
+}
diff --git a/tests/unit/type/stream.tcl b/tests/unit/type/stream.tcl
new file mode 100644
index 0000000..3081c40
--- /dev/null
+++ b/tests/unit/type/stream.tcl
@@ -0,0 +1,940 @@
+# 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 {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: 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}
+ }
+}
+
+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
+ }
+}
diff --git a/tests/unit/type/string.tcl b/tests/unit/type/string.tcl
new file mode 100644
index 0000000..94702ec
--- /dev/null
+++ b/tests/unit/type/string.tcl
@@ -0,0 +1,674 @@
+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]
+ }
+
+ 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 "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]
+ }
+
+ 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]
+ }
+
+ 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}
+}
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
new file mode 100644
index 0000000..33427d8
--- /dev/null
+++ b/tests/unit/type/zset.tcl
@@ -0,0 +1,2654 @@
+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 "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}
+ }
+
+ 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 {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]
+ }
+
+ 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}
+ }
+
+ 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 "ZRANGEBYSLEX 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 {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]
+ }
+
+ 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 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 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 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 <count> 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 <count> 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 <count> - $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
+ }
+ }
+}