From 8be47edab99cc9b71332d7f7d77375857f923bbc Mon Sep 17 00:00:00 2001 From: Mixficsol <838844609@qq.com> Date: Thu, 11 Apr 2024 17:03:07 +0800 Subject: [PATCH] Added redis latest tcl test --- tests/support/server.tcl | 2 +- tests/test_helper.tcl | 8 +- tests/unit/type/hash.tcl | 489 ++++++- tests/unit/type/list-2.tcl | 11 +- tests/unit/type/list-3.tcl | 161 ++- tests/unit/type/list.tcl | 2683 ++++++++++++++++++++++++++++-------- tests/unit/type/set.tcl | 1138 ++++++++++++--- tests/unit/type/string.tcl | 571 ++++---- tests/unit/type/zset.tcl | 2203 ++++++++++++++++++++++++++--- 9 files changed, 5924 insertions(+), 1342 deletions(-) diff --git a/tests/support/server.tcl b/tests/support/server.tcl index 15eb96113..d6ced093a 100644 --- a/tests/support/server.tcl +++ b/tests/support/server.tcl @@ -142,7 +142,7 @@ proc start_server {options {code undefined}} { dict set srv "port" $::port set client [redis $::host $::port] dict set srv "client" $client - $client select 9 + # $client select 9 # append the server to the stack lappend ::servers $srv diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index a3d803553..3d4dac96c 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -25,9 +25,9 @@ set ::all_tests { # unit/hyperloglog # unit/type # unit/acl - unit/type/list-2 - # unit/type/list-3 - # unit/type/set + # unit/type/list-2 + # unit/type/list-3 + # unit/type/set # unit/type/zset # unit/type/string # unit/type/hash @@ -136,7 +136,7 @@ proc reconnect {args} { # select the right db when we don't have to authenticate if {![dict exists $config "requirepass"]} { - $client select 9 + # $client select 9 } # re-set $srv in the servers list diff --git a/tests/unit/type/hash.tcl b/tests/unit/type/hash.tcl index c526a5790..7fc2d1ca4 100644 --- a/tests/unit/type/hash.tcl +++ b/tests/unit/type/hash.tcl @@ -2,8 +2,8 @@ start_server {tags {"hash"}} { test {HSET/HLEN - Small hash creation} { array set smallhash {} for {set i 0} {$i < 8} {incr i} { - set key [randstring 0 8 alpha] - set val [randstring 0 8 alpha] + 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 @@ -14,16 +14,225 @@ start_server {tags {"hash"}} { list [r hlen smallhash] } {8} -# Pika does not support the debug command -# test {Is the small hash encoded with a ziplist?} { -# assert_encoding ziplist smallhash -# } + 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 against non existing key" { + r hrandfield nonexisting_key 100 + } {} + + # Make sure we can distinguish between an empty array and a null response + r readraw 1 + + test "HRANDFIELD count of 0 is handled correctly - emptyarray" { + r hrandfield myhash 0 + } {*0} + + test "HRANDFIELD with against non existing key - emptyarray" { + r hrandfield nonexisting_key 100 + } {*0} + + r readraw 0 + + foreach {type contents} " + hashtable {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {[randstring 70 90 alpha] 10}} + listpack {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {10 j}} " { + test "HRANDFIELD with - $type" { + set original_max_value [lindex [r config get hash-max-ziplist-value] 1] + r config set hash-max-ziplist-value 10 + create_hash myhash $contents + assert_encoding $type myhash + + # create a dict for easy lookup + set mydict [dict create {*}[r hgetall myhash]] + + # We'll stress different parts of the code, see the implementation + # of HRANDFIELD for more information, but basically there are + # four different code paths. + + # PATH 1: Use negative count. + + # 1) Check that it returns repeated elements with and without values. + set res [r hrandfield myhash -20] + assert_equal [llength $res] 20 + set res [r hrandfield myhash -1001] + assert_equal [llength $res] 1001 + # again with WITHVALUES + set res [r hrandfield myhash -20 withvalues] + assert_equal [llength $res] 40 + set res [r hrandfield myhash -1001 withvalues] + assert_equal [llength $res] 2002 + + # Test random uniform distribution + # df = 9, 40 means 0.00001 probability + set res [r hrandfield myhash -1000] + assert_lessthan [chi_square_value $res] 40 + + # 2) Check that all the elements actually belong to the original hash. + foreach {key val} $res { + assert {[dict exists $mydict $key]} + } + + # 3) Check that eventually all the elements are returned. + # Use both WITHVALUES and without + unset -nocomplain auxset + set iterations 1000 + while {$iterations != 0} { + incr iterations -1 + if {[expr {$iterations % 2}] == 0} { + set res [r hrandfield myhash -3 withvalues] + foreach {key val} $res { + dict append auxset $key $val + } + } else { + set res [r hrandfield myhash -3] + foreach key $res { + dict append auxset $key $val + } + } + if {[lsort [dict keys $mydict]] eq + [lsort [dict keys $auxset]]} { + break; + } + } + assert {$iterations != 0} + + # PATH 2: positive count (unique behavior) with requested size + # equal or greater than set size. + foreach size {10 20} { + set res [r hrandfield myhash $size] + assert_equal [llength $res] 10 + assert_equal [lsort $res] [lsort [dict keys $mydict]] + + # again with WITHVALUES + set res [r hrandfield myhash $size withvalues] + assert_equal [llength $res] 20 + assert_equal [lsort $res] [lsort $mydict] + } + + # PATH 3: Ask almost as elements as there are in the set. + # In this case the implementation will duplicate the original + # set and will remove random elements up to the requested size. + # + # PATH 4: Ask a number of elements definitely smaller than + # the set size. + # + # We can test both the code paths just changing the size but + # using the same code. + foreach size {8 2} { + set res [r hrandfield myhash $size] + assert_equal [llength $res] $size + # again with WITHVALUES + set res [r hrandfield myhash $size withvalues] + assert_equal [llength $res] [expr {$size * 2}] + + # 1) Check that all the elements actually belong to the + # original set. + foreach ele [dict keys $res] { + assert {[dict exists $mydict $ele]} + } + + # 2) Check that eventually all the elements are returned. + # Use both WITHVALUES and without + unset -nocomplain auxset + unset -nocomplain allkey + set iterations [expr {1000 / $size}] + set all_ele_return false + while {$iterations != 0} { + incr iterations -1 + if {[expr {$iterations % 2}] == 0} { + set res [r hrandfield myhash $size withvalues] + foreach {key value} $res { + dict append auxset $key $value + lappend allkey $key + } + } else { + set res [r hrandfield myhash $size] + foreach key $res { + dict append auxset $key + lappend allkey $key + } + } + if {[lsort [dict keys $mydict]] eq + [lsort [dict keys $auxset]]} { + set all_ele_return true + } + } + assert_equal $all_ele_return true + # df = 9, 40 means 0.00001 probability + assert_lessthan [chi_square_value $allkey] 40 + } + } + r config set hash-max-ziplist-value $original_max_value + } + test {HSET/HLEN - Big hash creation} { array set bighash {} for {set i 0} {$i < 1024} {incr i} { - set key [randstring 0 8 alpha] - set val [randstring 0 8 alpha] + 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 @@ -34,10 +243,9 @@ start_server {tags {"hash"}} { list [r hlen bighash] } {1024} -# Pika does not support the debug command -# test {Is the big hash encoded with a ziplist?} { -# assert_encoding hashtable bighash -# } + test {Is the big hash encoded with an hash table?} { + assert_encoding hashtable bighash + } test {HGET against the small hash} { set err {} @@ -109,10 +317,10 @@ start_server {tags {"hash"}} { set _ $result } {foo} - test {HMSET wrong number of args} { - catch {r hmset smallhash key1 val1 key2} err - format $err - } {*wrong number*} + 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 {} @@ -142,11 +350,25 @@ start_server {tags {"hash"}} { set _ $rv } {{{} {}} {{} {}} {{} {}}} -# Keys for multiple data types of Pika can be duplicate -# test {HMGET against wrong type} { -# r set wrongtype somevalue -# assert_error "*wrong*" {r hmget wrongtype field1 field2} -# } + test {Hash commands against wrong type} { + r set wrongtype somevalue + assert_error "WRONGTYPE Operation against a key*" {r hmget wrongtype field1 field2} + assert_error "WRONGTYPE Operation against a key*" {r hrandfield wrongtype} + assert_error "WRONGTYPE Operation against a key*" {r hget wrongtype field1} + assert_error "WRONGTYPE Operation against a key*" {r hgetall wrongtype} + assert_error "WRONGTYPE Operation against a key*" {r hdel wrongtype field1} + assert_error "WRONGTYPE Operation against a key*" {r hincrby wrongtype field1 2} + assert_error "WRONGTYPE Operation against a key*" {r hincrbyfloat wrongtype field1 2.5} + assert_error "WRONGTYPE Operation against a key*" {r hstrlen wrongtype field1} + assert_error "WRONGTYPE Operation against a key*" {r hvals wrongtype} + assert_error "WRONGTYPE Operation against a key*" {r hkeys wrongtype} + assert_error "WRONGTYPE Operation against a key*" {r hexists wrongtype field1} + assert_error "WRONGTYPE Operation against a key*" {r hset wrongtype field1 val1} + assert_error "WRONGTYPE Operation against a key*" {r hmset wrongtype field1 val1 field2 val2} + assert_error "WRONGTYPE Operation against a key*" {r hsetnx wrongtype field1 val1} + assert_error "WRONGTYPE Operation against a key*" {r hlen wrongtype} + assert_error "WRONGTYPE Operation against a key*" {r hscan wrongtype 0} + } test {HMGET - small hash} { set keys {} @@ -212,6 +434,11 @@ start_server {tags {"hash"}} { lsort [r hgetall bighash] } [lsort [array get bighash]] + test {HGETALL against non-existing key} { + r del htest + r hgetall htest + } {} + test {HDEL and return value} { set rv {} lappend rv [r hdel smallhash nokey] @@ -255,17 +482,23 @@ start_server {tags {"hash"}} { lappend rv [r hexists bighash nokey] } {1 0 1 0} -# Pika does not support the debug command -# test {Is a ziplist encoded Hash promoted on big payload?} { -# r hset smallhash foo [string repeat a 1024] -# r debug object smallhash -# } {*hashtable*} + test {Is a ziplist encoded Hash promoted on big payload?} { + r hset smallhash foo [string repeat a 1024] + r debug object smallhash + } {*hashtable*} {needs:debug} test {HINCRBY against non existing database key} { r del htest list [r hincrby htest foo 2] } {2} + test {HINCRBY HINCRBYFLOAT against non-integer increment value} { + r del incrhash + r hset incrhash field 5 + assert_error "*value is not an integer*" {r hincrby incrhash field v} + assert_error "*value is not a*" {r hincrbyfloat incrhash field v} + } + test {HINCRBY against non existing hash key} { set rv {} r hdel smallhash tmp @@ -306,20 +539,20 @@ start_server {tags {"hash"}} { r hset smallhash str " 11" r hset bighash str " 11" catch {r hincrby smallhash str 1} smallerr - catch {r hincrby smallhash str 1} bigerr + 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] + 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 smallhash str 1} bigerr + 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] + 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} { @@ -378,22 +611,102 @@ start_server {tags {"hash"}} { r hset smallhash str " 11" r hset bighash str " 11" catch {r hincrbyfloat smallhash str 1} smallerr - catch {r hincrbyfloat smallhash str 1} bigerr + 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] + 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 smallhash str 1} bigerr + 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] + 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 @@ -461,16 +774,90 @@ start_server {tags {"hash"}} { } } -# Pika does not support the debug command -# The hash-max-ziplist-entries parameter is not available in Pika -# 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 {[r object encoding myhash] eq {hashtable}} -# } -# } -} + 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] + } + } +} \ No newline at end of file diff --git a/tests/unit/type/list-2.tcl b/tests/unit/type/list-2.tcl index bf6a055eb..b54bdc85a 100644 --- a/tests/unit/type/list-2.tcl +++ b/tests/unit/type/list-2.tcl @@ -1,8 +1,7 @@ start_server { tags {"list"} overrides { - "list-max-ziplist-value" 16 - "list-max-ziplist-entries" 256 + "list-max-ziplist-size" 4 } } { source "tests/unit/type/list-common.tcl" @@ -28,17 +27,21 @@ start_server { 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] + 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" } } } } } -} +} \ No newline at end of file diff --git a/tests/unit/type/list-3.tcl b/tests/unit/type/list-3.tcl index 94f9a0b79..eba209f9f 100644 --- a/tests/unit/type/list-3.tcl +++ b/tests/unit/type/list-3.tcl @@ -1,8 +1,43 @@ +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} + tags {"list ziplist"} overrides { - "list-max-ziplist-value" 200000 - "list-max-ziplist-entries" 256 + "list-max-ziplist-size" 16 } } { test {Explicit regression for a list bug} { @@ -14,6 +49,124 @@ start_server { 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} @@ -76,4 +229,4 @@ start_server { } } } -} +} \ No newline at end of file diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl index 9cad80096..8e2048e8d 100644 --- a/tests/unit/type/list.tcl +++ b/tests/unit/type/list.tcl @@ -1,73 +1,633 @@ +# check functionality compression of plain and packed nodes +start_server [list overrides [list save ""] ] { + r config set list-compress-depth 2 + r config set list-max-ziplist-size 1 + + # 3 test to check compression with plain and packed nodes + # 1. using push + insert + # 2. using push + insert + trim + # 3. using push + insert + set + + foreach {container size} {packed 500 plain 8193} { + test "$container node check compression with insert and pop" { + r flushdb + r lpush list1 [string repeat a $size] + r lpush list1 [string repeat b $size] + r lpush list1 [string repeat c $size] + r lpush list1 [string repeat d $size] + r linsert list1 after [string repeat d $size] [string repeat e $size] + r linsert list1 after [string repeat d $size] [string repeat f $size] + r linsert list1 after [string repeat d $size] [string repeat g $size] + r linsert list1 after [string repeat d $size] [string repeat j $size] + assert_equal [r lpop list1] [string repeat d $size] + assert_equal [r lpop list1] [string repeat j $size] + assert_equal [r lpop list1] [string repeat g $size] + assert_equal [r lpop list1] [string repeat f $size] + assert_equal [r lpop list1] [string repeat e $size] + assert_equal [r lpop list1] [string repeat c $size] + assert_equal [r lpop list1] [string repeat b $size] + assert_equal [r lpop list1] [string repeat a $size] + }; + + test "$container node check compression combined with trim" { + r flushdb + r lpush list2 [string repeat a $size] + r linsert list2 after [string repeat a $size] [string repeat b $size] + r rpush list2 [string repeat c $size] + assert_equal [string repeat b $size] [r lindex list2 1] + r LTRIM list2 1 -1 + r llen list2 + } {2} + + test "$container node check compression with lset" { + r flushdb + r lpush list3 [string repeat a $size] + r LSET list3 0 [string repeat b $size] + assert_equal [string repeat b $size] [r lindex list3 0] + r lpush list3 [string repeat c $size] + r LSET list3 0 [string repeat d $size] + assert_equal [string repeat d $size] [r lindex list3 0] + } + } ;# foreach + + # revert config for external mode tests. + r config set list-compress-depth 0 +} + +# check functionality of plain nodes using low packed-threshold +start_server [list overrides [list save ""] ] { +foreach type {listpack quicklist} { + if {$type eq "listpack"} { + r config set list-max-listpack-size -2 + } else { + r config set list-max-listpack-size 1 + } + + # basic command check for plain nodes - "LPUSH & LPOP" + test {Test LPUSH and LPOP on plain nodes} { + r flushdb + r debug quicklist-packed-threshold 1b + r lpush lst 9 + r lpush lst xxxxxxxxxx + r lpush lst xxxxxxxxxx + assert_encoding $type lst + set s0 [s used_memory] + assert {$s0 > 10} + assert {[r llen lst] == 3} + set s0 [r rpop lst] + set s1 [r rpop lst] + assert {$s0 eq "9"} + assert {[r llen lst] == 1} + r lpop lst + assert {[string length $s1] == 10} + # check rdb + r lpush lst xxxxxxxxxx + r lpush lst bb + r debug reload + assert_equal [r rpop lst] "xxxxxxxxxx" + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # basic command check for plain nodes - "LINDEX & LINSERT" + test {Test LINDEX and LINSERT on plain nodes} { + r flushdb + r debug quicklist-packed-threshold 1b + r lpush lst xxxxxxxxxxx + r lpush lst 9 + r lpush lst xxxxxxxxxxx + assert_encoding $type lst + r linsert lst before "9" "8" + assert {[r lindex lst 1] eq "8"} + r linsert lst BEFORE "9" "7" + r linsert lst BEFORE "9" "xxxxxxxxxxx" + assert {[r lindex lst 3] eq "xxxxxxxxxxx"} + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # basic command check for plain nodes - "LTRIM" + test {Test LTRIM on plain nodes} { + r flushdb + r debug quicklist-packed-threshold 1b + r lpush lst1 9 + r lpush lst1 xxxxxxxxxxx + r lpush lst1 9 + assert_encoding $type lst1 + r LTRIM lst1 1 -1 + assert_equal [r llen lst1] 2 + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # basic command check for plain nodes - "LREM" + test {Test LREM on plain nodes} { + r flushdb + r debug quicklist-packed-threshold 1b + r lpush lst one + r lpush lst xxxxxxxxxxx + assert_encoding $type lst + set s0 [s used_memory] + assert {$s0 > 10} + r lpush lst 9 + r LREM lst -2 "one" + assert_equal [r llen lst] 2 + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # basic command check for plain nodes - "LPOS" + test {Test LPOS on plain nodes} { + r flushdb + r debug quicklist-packed-threshold 1b + r RPUSH lst "aa" + r RPUSH lst "bb" + r RPUSH lst "cc" + assert_encoding $type lst + r LSET lst 0 "xxxxxxxxxxx" + assert_equal [r LPOS lst "xxxxxxxxxxx"] 0 + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # basic command check for plain nodes - "LMOVE" + test {Test LMOVE on plain nodes} { + r flushdb + r debug quicklist-packed-threshold 1b + r RPUSH lst2{t} "aa" + r RPUSH lst2{t} "bb" + assert_encoding $type lst2{t} + r LSET lst2{t} 0 xxxxxxxxxxx + r RPUSH lst2{t} "cc" + r RPUSH lst2{t} "dd" + r LMOVE lst2{t} lst{t} RIGHT LEFT + r LMOVE lst2{t} lst{t} LEFT RIGHT + assert_equal [r llen lst{t}] 2 + assert_equal [r llen lst2{t}] 2 + assert_equal [r lpop lst2{t}] "bb" + assert_equal [r lpop lst2{t}] "cc" + assert_equal [r lpop lst{t}] "dd" + assert_equal [r lpop lst{t}] "xxxxxxxxxxx" + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # testing LSET with combinations of node types + # plain->packed , packed->plain, plain->plain, packed->packed + test {Test LSET with packed / plain combinations} { + r debug quicklist-packed-threshold 5b + r RPUSH lst "aa" + r RPUSH lst "bb" + assert_encoding $type lst + r lset lst 0 [string repeat d 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat d 50001] + r RPUSH lst [string repeat f 50001] + r lset lst 0 [string repeat e 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat e 50001] + r RPUSH lst [string repeat m 50001] + r lset lst 0 "bb" + set s1 [r lpop lst] + assert_equal $s1 "bb" + r RPUSH lst "bb" + r lset lst 0 "cc" + set s1 [r lpop lst] + assert_equal $s1 "cc" + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} + + # checking LSET in case ziplist needs to be split + test {Test LSET with packed is split in the middle} { + set original_config [config_get_set list-max-listpack-size 4] + r flushdb + r debug quicklist-packed-threshold 5b + r RPUSH lst "aa" + r RPUSH lst "bb" + r RPUSH lst "cc" + r RPUSH lst "dd" + r RPUSH lst "ee" + assert_encoding quicklist lst + r lset lst 2 [string repeat e 10] + assert_equal [r lpop lst] "aa" + assert_equal [r lpop lst] "bb" + assert_equal [r lpop lst] [string repeat e 10] + assert_equal [r lpop lst] "dd" + assert_equal [r lpop lst] "ee" + r debug quicklist-packed-threshold 0 + r config set list-max-listpack-size $original_config + } {OK} {needs:debug} + + + # repeating "plain check LSET with combinations" + # but now with single item in each ziplist + test {Test LSET with packed consist only one item} { + r flushdb + set original_config [config_get_set list-max-ziplist-size 1] + r debug quicklist-packed-threshold 1b + r RPUSH lst "aa" + r RPUSH lst "bb" + r lset lst 0 [string repeat d 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat d 50001] + r RPUSH lst [string repeat f 50001] + r lset lst 0 [string repeat e 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat e 50001] + r RPUSH lst [string repeat m 50001] + r lset lst 0 "bb" + set s1 [r lpop lst] + assert_equal $s1 "bb" + r RPUSH lst "bb" + r lset lst 0 "cc" + set s1 [r lpop lst] + assert_equal $s1 "cc" + r debug quicklist-packed-threshold 0 + r config set list-max-ziplist-size $original_config + } {OK} {needs:debug} + + test {Crash due to delete entry from a compress quicklist node} { + r flushdb + r debug quicklist-packed-threshold 100b + set original_config [config_get_set list-compress-depth 1] + + set small_ele [string repeat x 32] + set large_ele [string repeat x 100] + + # Push a large element + r RPUSH lst $large_ele + + # Insert two elements and keep them in the same node + r RPUSH lst $small_ele + r RPUSH lst $small_ele + assert_encoding $type lst + + # When setting the position of -1 to a large element, we first insert + # a large element at the end and then delete its previous element. + r LSET lst -1 $large_ele + assert_equal "$large_ele $small_ele $large_ele" [r LRANGE lst 0 -1] + + r debug quicklist-packed-threshold 0 + r config set list-compress-depth $original_config + } {OK} {needs:debug} + + test {Crash due to split quicklist node wrongly} { + r flushdb + r debug quicklist-packed-threshold 10b + + r LPUSH lst "aa" + r LPUSH lst "bb" + assert_encoding $type lst + r LSET lst -2 [string repeat x 10] + r RPOP lst + assert_equal [string repeat x 10] [r LRANGE lst 0 -1] + + r debug quicklist-packed-threshold 0 + } {OK} {needs:debug} +} +} + +run_solo {list-large-memory} { +start_server [list overrides [list save ""] ] { + +# test if the server supports such large configs (avoid 32 bit builds) +catch { + r config set proto-max-bulk-len 10000000000 ;#10gb + r config set client-query-buffer-limit 10000000000 ;#10gb +} +if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { + + set str_length 5000000000 + + # repeating all the plain nodes basic checks with 5gb values + test {Test LPUSH and LPOP on plain nodes over 4GB} { + r flushdb + r lpush lst 9 + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + set s0 [s used_memory] + assert {$s0 > $str_length} + assert {[r llen lst] == 3} + assert_equal [r rpop lst] "9" + assert_equal [read_big_bulk {r rpop lst}] $str_length + assert {[r llen lst] == 1} + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LINDEX and LINSERT on plain nodes over 4GB} { + r flushdb + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r lpush lst 9 + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r linsert lst before "9" "8" + assert_equal [r lindex lst 1] "8" + r LINSERT lst BEFORE "9" "7" + r write "*5\r\n\$7\r\nLINSERT\r\n\$3\r\nlst\r\n\$6\r\nBEFORE\r\n\$3\r\n\"9\"\r\n" + write_big_bulk 10; + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LTRIM on plain nodes over 4GB} { + r flushdb + r lpush lst 9 + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r lpush lst 9 + r LTRIM lst 1 -1 + assert_equal [r llen lst] 2 + assert_equal [r rpop lst] 9 + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LREM on plain nodes over 4GB} { + r flushdb + r lpush lst one + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r lpush lst 9 + r LREM lst -2 "one" + assert_equal [read_big_bulk {r rpop lst}] $str_length + r llen lst + } {1} {large-memory} + + test {Test LSET on plain nodes over 4GB} { + r flushdb + r RPUSH lst "aa" + r RPUSH lst "bb" + r RPUSH lst "cc" + r write "*4\r\n\$4\r\nLSET\r\n\$3\r\nlst\r\n\$1\r\n0\r\n" + write_big_bulk $str_length; + assert_equal [r rpop lst] "cc" + assert_equal [r rpop lst] "bb" + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LSET on plain nodes with large elements under packed_threshold over 4GB} { + r flushdb + r rpush lst a b c d e + for {set i 0} {$i < 5} {incr i} { + r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" + write_big_bulk 1000000000 + } + r ping + } {PONG} {large-memory} + + test {Test LSET splits a quicklist node, and then merge} { + # Test when a quicklist node can't be inserted and is split, the split + # node merges with the node before it and the `before` node is kept. + r flushdb + r rpush lst [string repeat "x" 4096] + r lpush lst a b c d e f g + r lpush lst [string repeat "y" 4096] + # now: [y...] [g f e d c b a x...] + # (node0) (node1) + # Keep inserting elements into node1 until node1 is split into two + # nodes([g] [...]), eventually node0 will merge with the [g] node. + # Since node0 is larger, after the merge node0 will be kept and + # the [g] node will be deleted. + for {set i 7} {$i >= 3} {incr i -1} { + r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" + write_big_bulk 1000000000 + } + assert_equal "g" [r lindex lst 1] + r ping + } {PONG} {large-memory} + + test {Test LSET splits a LZF compressed quicklist node, and then merge} { + # Test when a LZF compressed quicklist node can't be inserted and is split, + # the split node merges with the node before it and the split node is kept. + r flushdb + r config set list-compress-depth 1 + r lpush lst [string repeat "x" 2000] + r rpush lst [string repeat "y" 7000] + r rpush lst a b c d e f g + r rpush lst [string repeat "z" 8000] + r lset lst 0 h + # now: [h] [y... a b c d e f g] [z...] + # node0 node1(LZF) + # Keep inserting elements into node1 until node1 is split into two + # nodes([y...] [...]), eventually node0 will merge with the [y...] node. + # Since [y...] node is larger, after the merge node0 will be deleted and + # the [y...] node will be kept. + for {set i 7} {$i >= 3} {incr i -1} { + r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" + write_big_bulk 1000000000 + } + assert_equal "h" [r lindex lst 0] + r config set list-compress-depth 0 + r ping + } {PONG} {large-memory} + + test {Test LMOVE on plain nodes over 4GB} { + r flushdb + r RPUSH lst2{t} "aa" + r RPUSH lst2{t} "bb" + r write "*4\r\n\$4\r\nLSET\r\n\$7\r\nlst2{t}\r\n\$1\r\n0\r\n" + write_big_bulk $str_length; + r RPUSH lst2{t} "cc" + r RPUSH lst2{t} "dd" + r LMOVE lst2{t} lst{t} RIGHT LEFT + assert_equal [read_big_bulk {r LMOVE lst2{t} lst{t} LEFT RIGHT}] $str_length + assert_equal [r llen lst{t}] 2 + assert_equal [r llen lst2{t}] 2 + assert_equal [r lpop lst2{t}] "bb" + assert_equal [r lpop lst2{t}] "cc" + assert_equal [r lpop lst{t}] "dd" + assert_equal [read_big_bulk {r rpop lst{t}}] $str_length + } {} {large-memory} + + # restore defaults + r config set proto-max-bulk-len 536870912 + r config set client-query-buffer-limit 1073741824 + +} ;# skip 32bit builds +} +} ;# run_solo + start_server { tags {"list"} overrides { - "list-max-ziplist-value" 16 - "list-max-ziplist-entries" 256 + "list-max-ziplist-size" -1 } } { source "tests/unit/type/list-common.tcl" -# No cause has been confirmed -# test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} { + # 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 -# assert_equal 1 [r lpush myziplist1 a] -# assert_equal 2 [r rpush myziplist1 b] -# assert_equal 3 [r rpush myziplist1 c] -# assert_equal 3 [r llen myziplist1] -# assert_equal a [r lindex myziplist1 0] -# assert_equal b [r lindex myziplist1 1] -# assert_equal c [r lindex myziplist1 2] -# assert_equal {} [r lindex myziplist2 3] -# assert_equal c [r rpop myziplist1] -# assert_equal a [r lpop myziplist1] -# assert_encoding ziplist myziplist1 + 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 -# assert_equal 1 [r rpush myziplist2 a] -# assert_equal 2 [r lpush myziplist2 b] -# assert_equal 3 [r lpush myziplist2 c] -# assert_equal 3 [r llen myziplist2] -# assert_equal c [r lindex myziplist2 0] -# assert_equal b [r lindex myziplist2 1] -# assert_equal a [r lindex myziplist2 2] -# assert_equal {} [r lindex myziplist2 3] -# assert_equal a [r rpop myziplist2] -# assert_equal c [r lpop myziplist2] -# assert_encoding ziplist myziplist2 -# } - -# No cause has been confirmed -# test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} { - # first lpush then rpush -# assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)] -# assert_encoding linkedlist mylist1 -# assert_equal 2 [r rpush mylist1 b] -# assert_equal 3 [r rpush mylist1 c] -# assert_equal 3 [r llen mylist1] -# assert_equal $largevalue(linkedlist) [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 $largevalue(linkedlist) [r lpop mylist1] - -# # first rpush then lpush -# assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)] -# assert_encoding linkedlist mylist2 -# assert_equal 2 [r lpush mylist2 b] -# assert_equal 3 [r lpush mylist2 c] -# assert_equal 3 [r llen mylist2] -# assert_equal c [r lindex mylist2 0] -# assert_equal b [r lindex mylist2 1] -# assert_equal $largevalue(linkedlist) [r lindex mylist2 2] -# assert_equal {} [r lindex mylist2 3] -# assert_equal $largevalue(linkedlist) [r rpop mylist2] -# assert_equal c [r lpop mylist2] -# } - - test {R/LPOP against empty list} { - r lpop non-existing-list - } {} + r del mylist2 + assert_equal 1 [r rpush mylist2 $large] + assert_equal 2 [r lpush mylist2 b] + assert_equal 3 [r lpush mylist2 c] + assert_encoding $type mylist2 + assert_equal 3 [r llen mylist2] + assert_equal c [r lindex mylist2 0] + assert_equal b [r lindex mylist2 1] + assert_equal $large [r lindex mylist2 2] + assert_equal {} [r lindex mylist2 3] + assert_equal $large [r rpop mylist2] + assert_equal c [r lpop mylist2] + } + + test "LPOP/RPOP with wrong number of arguments" { + assert_error {*wrong number of arguments for 'lpop' command} {r lpop key 1 1} + assert_error {*wrong number of arguments for 'rpop' command} {r rpop key 2 2} + } + + test "RPOP/LPOP with the optional count argument - $type" { + assert_equal 7 [r lpush listcount aa $large cc dd ee ff gg] + assert_equal {gg} [r lpop listcount 1] + assert_equal {ff ee} [r lpop listcount 2] + assert_equal "aa $large" [r rpop listcount 2] + assert_equal {cc} [r rpop listcount 1] + assert_equal {dd} [r rpop listcount 123] + assert_error "*ERR*range*" {r lpop forbarqaz -123} + } +} + + proc verify_resp_response {resp response resp2_response resp3_response} { + if {$resp == 2} { + assert_equal $response $resp2_response + } elseif {$resp == 3} { + assert_equal $response $resp3_response + } + } + + foreach resp {3 2} { + if {[lsearch $::denytags "resp3"] >= 0} { + if {$resp == 3} {continue} + } elseif {$::force_resp3} { + if {$resp == 2} {continue} + } + r hello $resp + + # Make sure we can distinguish between an empty array and a null response + r readraw 1 + + test "LPOP/RPOP with the count 0 returns an empty array in RESP$resp" { + r lpush listcount zero + assert_equal {*0} [r lpop listcount 0] + assert_equal {*0} [r rpop listcount 0] + } + + test "LPOP/RPOP against non existing key in RESP$resp" { + r del non_existing_key + + verify_resp_response $resp [r lpop non_existing_key] {$-1} {_} + verify_resp_response $resp [r rpop non_existing_key] {$-1} {_} + } + + test "LPOP/RPOP with against non existing key in RESP$resp" { + r del non_existing_key + + verify_resp_response $resp [r lpop non_existing_key 0] {*-1} {_} + verify_resp_response $resp [r lpop non_existing_key 1] {*-1} {_} + + verify_resp_response $resp [r rpop non_existing_key 0] {*-1} {_} + verify_resp_response $resp [r rpop non_existing_key 1] {*-1} {_} + } + + r readraw 0 + r hello 2 + } test {Variadic RPUSH/LPUSH} { r del mylist @@ -75,116 +635,160 @@ start_server { 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] } -# No cause has been confirmed -# test {DEL a list - ziplist} { -# assert_equal 1 [r del myziplist2] -# assert_equal 0 [r exists myziplist2] -# assert_equal 0 [r llen myziplist2] -# } - -# No cause has been confirmed -# test {DEL a list - regular list} { -# assert_equal 1 [r del mylist2] -# assert_equal 0 [r exists mylist2] -# assert_equal 0 [r llen mylist2] -# } - - proc create_ziplist {key entries} { - r del $key - foreach entry $entries { r rpush $key $entry } -# assert_encoding ziplist $key - } - proc create_linkedlist {key entries} { - r del $key - foreach entry $entries { r rpush $key $entry } -# assert_encoding linkedlist $key + 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] { - test "BLPOP, BRPOP: single existing list - $type" { + foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop: single existing list - $type" { set rd [redis_deferring_client] create_$type blist "a b $large c d" - $rd blpop blist 1 + bpop_command $rd $pop blist 1 assert_equal {blist a} [$rd read] - $rd brpop blist 1 + if {$pop == "BLPOP"} { + bpop_command $rd BRPOP blist 1 + } else { + bpop_command $rd BLMPOP_RIGHT blist 1 + } assert_equal {blist d} [$rd read] - $rd blpop blist 1 + bpop_command $rd $pop blist 1 assert_equal {blist b} [$rd read] - $rd brpop blist 1 + if {$pop == "BLPOP"} { + bpop_command $rd BRPOP blist 1 + } else { + bpop_command $rd BLMPOP_RIGHT blist 1 + } assert_equal {blist c} [$rd read] - } - -# No cause has been confirmed -# test "BLPOP, BRPOP: multiple existing lists - $type" { -# set rd [redis_deferring_client] -# create_$type blist1 "a $large c" -# create_$type blist2 "d $large f" -# -# $rd blpop blist1 blist2 1 -# assert_equal {blist1 a} [$rd read] -# $rd brpop blist1 blist2 1 -# assert_equal {blist1 c} [$rd read] -# assert_equal 1 [r llen blist1] -# assert_equal 3 [r llen blist2] -# -# $rd blpop blist2 blist1 1 -# assert_equal {blist2 d} [$rd read] -# $rd brpop blist2 blist1 1 -# assert_equal {blist2 f} [$rd read] -# assert_equal 1 [r llen blist1] -# assert_equal 1 [r llen blist2] -# } - - test "BLPOP, BRPOP: second list has an entry - $type" { + + assert_equal 1 [r llen blist] + $rd close + } + + test "$pop: multiple existing lists - $type" { set rd [redis_deferring_client] - r del blist1 - create_$type blist2 "d $large f" - - $rd blpop blist1 blist2 1 - assert_equal {blist2 d} [$rd read] - $rd brpop blist1 blist2 1 - assert_equal {blist2 f} [$rd read] - assert_equal 0 [r llen blist1] - assert_equal 1 [r llen blist2] - } - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH - $type" { -# r del target -# -# set rd [redis_deferring_client] -# create_$type blist "a b $large c d" -# -# $rd brpoplpush blist target 1 -# assert_equal d [$rd read] -# -# assert_equal d [r rpop target] -# assert_equal "a b $large c" [r lrange blist 0 -1] -# } - } - - test "BLPOP, LPUSH + DEL should not awake blocked 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 - $rd blpop list 0 + 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 - $rd read - } {list b} + assert_equal {list b} [$rd read] + $rd close + } - test "BLPOP, LPUSH + DEL + SET should not awake blocked client" { + test "$pop, LPUSH + DEL + SET should not awake blocked client" { set rd [redis_deferring_client] r del list - $rd blpop list 0 + bpop_command $rd $pop list 0 + wait_for_blocked_client + r multi r lpush list a r del list @@ -192,353 +796,647 @@ start_server { r exec r del list r lpush list b - $rd read - } {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 list2 + r del list1{t} list2{t} # Data arriving after the BLPOP. - $rd blpop list1 list2 list2 list1 0 - r lpush list1 a - assert_equal [$rd read] {list1 a} - $rd blpop list1 list2 list2 list1 0 - r lpush list2 b - assert_equal [$rd read] {list2 b} + $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 a - r lpush list2 b - $rd blpop list1 list2 list2 list1 0 - assert_equal [$rd read] {list1 a} - $rd blpop list1 list2 list2 list1 0 - assert_equal [$rd read] {list2 b} + 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 } - test "MULTI/EXEC is isolated from the point of view of BLPOP" { +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 - $rd blpop list 0 + + 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 - $rd read - } {list c} + assert_equal {list c} [$rd read] + $rd close + } - test "BLPOP with variadic LPUSH" { + test "$pop with variadic LPUSH" { set rd [redis_deferring_client] - r del blist target - if {$::valgrind} {after 100} - $rd blpop blist 0 - if {$::valgrind} {after 100} + r del blist + bpop_command $rd $pop blist 0 + wait_for_blocked_client assert_equal 2 [r lpush blist foo bar] - if {$::valgrind} {after 100} 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 } -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH with zero timeout should block indefinitely" { -# set rd [redis_deferring_client] -# r del blist target -# $rd brpoplpush blist target 0 -# after 1000 -# r rpush blist foo -# assert_equal foo [$rd read] -# assert_equal {foo} [r lrange target 0 -1] -# } - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH with a client BLPOPing the target list" { -# set rd [redis_deferring_client] -# set rd2 [redis_deferring_client] -# r del blist target -# $rd2 blpop target 0 -# $rd brpoplpush blist target 0 -# after 1000 -# r rpush blist foo -# assert_equal foo [$rd read] -# assert_equal {target foo} [$rd2 read] -# assert_equal 0 [r exists target] -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH with wrong source type" { -# set rd [redis_deferring_client] -# r del blist target -# r set blist nolist -# $rd brpoplpush blist target 1 -# assert_error "WRONGTYPE*" {$rd read} -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH with wrong destination type" { -# set rd [redis_deferring_client] -# r del blist target -# r set target nolist -# r lpush blist foo -# $rd brpoplpush blist target 1 -# assert_error "WRONGTYPE*" {$rd read} -# -# set rd [redis_deferring_client] -# r del blist target -# r set target nolist -# $rd brpoplpush blist target 0 -# after 1000 -# r rpush blist foo -# assert_error "WRONGTYPE*" {$rd read} -# assert_equal {foo} [r lrange blist 0 -1] -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH maintains order of elements after failure" { -# set rd [redis_deferring_client] -# r del blist target -# r set target nolist -# $rd brpoplpush blist target 0 -# r rpush blist a b c -# assert_error "WRONGTYPE*" {$rd read} -# r lrange blist 0 -1 -# } {a b c} -# - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH with multiple blocked clients" { -# set rd1 [redis_deferring_client] -# set rd2 [redis_deferring_client] -# r del blist target1 target2 -# r set target1 nolist -# $rd1 brpoplpush blist target1 0 -# $rd2 brpoplpush blist target2 0 -# r lpush blist foo -# -# assert_error "WRONGTYPE*" {$rd1 read} -# assert_equal {foo} [$rd2 read] -# assert_equal {foo} [r lrange target2 0 -1] -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "Linked BRPOPLPUSH" { -# set rd1 [redis_deferring_client] -# set rd2 [redis_deferring_client] -# -# r del list1 list2 list3 -# -# $rd1 brpoplpush list1 list2 0 -# $rd2 brpoplpush list2 list3 0 -# -# r rpush list1 foo -# -# assert_equal {} [r lrange list1 0 -1] -# assert_equal {} [r lrange list2 0 -1] -# assert_equal {foo} [r lrange list3 0 -1] -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "Circular BRPOPLPUSH" { -# set rd1 [redis_deferring_client] -# set rd2 [redis_deferring_client] -# -# r del list1 list2 -# -# $rd1 brpoplpush list1 list2 0 -# $rd2 brpoplpush list2 list1 0 -# -# r rpush list1 foo -# -# assert_equal {foo} [r lrange list1 0 -1] -# assert_equal {} [r lrange list2 0 -1] -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "Self-referential BRPOPLPUSH" { -# set rd [redis_deferring_client] -# -# r del blist -# -# $rd brpoplpush blist blist 0 -# -# r rpush blist foo -# -# assert_equal {foo} [r lrange blist 0 -1] -# } -# - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH inside a transaction" { -# r del xlist target -# r lpush xlist foo -# r lpush xlist bar -# -# r multi -# r brpoplpush xlist target 0 -# r brpoplpush xlist target 0 -# r brpoplpush xlist target 0 -# r lrange xlist 0 -1 -# r lrange target 0 -1 -# r exec -# } {foo bar {} {} {bar foo}} -# - -# Pika does not support the BRPOPLPUSH command -# test "PUSH resulting from BRPOPLPUSH affect WATCH" { -# set blocked_client [redis_deferring_client] -# set watching_client [redis_deferring_client] -# r del srclist dstlist somekey -# r set somekey somevalue -# $blocked_client brpoplpush srclist dstlist 0 -# $watching_client watch dstlist -# $watching_client read -# $watching_client multi -# $watching_client read -# $watching_client get somekey -# $watching_client read -# r lpush srclist element -# $watching_client exec -# $watching_client read -# } {} -# - -# Pika does not support the BRPOPLPUSH command -# test "BRPOPLPUSH does not affect WATCH while still blocked" { -# set blocked_client [redis_deferring_client] -# set watching_client [redis_deferring_client] -# r del srclist dstlist somekey -# r set somekey somevalue -# $blocked_client brpoplpush srclist dstlist 0 -# $watching_client watch dstlist -# $watching_client read -# $watching_client multi -# $watching_client read -# $watching_client get somekey -# $watching_client read -# $watching_client exec -# # Blocked BLPOPLPUSH may create problems, unblock it. -# r lpush srclist element -# $watching_client read -# } {somevalue} -# - -# Pika does not support the BRPOPLPUSH command -# test {BRPOPLPUSH timeout} { -# set rd [redis_deferring_client] -# -# $rd brpoplpush foo_list bar_list 1 -# after 2000 -# $rd read -# } {} - -# Pika does not yet support the RENAME command -# test "BLPOP when new key is moved into place" { -# set rd [redis_deferring_client] -# -# $rd blpop foo 5 -# r lpush bob abc def hij -# r rename bob foo -# $rd read -# } {foo hij} -# - -# Pika does not yet support the SORT command -# test "BLPOP 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 -# r lpop foo -# r lpop foo -# -# $rd blpop foo 5 -# r lpush notfoo hello hola aguacate konichiwa zanzibar -# r sort notfoo ALPHA store foo -# $rd read -# } {foo aguacate} -# - foreach {pop} {BLPOP BRPOP} { - test "$pop: with single empty list argument" { - set rd [redis_deferring_client] - r del blist1 - $rd $pop blist1 1 - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - assert_equal 0 [r exists blist1] - } -# No cause has been confirmed -# test "$pop: with negative timeout" { -# set rd [redis_deferring_client] -# $rd $pop blist1 -1 -# assert_error "ERR*is negative*" {$rd read} -# } -# + 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 expired on the replication stream. + $rd set somekey1 someval1 + $rd exists k + r set somekey2 someval2 + + assert_replication_stream $repl { + {select *} + {flushall} + {select 1} + {rpush k hello} + {pexpireat k *} + {swapdb 1 9} + {select 9} + {set somekey1 someval1} + {del k} + {select 1} + {set somekey2 someval2} + } + close_replication_stream $repl + r debug set-active-expire 1 + # Restore server and client state + r select 9 + } {OK} {singledb:skip needs:debug} + + test {MULTI + LPUSH + EXPIRE + DEBUG SLEEP on blocked client, key already expired} { + set repl [attach_to_replication_stream] + r flushall + r debug set-active-expire 0 + + set rd [redis_deferring_client] + $rd client id + set id [$rd read] + $rd brpop k 0 + wait_for_blocked_clients_count 1 + + r multi + r rpush k hello + r pexpire k 100 + r debug sleep 0.2 + r exec + + # The EXEC command tries to awake the blocked client, but it remains + # blocked because the key is expired. Check that the deferred client is + # still blocked. Then unblock it. + assert_match "*flags=b*" [r client list id $id] + r client unblock $id + assert_equal {} [$rd read] + # We want to force key deletion to be propagated to the replica + # in order to verify it was expired on the replication stream. + $rd exists k + assert_equal {0} [$rd read] + assert_replication_stream $repl { + {select *} + {flushall} + {multi} + {rpush k hello} + {pexpireat k *} + {exec} + {del k} + } + close_replication_stream $repl + # Restore server and client state + r debug set-active-expire 1 + r select 9 + } {OK} {singledb:skip needs:debug} + + test {BLPOP unblock but the key is expired and then block again - reprocessing command} { + r flushall + r debug set-active-expire 0 + set rd [redis_deferring_client] + + set start [clock milliseconds] + $rd blpop mylist 1 + wait_for_blocked_clients_count 1 + + # The exec will try to awake the blocked client, but the key is expired, + # so the client will be blocked again during the command reprocessing. + r multi + r rpush mylist a + r pexpire mylist 100 + r debug sleep 0.2 + r exec + + assert_equal {} [$rd read] + set end [clock milliseconds] + + # Before the fix in #13004, this time would have been 1200+ (i.e. more than 1200ms), + # now it should be 1000, but in order to avoid timing issues, we increase the range a bit. + assert_range [expr $end-$start] 1000 1150 + + r debug set-active-expire 1 + $rd close + } {0} {needs:debug} + +foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop when new key is moved into place" { + set rd [redis_deferring_client] + r del foo{t} + + bpop_command $rd $pop foo{t} 0 + wait_for_blocked_client + r lpush bob{t} abc def hij + r rename bob{t} foo{t} + set res [$rd read] + $rd close + set _ $res + } {foo{t} hij} + + test "$pop when result key is created by SORT..STORE" { + set rd [redis_deferring_client] + + # zero out list from previous test without explicit delete + r lpop foo{t} + r lpop foo{t} + r lpop foo{t} + + bpop_command $rd $pop foo{t} 5 + wait_for_blocked_client + r lpush notfoo{t} hello hola aguacate konichiwa zanzibar + r sort notfoo{t} ALPHA store foo{t} + set res [$rd read] + $rd close + set _ $res + } {foo{t} aguacate} +} + + test "BLPOP: timeout value out of range" { + # Timeout is parsed as float and multiplied by 1000, added mstime() + # and stored in long-long which might lead to out-of-range value. + # (Even though given timeout is smaller than LLONG_MAX, the result + # will be bigger) + assert_error "ERR *is out of range*" {r BLPOP blist1 0x7FFFFFFFFFFFFF} + } + + foreach {pop} {BLPOP BRPOP BLMPOP_LEFT BLMPOP_RIGHT} { + test "$pop: with single empty list argument" { + set rd [redis_deferring_client] + r del blist1 + bpop_command $rd $pop blist1 1 + wait_for_blocked_client + r rpush blist1 foo + assert_equal {blist1 foo} [$rd read] + assert_equal 0 [r exists blist1] + $rd close + } + + test "$pop: with negative timeout" { + set rd [redis_deferring_client] + bpop_command $rd $pop blist1 -1 + assert_error "ERR *is negative*" {$rd read} + $rd close + } + test "$pop: with non-integer timeout" { set rd [redis_deferring_client] - $rd $pop blist1 1.1 - assert_error "ERR*not an integer*" {$rd read} + 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] - $rd $pop blist1 0 - after 1000 + bpop_command $rd $pop blist1 0 + wait_for_blocked_client r rpush blist1 foo assert_equal {blist1 foo} [$rd read] + $rd close } -# Keys for multiple data types of Pika can be duplicate -# test "$pop: second argument is not a list" { -# set rd [redis_deferring_client] -# r del blist1 blist2 -# r set blist2 nolist -# $rd $pop blist1 blist2 1 -# assert_error "WRONGTYPE*" {$rd read} -# } -# - test "$pop: timeout" { + 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] - r del blist1 blist2 - $rd $pop blist1 blist2 1 + bpop_command $rd $pop blist1 0.001 + wait_for_blocked_clients_count 0 assert_equal {} [$rd read] + $rd close } - test "$pop: arguments are empty" { + test "$pop: second argument is not a list" { set rd [redis_deferring_client] - r del blist1 blist2 + 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 + } - $rd $pop blist1 blist2 1 - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - assert_equal 0 [r exists blist1] - assert_equal 0 [r exists blist2] + 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 + } - $rd $pop blist1 blist2 1 - r rpush blist2 foo - assert_equal {blist2 foo} [$rd read] - assert_equal 0 [r exists blist1] - assert_equal 0 [r exists blist2] + 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 } } - test {BLPOP inside a transaction} { +foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop inside a transaction" { r del xlist r lpush xlist foo r lpush xlist bar r multi - r blpop xlist 0 - r blpop xlist 0 - r blpop xlist 0 + + 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 @@ -553,25 +1451,27 @@ start_server { create_$type xlist "$large c" assert_equal 3 [r rpushx xlist d] assert_equal 4 [r lpushx xlist a] - assert_equal "a $large c d" [r lrange xlist 0 -1] + 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] - assert_equal "a $large zz c d" [r lrange xlist 0 10] - assert_equal 6 [r linsert xlist after c yy] - assert_equal "a $large zz c yy d" [r lrange xlist 0 10] - assert_equal 7 [r linsert xlist after d dd] - assert_equal -1 [r linsert xlist after bad ddd] - assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] - assert_equal 8 [r linsert xlist before a aa] - assert_equal -1 [r linsert xlist before bad aaa] - assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] + 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] - assert_equal 42 [r lrange xlist 0 0] + assert_equal 9 [r linsert xlist before aa 42] "before aa" + assert_equal 42 [r lrange xlist 0 0] "lrangeE" } } @@ -580,57 +1480,23 @@ start_server { set e } {*ERR*syntax*error*} -# No cause has been confirmed -# test {LPUSHX, RPUSHX convert from ziplist to list} { -# set large $largevalue(linkedlist) -# -# # convert when a large value is pushed -# create_ziplist xlist a -# assert_equal 2 [r rpushx xlist $large] -# assert_encoding linkedlist xlist -# create_ziplist xlist a -# assert_equal 2 [r lpushx xlist $large] -# assert_encoding linkedlist xlist -# -# # convert when the length threshold is exceeded -# create_ziplist xlist [lrepeat 256 a] -# assert_equal 257 [r rpushx xlist b] -# assert_encoding linkedlist xlist -# create_ziplist xlist [lrepeat 256 a] -# assert_equal 257 [r lpushx xlist b] -# assert_encoding linkedlist xlist -# } - -# No cause has been confirmed -# test {LINSERT convert from ziplist to list} { -# set large $largevalue(linkedlist) -# -# # convert when a large value is inserted -# create_ziplist xlist a -# assert_equal 2 [r linsert xlist before a $large] -# assert_encoding linkedlist xlist -# create_ziplist xlist a -# assert_equal 2 [r linsert xlist after a $large] -# assert_encoding linkedlist xlist -# -# # convert when the length threshold is exceeded -# create_ziplist xlist [lrepeat 256 a] -# assert_equal 257 [r linsert xlist before a a] -# assert_encoding linkedlist xlist -# create_ziplist xlist [lrepeat 256 a] -# assert_equal 257 [r linsert xlist after a a] -# assert_encoding linkedlist xlist -# -# # don't convert when the value could not be inserted -# create_ziplist xlist [lrepeat 256 a] -# assert_equal -1 [r linsert xlist before foo a] -# assert_encoding ziplist xlist -# create_ziplist xlist [lrepeat 256 a] -# assert_equal -1 [r linsert xlist after foo a] -# assert_encoding ziplist xlist -# } - - foreach {type num} {ziplist 250 linkedlist 500} { + 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} { @@ -653,149 +1519,247 @@ start_server { for {set i 0} {$i < $num} {incr i} { r rpush mylist $i } -# assert_encoding $type mylist + assert_encoding $type mylist check_numbered_list_consistency mylist } test "LINDEX random access - $type" { -# assert_encoding $type mylist + assert_encoding $type mylist check_random_access_consistency mylist } -# Pika does not support the debug command -# 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 -# } + 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 } +} -# Keys for multiple data types of Pika can be duplicate -# test {LLEN against non-list value error} { -# r del mylist -# r set mylist foobar -# assert_error WRONGTYPE* {r llen mylist} -# } + 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] } -# Currently Redis and Pika are consistent -# test {LINDEX against non-list value error} { -# assert_error WRONGTYPE* {r lindex mylist 0} -# } + 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] } -# Currently Redis and Pika are consistent -# test {LPUSH against non-list value error} { -# assert_error WRONGTYPE* {r lpush mylist 0} -# } + test {LPUSH against non-list value error} { + assert_error WRONGTYPE* {r lpush mylist 0} + } -# Currently Redis and Pika are consistent -# test {RPUSH against non-list value error} { -# assert_error WRONGTYPE* {r rpush 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 mylist2 - create_$type mylist1 "a $large c d" - assert_equal d [r rpoplpush mylist1 mylist2] - assert_equal c [r rpoplpush mylist1 mylist2] - assert_equal "a $large" [r lrange mylist1 0 -1] - assert_equal "c d" [r lrange mylist2 0 -1] -# assert_encoding ziplist mylist2 - } - -# Currently Redis and Pika are consistent -# test "RPOPLPUSH with the same list as src and dst - $type" { -# create_$type mylist "a $large c" -# assert_equal "a $large c" [r lrange mylist 0 -1] -# assert_equal c [r rpoplpush mylist mylist] -# assert_equal "c a $large" [r lrange mylist 0 -1] -# } + 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 "a b c $large" - create_$othertype dstlist "$otherlarge" - assert_equal $large [r rpoplpush srclist dstlist] - assert_equal c [r rpoplpush srclist dstlist] - assert_equal "a b" [r lrange srclist 0 -1] - assert_equal "c $large $otherlarge" [r lrange dstlist 0 -1] + 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 "linkedlist"} { -# assert_encoding linkedlist dstlist -# } + 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 dstlist - assert_equal {} [r rpoplpush srclist dstlist] - assert_equal 0 [r exists srclist] - assert_equal 0 [r exists dstlist] + 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 dstlist - r set srclist x -# assert_error WRONGTYPE* {r rpoplpush srclist dstlist} -# assert_type string srclist - assert_equal 0 [r exists newlist] + 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}] } - test {RPOPLPUSH against non list dst key} { - create_ziplist srclist {a b c d} - r set dstlist x -# assert_error WRONGTYPE* {r rpoplpush srclist dstlist} -# assert_type string dstlist - assert_equal {a b c d} [r lrange srclist 0 -1] +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 dstlist - assert_equal {} [r rpoplpush srclist dstlist] + r del srclist{t} dstlist{t} + assert_equal {} [r rpoplpush srclist{t} dstlist{t}] } {} foreach {type large} [array get largevalue] { - test "Basic LPOP/RPOP - $type" { + 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] - # pop on empty list - assert_equal {} [r lpop mylist] - assert_equal {} [r rpop 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] } } -# Keys for multiple data types of Pika can be duplicate -# test {LPOP/RPOP against non list value} { -# r set notalist foo -# assert_error WRONGTYPE* {r lpop notalist} -# assert_error WRONGTYPE* {r rpop notalist} -# } + 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 {type num} {ziplist 250 linkedlist 500} { + 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 + assert_encoding $type mylist set sum2 0 for {set i 0} {$i < [expr $num/2]} {incr i} { incr sum2 [r lpop mylist] @@ -805,6 +1769,125 @@ start_server { } } + 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" @@ -834,6 +1917,12 @@ start_server { 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 @@ -863,9 +1952,6 @@ start_server { assert_equal {} [trim_list $type 0 -6] } - } - - foreach {type large} [array get largevalue] { test "LSET - $type" { create_$type mylist "99 98 $large 96 95" r lset mylist 1 foo @@ -882,11 +1968,10 @@ start_server { assert_error ERR*key* {r lset nosuchkey 10 foo} } -# Keys for multiple data types of Pika can be duplicate -# test {LSET against non list value} { -# r set nolist foobar -# assert_error WRONGTYPE* {r lset nolist 0 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" { @@ -927,13 +2012,415 @@ start_server { set rd1 [redis_deferring_client] set rd2 [redis_deferring_client] - $rd1 brpoplpush a b 0 - $rd1 brpoplpush a b 0 - $rd2 brpoplpush b c 0 - after 1000 - r lpush a data + $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 \ No newline at end of file diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl index 787c3d3cc..f5bf8e4fc 100644 --- a/tests/unit/type/set.tcl +++ b/tests/unit/type/set.tcl @@ -2,6 +2,8 @@ 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} { @@ -9,56 +11,162 @@ start_server { foreach entry $entries { r sadd $key $entry } } - test {SADD, SCARD, SISMEMBER, SMEMBERS basics - regular set} { - create_set myset {foo} -# assert_encoding hashtable myset - assert_equal 1 [r sadd myset bar] - assert_equal 0 [r sadd myset bar] - assert_equal 2 [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 {bar foo} [lsort [r smembers myset]] + # 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] } - test {SADD, SCARD, SISMEMBER, 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 {16 17} [lsort [r smembers myset]] - } +# 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]] +# } +# } -# Keys for multiple data types of Pika can be duplicate -# test {SADD against non set} { -# r lpush mylist foo -# assert_error WRONGTYPE* {r sadd mylist bar} +# 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 "SADD a non-integer against an intset" { + 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_encoding intset myset assert_equal 1 [r sadd myset a] -# assert_encoding hashtable myset + 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 hashtable myset + 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] } - test "SADD overflows the maximum allowed integers in an intset" { +foreach type {single multiple single_multiple} { + test "SADD overflows the maximum allowed integers in an intset - $type" { r del myset - for {set i 0} {$i < 512} {incr i} { r sadd myset $i } -# assert_encoding intset 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 + 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 @@ -67,33 +175,41 @@ start_server { assert_equal [lsort {A a b c B}] [lsort [r smembers myset]] } -# Pika does not support the debug command -# test "Set encoding after DEBUG RELOAD" { -# r del myintset myhashset mylargeintset -# 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 < 256} {incr i} { r sadd myhashset [format "i%03d" $i] } -# assert_encoding intset myintset -# assert_encoding hashtable mylargeintset -# assert_encoding hashtable myhashset -# -# r debug reload -# assert_encoding intset myintset -# assert_encoding hashtable mylargeintset -# assert_encoding hashtable myhashset -# } + 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 - test {SREM basics - regular set} { - create_set myset {foo bar ciao} -# assert_encoding hashtable myset - assert_equal 0 [r srem myset qux] - assert_equal 1 [r srem myset foo] - assert_equal {bar ciao} [lsort [r smembers myset]] + 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_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]] @@ -113,107 +229,213 @@ start_server { r srem myset 1 2 3 4 5 6 7 8 } {3} - foreach {type} {hashtable intset} { + 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" $i] + r del [format "set%d{t}" $i] } for {set i 0} {$i < 200} {incr i} { - r sadd set1 $i - r sadd set2 [expr $i+195] + r sadd set1{t} $i + r sadd set2{t} [expr $i+195] } foreach i {199 195 1000 2000} { - r sadd set3 $i + r sadd set3{t} $i } for {set i 5} {$i < 200} {incr i} { - r sadd set4 $i + r sadd set4{t} $i } - r sadd set5 0 + 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 "hashtable"} { + if {$type eq "regular"} { set large foo } for {set i 1} {$i <= 5} {incr i} { - r sadd [format "set%d" $i] $large + r sadd [format "set%d{t}" $i] $large } -# test "Generated sets must be encoded as $type" { -# for {set i 1} {$i <= 5} {incr i} { -# assert_encoding $type [format "set%d" $i] -# } -# } + 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 set2]] + 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 set1 set2 -# assert_encoding $type setres - assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]] - } -# Pika does not support the debug command -# test "SINTERSTORE with two sets, after a DEBUG RELOAD - $type" { -# r debug reload -# r sinterstore setres set1 set2 -# assert_encoding $type setres -# assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]] -# } + 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] [r smembers set2]"] - assert_equal $expected [lsort [r sunion set1 set2]] + 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 set1 set2 -# assert_encoding $type setres - set expected [lsort -uniq "[r smembers set1] [r smembers set2]"] - assert_equal $expected [lsort [r smembers setres]] + 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 set2 set3]] + 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 set1 set2 set3 - assert_equal [list 195 199 $large] [lsort [r smembers setres]] + 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] [r smembers set2]"] - assert_equal $expected [lsort [r sunion nokey1 set1 set2 nokey2]] + 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 set4]] + 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 set4 set5]] + assert_equal {1 2 3 4} [lsort [r sdiff set1{t} set4{t} set5{t}]] } test "SDIFFSTORE with three sets - $type" { - r sdiffstore setres set1 set4 set5 + 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 -# } - assert_equal {1 2 3 4} [lsort [r smembers setres]] + 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 set2 set3 - r sadd set2 1 2 3 4 - r sadd set3 a b c d - r sdiff set1 set2 set3 + 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" { @@ -230,11 +452,11 @@ start_server { 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 - lappend args set_$i + r del set_$i{t} + lappend args set_$i{t} while {$num_elements} { set ele [randomValue] - r sadd set_$i $ele + r sadd set_$i{t} $ele if {$i == 0} { set s($ele) x } else { @@ -247,55 +469,238 @@ start_server { assert_equal $result [lsort [array names s]] } } -# Bug need Fix -# test "SINTER against non-set should throw error" { -# r set key1 x -# assert_error "WRONGTYPE*" {r sinter key1 noset} -# } -# Bug need Fix -# test "SUNION against non-set should throw error" { -# r set key1 x -# assert_error "WRONGTYPE*" {r sunion key1 noset} -# } + 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 set2 set3 - r sadd set1 a b c - r sadd set2 b c d - r sinter set1 set2 set3 + 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 set2 - r sadd set1 1 2 3 - r sadd set2 1 2 3 a - r srem set2 a -# assert_encoding intset set1 -# assert_encoding hashtable set2 - lsort [r sinter set1 set2] + 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 set setres xxx - assert_equal 0 [r sinterstore setres foo111 bar222] -# assert_equal 0 [r exists setres] + 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 xxx - assert_equal 0 [r sunionstore setres foo111 bar222] -# assert_equal 0 [r exists setres] + 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} {hashtable {a b c} intset {1 2 3}} { + foreach {type contents} {listpack {a b c} intset {1 2 3}} { test "SPOP basics - $type" { create_set myset $contents -# assert_encoding $type myset + assert_encoding $type myset assert_equal $contents [lsort [list [r spop myset] [r spop myset] [r spop myset]]] assert_equal 0 [r scard myset] } + test "SPOP with =1 - $type" { + create_set myset $contents + assert_encoding $type myset + assert_equal $contents [lsort [list [r spop myset 1] [r spop myset 1] [r spop myset 1]]] + assert_equal 0 [r scard myset] + } + test "SRANDMEMBER - $type" { create_set myset $contents unset -nocomplain myset @@ -307,12 +712,134 @@ start_server { } } + test "SPOP integer from listpack set" { + create_set myset {a 1 2 3 4 5 6 7} + assert_encoding listpack myset + set a [r spop myset] + set b [r spop myset] + assert {[string is digit $a] || [string is digit $b]} + } + + foreach {type contents} { + listpack {a b c d e f g h i j k l m n o p q r s t u v w x y z} + intset {1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23 24 25 26 3 4 5 6 7 8 9} + hashtable {ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 b c d e f g h i j k l m n o p q r s t u v w x y z} + } { + test "SPOP with - $type" { + create_set myset $contents + assert_encoding $type myset + assert_equal $contents [lsort [concat [r spop myset 11] [r spop myset 9] [r spop myset 0] [r spop myset 4] [r spop myset 1] [r spop myset 0] [r spop myset 1] [r spop myset 0]]] + assert_equal 0 [r scard myset] + } + } + + # As seen in intsetRandomMembers + test "SPOP using integers, testing Knuth's and Floyd's algorithm" { + create_set myset {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} + assert_encoding intset myset + assert_equal 20 [r scard myset] + r spop myset 1 + assert_equal 19 [r scard myset] + r spop myset 2 + assert_equal 17 [r scard myset] + r spop myset 3 + assert_equal 14 [r scard myset] + r spop myset 10 + assert_equal 4 [r scard myset] + r spop myset 10 + assert_equal 0 [r scard myset] + r spop myset 1 + assert_equal 0 [r scard myset] + } {} + + test "SPOP using integers with Knuth's algorithm" { + r spop nonexisting_key 100 + } {} + + foreach {type content} { + intset {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} + listpack {a 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20} + } { + test "SPOP new implementation: code path #1 $type" { + create_set myset $content + assert_encoding $type myset + set res [r spop myset 30] + assert {[lsort $content] eq [lsort $res]} + assert_equal {0} [r exists myset] + } + + test "SPOP new implementation: code path #2 $type" { + create_set myset $content + assert_encoding $type myset + set res [r spop myset 2] + assert {[llength $res] == 2} + assert {[r scard myset] == 18} + set union [concat [r smembers myset] $res] + assert {[lsort $union] eq [lsort $content]} + } + + test "SPOP new implementation: code path #3 $type" { + create_set myset $content + assert_encoding $type myset + set res [r spop myset 18] + assert {[llength $res] == 18} + assert {[r scard myset] == 2} + set union [concat [r smembers myset] $res] + assert {[lsort $union] eq [lsort $content]} + } + } + + test "SPOP new implementation: code path #1 propagate as DEL or UNLINK" { + r del myset1{t} myset2{t} + r sadd myset1{t} 1 2 3 4 5 + r sadd myset2{t} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 + + set repl [attach_to_replication_stream] + + r config set lazyfree-lazy-server-del no + r spop myset1{t} [r scard myset1{t}] + r config set lazyfree-lazy-server-del yes + r spop myset2{t} [r scard myset2{t}] + assert_equal {0} [r exists myset1{t} myset2{t}] + + # Verify the propagate of DEL and UNLINK. + assert_replication_stream $repl { + {select *} + {del myset1{t}} + {unlink myset2{t}} + } + + close_replication_stream $repl + } {} {needs:repl} + + test "SRANDMEMBER count of 0 is handled correctly" { + r srandmember myset 0 + } {} + test "SRANDMEMBER with against non existing key" { r srandmember nonexisting_key 100 } {} + test "SRANDMEMBER count overflow" { + r sadd myset a + assert_error {*value is out of range*} {r srandmember myset -9223372036854775808} + } {} + + # Make sure we can distinguish between an empty array and a null response + r readraw 1 + + test "SRANDMEMBER count of 0 is handled correctly - emptyarray" { + r srandmember myset 0 + } {*0} + + test "SRANDMEMBER with against non existing key - emptyarray" { + r srandmember nonexisting_key 100 + } {*0} + + r readraw 0 + foreach {type contents} { - hashtable { + listpack { 1 5 10 50 125 50000 33959417 4775547 65434162 12098459 427716 483706 2726473884 72615637475 MARY PATRICIA LINDA BARBARA ELIZABETH JENNIFER MARIA @@ -327,9 +854,20 @@ start_server { 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 } + hashtable { + ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + 1 5 10 50 125 50000 33959417 4775547 65434162 + 12098459 427716 483706 2726473884 72615637475 + MARY PATRICIA LINDA BARBARA ELIZABETH JENNIFER MARIA + SUSAN MARGARET DOROTHY LISA NANCY KAREN BETTY HELEN + SANDRA DONNA CAROL RUTH SHARON MICHELLE LAURA SARAH + KIMBERLY DEBORAH JESSICA SHIRLEY CYNTHIA ANGELA MELISSA + BRENDA AMY ANNA REBECCA VIRGINIA + } } { test "SRANDMEMBER with - $type" { create_set myset $contents + assert_encoding $type myset unset -nocomplain myset array set myset {} foreach ele [r smembers myset] { @@ -405,7 +943,7 @@ start_server { set iterations 1000 while {$iterations != 0} { incr iterations -1 - set res [r srandmember myset -10] + set res [r srandmember myset $size] foreach ele $res { set auxset($ele) 1 } @@ -419,85 +957,283 @@ start_server { } } + foreach {type contents} { + listpack { + 1 5 10 50 125 + MARY PATRICIA LINDA BARBARA ELIZABETH + } + intset { + 0 1 2 3 4 5 6 7 8 9 + } + hashtable { + ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + 1 5 10 50 125 + MARY PATRICIA LINDA BARBARA + } + } { + test "SRANDMEMBER histogram distribution - $type" { + create_set myset $contents + assert_encoding $type myset + unset -nocomplain myset + array set myset {} + foreach ele [r smembers myset] { + set myset($ele) 1 + } + + # Use negative count (PATH 1). + # df = 9, 40 means 0.00001 probability + set res [r srandmember myset -1000] + assert_lessthan [chi_square_value $res] 40 + + # Use positive count (both PATH 3 and PATH 4). + foreach size {8 2} { + unset -nocomplain allkey + set iterations [expr {1000 / $size}] + while {$iterations != 0} { + incr iterations -1 + set res [r srandmember myset $size] + foreach ele $res { + lappend allkey $ele + } + } + # df = 9, 40 means 0.00001 probability + assert_lessthan [chi_square_value $allkey] 40 + } + } + } + + proc is_rehashing {myset} { + set htstats [r debug HTSTATS-KEY $myset] + return [string match {*rehashing target*} $htstats] + } + + proc rem_hash_set_top_N {myset n} { + set cursor 0 + set members {} + set enough 0 + while 1 { + set res [r sscan $myset $cursor] + set cursor [lindex $res 0] + set k [lindex $res 1] + foreach m $k { + lappend members $m + if {[llength $members] >= $n} { + set enough 1 + break + } + } + if {$enough || $cursor == 0} { + break + } + } + r srem $myset {*}$members + } + + proc verify_rehashing_completed_key {myset table_size keys} { + set htstats [r debug HTSTATS-KEY $myset] + assert {![string match {*rehashing target*} $htstats]} + return {[string match {*table size: $table_size*number of elements: $keys*} $htstats]} + } + + test "SRANDMEMBER with a dict containing long chain" { + set origin_save [config_get_set save ""] + set origin_max_lp [config_get_set set-max-listpack-entries 0] + set origin_save_delay [config_get_set rdb-key-save-delay 2147483647] + + # 1) Create a hash set with 100000 members. + set members {} + for {set i 0} {$i < 100000} {incr i} { + lappend members [format "m:%d" $i] + } + create_set myset $members + + # 2) Wait for the hash set rehashing to finish. + while {[is_rehashing myset]} { + r srandmember myset 100 + } + + # 3) Turn off the rehashing of this set, and remove the members to 500. + r bgsave + rem_hash_set_top_N myset [expr {[r scard myset] - 500}] + assert_equal [r scard myset] 500 + + # 4) Kill RDB child process to restart rehashing. + set pid1 [get_child_pid 0] + catch {exec kill -9 $pid1} + waitForBgsave r + + # 5) Let the set hash to start rehashing + r spop myset 1 + assert [is_rehashing myset] + + # 6) Verify that when rdb saving is in progress, rehashing will still be performed (because + # the ratio is extreme) by waiting for it to finish during an active bgsave. + r bgsave + + while {[is_rehashing myset]} { + r srandmember myset 1 + } + if {$::verbose} { + puts [r debug HTSTATS-KEY myset full] + } + + set pid1 [get_child_pid 0] + catch {exec kill -9 $pid1} + waitForBgsave r + + # 7) Check that eventually, SRANDMEMBER returns all elements. + array set allmyset {} + foreach ele [r smembers myset] { + set allmyset($ele) 1 + } + unset -nocomplain auxset + set iterations 1000 + while {$iterations != 0} { + incr iterations -1 + set res [r srandmember myset -10] + foreach ele $res { + set auxset($ele) 1 + } + if {[lsort [array names allmyset]] eq + [lsort [array names auxset]]} { + break; + } + } + assert {$iterations != 0} + + # 8) Remove the members to 30 in order to calculate the value of Chi-Square Distribution, + # otherwise we would need more iterations. + rem_hash_set_top_N myset [expr {[r scard myset] - 30}] + assert_equal [r scard myset] 30 + + # Hash set rehashing would be completed while removing members from the `myset` + # We also check the size and members in the hash table. + verify_rehashing_completed_key myset 64 30 + + # Now that we have a hash set with only one long chain bucket. + set htstats [r debug HTSTATS-KEY myset full] + assert {[regexp {different slots: ([0-9]+)} $htstats - different_slots]} + assert {[regexp {max chain length: ([0-9]+)} $htstats - max_chain_length]} + assert {$different_slots == 1 && $max_chain_length == 30} + + # 9) Use positive count (PATH 4) to get 10 elements (out of 30) each time. + unset -nocomplain allkey + set iterations 1000 + while {$iterations != 0} { + incr iterations -1 + set res [r srandmember myset 10] + foreach ele $res { + lappend allkey $ele + } + } + # validate even distribution of random sampling (df = 29, 73 means 0.00001 probability) + assert_lessthan [chi_square_value $allkey] 73 + + r config set save $origin_save + r config set set-max-listpack-entries $origin_max_lp + r config set rdb-key-save-delay $origin_save_delay + } {OK} {needs:debug slow} + proc setup_move {} { - r del myset3 myset4 - create_set myset1 {1 a b} - create_set myset2 {2 3 4} -# assert_encoding hashtable myset1 -# assert_encoding intset myset2 + 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 myset2 a] - assert_equal {1 b} [lsort [r smembers myset1]] - assert_equal {2 3 4 a} [lsort [r smembers myset2]] -# assert_encoding hashtable myset2 + 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 myset2 1] - assert_equal {a b} [lsort [r smembers myset1]] - assert_equal {1 2 3 4} [lsort [r smembers myset2]] -# assert_encoding intset myset2 + 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 myset1 2] - assert_equal {1 2 a b} [lsort [r smembers myset1]] - assert_equal {3 4} [lsort [r smembers myset2]] + 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 myset2 foo] - assert_equal {1 a b} [lsort [r smembers myset1]] - assert_equal {2 3 4} [lsort [r smembers myset2]] + 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 myset2 foo] - assert_equal {2 3 4} [lsort [r smembers myset2]] + 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 myset3 a] - assert_equal {1 b} [lsort [r smembers myset1]] - assert_equal {a} [lsort [r smembers myset3]] -# assert_encoding hashtable myset3 + 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 myset3 2] - assert_equal {3 4} [lsort [r smembers myset2]] - assert_equal {2} [lsort [r smembers myset3]] -# assert_encoding intset myset3 + 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} } -# Bug need Fix -# test "SMOVE wrong src key type" { -# r set x 10 -# assert_error "WRONGTYPE*" {r smove x myset2 foo} -# } + test "SMOVE wrong src key type" { + r set x{t} 10 + assert_error "WRONGTYPE*" {r smove x{t} myset2{t} foo} + } -# Bug need Fix -# test "SMOVE wrong dst key type" { -# r set x 10 -# assert_error "WRONGTYPE*" {r smove myset2 x 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 - r sadd set a b c - r smove set set b - lsort [r smembers set] + 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} { @@ -534,3 +1270,45 @@ start_server { } } } + +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 \ No newline at end of file diff --git a/tests/unit/type/string.tcl b/tests/unit/type/string.tcl index 8658825a9..49615e8aa 100644 --- a/tests/unit/type/string.tcl +++ b/tests/unit/type/string.tcl @@ -54,9 +54,9 @@ start_server {tags {"string"}} { set _ $err } {} - # test {DBSIZE should be 10000 now} { - # r dbsize - # } {10000} + test {DBSIZE should be 10000 now} { + r dbsize + } {10000} } test "SETNX target key missing" { @@ -101,112 +101,101 @@ start_server {tags {"string"}} { assert_equal 1 [r setnx x 20] assert_equal 20 [r get x] } -# Pika does not support the getex command - # test "GETEX EX option" { - # r del foo - # r set foo bar - # r getex foo ex 10 - # assert_range [r ttl foo] 5 10 - # } - -# Pika does not support the getex command - # test "GETEX PX option" { - # r del foo - # r set foo bar - # r getex foo px 10000 - # assert_range [r pttl foo] 5000 10000 - # } - -# Pika does not support the getex command - # 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 - # } - -# Pika does not support the getex command - # 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 - # } - -# Pika does not support the getex command - # 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] - # } - -# Pika does not support the getex command - # test "GETEX no option" { - # r del foo - # r set foo bar - # r getex foo - # assert_equal bar [r getex foo] - # } - -# Pika does not support the getex command - # test "GETEX syntax errors" { - # set ex {} - # catch {r getex foo non-existent-option} ex - # set ex - # } {*syntax*} - -# Pika does not support the getex command - # 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] - # } - -# Pika does not support the getex command - # test "GETEX no arguments" { - # set ex {} - # catch {r getex} ex - # set ex - # } {*wrong number of arguments for 'getex' command} - -# Pika does not support the getdel command - # test "GETDEL command" { - # r del foo - # r set foo bar - # assert_equal bar [r getdel foo ] - # assert_equal {} [r getdel foo ] - # } - -# Pika does not support the getdel command - # 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} - -# Pika does not support the getex command - # 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 "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 @@ -297,24 +286,22 @@ start_server {tags {"string"}} { assert_equal [binary format B* 00100000] [r get mykey] } -# Pika does not support the debug command - # test "SETBIT against integer-encoded key" { - # # Ascii "1" is integer 49 = 00 11 00 01 - # r set mykey 1 - # assert_encoding int 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] - # } + 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] + } -# Keys for multiple data types of Pika can be duplicate - # test "SETBIT against key with wrong type" { - # r del mykey - # r lpush mykey "foo" - # assert_error "WRONGTYPE*" {r setbit mykey 0 1} - # } + 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 @@ -344,7 +331,7 @@ start_server {tags {"string"}} { set str [string map {" " 0} [format $fmt $head $bitval $tail]] r setbit mykey $bitnum $bitval - # assert_equal [binary format B* $str] [r get mykey] + assert_equal [binary format B* $str] [r get mykey] } } @@ -371,7 +358,7 @@ start_server {tags {"string"}} { test "GETBIT against integer-encoded key" { r set mykey 1 - # assert_encoding int mykey + assert_encoding int mykey # Ascii "1" is integer 49 = 00 11 00 01 assert_equal 0 [r getbit mykey 0] @@ -390,9 +377,9 @@ start_server {tags {"string"}} { 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 0 [r setrange mykey 0 ""] + assert_equal 0 [r exists mykey] r del mykey assert_equal 4 [r setrange mykey 1 foo] @@ -419,58 +406,55 @@ start_server {tags {"string"}} { test "SETRANGE against integer-encoded key" { r set mykey 1234 - # assert_encoding int mykey + assert_encoding int mykey assert_equal 4 [r setrange mykey 0 2] - # assert_encoding raw mykey + 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_encoding int mykey assert_equal 4 [r setrange mykey 0 ""] - # assert_encoding int mykey + assert_encoding int mykey assert_equal 1234 [r get mykey] r set mykey 1234 - # assert_encoding int mykey + assert_encoding int mykey assert_equal 4 [r setrange mykey 1 3] - # assert_encoding raw mykey + assert_encoding raw mykey assert_equal 1334 [r get mykey] r set mykey 1234 - # assert_encoding int mykey + assert_encoding int mykey assert_equal 6 [r setrange mykey 5 2] - # assert_encoding raw mykey + assert_encoding raw mykey assert_equal "1234\0002" [r get mykey] } -# Keys for multiple data types of Pika can be duplicate - # test "SETRANGE against key with wrong type" { - # r del mykey - # r lpush mykey "foo" - # assert_error "WRONGTYPE*" {r setrange mykey 0 bar} - # } + test "SETRANGE against key with wrong type" { + r del mykey + r lpush mykey "foo" + assert_error "WRONGTYPE*" {r setrange mykey 0 bar} + } -# Keys for multiple data types of Pika can be duplicate - # test "SETRANGE with out of range offset" { - # r del mykey - # assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world} + 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} - # } + 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] } -# Keys for multiple data types of Pika can be duplicate - # 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 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" @@ -503,16 +487,15 @@ start_server {tags {"string"}} { } } -# Pika does not support the substr command - # 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] - # } + 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} { @@ -546,65 +529,58 @@ if {[string match {*jemalloc*} [s mem_allocator]]} { list $v1 $v2 [r get foo] } {{} OK 2} -# Bug need Fix - # 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} - -# Bug need Fix - # 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} - -# Bug need Fix - # 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} - -# Bug need Fix - # 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 - # } {{} {}} - -# Bug need Fix - # 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} - -# Bug need Fix - # 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} - -# Keys for multiple data types of Pika can be duplicate - # 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 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 @@ -613,77 +589,86 @@ if {[string match {*jemalloc*} [s mem_allocator]]} { 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} - } - -# No cause has been confirmed - # test "Extended SET EXAT option" { - # r del foo - # r set foo bar exat [expr [clock seconds] + 10] - # assert_range [r ttl foo] 5 10 - # } - -# No cause has been confirmed - # test "Extended SET PXAT option" { - # r del foo - # r set foo bar pxat [expr [clock milliseconds] + 10000] - # assert_range [r ttl foo] 5 10 - # } - -# No cause has been confirmed - # 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 {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} -# Pika does not support the lcs command - # 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}} - -# No cause has been confirmed - # 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" - # } - # } - # } + 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} } \ No newline at end of file diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index df3ae2a25..f23dfeb65 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -6,18 +6,109 @@ start_server {tags {"zset"}} { } } + # 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} { -# This parameter is not available in Pika - #if {$encoding == "ziplist"} { - # 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 - #} +# 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 @@ -37,73 +128,249 @@ start_server {tags {"zset"}} { } test "ZSET element can't be set to NaN with ZADD - $encoding" { - assert_error "*not*float*" {r zadd myzset abcde abc} + 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 "ZSET element can't be set to NaN with ZINCRBY" { - assert_error "*not*float*" {r zadd myzset abcde abc} + 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 "ZINCRBY calls leading to NaN result in error" { - r zincrby myzset 999999999 abc - assert_error "*not*float*" {r zincrby myzset abcde abc} + 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 - Variadic version base case} { + test "ZADD GT updates existing elements when new scores are greater - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + assert {[r zadd ztmp gt ch 5 foo 11 x 21 y 29 z] == 3} + assert {[r zcard ztmp] == 4} + assert {[r zscore ztmp x] == 11} + assert {[r zscore ztmp y] == 21} + assert {[r zscore ztmp z] == 30} + } + + test "ZADD LT updates existing elements when new scores are lower - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + assert {[r zadd ztmp lt ch 5 foo 11 x 21 y 29 z] == 2} + assert {[r zcard ztmp] == 4} + assert {[r zscore ztmp x] == 10} + assert {[r zscore ztmp y] == 20} + assert {[r zscore ztmp z] == 29} + } + + test "ZADD GT XX updates existing elements when new scores are greater and skips new elements - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + assert {[r zadd ztmp gt xx ch 5 foo 11 x 21 y 29 z] == 2} + assert {[r zcard ztmp] == 3} + assert {[r zscore ztmp x] == 11} + assert {[r zscore ztmp y] == 21} + assert {[r zscore ztmp z] == 30} + } + + test "ZADD LT XX updates existing elements when new scores are lower and skips new elements - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + assert {[r zadd ztmp lt xx ch 5 foo 11 x 21 y 29 z] == 1} + assert {[r zcard ztmp] == 3} + assert {[r zscore ztmp x] == 10} + assert {[r zscore ztmp y] == 20} + assert {[r zscore ztmp z] == 29} + } + + test "ZADD XX and NX are not compatible - $encoding" { + r del ztmp + catch {r zadd ztmp xx nx 10 x} err + set err + } {ERR*} + + test "ZADD NX with non existing key - $encoding" { + r del ztmp + r zadd ztmp nx 10 x 20 y 30 z + assert {[r zcard ztmp] == 3} + } + + test "ZADD NX only add new elements without updating old ones - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + assert {[r zadd ztmp nx 11 x 21 y 100 a 200 b] == 2} + assert {[r zscore ztmp x] == 10} + assert {[r zscore ztmp y] == 20} + assert {[r zscore ztmp a] == 100} + assert {[r zscore ztmp b] == 200} + } + + test "ZADD GT and NX are not compatible - $encoding" { + r del ztmp + catch {r zadd ztmp gt nx 10 x} err + set err + } {ERR*} + + test "ZADD LT and NX are not compatible - $encoding" { + r del ztmp + catch {r zadd ztmp lt nx 10 x} err + set err + } {ERR*} + + test "ZADD LT and GT are not compatible - $encoding" { + r del ztmp + catch {r zadd ztmp lt gt 10 x} err + set err + } {ERR*} + + test "ZADD INCR LT/GT replies with nill if score not updated - $encoding" { + r del ztmp + r zadd ztmp 28 x + assert {[r zadd ztmp lt incr 1 x] eq {}} + assert {[r zscore ztmp x] == 28} + assert {[r zadd ztmp gt incr -1 x] eq {}} + assert {[r zscore ztmp x] == 28} + } + + test "ZADD INCR LT/GT with inf - $encoding" { + r del ztmp + r zadd ztmp +inf x -inf y + + assert {[r zadd ztmp lt incr 1 x] eq {}} + assert {[r zscore ztmp x] == inf} + assert {[r zadd ztmp gt incr -1 x] eq {}} + assert {[r zscore ztmp x] == inf} + assert {[r zadd ztmp lt incr -1 x] eq {}} + assert {[r zscore ztmp x] == inf} + assert {[r zadd ztmp gt incr 1 x] eq {}} + assert {[r zscore ztmp x] == inf} + + assert {[r zadd ztmp lt incr 1 y] eq {}} + assert {[r zscore ztmp y] == -inf} + assert {[r zadd ztmp gt incr -1 y] eq {}} + assert {[r zscore ztmp y] == -inf} + assert {[r zadd ztmp lt incr -1 y] eq {}} + assert {[r zscore ztmp y] == -inf} + assert {[r zadd ztmp gt incr 1 y] eq {}} + assert {[r zscore ztmp y] == -inf} + } + + test "ZADD INCR works like ZINCRBY - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + r zadd ztmp INCR 15 x + assert {[r zscore ztmp x] == 25} + } + + test "ZADD INCR works with a single score-elemenet pair - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + catch {r zadd ztmp INCR 15 x 10 y} err + set err + } {ERR*} + + test "ZADD CH option changes return value to all changed elements - $encoding" { + r del ztmp + r zadd ztmp 10 x 20 y 30 z + assert {[r zadd ztmp 11 x 21 y 30 z] == 0} + assert {[r zadd ztmp ch 12 x 22 y 30 z] == 2} + } + + test "ZINCRBY calls leading to NaN result in error - $encoding" { + r zincrby myzset +inf abc + assert_error "*NaN*" {r zincrby myzset -inf abc} + } + + test "ZINCRBY against invalid incr value - $encoding" { + r del zincr + r zadd zincr 1 "one" + assert_error "*value is not a valid*" {r zincrby zincr v "one"} + } + + 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} { + 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} { + 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 - } + r exists myzset + } {0} - test {ZADD - Variadic version will raise error on missing arg} { + 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} { + 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" { + 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 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] + assert_equal 0 [r exists ztmp] } - test "ZREM variadic version" { + 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] - #assert_equal 0 [r exists ztmp] - } + r exists ztmp + } {0} - test "ZREM variadic version -- remove elements after key deletion" { + 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 @@ -165,11 +432,15 @@ start_server {tags {"zset"}} { assert_equal {} [r zrevrange ztmp 0 -5] assert_equal {} [r zrevrange ztmp 1 -5] - ## withscores + # 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 @@ -177,17 +448,37 @@ start_server {tags {"zset"}} { assert_equal 0 [r zrank zranktmp x] assert_equal 1 [r zrank zranktmp y] assert_equal 2 [r zrank zranktmp z] - assert_equal "" [r zrank zranktmp foo] assert_equal 2 [r zrevrank zranktmp x] assert_equal 1 [r zrevrank zranktmp y] assert_equal 0 [r zrevrank zranktmp z] - assert_equal "" [r zrevrank zranktmp foo] + 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" { @@ -211,33 +502,46 @@ start_server {tags {"zset"}} { 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 {-999999999 a 1 b 2 c 3 d 4 e 5 f 999999999 g} + create_zset zset {-inf a 1 b 2 c 3 d 4 e 5 f +inf g} + } + + proc create_long_zset {key length} { + r del $key + for {set i 0} {$i < $length} {incr i 1} { + r zadd $key $i i$i + } } - test "ZRANGEBYSCORE/ZREVRANGEBYSCORE/ZCOUNT basics" { + test "ZRANGEBYSCORE/ZREVRANGEBYSCORE/ZCOUNT basics - $encoding" { create_default_zset # inclusive range - assert_equal {a b c} [r zrangebyscore zset -999999999 2] + 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 999999999] - assert_equal {c b a} [r zrevrangebyscore zset 2 -999999999] + 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 999999999 4] + 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 (-999999999 (2] + 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 (999999999] - assert_equal {b} [r zrevrangebyscore zset (2 (-999999999] + 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 (999999999 (4] + assert_equal {f} [r zrevrangebyscore zset (+inf (4] assert_equal 2 [r zcount zset (0 (3] # test empty ranges @@ -246,19 +550,19 @@ start_server {tags {"zset"}} { # inclusive assert_equal {} [r zrangebyscore zset 4 2] - assert_equal {} [r zrangebyscore zset 6 999999999] - assert_equal {} [r zrangebyscore zset -999999999 -6] - assert_equal {} [r zrevrangebyscore zset 999999999 6] - assert_equal {} [r zrevrangebyscore zset -6 -999999999] + 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 (999999999] - assert_equal {} [r zrangebyscore zset (-999999999 (-6] - assert_equal {} [r zrevrangebyscore zset (999999999 (6] - assert_equal {} [r zrevrangebyscore zset (-6 (-999999999] + 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] @@ -267,13 +571,13 @@ start_server {tags {"zset"}} { assert_equal {} [r zrangebyscore zset (2.4 (2.6] } - test "ZRANGEBYSCORE with WITHSCORES" { + 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" { + 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] @@ -283,18 +587,29 @@ start_server {tags {"zset"}} { 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] + # zrangebyscore uses different logic when offset > ZSKIPLIST_MAX_SEARCH + create_long_zset zset 30 + assert_equal {i12 i13 i14} [r zrangebyscore zset 0 20 LIMIT 12 3] + assert_equal {i14 i15} [r zrangebyscore zset 0 20 LIMIT 14 2] + assert_equal {i19 i20 i21} [r zrangebyscore zset 0 30 LIMIT 19 3] + assert_equal {i29} [r zrangebyscore zset 10 30 LIMIT 19 2] + assert_equal {i17 i16 i15} [r zrevrangebyscore zset 30 10 LIMIT 12 3] + assert_equal {i6 i5} [r zrevrangebyscore zset 20 0 LIMIT 14 2] + assert_equal {i2 i1 i0} [r zrevrangebyscore zset 20 0 LIMIT 18 5] + assert_equal {i0} [r zrevrangebyscore zset 20 0 LIMIT 20 5] } - test "ZRANGEBYSCORE with LIMIT and WITHSCORES" { + 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" { + 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 abcde} + assert_error "*not*float*" {r zrangebyscore fooz 1 NaN} } proc create_default_lex_zset {} { @@ -303,17 +618,25 @@ start_server {tags {"zset"}} { 0 omega} } - test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" { + proc create_long_lex_zset {} { + create_zset zset {0 alpha 0 bar 0 cool 0 down + 0 elephant 0 foo 0 great 0 hill + 0 island 0 jacket 0 key 0 lip + 0 max 0 null 0 omega 0 point + 0 query 0 result 0 sea 0 tree} + } + + test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics - $encoding" { create_default_lex_zset # inclusive range assert_equal {alpha bar cool} [r zrangebylex zset - \[cool] - assert_equal {bar cool down} [r zrangebylex zset \[bar \[down] - assert_equal {great hill omega} [r zrangebylex zset \[g +] + assert_equal {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] + assert_equal 3 [r zlexcount zset \[ele \[h] # exclusive range assert_equal {alpha bar} [r zrangebylex zset - (cool] @@ -332,7 +655,23 @@ start_server {tags {"zset"}} { assert_equal {} [r zrevrangebylex zset (hill (omega] } - test "ZRANGEBYSLEX with LIMIT" { + test "ZLEXCOUNT advanced - $encoding" { + create_default_lex_zset + + assert_equal 9 [r zlexcount zset - +] + assert_equal 0 [r zlexcount zset + -] + assert_equal 0 [r zlexcount zset + \[c] + assert_equal 0 [r zlexcount zset \[c -] + assert_equal 8 [r zlexcount zset \[bar +] + assert_equal 5 [r zlexcount zset \[bar \[foo] + assert_equal 4 [r zlexcount zset \[bar (foo] + assert_equal 4 [r zlexcount zset (bar \[foo] + assert_equal 3 [r zlexcount zset (bar (foo] + assert_equal 5 [r zlexcount zset - (foo] + assert_equal 1 [r zlexcount zset (maxstring +] + } + + test "ZRANGEBYLEX with LIMIT - $encoding" { create_default_lex_zset assert_equal {alpha bar} [r zrangebylex zset - \[cool LIMIT 0 2] assert_equal {bar cool} [r zrangebylex zset - \[cool LIMIT 1 2] @@ -343,9 +682,25 @@ start_server {tags {"zset"}} { 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] + assert_equal {great foo elephant} [r zrevrangebylex zset + \[d LIMIT 2 3] + # zrangebylex uses different logic when offset > ZSKIPLIST_MAX_SEARCH + create_long_lex_zset + assert_equal {max null} [r zrangebylex zset - \[tree LIMIT 12 2] + assert_equal {point query} [r zrangebylex zset - \[tree LIMIT 15 2] + assert_equal {} [r zrangebylex zset \[max \[tree LIMIT 10 0] + assert_equal {} [r zrangebylex zset \[max \[tree LIMIT 12 0] + assert_equal {max} [r zrangebylex zset \[max \[null LIMIT 0 1] + assert_equal {null} [r zrangebylex zset \[max \[null LIMIT 1 1] + assert_equal {max null omega point} [r zrangebylex zset \[max \[point LIMIT 0 100] + assert_equal {tree sea result query point} [r zrevrangebylex zset + \[o LIMIT 0 5] + assert_equal {tree sea result query} [r zrevrangebylex zset + \[o LIMIT 0 4] + assert_equal {omega null max lip} [r zrevrangebylex zset + \[l LIMIT 5 4] + assert_equal {elephant down} [r zrevrangebylex zset + \[a LIMIT 15 2] + assert_equal {bar alpha} [r zrevrangebylex zset + - LIMIT 18 6] + assert_equal {hill great foo} [r zrevrangebylex zset + \[c LIMIT 12 3] } - test "ZRANGEBYLEX with invalid lex range specifiers" { + 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} @@ -353,10 +708,10 @@ start_server {tags {"zset"}} { assert_error "*not*string*" {r zrangebylex fooz -x \[bar} } - test "ZREMRANGEBYSCORE basics" { + 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] + assert_equal 1 [r exists zset] r zremrangebyscore zset $min $max } @@ -376,16 +731,16 @@ start_server {tags {"zset"}} { assert_equal 0 [remrangebyscore 4 2] assert_equal {a b c d e} [r zrange zset 0 -1] - # -999999999 to mid - assert_equal 3 [remrangebyscore -999999999 3] + # -inf to mid + assert_equal 3 [remrangebyscore -inf 3] assert_equal {d e} [r zrange zset 0 -1] - # mid to 999999999 - assert_equal 3 [remrangebyscore 3 999999999] + # mid to +inf + assert_equal 3 [remrangebyscore 3 +inf] assert_equal {a b} [r zrange zset 0 -1] - # -999999999 to 999999999 - assert_equal 5 [remrangebyscore -999999999 999999999] + # -inf to +inf + assert_equal 5 [remrangebyscore -inf +inf] assert_equal {} [r zrange zset 0 -1] # exclusive min @@ -406,19 +761,19 @@ start_server {tags {"zset"}} { # destroy when empty assert_equal 5 [remrangebyscore 1 5] - # assert_equal 0 [r exists zset] + assert_equal 0 [r exists zset] } - test "ZREMRANGEBYSCORE with non-value min or max" { + 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 abcde} + assert_error "*not*float*" {r zremrangebyscore fooz 1 NaN} } - test "ZREMRANGEBYRANK basics" { + 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] + assert_equal 1 [r exists zset] r zremrangebyrank zset $min $max } @@ -444,183 +799,819 @@ start_server {tags {"zset"}} { # destroy when empty assert_equal 5 [remrangebyrank 0 4] - #assert_equal 0 [r exists zset] + 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 0 [r zunionstore dst_key 1 zseta] - #assert_equal 0 [r exists dst_key] + 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 zsetb - r zadd zseta 1 a - r zadd zseta 2 b - r zunionstore zsetc 2 zseta zsetb - r zrange zsetc 0 -1 withscores + 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 zsetb zsetc - r zadd zseta 1 a - r zadd zseta 2 b - r zadd zseta 3 c - r zadd zsetb 1 b - r zadd zsetb 2 c - r zadd zsetb 3 d + 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] + } - assert_equal 4 [r zunionstore zsetc 2 zseta zsetb] - assert_equal {a 1 b 3 d 3 c 5} [r zrange zsetc 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 2 zseta zsetb weights 2 3] - assert_equal {a 2 b 7 d 9 c 12} [r zrange zsetc 0 -1 withscores] + 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 - r sadd seta a - r sadd seta b - r sadd seta c + 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 2 seta zsetb weights 2 3] - # assert_equal {a 2 b 5 c 8 d 9} [r zrange zsetc 0 -1 withscores] + 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 2 zseta zsetb aggregate min] - assert_equal {a 1 b 1 c 2 d 3} [r zrange zsetc 0 -1 withscores] + 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 2 zseta zsetb aggregate max] - assert_equal {a 1 b 2 c 3 d 3} [r zrange zsetc 0 -1 withscores] + 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 2 zseta zsetb] - assert_equal {b 3 c 5} [r zrange zsetc 0 -1 withscores] + 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 2 zseta zsetb weights 2 3] - assert_equal {b 7 c 12} [r zrange zsetc 0 -1 withscores] + 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 - r sadd seta a - r sadd seta b - r sadd seta c - # assert_equal 2 [r zinterstore zsetc 2 seta zsetb weights 2 3] - # assert_equal {b 5 c 8} [r zrange zsetc 0 -1 withscores] + 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 2 zseta zsetb aggregate min] - assert_equal {b 1 c 2} [r zrange zsetc 0 -1 withscores] + 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 2 zseta zsetb aggregate max] - assert_equal {b 2 c 3} [r zrange zsetc 0 -1 withscores] + 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 999999999/-999999999 scores - $encoding" { - r del zsetinf1 zsetinf2 + 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] + } - r zadd zsetinf1 999999999 key - r zadd zsetinf2 999999999 key - r $cmd zsetinf3 2 zsetinf1 zsetinf2 - assert_equal 1999999998 [r zscore zsetinf3 key] + test "$cmd with NaN weights - $encoding" { + r del zsetinf1{t} zsetinf2{t} - r zadd zsetinf1 -999999999 key - r zadd zsetinf2 999999999 key - r $cmd zsetinf3 2 zsetinf1 zsetinf2 - assert_equal 0 [r zscore zsetinf3 key] + 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 + } + } + } - r zadd zsetinf1 999999999 key - r zadd zsetinf2 -999999999 key - r $cmd zsetinf3 2 zsetinf1 zsetinf2 - assert_equal 0 [r zscore zsetinf3 key] + 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] + } - r zadd zsetinf1 -999999999 key - r zadd zsetinf2 -999999999 key - r $cmd zsetinf3 2 zsetinf1 zsetinf2 - assert_equal -1999999998 [r zscore zsetinf3 key] - } + test "ZDIFF basics - $encoding" { + assert_equal {a 1} [r zdiff 2 zseta{t} zsetb{t} withscores] + } - test "$cmd with NaN weights $encoding" { - r del zsetinf1 zsetinf2 + 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] + } - r zadd zsetinf1 1.0 key - r zadd zsetinf2 1.0 key - assert_error "*weight*not*float*" { - r $cmd zsetinf3 2 zsetinf1 zsetinf2 weights abcde abcde + 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 ziplist + 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 setb setc - r sadd set1 a - r sadd set2 10 - r zinterstore set3 2 set1 set2 + 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 -999999999 neginf - r zunionstore out 1 z weights 0 - r zrange out 0 -1 withscores + r zadd z{t} -inf neginf + r zunionstore out{t} 1 z{t} weights 0 + r zrange out{t} 0 -1 withscores } {neginf 0} -# Bug need Fix - # test {ZINTERSTORE #516 regression, mixed sets and ziplist zsets} { - # r sadd one 100 101 102 103 - # r sadd two 100 200 201 202 - # r zadd three 1 500 1 501 1 502 1 503 1 100 - # r zinterstore to_here 3 one two three WEIGHTS 0 0 1 - # r zrange to_here 0 -1 - # } {100} + 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 two dest - set cmd1 [list r zadd one] - set cmd2 [list r zadd two] + 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] > 100} - assert {[r zcard two] > 100} - r zunionstore dest 2 one two + 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 0 -1 withscores] { + 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} { - if {$encoding == "ziplist"} { + 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 + 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 + 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" @@ -636,13 +1627,17 @@ start_server {tags {"zset"}} { r zadd zscoretest $score $i } - #assert_encoding $encoding zscoretest + assert_encoding $encoding zscoretest for {set i 0} {$i < $elements} {incr i} { - assert_equal [lindex $aux $i] [r zscore zscoretest $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 "ZSCORE after a DEBUG RELOAD - $encoding" { + test "ZMSCORE - $encoding" { r del zscoretest set aux {} for {set i 0} {$i < $elements} {incr i} { @@ -651,13 +1646,30 @@ start_server {tags {"zset"}} { r zadd zscoretest $score $i } - #r debug reload - #assert_encoding $encoding zscoretest + assert_encoding $encoding zscoretest for {set i 0} {$i < $elements} {incr i} { - assert_equal [lindex $aux $i] [r zscore zscoretest $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} { @@ -694,7 +1706,7 @@ start_server {tags {"zset"}} { lappend auxlist [lindex $x 1] } - #assert_encoding $encoding myzset + assert_encoding $encoding myzset set fromredis [r zrange myzset 0 -1] set delta 0 for {set i 0} {$i < [llength $fromredis]} {incr i} { @@ -713,7 +1725,7 @@ start_server {tags {"zset"}} { r zadd zset [expr rand()] $i } - #assert_encoding $encoding zset + assert_encoding $encoding zset for {set i 0} {$i < 100} {incr i} { set min [expr rand()] set max [expr rand()] @@ -722,29 +1734,29 @@ start_server {tags {"zset"}} { set min $max set max $aux } - set low [r zrangebyscore zset -999999999 $min] + set low [r zrangebyscore zset -inf $min] set ok [r zrangebyscore zset $min $max] - set high [r zrangebyscore zset $max 999999999] - set lowx [r zrangebyscore zset -999999999 ($min] + 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 999999999] + set highx [r zrangebyscore zset ($max +inf] - if {[r zcount zset -999999999 $min] != [llength $low]} { + 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 999999999] != [llength $high]} { + if {[r zcount zset $max +inf] != [llength $high]} { append err "Error, len does not match zcount\n" } - if {[r zcount zset -999999999 ($min] != [llength $lowx]} { + 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 999999999] != [llength $highx]} { + if {[r zcount zset ($max +inf] != [llength $highx]} { append err "Error, len does not match zcount\n" } @@ -792,21 +1804,20 @@ start_server {tags {"zset"}} { set lexset {} r del zset for {set j 0} {$j < $elements} {incr j} { - set e [randstring 1 30 alpha] + 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 1 30 alpha] - set max [randstring 1 30 alpha] + 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} { - break set cmd zrevrangebylex } else { set cmd zrangebylex @@ -856,33 +1867,34 @@ start_server {tags {"zset"}} { test "ZREMRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" { set lexset {} - r del zset zsetcopy + r del zset{t} zsetcopy{t} for {set j 0} {$j < $elements} {incr j} { - set e [randstring 1 30 alpha] + set e [randstring 0 30 alpha] lappend lexset $e - r zadd zset 0 $e + r zadd zset{t} 0 $e } set lexset [lsort -unique $lexset] for {set j 0} {$j < 100} {incr j} { # Copy... - r zunionstore zsetcopy 1 zset + r zunionstore zsetcopy{t} 1 zset{t} set lexsetcopy $lexset - set min [randstring 1 30 alpha] - set max [randstring 1 30 alpha] + 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 0 -1] eq $lexset} + assert {[r zrange zset{t} 0 -1] eq $lexset} # Get the range we are going to remove - set torem [r zrangebylex zset $cmin $cmax] - set toremlen [r zlexcount zset $cmin $cmax] - r zremrangebylex zsetcopy $cmin $cmax - set output [r zrange zsetcopy 0 -1] + 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]] @@ -900,7 +1912,7 @@ start_server {tags {"zset"}} { r zrem myzset "Element-[expr int(rand()*$elements)]" } - #assert_encoding $encoding myzset + 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} { @@ -921,7 +1933,7 @@ start_server {tags {"zset"}} { } else { set score [expr rand()] r zadd myzset $score $i - #assert_encoding $encoding myzset + assert_encoding $encoding myzset } set card [r zcard myzset] @@ -937,10 +1949,787 @@ start_server {tags {"zset"}} { } assert_equal {} $err } + + foreach {pop} {BZPOPMIN BZMPOP_MIN} { + test "$pop, ZADD + DEL should not awake blocked client" { + set rd [redis_deferring_client] + r del zset + + bzpop_command $rd $pop zset 0 + wait_for_blocked_client + + r multi + r zadd zset 0 foo + r del zset + r exec + r del zset + r zadd zset 1 bar + + verify_pop_response $pop [$rd read] {zset bar 1} {zset {{bar 1}}} + $rd close + } + + test "$pop, ZADD + DEL + SET should not awake blocked client" { + set rd [redis_deferring_client] + r del zset + + bzpop_command $rd $pop zset 0 + wait_for_blocked_client + + r multi + r zadd zset 0 foo + r del zset + r set zset foo + r exec + r del zset + r zadd zset 1 bar + + verify_pop_response $pop [$rd read] {zset bar 1} {zset {{bar 1}}} + $rd close + } + } + + test {BZPOPMIN unblock but the key is expired and then block again - reprocessing command} { + r flushall + r debug set-active-expire 0 + set rd [redis_deferring_client] + + set start [clock milliseconds] + $rd bzpopmin zset{t} 1 + wait_for_blocked_clients_count 1 + + # The exec will try to awake the blocked client, but the key is expired, + # so the client will be blocked again during the command reprocessing. + r multi + r zadd zset{t} 1 one + r pexpire zset{t} 100 + r debug sleep 0.2 + r exec + + assert_equal {} [$rd read] + set end [clock milliseconds] + + # Before the fix in #13004, this time would have been 1200+ (i.e. more than 1200ms), + # now it should be 1000, but in order to avoid timing issues, we increase the range a bit. + assert_range [expr $end-$start] 1000 1150 + + r debug set-active-expire 1 + $rd close + } {0} {needs:debug} + + test "BZPOPMIN with same key multiple times should work" { + set rd [redis_deferring_client] + r del z1{t} z2{t} + + # Data arriving after the BZPOPMIN. + $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 + wait_for_blocked_client + r zadd z1{t} 0 a + assert_equal [$rd read] {z1{t} a 0} + $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 + wait_for_blocked_client + r zadd z2{t} 1 b + assert_equal [$rd read] {z2{t} b 1} + + # Data already there. + r zadd z1{t} 0 a + r zadd z2{t} 1 b + $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 + assert_equal [$rd read] {z1{t} a 0} + $rd bzpopmin z1{t} z2{t} z2{t} z1{t} 0 + assert_equal [$rd read] {z2{t} b 1} + $rd close + } + + foreach {pop} {BZPOPMIN BZMPOP_MIN} { + test "MULTI/EXEC is isolated from the point of view of $pop" { + set rd [redis_deferring_client] + r del zset + + bzpop_command $rd $pop zset 0 + wait_for_blocked_client + + r multi + r zadd zset 0 a + r zadd zset 1 b + r zadd zset 2 c + r exec + + verify_pop_response $pop [$rd read] {zset a 0} {zset {{a 0}}} + $rd close + } + + test "$pop with variadic ZADD" { + set rd [redis_deferring_client] + r del zset + if {$::valgrind} {after 100} + bzpop_command $rd $pop zset 0 + wait_for_blocked_client + if {$::valgrind} {after 100} + assert_equal 2 [r zadd zset -1 foo 1 bar] + if {$::valgrind} {after 100} + verify_pop_response $pop [$rd read] {zset foo -1} {zset {{foo -1}}} + assert_equal {bar} [r zrange zset 0 -1] + $rd close + } + + test "$pop with zero timeout should block indefinitely" { + set rd [redis_deferring_client] + r del zset + bzpop_command $rd $pop zset 0 + wait_for_blocked_client + after 1000 + r zadd zset 0 foo + verify_pop_response $pop [$rd read] {zset foo 0} {zset {{foo 0}}} + $rd close + } + } + + r config set zset-max-ziplist-entries $original_max_entries + r config set zset-max-ziplist-value $original_max_value } tags {"slow"} { - stressers ziplist + stressers listpack stressers skiplist } -} + + test "BZPOP/BZMPOP against wrong type" { + r set foo{t} bar + assert_error "*WRONGTYPE*" {r bzpopmin foo{t} 1} + assert_error "*WRONGTYPE*" {r bzpopmax foo{t} 1} + + assert_error "*WRONGTYPE*" {r bzmpop 1 1 foo{t} min} + assert_error "*WRONGTYPE*" {r bzmpop 1 1 foo{t} max} + assert_error "*WRONGTYPE*" {r bzmpop 1 1 foo{t} min count 10} + + r del foo{t} + r set foo2{t} bar + assert_error "*WRONGTYPE*" {r bzmpop 1 2 foo{t} foo2{t} min} + assert_error "*WRONGTYPE*" {r bzmpop 1 2 foo2{t} foo{t} max count 1} + } + + test "BZMPOP with illegal argument" { + assert_error "ERR wrong number of arguments for 'bzmpop' command" {r bzmpop} + assert_error "ERR wrong number of arguments for 'bzmpop' command" {r bzmpop 0 1} + assert_error "ERR wrong number of arguments for 'bzmpop' command" {r bzmpop 0 1 myzset{t}} + + assert_error "ERR numkeys*" {r bzmpop 1 0 myzset{t} MIN} + assert_error "ERR numkeys*" {r bzmpop 1 a myzset{t} MIN} + assert_error "ERR numkeys*" {r bzmpop 1 -1 myzset{t} MAX} + + assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} bad_where} + assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} MIN bar_arg} + assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} MAX MIN} + assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} COUNT} + assert_error "ERR syntax error*" {r bzmpop 1 1 myzset{t} MIN COUNT 1 COUNT 2} + assert_error "ERR syntax error*" {r bzmpop 1 2 myzset{t} myzset2{t} bad_arg} + + assert_error "ERR count*" {r bzmpop 1 1 myzset{t} MIN COUNT 0} + assert_error "ERR count*" {r bzmpop 1 1 myzset{t} MAX COUNT a} + assert_error "ERR count*" {r bzmpop 1 1 myzset{t} MIN COUNT -1} + assert_error "ERR count*" {r bzmpop 1 2 myzset{t} myzset2{t} MAX COUNT -1} + } + + test "BZMPOP with multiple blocked clients" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + set rd3 [redis_deferring_client] + set rd4 [redis_deferring_client] + r del myzset{t} myzset2{t} + + $rd1 bzmpop 0 2 myzset{t} myzset2{t} min count 1 + wait_for_blocked_clients_count 1 + $rd2 bzmpop 0 2 myzset{t} myzset2{t} max count 10 + wait_for_blocked_clients_count 2 + $rd3 bzmpop 0 2 myzset{t} myzset2{t} min count 10 + wait_for_blocked_clients_count 3 + $rd4 bzmpop 0 2 myzset{t} myzset2{t} max count 1 + wait_for_blocked_clients_count 4 + + r multi + r zadd myzset{t} 1 a 2 b 3 c 4 d 5 e + r zadd myzset2{t} 1 a 2 b 3 c 4 d 5 e + r exec + + assert_equal {myzset{t} {{a 1}}} [$rd1 read] + assert_equal {myzset{t} {{e 5} {d 4} {c 3} {b 2}}} [$rd2 read] + assert_equal {myzset2{t} {{a 1} {b 2} {c 3} {d 4} {e 5}}} [$rd3 read] + + r zadd myzset2{t} 1 a 2 b 3 c + assert_equal {myzset2{t} {{c 3}}} [$rd4 read] + + r del myzset{t} myzset2{t} + $rd1 close + $rd2 close + $rd3 close + $rd4 close + } + + test "BZMPOP propagate as pop with count command to replica" { + set rd [redis_deferring_client] + set repl [attach_to_replication_stream] + + # BZMPOP without being blocked. + r zadd myzset{t} 1 one 2 two 3 three + r zadd myzset2{t} 4 four 5 five 6 six + r bzmpop 0 1 myzset{t} min + r bzmpop 0 2 myzset{t} myzset2{t} max count 10 + r bzmpop 0 2 myzset{t} myzset2{t} max count 10 + + # BZMPOP that gets blocked. + $rd bzmpop 0 1 myzset{t} min count 1 + wait_for_blocked_client + r zadd myzset{t} 1 one + $rd bzmpop 0 2 myzset{t} myzset2{t} min count 5 + wait_for_blocked_client + r zadd myzset{t} 1 one 2 two 3 three + $rd bzmpop 0 2 myzset{t} myzset2{t} max count 10 + wait_for_blocked_client + r zadd myzset2{t} 4 four 5 five 6 six + + # Released on timeout. + assert_equal {} [r bzmpop 0.01 1 myzset{t} max count 10] + r set foo{t} bar ;# something else to propagate after, so we can make sure the above pop didn't. + + $rd close + + assert_replication_stream $repl { + {select *} + {zadd myzset{t} 1 one 2 two 3 three} + {zadd myzset2{t} 4 four 5 five 6 six} + {zpopmin myzset{t} 1} + {zpopmax myzset{t} 2} + {zpopmax myzset2{t} 3} + {zadd myzset{t} 1 one} + {zpopmin myzset{t} 1} + {zadd myzset{t} 1 one 2 two 3 three} + {zpopmin myzset{t} 3} + {zadd myzset2{t} 4 four 5 five 6 six} + {zpopmax myzset2{t} 3} + {set foo{t} bar} + } + close_replication_stream $repl + } {} {needs:repl} + + test "BZMPOP should not blocks on non key arguments - #10762" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + r del myzset myzset2 myzset3 + + $rd1 bzmpop 0 1 myzset min count 10 + wait_for_blocked_clients_count 1 + $rd2 bzmpop 0 2 myzset2 myzset3 max count 10 + wait_for_blocked_clients_count 2 + + # These non-key keys will not unblock the clients. + r zadd 0 100 timeout_value + r zadd 1 200 numkeys_value + r zadd min 300 min_token + r zadd max 400 max_token + r zadd count 500 count_token + r zadd 10 600 count_value + + r zadd myzset 1 zset + r zadd myzset3 1 zset3 + assert_equal {myzset {{zset 1}}} [$rd1 read] + assert_equal {myzset3 {{zset3 1}}} [$rd2 read] + + $rd1 close + $rd2 close + } {0} {cluster:skip} + + test {ZSET skiplist order consistency when elements are moved} { + set original_max [lindex [r config get zset-max-ziplist-entries] 1] + r config set zset-max-ziplist-entries 0 + for {set times 0} {$times < 10} {incr times} { + r del zset + for {set j 0} {$j < 1000} {incr j} { + r zadd zset [randomInt 50] ele-[randomInt 10] + } + + # Make sure that element ordering is correct + set prev_element {} + set prev_score -1 + foreach {element score} [r zrange zset 0 -1 WITHSCORES] { + # Assert that elements are in increasing ordering + assert { + $prev_score < $score || + ($prev_score == $score && + [string compare $prev_element $element] == -1) + } + set prev_element $element + set prev_score $score + } + } + r config set zset-max-ziplist-entries $original_max + } + + test {ZRANGESTORE basic} { + r flushall + r zadd z1{t} 1 a 2 b 3 c 4 d + set res [r zrangestore z2{t} z1{t} 0 -1] + assert_equal $res 4 + r zrange z2{t} 0 -1 withscores + } {a 1 b 2 c 3 d 4} + + test {ZRANGESTORE RESP3} { + r hello 3 + assert_equal [r zrange z2{t} 0 -1 withscores] {{a 1.0} {b 2.0} {c 3.0} {d 4.0}} + r hello 2 + } + + test {ZRANGESTORE range} { + set res [r zrangestore z2{t} z1{t} 1 2] + assert_equal $res 2 + r zrange z2{t} 0 -1 withscores + } {b 2 c 3} + + test {ZRANGESTORE BYLEX} { + set res [r zrangestore z3{t} z1{t} \[b \[c BYLEX] + assert_equal $res 2 + assert_encoding listpack z3{t} + set res [r zrangestore z2{t} z1{t} \[b \[c BYLEX] + assert_equal $res 2 + r zrange z2{t} 0 -1 withscores + } {b 2 c 3} + + test {ZRANGESTORE BYSCORE} { + set res [r zrangestore z4{t} z1{t} 1 2 BYSCORE] + assert_equal $res 2 + assert_encoding listpack z4{t} + set res [r zrangestore z2{t} z1{t} 1 2 BYSCORE] + assert_equal $res 2 + r zrange z2{t} 0 -1 withscores + } {a 1 b 2} + + test {ZRANGESTORE BYSCORE LIMIT} { + set res [r zrangestore z2{t} z1{t} 0 5 BYSCORE LIMIT 0 2] + assert_equal $res 2 + r zrange z2{t} 0 -1 withscores + } {a 1 b 2} + + test {ZRANGESTORE BYSCORE REV LIMIT} { + set res [r zrangestore z2{t} z1{t} 5 0 BYSCORE REV LIMIT 0 2] + assert_equal $res 2 + r zrange z2{t} 0 -1 withscores + } {c 3 d 4} + + test {ZRANGE BYSCORE REV LIMIT} { + r zrange z1{t} 5 0 BYSCORE REV LIMIT 0 2 WITHSCORES + } {d 4 c 3} + + test {ZRANGESTORE - src key missing} { + set res [r zrangestore z2{t} missing{t} 0 -1] + assert_equal $res 0 + r exists z2{t} + } {0} + + test {ZRANGESTORE - src key wrong type} { + r zadd z2{t} 1 a + r set foo{t} bar + assert_error "*WRONGTYPE*" {r zrangestore z2{t} foo{t} 0 -1} + r zrange z2{t} 0 -1 + } {a} + + test {ZRANGESTORE - empty range} { + set res [r zrangestore z2{t} z1{t} 5 6] + assert_equal $res 0 + r exists z2{t} + } {0} + + test {ZRANGESTORE BYLEX - empty range} { + set res [r zrangestore z2{t} z1{t} \[f \[g BYLEX] + assert_equal $res 0 + r exists z2{t} + } {0} + + test {ZRANGESTORE BYSCORE - empty range} { + set res [r zrangestore z2{t} z1{t} 5 6 BYSCORE] + assert_equal $res 0 + r exists z2{t} + } {0} + + test {ZRANGE BYLEX} { + r zrange z1{t} \[b \[c BYLEX + } {b c} + + test {ZRANGESTORE invalid syntax} { + catch {r zrangestore z2{t} z1{t} 0 -1 limit 1 2} err + assert_match "*syntax*" $err + catch {r zrangestore z2{t} z1{t} 0 -1 WITHSCORES} err + assert_match "*syntax*" $err + } + + test {ZRANGESTORE with zset-max-listpack-entries 0 #10767 case} { + set original_max [lindex [r config get zset-max-listpack-entries] 1] + r config set zset-max-listpack-entries 0 + r del z1{t} z2{t} + r zadd z1{t} 1 a + assert_encoding skiplist z1{t} + assert_equal 1 [r zrangestore z2{t} z1{t} 0 -1] + assert_encoding skiplist z2{t} + r config set zset-max-listpack-entries $original_max + } + + test {ZRANGESTORE with zset-max-listpack-entries 1 dst key should use skiplist encoding} { + set original_max [lindex [r config get zset-max-listpack-entries] 1] + r config set zset-max-listpack-entries 1 + r del z1{t} z2{t} z3{t} + r zadd z1{t} 1 a 2 b + assert_equal 1 [r zrangestore z2{t} z1{t} 0 0] + assert_encoding listpack z2{t} + assert_equal 2 [r zrangestore z3{t} z1{t} 0 1] + assert_encoding skiplist z3{t} + r config set zset-max-listpack-entries $original_max + } + + test {ZRANGE invalid syntax} { + catch {r zrange z1{t} 0 -1 limit 1 2} err + assert_match "*syntax*" $err + catch {r zrange z1{t} 0 -1 BYLEX WITHSCORES} err + assert_match "*syntax*" $err + catch {r zrevrange z1{t} 0 -1 BYSCORE} err + assert_match "*syntax*" $err + catch {r zrangebyscore z1{t} 0 -1 REV} err + assert_match "*syntax*" $err + } + + proc get_keys {l} { + set res {} + foreach {score key} $l { + lappend res $key + } + return $res + } + + # Check whether the zset members belong to the zset + proc check_member {mydict res} { + foreach ele $res { + assert {[dict exists $mydict $ele]} + } + } + + # Check whether the zset members and score belong to the zset + proc check_member_and_score {mydict res} { + foreach {key val} $res { + assert_equal $val [dict get $mydict $key] + } + } + + foreach {type contents} "listpack {1 a 2 b 3 c} skiplist {1 a 2 b 3 [randstring 70 90 alpha]}" { + set original_max_value [lindex [r config get zset-max-ziplist-value] 1] + r config set zset-max-ziplist-value 10 + create_zset myzset $contents + assert_encoding $type myzset + + test "ZRANDMEMBER - $type" { + unset -nocomplain myzset + array set myzset {} + for {set i 0} {$i < 100} {incr i} { + set key [r zrandmember myzset] + set myzset($key) 1 + } + assert_equal [lsort [get_keys $contents]] [lsort [array names myzset]] + } + r config set zset-max-ziplist-value $original_max_value + } + + test "ZRANDMEMBER with RESP3" { + r hello 3 + set res [r zrandmember myzset 3 withscores] + assert_equal [llength $res] 3 + assert_equal [llength [lindex $res 1]] 2 + + set res [r zrandmember myzset 3] + assert_equal [llength $res] 3 + assert_equal [llength [lindex $res 1]] 1 + r hello 2 + } + + test "ZRANDMEMBER count of 0 is handled correctly" { + r zrandmember myzset 0 + } {} + + test "ZRANDMEMBER with against non existing key" { + r zrandmember nonexisting_key 100 + } {} + + test "ZRANDMEMBER count overflow" { + r zadd myzset 0 a + assert_error {*value is out of range*} {r zrandmember myzset -9223372036854770000 withscores} + assert_error {*value is out of range*} {r zrandmember myzset -9223372036854775808 withscores} + assert_error {*value is out of range*} {r zrandmember myzset -9223372036854775808} + } {} + + # Make sure we can distinguish between an empty array and a null response + r readraw 1 + + test "ZRANDMEMBER count of 0 is handled correctly - emptyarray" { + r zrandmember myzset 0 + } {*0} + + test "ZRANDMEMBER with against non existing key - emptyarray" { + r zrandmember nonexisting_key 100 + } {*0} + + r readraw 0 + + foreach {type contents} " + skiplist {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 [randstring 70 90 alpha]} + listpack {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 j} " { + test "ZRANDMEMBER with - $type" { + set original_max_value [lindex [r config get zset-max-ziplist-value] 1] + r config set zset-max-ziplist-value 10 + create_zset myzset $contents + assert_encoding $type myzset + + # create a dict for easy lookup + set mydict [dict create {*}[r zrange myzset 0 -1 withscores]] + + # We'll stress different parts of the code, see the implementation + # of ZRANDMEMBER for more information, but basically there are + # four different code paths. + + # PATH 1: Use negative count. + + # 1) Check that it returns repeated elements with and without values. + # 2) Check that all the elements actually belong to the original zset. + set res [r zrandmember myzset -20] + assert_equal [llength $res] 20 + check_member $mydict $res + + set res [r zrandmember myzset -1001] + assert_equal [llength $res] 1001 + check_member $mydict $res + + # again with WITHSCORES + set res [r zrandmember myzset -20 withscores] + assert_equal [llength $res] 40 + check_member_and_score $mydict $res + + set res [r zrandmember myzset -1001 withscores] + assert_equal [llength $res] 2002 + check_member_and_score $mydict $res + + # Test random uniform distribution + # df = 9, 40 means 0.00001 probability + set res [r zrandmember myzset -1000] + assert_lessthan [chi_square_value $res] 40 + check_member $mydict $res + + # 3) Check that eventually all the elements are returned. + # Use both WITHSCORES and without + unset -nocomplain auxset + set iterations 1000 + while {$iterations != 0} { + incr iterations -1 + if {[expr {$iterations % 2}] == 0} { + set res [r zrandmember myzset -3 withscores] + foreach {key val} $res { + dict append auxset $key $val + } + } else { + set res [r zrandmember myzset -3] + foreach key $res { + dict append auxset $key + } + } + if {[lsort [dict keys $mydict]] eq + [lsort [dict keys $auxset]]} { + break; + } + } + assert {$iterations != 0} + + # PATH 2: positive count (unique behavior) with requested size + # equal or greater than set size. + foreach size {10 20} { + set res [r zrandmember myzset $size] + assert_equal [llength $res] 10 + assert_equal [lsort $res] [lsort [dict keys $mydict]] + check_member $mydict $res + + # again with WITHSCORES + set res [r zrandmember myzset $size withscores] + assert_equal [llength $res] 20 + assert_equal [lsort $res] [lsort $mydict] + check_member_and_score $mydict $res + } + + # PATH 3: Ask almost as elements as there are in the set. + # In this case the implementation will duplicate the original + # set and will remove random elements up to the requested size. + # + # PATH 4: Ask a number of elements definitely smaller than + # the set size. + # + # We can test both the code paths just changing the size but + # using the same code. + foreach size {1 2 8} { + # 1) Check that all the elements actually belong to the + # original set. + set res [r zrandmember myzset $size] + assert_equal [llength $res] $size + check_member $mydict $res + + # again with WITHSCORES + set res [r zrandmember myzset $size withscores] + assert_equal [llength $res] [expr {$size * 2}] + check_member_and_score $mydict $res + + # 2) Check that eventually all the elements are returned. + # Use both WITHSCORES and without + unset -nocomplain auxset + unset -nocomplain allkey + set iterations [expr {1000 / $size}] + set all_ele_return false + while {$iterations != 0} { + incr iterations -1 + if {[expr {$iterations % 2}] == 0} { + set res [r zrandmember myzset $size withscores] + foreach {key value} $res { + dict append auxset $key $value + lappend allkey $key + } + } else { + set res [r zrandmember myzset $size] + foreach key $res { + dict append auxset $key + lappend allkey $key + } + } + if {[lsort [dict keys $mydict]] eq + [lsort [dict keys $auxset]]} { + set all_ele_return true + } + } + assert_equal $all_ele_return true + # df = 9, 40 means 0.00001 probability + assert_lessthan [chi_square_value $allkey] 40 + } + } + r config set zset-max-ziplist-value $original_max_value + } + + test {zset score double range} { + set dblmax 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.00000000000000000 + r del zz + r zadd zz $dblmax dblmax + assert_encoding listpack zz + r zscore zz dblmax + } {1.7976931348623157e+308} + + test {zunionInterDiffGenericCommand acts on SET and ZSET} { + r del set_small{t} set_big{t} zset_small{t} zset_big{t} zset_dest{t} + + foreach set_type {intset listpack hashtable} { + # Restore all default configurations before each round of testing. + r config set set-max-intset-entries 512 + r config set set-max-listpack-entries 128 + r config set zset-max-listpack-entries 128 + + r del set_small{t} set_big{t} + + if {$set_type == "intset"} { + r sadd set_small{t} 1 2 3 + r sadd set_big{t} 1 2 3 4 5 + assert_encoding intset set_small{t} + assert_encoding intset set_big{t} + } elseif {$set_type == "listpack"} { + # Add an "a" and then remove it, make sure the set is listpack encoding. + r sadd set_small{t} a 1 2 3 + r sadd set_big{t} a 1 2 3 4 5 + r srem set_small{t} a + r srem set_big{t} a + assert_encoding listpack set_small{t} + assert_encoding listpack set_big{t} + } elseif {$set_type == "hashtable"} { + r config set set-max-intset-entries 0 + r config set set-max-listpack-entries 0 + r sadd set_small{t} 1 2 3 + r sadd set_big{t} 1 2 3 4 5 + assert_encoding hashtable set_small{t} + assert_encoding hashtable set_big{t} + } + + foreach zset_type {listpack skiplist} { + r del zset_small{t} zset_big{t} + + if {$zset_type == "listpack"} { + r zadd zset_small{t} 1 1 2 2 3 3 + r zadd zset_big{t} 1 1 2 2 3 3 4 4 5 5 + assert_encoding listpack zset_small{t} + assert_encoding listpack zset_big{t} + } elseif {$zset_type == "skiplist"} { + r config set zset-max-listpack-entries 0 + r zadd zset_small{t} 1 1 2 2 3 3 + r zadd zset_big{t} 1 1 2 2 3 3 4 4 5 5 + assert_encoding skiplist zset_small{t} + assert_encoding skiplist zset_big{t} + } + + # Test one key is big and one key is small separately. + # The reason for this is because we will sort the sets from smallest to largest. + # So set one big key and one small key, then the test can cover more code paths. + foreach {small_or_big set_key zset_key} { + small set_small{t} zset_big{t} + big set_big{t} zset_small{t} + } { + # The result of these commands are not related to the order of the keys. + assert_equal {1 2 3 4 5} [lsort [r zunion 2 $set_key $zset_key]] + assert_equal {5} [r zunionstore zset_dest{t} 2 $set_key $zset_key] + assert_equal {1 2 3} [lsort [r zinter 2 $set_key $zset_key]] + assert_equal {3} [r zinterstore zset_dest{t} 2 $set_key $zset_key] + assert_equal {3} [r zintercard 2 $set_key $zset_key] + + # The result of sdiff is related to the order of the keys. + if {$small_or_big == "small"} { + assert_equal {} [r zdiff 2 $set_key $zset_key] + assert_equal {0} [r zdiffstore zset_dest{t} 2 $set_key $zset_key] + } else { + assert_equal {4 5} [lsort [r zdiff 2 $set_key $zset_key]] + assert_equal {2} [r zdiffstore zset_dest{t} 2 $set_key $zset_key] + } + } + } + } + + r config set set-max-intset-entries 512 + r config set set-max-listpack-entries 128 + r config set zset-max-listpack-entries 128 + } + + foreach type {single multiple single_multiple} { + test "ZADD overflows the maximum allowed elements in a listpack - $type" { + r del myzset + + set max_entries 64 + set original_max [lindex [r config get zset-max-listpack-entries] 1] + r config set zset-max-listpack-entries $max_entries + + if {$type == "single"} { + # All are single zadd commands. + for {set i 0} {$i < $max_entries} {incr i} { r zadd myzset $i $i } + } elseif {$type == "multiple"} { + # One zadd command to add all elements. + set args {} + for {set i 0} {$i < $max_entries * 2} {incr i} { lappend args $i } + r zadd myzset {*}$args + } elseif {$type == "single_multiple"} { + # First one zadd adds an element (creates a key) and then one zadd adds all elements. + r zadd myzset 1 1 + set args {} + for {set i 0} {$i < $max_entries * 2} {incr i} { lappend args $i } + r zadd myzset {*}$args + } + + assert_encoding listpack myzset + assert_equal $max_entries [r zcard myzset] + assert_equal 1 [r zadd myzset 1 b] + assert_encoding skiplist myzset + + r config set zset-max-listpack-entries $original_max + } + } +} \ No newline at end of file