Commit a83e3663 authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'origin/unstable' into 7.0

parents d5915a16 5860fa3d
......@@ -238,6 +238,7 @@ start_server {tags {"info" "external:skip"}} {
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat OOM]
r config set maxmemory 0
}
test {errorstats: rejected call by authorization error} {
......@@ -253,6 +254,25 @@ start_server {tags {"info" "external:skip"}} {
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat NOPERM]
r auth default ""
}
test {errorstats: blocking commands} {
r config resetstat
set rd [redis_deferring_client]
$rd client id
set rd_id [$rd read]
r del list1{t}
$rd blpop list1{t} 0
wait_for_blocked_client
r client unblock $rd_id error
assert_error {UNBLOCKED*} {$rd read}
assert_match {*count=1*} [errorstat UNBLOCKED]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat blpop]
assert_equal [s total_error_replies] 1
$rd close
}
}
}
......@@ -33,6 +33,15 @@ start_server {tags {"introspection"}} {
assert_match {} [cmdstat zadd]
} {} {needs:config-resetstat}
test {errors stats for GEOADD} {
r config resetstat
# make sure geo command will failed
r set foo 1
assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r GEOADD foo 0 0 bar}
assert_match {*calls=1*,rejected_calls=0,failed_calls=1*} [cmdstat geoadd]
assert_match {} [cmdstat zadd]
} {} {needs:config-resetstat}
test {command stats for EXPIRE} {
r config resetstat
r SET foo bar
......@@ -81,6 +90,13 @@ start_server {tags {"introspection"}} {
assert_equal {key} [r command getkeys get key]
}
test {COMMAND GETKEYSANDFLAGS} {
assert_equal {{k1 {OW update}}} [r command getkeysandflags set k1 v1]
assert_equal {{k1 {OW update}} {k2 {OW update}}} [r command getkeysandflags mset k1 v1 k2 v2]
assert_equal {{k1 {RW access delete}} {k2 {RW insert}}} [r command getkeysandflags LMOVE k1 k2 left right]
assert_equal {{k1 {RO access}} {k2 {OW update}}} [r command getkeysandflags sort k1 store k2]
}
test {COMMAND GETKEYS MEMORY USAGE} {
assert_equal {key} [r command getkeys memory usage key]
}
......
......@@ -7,7 +7,7 @@ start_server {tags {"introspection"}} {
test {CLIENT LIST} {
r client list
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|list user=* redir=-1 resp=2*}
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|list user=* redir=-1 resp=2*}
test {CLIENT LIST with IDs} {
set myid [r client id]
......@@ -17,7 +17,7 @@ start_server {tags {"introspection"}} {
test {CLIENT INFO} {
r client info
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|info user=* redir=-1 resp=2*}
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|info user=* redir=-1 resp=2*}
test {CLIENT KILL with illegal arguments} {
assert_error "ERR wrong number of arguments for 'client|kill' command" {r client kill}
......
......@@ -158,6 +158,88 @@ start_server {tags {"defrag external:skip"} overrides {appendonly yes auto-aof-r
r config set appendonly no
r config set key-load-delay 0
test "Active defrag eval scripts" {
r flushdb
r script flush sync
r config resetstat
r config set hz 100
r config set activedefrag no
r config set active-defrag-threshold-lower 5
r config set active-defrag-cycle-min 65
r config set active-defrag-cycle-max 75
r config set active-defrag-ignore-bytes 1500kb
r config set maxmemory 0
set n 50000
# Populate memory with interleaving script-key pattern of same size
set dummy_script "--[string repeat x 400]\nreturn "
set rd [redis_deferring_client]
for {set j 0} {$j < $n} {incr j} {
set val "$dummy_script[format "%06d" $j]"
$rd script load $val
$rd set k$j $val
}
for {set j 0} {$j < $n} {incr j} {
$rd read ; # Discard script load replies
$rd read ; # Discard set replies
}
after 120 ;# serverCron only updates the info once in 100ms
if {$::verbose} {
puts "used [s allocator_allocated]"
puts "rss [s allocator_active]"
puts "frag [s allocator_frag_ratio]"
puts "frag_bytes [s allocator_frag_bytes]"
}
assert_lessthan [s allocator_frag_ratio] 1.05
# Delete all the keys to create fragmentation
for {set j 0} {$j < $n} {incr j} { $rd del k$j }
for {set j 0} {$j < $n} {incr j} { $rd read } ; # Discard del replies
$rd close
after 120 ;# serverCron only updates the info once in 100ms
if {$::verbose} {
puts "used [s allocator_allocated]"
puts "rss [s allocator_active]"
puts "frag [s allocator_frag_ratio]"
puts "frag_bytes [s allocator_frag_bytes]"
}
assert_morethan [s allocator_frag_ratio] 1.4
catch {r config set activedefrag yes} e
if {[r config get activedefrag] eq "activedefrag yes"} {
# wait for the active defrag to start working (decision once a second)
wait_for_condition 50 100 {
[s active_defrag_running] ne 0
} else {
fail "defrag not started."
}
# wait for the active defrag to stop working
wait_for_condition 500 100 {
[s active_defrag_running] eq 0
} else {
after 120 ;# serverCron only updates the info once in 100ms
puts [r info memory]
puts [r memory malloc-stats]
fail "defrag didn't stop."
}
# test the the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
if {$::verbose} {
puts "used [s allocator_allocated]"
puts "rss [s allocator_active]"
puts "frag [s allocator_frag_ratio]"
puts "frag_bytes [s allocator_frag_bytes]"
}
assert_lessthan_equal [s allocator_frag_ratio] 1.05
}
# Flush all script to make sure we don't crash after defragging them
r script flush sync
} {OK}
test "Active defrag big keys" {
r flushdb
r config resetstat
......
......@@ -26,6 +26,12 @@ start_server {tags {"modules acl"}} {
catch {r aclcheck.set.check.key "*" v 5} e
assert_match "*DENIED KEY*" $e
assert_equal [r aclcheck.set.check.key "~" x 5] OK
assert_equal [r aclcheck.set.check.key "~" y 5] OK
assert_equal [r aclcheck.set.check.key "~" z 5] OK
catch {r aclcheck.set.check.key "~" v 5} e
assert_match "*DENIED KEY*" $e
assert_equal [r aclcheck.set.check.key "W" y 5] OK
catch {r aclcheck.set.check.key "W" v 5} e
assert_match "*DENIED KEY*" $e
......
......@@ -180,6 +180,37 @@ start_server {tags {"modules"}} {
assert_no_match "*name=myclient*" $clients
}
test {module client error stats} {
r config resetstat
# simple module command that replies with string error
assert_error "NULL reply returned" {r do_rm_call hgetalllll}
assert_equal [errorrstat NULL r] {count=1}
# module command that replies with string error from bg thread
assert_error "NULL reply returned" {r do_bg_rm_call hgetalllll}
assert_equal [errorrstat NULL r] {count=2}
# module command that returns an arity error
r do_rm_call set x x
assert_error "ERR wrong number of arguments for 'do_rm_call' command" {r do_rm_call}
assert_equal [errorrstat ERR r] {count=1}
# RM_Call that propagates an error
assert_error "WRONGTYPE*" {r do_rm_call hgetall x}
assert_equal [errorrstat WRONGTYPE r] {count=1}
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdrstat hgetall r]
# RM_Call from bg thread that propagates an error
assert_error "WRONGTYPE*" {r do_bg_rm_call hgetall x}
assert_equal [errorrstat WRONGTYPE r] {count=2}
assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat hgetall r]
assert_equal [s total_error_replies] 5
assert_match {*calls=4,*,rejected_calls=0,failed_calls=3} [cmdrstat do_rm_call r]
assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat do_bg_rm_call r]
}
test "Unload the module - blockedclient" {
assert_equal {OK} [r module unload blockedclient]
}
......
set testmodule [file normalize tests/modules/cmdintrospection.so]
start_server {tags {"modules"}} {
r module load $testmodule
# cmdintrospection.xadd mimics XADD with regards to how
# what COMMAND exposes. There are two differences:
#
# 1. cmdintrospection.xadd (and all module commands) do not have ACL categories
# 2. cmdintrospection.xadd's `group` is "module"
#
# This tests verify that, apart from the above differences, the output of
# COMMAND INFO and COMMAND DOCS are identical for the two commands.
test "Module command introspection via COMMAND INFO" {
set redis_reply [lindex [r command info xadd] 0]
set module_reply [lindex [r command info cmdintrospection.xadd] 0]
for {set i 1} {$i < [llength $redis_reply]} {incr i} {
if {$i == 2} {
# Remove the "module" flag
set mylist [lindex $module_reply $i]
set idx [lsearch $mylist "module"]
set mylist [lreplace $mylist $idx $idx]
lset module_reply $i $mylist
}
if {$i == 6} {
# Skip ACL categories
continue
}
assert_equal [lindex $redis_reply $i] [lindex $module_reply $i]
}
}
test "Module command introspection via COMMAND DOCS" {
set redis_reply [dict create {*}[lindex [r command docs xadd] 1]]
set module_reply [dict create {*}[lindex [r command docs cmdintrospection.xadd] 1]]
# Compare the maps. We need to pop "group" first.
dict unset redis_reply group
dict unset module_reply group
assert_equal $redis_reply $module_reply
}
}
set testmodule [file normalize tests/modules/getchannels.so]
start_server {tags {"modules"}} {
r module load $testmodule
# Channels are currently used to just validate ACLs, so test them here
r ACL setuser testuser +@all resetchannels &channel &pattern*
test "module getchannels-api with literals - ACL" {
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command subscribe literal channel subscribe literal pattern1]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command publish literal channel publish literal pattern1]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe literal channel unsubscribe literal pattern1]
assert_equal "This user has no permissions to access the 'nopattern1' channel" [r ACL DRYRUN testuser getchannels.command subscribe literal channel subscribe literal nopattern1]
assert_equal "This user has no permissions to access the 'nopattern1' channel" [r ACL DRYRUN testuser getchannels.command publish literal channel subscribe literal nopattern1]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe literal channel unsubscribe literal nopattern1]
assert_equal "This user has no permissions to access the 'otherchannel' channel" [r ACL DRYRUN testuser getchannels.command subscribe literal otherchannel subscribe literal pattern1]
assert_equal "This user has no permissions to access the 'otherchannel' channel" [r ACL DRYRUN testuser getchannels.command publish literal otherchannel subscribe literal pattern1]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe literal otherchannel unsubscribe literal pattern1]
}
test "module getchannels-api with patterns - ACL" {
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command subscribe pattern pattern*]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command publish pattern pattern*]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe pattern pattern*]
assert_equal "This user has no permissions to access the 'pattern1' channel" [r ACL DRYRUN testuser getchannels.command subscribe pattern pattern1 subscribe pattern pattern*]
assert_equal "This user has no permissions to access the 'pattern1' channel" [r ACL DRYRUN testuser getchannels.command publish pattern pattern1 subscribe pattern pattern*]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe pattern pattern1 unsubscribe pattern pattern*]
assert_equal "This user has no permissions to access the 'otherpattern*' channel" [r ACL DRYRUN testuser getchannels.command subscribe pattern otherpattern* subscribe pattern pattern*]
assert_equal "This user has no permissions to access the 'otherpattern*' channel" [r ACL DRYRUN testuser getchannels.command publish pattern otherpattern* subscribe pattern pattern*]
assert_equal "OK" [r ACL DRYRUN testuser getchannels.command unsubscribe pattern otherpattern* unsubscribe pattern pattern*]
}
test "Unload the module - getchannels" {
assert_equal {OK} [r module unload getchannels]
}
}
......@@ -16,32 +16,64 @@ start_server {tags {"modules"}} {
r command getkeys getkeys.command arg1 arg2 key key1 arg3 key key2 key key3
} {key1 key2 key3}
test {COMMAND GETKEYS correctly reports a movable keys module command using flags} {
r command getkeys getkeys.command_with_flags arg1 arg2 key key1 arg3 key key2 key key3
} {key1 key2 key3}
test {COMMAND GETKEYSANDFLAGS correctly reports a movable keys module command not using flags} {
r command getkeysandflags getkeys.command arg1 arg2 key key1 arg3 key key2
} {{key1 {RW access update}} {key2 {RW access update}}}
test {COMMAND GETKEYSANDFLAGS correctly reports a movable keys module command using flags} {
r command getkeysandflags getkeys.command_with_flags arg1 arg2 key key1 arg3 key key2 key key3
} {{key1 {RO access}} {key2 {RO access}} {key3 {RO access}}}
test {RM_GetCommandKeys on non-existing command} {
catch {r getkeys.introspect non-command key1 key2} e
catch {r getkeys.introspect 0 non-command key1 key2} e
set _ $e
} {*ENOENT*}
test {RM_GetCommandKeys on built-in fixed keys command} {
r getkeys.introspect set key1 value1
r getkeys.introspect 0 set key1 value1
} {key1}
test {RM_GetCommandKeys on built-in fixed keys command with flags} {
r getkeys.introspect 1 set key1 value1
} {{key1 OW}}
test {RM_GetCommandKeys on EVAL} {
r getkeys.introspect eval "" 4 key1 key2 key3 key4 arg1 arg2
r getkeys.introspect 0 eval "" 4 key1 key2 key3 key4 arg1 arg2
} {key1 key2 key3 key4}
test {RM_GetCommandKeys on a movable keys module command} {
r getkeys.introspect getkeys.command arg1 arg2 key key1 arg3 key key2 key key3
r getkeys.introspect 0 getkeys.command arg1 arg2 key key1 arg3 key key2 key key3
} {key1 key2 key3}
test {RM_GetCommandKeys on a non-movable module command} {
r getkeys.introspect getkeys.fixed arg1 key1 key2 key3 arg2
r getkeys.introspect 0 getkeys.fixed arg1 key1 key2 key3 arg2
} {key1 key2 key3}
test {RM_GetCommandKeys with bad arity} {
catch {r getkeys.introspect set key} e
catch {r getkeys.introspect 0 set key} e
set _ $e
} {*EINVAL*}
# user that can only read from "read" keys, write to "write" keys, and read+write to "RW" keys
r ACL setuser testuser +@all %R~read* %W~write* %RW~rw*
test "module getkeys-api - ACL" {
# legacy triple didn't provide flags, so they require both read and write
assert_equal "OK" [r ACL DRYRUN testuser getkeys.command key rw]
assert_equal "This user has no permissions to access the 'read' key" [r ACL DRYRUN testuser getkeys.command key read]
assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN testuser getkeys.command key write]
}
test "module getkeys-api with flags - ACL" {
assert_equal "OK" [r ACL DRYRUN testuser getkeys.command_with_flags key rw]
assert_equal "OK" [r ACL DRYRUN testuser getkeys.command_with_flags key read]
assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN testuser getkeys.command_with_flags key write]
}
test "Unload the module - getkeys" {
assert_equal {OK} [r module unload getkeys]
}
......
......@@ -64,7 +64,7 @@ start_server {tags {"modules"}} {
}
test {module info one module} {
set info [r info INFOTEST]
set info [r info INFOtest] ;# test case insensitive compare
# info all does not contain modules
assert { [string match "*Spanish*" $info] }
assert { ![string match "*used_memory*" $info] }
......@@ -72,7 +72,7 @@ start_server {tags {"modules"}} {
} {-2}
test {module info one section} {
set info [r info INFOTEST_SPANISH]
set info [r info INFOtest_SpanisH] ;# test case insensitive compare
assert { ![string match "*used_memory*" $info] }
assert { ![string match "*Italian*" $info] }
assert { ![string match "*infotest_global*" $info] }
......@@ -90,6 +90,31 @@ start_server {tags {"modules"}} {
assert_match {*infotest_unsafe_field:value=1*} $info
}
test {module info multiply sections without all, everything, default keywords} {
set info [r info replication INFOTEST]
assert { [string match "*Spanish*" $info] }
assert { ![string match "*used_memory*" $info] }
assert { [string match "*repl_offset*" $info] }
}
test {module info multiply sections with all keyword and modules} {
set info [r info all modules]
assert { [string match "*cluster*" $info] }
assert { [string match "*cmdstat_info*" $info] }
assert { [string match "*infotest_global*" $info] }
}
test {module info multiply sections with everything keyword} {
set info [r info replication everything cpu]
assert { [string match "*client_recent*" $info] }
assert { [string match "*cmdstat_info*" $info] }
assert { [string match "*Italian*" $info] }
# check that we didn't get the same info twice
assert { ![string match "*used_cpu_user_children*used_cpu_user_children*" $info] }
assert { ![string match "*Italian*Italian*" $info] }
field $info infotest_dos
} {2}
test "Unload the module - infotest" {
assert_equal {OK} [r module unload infotest]
}
......
set testmodule [file normalize tests/modules/keyspecs.so]
if 0 { ; # Test suite disabled due to planned API changes
start_server {tags {"modules"}} {
r module load $testmodule
test "Module key specs: Legacy" {
set reply [lindex [r command info kspec.legacy] 0]
# Verify (first, last, step)
test "Module key specs: No spec, only legacy triple" {
set reply [lindex [r command info kspec.none] 0]
# Verify (first, last, step) and not movablekeys
assert_equal [lindex $reply 2] {module}
assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] -1
assert_equal [lindex $reply 5] 2
# Verify key-spec auto-generated from the legacy triple
set keyspecs [lindex $reply 8]
assert_equal [llength $keyspecs] 1
assert_equal [lindex $keyspecs 0] {flags {RW access update variable_flags} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey -1 keystep 2 limit 0}}}
assert_equal [r command getkeys kspec.none key1 val1 key2 val2] {key1 key2}
}
test "Module key specs: Two ranges" {
set reply [lindex [r command info kspec.tworanges] 0]
# Verify (first, last, step) and not movablekeys
assert_equal [lindex $reply 2] {module}
assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 2
assert_equal [lindex $reply 5] 1
......@@ -14,24 +28,41 @@ start_server {tags {"modules"}} {
set keyspecs [lindex $reply 8]
assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [r command getkeys kspec.tworanges foo bar baz quux] {foo bar}
}
test "Module key specs: Keyword-only spec clears the legacy triple" {
set reply [lindex [r command info kspec.keyword] 0]
# Verify (first, last, step) and movablekeys
assert_equal [lindex $reply 2] {module movablekeys}
assert_equal [lindex $reply 3] 0
assert_equal [lindex $reply 4] 0
assert_equal [lindex $reply 5] 0
# Verify key-specs
set keyspecs [lindex $reply 8]
assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type keyword spec {keyword KEYS startfrom 1}} find_keys {type range spec {lastkey -1 keystep 1 limit 0}}}
assert_equal [r command getkeys kspec.keyword foo KEYS bar baz] {bar baz}
}
test "Module key specs: Complex specs, case 1" {
set reply [lindex [r command info kspec.complex1] 0]
# Verify (first, last, step)
# Verify (first, last, step) and movablekeys
assert_equal [lindex $reply 2] {module movablekeys}
assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 1
assert_equal [lindex $reply 5] 1
# Verify key-specs
set keyspecs [lindex $reply 8]
assert_equal [lindex $keyspecs 0] {flags {} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 0] {flags RO begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type keyword spec {keyword STORE startfrom 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 2] {flags {RO access} begin_search {type keyword spec {keyword KEYS startfrom 2}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}}
assert_equal [r command getkeys kspec.complex1 foo dummy KEYS 1 bar baz STORE quux] {foo quux bar}
}
test "Module key specs: Complex specs, case 2" {
set reply [lindex [r command info kspec.complex2] 0]
# Verify (first, last, step)
# Verify (first, last, step) and movablekeys
assert_equal [lindex $reply 2] {module movablekeys}
assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 2
assert_equal [lindex $reply 5] 1
......@@ -42,17 +73,42 @@ start_server {tags {"modules"}} {
assert_equal [lindex $keyspecs 2] {flags {RO access} begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 3] {flags {RW update} begin_search {type index spec {index 3}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}}
assert_equal [lindex $keyspecs 4] {flags {RW update} begin_search {type keyword spec {keyword MOREKEYS startfrom 5}} find_keys {type range spec {lastkey -1 keystep 1 limit 0}}}
assert_equal [r command getkeys kspec.complex2 foo bar 2 baz quux banana STORE dst dummy MOREKEYS hey ho] {dst foo bar baz quux hey ho}
}
test "Module command list filtering" {
;# Note: we piggyback this tcl file to test the general functionality of command list filtering
set reply [r command list filterby module keyspecs]
assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.legacy}
assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.keyword kspec.none kspec.tworanges}
assert_equal [r command getkeys kspec.complex2 foo bar 2 baz quux banana STORE dst dummy MOREKEYS hey ho] {dst foo bar baz quux hey ho}
}
test {COMMAND GETKEYSANDFLAGS correctly reports module key-spec without flags} {
r command getkeysandflags kspec.none key1 val1 key2 val2
} {{key1 {RW access update variable_flags}} {key2 {RW access update variable_flags}}}
test {COMMAND GETKEYSANDFLAGS correctly reports module key-spec flags} {
r command getkeysandflags kspec.keyword keys key1 key2 key3
} {{key1 {RO access}} {key2 {RO access}} {key3 {RO access}}}
# user that can only read from "read" keys, write to "write" keys, and read+write to "RW" keys
r ACL setuser testuser +@all %R~read* %W~write* %RW~rw*
test "Module key specs: No spec, only legacy triple - ACL" {
# legacy triple didn't provide flags, so they require both read and write
assert_equal "OK" [r ACL DRYRUN testuser kspec.none rw val1]
assert_equal "This user has no permissions to access the 'read' key" [r ACL DRYRUN testuser kspec.none read val1]
assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN testuser kspec.none write val1]
}
test "Module key specs: tworanges - ACL" {
assert_equal "OK" [r ACL DRYRUN testuser kspec.tworanges read write]
assert_equal "OK" [r ACL DRYRUN testuser kspec.tworanges rw rw]
assert_equal "This user has no permissions to access the 'read' key" [r ACL DRYRUN testuser kspec.tworanges rw read]
assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN testuser kspec.tworanges write rw]
}
test "Unload the module - keyspecs" {
assert_equal {OK} [r module unload keyspecs]
}
}
} ; # Test suite disabled
......@@ -8,20 +8,15 @@ start_server {tags {"modules"}} {
set command_reply [r command info subcommands.bitarray]
set first_cmd [lindex $command_reply 0]
set subcmds_in_command [lsort [lindex $first_cmd 9]]
if 0 { ; # Keyspecs disabled due to planned changes in keyspec API
assert_equal [lindex $subcmds_in_command 0] {subcommands.bitarray|get -2 module 1 1 1 {} {} {{flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}
assert_equal [lindex $subcmds_in_command 1] {subcommands.bitarray|set -2 module 1 1 1 {} {} {{flags {RW update} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}
} else { ; # The same asserts without the key specs
assert_equal [lindex $subcmds_in_command 0] {subcommands.bitarray|get -2 module 0 0 0 {} {} {} {}}
assert_equal [lindex $subcmds_in_command 1] {subcommands.bitarray|set -2 module 0 0 0 {} {} {} {}}
}
# Verify that module subcommands are displayed correctly in COMMAND DOCS
set docs_reply [r command docs subcommands.bitarray]
set docs [dict create {*}[lindex $docs_reply 1]]
set subcmds_in_cmd_docs [dict create {*}[dict get $docs subcommands]]
assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|get"] {summary {} since {} group module}
assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {summary {} since {} group module}
assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|get"] {group module}
assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {group module}
}
test "Module pure-container command fails on arity error" {
......
......@@ -47,6 +47,12 @@ tags "modules" {
}
}
test {Verify module options info} {
start_server [list overrides [list loadmodule "$testmodule"]] {
assert_match "*\[handle-io-errors|handle-repl-async-load\]*" [r info modules]
}
}
tags {repl} {
test {diskless loading short read with module} {
start_server [list overrides [list loadmodule "$testmodule"]] {
......
......@@ -26,6 +26,8 @@ start_server {tags {"modules"}} {
assert_equal "timer-incr-key" [lindex $info 0]
set remaining [lindex $info 1]
assert {$remaining < 10000 && $remaining > 1}
# Stop the timer after get timer test
assert_equal 1 [r test.stoptimer $id]
}
test {RM_StopTimer: basic sanity} {
......@@ -54,7 +56,43 @@ start_server {tags {"modules"}} {
assert_equal {} [r test.gettimer $id]
}
test "Unload the module - timer" {
test "Module can be unloaded when timer was finished" {
r set "timer-incr-key" 0
r test.createtimer 500 timer-incr-key
# Make sure the Timer has not been fired
assert_equal 0 [r get timer-incr-key]
# Module can not be unloaded since the timer was ongoing
catch {r module unload timer} err
assert_match {*the module holds timer that is not fired*} $err
# Wait to be sure timer has been finished
wait_for_condition 10 500 {
[r get timer-incr-key] == 1
} else {
fail "Timer not fired"
}
# Timer fired, can be unloaded now.
assert_equal {OK} [r module unload timer]
}
test "Module can be unloaded when timer was stopped" {
r module load $testmodule
r set "timer-incr-key" 0
set id [r test.createtimer 5000 timer-incr-key]
# Module can not be unloaded since the timer was ongoing
catch {r module unload timer} err
assert_match {*the module holds timer that is not fired*} $err
# Stop the timer
assert_equal 1 [r test.stoptimer $id]
# Make sure the Timer has not been fired
assert_equal 0 [r get timer-incr-key]
# Timer has stopped, can be unloaded now.
assert_equal {OK} [r module unload timer]
}
}
......
......@@ -132,18 +132,61 @@ start_server {tags {"multi"}} {
} {} {cluster:skip}
test {EXEC fail on lazy expired WATCHed key} {
r flushall
r del key
r debug set-active-expire 0
r del key
r set key 1 px 2
for {set j 0} {$j < 10} {incr j} {
r set key 1 px 100
r watch key
after 101
r multi
r incr key
after 100
set res [r exec]
if {$res eq {}} break
}
if {$::verbose} { puts "EXEC fail on lazy expired WATCHed key attempts: $j" }
r debug set-active-expire 1
set _ $res
} {} {needs:debug}
test {WATCH stale keys should not fail EXEC} {
r del x
r debug set-active-expire 0
r set x foo px 1
after 2
r watch x
r multi
r incr key
assert_equal [r exec] {}
r ping
assert_equal {PONG} [r exec]
r debug set-active-expire 1
} {OK} {needs:debug}
test {Delete WATCHed stale keys should not fail EXEC} {
r del x
r debug set-active-expire 0
r set x foo px 1
after 2
r watch x
# EXISTS triggers lazy expiry/deletion
assert_equal 0 [r exists x]
r multi
r ping
assert_equal {PONG} [r exec]
r debug set-active-expire 1
} {OK} {needs:debug}
test {FLUSHDB while watching stale keys should not fail EXEC} {
r del x
r debug set-active-expire 0
r set x foo px 1
after 2
r watch x
r flushdb
r multi
r ping
assert_equal {PONG} [r exec]
r debug set-active-expire 1
} {OK} {needs:debug}
......@@ -245,6 +288,52 @@ start_server {tags {"multi"}} {
r exec
} {} {singledb:skip}
test {SWAPDB does not touch watched stale keys} {
r flushall
r select 1
r debug set-active-expire 0
r set x foo px 1
after 2
r watch x
r swapdb 0 1 ; # expired key replaced with no key => no change
r multi
r ping
assert_equal {PONG} [r exec]
r debug set-active-expire 1
} {OK} {singledb:skip needs:debug}
test {SWAPDB does not touch non-existing key replaced with stale key} {
r flushall
r select 0
r debug set-active-expire 0
r set x foo px 1
after 2
r select 1
r watch x
r swapdb 0 1 ; # no key replaced with expired key => no change
r multi
r ping
assert_equal {PONG} [r exec]
r debug set-active-expire 1
} {OK} {singledb:skip needs:debug}
test {SWAPDB does not touch stale key replaced with another stale key} {
r flushall
r debug set-active-expire 0
r select 1
r set x foo px 1
r select 0
r set x bar px 1
after 2
r select 1
r watch x
r swapdb 0 1 ; # no key replaced with expired key => no change
r multi
r ping
assert_equal {PONG} [r exec]
r debug set-active-expire 1
} {OK} {singledb:skip needs:debug}
test {WATCH is able to remember the DB a key belongs to} {
r select 5
r set x 30
......
......@@ -139,13 +139,17 @@ start_server {tags {"protocol network"}} {
test {RESP3 attributes} {
r hello 3
set res [r debug protocol attrib]
# currently the parser in redis.tcl ignores the attributes
assert_equal {Some real reply following the attribute} [r debug protocol attrib]
assert_equal {key-popularity {key:123 90}} [r attributes]
# make sure attributes are not kept from previous command
r ping
assert_error {*attributes* no such element in array} {r attributes}
# restore state
r hello 2
set _ $res
} {Some real reply following the attribute} {needs:debug resp3}
set _ ""
} {} {needs:debug resp3}
test {RESP3 attributes readraw} {
r hello 3
......
proc get_reply_buffer_size {cname} {
set clients [split [string trim [r client list]] "\r\n"]
set c [lsearch -inline $clients *name=$cname*]
if {![regexp rbs=(\[a-zA-Z0-9-\]+) $c - rbufsize]} {
error "field rbus not found in $c"
}
return $rbufsize
}
start_server {tags {"replybufsize"}} {
test {verify reply buffer limits} {
# In order to reduce test time we can set the peak reset time very low
r debug replybuffer-peak-reset-time 100
# Create a simple idle test client
variable tc [redis_client]
$tc client setname test_client
# make sure the client is idle for 1 seconds to make it shrink the reply buffer
wait_for_condition 10 100 {
[get_reply_buffer_size test_client] >= 1024 && [get_reply_buffer_size test_client] < 2046
} else {
set rbs [get_reply_buffer_size test_client]
fail "reply buffer of idle client is $rbs after 1 seconds"
}
r set bigval [string repeat x 32768]
# In order to reduce test time we can set the peak reset time very low
r debug replybuffer-peak-reset-time never
wait_for_condition 10 100 {
[$tc get bigval ; get_reply_buffer_size test_client] >= 16384 && [get_reply_buffer_size test_client] < 32768
} else {
set rbs [get_reply_buffer_size test_client]
fail "reply buffer of busy client is $rbs after 1 seconds"
}
# Restore the peak reset time to default
r debug replybuffer-peak-reset-time reset
$tc close
} {0} {needs:debug}
}
\ No newline at end of file
......@@ -73,10 +73,10 @@ start_server {tags {"scripting"}} {
test {EVAL - Lua error reply -> Redis protocol type conversion} {
catch {
run_script {return {err='this is an error'}} 0
run_script {return {err='ERR this is an error'}} 0
} e
set _ $e
} {this is an error}
} {ERR this is an error}
test {EVAL - Lua table -> Redis protocol type conversion} {
run_script {return {1,2,3,'ciao',{1,2}}} 0
......@@ -378,7 +378,7 @@ start_server {tags {"scripting"}} {
r set foo bar
catch {run_script_ro {redis.call('del', KEYS[1]);} 1 foo} e
set e
} {*Write commands are not allowed from read-only scripts*}
} {ERR Write commands are not allowed from read-only scripts*}
if {$is_eval eq 1} {
# script command is only relevant for is_eval Lua
......@@ -439,12 +439,12 @@ start_server {tags {"scripting"}} {
test {Globals protection reading an undeclared global variable} {
catch {run_script {return a} 0} e
set e
} {*ERR*attempted to access * global*}
} {ERR*attempted to access * global*}
test {Globals protection setting an undeclared global*} {
catch {run_script {a=10} 0} e
set e
} {*ERR*attempted to create global*}
} {ERR*attempted to create global*}
test {Test an example script DECR_IF_GT} {
set decr_if_gt {
......@@ -599,8 +599,8 @@ start_server {tags {"scripting"}} {
} {ERR Number of keys can't be negative}
test {Scripts can handle commands with incorrect arity} {
assert_error "*Wrong number of args calling Redis command from script" {run_script "redis.call('set','invalid')" 0}
assert_error "*Wrong number of args calling Redis command from script" {run_script "redis.call('incr')" 0}
assert_error "ERR Wrong number of args calling Redis command from script*" {run_script "redis.call('set','invalid')" 0}
assert_error "ERR Wrong number of args calling Redis command from script*" {run_script "redis.call('incr')" 0}
}
test {Correct handling of reused argv (issue #1939)} {
......@@ -701,6 +701,32 @@ start_server {tags {"scripting"}} {
return redis.call("EXISTS", "key")
} 0] 0
}
test "Script ACL check" {
r acl setuser bob on {>123} {+@scripting} {+set} {~x*}
assert_equal [r auth bob 123] {OK}
# Check permission granted
assert_equal [run_script {
return redis.acl_check_cmd('set','xx',1)
} 1 xx] 1
# Check permission denied unauthorised command
assert_equal [run_script {
return redis.acl_check_cmd('hset','xx','f',1)
} 1 xx] {}
# Check permission denied unauthorised key
# Note: we don't pass the "yy" key as an argument to the script so key acl checks won't block the script
assert_equal [run_script {
return redis.acl_check_cmd('set','yy',1)
} 0] {}
# Check error due to invalid command
assert_error {ERR *Invalid command passed to redis.acl_check_cmd()*} {run_script {
return redis.acl_check_cmd('invalid-cmd','arg')
} 0}
}
}
# Start a new server since the last test in this stanza will kill the
......@@ -1262,7 +1288,7 @@ start_server {tags {"scripting"}} {
r config set maxmemory 1
# Fail to execute deny-oom command in OOM condition (backwards compatibility mode without flags)
assert_error {ERR Error running script *OOM command not allowed when used memory > 'maxmemory'.} {
assert_error {OOM command not allowed when used memory > 'maxmemory'*} {
r eval {
redis.call('set','x',1)
return 1
......@@ -1293,7 +1319,7 @@ start_server {tags {"scripting"}} {
}
test "no-writes shebang flag" {
assert_error {ERR Error running script *Write commands are not allowed from read-only scripts.} {
assert_error {ERR Write commands are not allowed from read-only scripts*} {
r eval {#!lua flags=no-writes
redis.call('set','x',1)
return 1
......@@ -1374,3 +1400,154 @@ start_server {tags {"scripting"}} {
set _ {}
} {} {external:skip}
}
# Additional eval only tests
start_server {tags {"scripting"}} {
test "Consistent eval error reporting" {
r config resetstat
r config set maxmemory 1
# Script aborted due to Redis state (OOM) should report script execution error with detailed internal error
assert_error {OOM command not allowed when used memory > 'maxmemory'*} {
r eval {return redis.call('set','x','y')} 1 x
}
assert_equal [errorrstat OOM r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
# redis.pcall() failure due to Redis state (OOM) returns lua error table with Redis error message without '-' prefix
r config resetstat
assert_equal [
r eval {
local t = redis.pcall('set','x','y')
if t['err'] == "OOM command not allowed when used memory > 'maxmemory'." then
return 1
else
return 0
end
} 1 x
] 1
# error stats were not incremented
assert_equal [errorrstat ERR r] {}
assert_equal [errorrstat OOM r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval r]
# Returning an error object from lua is handled as a valid RESP error result.
r config resetstat
assert_error {OOM command not allowed when used memory > 'maxmemory'.} {
r eval { return redis.pcall('set','x','y') } 1 x
}
assert_equal [errorrstat ERR r] {}
assert_equal [errorrstat OOM r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
r config set maxmemory 0
r config resetstat
# Script aborted due to error result of Redis command
assert_error {ERR DB index is out of range*} {
r eval {return redis.call('select',99)} 0
}
assert_equal [errorrstat ERR r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat select r]
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
# redis.pcall() failure due to error in Redis command returns lua error table with redis error message without '-' prefix
r config resetstat
assert_equal [
r eval {
local t = redis.pcall('select',99)
if t['err'] == "ERR DB index is out of range" then
return 1
else
return 0
end
} 0
] 1
assert_equal [errorrstat ERR r] {count=1} ;
assert_equal [s total_error_replies] {1}
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat select r]
assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval r]
# Script aborted due to scripting specific error state (write cmd with eval_ro) should report script execution error with detailed internal error
r config resetstat
assert_error {ERR Write commands are not allowed from read-only scripts*} {
r eval_ro {return redis.call('set','x','y')} 1 x
}
assert_equal [errorrstat ERR r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval_ro r]
# redis.pcall() failure due to scripting specific error state (write cmd with eval_ro) returns lua error table with Redis error message without '-' prefix
r config resetstat
assert_equal [
r eval_ro {
local t = redis.pcall('set','x','y')
if t['err'] == "ERR Write commands are not allowed from read-only scripts." then
return 1
else
return 0
end
} 1 x
] 1
assert_equal [errorrstat ERR r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval_ro r]
r config resetstat
# make sure geoadd will failed
r set Sicily 1
assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {
r eval {return redis.call('GEOADD', 'Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania')} 1 x
}
assert_equal [errorrstat WRONGTYPE r] {count=1}
assert_equal [s total_error_replies] {1}
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat geoadd r]
assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
} {} {cluster:skip}
test "LUA redis.error_reply API" {
r config resetstat
assert_error {MY_ERR_CODE custom msg} {
r eval {return redis.error_reply("MY_ERR_CODE custom msg")} 0
}
assert_equal [errorrstat MY_ERR_CODE r] {count=1}
}
test "LUA redis.error_reply API with empty string" {
r config resetstat
assert_error {ERR} {
r eval {return redis.error_reply("")} 0
}
assert_equal [errorrstat ERR r] {count=1}
}
test "LUA redis.status_reply API" {
r config resetstat
r readraw 1
assert_equal [
r eval {return redis.status_reply("MY_OK_CODE custom msg")} 0
] {+MY_OK_CODE custom msg}
r readraw 0
assert_equal [errorrstat MY_ERR_CODE r] {} ;# error stats were not incremented
}
test "LUA test pcall" {
assert_equal [
r eval {local status, res = pcall(function() return 1 end); return 'status: ' .. tostring(status) .. ' result: ' .. res} 0
] {status: true result: 1}
}
test "LUA test pcall with error" {
assert_match {status: false result:*Script attempted to access nonexistent global variable 'foo'} [
r eval {local status, res = pcall(function() return foo end); return 'status: ' .. tostring(status) .. ' result: ' .. res} 0
]
}
}
......@@ -281,14 +281,20 @@ start_server {
}
test {XGROUP DESTROY should unblock XREADGROUP with -NOGROUP} {
r config resetstat
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
wait_for_blocked_clients_count 1
r XGROUP DESTROY mystream mygroup
assert_error "*NOGROUP*" {$rd read}
assert_error "NOGROUP*" {$rd read}
$rd close
# verify command stats, error stats and error counter work on failed blocked command
assert_match {*count=1*} [errorrstat NOGROUP r]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdrstat xreadgroup r]
assert_equal [s total_error_replies] 1
}
test {RENAME can unblock XREADGROUP with data} {
......@@ -355,24 +361,22 @@ start_server {
# Delete item 2 from the stream. Now consumer 1 has PEL that contains
# only item 3. Try to use consumer 2 to claim the deleted item 2
# from the PEL of consumer 1, this should return nil
# from the PEL of consumer 1, this should be NOP
r XDEL mystream $id2
set reply [
r XCLAIM mystream mygroup consumer2 10 $id2
]
assert {[llength $reply] == 1}
assert_equal "" [lindex $reply 0]
assert {[llength $reply] == 0}
# Delete item 3 from the stream. Now consumer 1 has PEL that is empty.
# Try to use consumer 2 to claim the deleted item 3 from the PEL
# of consumer 1, this should return nil
# of consumer 1, this should be NOP
after 200
r XDEL mystream $id3
set reply [
r XCLAIM mystream mygroup consumer2 10 $id3
]
assert {[llength $reply] == 1}
assert_equal "" [lindex $reply 0]
assert {[llength $reply] == 0}
}
test {XCLAIM without JUSTID increments delivery count} {
......@@ -445,6 +449,7 @@ start_server {
set id1 [r XADD mystream * a 1]
set id2 [r XADD mystream * b 2]
set id3 [r XADD mystream * c 3]
set id4 [r XADD mystream * d 4]
r XGROUP CREATE mystream mygroup 0
# Consumer 1 reads item 1 from the stream without acknowledgements.
......@@ -454,7 +459,7 @@ start_server {
assert_equal [lindex $reply 0 1 0 1] {a 1}
after 200
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 1]
assert_equal [llength $reply] 2
assert_equal [llength $reply] 3
assert_equal [lindex $reply 0] "0-0"
assert_equal [llength [lindex $reply 1]] 1
assert_equal [llength [lindex $reply 1 0]] 2
......@@ -462,7 +467,7 @@ start_server {
assert_equal [lindex $reply 1 0 1] {a 1}
# Consumer 1 reads another 2 items from stream
r XREADGROUP GROUP mygroup consumer1 count 2 STREAMS mystream >
r XREADGROUP GROUP mygroup consumer1 count 3 STREAMS mystream >
# For min-idle-time
after 200
......@@ -471,33 +476,37 @@ start_server {
# only item 3. Try to use consumer 2 to claim the deleted item 2
# from the PEL of consumer 1, this should return nil
r XDEL mystream $id2
# id1 and id3 are self-claimed here but not id2 ('count' was set to 2)
# we make sure id2 is indeed skipped (the cursor points to id4)
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 2]
# id1 is self-claimed here but not id2 ('count' was set to 2)
assert_equal [llength $reply] 2
assert_equal [lindex $reply 0] $id3
assert_equal [llength $reply] 3
assert_equal [lindex $reply 0] $id4
assert_equal [llength [lindex $reply 1]] 2
assert_equal [llength [lindex $reply 1 0]] 2
assert_equal [llength [lindex $reply 1 0 1]] 2
assert_equal [lindex $reply 1 0 1] {a 1}
assert_equal [lindex $reply 1 1] ""
assert_equal [lindex $reply 1 1 1] {c 3}
# Delete item 3 from the stream. Now consumer 1 has PEL that is empty.
# Try to use consumer 2 to claim the deleted item 3 from the PEL
# of consumer 1, this should return nil
after 200
r XDEL mystream $id3
r XDEL mystream $id4
# id1 and id3 are self-claimed here but not id2 and id4 ('count' is default 100)
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - JUSTID]
# id1 is self-claimed here but not id2 and id3 ('count' is default 100)
# we also test the JUSTID modifier here. note that, when using JUSTID,
# deleted entries are returned in reply (consistent with XCLAIM).
assert_equal [llength $reply] 2
assert_equal [lindex $reply 0] "0-0"
assert_equal [llength [lindex $reply 1]] 3
assert_equal [llength $reply] 3
assert_equal [lindex $reply 0] {0-0}
assert_equal [llength [lindex $reply 1]] 2
assert_equal [lindex $reply 1 0] $id1
assert_equal [lindex $reply 1 1] $id2
assert_equal [lindex $reply 1 2] $id3
assert_equal [lindex $reply 1 1] $id3
}
test {XAUTOCLAIM as an iterator} {
......@@ -518,7 +527,7 @@ start_server {
# Claim 2 entries
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 2]
assert_equal [llength $reply] 2
assert_equal [llength $reply] 3
set cursor [lindex $reply 0]
assert_equal $cursor $id3
assert_equal [llength [lindex $reply 1]] 2
......@@ -527,7 +536,7 @@ start_server {
# Claim 2 more entries
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 $cursor COUNT 2]
assert_equal [llength $reply] 2
assert_equal [llength $reply] 3
set cursor [lindex $reply 0]
assert_equal $cursor $id5
assert_equal [llength [lindex $reply 1]] 2
......@@ -536,7 +545,7 @@ start_server {
# Claim last entry
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 $cursor COUNT 1]
assert_equal [llength $reply] 2
assert_equal [llength $reply] 3
set cursor [lindex $reply 0]
assert_equal $cursor {0-0}
assert_equal [llength [lindex $reply 1]] 1
......@@ -548,6 +557,56 @@ start_server {
assert_error "ERR COUNT must be > 0" {r XAUTOCLAIM key group consumer 1 1 COUNT 0}
}
test {XCLAIM with XDEL} {
r DEL x
r XADD x 1-0 f v
r XADD x 2-0 f v
r XADD x 3-0 f v
r XGROUP CREATE x grp 0
assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}}
r XDEL x 2-0
assert_equal [r XCLAIM x grp Bob 0 1-0 2-0 3-0] {{1-0 {f v}} {3-0 {f v}}}
assert_equal [r XPENDING x grp - + 10 Alice] {}
}
test {XCLAIM with trimming} {
r DEL x
r config set stream-node-max-entries 2
r XADD x 1-0 f v
r XADD x 2-0 f v
r XADD x 3-0 f v
r XGROUP CREATE x grp 0
assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}}
r XTRIM x MAXLEN 1
assert_equal [r XCLAIM x grp Bob 0 1-0 2-0 3-0] {{3-0 {f v}}}
assert_equal [r XPENDING x grp - + 10 Alice] {}
}
test {XAUTOCLAIM with XDEL} {
r DEL x
r XADD x 1-0 f v
r XADD x 2-0 f v
r XADD x 3-0 f v
r XGROUP CREATE x grp 0
assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}}
r XDEL x 2-0
assert_equal [r XAUTOCLAIM x grp Bob 0 0-0] {0-0 {{1-0 {f v}} {3-0 {f v}}} 2-0}
assert_equal [r XPENDING x grp - + 10 Alice] {}
}
test {XCLAIM with trimming} {
r DEL x
r config set stream-node-max-entries 2
r XADD x 1-0 f v
r XADD x 2-0 f v
r XADD x 3-0 f v
r XGROUP CREATE x grp 0
assert_equal [r XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}}}}}
r XTRIM x MAXLEN 1
assert_equal [r XAUTOCLAIM x grp Bob 0 0-0] {0-0 {{3-0 {f v}}} {1-0 2-0}}
assert_equal [r XPENDING x grp - + 10 Alice] {}
}
test {XINFO FULL output} {
r del x
r XADD x 100 a 1
......@@ -564,22 +623,30 @@ start_server {
r XDEL x 103
set reply [r XINFO STREAM x FULL]
assert_equal [llength $reply] 12
assert_equal [lindex $reply 1] 4 ;# stream length
assert_equal [lindex $reply 9] "{100-0 {a 1}} {101-0 {b 1}} {102-0 {c 1}} {104-0 {f 1}}" ;# entries
assert_equal [lindex $reply 11 0 1] "g1" ;# first group name
assert_equal [lindex $reply 11 0 7 0 0] "100-0" ;# first entry in group's PEL
assert_equal [lindex $reply 11 0 9 0 1] "Alice" ;# first consumer
assert_equal [lindex $reply 11 0 9 0 7 0 0] "100-0" ;# first entry in first consumer's PEL
assert_equal [lindex $reply 11 1 1] "g2" ;# second group name
assert_equal [lindex $reply 11 1 9 0 1] "Charlie" ;# first consumer
assert_equal [lindex $reply 11 1 9 0 7 0 0] "100-0" ;# first entry in first consumer's PEL
assert_equal [lindex $reply 11 1 9 0 7 1 0] "101-0" ;# second entry in first consumer's PEL
assert_equal [llength $reply] 18
assert_equal [dict get $reply length] 4
assert_equal [dict get $reply entries] "{100-0 {a 1}} {101-0 {b 1}} {102-0 {c 1}} {104-0 {f 1}}"
# First consumer group
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group name] "g1"
assert_equal [lindex [dict get $group pending] 0 0] "100-0"
set consumer [lindex [dict get $group consumers] 0]
assert_equal [dict get $consumer name] "Alice"
assert_equal [lindex [dict get $consumer pending] 0 0] "100-0" ;# first entry in first consumer's PEL
# Second consumer group
set group [lindex [dict get $reply groups] 1]
assert_equal [dict get $group name] "g2"
set consumer [lindex [dict get $group consumers] 0]
assert_equal [dict get $consumer name] "Charlie"
assert_equal [lindex [dict get $consumer pending] 0 0] "100-0" ;# first entry in first consumer's PEL
assert_equal [lindex [dict get $consumer pending] 1 0] "101-0" ;# second entry in first consumer's PEL
set reply [r XINFO STREAM x FULL COUNT 1]
assert_equal [llength $reply] 12
assert_equal [lindex $reply 1] 4
assert_equal [lindex $reply 9] "{100-0 {a 1}}"
assert_equal [llength $reply] 18
assert_equal [dict get $reply length] 4
assert_equal [dict get $reply entries] "{100-0 {a 1}}"
}
test {XGROUP CREATECONSUMER: create consumer if does not exist} {
......@@ -643,7 +710,7 @@ start_server {
set grpinfo [r xinfo groups mystream]
r debug loadaof
assert {[r xinfo groups mystream] == $grpinfo}
assert_equal [r xinfo groups mystream] $grpinfo
set reply [r xinfo consumers mystream mygroup]
set consumer_info [lindex $reply 0]
assert_equal [lindex $consumer_info 1] "Alice" ;# consumer name
......@@ -682,6 +749,154 @@ start_server {
}
}
test {Consumer group read counter and lag in empty streams} {
r DEL x
r XGROUP CREATE x g1 0 MKSTREAM
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $reply max-deleted-entry-id] "0-0"
assert_equal [dict get $reply entries-added] 0
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] 0
r XADD x 1-0 data a
r XDEL x 1-0
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $reply max-deleted-entry-id] "1-0"
assert_equal [dict get $reply entries-added] 1
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] 0
}
test {Consumer group read counter and lag sanity} {
r DEL x
r XADD x 1-0 data a
r XADD x 2-0 data b
r XADD x 3-0 data c
r XADD x 4-0 data d
r XADD x 5-0 data e
r XGROUP CREATE x g1 0
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] 5
r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 1
assert_equal [dict get $group lag] 4
r XREADGROUP GROUP g1 c12 COUNT 10 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 5
assert_equal [dict get $group lag] 0
r XADD x 6-0 data f
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 5
assert_equal [dict get $group lag] 1
}
test {Consumer group lag with XDELs} {
r DEL x
r XADD x 1-0 data a
r XADD x 2-0 data b
r XADD x 3-0 data c
r XADD x 4-0 data d
r XADD x 5-0 data e
r XDEL x 3-0
r XGROUP CREATE x g1 0
r XGROUP CREATE x g2 0
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] {}
r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] {}
r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] {}
r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] {}
r XREADGROUP GROUP g1 c11 COUNT 1 STREAMS x >
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 5
assert_equal [dict get $group lag] 0
r XADD x 6-0 data f
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 5
assert_equal [dict get $group lag] 1
r XTRIM x MINID = 3-0
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 5
assert_equal [dict get $group lag] 1
set group [lindex [dict get $reply groups] 1]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] 3
r XTRIM x MINID = 5-0
set reply [r XINFO STREAM x FULL]
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 5
assert_equal [dict get $group lag] 1
set group [lindex [dict get $reply groups] 1]
assert_equal [dict get $group entries-read] {}
assert_equal [dict get $group lag] 2
}
test {Loading from legacy (Redis <= v6.2.x, rdb_ver < 10) persistence} {
# The payload was DUMPed from a v5 instance after:
# XADD x 1-0 data a
# XADD x 2-0 data b
# XADD x 3-0 data c
# XADD x 4-0 data d
# XADD x 5-0 data e
# XADD x 6-0 data f
# XDEL x 3-0
# XGROUP CREATE x g1 0
# XGROUP CREATE x g2 0
# XREADGROUP GROUP g1 c11 COUNT 4 STREAMS x >
# XTRIM x MAXLEN = 2
r DEL x
r RESTORE x 0 "\x0F\x01\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4A\x40\x57\x16\x57\x00\x00\x00\x23\x00\x02\x01\x04\x01\x01\x01\x84\x64\x61\x74\x61\x05\x00\x01\x03\x01\x00\x20\x01\x03\x81\x61\x02\x04\x20\x0A\x00\x01\x40\x0A\x00\x62\x60\x0A\x00\x02\x40\x0A\x00\x63\x60\x0A\x40\x22\x01\x81\x64\x20\x0A\x40\x39\x20\x0A\x00\x65\x60\x0A\x00\x05\x40\x0A\x00\x66\x20\x0A\x00\xFF\x02\x06\x00\x02\x02\x67\x31\x05\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x3E\xF7\x83\x43\x7A\x01\x00\x00\x01\x01\x03\x63\x31\x31\x3E\xF7\x83\x43\x7A\x01\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x02\x67\x32\x00\x00\x00\x00\x09\x00\x3D\x52\xEF\x68\x67\x52\x1D\xFA"
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply max-deleted-entry-id] "0-0"
assert_equal [dict get $reply entries-added] 2
set group [lindex [dict get $reply groups] 0]
assert_equal [dict get $group entries-read] 1
assert_equal [dict get $group lag] 1
set group [lindex [dict get $reply groups] 1]
assert_equal [dict get $group entries-read] 0
assert_equal [dict get $group lag] 2
}
start_server {tags {"external:skip"}} {
set master [srv -1 client]
set master_host [srv -1 host]
......@@ -733,6 +948,46 @@ start_server {
}
}
start_server {tags {"external:skip"}} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set replica [srv 0 client]
foreach autoclaim {0 1} {
test "Replication tests of XCLAIM with deleted entries (autclaim=$autoclaim)" {
$replica replicaof $master_host $master_port
wait_for_condition 50 100 {
[s 0 master_link_status] eq {up}
} else {
fail "Replication not started."
}
$master DEL x
$master XADD x 1-0 f v
$master XADD x 2-0 f v
$master XADD x 3-0 f v
$master XADD x 4-0 f v
$master XADD x 5-0 f v
$master XGROUP CREATE x grp 0
assert_equal [$master XREADGROUP GROUP grp Alice STREAMS x >] {{x {{1-0 {f v}} {2-0 {f v}} {3-0 {f v}} {4-0 {f v}} {5-0 {f v}}}}}
wait_for_ofs_sync $master $replica
assert_equal [llength [$replica XPENDING x grp - + 10 Alice]] 5
$master XDEL x 2-0
$master XDEL x 4-0
if {$autoclaim} {
assert_equal [$master XAUTOCLAIM x grp Bob 0 0-0] {0-0 {{1-0 {f v}} {3-0 {f v}} {5-0 {f v}}} {2-0 4-0}}
wait_for_ofs_sync $master $replica
assert_equal [llength [$replica XPENDING x grp - + 10 Alice]] 0
} else {
assert_equal [$master XCLAIM x grp Bob 0 1-0 2-0 3-0 4-0] {{1-0 {f v}} {3-0 {f v}}}
wait_for_ofs_sync $master $replica
assert_equal [llength [$replica XPENDING x grp - + 10 Alice]] 1
}
}
}
}
start_server {tags {"stream needs:debug"} overrides {appendonly yes aof-use-rdb-preamble no}} {
test {Empty stream with no lastid can be rewrite into AOF correctly} {
r XGROUP CREATE mystream group-name $ MKSTREAM
......@@ -742,7 +997,7 @@ start_server {
waitForBgrewriteaof r
r debug loadaof
assert {[dict get [r xinfo stream mystream] length] == 0}
assert {[r xinfo groups mystream] == $grpinfo}
assert_equal [r xinfo groups mystream] $grpinfo
}
}
}
......@@ -760,7 +760,9 @@ start_server {tags {"stream xsetid"}} {
test {XSETID can set a specific ID} {
r XSETID mystream "200-0"
assert {[dict get [r xinfo stream mystream] last-generated-id] == "200-0"}
set reply [r XINFO stream mystream]
assert_equal [dict get $reply last-generated-id] "200-0"
assert_equal [dict get $reply entries-added] 1
}
test {XSETID cannot SETID with smaller ID} {
......@@ -774,6 +776,98 @@ start_server {tags {"stream xsetid"}} {
catch {r XSETID stream 1-1} err
set _ $err
} {ERR no such key}
test {XSETID cannot run with an offset but without a maximal tombstone} {
catch {r XSETID stream 1-1 0} err
set _ $err
} {ERR syntax error}
test {XSETID cannot run with a maximal tombstone but without an offset} {
catch {r XSETID stream 1-1 0-0} err
set _ $err
} {ERR syntax error}
test {XSETID errors on negstive offset} {
catch {r XSETID stream 1-1 ENTRIESADDED -1 MAXDELETEDID 0-0} err
set _ $err
} {ERR*must be positive}
test {XSETID cannot set the maximal tombstone with larger ID} {
r DEL x
r XADD x 1-0 a b
catch {r XSETID x "1-0" ENTRIESADDED 1 MAXDELETEDID "2-0" } err
r XADD mystream MAXLEN 0 * a b
set err
} {ERR*smaller*}
test {XSETID cannot set the offset to less than the length} {
r DEL x
r XADD x 1-0 a b
catch {r XSETID x "1-0" ENTRIESADDED 0 MAXDELETEDID "0-0" } err
r XADD mystream MAXLEN 0 * a b
set err
} {ERR*smaller*}
}
start_server {tags {"stream offset"}} {
test {XADD advances the entries-added counter and sets the recorded-first-entry-id} {
r DEL x
r XADD x 1-0 data a
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply entries-added] 1
assert_equal [dict get $reply recorded-first-entry-id] "1-0"
r XADD x 2-0 data a
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply entries-added] 2
assert_equal [dict get $reply recorded-first-entry-id] "1-0"
}
test {XDEL/TRIM are reflected by recorded first entry} {
r DEL x
r XADD x 1-0 data a
r XADD x 2-0 data a
r XADD x 3-0 data a
r XADD x 4-0 data a
r XADD x 5-0 data a
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply entries-added] 5
assert_equal [dict get $reply recorded-first-entry-id] "1-0"
r XDEL x 2-0
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply recorded-first-entry-id] "1-0"
r XDEL x 1-0
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply recorded-first-entry-id] "3-0"
r XTRIM x MAXLEN = 2
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply recorded-first-entry-id] "4-0"
}
test {Maxmimum XDEL ID behaves correctly} {
r DEL x
r XADD x 1-0 data a
r XADD x 2-0 data b
r XADD x 3-0 data c
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply max-deleted-entry-id] "0-0"
r XDEL x 2-0
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply max-deleted-entry-id] "2-0"
r XDEL x 1-0
set reply [r XINFO STREAM x FULL]
assert_equal [dict get $reply max-deleted-entry-id] "2-0"
}
}
start_server {tags {"stream needs:debug"} overrides {appendonly yes aof-use-rdb-preamble no}} {
......@@ -796,7 +890,7 @@ start_server {tags {"stream needs:debug"} overrides {appendonly yes aof-use-rdb-
waitForBgrewriteaof r
r debug loadaof
assert {[dict get [r xinfo stream mystream] length] == 1}
assert {[dict get [r xinfo stream mystream] last-generated-id] == "2-2"}
assert_equal [dict get [r xinfo stream mystream] last-generated-id] "2-2"
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment