summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/unit/moduleapi
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
commitdcacc00e3750300617ba6e16eb346713f91a783a (patch)
tree38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/redis-unstable/tests/unit/moduleapi
parent58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff)
downloadcrep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz
Remove testing data
Diffstat (limited to 'examples/redis-unstable/tests/unit/moduleapi')
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl207
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl437
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/auth.tcl90
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/basics.tcl70
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl310
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl124
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl366
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/cluster.tcl227
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl50
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl175
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl227
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/crash.tcl129
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/datatype.tcl238
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl232
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/defrag.tcl101
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl28
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/fork.tcl41
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl40
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl89
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/hash.tcl176
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/hooks.tcl321
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/infotest.tcl131
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/infra.tcl25
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl287
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl910
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl140
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl160
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/list.tcl225
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl21
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/misc.tcl617
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl405
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl386
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl219
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/propagate.tcl806
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/publish.tcl34
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl203
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/reply.tcl152
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/scan.tcl103
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/stream.tcl176
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl61
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl32
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl311
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/timer.tcl99
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/usercall.tcl186
-rw-r--r--examples/redis-unstable/tests/unit/moduleapi/zset.tcl121
45 files changed, 0 insertions, 9488 deletions
diff --git a/examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl b/examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl
deleted file mode 100644
index aa571b7..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/aclcheck.tcl
+++ /dev/null
@@ -1,207 +0,0 @@
-set testmodule [file normalize tests/modules/aclcheck.so]
-
-start_server {tags {"modules acl external:skip"}} {
- r module load $testmodule
-
- test {test module check acl for command perm} {
- # by default all commands allowed
- assert_equal [r aclcheck.rm_call.check.cmd set x 5] OK
- # block SET command for user
- r acl setuser default -set
- catch {r aclcheck.rm_call.check.cmd set x 5} e
- assert_match {*DENIED CMD*} $e
-
- # verify that new log entry added
- set entry [lindex [r ACL LOG] 0]
- assert {[dict get $entry username] eq {default}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry object] eq {set}}
- assert {[dict get $entry reason] eq {command}}
- }
-
- test {test module check acl for key prefix permission} {
- r acl setuser default +set resetkeys ~CART* %W~ORDER* %R~PRODUCT* ~ESCAPED_STAR\\* ~NON_ESCAPED_STAR\\\\*
-
- # check for key permission of prefix CART* (READ+WRITE)
- catch {r aclcheck.set.check.prefixkey "~" CAR CART_CLOTHES_7 5} e
- assert_match "*DENIED KEY*" $e
- assert_equal [r aclcheck.set.check.prefixkey "~" CART CART 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "W" CART_BOOKS CART_BOOKS_12 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "R" CART_CLOTHES CART_CLOTHES_7 5] OK
-
- # check for key permission of prefix ORDER* (WRITE)
- catch {r aclcheck.set.check.prefixkey "~" ORDE ORDER_2024_155351 5} e
- assert_match "*DENIED KEY*" $e
- assert_equal [r aclcheck.set.check.prefixkey "~" ORDER ORDER 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "W" ORDER_2024 ORDER_2024_564879 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "~" ORDER_2023 ORDER_2023_564879 5] OK
- catch {r aclcheck.set.check.prefixkey "R" ORDER_2023 ORDER_2023_564879 5}
- assert_match "*DENIED KEY*" $e
-
- # check for key permission of prefix PRODUCT* (READ)
- catch {r aclcheck.set.check.prefixkey "~" PRODUC PRODUCT_CLOTHES_753376 5} e
- assert_match "*DENIED KEY*" $e
- assert_equal [r aclcheck.set.check.prefixkey "~" PRODUCT PRODUCT 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "~" PRODUCT_BOOKS PRODUCT_BOOKS_753376 5] OK
-
- # pattern ends with a escaped '*' character should not be counted as a prefix
- catch {r aclcheck.set.check.prefixkey "~" ESCAPED_STAR ESCAPED_STAR_12 5} e
- assert_match "*DENIED KEY*" $e
- catch {r aclcheck.set.check.prefixkey "~" ESCAPED_STAR* ESCAPED_STAR* 5} e
- assert_match "*DENIED KEY*" $e
- assert_equal [r aclcheck.set.check.prefixkey "~" NON_ESCAPED_STAR\\ NON_ESCAPED_STAR\\clothes 5] OK
- }
-
- test {check ACL permissions versus empty string prefix} {
- # The empty string should should match all keys permissions
- r acl setuser default +set resetkeys %R~* %W~* ~*
- assert_equal [r aclcheck.set.check.prefixkey "~" "" CART_BOOKS_12 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "W" "" ORDER_2024_564879 5] OK
- assert_equal [r aclcheck.set.check.prefixkey "R" "" PRODUCT_BOOKS_753376 5] OK
-
- # The empty string prefix should not match if cannot access all keys
- r acl setuser default +set resetkeys %R~x* %W~x* ~x*
- catch {r aclcheck.set.check.prefixkey "~" "" CART_BOOKS_12 5} e
- assert_match "*DENIED KEY*" $e
- }
-
- test {test module check acl for key perm} {
- # give permission for SET and block all keys but x(READ+WRITE), y(WRITE), z(READ)
- r acl setuser default +set resetkeys ~x %W~y %R~z ~ESCAPED_STAR\\*
-
- assert_equal [r aclcheck.set.check.key "*" x 5] OK
- 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
-
- assert_equal [r aclcheck.set.check.key "R" z 5] OK
- catch {r aclcheck.set.check.key "R" v 5} e
- assert_match "*DENIED KEY*" $e
-
- # check pattern ends with escaped '*' character
- assert_equal [r aclcheck.set.check.key "~" ESCAPED_STAR* 5] OK
- }
-
- test {test module check acl for module user} {
- # the module user has access to all keys
- assert_equal [r aclcheck.rm_call.check.cmd.module.user set y 5] OK
- }
-
- test {test module check acl for channel perm} {
- # block all channels but ch1
- r acl setuser default resetchannels &ch1
- assert_equal [r aclcheck.publish.check.channel ch1 msg] 0
- catch {r aclcheck.publish.check.channel ch2 msg} e
- set e
- } {*DENIED CHANNEL*}
-
- test {test module check acl in rm_call} {
- # rm call check for key permission (x: READ + WRITE)
- assert_equal [r aclcheck.rm_call set x 5] OK
- assert_equal [r aclcheck.rm_call set x 6 get] 5
-
- # rm call check for key permission (y: only WRITE)
- assert_equal [r aclcheck.rm_call set y 5] OK
- assert_error {*NOPERM*} {r aclcheck.rm_call set y 5 get}
- assert_error {*NOPERM*No permissions to access a key*} {r aclcheck.rm_call_with_errors set y 5 get}
-
- # rm call check for key permission (z: only READ)
- assert_error {*NOPERM*} {r aclcheck.rm_call set z 5}
- catch {r aclcheck.rm_call_with_errors set z 5} e
- assert_match {*NOPERM*No permissions to access a key*} $e
- assert_error {*NOPERM*} {r aclcheck.rm_call set z 6 get}
- assert_error {*NOPERM*No permissions to access a key*} {r aclcheck.rm_call_with_errors set z 6 get}
-
- # verify that new log entry added
- set entry [lindex [r ACL LOG] 0]
- assert {[dict get $entry username] eq {default}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry object] eq {z}}
- assert {[dict get $entry reason] eq {key}}
-
- # rm call check for command permission
- r acl setuser default -set
- assert_error {*NOPERM*} {r aclcheck.rm_call set x 5}
- assert_error {*NOPERM*has no permissions to run the 'set' command*} {r aclcheck.rm_call_with_errors set x 5}
-
- # verify that new log entry added
- set entry [lindex [r ACL LOG] 0]
- assert {[dict get $entry username] eq {default}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry object] eq {set}}
- assert {[dict get $entry reason] eq {command}}
- }
-
- test {test blocking of Commands outside of OnLoad} {
- assert_equal [r block.commands.outside.onload] OK
- }
-
- test {test users to have access to module commands having acl categories} {
- r acl SETUSER j1 on >password -@all +@WRITE
- r acl SETUSER j2 on >password -@all +@READ
- assert_equal [r acl DRYRUN j1 aclcheck.module.command.aclcategories.write] OK
- assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.write.function.read.category] OK
- assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.read.only.category] OK
- }
-
- test {Unload the module - aclcheck} {
- assert_equal {OK} [r module unload aclcheck]
- }
-}
-
-start_server {tags {"modules acl external:skip"}} {
- test {test existing users to have access to module commands loaded on runtime} {
- r acl SETUSER j3 on >password -@all +@WRITE
- assert_equal [r module load $testmodule] OK
- assert_equal [r acl DRYRUN j3 aclcheck.module.command.aclcategories.write] OK
- assert_equal {OK} [r module unload aclcheck]
- }
-}
-
-start_server {tags {"modules acl external:skip"}} {
- test {test existing users without permissions, do not have access to module commands loaded on runtime.} {
- r acl SETUSER j4 on >password -@all +@READ
- r acl SETUSER j5 on >password -@all +@WRITE
- assert_equal [r module load $testmodule] OK
- catch {r acl DRYRUN j4 aclcheck.module.command.aclcategories.write} e
- assert_equal {User j4 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e
- catch {r acl DRYRUN j5 aclcheck.module.command.aclcategories.write.function.read.category} e
- assert_equal {User j5 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e
- }
-
- test {test users without permissions, do not have access to module commands.} {
- r acl SETUSER j6 on >password -@all +@READ
- catch {r acl DRYRUN j6 aclcheck.module.command.aclcategories.write} e
- assert_equal {User j6 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e
- r acl SETUSER j7 on >password -@all +@WRITE
- catch {r acl DRYRUN j7 aclcheck.module.command.aclcategories.write.function.read.category} e
- assert_equal {User j7 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e
- }
-
- test {test if foocategory acl categories is added} {
- r acl SETUSER j8 on >password -@all +@foocategory
- assert_equal [r acl DRYRUN j8 aclcheck.module.command.test.add.new.aclcategories] OK
- }
-
- test {test permission compaction and simplification for categories added by a module} {
- r acl SETUSER j9 on >password -@all +@foocategory -@foocategory
- catch {r ACL GETUSER j9} res
- assert_equal {-@all -@foocategory} [lindex $res 5]
- assert_equal {OK} [r module unload aclcheck]
- }
-}
-
-start_server {tags {"modules acl external:skip"}} {
- test {test module load fails if exceeds the maximum number of adding acl categories} {
- assert_error {ERR Error loading the extension. Please check the server logs.} {r module load $testmodule 1}
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl b/examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl
deleted file mode 100644
index d645de4..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/async_rm_call.tcl
+++ /dev/null
@@ -1,437 +0,0 @@
-set testmodule [file normalize tests/modules/blockedclient.so]
-set testmodule2 [file normalize tests/modules/postnotifications.so]
-set testmodule3 [file normalize tests/modules/blockonkeys.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Locked GIL acquisition from async RM_Call} {
- assert_equal {OK} [r do_rm_call_async acquire_gil]
- }
-
- test "Blpop on async RM_Call fire and forget" {
- assert_equal {Blocked} [r do_rm_call_fire_and_forget blpop l 0]
- r lpush l a
- assert_equal {0} [r llen l]
- }
-
- test "Blpop on threaded async RM_Call" {
- set rd [redis_deferring_client]
-
- $rd do_rm_call_async_on_thread blpop l 0
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {l a}
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- foreach cmd {do_rm_call_async do_rm_call_async_script_mode } {
-
- test "Blpop on async RM_Call using $cmd" {
- set rd [redis_deferring_client]
-
- $rd $cmd blpop l 0
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {l a}
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test "Brpop on async RM_Call using $cmd" {
- set rd [redis_deferring_client]
-
- $rd $cmd brpop l 0
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {l a}
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test "Brpoplpush on async RM_Call using $cmd" {
- set rd [redis_deferring_client]
-
- $rd $cmd brpoplpush l1 l2 0
- wait_for_blocked_clients_count 1
- r lpush l1 a
- assert_equal [$rd read] {a}
- wait_for_blocked_clients_count 0
- $rd close
- r lpop l2
- } {a}
-
- test "Blmove on async RM_Call using $cmd" {
- set rd [redis_deferring_client]
-
- $rd $cmd blmove l1 l2 LEFT LEFT 0
- wait_for_blocked_clients_count 1
- r lpush l1 a
- assert_equal [$rd read] {a}
- wait_for_blocked_clients_count 0
- $rd close
- r lpop l2
- } {a}
-
- test "Bzpopmin on async RM_Call using $cmd" {
- set rd [redis_deferring_client]
-
- $rd $cmd bzpopmin s 0
- wait_for_blocked_clients_count 1
- r zadd s 10 foo
- assert_equal [$rd read] {s foo 10}
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test "Bzpopmax on async RM_Call using $cmd" {
- set rd [redis_deferring_client]
-
- $rd $cmd bzpopmax s 0
- wait_for_blocked_clients_count 1
- r zadd s 10 foo
- assert_equal [$rd read] {s foo 10}
- wait_for_blocked_clients_count 0
- $rd close
- }
- }
-
- test {Nested async RM_Call} {
- set rd [redis_deferring_client]
-
- $rd do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {l a}
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test {Test multiple async RM_Call waiting on the same event} {
- set rd1 [redis_deferring_client]
- set rd2 [redis_deferring_client]
-
- $rd1 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0
- $rd2 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0
- wait_for_blocked_clients_count 2
- r lpush l element element
- assert_equal [$rd1 read] {l element}
- assert_equal [$rd2 read] {l element}
- wait_for_blocked_clients_count 0
- $rd1 close
- $rd2 close
- }
-
- test {async RM_Call calls RM_Call} {
- assert_equal {PONG} [r do_rm_call_async do_rm_call ping]
- }
-
- test {async RM_Call calls background RM_Call calls RM_Call} {
- assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call ping]
- }
-
- test {async RM_Call calls background RM_Call calls RM_Call calls async RM_Call} {
- assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call do_rm_call_async ping]
- }
-
- test {async RM_Call inside async RM_Call callback} {
- set rd [redis_deferring_client]
- $rd wait_and_do_rm_call blpop l 0
- wait_for_blocked_clients_count 1
-
- start_server {} {
- test "Connect a replica to the master instance" {
- r slaveof [srv -1 host] [srv -1 port]
- wait_for_condition 50 100 {
- [s role] eq {slave} &&
- [string match {*master_link_status:up*} [r info replication]]
- } else {
- fail "Can't turn the instance into a replica"
- }
- }
-
- assert_equal {1} [r -1 lpush l a]
- assert_equal [$rd read] {l a}
- }
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test {Become replica while having async RM_Call running} {
- r flushall
- set rd [redis_deferring_client]
- $rd do_rm_call_async blpop l 0
- wait_for_blocked_clients_count 1
-
- #become a replica of a not existing redis
- r replicaof localhost 30000
-
- catch {[$rd read]} e
- assert_match {UNBLOCKED force unblock from blocking operation*} $e
- wait_for_blocked_clients_count 0
-
- r replicaof no one
-
- r lpush l 1
- # make sure the async rm_call was aborted
- assert_equal [r llen l] {1}
- $rd close
- }
-
- test {Pipeline with blocking RM_Call} {
- r flushall
- set rd [redis_deferring_client]
- set buf ""
- append buf "do_rm_call_async blpop l 0\r\n"
- append buf "ping\r\n"
- $rd write $buf
- $rd flush
- wait_for_blocked_clients_count 1
-
- # release the blocked client
- r lpush l 1
-
- assert_equal [$rd read] {l 1}
- assert_equal [$rd read] {PONG}
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test {blocking RM_Call abort} {
- r flushall
- set rd [redis_deferring_client]
-
- $rd client id
- set client_id [$rd read]
-
- $rd do_rm_call_async blpop l 0
- wait_for_blocked_clients_count 1
-
- r client kill ID $client_id
- assert_error {*error reading reply*} {$rd read}
-
- wait_for_blocked_clients_count 0
-
- r lpush l 1
- # make sure the async rm_call was aborted
- assert_equal [r llen l] {1}
- $rd close
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Test basic replication stream on unblock handler} {
- r flushall
- set repl [attach_to_replication_stream]
-
- set rd [redis_deferring_client]
-
- $rd do_rm_call_async blpop l 0
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {l a}
-
- assert_replication_stream $repl {
- {select *}
- {lpush l a}
- {lpop l}
- }
- close_replication_stream $repl
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test {Test unblock handler are executed as a unit} {
- r flushall
- set repl [attach_to_replication_stream]
-
- set rd [redis_deferring_client]
-
- $rd blpop_and_set_multiple_keys l x 1 y 2
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {OK}
-
- assert_replication_stream $repl {
- {select *}
- {lpush l a}
- {multi}
- {lpop l}
- {set x 1}
- {set y 2}
- {exec}
- }
- close_replication_stream $repl
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test {Test no propagation of blocking command} {
- r flushall
- set repl [attach_to_replication_stream]
-
- set rd [redis_deferring_client]
-
- $rd do_rm_call_async_no_replicate blpop l 0
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {l a}
-
- # make sure the lpop are not replicated
- r set x 1
-
- assert_replication_stream $repl {
- {select *}
- {lpush l a}
- {set x 1}
- }
- close_replication_stream $repl
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
- r module load $testmodule2
-
- test {Test unblock handler are executed as a unit with key space notifications} {
- r flushall
- set repl [attach_to_replication_stream]
-
- set rd [redis_deferring_client]
-
- $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {OK}
-
- # Explanation of the first multi exec block:
- # {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys'
- # {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys'
- # {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys'
- # {incr string_changed{string_foo}} - post notification job that was registered when 'string_foo' changed
- # {incr string_changed{string_bar}} - post notification job that was registered when 'string_bar' changed
- # {incr string_total} - post notification job that was registered when string_changed{string_foo} changed
- # {incr string_total} - post notification job that was registered when string_changed{string_bar} changed
- assert_replication_stream $repl {
- {select *}
- {lpush l a}
- {multi}
- {lpop l}
- {set string_foo 1}
- {set string_bar 2}
- {incr string_changed{string_foo}}
- {incr string_changed{string_bar}}
- {incr string_total}
- {incr string_total}
- {exec}
- }
- close_replication_stream $repl
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-
- test {Test unblock handler are executed as a unit with lazy expire} {
- r flushall
- r DEBUG SET-ACTIVE-EXPIRE 0
- set repl [attach_to_replication_stream]
-
- set rd [redis_deferring_client]
-
- $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {OK}
-
- # set expiration on string_foo
- r pexpire string_foo 1
- after 10
-
- # now the key should have been expired
- $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2
- wait_for_blocked_clients_count 1
- r lpush l a
- assert_equal [$rd read] {OK}
-
- # Explanation of the first multi exec block:
- # {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys'
- # {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys'
- # {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys'
- # {incr string_changed{string_foo}} - post notification job that was registered when 'string_foo' changed
- # {incr string_changed{string_bar}} - post notification job that was registered when 'string_bar' changed
- # {incr string_total} - post notification job that was registered when string_changed{string_foo} changed
- # {incr string_total} - post notification job that was registered when string_changed{string_bar} changed
- #
- # Explanation of the second multi exec block:
- # {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys'
- # {del string_foo} - lazy expiration of string_foo when 'blpop_and_set_multiple_keys' tries to write to it.
- # {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys'
- # {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys'
- # {incr expired} - the post notification job, registered after string_foo got expired
- # {incr string_changed{string_foo}} - post notification job triggered when we set string_foo
- # {incr string_changed{string_bar}} - post notification job triggered when we set string_bar
- # {incr string_total} - post notification job triggered when we incr 'string_changed{string_foo}'
- # {incr string_total} - post notification job triggered when we incr 'string_changed{string_bar}'
- assert_replication_stream $repl {
- {select *}
- {lpush l a}
- {multi}
- {lpop l}
- {set string_foo 1}
- {set string_bar 2}
- {incr string_changed{string_foo}}
- {incr string_changed{string_bar}}
- {incr string_total}
- {incr string_total}
- {exec}
- {pexpireat string_foo *}
- {lpush l a}
- {multi}
- {lpop l}
- {del string_foo}
- {set string_foo 1}
- {set string_bar 2}
- {incr expired}
- {incr string_changed{string_foo}}
- {incr string_changed{string_bar}}
- {incr string_total}
- {incr string_total}
- {exec}
- }
- close_replication_stream $repl
- r DEBUG SET-ACTIVE-EXPIRE 1
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
- r module load $testmodule3
-
- test {Test unblock handler on module blocked on keys} {
- set rd [redis_deferring_client]
-
- r fsl.push l 1
- $rd do_rm_call_async FSL.BPOPGT l 3 0
- wait_for_blocked_clients_count 1
- r fsl.push l 2
- r fsl.push l 3
- r fsl.push l 4
- assert_equal [$rd read] {4}
-
- wait_for_blocked_clients_count 0
- $rd close
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/auth.tcl b/examples/redis-unstable/tests/unit/moduleapi/auth.tcl
deleted file mode 100644
index e8950ed..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/auth.tcl
+++ /dev/null
@@ -1,90 +0,0 @@
-set testmodule [file normalize tests/modules/auth.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Modules can create a user that can be authenticated} {
- # Make sure we start authenticated with default user
- r auth default ""
- assert_equal [r acl whoami] "default"
- r auth.createmoduleuser
-
- set id [r auth.authmoduleuser]
- assert_equal [r client id] $id
-
- # Verify returned id is the same as our current id and
- # we are authenticated with the specified user
- assert_equal [r acl whoami] "global"
- }
-
- test {De-authenticating clients is tracked and kills clients} {
- assert_equal [r auth.changecount] 0
- r auth.createmoduleuser
-
- # Catch the I/O exception that was thrown when Redis
- # disconnected with us.
- catch { [r ping] } e
- assert_match {*I/O*} $e
-
- # Check that a user change was registered
- assert_equal [r auth.changecount] 1
- }
-
- test {Modules can't authenticate with ACLs users that dont exist} {
- catch { [r auth.authrealuser auth-module-test-fake] } e
- assert_match {*Invalid user*} $e
- }
-
- test {Modules can authenticate with ACL users} {
- assert_equal [r acl whoami] "default"
-
- # Create user to auth into
- r acl setuser auth-module-test on allkeys allcommands
-
- set id [r auth.authrealuser auth-module-test]
-
- # Verify returned id is the same as our current id and
- # we are authenticated with the specified user
- assert_equal [r client id] $id
- assert_equal [r acl whoami] "auth-module-test"
- }
-
- test {Client callback is called on user switch} {
- assert_equal [r auth.changecount] 0
-
- # Auth again and validate change count
- r auth.authrealuser auth-module-test
- assert_equal [r auth.changecount] 1
-
- # Re-auth with the default user
- r auth default ""
- assert_equal [r auth.changecount] 1
- assert_equal [r acl whoami] "default"
-
- # Re-auth with the default user again, to
- # verify the callback isn't fired again
- r auth default ""
- assert_equal [r auth.changecount] 0
- assert_equal [r acl whoami] "default"
- }
-
- test {modules can redact arguments} {
- r config set slowlog-log-slower-than 0
- r slowlog reset
- r auth.redact 1 2 3 4
- r auth.redact 1 2 3
- r config set slowlog-log-slower-than -1
- set slowlog_resp [r slowlog get]
-
- # There will be 3 records, slowlog reset and the
- # two auth redact calls.
- assert_equal 3 [llength $slowlog_resp]
- assert_equal {slowlog reset} [lindex [lindex $slowlog_resp 2] 3]
- assert_equal {auth.redact 1 (redacted) 3 (redacted)} [lindex [lindex $slowlog_resp 1] 3]
- assert_equal {auth.redact (redacted) 2 (redacted)} [lindex [lindex $slowlog_resp 0] 3]
- }
-
- test "Unload the module - testacl" {
- assert_equal {OK} [r module unload testacl]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/basics.tcl b/examples/redis-unstable/tests/unit/moduleapi/basics.tcl
deleted file mode 100644
index 49ea9f3..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/basics.tcl
+++ /dev/null
@@ -1,70 +0,0 @@
-set testmodule [file normalize tests/modules/basics.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {test module api basics} {
- r test.basics
- } {ALL TESTS PASSED}
-
- test {test rm_call auto mode} {
- r hello 2
- set reply [r test.rmcallautomode]
- assert_equal [lindex $reply 0] f1
- assert_equal [lindex $reply 1] v1
- assert_equal [lindex $reply 2] f2
- assert_equal [lindex $reply 3] v2
- r hello 3
- set reply [r test.rmcallautomode]
- assert_equal [dict get $reply f1] v1
- assert_equal [dict get $reply f2] v2
- }
-
- test {test get resp} {
- foreach resp {3 2} {
- if {[lsearch $::denytags "resp3"] >= 0} {
- if {$resp == 3} {continue}
- } elseif {$::force_resp3} {
- if {$resp == 2} {continue}
- }
- r hello $resp
- set reply [r test.getresp]
- assert_equal $reply $resp
- r hello 2
- }
- }
-
- test "Unload the module - test" {
- assert_equal {OK} [r module unload test]
- }
-}
-
-start_server {tags {"modules external:skip"} overrides {enable-module-command no}} {
- test {module command disabled} {
- assert_error "ERR *MODULE command not allowed*" {r module load $testmodule}
- }
-}
-
-start_server {tags {"modules external:skip"} overrides {enable-debug-command no}} {
- r module load $testmodule
-
- test {debug command disabled} {
- assert_equal {no} [r test.candebug]
- }
-}
-
-start_server {tags {"modules external:skip"} overrides {enable-debug-command yes}} {
- r module load $testmodule
-
- test {debug command enabled} {
- assert_equal {yes} [r test.candebug]
- }
-}
-
-start_server {tags {"modules external:skip"} overrides {enable-debug-command local}} {
- r module load $testmodule
-
- test {debug commands are enabled for local connection} {
- assert_equal {yes} [r test.candebug]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl b/examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl
deleted file mode 100644
index 7dcc1d6..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/blockedclient.tcl
+++ /dev/null
@@ -1,310 +0,0 @@
-set testmodule [file normalize tests/modules/blockedclient.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Locked GIL acquisition} {
- assert_match "OK" [r acquire_gil]
- }
-
- test {Locked GIL acquisition during multi} {
- r multi
- r acquire_gil
- assert_equal {{Blocked client is not supported inside multi}} [r exec]
- }
-
- test {Locked GIL acquisition from RM_Call} {
- assert_equal {Blocked client is not allowed} [r do_rm_call acquire_gil]
- }
-
- test {Blocking command are not block the client on RM_Call} {
- r lpush l test
- assert_equal [r do_rm_call blpop l 0] {l test}
-
- r lpush l test
- assert_equal [r do_rm_call brpop l 0] {l test}
-
- r lpush l1 test
- assert_equal [r do_rm_call brpoplpush l1 l2 0] {test}
- assert_equal [r do_rm_call brpop l2 0] {l2 test}
-
- r lpush l1 test
- assert_equal [r do_rm_call blmove l1 l2 LEFT LEFT 0] {test}
- assert_equal [r do_rm_call brpop l2 0] {l2 test}
-
- r ZADD zset1 0 a 1 b 2 c
- assert_equal [r do_rm_call bzpopmin zset1 0] {zset1 a 0}
- assert_equal [r do_rm_call bzpopmax zset1 0] {zset1 c 2}
-
- r xgroup create s g $ MKSTREAM
- r xadd s * foo bar
- assert {[r do_rm_call xread BLOCK 0 STREAMS s 0-0] ne {}}
- assert {[r do_rm_call xreadgroup group g c BLOCK 0 STREAMS s >] ne {}}
-
- assert {[r do_rm_call blpop empty_list 0] eq {}}
- assert {[r do_rm_call brpop empty_list 0] eq {}}
- assert {[r do_rm_call brpoplpush empty_list1 empty_list2 0] eq {}}
- assert {[r do_rm_call blmove empty_list1 empty_list2 LEFT LEFT 0] eq {}}
-
- assert {[r do_rm_call bzpopmin empty_zset 0] eq {}}
- assert {[r do_rm_call bzpopmax empty_zset 0] eq {}}
-
- r xgroup create empty_stream g $ MKSTREAM
- assert {[r do_rm_call xread BLOCK 0 STREAMS empty_stream $] eq {}}
- assert {[r do_rm_call xreadgroup group g c BLOCK 0 STREAMS empty_stream >] eq {}}
-
- }
-
- test {Monitor disallow inside RM_Call} {
- set e {}
- catch {
- r do_rm_call monitor
- } e
- set e
- } {*ERR*DENY BLOCKING*}
-
- test {subscribe disallow inside RM_Call} {
- set e {}
- catch {
- r do_rm_call subscribe x
- } e
- set e
- } {*ERR*DENY BLOCKING*}
-
- test {RM_Call from blocked client} {
- r hset hash foo bar
- r do_bg_rm_call hgetall hash
- } {foo bar}
-
- test {RM_Call from blocked client with script mode} {
- r do_bg_rm_call_format S hset k foo bar
- } {1}
-
- test {RM_Call from blocked client with oom mode} {
- r config set maxmemory 1
- # will set server.pre_command_oom_state to 1
- assert_error {OOM command not allowed*} {r hset hash foo bar}
- r config set maxmemory 0
- # now its should be OK to call OOM commands
- r do_bg_rm_call_format M hset k1 foo bar
- } {1} {needs:config-maxmemory}
-
- test {RESP version carries through to blocked client} {
- for {set client_proto 2} {$client_proto <= 3} {incr client_proto} {
- if {[lsearch $::denytags "resp3"] >= 0} {
- if {$client_proto == 3} {continue}
- } elseif {$::force_resp3} {
- if {$client_proto == 2} {continue}
- }
- r hello $client_proto
- r readraw 1
- set ret [r do_fake_bg_true]
- if {$client_proto == 2} {
- assert_equal $ret {:1}
- } else {
- assert_equal $ret "#t"
- }
- r readraw 0
- r hello 2
- }
- }
-
-foreach call_type {nested normal} {
- test "Busy module command - $call_type" {
- set busy_time_limit 50
- set old_time_limit [lindex [r config get busy-reply-threshold] 1]
- r config set busy-reply-threshold $busy_time_limit
- set rd [redis_deferring_client]
-
- # run command that blocks until released
- set start [clock clicks -milliseconds]
- if {$call_type == "nested"} {
- $rd do_rm_call slow_fg_command 0
- } else {
- $rd slow_fg_command 0
- }
- $rd flush
-
- # send another command after the blocked one, to make sure we don't attempt to process it
- $rd ping
- $rd flush
-
- # make sure we get BUSY error, and that we didn't get it too early
- wait_for_condition 50 100 {
- ([catch {r ping} reply] == 1) &&
- ([string match {*BUSY Slow module operation*} $reply])
- } else {
- fail "Failed waiting for busy slow response"
- }
- assert_morethan_equal [expr [clock clicks -milliseconds]-$start] $busy_time_limit
-
- # abort the blocking operation
- r stop_slow_fg_command
- wait_for_condition 50 100 {
- [catch {r ping} e] == 0
- } else {
- fail "Failed waiting for busy command to end"
- }
- assert_equal [$rd read] "1"
- assert_equal [$rd read] "PONG"
-
- # run command that blocks for 200ms
- set start [clock clicks -milliseconds]
- if {$call_type == "nested"} {
- $rd do_rm_call slow_fg_command 200000
- } else {
- $rd slow_fg_command 200000
- }
- $rd flush
- after 10 ;# try to make sure redis started running the command before we proceed
-
- # make sure we didn't get BUSY error, it simply blocked till the command was done
- r ping
- # The command blocks for 200ms, allow 1-2ms clock skew (1%)
- # to accommodate differences between using of monotonic timer and ustime
- assert_morethan_equal [expr [clock clicks -milliseconds]-$start] 198
- $rd read
-
- $rd close
- r config set busy-reply-threshold $old_time_limit
- }
-}
-
- test {RM_Call from blocked client} {
- set busy_time_limit 50
- set old_time_limit [lindex [r config get busy-reply-threshold] 1]
- r config set busy-reply-threshold $busy_time_limit
-
- # trigger slow operation
- r set_slow_bg_operation 1
- r hset hash foo bar
- set rd [redis_deferring_client]
- set start [clock clicks -milliseconds]
- $rd do_bg_rm_call hgetall hash
-
- # send another command after the blocked one, to make sure we don't attempt to process it
- $rd ping
- $rd flush
-
- # wait till we know we're blocked inside the module
- wait_for_condition 50 100 {
- [r is_in_slow_bg_operation] eq 1
- } else {
- fail "Failed waiting for slow operation to start"
- }
-
- # make sure we get BUSY error, and that we didn't get here too early
- assert_error {*BUSY Slow module operation*} {r ping}
- assert_morethan_equal [expr [clock clicks -milliseconds]-$start] $busy_time_limit
- # abort the blocking operation
- r set_slow_bg_operation 0
-
- wait_for_condition 50 100 {
- [r is_in_slow_bg_operation] eq 0
- } else {
- fail "Failed waiting for slow operation to stop"
- }
- assert_equal [r ping] {PONG}
-
- r config set busy-reply-threshold $old_time_limit
- assert_equal [$rd read] {foo bar}
- assert_equal [$rd read] {PONG}
- $rd close
- }
-
- test {blocked client reaches client output buffer limit} {
- r hset hash big [string repeat x 50000]
- r hset hash bada [string repeat x 50000]
- r hset hash boom [string repeat x 50000]
- r config set client-output-buffer-limit {normal 100000 0 0}
- r client setname myclient
- catch {r do_bg_rm_call hgetall hash} e
- assert_match "*I/O error*" $e
- reconnect
- set clients [r client list]
- assert_no_match "*name=myclient*" $clients
- }
-
- test {module client error stats} {
- r config resetstat
-
- # simple module command that replies with string error
- assert_error "ERR unknown command 'hgetalllll'" {r do_rm_call hgetalllll}
- assert_equal [errorrstat ERR r] {count=1}
-
- # simple module command that replies with string error
- assert_error "ERR unknown subcommand 'bla'. Try CONFIG HELP." {r do_rm_call config bla}
- assert_equal [errorrstat ERR r] {count=2}
-
- # 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=1}
-
- # 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=3}
-
- # 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] 6
- assert_match {*calls=5,*,rejected_calls=0,failed_calls=4} [cmdrstat do_rm_call r]
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat do_bg_rm_call r]
- }
-
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
- start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] {
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
-
- # Start the replication process...
- $replica replicaof $master_host $master_port
- wait_for_sync $replica
-
- test {WAIT command on module blocked client} {
- pause_process [srv 0 pid]
-
- $master do_bg_rm_call_format ! hset bk1 foo bar
-
- assert_equal [$master wait 1 1000] 0
- resume_process [srv 0 pid]
- assert_equal [$master wait 1 1000] 1
- assert_equal [$replica hget bk1 foo] bar
- }
- }
-
- test {Unblock by timer} {
- # When the client is unlock, we will get the OK reply.
- assert_match "OK" [r unblock_by_timer 100 0]
- }
-
- test {block time is shorter than timer period} {
- # This command does not have the reply.
- set rd [redis_deferring_client]
- $rd unblock_by_timer 100 10
- # Wait for the client to unlock.
- after 120
- $rd close
- }
-
- test {block time is equal to timer period} {
- # These time is equal, they will be unlocked in the same event loop,
- # when the client is unlock, we will get the OK reply from timer.
- assert_match "OK" [r unblock_by_timer 100 100]
- }
-
- test "Unload the module - blockedclient" {
- assert_equal {OK} [r module unload blockedclient]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl b/examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl
deleted file mode 100644
index e8b9f55..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/blockonbackground.tcl
+++ /dev/null
@@ -1,124 +0,0 @@
-set testmodule [file normalize tests/modules/blockonbackground.so]
-
-proc latency_percentiles_usec {cmd} {
- return [latencyrstat_percentiles $cmd r]
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test { blocked clients time tracking - check blocked command that uses RedisModule_BlockedClientMeasureTimeStart() is tracking background time} {
- r slowlog reset
- r config set slowlog-log-slower-than 200000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r block.debug 0 10000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r config resetstat
- r config set latency-tracking yes
- r config set latency-tracking-info-percentiles "50.0"
- r block.debug 200 10000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 1
- }
-
- set cmdstatline [cmdrstat block.debug r]
- set latencystatline_debug [latency_percentiles_usec block.debug]
-
- regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline -> usec usec_per_call
- regexp "p50=(.+\..+)" $latencystatline_debug -> p50
- assert {$usec >= 100000}
- assert {$usec_per_call >= 100000}
- assert {$p50 >= 100000}
- }
-
- test { blocked clients time tracking - check blocked command that uses RedisModule_BlockedClientMeasureTimeStart() is tracking background time even in timeout } {
- r slowlog reset
- r config set slowlog-log-slower-than 200000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r block.debug 0 20000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r config resetstat
- r block.debug 20000 500
- if {!$::no_latency} {
- assert_equal [r slowlog len] 1
- }
-
- set cmdstatline [cmdrstat block.debug r]
-
- regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline usec usec_per_call
- assert {$usec >= 250000}
- assert {$usec_per_call >= 250000}
- }
-
- test { blocked clients time tracking - check blocked command with multiple calls RedisModule_BlockedClientMeasureTimeStart() is tracking the total background time } {
- r slowlog reset
- r config set slowlog-log-slower-than 200000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r block.double_debug 0
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r config resetstat
- r block.double_debug 100
- if {!$::no_latency} {
- assert_equal [r slowlog len] 1
- }
- set cmdstatline [cmdrstat block.double_debug r]
-
- regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline usec usec_per_call
- assert {$usec >= 60000}
- assert {$usec_per_call >= 60000}
- }
-
- test { blocked clients time tracking - check blocked command without calling RedisModule_BlockedClientMeasureTimeStart() is not reporting background time } {
- r slowlog reset
- r config set slowlog-log-slower-than 200000
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- r block.debug_no_track 200 1000
- # ensure slowlog is still empty
- if {!$::no_latency} {
- assert_equal [r slowlog len] 0
- }
- }
-
- test "client unblock works only for modules with timeout support" {
- set rd [redis_deferring_client]
- $rd client id
- set id [$rd read]
-
- # Block with a timeout function - may unblock
- $rd block.block 20000
- wait_for_condition 50 100 {
- [r block.is_blocked] == 1
- } else {
- fail "Module did not block"
- }
-
- assert_equal 1 [r client unblock $id]
- assert_match {*Timed out*} [$rd read]
-
- # Block without a timeout function - cannot unblock
- $rd block.block 0
- wait_for_condition 50 100 {
- [r block.is_blocked] == 1
- } else {
- fail "Module did not block"
- }
-
- assert_equal 0 [r client unblock $id]
- assert_equal "OK" [r block.release foobar]
- assert_equal "foobar" [$rd read]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl b/examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl
deleted file mode 100644
index 53aee73..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/blockonkeys.tcl
+++ /dev/null
@@ -1,366 +0,0 @@
-set testmodule [file normalize tests/modules/blockonkeys.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test "Module client blocked on keys: Circular BPOPPUSH" {
- set rd1 [redis_deferring_client]
- set rd2 [redis_deferring_client]
-
- r del src dst
-
- $rd1 fsl.bpoppush src dst 0
- wait_for_blocked_clients_count 1
-
- $rd2 fsl.bpoppush dst src 0
- wait_for_blocked_clients_count 2
-
- r fsl.push src 42
- wait_for_blocked_clients_count 0
-
- assert_equal {42} [r fsl.getall src]
- assert_equal {} [r fsl.getall dst]
- }
-
- test "Module client blocked on keys: Self-referential BPOPPUSH" {
- set rd1 [redis_deferring_client]
-
- r del src
-
- $rd1 fsl.bpoppush src src 0
- wait_for_blocked_clients_count 1
- r fsl.push src 42
-
- assert_equal {42} [r fsl.getall src]
- }
-
- test "Module client blocked on keys: BPOPPUSH unblocked by timer" {
- set rd1 [redis_deferring_client]
-
- r del src dst
-
- set repl [attach_to_replication_stream]
-
- $rd1 fsl.bpoppush src dst 0
- wait_for_blocked_clients_count 1
-
- r fsl.pushtimer src 9000 10
- wait_for_blocked_clients_count 0
-
- assert_equal {9000} [r fsl.getall dst]
- assert_equal {} [r fsl.getall src]
-
- assert_replication_stream $repl {
- {select *}
- {fsl.push src 9000}
- {fsl.bpoppush src dst 0}
- }
-
- close_replication_stream $repl
- } {} {needs:repl}
-
- test {Module client blocked on keys (no metadata): No block} {
- r del k
- r fsl.push k 33
- r fsl.push k 34
- r fsl.bpop k 0
- } {34}
-
- test {Module client blocked on keys (no metadata): Timeout} {
- r del k
- set rd [redis_deferring_client]
- $rd fsl.bpop k 1
- assert_equal {Request timedout} [$rd read]
- }
-
- test {Module client blocked on keys (no metadata): Blocked} {
- r del k
- set rd [redis_deferring_client]
- $rd fsl.bpop k 0
- wait_for_blocked_clients_count 1
- r fsl.push k 34
- assert_equal {34} [$rd read]
- }
-
- test {Module client blocked on keys (with metadata): No block} {
- r del k
- r fsl.push k 34
- r fsl.bpopgt k 30 0
- } {34}
-
- test {Module client blocked on keys (with metadata): Timeout} {
- r del k
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- r fsl.push k 33
- $rd fsl.bpopgt k 35 1
- assert_equal {Request timedout} [$rd read]
- r client kill id $cid ;# try to smoke-out client-related memory leak
- }
-
- test {Module client blocked on keys (with metadata): Blocked, case 1} {
- r del k
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- r fsl.push k 33
- $rd fsl.bpopgt k 33 0
- wait_for_blocked_clients_count 1
- r fsl.push k 34
- assert_equal {34} [$rd read]
- r client kill id $cid ;# try to smoke-out client-related memory leak
- }
-
- test {Module client blocked on keys (with metadata): Blocked, case 2} {
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r fsl.push k 33
- r fsl.push k 34
- r fsl.push k 35
- r fsl.push k 36
- assert_equal {36} [$rd read]
- }
-
- test {Module client blocked on keys (with metadata): Blocked, DEL} {
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r del k
- assert_error {*UNBLOCKED key no longer exists*} {$rd read}
- }
-
- test {Module client blocked on keys (with metadata): Blocked, FLUSHALL} {
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r flushall
- assert_error {*UNBLOCKED key no longer exists*} {$rd read}
- }
-
- test {Module client blocked on keys (with metadata): Blocked, SWAPDB, no key} {
- r select 9
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r swapdb 0 9
- assert_error {*UNBLOCKED key no longer exists*} {$rd read}
- }
-
- test {Module client blocked on keys (with metadata): Blocked, SWAPDB, key exists, case 1} {
- ;# Key exists on other db, but wrong type
- r flushall
- r select 9
- r fsl.push k 32
- r select 0
- r lpush k 38
- r select 9
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r swapdb 0 9
- assert_error {*UNBLOCKED key no longer exists*} {$rd read}
- r select 9
- }
-
- test {Module client blocked on keys (with metadata): Blocked, SWAPDB, key exists, case 2} {
- ;# Key exists on other db, with the right type, but the value doesn't allow to unblock
- r flushall
- r select 9
- r fsl.push k 32
- r select 0
- r fsl.push k 34
- r select 9
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r swapdb 0 9
- assert_equal {1} [s 0 blocked_clients]
- r fsl.push k 38
- assert_equal {38} [$rd read]
- r select 9
- }
-
- test {Module client blocked on keys (with metadata): Blocked, SWAPDB, key exists, case 3} {
- ;# Key exists on other db, with the right type, the value allows to unblock
- r flushall
- r select 9
- r fsl.push k 32
- r select 0
- r fsl.push k 38
- r select 9
- set rd [redis_deferring_client]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r swapdb 0 9
- assert_equal {38} [$rd read]
- r select 9
- }
-
- test {Module client blocked on keys (with metadata): Blocked, CLIENT KILL} {
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r client kill id $cid ;# try to smoke-out client-related memory leak
- }
-
- test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK TIMEOUT} {
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r client unblock $cid timeout ;# try to smoke-out client-related memory leak
- assert_equal {Request timedout} [$rd read]
- }
-
- test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK ERROR} {
- r del k
- r fsl.push k 32
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- $rd fsl.bpopgt k 35 0
- wait_for_blocked_clients_count 1
- r client unblock $cid error ;# try to smoke-out client-related memory leak
- assert_error "*unblocked*" {$rd read}
- }
-
- test {Module client blocked on keys, no timeout CB, CLIENT UNBLOCK TIMEOUT} {
- r del k
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- $rd fsl.bpop k 0 NO_TO_CB
- wait_for_blocked_clients_count 1
- assert_equal [r client unblock $cid timeout] {0}
- $rd close
- }
-
- test {Module client blocked on keys, no timeout CB, CLIENT UNBLOCK ERROR} {
- r del k
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
- $rd fsl.bpop k 0 NO_TO_CB
- wait_for_blocked_clients_count 1
- assert_equal [r client unblock $cid error] {0}
- $rd close
- }
-
- test {Module client re-blocked on keys after woke up on wrong type} {
- r del k
- set rd [redis_deferring_client]
- $rd fsl.bpop k 0
- wait_for_blocked_clients_count 1
- r lpush k 12
- r lpush k 13
- r lpush k 14
- r del k
- r fsl.push k 34
- assert_equal {34} [$rd read]
- assert_equal {1} [r get fsl_wrong_type] ;# first lpush caused one wrong-type wake-up
- }
-
- test {Module client blocked on keys woken up by LPUSH} {
- r del k
- set rd [redis_deferring_client]
- $rd blockonkeys.popall k
- wait_for_blocked_clients_count 1
- r lpush k 42 squirrel banana
- assert_equal {banana squirrel 42} [$rd read]
- $rd close
- }
-
- test {Module client unblocks BLPOP} {
- r del k
- set rd [redis_deferring_client]
- $rd blpop k 3
- wait_for_blocked_clients_count 1
- r blockonkeys.lpush k 42
- assert_equal {k 42} [$rd read]
- $rd close
- }
-
- test {Module unblocks module blocked on non-empty list} {
- r del k
- r lpush k aa
- # Module client blocks to pop 5 elements from list
- set rd [redis_deferring_client]
- $rd blockonkeys.blpopn k 5
- wait_for_blocked_clients_count 1
- # Check that RM_SignalKeyAsReady() can wake up BLPOPN
- r blockonkeys.lpush_unblock k bb cc ;# Not enough elements for BLPOPN
- r lpush k dd ee ff ;# Doesn't unblock module
- r blockonkeys.lpush_unblock k gg ;# Unblocks module
- assert_equal {gg ff ee dd cc} [$rd read]
- $rd close
- }
-
- test {Module explicit unblock when blocked on keys} {
- r del k
- r set somekey someval
- # Module client blocks to pop 5 elements from list
- set rd [redis_deferring_client]
- $rd blockonkeys.blpopn_or_unblock k 5 0
- wait_for_blocked_clients_count 1
- # will now cause the module to trigger pop but instead will unblock the client from the reply_callback
- r lpush k dd
- # we should still get unblocked as the command should not reprocess
- wait_for_blocked_clients_count 0
- assert_equal {Action aborted} [$rd read]
- $rd get somekey
- assert_equal {someval} [$rd read]
- $rd close
- }
-
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
- start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] {
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
-
- # Start the replication process...
- $replica replicaof $master_host $master_port
- wait_for_sync $replica
-
- test {WAIT command on module blocked client on keys} {
- set rd [redis_deferring_client -1]
- $rd set x y
- $rd read
-
- pause_process [srv 0 pid]
-
- $master del k
- $rd fsl.bpop k 0
- wait_for_blocked_client -1
- $master fsl.push k 34
- $master fsl.push k 35
- assert_equal {34} [$rd read]
-
- assert_equal [$master wait 1 1000] 0
- resume_process [srv 0 pid]
- assert_equal [$master wait 1 1000] 1
- $rd close
- assert_equal {35} [$replica fsl.getall k]
- }
- }
-
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/cluster.tcl b/examples/redis-unstable/tests/unit/moduleapi/cluster.tcl
deleted file mode 100644
index 7141fe1..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/cluster.tcl
+++ /dev/null
@@ -1,227 +0,0 @@
-# Primitive tests on cluster-enabled redis with modules
-
-source tests/support/cli.tcl
-
-# cluster creation is complicated with TLS, and the current tests don't really need that coverage
-tags {tls:skip external:skip cluster modules} {
-
-set testmodule_nokey [file normalize tests/modules/blockonbackground.so]
-set testmodule_blockedclient [file normalize tests/modules/blockedclient.so]
-set testmodule [file normalize tests/modules/blockonkeys.so]
-
-set modules [list loadmodule $testmodule loadmodule $testmodule_nokey loadmodule $testmodule_blockedclient]
-start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] {
-
- set node1 [srv 0 client]
- set node2 [srv -1 client]
- set node3 [srv -2 client]
- set node3_pid [srv -2 pid]
-
- test "Run blocking command (blocked on key) on cluster node3" {
- # key9184688 is mapped to slot 10923 (first slot of node 3)
- set node3_rd [redis_deferring_client -2]
- $node3_rd fsl.bpop key9184688 0
- $node3_rd flush
- wait_for_condition 50 100 {
- [s -2 blocked_clients] eq {1}
- } else {
- fail "Client executing blocking command (blocked on key) not blocked"
- }
- }
-
- test "Run blocking command (no keys) on cluster node2" {
- set node2_rd [redis_deferring_client -1]
- $node2_rd block.block 0
- $node2_rd flush
-
- wait_for_condition 50 100 {
- [s -1 blocked_clients] eq {1}
- } else {
- fail "Client executing blocking command (no keys) not blocked"
- }
- }
-
-
- test "Perform a Resharding" {
- exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \
- --cluster-to [$node1 cluster myid] \
- --cluster-from [$node3 cluster myid] \
- --cluster-slots 1
- }
-
- test "Verify command (no keys) is unaffected after resharding" {
- # verify there are blocked clients on node2
- assert_equal [s -1 blocked_clients] {1}
-
- #release client
- $node2 block.release 0
- }
-
- test "Verify command (blocked on key) got unblocked after resharding" {
- # this (read) will wait for the node3 to realize the new topology
- assert_error {*MOVED*} {$node3_rd read}
-
- # verify there are no blocked clients
- assert_equal [s 0 blocked_clients] {0}
- assert_equal [s -1 blocked_clients] {0}
- assert_equal [s -2 blocked_clients] {0}
- }
-
- test "Wait for cluster to be stable" {
- wait_for_condition 1000 50 {
- [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 &&
- [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 &&
- [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 &&
- [CI 0 cluster_state] eq {ok} &&
- [CI 1 cluster_state] eq {ok} &&
- [CI 2 cluster_state] eq {ok}
- } else {
- fail "Cluster doesn't stabilize"
- }
- }
-
- test "Sanity test push cmd after resharding" {
- assert_error {*MOVED*} {$node3 fsl.push key9184688 1}
-
- set node1_rd [redis_deferring_client 0]
- $node1_rd fsl.bpop key9184688 0
- $node1_rd flush
-
- wait_for_condition 50 100 {
- [s 0 blocked_clients] eq {1}
- } else {
- puts "Client not blocked"
- puts "read from blocked client: [$node1_rd read]"
- fail "Client not blocked"
- }
-
- $node1 fsl.push key9184688 2
- assert_equal {2} [$node1_rd read]
- }
-
- $node1_rd close
- $node2_rd close
- $node3_rd close
-
- test "Run blocking command (blocked on key) again on cluster node1" {
- $node1 del key9184688
- # key9184688 is mapped to slot 10923 which has been moved to node1
- set node1_rd [redis_deferring_client 0]
- $node1_rd fsl.bpop key9184688 0
- $node1_rd flush
-
- wait_for_condition 50 100 {
- [s 0 blocked_clients] eq {1}
- } else {
- fail "Client executing blocking command (blocked on key) again not blocked"
- }
- }
-
- test "Run blocking command (no keys) again on cluster node2" {
- set node2_rd [redis_deferring_client -1]
-
- $node2_rd block.block 0
- $node2_rd flush
-
- wait_for_condition 50 100 {
- [s -1 blocked_clients] eq {1}
- } else {
- fail "Client executing blocking command (no keys) again not blocked"
- }
- }
-
- test "Kill a cluster node and wait for fail state" {
- # kill node3 in cluster
- pause_process $node3_pid
-
- wait_for_condition 1000 50 {
- [CI 0 cluster_state] eq {fail} &&
- [CI 1 cluster_state] eq {fail}
- } else {
- fail "Cluster doesn't fail"
- }
- }
-
- test "Verify command (blocked on key) got unblocked after cluster failure" {
- assert_error {*CLUSTERDOWN*} {$node1_rd read}
- }
-
- test "Verify command (no keys) got unblocked after cluster failure" {
- assert_error {*CLUSTERDOWN*} {$node2_rd read}
-
- # verify there are no blocked clients
- assert_equal [s 0 blocked_clients] {0}
- assert_equal [s -1 blocked_clients] {0}
- }
-
- test "Verify command RM_Call is rejected when cluster is down" {
- assert_error "ERR Can not execute a command 'set' while the cluster is down" {$node1 do_rm_call set x 1}
- }
-
- resume_process $node3_pid
- $node1_rd close
- $node2_rd close
-}
-
-set testmodule_keyspace_events [file normalize tests/modules/keyspace_events.so]
-set testmodule_postnotifications "[file normalize tests/modules/postnotifications.so] with_key_events"
-set modules [list loadmodule $testmodule_keyspace_events loadmodule $testmodule_postnotifications]
-start_cluster 2 2 [list tags {external:skip cluster modules} config_lines $modules] {
-
- set master1 [srv 0 client]
- set master2 [srv -1 client]
- set replica1 [srv -2 client]
- set replica2 [srv -3 client]
-
- test "Verify keys deletion and notification effects happened on cluster slots change are replicated inside multi exec" {
- $master2 set count_dels_{4oi} 1
- $master2 del count_dels_{4oi}
- assert_equal 1 [$master2 keyspace.get_dels]
- wait_for_ofs_sync $master2 $replica2
- assert_equal 1 [$replica2 keyspace.get_dels]
- $master2 set count_dels_{4oi} 1
-
- set repl [attach_to_replication_stream_on_connection -3]
-
- $master1 cluster bumpepoch
- $master1 cluster setslot 16382 node [$master1 cluster myid]
-
- wait_for_cluster_propagation
- wait_for_condition 50 100 {
- [$master2 keyspace.get_dels] eq 2
- } else {
- fail "master did not delete the key"
- }
- wait_for_condition 50 100 {
- [$replica2 keyspace.get_dels] eq 2
- } else {
- fail "replica did not increase del counter"
- }
-
- # the {lpush before_deleted count_dels_{4oi}} is a post notification job registered when 'count_dels_{4oi}' was removed
- assert_replication_stream $repl {
- {multi}
- {del count_dels_{4oi}}
- {keyspace.incr_dels}
- {lpush before_deleted count_dels_{4oi}}
- {exec}
- }
- close_replication_stream $repl
- }
-}
-
-}
-
-set testmodule [file normalize tests/modules/basics.so]
-set modules [list loadmodule $testmodule]
-start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] {
- set node1 [srv 0 client]
- set node2 [srv -1 client]
- set node3 [srv -2 client]
-
- test "Verify RM_Call inside module load function on cluster mode" {
- assert_equal {PONG} [$node1 PING]
- assert_equal {PONG} [$node2 PING]
- assert_equal {PONG} [$node3 PING]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl b/examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl
deleted file mode 100644
index 73be3f8..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/cmdintrospection.tcl
+++ /dev/null
@@ -1,50 +0,0 @@
-set testmodule [file normalize tests/modules/cmdintrospection.so]
-
-start_server {tags {"modules external:skip"}} {
- 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
- dict unset module_reply module
- if {$::log_req_res} {
- dict unset redis_reply reply_schema
- }
-
- assert_equal $redis_reply $module_reply
- }
-
- test "Unload the module - cmdintrospection" {
- assert_equal {OK} [r module unload cmdintrospection]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl b/examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl
deleted file mode 100644
index 5b600d0..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/commandfilter.tcl
+++ /dev/null
@@ -1,175 +0,0 @@
-set testmodule [file normalize tests/modules/commandfilter.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule log-key 0
-
- test {Retain a command filter argument} {
- # Retain an argument now. Later we'll try to re-read it and make sure
- # it is not corrupt and that valgrind does not complain.
- r rpush some-list @retain my-retained-string
- r commandfilter.retained
- } {my-retained-string}
-
- test {Command Filter handles redirected commands} {
- r set mykey @log
- r lrange log-key 0 -1
- } "{set mykey @log}"
-
- test {Command Filter can call RedisModule_CommandFilterArgDelete} {
- r rpush mylist elem1 @delme elem2
- r lrange mylist 0 -1
- } {elem1 elem2}
-
- test {Command Filter can call RedisModule_CommandFilterArgInsert} {
- r del mylist
- r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
- r lrange mylist 0 -1
- } {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3}
-
- test {Command Filter can call RedisModule_CommandFilterArgReplace} {
- r del mylist
- r rpush mylist elem1 @replaceme elem2
- r lrange mylist 0 -1
- } {elem1 --replaced-- elem2}
-
- test {Command Filter applies on RM_Call() commands} {
- r del log-key
- r commandfilter.ping
- r lrange log-key 0 -1
- } "{ping @log}"
-
- test {Command Filter applies on Lua redis.call()} {
- r del log-key
- r eval "redis.call('ping', '@log')" 0
- r lrange log-key 0 -1
- } "{ping @log}"
-
- test {Command Filter applies on Lua redis.call() that calls a module} {
- r del log-key
- r eval "redis.call('commandfilter.ping')" 0
- r lrange log-key 0 -1
- } "{ping @log}"
-
- test {Command Filter strings can be retained} {
- r commandfilter.retained
- } {my-retained-string}
-
- test {Command Filter is unregistered implicitly on module unload} {
- r del log-key
- r module unload commandfilter
- r set mykey @log
- r lrange log-key 0 -1
- } {}
-
- r module load $testmodule log-key 0
-
- test {Command Filter unregister works as expected} {
- # Validate reloading succeeded
- r del log-key
- r set mykey @log
- assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
-
- # Unregister
- r commandfilter.unregister
- r del log-key
-
- r set mykey @log
- r lrange log-key 0 -1
- } {}
-
- r module unload commandfilter
- r module load $testmodule log-key 1
-
- test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
- r set mykey @log
- assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
-
- r del log-key
- r commandfilter.ping
- assert_equal {} [r lrange log-key 0 -1]
-
- r eval "redis.call('commandfilter.ping')" 0
- assert_equal {} [r lrange log-key 0 -1]
- }
-
- test "Unload the module - commandfilter" {
- assert_equal {OK} [r module unload commandfilter]
- }
-}
-
-test {RM_CommandFilterArgInsert and script argv caching} {
- # coverage for scripts calling commands that expand the argv array
- # an attempt to add coverage for a possible bug in luaArgsToRedisArgv
- # this test needs a fresh server so that lua_argv_size is 0.
- # glibc realloc can return the same pointer even when the size changes
- # still this test isn't able to trigger the issue, but we keep it anyway.
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule log-key 0
- r del mylist
- # command with 6 args
- r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist
- # command with 3 args that is changed to 4
- r eval {redis.call('rpush', KEYS[1], '@insertafter')} 1 mylist
- # command with 6 args again
- r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist
- assert_equal [r lrange mylist 0 -1] {elem1 elem2 elem3 elem4 @insertafter --inserted-after-- elem1 elem2 elem3 elem4}
- }
-}
-
-# previously, there was a bug that command filters would be rerun (which would cause args to swap back)
-# this test is meant to protect against that bug
-test {Blocking Commands don't run through command filter when reprocessed} {
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule log-key 0
-
- r del list1{t}
- r del list2{t}
-
- r lpush list2{t} a b c d e
-
- set rd [redis_deferring_client]
- # we're asking to pop from the left, but the command filter swaps the two arguments,
- # if it didn't swap it, we would end up with e d c b a 5 (5 being the left most of the following lpush)
- # but since we swap the arguments, we end up with 1 e d c b a (1 being the right most of it).
- # if the command filter would run again on unblock, they would be swapped back.
- $rd blmove list1{t} list2{t} left right 0
- wait_for_blocked_client
- r lpush list1{t} 1 2 3 4 5
- # validate that we moved the correct element with the swapped args
- assert_equal [$rd read] 1
- # validate that we moved the correct elements to the correct side of the list
- assert_equal [r lpop list2{t}] 1
-
- $rd close
- }
-}
-
-test {Filtering based on client id} {
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule log-key 0
-
- set rr [redis_client]
- set cid [$rr client id]
- r unfilter_clientid $cid
-
- r rpush mylist elem1 @replaceme elem2
- assert_equal [r lrange mylist 0 -1] {elem1 --replaced-- elem2}
-
- r del mylist
-
- assert_equal [$rr rpush mylist elem1 @replaceme elem2] 3
- assert_equal [r lrange mylist 0 -1] {elem1 @replaceme elem2}
-
- $rr close
- }
-}
-
-start_server {tags {"external:skip"}} {
- test {OnLoad failure will handle un-registration} {
- catch {r module load $testmodule log-key 0 noload}
- r set mykey @log
- assert_equal [r lrange log-key 0 -1] {}
- r rpush mylist elem1 @delme elem2
- assert_equal [r lrange mylist 0 -1] {elem1 @delme elem2}
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl b/examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl
deleted file mode 100644
index 11aa962..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/configaccess.tcl
+++ /dev/null
@@ -1,227 +0,0 @@
-set testmodule [file normalize tests/modules/configaccess.so]
-set othermodule [file normalize tests/modules/moduleconfigs.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
- r module loadex $othermodule CONFIG moduleconfigs.mutable_bool yes
-
- test {Test module config get with standard Redis configs} {
- # Test getting standard Redis configs of different types
- set maxmemory [r config get maxmemory]
- assert_equal [lindex $maxmemory 1] [r configaccess.getnumeric maxmemory]
-
- set port [r config get port]
- assert_equal [lindex $port 1] [r configaccess.getnumeric port]
-
- set appendonly [r config get appendonly]
- assert_equal [string is true [lindex $appendonly 1]] [r configaccess.getbool appendonly]
-
- # Test string config
- set logfile [r config get logfile]
- assert_equal [lindex $logfile 1] [r configaccess.get logfile]
-
- # Test SDS config
- set requirepass [r config get requirepass]
- assert_equal [lindex $requirepass 1] [r configaccess.get requirepass]
-
- # Test special config
- set oom_score_adj_values [r config get oom-score-adj-values]
- assert_equal [lindex $oom_score_adj_values 1] [r configaccess.get oom-score-adj-values]
-
- set maxmemory_policy_name [r configaccess.getenum maxmemory-policy]
- assert_equal [lindex [r config get maxmemory-policy] 1] $maxmemory_policy_name
-
- # Test percent config
- r config set maxmemory 100000
- r configaccess.setnumeric maxmemory-clients -50
- assert_equal [lindex [r config get maxmemory-clients] 1] 50%
-
- # Test multi-argument enum config
- r config set moduleconfigs.flags "one two four"
- assert_equal "five two" [r configaccess.getenum moduleconfigs.flags]
-
- # Test getting multi-argument enum config via generic get
- r config set moduleconfigs.flags "two four"
- assert_equal "two four" [r configaccess.get moduleconfigs.flags]
- }
-
- test {Test module config get with non-existent configs} {
- # Test getting non-existent configs
- catch {r configaccess.getnumeric nonexistent_config} err
- assert_match "ERR*" $err
-
- catch {r configaccess.getbool nonexistent_config} err
- assert_match "ERR*" $err
-
- catch {r configaccess.get nonexistent_config} err
- assert_match "ERR*" $err
-
- catch {r configaccess.getenum nonexistent_config} err
- assert_match "ERR*" $err
- }
-
- test {Test module config set with standard Redis configs} {
- # Test setting numeric config
- set old_maxmemory_samples [r config get maxmemory-samples]
- r configaccess.setnumeric maxmemory-samples 10
- assert_equal "maxmemory-samples 10" [r config get maxmemory-samples]
- r config set maxmemory-samples [lindex $old_maxmemory_samples 1]
-
- # Test setting bool config
- set old_protected_mode [r config get protected-mode]
- r configaccess.setbool protected-mode no
- assert_equal "protected-mode no" [r config get protected-mode]
- r config set protected-mode [lindex $old_protected_mode 1]
-
- # Test setting string config
- set old_masteruser [r config get masteruser]
- r configaccess.set masteruser "__newmasteruser__"
- assert_equal "__newmasteruser__" [lindex [r config get masteruser] 1]
- r config set masteruser [lindex $old_masteruser 1]
-
- # Test setting enum config
- set old_loglevel [r config get loglevel]
- r config set loglevel "notice" ; # Set to some value we are sure is different than the one tested
- r configaccess.setenum loglevel warning
- assert_equal "loglevel warning" [r config get loglevel]
- r config set loglevel [lindex $old_loglevel 1]
-
- # Test setting multi-argument enum config
- r config set moduleconfigs.flags "one two four"
- assert_equal "moduleconfigs.flags {five two}" [r config get moduleconfigs.flags]
- r configaccess.setenum moduleconfigs.flags "two four"
- assert_equal "moduleconfigs.flags {two four}" [r config get moduleconfigs.flags]
-
- # Test setting multi-argument enum config via generic set
- r config set moduleconfigs.flags "one two four"
- assert_equal "moduleconfigs.flags {five two}" [r config get moduleconfigs.flags]
- r configaccess.set moduleconfigs.flags "two four"
- assert_equal "moduleconfigs.flags {two four}" [r config get moduleconfigs.flags]
- }
-
- test {Test module config set with module configs} {
- # Test setting module bool config
- assert_equal "OK" [r configaccess.setbool configaccess.bool no]
- assert_equal "configaccess.bool no" [r config get configaccess.bool]
-
- # Test setting module bool config from another module
- assert_equal "OK" [r configaccess.setbool moduleconfigs.mutable_bool no]
- assert_equal "moduleconfigs.mutable_bool no" [r config get moduleconfigs.mutable_bool]
-
- # Test setting module numeric config
- assert_equal "OK" [r configaccess.setnumeric moduleconfigs.numeric 100]
- assert_equal "moduleconfigs.numeric 100" [r config get moduleconfigs.numeric]
-
- # Test setting module enum config
- assert_equal "OK" [r configaccess.setenum moduleconfigs.enum "five"]
- assert_equal "moduleconfigs.enum five" [r config get moduleconfigs.enum]
- }
-
- test {Test module config set with error cases} {
- # Test setting a non-existent config
- catch {r configaccess.setbool nonexistent_config yes} err
- assert_match "*ERR*" $err
-
- # Test setting a read-only config
- catch {r configaccess.setbool moduleconfigs.immutable_bool yes} err
- assert_match "*ERR*" $err
-
- # Test setting an enum config with invalid value
- catch {r configaccess.setenumname moduleconfigs.enum "invalid_value"} err
- assert_match "*ERR*" $err
-
- # Test setting a numeric config with out-of-range value
- catch {r configaccess.setnumeric moduleconfigs.numeric 5000} err
- assert_match "*ERR*" $err
- catch {r configaccess.setnumeric maxclients -1} err
- assert_match "*Failed to set numeric config maxclients: argument must be between*" $err
- catch {r configaccess.setnumeric maxclients -9223372036854775808} err
- assert_match "*Failed to set numeric config maxclients: argument must be between*" $err
-
- # Sanity check
- assert_equal [r configaccess.setnumeric maxmemory 18446744073709551615] "OK"
- assert_equal [r configaccess.setnumeric maxmemory -1] "OK"
- }
-
- test {Test module get all configs} {
- # Get all configs using the module command
- set all_configs [r configaccess.getconfigs]
-
- # Verify the number of configs matches the number of configs returned
- # by Redis's native CONFIG GET command.
- set all_configs_std_pairs [llength [r config get *]]
-
- # When comparing with the standard CONFIG GET command, we need to divide
- # by 2 because the standard command returns a flattened array of
- # key-value pairs whereas our testing command returns an array of pairs.
- assert_equal [llength $all_configs] [expr $all_configs_std_pairs / 2]
-
- # Verify all the configs are present in both replies.
- foreach config_pair $all_configs {
- assert_equal 2 [llength $config_pair]
- set config_name [lindex $config_pair 0]
- set config_value [lindex $config_pair 1]
-
- # Verify that we can get this config using standard config get
- set redis_config [r config get $config_name]
- assert {[llength $redis_config] != 0}
-
- assert_equal $config_value [lindex $redis_config 1]
- }
-
- # Test that module configs are also included
- set found_module_config 0
- foreach config_pair $all_configs {
- set config_name [lindex $config_pair 0]
- if {$config_name eq "configaccess.bool"} {
- set found_module_config 1
- break
- }
- }
-
- assert {$found_module_config == 1}
-
- # Test pattern matching
- set moduleconfigs_count [r configaccess.getconfigs "moduleconfigs.*"]
- assert_equal 7 [llength $moduleconfigs_count]
-
- set memoryconfigs_count [r configaccess.getconfigs "*memory"]
- assert_equal 3 [llength $memoryconfigs_count]
- }
-
- test {Test module config type detection} {
- # Test getting config types for different config types
- assert_equal "bool" [r configaccess.getconfigtype appendonly]
- assert_equal "numeric" [r configaccess.getconfigtype port]
- assert_equal "string" [r configaccess.getconfigtype logfile]
- assert_equal "enum" [r configaccess.getconfigtype maxmemory-policy]
-
- # Test with module config
- assert_equal "bool" [r configaccess.getconfigtype configaccess.bool]
-
- # Test with non-existent config
- catch {r configaccess.getconfigtype nonexistent_config} err
- assert_match "ERR Config does not exist" $err
- }
-
- test {Test config rollback on apply} {
- set og_port [lindex [r config get port] 1]
-
- set used_port [find_available_port $::baseport $::portcount]
-
- # Run a dummy server on used_port so we know we can't configure redis to
- # use it. It's ok for this to fail because that means used_port is invalid
- # anyway
- catch {set sockfd [socket -server dummy_accept -myaddr 127.0.0.1 $used_port]} e
- if {$::verbose} { puts "dummy_accept: $e" }
-
- # Try to listen on the used port, pass some more configs to make sure the
- # returned failure message is for the first bad config and everything is rolled back.
- assert_error "ERR Failed to set numeric config port: Unable to listen on this port*" {
- eval "r configaccess.setnumeric port $used_port"
- }
-
- assert_equal [lindex [r config get port] 1] $og_port
- close $sockfd
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/crash.tcl b/examples/redis-unstable/tests/unit/moduleapi/crash.tcl
deleted file mode 100644
index 9d9477c..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/crash.tcl
+++ /dev/null
@@ -1,129 +0,0 @@
-# This file is used to test certain crash edge cases to make sure they produce
-# correct stack traces for debugging.
-set testmodule [file normalize tests/modules/crash.so]
-set backtrace_supported [system_backtrace_supported]
-
-# Valgrind will complain that the process terminated by a signal, skip it.
-if {!$::valgrind && !$::tsan} {
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule assert
- test {Test module crash when info crashes with an assertion } {
- catch {r 0 info modulecrash}
- set res [wait_for_log_messages 0 {"*=== REDIS BUG REPORT START: Cut & paste starting from here ===*"} 0 10 1000]
- set loglines [lindex $res 1]
-
- set res [wait_for_log_messages 0 {"*ASSERTION FAILED*"} $loglines 10 1000]
- set loglines [lindex $res 1]
-
- set res [wait_for_log_messages 0 {"*RECURSIVE ASSERTION FAILED*"} $loglines 10 1000]
- set loglines [lindex $res 1]
-
- wait_for_log_messages 0 {"*=== REDIS BUG REPORT END. Make sure to include from START to END. ===*"} $loglines 10 1000
- assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT END. Make sure to include from START to END. ==="]
- assert_equal 2 [count_log_message 0 "ASSERTION FAILED"]
- if {$backtrace_supported} {
- # Make sure the crash trace is printed twice. There will be 3 instances of,
- # assertCrash 1 in the first stack trace and 2 in the second.
- assert_equal 3 [count_log_message 0 "assertCrash"]
- }
- assert_equal 1 [count_log_message 0 "RECURSIVE ASSERTION FAILED"]
- assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT START: Cut & paste starting from here ==="]
- }
- }
-
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule segfault
- test {Test module crash when info crashes with a segfault} {
- catch {r 0 info modulecrash}
- set res [wait_for_log_messages 0 {"*=== REDIS BUG REPORT START: Cut & paste starting from here ===*"} 0 10 1000]
- set loglines [lindex $res 1]
-
- if {$backtrace_supported} {
- set res [wait_for_log_messages 0 {"*Crashed running the instruction at*"} $loglines 10 1000]
- set loglines [lindex $res 1]
-
- set res [wait_for_log_messages 0 {"*Crashed running signal handler. Providing reduced version of recursive crash report*"} $loglines 10 1000]
- set loglines [lindex $res 1]
- set res [wait_for_log_messages 0 {"*Crashed running the instruction at*"} $loglines 10 1000]
- set loglines [lindex $res 1]
- }
-
- wait_for_log_messages 0 {"*=== REDIS BUG REPORT END. Make sure to include from START to END. ===*"} $loglines 10 1000
- assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT END. Make sure to include from START to END. ==="]
- assert_equal 1 [count_log_message 0 "Crashed running signal handler. Providing reduced version of recursive crash report"]
- if {$backtrace_supported} {
- assert_equal 2 [count_log_message 0 "Crashed running the instruction at"]
- # Make sure the crash trace is printed twice. There will be 3 instances of
- # modulesCollectInfo, 1 in the first stack trace and 2 in the second.
- assert_equal 3 [count_log_message 0 "modulesCollectInfo"]
- }
- assert_equal 1 [count_log_message 0 "=== REDIS BUG REPORT START: Cut & paste starting from here ==="]
- }
- }
-
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- # memcheck confuses sanitizer
- r config set crash-memcheck-enabled no
-
- test {Test command tokens are printed when hide-user-data-from-log is enabled (xadd)} {
- r config set hide-user-data-from-log yes
- catch {r 0 modulecrash.xadd key NOMKSTREAM MAXLEN ~ 1000 * a b}
-
- wait_for_log_messages 0 {"*argv*0*: *modulecrash.xadd*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*1*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*2*: *NOMKSTREAM*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*3*: *MAXLEN*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*4*: *~*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*5*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*6*: *\**"} 0 10 1000
- wait_for_log_messages 0 {"*argv*7*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*8*: *redacted*"} 0 10 1000
- }
- }
-
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- # memcheck confuses sanitizer
- r config set crash-memcheck-enabled no
-
- test {Test command tokens are printed when hide-user-data-from-log is enabled (zunion)} {
- r config set hide-user-data-from-log yes
- catch {r 0 modulecrash.zunion 2 zset1 zset2 WEIGHTS 1 2 WITHSCORES somedata}
-
- wait_for_log_messages 0 {"*argv*0*: *modulecrash.zunion*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*1*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*2*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*3*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*4*: *WEIGHTS*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*5*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*6*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*7*: *WITHSCORES*"} 0 10 1000
-
- # We don't expect arguments after WITHSCORE but just in case there
- # is we rather not print it
- wait_for_log_messages 0 {"*argv*8*: *redacted*"} 0 10 1000
- }
- }
-
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- # memcheck confuses sanitizer
- r config set crash-memcheck-enabled no
-
- test {Test subcommand name is printed when hide-user-data-from-log is enabled} {
- r config set hide-user-data-from-log yes
- catch {r 0 modulecrash.parent subcmd key TOKEN a b}
-
- wait_for_log_messages 0 {"*argv*0*: *modulecrash.parent*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*1*: *subcmd*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*2*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*3*: *TOKEN*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*4*: *redacted*"} 0 10 1000
- wait_for_log_messages 0 {"*argv*5*: *redacted*"} 0 10 1000
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/datatype.tcl b/examples/redis-unstable/tests/unit/moduleapi/datatype.tcl
deleted file mode 100644
index fffb841..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/datatype.tcl
+++ /dev/null
@@ -1,238 +0,0 @@
-set testmodule [file normalize tests/modules/datatype.so]
-
-start_server {tags {"modules external:skip"}} {
- test {DataType: test loadex with invalid config} {
- catch { r module loadex $testmodule CONFIG invalid_config 1 } e
- assert_match {*ERR Error loading the extension*} $e
- }
-
- r module load $testmodule
-
- test {DataType: Test module is sane, GET/SET work.} {
- r datatype.set dtkey 100 stringval
- assert {[r datatype.get dtkey] eq {100 stringval}}
- }
-
- test {test blocking of datatype creation outside of OnLoad} {
- assert_equal [r block.create.datatype.outside.onload] OK
- }
-
- test {DataType: RM_SaveDataTypeToString(), RM_LoadDataTypeFromStringEncver() work} {
- r datatype.set dtkey -1111 MyString
- set encoded [r datatype.dump dtkey]
-
- assert {[r datatype.restore dtkeycopy $encoded 4] eq {4}}
- assert {[r datatype.get dtkeycopy] eq {-1111 MyString}}
- }
-
- test {DataType: Handle truncated RM_LoadDataTypeFromStringEncver()} {
- r datatype.set dtkey -1111 MyString
- set encoded [r datatype.dump dtkey]
- set truncated [string range $encoded 0 end-1]
-
- catch {r datatype.restore dtkeycopy $truncated 4} e
- set e
- } {*Invalid*}
-
- test {DataType: ModuleTypeReplaceValue() happy path works} {
- r datatype.set key-a 1 AAA
- r datatype.set key-b 2 BBB
-
- assert {[r datatype.swap key-a key-b] eq {OK}}
- assert {[r datatype.get key-a] eq {2 BBB}}
- assert {[r datatype.get key-b] eq {1 AAA}}
- }
-
- test {DataType: ModuleTypeReplaceValue() fails on non-module keys} {
- r datatype.set key-a 1 AAA
- r set key-b RedisString
-
- catch {r datatype.swap key-a key-b} e
- set e
- } {*ERR*}
-
- test {DataType: Copy command works for modules} {
- # Test failed copies
- r datatype.set answer-to-universe 42 AAA
- catch {r copy answer-to-universe answer2} e
- assert_match {*module key failed to copy*} $e
-
- # Our module's data type copy function copies the int value as-is
- # but appends /<from-key>/<to-key> to the string value so we can
- # track passed arguments.
- r datatype.set sourcekey 1234 AAA
- r copy sourcekey targetkey
- r datatype.get targetkey
- } {1234 AAA/sourcekey/targetkey}
-
- test {DataType: Slow Loading} {
- r config set busy-reply-threshold 5000 ;# make sure we're using a high default
- # trigger slow loading
- r datatype.slow_loading 1
- set rd [redis_deferring_client]
- set start [clock clicks -milliseconds]
- $rd debug reload
-
- # wait till we know we're blocked inside the module
- wait_for_condition 50 100 {
- [r datatype.is_in_slow_loading] eq 1
- } else {
- fail "Failed waiting for slow loading to start"
- }
-
- # make sure we get LOADING error, and that we didn't get here late (not waiting for busy-reply-threshold)
- assert_error {*LOADING*} {r ping}
- assert_lessthan [expr [clock clicks -milliseconds]-$start] 2000
-
- # abort the blocking operation
- r datatype.slow_loading 0
- wait_for_condition 50 100 {
- [s loading] eq {0}
- } else {
- fail "Failed waiting for loading to end"
- }
- $rd read
- $rd close
- }
-
- test {DataType: check the type name} {
- r flushdb
- r datatype.set foo 111 bar
- assert_type test___dt foo
- }
-
- test {SCAN module datatype} {
- r flushdb
- populate 1000
- r datatype.set foo 111 bar
- set type [r type foo]
- set cur 0
- set keys {}
- while 1 {
- set res [r scan $cur type $type]
- set cur [lindex $res 0]
- set k [lindex $res 1]
- lappend keys {*}$k
- if {$cur == 0} break
- }
-
- assert_equal 1 [llength $keys]
- }
-
- test {SCAN module datatype with case sensitive} {
- r flushdb
- populate 1000
- r datatype.set foo 111 bar
- set type "tEsT___dT"
- set cur 0
- set keys {}
- while 1 {
- set res [r scan $cur type $type]
- set cur [lindex $res 0]
- set k [lindex $res 1]
- lappend keys {*}$k
- if {$cur == 0} break
- }
-
- assert_equal 1 [llength $keys]
- }
-
- if {[string match {*jemalloc*} [s mem_allocator]] && [r debug mallctl arenas.page] <= 8192} {
- test {Reduce defrag CPU usage when module data can't be defragged} {
- r flushdb
- 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 25
- r config set active-defrag-cycle-max 75
- r config set active-defrag-ignore-bytes 100kb
-
- # Populate memory with interleaving field of same size.
- set n 20000
- set dummy "[string repeat x 400]"
- set rd [redis_deferring_client]
- for {set i 0} {$i < $n} {incr i} { $rd datatype.set k$i 1 $dummy }
- for {set i 0} {$i < [expr $n]} {incr i} { $rd read } ;# Discard 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
-
- for {set i 0} {$i < $n} {incr i 2} { $rd del k$i }
- for {set j 0} {$j < $n} {incr j 2} { $rd read } ; # Discard del replies
- after 120 ;# serverCron only updates the info once in 100ms
- 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 total_active_defrag_time] ne 0
- } else {
- after 120 ;# serverCron only updates the info once in 100ms
- puts [r info memory]
- puts [r info stats]
- puts [r memory malloc-stats]
- fail "defrag not started."
- }
- assert_morethan [s allocator_frag_ratio] 1.4
-
- # The cpu usage of defragment will drop to active-defrag-cycle-min
- wait_for_condition 1000 50 {
- [s active_defrag_running] == 25
- } else {
- fail "Unable to reduce the defragmentation speed."
- }
-
- # Fuzzy test to restore defragmentation speed to normal
- set end_time [expr {[clock seconds] + 10}]
- set speed_restored 0
- while {[clock seconds] < $end_time} {
- for {set i 0} {$i < 500} {incr i} {
- switch [expr {int(rand() * 3)}] {
- 0 {
- # Randomly delete a key
- set random_key [r RANDOMKEY]
- if {$random_key != ""} {
- r DEL $random_key
- }
- }
- 1 {
- # Randomly overwrite a key
- set random_key [r RANDOMKEY]
- if {$random_key != ""} {
- r datatype.set $random_key 1 $dummy
- }
- }
- 2 {
- # Randomly generate a new key
- set random_key "key_[expr {int(rand() * 10000)}]"
- r datatype.set $random_key 1 $dummy
- }
- } ;# end of switch
- } ;# end of for
-
- # Wait for defragmentation speed to restore.
- if {{[count_log_message $loglines "*Starting active defrag, frag=*%, frag_bytes=*, cpu=5?%*"]} > 1} {
- set speed_restored 1
- break;
- }
- }
- # Make sure the speed is restored
- assert_equal $speed_restored 1
-
- # After the traffic disappears, the defragmentation speed will decrease again.
- wait_for_condition 1000 50 {
- [s active_defrag_running] == 25
- } else {
- fail "Unable to reduce the defragmentation speed after traffic disappears."
- }
- }
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl b/examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl
deleted file mode 100644
index 9f40178..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/datatype2.tcl
+++ /dev/null
@@ -1,232 +0,0 @@
-set testmodule [file normalize tests/modules/datatype2.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test "datatype2: test mem alloc and free" {
- r flushall
-
- r select 0
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal 2 [r mem.alloc k2 2]
-
- r select 1
- assert_equal 1 [r mem.alloc k1 1]
- assert_equal 5 [r mem.alloc k2 5]
-
- r select 0
- assert_equal 1 [r mem.free k1]
- assert_equal 1 [r mem.free k2]
-
- r select 1
- assert_equal 1 [r mem.free k1]
- assert_equal 1 [r mem.free k2]
- }
-
- test "datatype2: test del and unlink" {
- r flushall
-
- assert_equal 100 [r mem.alloc k1 100]
- assert_equal 60 [r mem.alloc k2 60]
-
- assert_equal 1 [r unlink k1]
- assert_equal 1 [r del k2]
- }
-
- test "datatype2: test read and write" {
- r flushall
-
- assert_equal 3 [r mem.alloc k1 3]
-
- set data datatype2
- assert_equal [string length $data] [r mem.write k1 0 $data]
- assert_equal $data [r mem.read k1 0]
- }
-
- test "datatype2: test rdb save and load" {
- r flushall
-
- r select 0
- set data k1
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal [string length $data] [r mem.write k1 1 $data]
-
- set data k2
- assert_equal 2 [r mem.alloc k2 2]
- assert_equal [string length $data] [r mem.write k2 0 $data]
-
- r select 1
- set data k3
- assert_equal 3 [r mem.alloc k3 3]
- assert_equal [string length $data] [r mem.write k3 1 $data]
-
- set data k4
- assert_equal 2 [r mem.alloc k4 2]
- assert_equal [string length $data] [r mem.write k4 0 $data]
-
- r bgsave
- waitForBgsave r
- r debug reload
-
- r select 0
- assert_equal k1 [r mem.read k1 1]
- assert_equal k2 [r mem.read k2 0]
-
- r select 1
- assert_equal k3 [r mem.read k3 1]
- assert_equal k4 [r mem.read k4 0]
- }
-
- test "datatype2: test aof rewrite" {
- r flushall
-
- r select 0
- set data k1
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal [string length $data] [r mem.write k1 1 $data]
-
- set data k2
- assert_equal 2 [r mem.alloc k2 2]
- assert_equal [string length $data] [r mem.write k2 0 $data]
-
- r select 1
- set data k3
- assert_equal 3 [r mem.alloc k3 3]
- assert_equal [string length $data] [r mem.write k3 1 $data]
-
- set data k4
- assert_equal 2 [r mem.alloc k4 2]
- assert_equal [string length $data] [r mem.write k4 0 $data]
-
- r bgrewriteaof
- waitForBgrewriteaof r
- r debug loadaof
-
- r select 0
- assert_equal k1 [r mem.read k1 1]
- assert_equal k2 [r mem.read k2 0]
-
- r select 1
- assert_equal k3 [r mem.read k3 1]
- assert_equal k4 [r mem.read k4 0]
- }
-
- test "datatype2: test copy" {
- r flushall
-
- r select 0
- set data k1
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal [string length $data] [r mem.write k1 1 $data]
- assert_equal $data [r mem.read k1 1]
-
- set data k2
- assert_equal 2 [r mem.alloc k2 2]
- assert_equal [string length $data] [r mem.write k2 0 $data]
- assert_equal $data [r mem.read k2 0]
-
- r select 1
- set data k3
- assert_equal 3 [r mem.alloc k3 3]
- assert_equal [string length $data] [r mem.write k3 1 $data]
-
- set data k4
- assert_equal 2 [r mem.alloc k4 2]
- assert_equal [string length $data] [r mem.write k4 0 $data]
-
- assert_equal {total 5 used 2} [r mem.usage 0]
- assert_equal {total 5 used 2} [r mem.usage 1]
-
- r select 0
- assert_equal 1 [r copy k1 k3]
- assert_equal k1 [r mem.read k3 1]
- assert_equal {total 8 used 3} [r mem.usage 0]
- assert_equal 1 [r copy k2 k1 db 1]
-
- r select 1
- assert_equal k2 [r mem.read k1 0]
- assert_equal {total 8 used 3} [r mem.usage 0]
- assert_equal {total 7 used 3} [r mem.usage 1]
- }
-
- test "datatype2: test swapdb" {
- r flushall
-
- r select 0
- set data k1
- assert_equal 5 [r mem.alloc k1 5]
- assert_equal [string length $data] [r mem.write k1 1 $data]
- assert_equal $data [r mem.read k1 1]
-
- set data k2
- assert_equal 4 [r mem.alloc k2 4]
- assert_equal [string length $data] [r mem.write k2 0 $data]
- assert_equal $data [r mem.read k2 0]
-
- r select 1
- set data k1
- assert_equal 3 [r mem.alloc k3 3]
- assert_equal [string length $data] [r mem.write k3 1 $data]
-
- set data k2
- assert_equal 2 [r mem.alloc k4 2]
- assert_equal [string length $data] [r mem.write k4 0 $data]
-
- assert_equal {total 9 used 2} [r mem.usage 0]
- assert_equal {total 5 used 2} [r mem.usage 1]
-
- assert_equal OK [r swapdb 0 1]
- assert_equal {total 9 used 2} [r mem.usage 1]
- assert_equal {total 5 used 2} [r mem.usage 0]
- }
-
- test "datatype2: test digest" {
- r flushall
-
- r select 0
- set data k1
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal [string length $data] [r mem.write k1 1 $data]
- assert_equal $data [r mem.read k1 1]
-
- set data k2
- assert_equal 2 [r mem.alloc k2 2]
- assert_equal [string length $data] [r mem.write k2 0 $data]
- assert_equal $data [r mem.read k2 0]
-
- r select 1
- set data k1
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal [string length $data] [r mem.write k1 1 $data]
- assert_equal $data [r mem.read k1 1]
-
- set data k2
- assert_equal 2 [r mem.alloc k2 2]
- assert_equal [string length $data] [r mem.write k2 0 $data]
- assert_equal $data [r mem.read k2 0]
-
- r select 0
- set digest0 [debug_digest]
-
- r select 1
- set digest1 [debug_digest]
-
- assert_equal $digest0 $digest1
- }
-
- test "datatype2: test memusage" {
- r flushall
-
- set data k1
- assert_equal 3 [r mem.alloc k1 3]
- assert_equal [string length $data] [r mem.write k1 1 $data]
- assert_equal $data [r mem.read k1 1]
-
- set data k2
- assert_equal 3 [r mem.alloc k2 3]
- assert_equal [string length $data] [r mem.write k2 0 $data]
- assert_equal $data [r mem.read k2 0]
-
- assert_equal [memory_usage k1] [memory_usage k2]
- }
-} \ No newline at end of file
diff --git a/examples/redis-unstable/tests/unit/moduleapi/defrag.tcl b/examples/redis-unstable/tests/unit/moduleapi/defrag.tcl
deleted file mode 100644
index 7e714da..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/defrag.tcl
+++ /dev/null
@@ -1,101 +0,0 @@
-set testmodule [file normalize tests/modules/defragtest.so]
-
-start_server {tags {"modules external:skip debug_defrag:skip"} overrides {{save ""}}} {
- r module load $testmodule
- r config set hz 100
- r config set active-defrag-ignore-bytes 1
- r config set active-defrag-threshold-lower 0
- r config set active-defrag-cycle-min 99
-
- # try to enable active defrag, it will fail if redis was compiled without it
- catch {r config set activedefrag yes} e
- if {[r config get activedefrag] eq "activedefrag yes"} {
-
- test {Module defrag: simple key defrag works} {
- r config set activedefrag no
- wait_for_condition 100 50 {
- [s active_defrag_running] eq 0
- } else {
- fail "Unable to wait for active defrag to stop"
- }
-
- r flushdb
- r frag.resetstats
- r frag.create key1 1 1000 0
-
- r config set activedefrag yes
- wait_for_condition 200 50 {
- [getInfoProperty [r info defragtest_stats] defragtest_defrag_ended] > 0
- } else {
- fail "Unable to wait for a complete defragmentation cycle to finish"
- }
-
- set info [r info defragtest_stats]
- assert {[getInfoProperty $info defragtest_datatype_attempts] > 0}
- assert_equal 0 [getInfoProperty $info defragtest_datatype_resumes]
- assert_morethan [getInfoProperty $info defragtest_datatype_raw_defragged] 0
- assert_morethan [getInfoProperty $info defragtest_defrag_started] 0
- assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0
- } {} {tsan:skip}
-
- test {Module defrag: late defrag with cursor works} {
- r config set activedefrag no
- wait_for_condition 100 50 {
- [s active_defrag_running] eq 0
- } else {
- fail "Unable to wait for active defrag to stop"
- }
-
- r flushdb
- r frag.resetstats
-
- # key can only be defragged in no less than 10 iterations
- # due to maxstep
- r frag.create key2 10000 100 1000
-
- r config set activedefrag yes
- wait_for_condition 1000 50 {
- [getInfoProperty [r info defragtest_stats] defragtest_defrag_ended] > 0 &&
- [getInfoProperty [r info defragtest_stats] defragtest_datatype_resumes] > 10
- } else {
- fail "Unable to wait for a complete defragmentation cycle to finish"
- }
-
- set info [r info defragtest_stats]
- assert_equal 0 [getInfoProperty $info defragtest_datatype_wrong_cursor]
- assert_morethan [getInfoProperty $info defragtest_datatype_raw_defragged] 0
- assert_morethan [getInfoProperty $info defragtest_defrag_started] 0
- assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0
- } {} {tsan:skip}
-
- test {Module defrag: global defrag works} {
- r config set activedefrag no
- wait_for_condition 100 50 {
- [s active_defrag_running] eq 0
- } else {
- fail "Unable to wait for active defrag to stop"
- }
-
- r flushdb
- r frag.resetstats
- r frag.create_frag_global 50000
- r config set activedefrag yes
-
- wait_for_condition 1000 50 {
- [getInfoProperty [r info defragtest_stats] defragtest_defrag_ended] > 0
- } else {
- fail "Unable to wait for a complete defragmentation cycle to finish"
- }
-
- set info [r info defragtest_stats]
- assert {[getInfoProperty $info defragtest_global_strings_attempts] > 0}
- assert {[getInfoProperty $info defragtest_global_dicts_attempts] > 0}
- assert {[getInfoProperty $info defragtest_global_dicts_defragged] > 0}
- assert {[getInfoProperty $info defragtest_global_dicts_items_defragged] > 0}
- assert_morethan [getInfoProperty $info defragtest_defrag_started] 0
- assert_morethan [getInfoProperty $info defragtest_defrag_ended] 0
- assert_morethan [getInfoProperty $info defragtest_global_dicts_resumes] [getInfoProperty $info defragtest_defrag_ended]
- assert_morethan [getInfoProperty $info defragtest_global_subdicts_resumes] [getInfoProperty $info defragtest_defrag_ended]
- } {} {tsan:skip}
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl b/examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl
deleted file mode 100644
index ba0d4a2..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/eventloop.tcl
+++ /dev/null
@@ -1,28 +0,0 @@
-set testmodule [file normalize tests/modules/eventloop.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test "Module eventloop sendbytes" {
- assert_match "OK" [r test.sendbytes 5000000]
- assert_match "OK" [r test.sendbytes 2000000]
- }
-
- test "Module eventloop iteration" {
- set iteration [r test.iteration]
- set next_iteration [r test.iteration]
- assert {$next_iteration > $iteration}
- }
-
- test "Module eventloop sanity" {
- r test.sanity
- }
-
- test "Module eventloop oneshot" {
- r test.oneshot
- }
-
- test "Unload the module - eventloop" {
- assert_equal {OK} [r module unload eventloop]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/fork.tcl b/examples/redis-unstable/tests/unit/moduleapi/fork.tcl
deleted file mode 100644
index 19b3a68..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/fork.tcl
+++ /dev/null
@@ -1,41 +0,0 @@
-set testmodule [file normalize tests/modules/fork.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Module fork} {
- # the argument to fork.create is the exitcode on termination
- # the second argument to fork.create is passed to usleep
- r fork.create 3 100000 ;# 100ms
- wait_for_condition 20 100 {
- [r fork.exitcode] != -1
- } else {
- fail "fork didn't terminate"
- }
- r fork.exitcode
- } {3}
-
- test {Module fork kill} {
- # use a longer time to avoid the child exiting before being killed
- r fork.create 3 100000000 ;# 100s
- wait_for_condition 20 100 {
- [count_log_message 0 "fork child started"] == 2
- } else {
- fail "fork didn't start"
- }
-
- # module fork twice
- assert_error {Fork failed} {r fork.create 0 1}
- assert {[count_log_message 0 "Can't fork for module: File exists"] eq "1"}
-
- r fork.kill
-
- assert {[count_log_message 0 "Received SIGUSR1 in child"] eq "1"}
- # check that it wasn't printed again (the print belong to the previous test)
- assert {[count_log_message 0 "fork child exiting"] eq "1"}
- }
-
- test "Unload the module - fork" {
- assert_equal {OK} [r module unload fork]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl b/examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl
deleted file mode 100644
index abfea5a..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/getchannels.tcl
+++ /dev/null
@@ -1,40 +0,0 @@
-set testmodule [file normalize tests/modules/getchannels.so]
-
-start_server {tags {"modules external:skip"}} {
- 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 "User testuser has no permissions to access the 'nopattern1' channel" [r ACL DRYRUN testuser getchannels.command subscribe literal channel subscribe literal nopattern1]
- assert_equal "User testuser 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 "User testuser has no permissions to access the 'otherchannel' channel" [r ACL DRYRUN testuser getchannels.command subscribe literal otherchannel subscribe literal pattern1]
- assert_equal "User testuser 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 "User testuser has no permissions to access the 'pattern1' channel" [r ACL DRYRUN testuser getchannels.command subscribe pattern pattern1 subscribe pattern pattern*]
- assert_equal "User testuser 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 "User testuser has no permissions to access the 'otherpattern*' channel" [r ACL DRYRUN testuser getchannels.command subscribe pattern otherpattern* subscribe pattern pattern*]
- assert_equal "User testuser 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]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl b/examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl
deleted file mode 100644
index 2854b86..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/getkeys.tcl
+++ /dev/null
@@ -1,89 +0,0 @@
-set testmodule [file normalize tests/modules/getkeys.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {COMMAND INFO correctly reports a movable keys module command} {
- set info [lindex [r command info getkeys.command] 0]
-
- assert_equal {module movablekeys} [lindex $info 2]
- assert_equal {0} [lindex $info 3]
- assert_equal {0} [lindex $info 4]
- assert_equal {0} [lindex $info 5]
- }
-
- test {COMMAND GETKEYS correctly reports a movable keys module command} {
- 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 0 non-command key1 key2} e
- set _ $e
- } {*ENOENT*}
-
- test {RM_GetCommandKeys on built-in fixed keys command} {
- 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 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 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 0 getkeys.fixed arg1 key1 key2 key3 arg2
- } {key1 key2 key3}
-
- test {RM_GetCommandKeys with bad arity} {
- catch {r getkeys.introspect 0 set key} e
- set _ $e
- } {*EINVAL*}
-
- test "introspect with > MAX_KEYS_BUFFER keys triggers RM_GetCommandKeysWithFlags heap alloc" {
- set args {}
- for {set i 0} {$i < 7} {incr i} {
- lappend args key k$i
- }
- set reply [r getkeys.introspect 1 getkeys.command_with_flags {*}$args]
- assert_equal {{k0 RO} {k1 RO} {k2 RO} {k3 RO} {k4 RO} {k5 RO} {k6 RO}} $reply
- }
-
- # 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_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN testuser getkeys.command key read]
- assert_match {*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_match {*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]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/hash.tcl b/examples/redis-unstable/tests/unit/moduleapi/hash.tcl
deleted file mode 100644
index 8373c2d..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/hash.tcl
+++ /dev/null
@@ -1,176 +0,0 @@
-set testmodule [file normalize tests/modules/hash.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Module hash set} {
- r set k mystring
- assert_error "WRONGTYPE*" {r hash.set k "" hello world}
- r del k
- # "" = count updates and deletes of existing fields only
- assert_equal 0 [r hash.set k "" squirrel yes]
- # "a" = COUNT_ALL = count inserted, modified and deleted fields
- assert_equal 2 [r hash.set k "a" banana no sushi whynot]
- # "n" = NX = only add fields not already existing in the hash
- # "x" = XX = only replace the value for existing fields
- assert_equal 0 [r hash.set k "n" squirrel hoho what nothing]
- assert_equal 1 [r hash.set k "na" squirrel hoho something nice]
- assert_equal 0 [r hash.set k "xa" new stuff not inserted]
- assert_equal 1 [r hash.set k "x" squirrel ofcourse]
- assert_equal 1 [r hash.set k "" sushi :delete: none :delete:]
- r hgetall k
- } {squirrel ofcourse banana no what nothing something nice}
-
- test {Module hash - set (override) NX expired field successfully} {
- r debug set-active-expire 0
- r del H1 H2
- r hash.set H1 "n" f1 v1
- r hpexpire H1 1 FIELDS 1 f1
- r hash.set H2 "n" f1 v1 f2 v2
- r hpexpire H2 1 FIELDS 1 f1
- after 5
- assert_equal 0 [r hash.set H1 "n" f1 xx]
- assert_equal "f1 xx" [r hgetall H1]
- assert_equal 0 [r hash.set H2 "n" f1 yy]
- assert_equal "f1 f2 v2 yy" [lsort [r hgetall H2]]
- r debug set-active-expire 1
- } {OK} {needs:debug}
-
- test {Module hash - set XX of expired field gets failed as expected} {
- r debug set-active-expire 0
- r del H1 H2
- r hash.set H1 "n" f1 v1
- r hpexpire H1 1 FIELDS 1 f1
- r hash.set H2 "n" f1 v1 f2 v2
- r hpexpire H2 1 FIELDS 1 f1
- after 5
-
- # expected to fail on condition XX. hgetall should return empty list
- r hash.set H1 "x" f1 xx
- assert_equal "" [lsort [r hgetall H1]]
- # But expired field was not lazy deleted
- assert_equal 1 [r hlen H1]
-
- # expected to fail on condition XX. hgetall should return list without expired f1
- r hash.set H2 "x" f1 yy
- assert_equal "f2 v2" [lsort [r hgetall H2]]
- # But expired field was not lazy deleted
- assert_equal 2 [r hlen H2]
-
- r debug set-active-expire 1
- } {OK} {needs:debug}
-
- test {Module hash - test open key with REDISMODULE_OPEN_KEY_ACCESS_EXPIRED to scan expired fields} {
- r debug set-active-expire 0
- r del H1
- r hash.set H1 "n" f1 v1 f2 v2 f3 v3
- r hpexpire H1 1 FIELDS 2 f1 f2
- after 10
- # Scan expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
- assert_equal "f1 f2 f3 v1 v2 v3" [lsort [r hash.hscan_expired H1]]
- # Get expired field with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
- assert_equal {v1} [r hash.hget_expired H1 f1]
- # Verify we can get the TTL of the expired field as well
- set now [expr [clock seconds]*1000]
- assert_range [r hash.hget_expire H1 f2] [expr {$now-1000}] [expr {$now+1000}]
- # Verify key doesn't exist on normal access without the flag
- assert_equal 0 [r hexists H1 f1]
- assert_equal 0 [r hexists H1 f2]
- # Scan again expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
- assert_equal "f3 v3" [lsort [r hash.hscan_expired H1]]
- r debug set-active-expire 1
- }
-
- test {Module hash - test open key with REDISMODULE_OPEN_KEY_ACCESS_EXPIRED to scan expired key} {
- r debug set-active-expire 0
- r del H1
- r hash.set H1 "n" f1 v1 f2 v2 f3 v3
- r pexpire H1 5
- after 10
- # Scan expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
- assert_equal "f1 f2 f3 v1 v2 v3" [lsort [r hash.hscan_expired H1]]
- # Get expired field with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
- assert_equal {v1} [r hash.hget_expired H1 f1]
- # Verify key doesn't exist on normal access without the flag
- assert_equal 0 [r exists H1]
- r debug set-active-expire 1
- }
-
- test {Module hash - Read field expiration time} {
- r del H1
- r hash.set H1 "n" f1 v1 f2 v2 f3 v3 f4 v4
- r hexpire H1 10 FIELDS 1 f1
- r hexpire H1 100 FIELDS 1 f2
- r hexpire H1 1000 FIELDS 1 f3
-
- # Validate that the expiration times for fields f1, f2, and f3 are correct
- set nowMsec [expr [clock seconds]*1000]
- assert_range [r hash.hget_expire H1 f1] [expr {$nowMsec+9000}] [expr {$nowMsec+11000}]
- assert_range [r hash.hget_expire H1 f2] [expr {$nowMsec+90000}] [expr {$nowMsec+110000}]
- assert_range [r hash.hget_expire H1 f3] [expr {$nowMsec+900000}] [expr {$nowMsec+1100000}]
-
- # Assert that field f4 and f5_not_exist have no expiration (should return -1)
- assert_equal [r hash.hget_expire H1 f4] -1
- assert_equal [r hash.hget_expire H1 f5_not_exist] -1
-
- # Assert that variadic version of hget_expire works as well
- assert_equal [r hash.hget_two_expire H1 f1 f2] [list [r hash.hget_expire H1 f1] [r hash.hget_expire H1 f2]]
- }
-
- test {Module hash - Read minimum expiration time} {
- r del H1
- r hash.set H1 "n" f1 v1 f2 v2 f3 v3 f4 v4
- r hexpire H1 100 FIELDS 1 f1
- r hexpire H1 10 FIELDS 1 f2
- r hexpire H1 1000 FIELDS 1 f3
-
- # Validate that the minimum expiration time is correct
- set nowMsec [expr [clock seconds]*1000]
- assert_range [r hash.hget_min_expire H1] [expr {$nowMsec+9000}] [expr {$nowMsec+11000}]
- assert_equal [r hash.hget_min_expire H1] [r hash.hget_expire H1 f2]
-
- # Assert error if key not found
- assert_error {*key not found*} {r hash.hget_min_expire non_exist_hash}
-
- # Assert return -1 if no expiration (=REDISMODULE_NO_EXPIRE)
- r del H2
- r hash.set H2 "n" f1 v1
- assert_equal [r hash.hget_min_expire H2] -1
- }
-
- test {Module hash - KEYSIZES is updated as expected} {
- proc run_cmd_verify_hist {cmd expOutput {retries 1}} {
- proc K {} {return [string map { "db0_distrib_hashes_items" "db0_HASH" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]}
- uplevel 1 $cmd
- wait_for_condition 50 $retries {
- $expOutput eq [K]
- } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" }
- }
-
- r select 0
- r flushall
- # Check RM_HashSet
- run_cmd_verify_hist {r hash.set H1 "n" f1 v1} {db0_HASH:1=1}
- run_cmd_verify_hist {r hash.set H2 "n" f1 v1} {db0_HASH:1=2}
- run_cmd_verify_hist {r hash.set H2 "n" f1 v1} {db0_HASH:1=2}
- run_cmd_verify_hist {r hash.set H2 "x" f1 v1} {db0_HASH:1=2}
- run_cmd_verify_hist {r hash.set H3 "x" f1 v1} {db0_HASH:1=2}
- run_cmd_verify_hist {r hash.set H1 "n" f2 v2} {db0_HASH:1=1,2=1}
- run_cmd_verify_hist {r hash.set H1 "a" f3 v3 f4 v4} {db0_HASH:1=1,4=1}
- run_cmd_verify_hist {r del H1} {db0_HASH:1=1}
- run_cmd_verify_hist {r del H2} {}
-
- # Check lazy expire
- r debug set-active-expire 0
- run_cmd_verify_hist {r hash.set H1 "n" f1 v1} {db0_HASH:1=1}
- run_cmd_verify_hist {r hpexpire H1 1 FIELDS 1 f1} {db0_HASH:1=1}
- run_cmd_verify_hist {after 5} {db0_HASH:1=1}
- run_cmd_verify_hist {r hash.hget_expired H1 f1} {db0_HASH:1=1}
- r debug set-active-expire 1
- run_cmd_verify_hist {after 5} {} 50
- }
-
- test "Unload the module - hash" {
- assert_equal {OK} [r module unload hash]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/hooks.tcl b/examples/redis-unstable/tests/unit/moduleapi/hooks.tcl
deleted file mode 100644
index c4be3e4..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/hooks.tcl
+++ /dev/null
@@ -1,321 +0,0 @@
-set testmodule [file normalize tests/modules/hooks.so]
-
-tags "modules external:skip" {
- start_server [list overrides [list loadmodule "$testmodule" appendonly yes]] {
- test {Test module aof save on server start from empty} {
- assert {[r hooks.event_count persistence-syncaof-start] == 1}
- }
-
- test {Test clients connection / disconnection hooks} {
- for {set j 0} {$j < 2} {incr j} {
- set rd1 [redis_deferring_client]
- $rd1 close
- }
- assert {[r hooks.event_count client-connected] > 1}
- assert {[r hooks.event_count client-disconnected] > 1}
- }
-
- test {Test module client change event for blocked client} {
- set rd [redis_deferring_client]
- # select db other than 0
- $rd select 1
- # block on key
- $rd brpop foo 0
- # kill blocked client
- r client kill skipme yes
- # assert server is still up
- assert_equal [r ping] PONG
- $rd close
- }
-
- test {Test module cron hook} {
- after 100
- assert {[r hooks.event_count cron-loop] > 0}
- set hz [r hooks.event_last cron-loop]
- assert_equal $hz 10
- }
-
- test {Test module loaded / unloaded hooks} {
- set othermodule [file normalize tests/modules/infotest.so]
- r module load $othermodule
- r module unload infotest
- assert_equal [r hooks.event_last module-loaded] "infotest"
- assert_equal [r hooks.event_last module-unloaded] "infotest"
- }
-
- test {Test module aofrw hook} {
- r debug populate 1000 foo 10000 ;# 10mb worth of data
- r config set rdbcompression no ;# rdb progress is only checked once in 2mb
- r BGREWRITEAOF
- waitForBgrewriteaof r
- assert_equal [string match {*module-event-persistence-aof-start*} [exec tail -20 < [srv 0 stdout]]] 1
- assert_equal [string match {*module-event-persistence-end*} [exec tail -20 < [srv 0 stdout]]] 1
- }
-
- test {Test module aof load and rdb/aof progress hooks} {
- # create some aof tail (progress is checked only once in 1000 commands)
- for {set j 0} {$j < 4000} {incr j} {
- r set "bar$j" x
- }
- # set some configs that will cause many loading progress events during aof loading
- r config set key-load-delay 500
- r config set dynamic-hz no
- r config set hz 500
- r DEBUG LOADAOF
- assert_equal [r hooks.event_last loading-aof-start] 0
- assert_equal [r hooks.event_last loading-end] 0
- assert {[r hooks.event_count loading-rdb-start] == 0}
- assert_lessthan 2 [r hooks.event_count loading-progress-rdb] ;# comes from the preamble section
- assert_lessthan 2 [r hooks.event_count loading-progress-aof]
- if {$::verbose} {
- puts "rdb progress events [r hooks.event_count loading-progress-rdb]"
- puts "aof progress events [r hooks.event_count loading-progress-aof]"
- }
- }
- # undo configs before next test
- r config set dynamic-hz yes
- r config set key-load-delay 0
-
- test {Test module rdb save hook} {
- # debug reload does: save, flush, load:
- assert {[r hooks.event_count persistence-syncrdb-start] == 0}
- assert {[r hooks.event_count loading-rdb-start] == 0}
- r debug reload
- assert {[r hooks.event_count persistence-syncrdb-start] == 1}
- assert {[r hooks.event_count loading-rdb-start] == 1}
- }
-
- test {Test key unlink hook} {
- r set testkey1 hello
- r del testkey1
- assert {[r hooks.event_count key-info-testkey1] == 1}
- assert_equal [r hooks.event_last key-info-testkey1] testkey1
- r lpush testkey1 hello
- r lpop testkey1
- assert {[r hooks.event_count key-info-testkey1] == 2}
- assert_equal [r hooks.event_last key-info-testkey1] testkey1
- r set testkey2 world
- r unlink testkey2
- assert {[r hooks.event_count key-info-testkey2] == 1}
- assert_equal [r hooks.event_last key-info-testkey2] testkey2
- }
-
- test {Test removed key event} {
- r set str abcd
- r set str abcde
- # For String Type value is returned
- assert_equal {abcd overwritten} [r hooks.is_key_removed str]
- assert_equal -1 [r hooks.pexpireat str]
-
- r del str
- assert_equal {abcde deleted} [r hooks.is_key_removed str]
- assert_equal -1 [r hooks.pexpireat str]
-
- # test int encoded string
- r set intstr 12345678
- # incr doesn't fire event
- r incr intstr
- catch {[r hooks.is_key_removed intstr]} output
- assert_match {ERR * removed} $output
- r del intstr
- assert_equal {12345679 deleted} [r hooks.is_key_removed intstr]
-
- catch {[r hooks.is_key_removed not-exists]} output
- assert_match {ERR * removed} $output
-
- r hset hash f v
- r hdel hash f
- assert_equal {0 deleted} [r hooks.is_key_removed hash]
-
- r hset hash f v a b
- r del hash
- assert_equal {2 deleted} [r hooks.is_key_removed hash]
-
- r lpush list 1
- r lpop list
- assert_equal {0 deleted} [r hooks.is_key_removed list]
-
- r lpush list 1 2 3
- r del list
- assert_equal {3 deleted} [r hooks.is_key_removed list]
-
- r sadd set 1
- r spop set
- assert_equal {0 deleted} [r hooks.is_key_removed set]
-
- r sadd set 1 2 3 4
- r del set
- assert_equal {4 deleted} [r hooks.is_key_removed set]
-
- r zadd zset 1 f
- r zpopmin zset
- assert_equal {0 deleted} [r hooks.is_key_removed zset]
-
- r zadd zset 1 f 2 d
- r del zset
- assert_equal {2 deleted} [r hooks.is_key_removed zset]
-
- r xadd stream 1-1 f v
- r xdel stream 1-1
- # Stream does not delete object when del entry
- catch {[r hooks.is_key_removed stream]} output
- assert_match {ERR * removed} $output
- r del stream
- assert_equal {0 deleted} [r hooks.is_key_removed stream]
-
- r xadd stream 2-1 f v
- r del stream
- assert_equal {1 deleted} [r hooks.is_key_removed stream]
-
- # delete key because of active expire
- set size [r dbsize]
- r set active-expire abcd px 1
- #ensure active expire
- wait_for_condition 50 100 {
- [r dbsize] == $size
- } else {
- fail "Active expire not trigger"
- }
- assert_equal {abcd expired} [r hooks.is_key_removed active-expire]
- # current time is greater than pexpireat
- set now [r time]
- set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)]
- assert {$mill >= [r hooks.pexpireat active-expire]}
-
- # delete key because of lazy expire
- r debug set-active-expire 0
- r set lazy-expire abcd px 1
- after 10
- r get lazy-expire
- assert_equal {abcd expired} [r hooks.is_key_removed lazy-expire]
- set now [r time]
- set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)]
- assert {$mill >= [r hooks.pexpireat lazy-expire]}
- r debug set-active-expire 1
-
- # delete key not yet expired
- set now [r time]
- set expireat [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+1000000]
- r set not-expire abcd pxat $expireat
- r del not-expire
- assert_equal {abcd deleted} [r hooks.is_key_removed not-expire]
- assert_equal $expireat [r hooks.pexpireat not-expire]
-
- # Test key evict
- set used [expr {[s used_memory] - [s mem_not_counted_for_evict]}]
- set limit [expr {$used+100*1024}]
- set old_policy [lindex [r config get maxmemory-policy] 1]
- r config set maxmemory $limit
- # We set policy volatile-random, so only keys with ttl will be evicted
- r config set maxmemory-policy volatile-random
- r setex volatile-key 10000 x
- # We use SETBIT here, so we can set a big key and get the used_memory
- # bigger than maxmemory. Next command will evict volatile keys. We
- # can't use SET, as SET uses big input buffer, so it will fail.
- r setbit big-key 1600000 0 ;# this will consume 200kb
- r getbit big-key 0
- assert_equal {x evicted} [r hooks.is_key_removed volatile-key]
- r config set maxmemory-policy $old_policy
- r config set maxmemory 0
- } {OK} {needs:debug}
-
- test {Test flushdb hooks} {
- r flushdb
- assert_equal [r hooks.event_last flush-start] 9
- assert_equal [r hooks.event_last flush-end] 9
- r flushall
- assert_equal [r hooks.event_last flush-start] -1
- assert_equal [r hooks.event_last flush-end] -1
- }
-
- # replication related tests
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
- start_server {} {
- r module load $testmodule
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
- $replica replicaof $master_host $master_port
-
- wait_replica_online $master
-
- test {Test master link up hook} {
- assert_equal [r hooks.event_count masterlink-up] 1
- assert_equal [r hooks.event_count masterlink-down] 0
- }
-
- test {Test role-replica hook} {
- assert_equal [r hooks.event_count role-replica] 1
- assert_equal [r hooks.event_count role-master] 0
- assert_equal [r hooks.event_last role-replica] [s 0 master_host]
- }
-
- test {Test replica-online hook} {
- assert_equal [r -1 hooks.event_count replica-online] 1
- assert_equal [r -1 hooks.event_count replica-offline] 0
- }
-
- test {Test master link down hook} {
- r client kill type master
- assert_equal [r hooks.event_count masterlink-down] 1
-
- wait_for_condition 50 100 {
- [string match {*master_link_status:up*} [r info replication]]
- } else {
- fail "Replica didn't reconnect"
- }
-
- assert_equal [r hooks.event_count masterlink-down] 1
- assert_equal [r hooks.event_count masterlink-up] 2
- }
-
- wait_for_condition 50 10 {
- [string match {*master_link_status:up*} [r info replication]]
- } else {
- fail "Can't turn the instance into a replica"
- }
-
- $replica replicaof no one
-
- test {Test role-master hook} {
- assert_equal [r hooks.event_count role-replica] 1
- assert_equal [r hooks.event_count role-master] 1
- assert_equal [r hooks.event_last role-master] {}
- }
-
- test {Test replica-offline hook} {
- assert_equal [r -1 hooks.event_count replica-online] 2
- assert_equal [r -1 hooks.event_count replica-offline] 2
- }
- # get the replica stdout, to be used by the next test
- set replica_stdout [srv 0 stdout]
- }
-
- test {Test swapdb hooks} {
- r swapdb 0 10
- assert_equal [r hooks.event_last swapdb-first] 0
- assert_equal [r hooks.event_last swapdb-second] 10
- }
-
- test {Test configchange hooks} {
- r config set rdbcompression no
- assert_equal [r hooks.event_last config-change-count] 1
- assert_equal [r hooks.event_last config-change-first] rdbcompression
- }
-
- # look into the log file of the server that just exited
- test {Test shutdown hook} {
- assert_equal [string match {*module-event-shutdown*} [exec tail -5 < $replica_stdout]] 1
- }
- }
-
- start_server {} {
- test {OnLoad failure will handle un-registration} {
- catch {r module load $testmodule noload}
- r flushall
- r ping
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/infotest.tcl b/examples/redis-unstable/tests/unit/moduleapi/infotest.tcl
deleted file mode 100644
index 493d24d..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/infotest.tcl
+++ /dev/null
@@ -1,131 +0,0 @@
-set testmodule [file normalize tests/modules/infotest.so]
-
-# Return value for INFO property
-proc field {info property} {
- if {[regexp "\r\n$property:(.*?)\r\n" $info _ value]} {
- set _ $value
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule log-key 0
-
- test {module reading info} {
- # check string, integer and float fields
- assert_equal [r info.gets replication role] "master"
- assert_equal [r info.getc replication role] "master"
- assert_equal [r info.geti stats expired_keys] 0
- assert_equal [r info.getd stats expired_stale_perc] 0
-
- # check signed and unsigned
- assert_equal [r info.geti infotest infotest_global] -2
- assert_equal [r info.getu infotest infotest_uglobal] -2
-
- # the above are always 0, try module info that is non-zero
- assert_equal [r info.geti infotest_italian infotest_due] 2
- set tre [r info.getd infotest_italian infotest_tre]
- assert {$tre > 3.2 && $tre < 3.4 }
-
- # search using the wrong section
- catch { [r info.gets badname redis_version] } e
- assert_match {*not found*} $e
-
- # check that section filter works
- assert { [string match "*usec_per_call*" [r info.gets all cmdstat_info.gets] ] }
- catch { [r info.gets default cmdstat_info.gets] ] } e
- assert_match {*not found*} $e
- }
-
- test {module info all} {
- set info [r info all]
- # info all does not contain modules
- assert { ![string match "*Spanish*" $info] }
- assert { ![string match "*infotest_*" $info] }
- assert { [string match "*used_memory*" $info] }
- }
-
- test {module info all infotest} {
- set info [r info all infotest]
- # info all infotest should contain both ALL and the module information
- assert { [string match "*Spanish*" $info] }
- assert { [string match "*infotest_*" $info] }
- assert { [string match "*used_memory*" $info] }
- }
-
- test {module info everything} {
- set info [r info everything]
- # info everything contains all default sections, but not ones for crash report
- assert { [string match "*infotest_global*" $info] }
- assert { [string match "*Spanish*" $info] }
- assert { [string match "*Italian*" $info] }
- assert { [string match "*used_memory*" $info] }
- assert { ![string match "*Klingon*" $info] }
- field $info infotest_dos
- } {2}
-
- test {module info modules} {
- set info [r info modules]
- # info all does not contain modules
- assert { [string match "*Spanish*" $info] }
- assert { [string match "*infotest_global*" $info] }
- assert { ![string match "*used_memory*" $info] }
- }
-
- test {module info one module} {
- 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] }
- field $info infotest_global
- } {-2}
-
- test {module info one section} {
- 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] }
- field $info infotest_uno
- } {one}
-
- test {module info dict} {
- set info [r info infotest_keyspace]
- set keyspace [field $info infotest_db0]
- set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d]
- } {3}
-
- test {module info unsafe fields} {
- set info [r info infotest_unsafe]
- 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]
- }
-
- # TODO: test crash report.
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/infra.tcl b/examples/redis-unstable/tests/unit/moduleapi/infra.tcl
deleted file mode 100644
index 80cc962..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/infra.tcl
+++ /dev/null
@@ -1,25 +0,0 @@
-set testmodule [file normalize tests/modules/infotest.so]
-
-test {modules config rewrite} {
-
- start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- set modules [lmap x [r module list] {dict get $x name}]
- assert_not_equal [lsearch $modules infotest] -1
-
- r config rewrite
- restart_server 0 true false
-
- set modules [lmap x [r module list] {dict get $x name}]
- assert_not_equal [lsearch $modules infotest] -1
-
- assert_equal {OK} [r module unload infotest]
-
- r config rewrite
- restart_server 0 true false
-
- set modules [lmap x [r module list] {dict get $x name}]
- assert_equal [lsearch $modules infotest] -1
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl b/examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl
deleted file mode 100644
index 3d0f1b1..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/internalsecret.tcl
+++ /dev/null
@@ -1,287 +0,0 @@
-tags {modules external:skip cluster} {
-set testmodule [file normalize tests/modules/internalsecret.so]
-
-set modules [list loadmodule $testmodule]
-start_cluster 1 0 [list config_lines $modules] {
- set r [srv 0 client]
-
- test {Internal command without internal connection fails as an unknown command} {
- assert_error {*unknown command*} {r internalauth.internalcommand}
- }
-
- test {Wrong internalsecret fails authentication} {
- assert_error {*WRONGPASS invalid internal password*} {r auth "internal connection" 123}
- }
-
- test {Internal connection basic flow} {
- # A non-internal connection cannot execute internal commands, and they
- # seem non-existent to it.
- assert_error {*unknown command*} {r internalauth.internalcommand}
-
- # Authenticate as an internal connection
- assert_equal {OK} [r debug mark-internal-client]
-
- # Now, internal commands are available.
- assert_equal {OK} [r internalauth.internalcommand]
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {Internal secret is not available in non-cluster mode} {
- # On non-cluster mode, the internal secret does not exist, nor is the
- # internal auth command available
- assert_error {*unknown command*} {r internalauth.internalcommand}
- assert_error {*ERR no internal secret available*} {r internalauth.getinternalsecret}
- assert_error {*Cannot authenticate as an internal connection on non-cluster instances*} {r auth "internal connection" somepassword}
- }
-
- test {marking and un-marking a connection as internal via a debug command} {
- # After marking the connection to an internal one via a debug command,
- # internal commands succeed.
- r debug mark-internal-client
- assert_equal {OK} [r internalauth.internalcommand]
-
- # After unmarking the connection, internal commands fail.
- r debug mark-internal-client unmark
- assert_error {*unknown command*} {r internalauth.internalcommand}
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {Test `COMMAND *` commands with\without internal connections} {
- # ------------------ Non-internal connection ------------------
- # `COMMAND DOCS <cmd>` returns empty response.
- assert_equal {} [r command docs internalauth.internalcommand]
-
- # `COMMAND INFO <cmd>` should reply with null for the internal command
- assert_equal {{}} [r command info internalauth.internalcommand]
-
- # `COMMAND GETKEYS/GETKEYSANDFLAGS <cmd> <args>` returns an invalid command error
- assert_error {*Invalid command specified*} {r command getkeys internalauth.internalcommand}
- assert_error {*Invalid command specified*} {r command getkeysandflags internalauth.internalcommand}
-
- # -------------------- Internal connection --------------------
- # Non-empty response for non-internal connections.
- assert_equal {OK} [r debug mark-internal-client]
-
- # `COMMAND DOCS <cmd>` returns a correct response.
- assert_match {*internalauth.internalcommand*} [r command docs internalauth.internalcommand]
-
- # `COMMAND INFO <cmd>` should reply with a full response for the internal command
- assert_match {*internalauth.internalcommand*} [r command info internalauth.internalcommand]
-
- # `COMMAND GETKEYS/GETKEYSANDFLAGS <cmd> <args>` returns a key error (not related to the internal connection)
- assert_error {*ERR The command has no key arguments*} {r command getkeys internalauth.internalcommand}
- assert_error {*ERR The command has no key arguments*} {r command getkeysandflags internalauth.internalcommand}
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {No authentication needed for internal connections} {
- # Authenticate with a user that does not have permissions to any command
- r acl setuser David on >123 &* ~* -@all +auth +internalauth.getinternalsecret +debug +internalauth.internalcommand
- assert_equal {OK} [r auth David 123]
-
- assert_equal {OK} [r debug mark-internal-client]
- # Execute a command for which David does not have permission
- assert_equal {OK} [r internalauth.internalcommand]
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {RM_Call of internal commands without user-flag succeeds only for all connections} {
- # Fail before authenticating as an internal connection.
- assert_equal {OK} [r internalauth.noninternal_rmcall internalauth.internalcommand]
- }
-
- test {Internal commands via RM_Call succeeds for non-internal connections depending on the user flag} {
- # A non-internal connection that calls rm_call of an internal command
- assert_equal {OK} [r internalauth.noninternal_rmcall internalauth.internalcommand]
-
- # A non-internal connection that calls rm_call of an internal command
- # with a user flag should fail.
- assert_error {*unknown command*} {r internalauth.noninternal_rmcall_withuser internalauth.internalcommand}
- }
-
- test {Internal connections override the user flag} {
- # Authenticate as an internal connection
- assert_equal {OK} [r debug mark-internal-client]
-
- assert_equal {OK} [r internalauth.noninternal_rmcall internalauth.internalcommand]
- assert_equal {OK} [r internalauth.noninternal_rmcall_withuser internalauth.internalcommand]
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {RM_Call with the user-flag after setting thread-safe-context from an internal connection should fail} {
- # Authenticate as an internal connection
- assert_equal {OK} [r debug mark-internal-client]
-
- # New threadSafeContexts do not inherit the internal flag.
- assert_error {*unknown command*} {r internalauth.noninternal_rmcall_detachedcontext_withuser internalauth.internalcommand}
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- r config set appendonly yes
- r config set appendfsync always
- waitForBgrewriteaof r
-
- test {AOF executes internal commands successfully} {
- # Authenticate as an internal connection
- assert_equal {OK} [r debug mark-internal-client]
-
- # Call an internal writing command
- assert_equal {OK} [r internalauth.internal_rmcall_replicated set x 5]
-
- # Reload the server from the AOF
- r debug loadaof
-
- # Check if the internal command was executed successfully
- assert_equal {5} [r get x]
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {Internal commands are not allowed from scripts} {
- # Internal commands are not allowed from scripts
- assert_error {*not allowed from script*} {r eval {redis.call('internalauth.internalcommand')} 0}
-
- # Even after authenticating as an internal connection
- assert_equal {OK} [r debug mark-internal-client]
- assert_error {*not allowed from script*} {r eval {redis.call('internalauth.internalcommand')} 0}
- }
-}
-
-start_cluster 1 1 [list config_lines $modules] {
- set master [srv 0 client]
- set slave [srv -1 client]
-
- test {Setup master} {
- # Authenticate as an internal connection
- set reply [$master internalauth.getinternalsecret]
- assert_equal {OK} [$master auth "internal connection" $reply]
- }
-
- test {Slaves successfully execute internal commands from the replication link} {
- assert {[s -1 role] eq {slave}}
- wait_for_condition 1000 50 {
- [s -1 master_link_status] eq {up}
- } else {
- fail "Master link status is not up"
- }
-
- # Execute internal command in master, that will set `x` to `5`.
- assert_equal {OK} [$master internalauth.internal_rmcall_replicated set x 5]
-
- # Wait for replica to have the key
- $slave readonly
- wait_for_condition 1000 50 {
- [$slave exists x] eq "1"
- } else {
- fail "Test key was not replicated"
- }
-
- # See that the slave has the same value for `x`.
- assert_equal {5} [$slave get x]
- }
-}
-
-start_server {} {
- r module load $testmodule
-
- test {Internal commands are not reported in the monitor output for non-internal connections when unsuccessful} {
- set rd [redis_deferring_client]
- $rd monitor
- $rd read ; # Discard the OK
- assert_error {*unknown command*} {r internalauth.internalcommand}
-
- # Assert that the monitor output does not contain the internal command
- r ping
- assert_match {*ping*} [$rd read]
- $rd close
- }
-
- test {Internal commands are not reported in the monitor output for non-internal connections when successful} {
- # Authenticate as an internal connection
- assert_equal {OK} [r debug mark-internal-client]
-
- set rd [redis_deferring_client]
- $rd monitor
- $rd read ; # Discard the OK
- assert_equal {OK} [r internalauth.internalcommand]
-
- # Assert that the monitor output does not contain the internal command
- r ping
- assert_match {*ping*} [$rd read]
- $rd close
- }
-
- test {Internal commands are reported in the monitor output for internal connections} {
- set rd [redis_deferring_client]
- $rd debug mark-internal-client
- assert_equal {OK} [$rd read]
- $rd monitor
- $rd read ; # Discard the OK
- assert_equal {OK} [r internalauth.internalcommand]
-
- # Assert that the monitor output contains the internal command
- assert_match {*internalauth.internalcommand*} [$rd read]
- $rd close
- }
-
- test {Internal commands are reported in the slowlog} {
- # Set up slowlog to log all commands
- r config set slowlog-log-slower-than 0
-
- # Execute an internal command
- r slowlog reset
- r internalauth.internalcommand
-
- # The slow-log should contain the internal command
- set log [r slowlog get 1]
- assert_match {*internalauth.internalcommand*} $log
- }
-
- test {Internal commands are reported in the latency report} {
- # The latency report should contain the internal command
- set report [r latency histogram internalauth.internalcommand]
- assert_match {*internalauth.internalcommand*} $report
- }
-
- test {Internal commands are reported in the command stats report} {
- # The INFO report should contain the internal command for both the internal
- # and non-internal connections.
- set report [r info commandstats]
- assert_match {*internalauth.internalcommand*} $report
-
- set report [r info latencystats]
- assert_match {*internalauth.internalcommand*} $report
-
- # Un-mark the connection as internal
- r debug mark-internal-client unmark
- assert_error {*unknown command*} {r internalauth.internalcommand}
-
- # We still expect to see the internal command in the report
- set report [r info commandstats]
- assert_match {*internalauth.internalcommand*} $report
-
- set report [r info latencystats]
- assert_match {*internalauth.internalcommand*} $report
- }
-}
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl b/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl
deleted file mode 100644
index ebb7784..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/keymeta.tcl
+++ /dev/null
@@ -1,910 +0,0 @@
-# ============================================================================
-# Key Metadata (keymeta) Test Suite
-# ============================================================================
-#
-# Tests the Redis module key metadata framework: up to 7 independent metadata
-# classes (IDs 1-7) can be attached to keys. Class ID 0 is reserved for key
-# expiration.
-#
-# The following features are sensitive to Key Metadata and are tested here:
-#
-# - KEY EXPIRATION (class ID 0)
-# - Stored at ((uint64_t *)kv) - 1 (first metadata slot)
-# - Managed via db->expires dictionary
-# - Must be preserved/updated when kvobj is reallocated
-# - HASH FIELD EXPIRATION (HFE)
-# - NOT in kvobj metadata slots (Maybe in the future...)
-# - Managed via db->hexpires ebuckets (holds direct kvobj pointer)
-# - Must be removed before kvobj reallocation (hashTypeRemoveFromExpires)
-# and restored after (hashTypeAddToExpires)
-# - MODULE METADATA (class IDs 1-7)
-# - Defines metadata lifecycle via callbacks
-# - EMBEDDED STRINGS vs. REGULAR OBJECTS
-# - Short strings and numbers are embedded into kvobj
-# - The rest are kept as distinct objects
-# - LAZYFREE
-# ============================================================================
-
-set testmodule [file normalize tests/modules/test_keymeta.so]
-
-# Helper procedure to convert class ID to 4-char-id name
-proc cname {cid} {
- return "KMT$cid"
-}
-
-# Helper procedure to check if a class should keep metadata for a given operation
-proc shouldKeep {cid operation classesSpec} {
- upvar $classesSpec specs
- set spec $specs($cid)
- switch $operation {
- "copy" { return [string match "*KEEPONCOPY*" $spec] }
- "rename" { return [string match "*KEEPONRENAME*" $spec] }
- "move" { return [string match "*KEEPONMOVE*" $spec] }
- default { return 0 }
- }
-}
-
-# Helper procedure to setup a key with metadata
-proc setupKeyMeta {keyname numClasses expiryBefore expiryAfter} {
- # Set expiry if requested
- if {$expiryBefore} {
- r expire $keyname 10000
- assert_range [r ttl $keyname] 9990 10000
- }
-
- # Set metadata for all classes
- for {set i 1} {$i <= $numClasses} {incr i} {
- # Set twice to verify overwrite behavior
- r keymeta.set [cname $i] $keyname "blabla$i"
- assert_equal [r keymeta.get [cname $i] $keyname] "blabla$i"
- r keymeta.set [cname $i] $keyname "meta$i"
- }
-
- # Verify metadata was set correctly
- for {set i 1} {$i <= $numClasses} {incr i} {
- assert_equal [r keymeta.get [cname $i] $keyname] "meta$i"
- }
-
- if {$expiryAfter} {
- r expire $keyname 10000
- assert_range [r ttl $keyname] 9990 10000
- }
-
- if {$expiryBefore} {
- assert_range [r ttl $keyname] 9990 10000
- }
-}
-
-# Helper procedure to verify metadata after an operation
-proc verifyKeyMeta {keyname operation numClasses hasExpiry classesSpec} {
- upvar $classesSpec specs
-
- # Verify expiry
- if {$hasExpiry} {
- assert_range [r ttl $keyname] 9990 10000
- }
-
- # Verify metadata based on class spec
- for {set i 1} {$i <= $numClasses} {incr i} {
- set expected [expr {[shouldKeep $i $operation specs] ? "meta$i" : ""}]
- assert_equal [r keymeta.get [cname $i] $keyname] $expected
- }
-}
-
-proc flushallAndVerifyCleanup {} {
- r flushall
- # Verify all metadata is cleaned up properly
- assert_equal [r keymeta.active] 0
-}
-
-start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} {
- r module load $testmodule
-
- array set classesSpec {}
- set classesSpec(1) "KEEPONCOPY:KEEPONRENAME:KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set classesSpec(2) "KEEPONCOPY:KEEPONRENAME:UNLINKFREE:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set classesSpec(3) "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set classesSpec(4) "ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set classesSpec(5) "KEEPONRENAME:KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set classesSpec(6) "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set classesSpec(7) "KEEPONMOVE:UNLINKFREE:ALLOWIGNORE:RDBLOAD:RDBSAVE"
-
- array set classes {}
- for {set cid 1} {$cid <= 7} {incr cid} {
- set spec $classesSpec($cid)
- set classes($cid) [r keymeta.register [cname $cid] 1 $spec]
- puts "Registered class $cid with spec $spec"
- assert_equal $classes($cid) $cid
- }
-
- # Validates metadata behavior across COPY/RENAME/MOVE operations
- # with varying numbers of metadata classes (1-7), key expiration states,
- # key types (string/hash), hash field expiration, and metadata class flags
- # (KEEPONCOPY, KEEPONRENAME, KEEPONMOVE).
- for {set numClasses 1} {$numClasses < 8} {incr numClasses} {
- foreach expiryBefore {0 1} {
- foreach expiryAfter {0 1} {
- set hasExpiry [expr {$expiryBefore || $expiryAfter}]
- set expiryStr "expiryBefore=$expiryBefore, expiryAfter=$expiryAfter)"
- # Test COPY operation
- test "KEYMETA - copy key-string with $numClasses classes, $expiryStr" {
- foreach value { 3 "value1" [string repeat "ABCD" 1000]} {
- r select 0
- r del k1 k2
- r set k1 $value
- setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter
- # Copy:
- r copy k1 k2
- # Verify:
- assert_equal [r get k1] $value
- assert_equal [r get k2] $value
- # Verify expiry and metadata
- verifyKeyMeta k2 "copy" $numClasses $hasExpiry classesSpec
- flushallAndVerifyCleanup
- }
- }
-
- test "KEYMETA - copy key-hash with $numClasses classes, $expiryStr" {
- r select 0
- r del h1 h2
- r HSET h1 field1 "value1" field2 "value2"
- r hexpire h1 10000 FIELDS 1 field1
- setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter
- # Copy:
- r copy h1 h2
- # Verify:
- verifyKeyMeta h2 "copy" $numClasses $hasExpiry classesSpec
- assert_range [r httl h1 FIELDS 1 field1] 9999 10000
- assert_range [r httl h2 FIELDS 1 field1] 9999 10000
- flushallAndVerifyCleanup
- }
-
- # Test RENAME operation
- test "KEYMETA - rename key-string with $numClasses classes, $expiryStr" {
- foreach value { 3 "value1" [string repeat "ABCD" 1000]} {
- r select 0
- r del k1 k2
- r set k1 $value
- setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter
- # Rename:
- r rename k1 k2
- # Verify:
- assert_equal [r exists k1] 0
- assert_equal [r get k2] $value
- # Verify expiry and metadata
- verifyKeyMeta k2 "rename" $numClasses $hasExpiry classesSpec
- flushallAndVerifyCleanup
- }
- }
-
- test "KEYMETA - rename key-hash with $numClasses classes, $expiryStr" {
- r select 0
- r del h1 h2
- r HSET h1 field1 "value1" field2 "value2"
- r hexpire h1 10000 FIELDS 1 field1
- setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter
- # Rename:
- r rename h1 h2
- # Verify:
- assert_equal [r exists h1] 0
- assert_range [r httl h2 FIELDS 1 field1] 9999 10000
- verifyKeyMeta h2 "rename" $numClasses $hasExpiry classesSpec
- flushallAndVerifyCleanup
- }
-
-
-
- # Test MOVE operation
- test "KEYMETA - move key-string with $numClasses classes, $expiryStr" {
- foreach value { 3 "value1" [string repeat "ABCD" 1000]} {
- r select 9
- r del k1
- r select 0
- r del k1
- r set k1 $value
- setupKeyMeta k1 $numClasses $expiryBefore $expiryAfter
- # Perform move
- assert_equal [r move k1 9] 1
- # Verify key moved
- assert_equal [r exists k1] 0
- r select 9
- assert_equal [r get k1] $value
- # Verify expiry and metadata
- verifyKeyMeta k1 "move" $numClasses $hasExpiry classesSpec
- r select 0
- flushallAndVerifyCleanup
- }
- }
-
- test "KEYMETA - move key-hash with $numClasses classes, $expiryStr" {
- r select 9
- r del h1
- r select 0
- r del h1
- r HSET h1 field1 "value1" field2 "value2"
- r hexpire h1 10000 FIELDS 1 field1
- setupKeyMeta h1 $numClasses $expiryBefore $expiryAfter
- assert_range [r httl h1 FIELDS 1 field1] 9999 10000
- assert_equal [r move h1 9] 1
- assert_equal [r exists h1] 0
- r select 9
- assert_range [r httl h1 FIELDS 1 field1] 9999 10000
- verifyKeyMeta h1 "move" $numClasses $hasExpiry classesSpec
- r select 0
- flushallAndVerifyCleanup
- }
- }
- }
- }
-
- test "KEYMETA - Verify active metadata count on copy" {
- for {set cid 1} {$cid < 7} {incr cid} {
- set numAlloc 0
- flushallAndVerifyCleanup
- set dupOnCopy [shouldKeep $cid "copy" classesSpec]
- r set k1 "v1"
- r keymeta.set [cname $cid] k1 "meta1"
- assert_equal [r keymeta.active] [incr numAlloc]
- r keymeta.set [cname $cid] k1 "meta1b"
- assert_equal [r keymeta.active] $numAlloc
- r copy k1 k1copy
- assert_equal [r keymeta.active] [incr numAlloc $dupOnCopy]
- r del k1
- assert_equal [r keymeta.active] [incr numAlloc -1]
- r del k1copy
- assert_equal [r keymeta.active] 0
- }
- }
-
- test "KEYMETA - Verify active metadata count on rename" {
- for {set cid 1} {$cid <= 7} {incr cid} {
- set numAlloc 0
- flushallAndVerifyCleanup
- set keepOnRename [shouldKeep $cid "rename" classesSpec]
- set discOnRename [expr {!$keepOnRename}]
- r set k1 "v1"
- r keymeta.set [cname $cid] k1 "meta1"
- assert_equal [r keymeta.active] [incr numAlloc]
- r rename k1 k1_renamed
- assert_equal [r keymeta.active] [incr numAlloc -$discOnRename]
- r del k1_renamed
- assert_equal [r keymeta.active] 0
- }
- }
-
- test "KEYMETA - Verify active metadata count on move" {
- for {set cid 1} {$cid <= 7} {incr cid} {
- set numAlloc 0
- r select 0
- flushallAndVerifyCleanup
-
- set keepOnMove [shouldKeep $cid "move" classesSpec]
- set discOnMove [expr {!$keepOnMove}]
-
- # Create keys with metadata in DB 0
- r set k1 "v1"
- r keymeta.set [cname $cid] k1 "meta1"
- assert_equal [r keymeta.active] [incr numAlloc]
- # Move: metadata discarded if !keepOnMove
- r move k1 9
- set active [r keymeta.active]
- assert_equal [r keymeta.active] [incr numAlloc -$discOnMove]
- # Cleanup
- r select 9
- r del k1
- r select 0
- assert_equal [r keymeta.active] 0
- }
- }
-
- test "KEYMETA - Verify metadta cleanup on lazyfree" {
- r config set lazyfree-lazy-user-del yes
- # Class 2 has UNLINKFREE flag, so it should call unlink callback when lazyfree is enabled
- # Class 1 does not have UNLINKFREE flag, so it should only call free callback
- foreach {cid} { 1 2 } {
- r config resetstat
- # Create a large unsorted set collection to ensure it exceeds LAZYFREE_THRESHOLD
- for {set i 0} {$i < 1024} {incr i} { r sadd myset $i }
- r keymeta.set [cname $cid] myset "meta"
- assert_equal [r keymeta.active] 1
- r del myset
-
- # Wait for lazyfree to complete and verify lazyfreed_objects incremented
- wait_for_condition 50 100 {
- [s lazyfree_pending_objects] == 0
- } else {
- fail "lazyfree isn't done"
- }
- assert_equal [r keymeta.active] 0
- assert_equal [s lazyfreed_objects] 1
- }
- r config set lazyfree-lazy-user-del no
- } {OK} {needs:config-resetstat}
-
- test "KEYMETA - Verify metadata cleanup on expire" {
- # Class 2 has UNLINKFREE flag, so it should call unlink callback when lazyfree is enabled
- # Class 1 does not have UNLINKFREE flag, so it should only call free callback
- foreach {cid} { 1 2 } {
- r set mykey "mykey$cid"
- r keymeta.set [cname $cid] mykey "meta"
- assert_equal [r keymeta.active] 1
- r pexpire mykey 1
- wait_for_condition 50 100 {
- [r exists mykey] == 0
- } else {
- fail "key not expired"
- }
- assert_equal [r keymeta.active] 0
- }
- }
-
- # ============================================================================
- # AOF Rewrite Tests
- # ============================================================================
- # Note: Full AOF round-trip tests (write → restart → load) are not included
- # because the test module registers classes dynamically via commands, which
- # creates a chicken-and-egg problem:
- # - Classes must be registered BEFORE AOF loading (in RedisModule_OnLoad)
- # - But the KEYMETA.REGISTER commands are in the AOF itself
- # - When server restarts and loads AOF, classes aren't registered yet
- # - KEYMETA.SET commands fail with "metadata class not found"
- #
- # For production modules, classes MUST be registered in RedisModule_OnLoad()
- # to ensure they're available when AOF/RDB files are loaded on server startup.
- # See src/module.c documentation for RM_CreateKeyMetaClass() for details.
- #
- # The test below verifies that AOF callbacks correctly emit KEYMETA.SET commands
- # to the AOF file during rewrite, which is the module's responsibility.
- test "KEYMETA - AOF rewrite emits correct KEYMETA.SET commands to file" {
- # This test verifies that the AOF callback implementation correctly writes
- # KEYMETA.SET commands to the AOF file during rewrite. We don't test the
- # full round-trip (restart + load) due to the dynamic registration limitation
- # explained above.
-
- r config set appendonly yes
- r config set auto-aof-rewrite-percentage 0
- r config set aof-use-rdb-preamble no
- # Wait for the initial AOF rewrite that Redis triggers when enabling AOF
- waitForBgrewriteaof r
-
- # Create keys with metadata from multiple classes
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "metadata_c1"
-
- r set key2 "value2"
- r keymeta.set [cname 2] key2 "metadata_c2"
- r keymeta.set [cname 3] key2 "metadata_c3"
-
- r hset hashkey field1 val1
- r keymeta.set [cname 4] hashkey "hash_meta"
-
- # Trigger AOF rewrite
- r bgrewriteaof
- waitForBgrewriteaof r
-
- # Get the AOF directory and read the AOF file
- set aof_dir [lindex [r config get dir] 1]
- set aof_base_filename [lindex [r config get appendfilename] 1]
-
- # Find the base AOF file (after rewrite)
- set aof_files [glob -nocomplain -directory $aof_dir appendonlydir/${aof_base_filename}.*.base.aof]
- assert {[llength $aof_files] > 0}
-
- # Read the most recent base AOF file
- set aof_file [lindex [lsort $aof_files] end]
- set fp [open $aof_file r]
- set aof_content [read $fp]
- close $fp
-
- # Verify the AOF contains KEYMETA.SET commands with correct format
- assert_match "*KEYMETA.SET*[cname 1]*key1*metadata_c1*" $aof_content
- assert_match "*KEYMETA.SET*[cname 2]*key2*metadata_c2*" $aof_content
- assert_match "*KEYMETA.SET*[cname 3]*key2*metadata_c3*" $aof_content
- assert_match "*KEYMETA.SET*[cname 4]*hashkey*hash_meta*" $aof_content
-
- # Verify the RESP format is correct by checking for the command structure
- # The AOF should contain: *4 (array of 4 elements)
- assert_match "*\$11*KEYMETA.SET*" $aof_content
- # Count how many KEYMETA.SET commands are in the AOF
- set keymeta_count [regexp -all {KEYMETA\.SET} $aof_content]
- assert_equal $keymeta_count 4
- } {} {external:skip}
-
- # ========================================================================
- # RDB Save/Load Tests
- # ========================================================================
-
- test {RDB: SAVE and reload preserves metadata} {
- # Create key with metadata
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "key1_meta1"
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
-
- r save
- r debug reload
-
- # Verify metadata persisted after reload
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
-
- flushallAndVerifyCleanup
- } {} {external:skip needs:save}
-
- test {RDB: BGSAVE writes metadata to RDB file} {
- # Create keys with different metadata combinations
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "key1_meta1"
-
- r set key2 "value2"
- r keymeta.set [cname 1] key2 "key2_meta1"
- r keymeta.set [cname 2] key2 "key2_meta2"
-
- # Trigger BGSAVE and reload (debug reload preserves modules)
- r bgsave
- waitForBgsave r
- r debug reload
-
- # Verify metadata persisted after reload
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
- assert_equal [r keymeta.get [cname 1] key2] "key2_meta1"
- assert_equal [r keymeta.get [cname 2] key2] "key2_meta2"
-
- flushallAndVerifyCleanup
- } {} {external:skip needs:save}
-
- test {RDB: Metadata persists with expiretime} {
- # Create key with both expiry and metadata
- r set key1 "value1"
- set expire_time [expr {[clock seconds] + 10000}]
- r expireat key1 $expire_time
- r keymeta.set [cname 1] key1 "meta_with_expire"
-
- assert_equal [r expiretime key1] $expire_time
- assert_equal [r keymeta.get [cname 1] key1] "meta_with_expire"
-
- # Reload from RDB
- r debug reload
-
- # Verify metadata and expiry persist after reload
- assert_equal [r expiretime key1] $expire_time
- assert_equal [r keymeta.get [cname 1] key1] "meta_with_expire"
-
- flushallAndVerifyCleanup
- } {} {external:skip needs:debug}
-
- test {RDB: Create keys with upto 7 meta classes, with or without expiry} {
- # Test all combinations of 1-7 metadata classes, with or without expiry
- for {set n 1} {$n <= 7} {incr n} {
- foreach hasExpiry {0 1} {
- set keyname "key_${n}_exp${hasExpiry}"
- r set $keyname "value$n"
-
- # Set expiry if hasExpiry is 1
- if {$hasExpiry} {
- set ttl [expr {3600 + $n}]
- r expire $keyname $ttl
- # Get the actual expiretime set by Redis to use as expected value
- set expExpiry [r expiretime $keyname]
- }
-
- # Create list of class IDs to attach (1 through n)
- set class_ids {}
- for {set i 1} {$i <= $n} {incr i} {
- lappend class_ids $i
- }
-
- # Randomize the order of metadata attachment
- set class_ids [lshuffle $class_ids]
-
- # Attach metadata in randomized order
- foreach cid $class_ids {
- r keymeta.set [cname $cid] $keyname "meta$cid"
- }
-
- # Verify metadata before RDB save
- # Verify exactly n metadata classes are attached
- for {set i 1} {$i <= 7} {incr i} {
- if {$i <= $n} {
- assert_equal [r keymeta.get [cname $i] $keyname] "meta$i"
- } else {
- assert_equal [r keymeta.get [cname $i] $keyname] ""
- }
- }
-
- # Verify expiry before RDB save
- if {$hasExpiry} {
- set actual_expiretime [r expiretime $keyname]
- assert_equal $actual_expiretime $expExpiry
- }
-
- # Save and reload from RDB (debug reload preserves modules)
- r save
- r debug reload
-
- # Verify metadata after RDB reload
- # Verify exactly n metadata classes are still attached
- for {set i 1} {$i <= 7} {incr i} {
- if {$i <= $n} {
- assert_equal [r keymeta.get [cname $i] $keyname] "meta$i"
- } else {
- assert_equal [r keymeta.get [cname $i] $keyname] ""
- }
- }
-
- # Verify expiry after RDB reload
- if {$hasExpiry} {
- set actual_expiretime [r expiretime $keyname]
- assert_equal $actual_expiretime $expExpiry
- } else {
- # Verify no expiry set
- assert_equal [r expiretime $keyname] -1
- }
- flushallAndVerifyCleanup
- }
- }
- } {} {external:skip needs:save}
-
- # ========================================================================
- # RDB Flag Tests: ALLOW_IGNORE, RDBLOAD, RDBSAVE
- # ========================================================================
-
- # Test all combinations except the error case (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)
- foreach RDBLOAD {0 1} {
- foreach RDBSAVE {0 1} {
- foreach ALLOW_IGNORE {0 1} {
- # Skip the error case - we'll test it last since it causes RDB load to fail
- if {!$RDBLOAD && $RDBSAVE && !$ALLOW_IGNORE} { continue }
-
- test "RDB: SAVE and LOAD (ALLOW_IGNORE=$ALLOW_IGNORE, RDBLOAD=$RDBLOAD, RDBSAVE=$RDBSAVE)" {
- # Flush all data and save empty RDB to start with a clean slate
- r flushall
- r save
-
- # re-register class 1 with new flags. Expected re-registered same class ID
- r keymeta.unregister [cname 1]
- # dummy default spec
- set newSpec "KEEPONCOPY"
- if {$ALLOW_IGNORE} { append newSpec ":ALLOWIGNORE" }
- if {$RDBLOAD} { append newSpec ":RDBLOAD" }
- if {$RDBSAVE} { append newSpec ":RDBSAVE" }
-
- # Must reuse same class-id that it had before
- assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec]
-
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "key1_meta1"
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
-
- r save
- r debug reload
-
- # Metadata is preserved only when BOTH rdb_save AND rdb_load are enabled
- # Otherwise metadata is lost (either not saved, or saved but not loaded)
- set metaPreserved [expr {$RDBSAVE && $RDBLOAD}]
- set expectedMeta [expr {$metaPreserved ? "key1_meta1" : ""}]
-
- assert_equal [r keymeta.get [cname 1] key1] $expectedMeta
-
- flushallAndVerifyCleanup
- } {} {external:skip needs:save}
- }
- }
- }
-
- # Test the error case last (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)
- # This test causes RDB load to fail, so we test it last to avoid polluting subsequent tests
- test "RDB: SAVE and LOAD Invalid combination: (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)" {
- # re-register class 1 with RDBSAVE flag but no RDBLOAD or ALLOW_IGNORE
- r keymeta.unregister [cname 1]
- set newSpec "KEEPONCOPY:RDBSAVE"
- assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec]
-
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "key1_meta1"
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
-
- r save
-
- # This combination causes RDB load to fail because:
- # - Metadata was saved (RDBSAVE=1)
- # - Class has no rdb_load callback (RDBLOAD=0)
- # - Errors are not ignored (ALLOW_IGNORE=0)
- catch {r debug reload} err
- assert_match "*Error trying to load the RDB dump*" $err
- } {} {external:skip needs:save}
-
- # ========================================================================
- # DUMP/RESTORE Tests
- # ========================================================================
-
- test {DUMP/RESTORE: 1 to 7 metadata classes, optional TTL} {
- foreach withTTL {0 1} {
- for {set numClasses 1} {$numClasses < 8} {incr numClasses} {
- # Re-register classes with RDBLOAD and RDBSAVE flags
- for {set cid 1} {$cid <= $numClasses} {incr cid} {
- r keymeta.unregister [cname $cid]
- assert_equal $classes($cid) [r keymeta.register [cname $cid] 1 $classesSpec($cid)]
- }
-
- # Create key with metadata classes
- r set key1 "value1"
- for {set i 1} {$i <= $numClasses} {incr i} {
- r keymeta.set [cname $i] key1 "meta${i}_value"
- }
-
- if {$withTTL} { r expire key1 10000 }
-
- # Verify all metadata before DUMP
- for {set i 1} {$i <= $numClasses} {incr i} {
- assert_equal [r keymeta.get [cname $i] key1] "meta${i}_value"
- }
-
- # DUMP the key
- set encoded [r dump key1]
-
- # Delete and RESTORE
- r del key1
- r restore key1 [expr {$withTTL ? 10000 : 0}] $encoded
-
- # Verify all metadata was restored
- assert_equal [r get key1] "value1"
- for {set i 1} {$i <= $numClasses} {incr i} {
- assert_equal [r keymeta.get [cname $i] key1] "meta${i}_value"
- }
- if {$withTTL} { assert_range [r pttl key1] 9000 10000 }
-
- flushallAndVerifyCleanup
- }
- }
- }
-
- test {DUMP/RESTORE: REPLACE with metadata} {
- # Create key with metadata
- r set key1 value1
- r keymeta.set [cname 1] key1 "meta1_original"
-
- # DUMP the key
- set encoded1 [r dump key1]
-
- # Create different key with different metadata
- r set key1 value2
- r keymeta.set [cname 1] key1 "meta1_new"
-
- # DUMP the second version
- set encoded2 [r dump key1]
-
- # Delete and restore first version
- r del key1
- r restore key1 0 $encoded1
- assert_equal [r get key1] "value1"
- assert_equal [r keymeta.get [cname 1] key1] "meta1_original"
-
- # RESTORE second version with REPLACE
- r restore key1 0 $encoded2 replace
- assert_equal [r get key1] "value2"
- assert_equal [r keymeta.get [cname 1] key1] "meta1_new"
-
- flushallAndVerifyCleanup
- }
-
-
- # Test all combinations except the error case (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)
- foreach RDBLOAD {0 1} {
- foreach RDBSAVE {0 1} {
- foreach ALLOW_IGNORE {0 1} {
- # Skip the error case - we'll test it last since it causes RESTORE to fail
- if {!$RDBLOAD && $RDBSAVE && !$ALLOW_IGNORE} { continue }
-
- test "DUMP/RESTORE: (ALLOW_IGNORE=$ALLOW_IGNORE, RDBLOAD=$RDBLOAD, RDBSAVE=$RDBSAVE)" {
- # re-register class 1 with new flags. Expected re-registered same class ID
- r keymeta.unregister [cname 1]
- # dummy default spec
- set newSpec "KEEPONCOPY"
- if {$ALLOW_IGNORE} { append newSpec ":ALLOWIGNORE" }
- if {$RDBLOAD} { append newSpec ":RDBLOAD" }
- if {$RDBSAVE} { append newSpec ":RDBSAVE" }
-
- # Must reuse same class-id that it had before
- assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec]
-
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "key1_meta1"
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
-
- # DUMP & RESTORE
- set encoded [r dump key1]
- r del key1
- r restore key1 0 $encoded
-
- # Metadata is preserved only when BOTH rdb_save AND rdb_load are enabled
- # Otherwise metadata is lost (either not saved, or saved but not loaded)
- set metaPreserved [expr {$RDBSAVE && $RDBLOAD}]
- set expectedMeta [expr {$metaPreserved ? "key1_meta1" : ""}]
-
- assert_equal [r keymeta.get [cname 1] key1] $expectedMeta
-
- flushallAndVerifyCleanup
- }
- }
- }
- }
-
- # Test the error case last (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)
- # This test causes RESTORE to fail, so we test it last to avoid polluting subsequent tests
- test "DUMP/RESTORE: Invalid combination: (ALLOW_IGNORE=0, RDBLOAD=0, RDBSAVE=1)" {
- # re-register class 1 with RDBSAVE flag but no RDBLOAD or ALLOW_IGNORE
- r keymeta.unregister [cname 1]
- set newSpec "KEEPONCOPY:RDBSAVE"
- assert_equal $classes(1) [r keymeta.register [cname 1] 1 $newSpec]
-
- r set key1 "value1"
- r keymeta.set [cname 1] key1 "key1_meta1"
- assert_equal [r keymeta.get [cname 1] key1] "key1_meta1"
-
- # DUMP the key
- set encoded [r dump key1]
-
- # Delete and try to RESTORE
- r del key1
-
- # This combination causes RESTORE to fail because:
- # - Metadata was saved (RDBSAVE=1)
- # - Class has no rdb_load callback (RDBLOAD=0)
- # - Errors are not ignored (ALLOW_IGNORE=0)
- catch {r restore key1 0 $encoded} err
- assert_match "*Bad data format*" $err
-
- flushallAndVerifyCleanup
- }
-}
-
-test "RDB: Load with different module registration order preserves metadata correctly" {
- # This test verifies out-of-order metadata attachment during RDB load.
- # When modules register in different order at load time vs save time,
- # metadata values should still be correctly associated with their classes.
- start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} {
- r module load $testmodule
-
- # Helper function to generate class names (needed in inner scope)
- proc cname {id} { return "CLS$id" }
-
- # Register classes in order: 1, 2, 3
- set spec1 "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set spec2 "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set spec3 "KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE"
-
- set class1 [r keymeta.register [cname 1] 1 $spec1]
- set class2 [r keymeta.register [cname 2] 1 $spec2]
- set class3 [r keymeta.register [cname 3] 1 $spec3]
-
- # Verify class IDs match registration order
- assert_equal $class1 1 "Class 1 registered first, gets ID 1"
- assert_equal $class2 2 "Class 2 registered second, gets ID 2"
- assert_equal $class3 3 "Class 3 registered third, gets ID 3"
-
- # OUTER SERVER: Create RDB with classes registered in order 1,2,3
- r flushall
- r set mykey "myvalue"
- r keymeta.set [cname 1] mykey "metadata_for_class1"
- r keymeta.set [cname 2] mykey "metadata_for_class2"
- r keymeta.set [cname 3] mykey "metadata_for_class3"
-
- # Verify metadata before save
- assert_equal [r keymeta.get [cname 1] mykey] "metadata_for_class1"
- assert_equal [r keymeta.get [cname 2] mykey] "metadata_for_class2"
- assert_equal [r keymeta.get [cname 3] mykey] "metadata_for_class3"
-
- r save
-
- # Get RDB file path & Copy RDB to a temp location with unique name
- set rdb_dir [lindex [r config get dir] 1]
- set rdb_file [lindex [r config get dbfilename] 1]
- set rdb_path [file join $rdb_dir $rdb_file]
- set temp_rdb [file join $rdb_dir "temp_metadata_outoforder_[pid].rdb"]
- file copy -force $rdb_path $temp_rdb
-
- # INNER SERVER: Start new server, register classes in DIFFERENT order, then load RDB
- start_server [list overrides [list dir $rdb_dir enable-debug-command yes]] {
- r module load $testmodule
-
- # Helper function to generate class names (needed in inner scope)
- proc cname {id} { return "CLS$id" }
-
- # Register classes in DIFFERENT order: 3, 1, 2
- # This simulates a server where modules load in different order
- set spec1 "KEEPONCOPY:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set spec2 "KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE"
- set spec3 "KEEPONMOVE:ALLOWIGNORE:RDBLOAD:RDBSAVE"
-
- set class3 [r keymeta.register [cname 3] 1 $spec3]
- set class1 [r keymeta.register [cname 1] 1 $spec1]
- set class2 [r keymeta.register [cname 2] 1 $spec2]
-
- # Verify class IDs are assigned by REGISTRATION ORDER, not name
- # We registered in order 3,1,2, so the runtime IDs are:
- # - class3 (name "CLS3") gets ID 1 (first registered)
- # - class1 (name "CLS1") gets ID 2 (second registered)
- # - class2 (name "CLS2") gets ID 3 (third registered)
- # This is DIFFERENT from outer server which registered in order 1,2,3
- assert_equal $class3 1 "Class 3 registered first, gets ID 1"
- assert_equal $class1 2 "Class 1 registered second, gets ID 2"
- assert_equal $class2 3 "Class 2 registered third, gets ID 3"
-
- # Copy the saved RDB to this server's dbfilename
- set inner_rdb_file [lindex [r config get dbfilename] 1]
- set inner_rdb_path [file join $rdb_dir $inner_rdb_file]
- file copy -force $temp_rdb $inner_rdb_path
-
- # NOW load the RDB (AFTER registration in different order)
- # Use 'nosave' to reload from the copied RDB without saving current state first
- r debug reload nosave
-
- # Verify the key exists
- assert_equal [r exists mykey] 1 "Key should exist after RDB load"
- assert_equal [r get mykey] "myvalue" "Key value should be preserved"
-
- # Verify metadata values are correctly associated with their classes
- # WITHOUT metadata would be swapped because:
- # - At SAVE time (outer): classes registered in order 1,2,3
- # - At LOAD time (inner): classes registered in order 3,1,2
- # - RDB contains metadata in saved order, but keyMetaClassLookupByName
- # maps them back to correct classes by NAME, not by registration order
- assert_equal [r keymeta.get [cname 1] mykey] "metadata_for_class1"
- assert_equal [r keymeta.get [cname 2] mykey] "metadata_for_class2"
- assert_equal [r keymeta.get [cname 3] mykey] "metadata_for_class3"
- }
-
- # Cleanup temp file
- file delete $temp_rdb
-
- }
-} {} {external:skip needs:save}
-
-test "RDB: File size same with/without metadata when no rdb_save callback" {
- # This test verifies that when a metadata class has no rdb_save callback,
- # the metadata is not serialized to RDB, so the RDB file size should be
- # approximately the same (within a small tolerance for header differences).
-
- start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command yes}} {
- r module load $testmodule
-
- # Get RDB directory
- set rdb_dir [lindex [r config get dir] 1]
- set rdb_file [lindex [r config get dbfilename] 1]
- set rdb_path [file join $rdb_dir $rdb_file]
-
- # Test 1: Create key WITHOUT metadata and save
- r flushall
- r set key1 "test_value_12345"
- r save
- set size_without_meta [file size $rdb_path]
-
- # Test 2: Create identical key WITH metadata (but no rdb_save) and save
- # Register a class WITHOUT rdb_save callback (RDBSAVE=0)
- # Use ALLOWIGNORE so loading doesn't fail when metadata is missing
- set spec "ALLOWIGNORE"
- r keymeta.register [cname 1] 1 $spec
-
- r flushall
- r set key1 "test_value_12345"
- r keymeta.set [cname 1] key1 "some_metadata_value"
-
- # Verify metadata is attached
- assert_equal [r keymeta.get [cname 1] key1] "some_metadata_value"
-
- r save
- set size_with_meta [file size $rdb_path]
-
- # The file sizes should be the same (metadata not serialized)
- assert_equal $size_without_meta $size_with_meta
- }
-} {} {external:skip needs:save}
-
-test "Creating key metadata not during OnLoad should fail" {
- # This time start_server without "enable-debug-command yes"
- start_server {tags {"modules" "external:skip" "cluster:skip"} overrides {enable-debug-command no}} {
- r module load $testmodule
- # Creating a class not during OnLoad should fail
- catch {r keymeta.register [cname 1] 1 "ALLOWIGNORE"} err
- assert_match {*failed to create metadata class*} $err
- }
-} {} {external:skip needs:save}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl b/examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl
deleted file mode 100644
index 5d62a71..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/keyspace_events.tcl
+++ /dev/null
@@ -1,140 +0,0 @@
-set testmodule [file normalize tests/modules/keyspace_events.so]
-
-tags "modules external:skip" {
- start_server [list overrides [list loadmodule "$testmodule"]] {
-
- # avoid using shared integers, to increase the chance of detection heap issues
- r config set maxmemory-policy allkeys-lru
- r config set maxmemory 1gb
-
- test {Test loaded key space event} {
- r set x 1
- r hset y f v
- r lpush z 1 2 3
- r sadd p 1 2 3
- r zadd t 1 f1 2 f2
- r xadd s * f v
- r debug reload
- assert_equal {1 x} [r keyspace.is_key_loaded x]
- assert_equal {1 y} [r keyspace.is_key_loaded y]
- assert_equal {1 z} [r keyspace.is_key_loaded z]
- assert_equal {1 p} [r keyspace.is_key_loaded p]
- assert_equal {1 t} [r keyspace.is_key_loaded t]
- assert_equal {1 s} [r keyspace.is_key_loaded s]
- }
-
- test {Nested multi due to RM_Call} {
- r del multi
- r del lua
-
- r set x 1
- r set x_copy 1
- r keyspace.del_key_copy x
- r keyspace.incr_case1 x
- r keyspace.incr_case2 x
- r keyspace.incr_case3 x
- assert_equal {} [r get multi]
- assert_equal {} [r get lua]
- r get x
- } {3}
-
- test {Nested multi due to RM_Call, with client MULTI} {
- r del multi
- r del lua
-
- r set x 1
- r set x_copy 1
- r multi
- r keyspace.del_key_copy x
- r keyspace.incr_case1 x
- r keyspace.incr_case2 x
- r keyspace.incr_case3 x
- r exec
- assert_equal {1} [r get multi]
- assert_equal {} [r get lua]
- r get x
- } {3}
-
- test {Nested multi due to RM_Call, with EVAL} {
- r del multi
- r del lua
-
- r set x 1
- r set x_copy 1
- r eval {
- redis.pcall('keyspace.del_key_copy', KEYS[1])
- redis.pcall('keyspace.incr_case1', KEYS[1])
- redis.pcall('keyspace.incr_case2', KEYS[1])
- redis.pcall('keyspace.incr_case3', KEYS[1])
- } 1 x
- assert_equal {} [r get multi]
- assert_equal {1} [r get lua]
- r get x
- } {3}
-
- test {Test module key space event} {
- r keyspace.notify x
- assert_equal {1 x} [r keyspace.is_module_key_notified x]
- }
-
- test "Keyspace notifications: module events test" {
- r config set notify-keyspace-events Kd
- r del x
- set rd1 [redis_deferring_client]
- assert_equal {1} [psubscribe $rd1 *]
- r keyspace.notify x
- assert_equal {pmessage * __keyspace@9__:x notify} [$rd1 read]
- $rd1 close
- }
-
- test "Keyspace notifications: unsubscribe removes handler" {
- r config set notify-keyspace-events KEA
- set before [r keyspace.callback_count]
- r set a 1
- r del a
- wait_for_condition 100 10 {
- [r keyspace.callback_count] > $before
- } else {
- fail "callback did not trigger"
- }
- set before_unsub [r keyspace.callback_count]
- r keyspace.unsubscribe 4 ;# REDISMODULE_NOTIFY_GENERIC
- r set a 1
- r del a
- set after_unsub [r keyspace.callback_count]
- assert_equal $before_unsub $after_unsub
- }
-
- test {Test expired key space event} {
- set prev_expired [s expired_keys]
- r set exp 1 PX 10
- wait_for_condition 100 10 {
- [s expired_keys] eq $prev_expired + 1
- } else {
- fail "key not expired"
- }
- assert_equal [r get testkeyspace:expired] 1
- }
-
- test "Unload the module - testkeyspace" {
- assert_equal {OK} [r module unload testkeyspace]
- }
-
- test "Verify RM_StringDMA with expiration are not causing invalid memory access" {
- assert_equal {OK} [r set x 1 EX 1]
- }
- }
-
- start_server {} {
- test {OnLoad failure will handle un-registration} {
- catch {r module load $testmodule noload}
- r set x 1
- r hset y f v
- r lpush z 1 2 3
- r sadd p 1 2 3
- r zadd t 1 f1 2 f2
- r xadd s * f v
- r ping
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl b/examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl
deleted file mode 100644
index 3b98b80..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/keyspecs.tcl
+++ /dev/null
@@ -1,160 +0,0 @@
-set testmodule [file normalize tests/modules/keyspecs.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- 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} 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: No spec, only legacy triple with getkeys-api" {
- set reply [lindex [r command info kspec.nonewithgetkeys] 0]
- # 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] 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.nonewithgetkeys 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
- # Verify key-specs
- 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: Two ranges with gap" {
- set reply [lindex [r command info kspec.tworangeswithgap] 0]
- # 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 {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 3}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
- assert_equal [r command getkeys kspec.tworangeswithgap foo bar baz quux] {foo baz}
- }
-
- 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) 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 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) 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
- # Verify key-specs
- set keyspecs [lindex $reply 8]
- assert_equal [lindex $keyspecs 0] {flags {RW update} begin_search {type keyword spec {keyword STORE startfrom 5}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
- assert_equal [lindex $keyspecs 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 $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.keyword kspec.none kspec.nonewithgetkeys kspec.tworanges kspec.tworangeswithgap}
- 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}} {key2 {RW access update}}}
-
- test {COMMAND GETKEYSANDFLAGS correctly reports module key-spec with flags} {
- r command getkeysandflags kspec.nonewithgetkeys key1 val1 key2 val2
- } {{key1 {RO access}} {key2 {RO access}}}
-
- 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_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN testuser kspec.none read val1]
- assert_match {*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_match {*has no permissions to access the 'read' key*} [r ACL DRYRUN testuser kspec.tworanges rw read]
- assert_match {*has no permissions to access the 'write' key*} [r ACL DRYRUN testuser kspec.tworanges write rw]
- }
-
- foreach cmd {kspec.none kspec.tworanges} {
- test "$cmd command will not be marked with movablekeys" {
- set info [lindex [r command info $cmd] 0]
- assert_no_match {*movablekeys*} [lindex $info 2]
- }
- }
-
- foreach cmd {kspec.keyword kspec.complex1 kspec.complex2 kspec.nonewithgetkeys} {
- test "$cmd command is marked with movablekeys" {
- set info [lindex [r command info $cmd] 0]
- assert_match {*movablekeys*} [lindex $info 2]
- }
- }
-
- test "Unload the module - keyspecs" {
- assert_equal {OK} [r module unload keyspecs]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/list.tcl b/examples/redis-unstable/tests/unit/moduleapi/list.tcl
deleted file mode 100644
index 5f7532c..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/list.tcl
+++ /dev/null
@@ -1,225 +0,0 @@
-set testmodule [file normalize tests/modules/list.so]
-
-# The following arguments can be passed to args:
-# i -- the number of inserts
-# d -- the number of deletes
-# r -- the number of replaces
-# index -- the last index
-# entry -- The entry pointed to by index
-proc verify_list_edit_reply {reply argv} {
- foreach {k v} $argv {
- assert_equal [dict get $reply $k] $v
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Module list set, get, insert, delete} {
- r del k
- assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r list.set k 1 xyz}
- r rpush k x
- # insert, set, get
- r list.insert k 0 foo
- r list.insert k -1 bar
- r list.set k 1 xyz
- assert_equal {foo xyz bar} [r list.getall k]
- assert_equal {foo} [r list.get k 0]
- assert_equal {xyz} [r list.get k 1]
- assert_equal {bar} [r list.get k 2]
- assert_equal {bar} [r list.get k -1]
- assert_equal {foo} [r list.get k -3]
- assert_error {ERR index out*} {r list.get k -4}
- assert_error {ERR index out*} {r list.get k 3}
- # remove
- assert_error {ERR index out*} {r list.delete k -4}
- assert_error {ERR index out*} {r list.delete k 3}
- r list.delete k 0
- r list.delete k -1
- assert_equal {xyz} [r list.getall k]
- # removing the last element deletes the list
- r list.delete k 0
- assert_equal 0 [r exists k]
- }
-
- test {Module list iteration} {
- r del k
- r rpush k x y z
- assert_equal {x y z} [r list.getall k]
- assert_equal {z y x} [r list.getall k REVERSE]
- }
-
- test {Module list insert & delete} {
- r del k
- r rpush k x y z
- verify_list_edit_reply [r list.edit k ikikdi foo bar baz] {i 3 index 5}
- r list.getall k
- } {foo x bar y baz}
-
- test {Module list insert & delete, neg index} {
- r del k
- r rpush k x y z
- verify_list_edit_reply [r list.edit k REVERSE ikikdi foo bar baz] {i 3 index -6}
- r list.getall k
- } {baz y bar z foo}
-
- test {Module list set while iterating} {
- r del k
- r rpush k x y z
- verify_list_edit_reply [r list.edit k rkr foo bar] {r 2 index 3}
- r list.getall k
- } {foo y bar}
-
- test {Module list set while iterating, neg index} {
- r del k
- r rpush k x y z
- verify_list_edit_reply [r list.edit k reverse rkr foo bar] {r 2 index -4}
- r list.getall k
- } {bar y foo}
-
- test {Module list - encoding conversion while inserting} {
- r config set list-max-listpack-size 4
- r del k
- r rpush k a b c d
- assert_encoding listpack k
-
- # Converts to quicklist after inserting.
- r list.edit k dii foo bar
- assert_encoding quicklist k
- assert_equal [r list.getall k] {foo bar b c d}
-
- # Converts to listpack after deleting three entries.
- r list.edit k ddd e
- assert_encoding listpack k
- assert_equal [r list.getall k] {c d}
- }
-
- test {Module list - encoding conversion while replacing} {
- r config set list-max-listpack-size -1
- r del k
- r rpush k x y z
- assert_encoding listpack k
-
- # Converts to quicklist after replacing.
- set big [string repeat "x" 4096]
- r list.edit k r $big
- assert_encoding quicklist k
- assert_equal [r list.getall k] "$big y z"
-
- # Converts to listpack after deleting the big entry.
- r list.edit k d
- assert_encoding listpack k
- assert_equal [r list.getall k] {y z}
- }
-
- test {Module list - list entry and index should be updated when deletion} {
- set original_config [config_get_set list-max-listpack-size 1]
-
- # delete from start (index 0)
- r del l
- r rpush l x y z
- verify_list_edit_reply [r list.edit l dd] {d 2 index 0 entry z}
- assert_equal [r list.getall l] {z}
-
- # delete from start (index -3)
- r del l
- r rpush l x y z
- verify_list_edit_reply [r list.edit l reverse kkd] {d 1 index -3}
- assert_equal [r list.getall l] {y z}
-
- # # delete from tail (index 2)
- r del l
- r rpush l x y z
- verify_list_edit_reply [r list.edit l kkd] {d 1 index 2}
- assert_equal [r list.getall l] {x y}
-
- # # delete from tail (index -1)
- r del l
- r rpush l x y z
- verify_list_edit_reply [r list.edit l reverse dd] {d 2 index -1 entry x}
- assert_equal [r list.getall l] {x}
-
- # # delete from middle (index 1)
- r del l
- r rpush l x y z
- verify_list_edit_reply [r list.edit l kdd] {d 2 index 1}
- assert_equal [r list.getall l] {x}
-
- # # delete from middle (index -2)
- r del l
- r rpush l x y z
- verify_list_edit_reply [r list.edit l reverse kdd] {d 2 index -2}
- assert_equal [r list.getall l] {z}
-
- config_set list-max-listpack-size $original_config
- }
-
- test {Module list - KEYSIZES is updated as expected} {
- proc run_cmd_verify_hist {cmd expOutput {retries 1}} {
- proc K {} {return [string map { "db0_distrib_lists_items" "db0_LIST" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]}
- uplevel 1 $cmd
- wait_for_condition 50 $retries {
- $expOutput eq [K]
- } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" }
- }
-
- r select 0
-
- # RedisModule_ListPush & RedisModule_ListDelete
- run_cmd_verify_hist {r flushall} {}
- run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1}
- run_cmd_verify_hist {r list.insert L1 0 bla} {db0_LIST:2=1}
- run_cmd_verify_hist {r list.delete L1 0} {db0_LIST:1=1}
- run_cmd_verify_hist {r list.delete L1 0} {}
-
-
- # RedisModule_ListSet & RedisModule_ListDelete
- run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1}
- run_cmd_verify_hist {r list.set L1 0 bar} {db0_LIST:1=1}
- run_cmd_verify_hist {r list.set L1 0 baz} {db0_LIST:1=1}
- run_cmd_verify_hist {r list.delete L1 0} {}
-
- # Check lazy expire
- r debug set-active-expire 0
- run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1}
- run_cmd_verify_hist {r pexpire L1 1} {db0_LIST:1=1}
- run_cmd_verify_hist {after 5} {db0_LIST:1=1}
- r debug set-active-expire 1
- run_cmd_verify_hist {after 5} {} 50
- }
-
- test "Unload the module - list" {
- assert_equal {OK} [r module unload list]
- }
-}
-
-# A basic test that exercises a module's list commands under cluster mode.
-# Currently, many module commands are never run even once in a clustered setup.
-# This test helps ensure that basic module functionality works correctly and that
-# the KEYSIZES histogram remains accurate and that insert & delete was tested.
-set testmodule [file normalize tests/modules/list.so]
-set modules [list loadmodule $testmodule]
-start_cluster 2 2 [list tags {external:skip cluster modules} config_lines [list loadmodule $testmodule enable-debug-command yes]] {
- test "Module list - KEYSIZES is updated correctly in cluster mode" {
- for {set srvid -2} {$srvid <= 0} {incr srvid} {
- set instance [srv $srvid client]
- # Assert consistency after each command
- $instance DEBUG KEYSIZES-HIST-ASSERT 1
-
- for {set i 0} {$i < 50} {incr i} {
- for {set j 0} {$j < 4} {incr j} {
- catch {$instance list.insert "list:$i" $j "item:$j"} e
- if {![string match "OK" $e]} {assert_match "*MOVED*" $e}
- }
- }
- for {set i 0} {$i < 50} {incr i} {
- for {set j 0} {$j < 4} {incr j} {
- catch {$instance list.delete "list:$i" 0} e
- if {![string match "OK" $e]} {assert_match "*MOVED*" $e}
- }
- }
- # Verify also that instance is responsive and didn't crash on assert
- assert_equal [$instance dbsize] 0
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl b/examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl
deleted file mode 100644
index f5c4fb3..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/mallocsize.tcl
+++ /dev/null
@@ -1,21 +0,0 @@
-set testmodule [file normalize tests/modules/mallocsize.so]
-
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {MallocSize of raw bytes} {
- assert_equal [r mallocsize.setraw key 40] {OK}
- assert_morethan [r memory usage key] 40
- }
-
- test {MallocSize of string} {
- assert_equal [r mallocsize.setstr key abcdefg] {OK}
- assert_morethan [r memory usage key] 7 ;# Length of "abcdefg"
- }
-
- test {MallocSize of dict} {
- assert_equal [r mallocsize.setdict key f1 v1 f2 v2] {OK}
- assert_morethan [r memory usage key] 8 ;# Length of "f1v1f2v2"
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/misc.tcl b/examples/redis-unstable/tests/unit/moduleapi/misc.tcl
deleted file mode 100644
index b51fffb..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/misc.tcl
+++ /dev/null
@@ -1,617 +0,0 @@
-set testmodule [file normalize tests/modules/misc.so]
-
-start_server {overrides {save {900 1}} tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {test RM_Call} {
- set info [r test.call_info commandstats]
- # cmdstat is not in a default section, so we also test an argument was passed
- assert { [string match "*cmdstat_module*" $info] }
- }
-
- test {test RM_Call args array} {
- set info [r test.call_generic info commandstats]
- # cmdstat is not in a default section, so we also test an argument was passed
- assert { [string match "*cmdstat_module*" $info] }
- }
-
- test {test RM_Call recursive} {
- set info [r test.call_generic test.call_generic info commandstats]
- assert { [string match "*cmdstat_module*" $info] }
- }
-
- test {test redis version} {
- set version [s redis_version]
- assert_equal $version [r test.redisversion]
- }
-
- test {test long double conversions} {
- set ld [r test.ld_conversion]
- assert {[string match $ld "0.00000000000000001"]}
- }
-
- test {test unsigned long long conversions} {
- set ret [r test.ull_conversion]
- assert {[string match $ret "ok"]}
- }
-
- test {test module db commands} {
- r set x foo
- set key [r test.randomkey]
- assert_equal $key "x"
- assert_equal [r test.dbsize] 1
- r test.flushall
- assert_equal [r test.dbsize] 0
- }
-
- test {test RedisModule_ResetDataset do not reset functions} {
- r function load {#!lua name=lib
- redis.register_function('test', function() return 1 end)
- }
- assert_equal [r function list] {{library_name lib engine LUA functions {{name test description {} flags {}}}}}
- r test.flushall
- assert_equal [r function list] {{library_name lib engine LUA functions {{name test description {} flags {}}}}}
- r function flush
- }
-
- test {test module keyexists} {
- r set x foo
- assert_equal 1 [r test.keyexists x]
- r del x
- assert_equal 0 [r test.keyexists x]
- }
-
- test {test module lru api} {
- r config set maxmemory-policy allkeys-lru
- r set x foo
- set lru [r test.getlru x]
- assert { $lru <= 1000 }
- set was_set [r test.setlru x 100000]
- assert { $was_set == 1 }
- set idle [r object idletime x]
- assert { $idle >= 100 }
- set lru [r test.getlru x]
- assert { $lru >= 100000 }
- r config set maxmemory-policy allkeys-lfu
- set lru [r test.getlru x]
- assert { $lru == -1 }
- set was_set [r test.setlru x 100000]
- assert { $was_set == 0 }
- }
- r config set maxmemory-policy allkeys-lru
-
- test {test module lfu api} {
- r config set maxmemory-policy allkeys-lfu
- r set x foo
- set lfu [r test.getlfu x]
- assert { $lfu >= 1 }
- set was_set [r test.setlfu x 100]
- assert { $was_set == 1 }
- set freq [r object freq x]
- assert { $freq <= 100 }
- set lfu [r test.getlfu x]
- assert { $lfu <= 100 }
- r config set maxmemory-policy allkeys-lru
- set lfu [r test.getlfu x]
- assert { $lfu == -1 }
- set was_set [r test.setlfu x 100]
- assert { $was_set == 0 }
- }
-
- test {test module clientinfo api} {
- # Test basic sanity and SSL flag
- set info [r test.clientinfo]
- set ssl_flag [expr $::tls ? {"ssl:"} : {":"}]
-
- assert { [dict get $info db] == 9 }
- assert { [dict get $info flags] == "${ssl_flag}::::" }
-
- # Test MULTI flag
- r multi
- r test.clientinfo
- set info [lindex [r exec] 0]
- assert { [dict get $info flags] == "${ssl_flag}::::multi" }
-
- # Test TRACKING flag
- r client tracking on
- set info [r test.clientinfo]
- assert { [dict get $info flags] == "${ssl_flag}::tracking::" }
- r CLIENT TRACKING off
- }
-
- test {tracking with rm_call sanity} {
- set rd_trk [redis_client]
- $rd_trk HELLO 3
- $rd_trk CLIENT TRACKING on
- r MSET key1{t} 1 key2{t} 1
-
- # GET triggers tracking, SET does not
- $rd_trk test.rm_call GET key1{t}
- $rd_trk test.rm_call SET key2{t} 2
- r MSET key1{t} 2 key2{t} 2
- assert_equal {invalidate key1{t}} [$rd_trk read]
- assert_equal "PONG" [$rd_trk ping]
- $rd_trk close
- }
-
- test {tracking with rm_call with script} {
- set rd_trk [redis_client]
- $rd_trk HELLO 3
- $rd_trk CLIENT TRACKING on
- r MSET key1{t} 1 key2{t} 1
-
- # GET triggers tracking, SET does not
- $rd_trk test.rm_call EVAL "redis.call('get', 'key1{t}')" 2 key1{t} key2{t}
- r MSET key1{t} 2 key2{t} 2
- assert_equal {invalidate key1{t}} [$rd_trk read]
- assert_equal "PONG" [$rd_trk ping]
- $rd_trk close
- }
-
- test {RM_SignalModifiedKey - tracking invalidation} {
- set rd_trk [redis_client]
- $rd_trk HELLO 3
- $rd_trk CLIENT TRACKING on
- r SET mykey{t} abc
-
- # Track the key by reading it
- $rd_trk GET mykey{t}
-
- # # Modify the key using module command that calls RM_SignalModifiedKey
- r test.signalmodifiedkey mykey{t}
-
- # # Should receive invalidation message
- assert_equal {invalidate mykey{t}} [$rd_trk read]
- assert_equal "PONG" [$rd_trk ping]
- $rd_trk close
- }
-
- test {RM_SignalModifiedKey - update LRM timestamp} {
- set old_policy [config_get_set maxmemory-policy allkeys-lrm]
- r SET mykey{t} abc
- after 2000
- assert_morethan_equal [r object idletime mykey{t}] 1
-
- # LRM should be updated.
- r test.signalmodifiedkey mykey{t}
- assert_lessthan [r object idletime mykey{t}] 2
- r config set maxmemory-policy $old_policy
- } {OK} {slow}
-
- test {publish to self inside rm_call} {
- r hello 3
- r subscribe foo
-
- # published message comes after the response of the command that issued it.
- assert_equal [r test.rm_call publish foo bar] {1}
- assert_equal [r read] {message foo bar}
-
- r unsubscribe foo
- r hello 2
- set _ ""
- } {} {resp3}
-
- test {test module get/set client name by id api} {
- catch { r test.getname } e
- assert_equal "-ERR No name" $e
- r client setname nobody
- catch { r test.setname "name with spaces" } e
- assert_match "*Invalid argument*" $e
- assert_equal nobody [r client getname]
- assert_equal nobody [r test.getname]
- r test.setname somebody
- assert_equal somebody [r client getname]
- }
-
- test {test module getclientcert api} {
- set cert [r test.getclientcert]
-
- if {$::tls} {
- assert {$cert != ""}
- } else {
- assert {$cert == ""}
- }
- }
-
- test {test detached thread safe cnotext} {
- r test.log_tsctx "info" "Test message"
- verify_log_message 0 "*<misc> Test message*" 0
- }
-
- test {test RM_Call CLIENT INFO} {
- assert_match "*fd=-1*" [r test.call_generic client info]
- }
-
- test {Unsafe command names are sanitized in INFO output} {
- r test.weird:cmd
- set info [r info commandstats]
- assert_match {*cmdstat_test.weird_cmd:calls=1*} $info
- }
-
- test {test monotonic time} {
- set x [r test.monotonic_time]
- assert { [r test.monotonic_time] >= $x }
- }
-
- test {rm_call OOM} {
- r config set maxmemory 1
- r config set maxmemory-policy volatile-lru
-
- # sanity test plain call
- assert_equal {OK} [
- r test.rm_call set x 1
- ]
-
- # add the M flag
- assert_error {OOM *} {
- r test.rm_call_flags M set x 1
-
- }
-
- # test a non deny-oom command
- assert_equal {1} [
- r test.rm_call_flags M get x
- ]
-
- r config set maxmemory 0
- } {OK} {needs:config-maxmemory}
-
- test {rm_call clear OOM} {
- r config set maxmemory 1
-
- # verify rm_call fails with OOM
- assert_error {OOM *} {
- r test.rm_call_flags M set x 1
- }
-
- # clear OOM state
- r config set maxmemory 0
-
- # test set command is allowed
- r test.rm_call_flags M set x 1
- } {OK} {needs:config-maxmemory}
-
- test {rm_call OOM Eval} {
- r config set maxmemory 1
- r config set maxmemory-policy volatile-lru
-
- # use the M flag without allow-oom shebang flag
- assert_error {OOM *} {
- r test.rm_call_flags M eval {#!lua
- redis.call('set','x',1)
- return 1
- } 1 x
- }
-
- # add the M flag with allow-oom shebang flag
- assert_equal {1} [
- r test.rm_call_flags M eval {#!lua flags=allow-oom
- redis.call('set','x',1)
- return 1
- } 1 x
- ]
-
- r config set maxmemory 0
- } {OK} {needs:config-maxmemory}
-
- test {rm_call write flag} {
- # add the W flag
- assert_error {ERR Write command 'set' was called while write is not allowed.} {
- r test.rm_call_flags W set x 1
- }
-
- # test a non deny-oom command
- r test.rm_call_flags W get x
- } {1}
-
- test {rm_call EVAL} {
- r test.rm_call eval {
- redis.call('set','x',1)
- return 1
- } 1 x
-
- assert_error {ERR Write commands are not allowed from read-only scripts.*} {
- r test.rm_call eval {#!lua flags=no-writes
- redis.call('set','x',1)
- return 1
- } 1 x
- }
- }
-
- # Note: each script is unique, to check that flags are extracted correctly
- test {rm_call EVAL - OOM - with M flag} {
- r config set maxmemory 1
-
- # script without shebang, but uses SET, so fails
- assert_error {*OOM command not allowed when used memory > 'maxmemory'*} {
- r test.rm_call_flags M eval {
- redis.call('set','x',1)
- return 1
- } 1 x
- }
-
- # script with an allow-oom flag, succeeds despite using SET
- r test.rm_call_flags M eval {#!lua flags=allow-oom
- redis.call('set','x', 1)
- return 2
- } 1 x
-
- # script with no-writes flag, implies allow-oom, succeeds
- r test.rm_call_flags M eval {#!lua flags=no-writes
- redis.call('get','x')
- return 2
- } 1 x
-
- # script with shebang using default flags, so fails regardless of using only GET
- assert_error {*OOM command not allowed when used memory > 'maxmemory'*} {
- r test.rm_call_flags M eval {#!lua
- redis.call('get','x')
- return 3
- } 1 x
- }
-
- # script without shebang, but uses GET, so succeeds
- r test.rm_call_flags M eval {
- redis.call('get','x')
- return 4
- } 1 x
-
- r config set maxmemory 0
- } {OK} {needs:config-maxmemory}
-
- # All RM_Call for script succeeds in OOM state without using the M flag
- test {rm_call EVAL - OOM - without M flag} {
- r config set maxmemory 1
-
- # no shebang at all
- r test.rm_call eval {
- redis.call('set','x',1)
- return 6
- } 1 x
-
- # Shebang without flags
- r test.rm_call eval {#!lua
- redis.call('set','x', 1)
- return 7
- } 1 x
-
- # with allow-oom flag
- r test.rm_call eval {#!lua flags=allow-oom
- redis.call('set','x', 1)
- return 8
- } 1 x
-
- r config set maxmemory 0
- } {OK} {needs:config-maxmemory}
-
- test "not enough good replicas" {
- r set x "some value"
- r config set min-replicas-to-write 1
-
- # rm_call in script mode
- assert_error {NOREPLICAS *} {r test.rm_call_flags S set x s}
-
- assert_equal [
- r test.rm_call eval {#!lua flags=no-writes
- return redis.call('get','x')
- } 1 x
- ] "some value"
-
- assert_equal [
- r test.rm_call eval {
- return redis.call('get','x')
- } 1 x
- ] "some value"
-
- assert_error {NOREPLICAS *} {
- r test.rm_call eval {#!lua
- return redis.call('get','x')
- } 1 x
- }
-
- assert_error {NOREPLICAS *} {
- r test.rm_call eval {
- return redis.call('set','x', 1)
- } 1 x
- }
-
- r config set min-replicas-to-write 0
- }
-
- test {rm_call EVAL - read-only replica} {
- r replicaof 127.0.0.1 1
-
- # rm_call in script mode
- assert_error {READONLY *} {r test.rm_call_flags S set x 1}
-
- assert_error {READONLY You can't write against a read only replica. script*} {
- r test.rm_call eval {
- redis.call('set','x',1)
- return 1
- } 1 x
- }
-
- r test.rm_call eval {#!lua flags=no-writes
- redis.call('get','x')
- return 2
- } 1 x
-
- assert_error {READONLY Can not run script with write flag on readonly replica*} {
- r test.rm_call eval {#!lua
- redis.call('get','x')
- return 3
- } 1 x
- }
-
- r test.rm_call eval {
- redis.call('get','x')
- return 4
- } 1 x
-
- r replicaof no one
- } {OK} {needs:config-maxmemory}
-
- test {rm_call EVAL - stale replica} {
- r replicaof 127.0.0.1 1
- r config set replica-serve-stale-data no
-
- # rm_call in script mode
- assert_error {MASTERDOWN *} {
- r test.rm_call_flags S get x
- }
-
- assert_error {MASTERDOWN *} {
- r test.rm_call eval {#!lua flags=no-writes
- redis.call('get','x')
- return 2
- } 1 x
- }
-
- assert_error {MASTERDOWN *} {
- r test.rm_call eval {
- redis.call('get','x')
- return 4
- } 1 x
- }
-
- r replicaof no one
- r config set replica-serve-stale-data yes
- } {OK} {needs:config-maxmemory}
-
- test "rm_call EVAL - failed bgsave prevents writes" {
- r config set rdb-key-save-delay 10000000
- populate 1000
- r set x x
- r bgsave
- set pid1 [get_child_pid 0]
- catch {exec kill -9 $pid1}
- waitForBgsave r
-
- # make sure a read command succeeds
- assert_equal [r get x] x
-
- # make sure a write command fails
- assert_error {MISCONF *} {r set x y}
-
- # rm_call in script mode
- assert_error {MISCONF *} {r test.rm_call_flags S set x 1}
-
- # repeate with script
- assert_error {MISCONF *} {r test.rm_call eval {
- return redis.call('set','x',1)
- } 1 x
- }
- assert_equal {x} [r test.rm_call eval {
- return redis.call('get','x')
- } 1 x
- ]
-
- # again with script using shebang
- assert_error {MISCONF *} {r test.rm_call eval {#!lua
- return redis.call('set','x',1)
- } 1 x
- }
- assert_equal {x} [r test.rm_call eval {#!lua flags=no-writes
- return redis.call('get','x')
- } 1 x
- ]
-
- r config set rdb-key-save-delay 0
- r bgsave
- waitForBgsave r
-
- # server is writable again
- r set x y
- } {OK}
-
- test "malloc API" {
- assert_equal {OK} [r test.malloc_api 0]
- }
-
- test "Cluster keyslot" {
- assert_equal 12182 [r test.keyslot foo]
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {test Dry Run - OK OOM/ACL} {
- set x 5
- r set x $x
- catch {r test.rm_call_flags DMC set x 10} e
- assert_match {*NULL reply returned*} $e
- assert_equal [r get x] 5
- }
-
- test {test Dry Run - Fail OOM} {
- set x 5
- r set x $x
- r config set maxmemory 1
- catch {r test.rm_call_flags DM set x 10} e
- assert_match {*OOM*} $e
- assert_equal [r get x] $x
- r config set maxmemory 0
- } {OK} {needs:config-maxmemory}
-
- test {test Dry Run - Fail ACL} {
- set x 5
- r set x $x
- # deny all permissions besides the dryrun command
- r acl setuser default resetkeys
-
- catch {r test.rm_call_flags DC set x 10} e
- assert_match {*NOPERM No permissions to access a key*} $e
- r acl setuser default +@all ~*
- assert_equal [r get x] $x
- }
-
- test {test silent open key} {
- r debug set-active-expire 0
- r test.clear_n_events
- r set x 1 PX 10
- after 1000
- # now the key has been expired, open it silently and make sure not event were fired.
- assert_error {key not found} {r test.silent_open_key x}
- assert_equal {0} [r test.get_n_events]
- }
-
-if {[string match {*jemalloc*} [s mem_allocator]]} {
- test {test RM_Call with large arg for SET command} {
- # set a big value to trigger increasing the query buf
- r set foo [string repeat A 100000]
- # set a smaller value but > PROTO_MBULK_BIG_ARG (32*1024) Redis will try to save the query buf itself on the DB.
- r test.call_generic set bar [string repeat A 33000]
- # asset the value was trimmed
- assert {[r memory usage bar] < 42000}; # 42K to count for Jemalloc's additional memory overhead.
- }
-} ;# if jemalloc
-
- test "Unload the module - misc" {
- assert_equal {OK} [r module unload misc]
- }
-}
-
-start_server {tags {"modules external:skip"}} {
- test {Detect incompatible operations in cluster mode for module} {
- r config set cluster-compatibility-sample-ratio 100
- set incompatible_ops [s cluster_incompatible_ops]
-
- # since test.no_cluster_cmd and its subcommand have 'no-cluster' flag,
- # they should not be counted as incompatible ops, increment the counter by 2
- r module load $testmodule
- assert_equal [expr $incompatible_ops+2] [s cluster_incompatible_ops]
-
- # incompatible_cluster_cmd is similar with MSET, check if it is counted as
- # incompatible ops with different number of keys
- # only 1 key, should not increment the counter
- r test.incompatible_cluster_cmd foo bar
- assert_equal [expr $incompatible_ops+2] [s cluster_incompatible_ops]
- # 2 cross slot keys, should increment the counter
- r test.incompatible_cluster_cmd foo bar bar foo
- assert_equal [expr $incompatible_ops+3] [s cluster_incompatible_ops]
- # 2 non cross slot keys, should not increment the counter
- r test.incompatible_cluster_cmd foo bar bar{foo} bar
- assert_equal [expr $incompatible_ops+3] [s cluster_incompatible_ops]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl b/examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl
deleted file mode 100644
index 8de6670..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/moduleauth.tcl
+++ /dev/null
@@ -1,405 +0,0 @@
-set testmodule [file normalize tests/modules/auth.so]
-set testmoduletwo [file normalize tests/modules/moduleauthtwo.so]
-set miscmodule [file normalize tests/modules/misc.so]
-
-proc cmdstat {cmd} {
- return [cmdrstat $cmd r]
-}
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
- r module load $testmoduletwo
-
- set hello2_response [r HELLO 2]
- set hello3_response [r HELLO 3]
-
- test {test registering module auth callbacks} {
- assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb]
- assert_equal {OK} [r testmoduletwo.rm_register_auth_cb]
- assert_equal {OK} [r testmoduleone.rm_register_auth_cb]
- }
-
- test {test module AUTH for non existing / disabled users} {
- r config resetstat
- # Validate that an error is thrown for non existing users.
- assert_error {*WRONGPASS*} {r AUTH foo pwd}
- assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- # Validate that an error is thrown for disabled users.
- r acl setuser foo >pwd off ~* &* +@all
- assert_error {*WRONGPASS*} {r AUTH foo pwd}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdstat auth]
- }
-
- test {test non blocking module AUTH} {
- r config resetstat
- # Test for a fixed password user
- r acl setuser foo >pwd on ~* &* +@all
- assert_equal {OK} [r AUTH foo allow]
- assert_error {*Auth denied by Misc Module*} {r AUTH foo deny}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- assert_error {*WRONGPASS*} {r AUTH foo nomatch}
- assert_match {*calls=3,*,rejected_calls=0,failed_calls=2} [cmdstat auth]
- assert_equal {OK} [r AUTH foo pwd]
- # Test for No Pass user
- r acl setuser foo on ~* &* +@all nopass
- assert_equal {OK} [r AUTH foo allow]
- assert_error {*Auth denied by Misc Module*} {r AUTH foo deny}
- assert_match {*calls=6,*,rejected_calls=0,failed_calls=3} [cmdstat auth]
- assert_equal {OK} [r AUTH foo nomatch]
-
- # Validate that the Module added an ACL Log entry.
- set entry [lindex [r ACL LOG] 0]
- assert {[dict get $entry username] eq {foo}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry reason] eq {auth}}
- assert {[dict get $entry object] eq {Module Auth}}
- assert_match {*cmd=auth*} [dict get $entry client-info]
- r ACL LOG RESET
- }
-
- test {test non blocking module HELLO AUTH} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- # Validate proto 2 and 3 in case of success
- assert_equal $hello2_response [r HELLO 2 AUTH foo pwd]
- assert_equal $hello2_response [r HELLO 2 AUTH foo allow]
- assert_equal $hello3_response [r HELLO 3 AUTH foo pwd]
- assert_equal $hello3_response [r HELLO 3 AUTH foo allow]
- # Validate denying AUTH for the HELLO cmd
- assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo deny}
- assert_match {*calls=5,*,rejected_calls=0,failed_calls=1} [cmdstat hello]
- assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch}
- assert_match {*calls=6,*,rejected_calls=0,failed_calls=2} [cmdstat hello]
- assert_error {*Auth denied by Misc Module*} {r HELLO 3 AUTH foo deny}
- assert_match {*calls=7,*,rejected_calls=0,failed_calls=3} [cmdstat hello]
- assert_error {*WRONGPASS*} {r HELLO 3 AUTH foo nomatch}
- assert_match {*calls=8,*,rejected_calls=0,failed_calls=4} [cmdstat hello]
-
- # Validate that the Module added an ACL Log entry.
- set entry [lindex [r ACL LOG] 1]
- assert {[dict get $entry username] eq {foo}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry reason] eq {auth}}
- assert {[dict get $entry object] eq {Module Auth}}
- assert_match {*cmd=hello*} [dict get $entry client-info]
- r ACL LOG RESET
- }
-
- test {test non blocking module HELLO AUTH SETNAME} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- # Validate clientname is set on success
- assert_equal $hello2_response [r HELLO 2 AUTH foo pwd setname client1]
- assert {[r client getname] eq {client1}}
- assert_equal $hello2_response [r HELLO 2 AUTH foo allow setname client2]
- assert {[r client getname] eq {client2}}
- # Validate clientname is not updated on failure
- r client setname client0
- assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo deny setname client1}
- assert {[r client getname] eq {client0}}
- assert_match {*calls=3,*,rejected_calls=0,failed_calls=1} [cmdstat hello]
- assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch setname client2}
- assert {[r client getname] eq {client0}}
- assert_match {*calls=4,*,rejected_calls=0,failed_calls=2} [cmdstat hello]
- }
-
- test {test blocking module AUTH} {
- r config resetstat
- # Test for a fixed password user
- r acl setuser foo >pwd on ~* &* +@all
- assert_equal {OK} [r AUTH foo block_allow]
- assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- assert_error {*WRONGPASS*} {r AUTH foo nomatch}
- assert_match {*calls=3,*,rejected_calls=0,failed_calls=2} [cmdstat auth]
- assert_equal {OK} [r AUTH foo pwd]
- # Test for No Pass user
- r acl setuser foo on ~* &* +@all nopass
- assert_equal {OK} [r AUTH foo block_allow]
- assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny}
- assert_match {*calls=6,*,rejected_calls=0,failed_calls=3} [cmdstat auth]
- assert_equal {OK} [r AUTH foo nomatch]
- # Validate that every Blocking AUTH command took at least 500000 usec.
- set stats [cmdstat auth]
- regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call
- assert {$usec_per_call >= 500000}
-
- # Validate that the Module added an ACL Log entry.
- set entry [lindex [r ACL LOG] 0]
- assert {[dict get $entry username] eq {foo}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry reason] eq {auth}}
- assert {[dict get $entry object] eq {Module Auth}}
- assert_match {*cmd=auth*} [dict get $entry client-info]
- r ACL LOG RESET
- }
-
- test {test blocking module HELLO AUTH} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- # validate proto 2 and 3 in case of success
- assert_equal $hello2_response [r HELLO 2 AUTH foo pwd]
- assert_equal $hello2_response [r HELLO 2 AUTH foo block_allow]
- assert_equal $hello3_response [r HELLO 3 AUTH foo pwd]
- assert_equal $hello3_response [r HELLO 3 AUTH foo block_allow]
- # validate denying AUTH for the HELLO cmd
- assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo block_deny}
- assert_match {*calls=5,*,rejected_calls=0,failed_calls=1} [cmdstat hello]
- assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch}
- assert_match {*calls=6,*,rejected_calls=0,failed_calls=2} [cmdstat hello]
- assert_error {*Auth denied by Misc Module*} {r HELLO 3 AUTH foo block_deny}
- assert_match {*calls=7,*,rejected_calls=0,failed_calls=3} [cmdstat hello]
- assert_error {*WRONGPASS*} {r HELLO 3 AUTH foo nomatch}
- assert_match {*calls=8,*,rejected_calls=0,failed_calls=4} [cmdstat hello]
- # Validate that every HELLO AUTH command took at least 500000 usec.
- set stats [cmdstat hello]
- regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call
- assert {$usec_per_call >= 500000}
-
- # Validate that the Module added an ACL Log entry.
- set entry [lindex [r ACL LOG] 1]
- assert {[dict get $entry username] eq {foo}}
- assert {[dict get $entry context] eq {module}}
- assert {[dict get $entry reason] eq {auth}}
- assert {[dict get $entry object] eq {Module Auth}}
- assert_match {*cmd=hello*} [dict get $entry client-info]
- r ACL LOG RESET
- }
-
- test {test blocking module HELLO AUTH SETNAME} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- # Validate clientname is set on success
- assert_equal $hello2_response [r HELLO 2 AUTH foo pwd setname client1]
- assert {[r client getname] eq {client1}}
- assert_equal $hello2_response [r HELLO 2 AUTH foo block_allow setname client2]
- assert {[r client getname] eq {client2}}
- # Validate clientname is not updated on failure
- r client setname client0
- assert_error {*Auth denied by Misc Module*} {r HELLO 2 AUTH foo block_deny setname client1}
- assert {[r client getname] eq {client0}}
- assert_match {*calls=3,*,rejected_calls=0,failed_calls=1} [cmdstat hello]
- assert_error {*WRONGPASS*} {r HELLO 2 AUTH foo nomatch setname client2}
- assert {[r client getname] eq {client0}}
- assert_match {*calls=4,*,rejected_calls=0,failed_calls=2} [cmdstat hello]
- # Validate that every HELLO AUTH SETNAME command took at least 500000 usec.
- set stats [cmdstat hello]
- regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call
- assert {$usec_per_call >= 500000}
- }
-
- test {test AUTH after registering multiple module auth callbacks} {
- r config resetstat
-
- # Register two more callbacks from the same module.
- assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb]
- assert_equal {OK} [r testmoduleone.rm_register_auth_cb]
-
- # Register another module auth callback from the second module.
- assert_equal {OK} [r testmoduletwo.rm_register_auth_cb]
-
- r acl setuser foo >pwd on ~* &* +@all
-
- # Case 1 - Non Blocking Success
- assert_equal {OK} [r AUTH foo allow]
-
- # Case 2 - Non Blocking Deny
- assert_error {*Auth denied by Misc Module*} {r AUTH foo deny}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
-
- r config resetstat
-
- # Case 3 - Blocking Success
- assert_equal {OK} [r AUTH foo block_allow]
-
- # Case 4 - Blocking Deny
- assert_error {*Auth denied by Misc Module*} {r AUTH foo block_deny}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
-
- # Validate that every Blocking AUTH command took at least 500000 usec.
- set stats [cmdstat auth]
- regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call
- assert {$usec_per_call >= 500000}
-
- r config resetstat
-
- # Case 5 - Non Blocking Success via the second module.
- assert_equal {OK} [r AUTH foo allow_two]
-
- # Case 6 - Non Blocking Deny via the second module.
- assert_error {*Auth denied by Misc Module*} {r AUTH foo deny_two}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
-
- r config resetstat
-
- # Case 7 - All four auth callbacks "Skip" by not explicitly allowing or denying.
- assert_error {*WRONGPASS*} {r AUTH foo nomatch}
- assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- assert_equal {OK} [r AUTH foo pwd]
-
- # Because we had to attempt all 4 callbacks, validate that the AUTH command took at least
- # 1000000 usec (each blocking callback takes 500000 usec).
- set stats [cmdstat auth]
- regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call
- assert {$usec_per_call >= 1000000}
- }
-
- test {module auth during blocking module auth} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- set rd [redis_deferring_client]
- set rd_two [redis_deferring_client]
-
- # Attempt blocking module auth. While this ongoing, attempt non blocking module auth from
- # moduleone/moduletwo and start another blocking module auth from another deferring client.
- $rd AUTH foo block_allow
- wait_for_blocked_clients_count 1
- assert_equal {OK} [r AUTH foo allow]
- assert_equal {OK} [r AUTH foo allow_two]
- # Validate that the non blocking module auth cmds finished before any blocking module auth.
- set info_clients [r info clients]
- assert_match "*blocked_clients:1*" $info_clients
- $rd_two AUTH foo block_allow
-
- # Validate that all of the AUTH commands succeeded.
- wait_for_blocked_clients_count 0 500 10
- $rd flush
- assert_equal [$rd read] "OK"
- $rd_two flush
- assert_equal [$rd_two read] "OK"
- assert_match {*calls=4,*,rejected_calls=0,failed_calls=0} [cmdstat auth]
- }
-
- test {module auth inside MULTI EXEC} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
-
- # Validate that non blocking module auth inside MULTI succeeds.
- r multi
- r AUTH foo allow
- assert_equal {OK} [r exec]
-
- # Validate that blocking module auth inside MULTI throws an err.
- r multi
- r AUTH foo block_allow
- assert_error {*ERR Blocking module command called from transaction*} {r exec}
- assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- }
-
- test {Disabling Redis User during blocking module auth} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- set rd [redis_deferring_client]
-
- # Attempt blocking module auth and disable the Redis user while module auth is in progress.
- $rd AUTH foo pwd
- wait_for_blocked_clients_count 1
- r acl setuser foo >pwd off ~* &* +@all
-
- # Validate that module auth failed.
- wait_for_blocked_clients_count 0 500 10
- $rd flush
- assert_error {*WRONGPASS*} { $rd read }
- assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- }
-
- test {Killing a client in the middle of blocking module auth} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
- set rd [redis_deferring_client]
- $rd client id
- set cid [$rd read]
-
- # Attempt blocking module auth command on client `cid` and kill the client while module auth
- # is in progress.
- $rd AUTH foo pwd
- wait_for_blocked_clients_count 1
- r client kill id $cid
-
- # Validate that the blocked client count goes to 0 and no AUTH command is tracked.
- wait_for_blocked_clients_count 0 500 10
- $rd flush
- assert_error {*I/O error reading reply*} { $rd read }
- assert_match {} [cmdstat auth]
- }
-
- test {test RM_AbortBlock Module API during blocking module auth} {
- r config resetstat
- r acl setuser foo >pwd on ~* &* +@all
-
- # Attempt module auth. With the "block_abort" as the password, the "testacl.so" module
- # blocks the client and uses the RM_AbortBlock API. This should result in module auth
- # failing and the client being unblocked with the default AUTH err message.
- assert_error {*WRONGPASS*} {r AUTH foo block_abort}
- assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
- }
-
- test {test RM_RegisterAuthCallback Module API during blocking module auth} {
- r config resetstat
- r acl setuser foo >defaultpwd on ~* &* +@all
- set rd [redis_deferring_client]
-
- # Start the module auth attempt with the standard Redis auth password for the user. This
- # will result in all module auth cbs attempted and then standard Redis auth will be tried.
- $rd AUTH foo defaultpwd
- wait_for_blocked_clients_count 1
-
- # Validate that we allow modules to register module auth cbs while module auth is already
- # in progress.
- assert_equal {OK} [r testmoduleone.rm_register_blocking_auth_cb]
- assert_equal {OK} [r testmoduletwo.rm_register_auth_cb]
-
- # Validate that blocking module auth succeeds.
- wait_for_blocked_clients_count 0 500 10
- $rd flush
- assert_equal [$rd read] "OK"
- set stats [cmdstat auth]
- assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} $stats
-
- # Validate that even the new blocking module auth cb which was registered in the middle of
- # blocking module auth is attempted - making it take twice the duration (2x 500000 us).
- regexp "usec_per_call=(\[0-9]{1,})\.*," $stats all usec_per_call
- assert {$usec_per_call >= 1000000}
- }
-
- test {Module unload during blocking module auth} {
- r config resetstat
- r module load $miscmodule
- set rd [redis_deferring_client]
- r acl setuser foo >pwd on ~* &* +@all
-
- # Start a blocking module auth attempt.
- $rd AUTH foo block_allow
- wait_for_blocked_clients_count 1
-
- # moduleone and moduletwo have module auth cbs registered. Because blocking module auth is
- # ongoing, they cannot be unloaded.
- catch {r module unload testacl} e
- assert_match {*the module has blocked clients*} $e
- # The moduleauthtwo module can be unregistered because no client is blocked on it.
- assert_equal "OK" [r module unload moduleauthtwo]
-
- # The misc module does not have module auth cbs registered, so it can be unloaded even when
- # blocking module auth is ongoing.
- assert_equal "OK" [r module unload misc]
-
- # Validate that blocking module auth succeeds.
- wait_for_blocked_clients_count 0 500 10
- $rd flush
- assert_equal [$rd read] "OK"
- assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat auth]
-
- # Validate that unloading the moduleauthtwo module does not unregister module auth cbs of
- # of the testacl module. Module based auth should succeed.
- assert_equal {OK} [r AUTH foo allow]
-
- # Validate that the testacl module can be unloaded since blocking module auth is done.
- r module unload testacl
-
- # Validate that since all module auth cbs are unregistered, module auth attempts fail.
- assert_error {*WRONGPASS*} {r AUTH foo block_allow}
- assert_error {*WRONGPASS*} {r AUTH foo allow_two}
- assert_error {*WRONGPASS*} {r AUTH foo allow}
- assert_match {*calls=5,*,rejected_calls=0,failed_calls=3} [cmdstat auth]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl b/examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl
deleted file mode 100644
index 25ed33a..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/moduleconfigs.tcl
+++ /dev/null
@@ -1,386 +0,0 @@
-set testmodule [file normalize tests/modules/moduleconfigs.so]
-set testmoduletwo [file normalize tests/modules/moduleconfigstwo.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
- test {Config get commands work} {
- # Make sure config get module config works
- assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
- assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
-
- # Check un-prefixed and aliased configuration
- assert_equal [r config get unprefix-bool] "unprefix-bool yes"
- assert_equal [r config get unprefix-noalias-bool] "unprefix-noalias-bool yes"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes"
- assert_equal [r config get unprefix.numeric] "unprefix.numeric -1"
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias -1"
- assert_equal [r config get unprefix-string] "unprefix-string {secret unprefix}"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias {secret unprefix}"
- assert_equal [r config get unprefix-enum] "unprefix-enum one"
- assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one"
- }
-
- test {Config set commands work} {
- # Make sure that config sets work during runtime
- r config set moduleconfigs.mutable_bool no
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
- r config set moduleconfigs.memory_numeric 1mb
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576"
- r config set moduleconfigs.string wafflewednesdays
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string wafflewednesdays"
- set not_embstr [string repeat A 50]
- r config set moduleconfigs.string $not_embstr
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string $not_embstr"
- r config set moduleconfigs.string \x73\x75\x70\x65\x72\x20\x00\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
- r config set moduleconfigs.enum two
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
- r config set moduleconfigs.flags two
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags two"
- r config set moduleconfigs.numeric -2
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2"
-
- # Check un-prefixed and aliased configuration
- r config set unprefix-bool no
- assert_equal [r config get unprefix-bool] "unprefix-bool no"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias no"
- r config set unprefix-bool-alias yes
- assert_equal [r config get unprefix-bool] "unprefix-bool yes"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes"
- r config set unprefix.numeric 5
- assert_equal [r config get unprefix.numeric] "unprefix.numeric 5"
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 5"
- r config set unprefix.numeric-alias 6
- assert_equal [r config get unprefix.numeric] "unprefix.numeric 6"
- r config set unprefix.string-alias "blabla"
- assert_equal [r config get unprefix-string] "unprefix-string blabla"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias blabla"
- r config set unprefix-enum two
- assert_equal [r config get unprefix-enum] "unprefix-enum two"
- assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias two"
- }
-
- test {Config set commands enum flags} {
- r config set moduleconfigs.flags "none"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags none"
-
- r config set moduleconfigs.flags "two four"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}"
-
- r config set moduleconfigs.flags "five"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags five"
-
- r config set moduleconfigs.flags "one four"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags five"
-
- r config set moduleconfigs.flags "one two four"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {five two}"
- }
-
- test {Immutable flag works properly and rejected strings dont leak} {
- # Configs flagged immutable should not allow sets
- catch {[r config set moduleconfigs.immutable_bool yes]} e
- assert_match {*can't set immutable config*} $e
- catch {[r config set moduleconfigs.string rejectisfreed]} e
- assert_match {*Cannot set string to 'rejectisfreed'*} $e
- }
-
- test {Numeric limits work properly} {
- # Configs over/under the limit shouldn't be allowed, and memory configs should only take memory values
- catch {[r config set moduleconfigs.memory_numeric 200gb]} e
- assert_match {*argument must be between*} $e
- catch {[r config set moduleconfigs.memory_numeric -5]} e
- assert_match {*argument must be a memory value*} $e
- catch {[r config set moduleconfigs.numeric -10]} e
- assert_match {*argument must be between*} $e
- }
-
- test {Enums only able to be set to passed in values} {
- # Module authors specify what values are valid for enums, check that only those values are ok on a set
- catch {[r config set moduleconfigs.enum asdf]} e
- assert_match {*must be one of the following*} $e
- }
-
- test {test blocking of config registration and load outside of OnLoad} {
- assert_equal [r block.register.configs.outside.onload] OK
- }
-
- test {Unload removes module configs} {
- r module unload moduleconfigs
- assert_equal [r config get moduleconfigs.*] ""
- r module load $testmodule
- # these should have reverted back to their module specified values
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
- assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
-
- # Check un-prefixed and aliased configuration
- assert_equal [r config get unprefix-bool] "unprefix-bool yes"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes"
- assert_equal [r config get unprefix.numeric] "unprefix.numeric -1"
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias -1"
- assert_equal [r config get unprefix-string] "unprefix-string {secret unprefix}"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias {secret unprefix}"
- assert_equal [r config get unprefix-enum] "unprefix-enum one"
- assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one"
-
-
- r module unload moduleconfigs
- }
-
- test {test loadex functionality} {
- r module loadex $testmodule CONFIG moduleconfigs.mutable_bool no \
- CONFIG moduleconfigs.immutable_bool yes \
- CONFIG moduleconfigs.memory_numeric 2mb \
- CONFIG moduleconfigs.string tclortickle \
- CONFIG unprefix-bool no \
- CONFIG unprefix.numeric-alias 123 \
- CONFIG unprefix-string abc_def \
-
- assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
- assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool yes"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string tclortickle"
- # Configs that were not changed should still be their module specified value
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
-
- # Check un-prefixed and aliased configuration
- assert_equal [r config get unprefix-bool] "unprefix-bool no"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias no"
- assert_equal [r config get unprefix.numeric] "unprefix.numeric 123"
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 123"
- assert_equal [r config get unprefix-string] "unprefix-string abc_def"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias abc_def"
- assert_equal [r config get unprefix-enum] "unprefix-enum one"
- assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one"
-
-
- }
-
- test {apply function works} {
- catch {[r config set moduleconfigs.mutable_bool yes]} e
- assert_match {*Bool configs*} $e
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
- catch {[r config set moduleconfigs.memory_numeric 1000 moduleconfigs.numeric 1000]} e
- assert_match {*cannot equal*} $e
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
- r module unload moduleconfigs
- }
-
- test {test double config argument to loadex} {
- r module loadex $testmodule CONFIG moduleconfigs.mutable_bool yes \
- CONFIG moduleconfigs.mutable_bool no \
- CONFIG unprefix.numeric-alias 1 \
- CONFIG unprefix.numeric-alias 2 \
- CONFIG unprefix-string blabla
-
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
- # Check un-prefixed and aliased configuration
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 2"
- assert_equal [r config get unprefix.numeric] "unprefix.numeric 2"
- assert_equal [r config get unprefix-string] "unprefix-string blabla"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias blabla"
- r module unload moduleconfigs
- }
-
- test {missing loadconfigs call} {
- catch {[r module loadex $testmodule CONFIG moduleconfigs.string "cool" ARGS noload]} e
- assert_match {*ERR*} $e
- }
-
- test {test loadex rejects bad configs} {
- # Bad config 200gb is over the limit
- catch {[r module loadex $testmodule CONFIG moduleconfigs.memory_numeric 200gb ARGS]} e
- assert_match {*ERR*} $e
- # We should completely remove all configs on a failed load
- assert_equal [r config get moduleconfigs.*] ""
- # No value for config, should error out
- catch {[r module loadex $testmodule CONFIG moduleconfigs.mutable_bool CONFIG moduleconfigs.enum two ARGS]} e
- assert_match {*ERR*} $e
- assert_equal [r config get moduleconfigs.*] ""
- # Asan will catch this if this string is not freed
- catch {[r module loadex $testmodule CONFIG moduleconfigs.string rejectisfreed]}
- assert_match {*ERR*} $e
- assert_equal [r config get moduleconfigs.*] ""
- # test we can't set random configs
- catch {[r module loadex $testmodule CONFIG maxclients 333]}
- assert_match {*ERR*} $e
- assert_equal [r config get moduleconfigs.*] ""
- assert_not_equal [r config get maxclients] "maxclients 333"
- # test we can't set other module's configs
- r module load $testmoduletwo
- catch {[r module loadex $testmodule CONFIG configs.test no]}
- assert_match {*ERR*} $e
- assert_equal [r config get configs.test] "configs.test yes"
- r module unload configs
- # Verify config name and its alias being used together gets failed
- catch {[r module loadex $testmodule CONFIG unprefix.numeric 1 CONFIG unprefix.numeric-alias 1]}
- assert_match {*ERR*} $e
- }
-
- test {test config rewrite with dynamic load} {
- #translates to: super \0secret password
- r module loadex $testmodule CONFIG moduleconfigs.string \x73\x75\x70\x65\x72\x20\x00\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64 ARGS
- assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
- r config set moduleconfigs.mutable_bool yes
- r config set moduleconfigs.memory_numeric 750
- r config set moduleconfigs.enum two
- r config set moduleconfigs.flags "four two"
- r config set unprefix-bool-alias no
- r config set unprefix.numeric 456
- r config set unprefix.string-alias "unprefix"
- r config set unprefix-enum two
- r config rewrite
- restart_server 0 true false
- # Ensure configs we rewrote are present and that the conf file is readable
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 750"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
-
- # Check unprefixed configuration and alias
- assert_equal [r config get unprefix-bool] "unprefix-bool no"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias no"
- assert_equal [r config get unprefix.numeric] "unprefix.numeric 456"
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias 456"
- assert_equal [r config get unprefix-string] "unprefix-string unprefix"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias unprefix"
- assert_equal [r config get unprefix-enum] "unprefix-enum two"
- assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias two"
-
- r module unload moduleconfigs
- }
-
- test {test multiple modules with configs} {
- r module load $testmodule
- r module loadex $testmoduletwo CONFIG configs.test yes
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
- assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
- assert_equal [r config get configs.test] "configs.test yes"
- r config set moduleconfigs.mutable_bool no
- r config set moduleconfigs.string nice
- r config set moduleconfigs.enum two
- r config set configs.test no
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string nice"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
- assert_equal [r config get configs.test] "configs.test no"
- r config rewrite
- # test we can load from conf file with multiple different modules.
- restart_server 0 true false
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string nice"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
- assert_equal [r config get configs.test] "configs.test no"
- r module unload moduleconfigs
- r module unload configs
- }
-
- test {test 1.module load 2.config rewrite 3.module unload 4.config rewrite works} {
- # Configs need to be removed from the old config file in this case.
- r module loadex $testmodule CONFIG moduleconfigs.memory_numeric 500 ARGS
- assert_not_equal [lsearch [lmap x [r module list] {dict get $x name}] moduleconfigs] -1
- r config rewrite
- r module unload moduleconfigs
- r config rewrite
- restart_server 0 true false
- # Ensure configs we rewrote are no longer present
- assert_equal [r config get moduleconfigs.*] ""
- }
- test {startup moduleconfigs} {
- # No loadmodule directive
- catch {exec src/redis-server --moduleconfigs.string "hello"} err
- assert_match {*Module Configuration detected without loadmodule directive or no ApplyConfig call: aborting*} $err
-
- # Bad config value
- catch {exec src/redis-server --loadmodule "$testmodule" --moduleconfigs.string "rejectisfreed"} err
- assert_match {*Issue during loading of configuration moduleconfigs.string : Cannot set string to 'rejectisfreed'*} $err
-
- # missing LoadConfigs call
- catch {exec src/redis-server --loadmodule "$testmodule" noload --moduleconfigs.string "hello"} err
- assert_match {*Module Configurations were not set, missing LoadConfigs call. Unloading the module.*} $err
-
- # successful
- start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two moduleconfigs.flags "two four"] tags {"external:skip"}] {
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string bootedup"
- assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
- assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no"
- assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
- assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two four}"
- assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
-
- # Check un-prefixed and aliased configuration
- assert_equal [r config get unprefix-bool] "unprefix-bool yes"
- assert_equal [r config get unprefix-bool-alias] "unprefix-bool-alias yes"
- assert_equal [r config get unprefix.numeric] "unprefix.numeric -1"
- assert_equal [r config get unprefix.numeric-alias] "unprefix.numeric-alias -1"
- assert_equal [r config get unprefix-string] "unprefix-string {secret unprefix}"
- assert_equal [r config get unprefix.string-alias] "unprefix.string-alias {secret unprefix}"
- assert_equal [r config get unprefix-enum] "unprefix-enum one"
- assert_equal [r config get unprefix-enum-alias] "unprefix-enum-alias one"
- }
- }
-
- test {loadmodule CONFIG values take precedence over module loadex ARGS values} {
- # Load module with conflicting CONFIG and ARGS values
- r module loadex $testmodule \
- CONFIG moduleconfigs.string goo \
- CONFIG moduleconfigs.memory_numeric 2mb \
- ARGS override-default
-
- # Verify CONFIG values took precedence over the values that override-default would have caused the module to set
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string goo"
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152"
-
- r module unload moduleconfigs
- }
-
- # Test: Ensure that modified configuration values from ARGS are correctly written to the config file
- test {Modified ARGS values are persisted after config rewrite when set through CONFIG commands} {
- # Load module with non-default ARGS values
- r module loadex $testmodule ARGS override-default
-
- # Verify the initial values were overwritten
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 123"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string foo"
-
- # Set new values to simulate user configuration changes
- r config set moduleconfigs.memory_numeric 1mb
- r config set moduleconfigs.string "modified_value"
-
- # Verify that the changes took effect
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string modified_value"
-
- # Perform a config rewrite
- r config rewrite
-
- restart_server 0 true false
- assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576"
- assert_equal [r config get moduleconfigs.string] "moduleconfigs.string modified_value"
- r module unload moduleconfigs
- }
-}
-
diff --git a/examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl b/examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl
deleted file mode 100644
index 31a4669..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/postnotifications.tcl
+++ /dev/null
@@ -1,219 +0,0 @@
-set testmodule [file normalize tests/modules/postnotifications.so]
-
-tags "modules external:skip" {
- start_server {} {
- r module load $testmodule with_key_events
-
- test {Test write on post notification callback} {
- set repl [attach_to_replication_stream]
-
- r set string_x 1
- assert_equal {1} [r get string_changed{string_x}]
- assert_equal {1} [r get string_total]
-
- r set string_x 2
- assert_equal {2} [r get string_changed{string_x}]
- assert_equal {2} [r get string_total]
-
- # the {lpush before_overwritten string_x} is a post notification job registered when 'string_x' was overwritten
- assert_replication_stream $repl {
- {multi}
- {select *}
- {set string_x 1}
- {incr string_changed{string_x}}
- {incr string_total}
- {exec}
- {multi}
- {set string_x 2}
- {lpush before_overwritten string_x}
- {incr string_changed{string_x}}
- {incr string_total}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {Test write on post notification callback from module thread} {
- r flushall
- set repl [attach_to_replication_stream]
-
- assert_equal {OK} [r postnotification.async_set]
- assert_equal {1} [r get string_changed{string_x}]
- assert_equal {1} [r get string_total]
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {set string_x 1}
- {incr string_changed{string_x}}
- {incr string_total}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {Test active expire} {
- r flushall
- set repl [attach_to_replication_stream]
-
- r set x 1
- r pexpire x 10
-
- wait_for_condition 100 50 {
- [r keys expired] == {expired}
- } else {
- puts [r keys *]
- fail "Failed waiting for x to expired"
- }
-
- # the {lpush before_expired x} is a post notification job registered before 'x' got expired
- assert_replication_stream $repl {
- {select *}
- {set x 1}
- {pexpireat x *}
- {multi}
- {del x}
- {lpush before_expired x}
- {incr expired}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {Test lazy expire} {
- r flushall
- r DEBUG SET-ACTIVE-EXPIRE 0
- set repl [attach_to_replication_stream]
-
- r set x 1
- r pexpire x 1
- after 10
- assert_equal {} [r get x]
-
- # the {lpush before_expired x} is a post notification job registered before 'x' got expired
- assert_replication_stream $repl {
- {select *}
- {set x 1}
- {pexpireat x *}
- {multi}
- {del x}
- {lpush before_expired x}
- {incr expired}
- {exec}
- }
- close_replication_stream $repl
- r DEBUG SET-ACTIVE-EXPIRE 1
- } {OK} {needs:debug}
-
- test {Test lazy expire inside post job notification} {
- r flushall
- r DEBUG SET-ACTIVE-EXPIRE 0
- set repl [attach_to_replication_stream]
-
- r set x 1
- r pexpire x 1
- after 10
- assert_equal {OK} [r set read_x 1]
-
- # the {lpush before_expired x} is a post notification job registered before 'x' got expired
- assert_replication_stream $repl {
- {select *}
- {set x 1}
- {pexpireat x *}
- {multi}
- {set read_x 1}
- {del x}
- {lpush before_expired x}
- {incr expired}
- {exec}
- }
- close_replication_stream $repl
- r DEBUG SET-ACTIVE-EXPIRE 1
- } {OK} {needs:debug}
-
- test {Test nested keyspace notification} {
- r flushall
- set repl [attach_to_replication_stream]
-
- assert_equal {OK} [r set write_sync_write_sync_x 1]
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {set x 1}
- {set write_sync_x 1}
- {set write_sync_write_sync_x 1}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {Test eviction} {
- r flushall
- set repl [attach_to_replication_stream]
- r set x 1
- r config set maxmemory-policy allkeys-random
- r config set maxmemory 1
-
- assert_error {OOM *} {r set y 1}
-
- # the {lpush before_evicted x} is a post notification job registered before 'x' got evicted
- assert_replication_stream $repl {
- {select *}
- {set x 1}
- {multi}
- {del x}
- {lpush before_evicted x}
- {incr evicted}
- {exec}
- }
- close_replication_stream $repl
- } {} {needs:config-maxmemory}
- }
-}
-
-set testmodule2 [file normalize tests/modules/keyspace_events.so]
-
-tags "modules external:skip" {
- start_server {} {
- r module load $testmodule with_key_events
- r module load $testmodule2
- test {Test write on post notification callback} {
- set repl [attach_to_replication_stream]
-
- r set string_x 1
- assert_equal {1} [r get string_changed{string_x}]
- assert_equal {1} [r get string_total]
-
- r set string_x 2
- assert_equal {2} [r get string_changed{string_x}]
- assert_equal {2} [r get string_total]
-
- r set string1_x 1
- assert_equal {1} [r get string_changed{string1_x}]
- assert_equal {3} [r get string_total]
-
- # the {lpush before_overwritten string_x} is a post notification job registered before 'string_x' got overwritten
- assert_replication_stream $repl {
- {multi}
- {select *}
- {set string_x 1}
- {incr string_changed{string_x}}
- {incr string_total}
- {exec}
- {multi}
- {set string_x 2}
- {lpush before_overwritten string_x}
- {incr string_changed{string_x}}
- {incr string_total}
- {exec}
- {multi}
- {set string1_x 1}
- {incr string_changed{string1_x}}
- {incr string_total}
- {exec}
- }
- close_replication_stream $repl
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/propagate.tcl b/examples/redis-unstable/tests/unit/moduleapi/propagate.tcl
deleted file mode 100644
index d3d08d9..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/propagate.tcl
+++ /dev/null
@@ -1,806 +0,0 @@
-set testmodule [file normalize tests/modules/propagate.so]
-set miscmodule [file normalize tests/modules/misc.so]
-set keyspace_events [file normalize tests/modules/keyspace_events.so]
-
-tags "modules external:skip" {
- test {Modules can propagate in async and threaded contexts} {
- start_server [list overrides [list loadmodule "$testmodule"]] {
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
- $replica module load $keyspace_events
- start_server [list overrides [list loadmodule "$testmodule"]] {
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
- $master module load $keyspace_events
-
- # Start the replication process...
- $replica replicaof $master_host $master_port
- wait_for_sync $replica
- after 1000
-
- test {module propagates from timer} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.timer
-
- wait_for_condition 500 10 {
- [$replica get timer] eq "3"
- } else {
- fail "The two counters don't match the expected value."
- }
-
- assert_replication_stream $repl {
- {select *}
- {incr timer}
- {incr timer}
- {incr timer}
- }
- close_replication_stream $repl
- }
-
- test {module propagation with notifications} {
- set repl [attach_to_replication_stream]
-
- $master set x y
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {set x y}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagation with notifications with multi} {
- set repl [attach_to_replication_stream]
-
- $master multi
- $master set x1 y1
- $master set x2 y2
- $master exec
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {set x1 y1}
- {incr notifications}
- {set x2 y2}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagation with notifications with active-expire} {
- $master debug set-active-expire 1
- set repl [attach_to_replication_stream]
-
- $master set asdf1 1 PX 300
- $master set asdf2 2 PX 300
- $master set asdf3 3 PX 300
-
- wait_for_condition 500 10 {
- [$replica keys asdf*] eq {}
- } else {
- fail "Not all keys have expired"
- }
-
- # Note whenever there's double notification: SET with PX issues two separate
- # notifications: one for "set" and one for "expire"
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {incr notifications}
- {set asdf1 1 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {set asdf2 2 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {set asdf3 3 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {incr testkeyspace:expired}
- {del asdf*}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {incr testkeyspace:expired}
- {del asdf*}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {incr testkeyspace:expired}
- {del asdf*}
- {exec}
- }
- close_replication_stream $repl
-
- $master debug set-active-expire 0
- }
-
- test {module propagation with notifications with eviction case 1} {
- $master flushall
- $master set asdf1 1
- $master set asdf2 2
- $master set asdf3 3
-
- $master config set maxmemory-policy allkeys-random
- $master config set maxmemory 1
-
- # Please note the following loop:
- # We evict a key and send a notification, which does INCR on the "notifications" key, so
- # that every time we evict any key, "notifications" key exist (it happens inside the
- # performEvictions loop). So even evicting "notifications" causes INCR on "notifications".
- # If maxmemory_eviction_tenacity would have been set to 100 this would be an endless loop, but
- # since the default is 10, at some point the performEvictions loop would end.
- # Bottom line: "notifications" always exists and we can't really determine the order of evictions
- # This test is here only for sanity
-
- # The replica will get the notification with multi exec and we have a generic notification handler
- # that performs `RedisModule_Call(ctx, "INCR", "c", "multi");` if the notification is inside multi exec.
- # so we will have 2 keys, "notifications" and "multi".
- wait_for_condition 500 10 {
- [$replica dbsize] eq 2
- } else {
- fail "Not all keys have been evicted"
- }
-
- $master config set maxmemory 0
- $master config set maxmemory-policy noeviction
- }
-
- test {module propagation with notifications with eviction case 2} {
- $master flushall
- set repl [attach_to_replication_stream]
-
- $master set asdf1 1 EX 300
- $master set asdf2 2 EX 300
- $master set asdf3 3 EX 300
-
- # Please note we use volatile eviction to prevent the loop described in the test above.
- # "notifications" is not volatile so it always remains
- $master config resetstat
- $master config set maxmemory-policy volatile-ttl
- $master config set maxmemory 1
-
- wait_for_condition 500 10 {
- [s evicted_keys] eq 3
- } else {
- fail "Not all keys have been evicted"
- }
-
- $master config set maxmemory 0
- $master config set maxmemory-policy noeviction
-
- $master set asdf4 4
-
- # Note whenever there's double notification: SET with EX issues two separate
- # notifications: one for "set" and one for "expire"
- # Note that although CONFIG SET maxmemory is called in this flow (see issue #10014),
- # eviction will happen and will not induce propagation of the CONFIG command (see #10019).
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {incr notifications}
- {set asdf1 1 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {set asdf2 2 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {set asdf3 3 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {del asdf*}
- {exec}
- {multi}
- {incr notifications}
- {del asdf*}
- {exec}
- {multi}
- {incr notifications}
- {del asdf*}
- {exec}
- {multi}
- {incr notifications}
- {set asdf4 4}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagation with timer and CONFIG SET maxmemory} {
- set repl [attach_to_replication_stream]
-
- $master config resetstat
- $master config set maxmemory-policy volatile-random
-
- $master propagate-test.timer-maxmemory
-
- # Wait until the volatile keys are evicted
- wait_for_condition 500 10 {
- [s evicted_keys] eq 2
- } else {
- fail "Not all keys have been evicted"
- }
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {incr notifications}
- {set timer-maxmemory-volatile-start 1 PXAT *}
- {incr timer-maxmemory-middle}
- {incr notifications}
- {incr notifications}
- {set timer-maxmemory-volatile-end 1 PXAT *}
- {exec}
- {multi}
- {incr notifications}
- {del timer-maxmemory-volatile-*}
- {exec}
- {multi}
- {incr notifications}
- {del timer-maxmemory-volatile-*}
- {exec}
- }
- close_replication_stream $repl
-
- $master config set maxmemory 0
- $master config set maxmemory-policy noeviction
- }
-
- test {module propagation with timer and EVAL} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.timer-eval
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {incrby timer-eval-start 1}
- {incr notifications}
- {set foo bar}
- {incr timer-eval-middle}
- {incr notifications}
- {incrby timer-eval-end 1}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates nested ctx case1} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.timer-nested
-
- wait_for_condition 500 10 {
- [$replica get timer-nested-end] eq "1"
- } else {
- fail "The two counters don't match the expected value."
- }
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incrby timer-nested-start 1}
- {incrby timer-nested-end 1}
- {exec}
- }
- close_replication_stream $repl
-
- # Note propagate-test.timer-nested just propagates INCRBY, causing an
- # inconsistency, so we flush
- $master flushall
- }
-
- test {module propagates nested ctx case2} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.timer-nested-repl
-
- wait_for_condition 500 10 {
- [$replica get timer-nested-end] eq "1"
- } else {
- fail "The two counters don't match the expected value."
- }
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incrby timer-nested-start 1}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr counter-3}
- {incr counter-4}
- {incr notifications}
- {incr after-call}
- {incr notifications}
- {incr before-call-2}
- {incr notifications}
- {incr asdf}
- {incr notifications}
- {del asdf}
- {incr notifications}
- {incr after-call-2}
- {incr notifications}
- {incr timer-nested-middle}
- {incrby timer-nested-end 1}
- {exec}
- }
- close_replication_stream $repl
-
- # Note propagate-test.timer-nested-repl just propagates INCRBY, causing an
- # inconsistency, so we flush
- $master flushall
- }
-
- test {module propagates from thread} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.thread
-
- wait_for_condition 500 10 {
- [$replica get a-from-thread] eq "3"
- } else {
- fail "The two counters don't match the expected value."
- }
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr a-from-thread}
- {incr notifications}
- {incr thread-call}
- {incr b-from-thread}
- {exec}
- {multi}
- {incr a-from-thread}
- {incr notifications}
- {incr thread-call}
- {incr b-from-thread}
- {exec}
- {multi}
- {incr a-from-thread}
- {incr notifications}
- {incr thread-call}
- {incr b-from-thread}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates from thread with detached ctx} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.detached-thread
-
- wait_for_condition 500 10 {
- [$replica get thread-detached-after] eq "1"
- } else {
- fail "The key doesn't match the expected value."
- }
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr thread-detached-before}
- {incr notifications}
- {incr thread-detached-1}
- {incr notifications}
- {incr thread-detached-2}
- {incr thread-detached-after}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates from command} {
- set repl [attach_to_replication_stream]
-
- $master propagate-test.simple
- $master propagate-test.mixed
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr counter-1}
- {incr counter-2}
- {exec}
- {multi}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {incr after-call}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates from EVAL} {
- set repl [attach_to_replication_stream]
-
- assert_equal [ $master eval { \
- redis.call("propagate-test.simple"); \
- redis.call("set", "x", "y"); \
- redis.call("propagate-test.mixed"); return "OK" } 0 ] {OK}
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {set x y}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {incr after-call}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates from command after good EVAL} {
- set repl [attach_to_replication_stream]
-
- assert_equal [ $master eval { return "hello" } 0 ] {hello}
- $master propagate-test.simple
- $master propagate-test.mixed
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr counter-1}
- {incr counter-2}
- {exec}
- {multi}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {incr after-call}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates from command after bad EVAL} {
- set repl [attach_to_replication_stream]
-
- catch { $master eval { return "hello" } -12 } e
- assert_equal $e {ERR Number of keys can't be negative}
- $master propagate-test.simple
- $master propagate-test.mixed
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr counter-1}
- {incr counter-2}
- {exec}
- {multi}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {incr after-call}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module propagates from multi-exec} {
- set repl [attach_to_replication_stream]
-
- $master multi
- $master propagate-test.simple
- $master propagate-test.mixed
- $master propagate-test.timer-nested-repl
- $master exec
-
- wait_for_condition 500 10 {
- [$replica get timer-nested-end] eq "1"
- } else {
- fail "The two counters don't match the expected value."
- }
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr notifications}
- {incr after-call}
- {exec}
- {multi}
- {incrby timer-nested-start 1}
- {incr notifications}
- {incr using-call}
- {incr counter-1}
- {incr counter-2}
- {incr counter-3}
- {incr counter-4}
- {incr notifications}
- {incr after-call}
- {incr notifications}
- {incr before-call-2}
- {incr notifications}
- {incr asdf}
- {incr notifications}
- {del asdf}
- {incr notifications}
- {incr after-call-2}
- {incr notifications}
- {incr timer-nested-middle}
- {incrby timer-nested-end 1}
- {exec}
- }
- close_replication_stream $repl
-
- # Note propagate-test.timer-nested just propagates INCRBY, causing an
- # inconsistency, so we flush
- $master flushall
- }
-
- test {module RM_Call of expired key propagation} {
- $master debug set-active-expire 0
-
- $master set k1 900 px 100
- after 110
-
- set repl [attach_to_replication_stream]
- $master propagate-test.incr k1
-
- assert_replication_stream $repl {
- {multi}
- {select *}
- {del k1}
- {propagate-test.incr k1}
- {exec}
- }
- close_replication_stream $repl
-
- assert_equal [$master get k1] 1
- assert_equal [$master ttl k1] -1
-
- wait_for_condition 50 100 {
- [$replica get k1] eq 1 &&
- [$replica ttl k1] eq -1
- } else {
- fail "failed RM_Call of expired key propagation"
- }
- }
-
- test {module notification on set} {
- set repl [attach_to_replication_stream]
-
- $master SADD s foo
-
- wait_for_condition 500 10 {
- [$replica SCARD s] eq "1"
- } else {
- fail "Failed to wait for set to be replicated"
- }
-
- $master SPOP s 1
-
- wait_for_condition 500 10 {
- [$replica SCARD s] eq "0"
- } else {
- fail "Failed to wait for set to be replicated"
- }
-
- # Currently the `del` command comes after the notification.
- # When we fix spop to fire notification at the end (like all other commands),
- # the `del` will come first.
- assert_replication_stream $repl {
- {multi}
- {select *}
- {incr notifications}
- {sadd s foo}
- {exec}
- {multi}
- {incr notifications}
- {incr notifications}
- {del s}
- {exec}
- }
- close_replication_stream $repl
- }
-
- test {module key miss notification do not cause read command to be replicated} {
- set repl [attach_to_replication_stream]
-
- $master flushall
-
- $master get unexisting_key
-
- wait_for_condition 500 10 {
- [$replica get missed] eq "1"
- } else {
- fail "Failed to wait for set to be replicated"
- }
-
- # Test is checking a wrong!!! behavior that causes a read command to be replicated to replica/aof.
- # We keep the test to verify that such a wrong behavior does not cause any crashes.
- assert_replication_stream $repl {
- {select *}
- {flushall}
- {multi}
- {incr notifications}
- {incr missed}
- {get unexisting_key}
- {exec}
- }
-
- close_replication_stream $repl
- }
-
- test "Unload the module - propagate-test/testkeyspace" {
- assert_equal {OK} [r module unload propagate-test]
- assert_equal {OK} [r module unload testkeyspace]
- }
-
- assert_equal [s -1 unexpected_error_replies] 0
- }
- }
- }
-}
-
-
-tags "modules aof external:skip" {
- foreach aofload_type {debug_cmd startup} {
- test "Modules RM_Replicate replicates MULTI/EXEC correctly: AOF-load type $aofload_type" {
- start_server [list overrides [list loadmodule "$testmodule"]] {
- # Enable the AOF
- r config set appendonly yes
- r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
- waitForBgrewriteaof r
-
- r propagate-test.simple
- r propagate-test.mixed
- r multi
- r propagate-test.simple
- r propagate-test.mixed
- r exec
-
- assert_equal [r get counter-1] {}
- assert_equal [r get counter-2] {}
- assert_equal [r get using-call] 2
- assert_equal [r get after-call] 2
- assert_equal [r get notifications] 4
-
- # Load the AOF
- if {$aofload_type == "debug_cmd"} {
- r debug loadaof
- } else {
- r config rewrite
- restart_server 0 true false
- wait_done_loading r
- }
-
- # This module behaves bad on purpose, it only calls
- # RM_Replicate for counter-1 and counter-2 so values
- # after AOF-load are different
- assert_equal [r get counter-1] 4
- assert_equal [r get counter-2] 4
- assert_equal [r get using-call] 2
- assert_equal [r get after-call] 2
- # 4+4+2+2 commands from AOF (just above) + 4 "INCR notifications" from AOF + 4 notifications for these INCRs
- assert_equal [r get notifications] 20
-
- assert_equal {OK} [r module unload propagate-test]
- assert_equal [s 0 unexpected_error_replies] 0
- }
- }
- test "Modules RM_Call does not update stats during aof load: AOF-load type $aofload_type" {
- start_server [list overrides [list loadmodule "$miscmodule"]] {
- # Enable the AOF
- r config set appendonly yes
- r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
- waitForBgrewriteaof r
-
- r config resetstat
- r set foo bar
- r EVAL {return redis.call('SET', KEYS[1], ARGV[1])} 1 foo bar2
- r test.rm_call_replicate set foo bar3
- r EVAL {return redis.call('test.rm_call_replicate',ARGV[1],KEYS[1],ARGV[2])} 1 foo set bar4
-
- r multi
- r set foo bar5
- r EVAL {return redis.call('SET', KEYS[1], ARGV[1])} 1 foo bar6
- r test.rm_call_replicate set foo bar7
- r EVAL {return redis.call('test.rm_call_replicate',ARGV[1],KEYS[1],ARGV[2])} 1 foo set bar8
- r exec
-
- assert_match {*calls=8,*,rejected_calls=0,failed_calls=0} [cmdrstat set r]
-
-
- # Load the AOF
- if {$aofload_type == "debug_cmd"} {
- r config resetstat
- r debug loadaof
- } else {
- r config rewrite
- restart_server 0 true false
- wait_done_loading r
- }
-
- assert_no_match {*calls=*} [cmdrstat set r]
-
- }
- }
- }
-}
-
-# This test does not really test module functionality, but rather uses a module
-# command to test Redis replication mechanisms.
-test {Replicas that was marked as CLIENT_CLOSE_ASAP should not keep the replication backlog from been trimmed} {
- start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] {
- set replica [srv 0 client]
- start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] {
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
- $master config set client-output-buffer-limit "replica 10mb 5mb 0"
-
- # Start the replication process...
- $replica replicaof $master_host $master_port
- wait_for_sync $replica
-
- test {module propagates from timer} {
- # Replicate large commands to make the replica disconnected.
- $master write [format_command propagate-test.verbatim 100000 [string repeat "a" 1000]] ;# almost 100mb
- # Execute this command together with module commands within the same
- # event loop to prevent periodic cleanup of replication backlog.
- $master write [format_command info memory]
- $master flush
- $master read ;# propagate-test.verbatim
- set res [$master read] ;# info memory
-
- # Wait for the replica to be disconnected.
- wait_for_log_messages 0 {"*flags=S*scheduled to be closed ASAP for overcoming of output buffer limits*"} 0 1500 10
- # Due to the replica reaching the soft limit (5MB), memory peaks should not significantly
- # exceed the replica soft limit. Furthermore, as the replica release its reference to
- # replication backlog, it should be properly trimmed, the memory usage of replication
- # backlog should not significantly exceed repl-backlog-size (default 1MB). */
- assert_lessthan [getInfoProperty $res used_memory_peak] 20000000;# less than 20mb
- assert_lessthan [getInfoProperty $res mem_replication_backlog] 2000000;# less than 2mb
- }
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/publish.tcl b/examples/redis-unstable/tests/unit/moduleapi/publish.tcl
deleted file mode 100644
index ef56b98..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/publish.tcl
+++ /dev/null
@@ -1,34 +0,0 @@
-set testmodule [file normalize tests/modules/publish.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {PUBLISH and SPUBLISH via a module} {
- set rd1 [redis_deferring_client]
- set rd2 [redis_deferring_client]
-
- assert_equal {1} [ssubscribe $rd1 {chan1}]
- assert_equal {1} [subscribe $rd2 {chan1}]
- assert_equal 1 [r publish.shard chan1 hello]
- assert_equal 1 [r publish.classic chan1 world]
- assert_equal {smessage chan1 hello} [$rd1 read]
- assert_equal {message chan1 world} [$rd2 read]
- $rd1 close
- $rd2 close
- }
-
- test {module publish to self with multi message} {
- r hello 3
- r subscribe foo
-
- # published message comes after the response of the command that issued it.
- assert_equal [r publish.classic_multi foo bar vaz] {1 1}
- assert_equal [r read] {message foo bar}
- assert_equal [r read] {message foo vaz}
-
- r unsubscribe foo
- r hello 2
- set _ ""
- } {} {resp3}
-
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl b/examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl
deleted file mode 100644
index ba2cfd6..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/rdbloadsave.tcl
+++ /dev/null
@@ -1,203 +0,0 @@
-set testmodule [file normalize tests/modules/rdbloadsave.so]
-
-start_server {tags {"modules external:skip debug_defrag:skip"}} {
- r module load $testmodule
-
- test "Module rdbloadsave sanity" {
- r test.sanity
-
- # Try to load non-existing file
- assert_error {*No such file or directory*} {r test.rdbload sanity.rdb}
-
- r set x 1
- assert_equal OK [r test.rdbsave sanity.rdb]
-
- r flushdb
- assert_equal OK [r test.rdbload sanity.rdb]
- assert_equal 1 [r get x]
- }
-
- test "Module rdbloadsave test with pipelining" {
- r config set save ""
- r config set loading-process-events-interval-bytes 1024
- r config set key-load-delay 50
- r flushdb
-
- populate 3000 a 1024
- r set x 111
- assert_equal [r dbsize] 3001
-
- assert_equal OK [r test.rdbsave blabla.rdb]
- r flushdb
- assert_equal [r dbsize] 0
-
- # Send commands with pipeline. First command will call RM_RdbLoad() in
- # the command callback. While loading RDB, Redis can go to networking to
- # reply -LOADING. By sending commands in pipeline, we verify it doesn't
- # cause a problem.
- # e.g. Redis won't try to process next message of the current client
- # while it is in the command callback for that client .
- set rd1 [redis_deferring_client]
- $rd1 test.rdbload blabla.rdb
-
- wait_for_condition 50 100 {
- [s loading] eq 1
- } else {
- fail "Redis did not start loading or loaded RDB too fast"
- }
-
- $rd1 get x
- $rd1 dbsize
-
- assert_equal OK [$rd1 read]
- assert_equal 111 [$rd1 read]
- assert_equal 3001 [$rd1 read]
- r flushdb
- r config set key-load-delay 0
- }
-
- test "Module rdbloadsave with aof" {
- r config set save ""
-
- # Enable the AOF
- r config set appendonly yes
- r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
- waitForBgrewriteaof r
-
- r set k v1
- assert_equal OK [r test.rdbsave aoftest.rdb]
-
- r set k v2
- r config set rdb-key-save-delay 10000000
- r bgrewriteaof
-
- # RM_RdbLoad() should kill aof fork
- assert_equal OK [r test.rdbload aoftest.rdb]
-
- wait_for_condition 50 100 {
- [string match {*Killing*AOF*child*} [exec tail -20 < [srv 0 stdout]]]
- } else {
- fail "Can't find 'Killing AOF child' in recent log lines"
- }
-
- # Verify the value in the loaded rdb
- assert_equal v1 [r get k]
-
- # Verify aof is still enabled after RM_RdbLoad() call
- assert_equal 1 [s aof_enabled]
-
- r flushdb
- r config set rdb-key-save-delay 0
- r config set appendonly no
- }
-
- test "Module rdbloadsave with bgsave" {
- r flushdb
- r config set save ""
-
- r set k v1
- assert_equal OK [r test.rdbsave bgsave.rdb]
-
- r set k v2
- r config set rdb-key-save-delay 500000
- r bgsave
-
- # RM_RdbLoad() should kill RDB fork
- assert_equal OK [r test.rdbload bgsave.rdb]
-
- wait_for_condition 10 1000 {
- [string match {*Background*saving*terminated*} [exec tail -20 < [srv 0 stdout]]]
- } else {
- fail "Can't find 'Background saving terminated' in recent log lines"
- }
-
- assert_equal v1 [r get k]
- r flushall
- waitForBgsave r
- r config set rdb-key-save-delay 0
- }
-
- test "Module rdbloadsave calls rdbsave in a module fork" {
- r flushdb
- r config set save ""
- r config set rdb-key-save-delay 500000
-
- r set k v1
-
- # Module will call RM_Fork() before calling RM_RdbSave()
- assert_equal OK [r test.rdbsave_fork rdbfork.rdb]
- assert_equal [s module_fork_in_progress] 1
-
- wait_for_condition 10 1000 {
- [status r module_fork_in_progress] == "0"
- } else {
- fail "Module fork didn't finish"
- }
-
- r set k v2
- assert_equal OK [r test.rdbload rdbfork.rdb]
- assert_equal v1 [r get k]
-
- r config set rdb-key-save-delay 0
- }
-
- test "Unload the module - rdbloadsave" {
- assert_equal {OK} [r module unload rdbloadsave]
- }
-
- tags {repl} {
- test {Module rdbloadsave on master and replica} {
- start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] {
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
- start_server [list overrides [list loadmodule "$testmodule"] tags {"external:skip"}] {
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
-
- $master set x 10000
-
- # Start the replication process...
- $replica replicaof $master_host $master_port
-
- wait_for_condition 100 100 {
- [status $master sync_full] == 1
- } else {
- fail "Master <-> Replica didn't start the full sync"
- }
-
- # RM_RdbSave() is allowed on replicas
- assert_equal OK [$replica test.rdbsave rep.rdb]
-
- # RM_RdbLoad() is not allowed on replicas
- assert_error {*supported*} {$replica test.rdbload rep.rdb}
-
- assert_equal OK [$master test.rdbsave master.rdb]
- $master set x 20000
-
- wait_for_condition 100 100 {
- [$replica get x] == 20000
- } else {
- fail "Replica didn't get the update"
- }
-
- # Loading RDB on master will drop replicas
- assert_equal OK [$master test.rdbload master.rdb]
-
- wait_for_condition 100 100 {
- [status $master sync_full] == 2
- } else {
- fail "Master <-> Replica didn't start the full sync"
- }
-
- wait_for_condition 100 100 {
- [$replica get x] == 10000
- } else {
- fail "Replica didn't get the update"
- }
- }
- }
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/reply.tcl b/examples/redis-unstable/tests/unit/moduleapi/reply.tcl
deleted file mode 100644
index af012f2..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/reply.tcl
+++ /dev/null
@@ -1,152 +0,0 @@
-set testmodule [file normalize tests/modules/reply.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- # test all with hello 2/3
- for {set proto 2} {$proto <= 3} {incr proto} {
- if {[lsearch $::denytags "resp3"] >= 0} {
- if {$proto == 3} {continue}
- } elseif {$::force_resp3} {
- if {$proto == 2} {continue}
- }
- r hello $proto
-
- test "RESP$proto: RM_ReplyWithString: an string reply" {
- # RedisString
- set string [r rw.string "Redis"]
- assert_equal "Redis" $string
- # C string
- set string [r rw.cstring]
- assert_equal "A simple string" $string
- }
-
- test "RESP$proto: RM_ReplyWithBigNumber: an string reply" {
- assert_equal "123456778901234567890" [r rw.bignumber "123456778901234567890"]
- }
-
- test "RESP$proto: RM_ReplyWithInt: an integer reply" {
- assert_equal 42 [r rw.int 42]
- }
-
- test "RESP$proto: RM_ReplyWithDouble: a float reply" {
- assert_equal 3.141 [r rw.double 3.141]
- }
-
- test "RESP$proto: RM_ReplyWithDouble: inf" {
- if {$proto == 2} {
- assert_equal "inf" [r rw.double inf]
- assert_equal "-inf" [r rw.double -inf]
- } else {
- # TCL convert inf to different results on different platforms, e.g. inf on mac
- # and Inf on others, so use readraw to verify the protocol
- r readraw 1
- assert_equal ",inf" [r rw.double inf]
- assert_equal ",-inf" [r rw.double -inf]
- r readraw 0
- }
- }
-
- test "RESP$proto: RM_ReplyWithDouble: NaN" {
- if {$proto == 2} {
- assert_equal "nan" [r rw.double 0 0]
- assert_equal "nan" [r rw.double]
- } else {
- # TCL won't convert nan into a double, use readraw to verify the protocol
- r readraw 1
- assert_equal ",nan" [r rw.double 0 0]
- assert_equal ",nan" [r rw.double]
- r readraw 0
- }
- }
-
- set ld 0.00000000000000001
- test "RESP$proto: RM_ReplyWithLongDouble: a float reply" {
- if {$proto == 2} {
- # here the response gets to TCL as a string
- assert_equal $ld [r rw.longdouble $ld]
- } else {
- # TCL doesn't support long double and the test infra converts it to a
- # normal double which causes precision loss. so we use readraw instead
- r readraw 1
- assert_equal ",$ld" [r rw.longdouble $ld]
- r readraw 0
- }
- }
-
- test "RESP$proto: RM_ReplyWithVerbatimString: a string reply" {
- assert_equal "bla\nbla\nbla" [r rw.verbatim "bla\nbla\nbla"]
- }
-
- test "RESP$proto: RM_ReplyWithArray: an array reply" {
- assert_equal {0 1 2 3 4} [r rw.array 5]
- }
-
- test "RESP$proto: RM_ReplyWithMap: an map reply" {
- set res [r rw.map 3]
- if {$proto == 2} {
- assert_equal {0 0 1 1.5 2 3} $res
- } else {
- assert_equal [dict create 0 0.0 1 1.5 2 3.0] $res
- }
- }
-
- test "RESP$proto: RM_ReplyWithSet: an set reply" {
- assert_equal {0 1 2} [r rw.set 3]
- }
-
- test "RESP$proto: RM_ReplyWithAttribute: an set reply" {
- if {$proto == 2} {
- catch {[r rw.attribute 3]} e
- assert_match "Attributes aren't supported by RESP 2" $e
- } else {
- r readraw 1
- set res [r rw.attribute 3]
- assert_equal [r read] {:0}
- assert_equal [r read] {,0}
- assert_equal [r read] {:1}
- assert_equal [r read] {,1.5}
- assert_equal [r read] {:2}
- assert_equal [r read] {,3}
- assert_equal [r read] {+OK}
- r readraw 0
- }
- }
-
- test "RESP$proto: RM_ReplyWithBool: a boolean reply" {
- assert_equal {0 1} [r rw.bool]
- }
-
- test "RESP$proto: RM_ReplyWithNull: a NULL reply" {
- assert_equal {} [r rw.null]
- }
-
- test "RESP$proto: RM_ReplyWithError: an error reply" {
- catch {r rw.error} e
- assert_match "An error" $e
- }
-
- test "RESP$proto: RM_ReplyWithErrorFormat: error format reply" {
- catch {r rw.error_format "An error: %s" foo} e
- assert_match "An error: foo" $e ;# Should not be used by a user, but compatible with RM_ReplyError
-
- catch {r rw.error_format "-ERR An error: %s" foo2} e
- assert_match "-ERR An error: foo2" $e ;# Should not be used by a user, but compatible with RM_ReplyError (There are two hyphens, TCL removes the first one)
-
- catch {r rw.error_format "-WRONGTYPE A type error: %s" foo3} e
- assert_match "-WRONGTYPE A type error: foo3" $e ;# Should not be used by a user, but compatible with RM_ReplyError (There are two hyphens, TCL removes the first one)
-
- catch {r rw.error_format "ERR An error: %s" foo4} e
- assert_match "ERR An error: foo4" $e
-
- catch {r rw.error_format "WRONGTYPE A type error: %s" foo5} e
- assert_match "WRONGTYPE A type error: foo5" $e
- }
-
- r hello 2
- }
-
- test "Unload the module - replywith" {
- assert_equal {OK} [r module unload replywith]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/scan.tcl b/examples/redis-unstable/tests/unit/moduleapi/scan.tcl
deleted file mode 100644
index c880477..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/scan.tcl
+++ /dev/null
@@ -1,103 +0,0 @@
-set testmodule [file normalize tests/modules/scan.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Module scan keyspace} {
- # the module create a scan command with filtering which also return values
- r set x 1
- r set y 2
- r set z 3
- r hset h f v
- lsort [r scan.scan_strings]
- } {{x 1} {y 2} {z 3}}
-
- test {Module scan hash listpack} {
- r hmset hh f1 v1 f2 v2
- assert_encoding listpack hh
- lsort [r scan.scan_key hh]
- } {{f1 v1} {f2 v2}}
-
- test {Module scan hash listpack with int value} {
- r hmset hh1 f1 1
- assert_encoding listpack hh1
- lsort [r scan.scan_key hh1]
- } {{f1 1}}
-
- test {Module scan hash listpack with hexpire} {
- r debug set-active-expire 0
- r hmset hh f1 v1 f2 v2 f3 v3
- r hexpire hh 100000 fields 1 f1
- r hpexpire hh 1 fields 1 f3
- after 10
- assert_range [r httl hh fields 1 f1] 10000 100000
- assert_encoding listpackex hh
- r debug set-active-expire 1
- lsort [r scan.scan_key hh]
- } {{f1 v1} {f2 v2}} {needs:debug}
-
- test {Module scan hash dict} {
- r config set hash-max-ziplist-entries 2
- r hmset hh f3 v3
- assert_encoding hashtable hh
- lsort [r scan.scan_key hh]
- } {{f1 v1} {f2 v2} {f3 v3}}
-
- test {Module scan hash dict with hexpire} {
- r config set hash-max-listpack-entries 1
- r del hh
- r hmset hh f1 v1 f2 v2 f3 v3
- r hexpire hh 100000 fields 1 f2
- r hpexpire hh 5 fields 1 f3
- assert_range [r httl hh fields 1 f2] 10000 100000
- assert_encoding hashtable hh
- after 10
- lsort [r scan.scan_key hh]
- } {{f1 v1} {f2 v2}}
-
- test {Module scan hash with hexpire can return no items} {
- r del hh
- r debug set-active-expire 0
- r hmset hh f1 v1 f2 v2 f3 v3
- r hpexpire hh 1 fields 3 f1 f2 f3
- after 10
- assert_equal [r scan.scan_key hh] {}
- r debug set-active-expire 1
- } {OK} {needs:debug}
-
- test {Module scan zset listpack} {
- r zadd zz 1 f1 2 f2
- assert_encoding listpack zz
- lsort [r scan.scan_key zz]
- } {{f1 1} {f2 2}}
-
- test {Module scan zset skiplist} {
- r config set zset-max-ziplist-entries 2
- r zadd zz 3 f3
- assert_encoding skiplist zz
- lsort [r scan.scan_key zz]
- } {{f1 1} {f2 2} {f3 3}}
-
- test {Module scan set intset} {
- r sadd ss 1 2
- assert_encoding intset ss
- lsort [r scan.scan_key ss]
- } {{1 {}} {2 {}}}
-
- test {Module scan set dict} {
- r config set set-max-intset-entries 2
- r sadd ss 3
- assert_encoding hashtable ss
- lsort [r scan.scan_key ss]
- } {{1 {}} {2 {}} {3 {}}}
-
- test {Module scan set listpack} {
- r sadd ss1 a b c
- assert_encoding listpack ss1
- lsort [r scan.scan_key ss1]
- } {{a {}} {b {}} {c {}}}
-
- test "Unload the module - scan" {
- assert_equal {OK} [r module unload scan]
- }
-} \ No newline at end of file
diff --git a/examples/redis-unstable/tests/unit/moduleapi/stream.tcl b/examples/redis-unstable/tests/unit/moduleapi/stream.tcl
deleted file mode 100644
index cc235f8..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/stream.tcl
+++ /dev/null
@@ -1,176 +0,0 @@
-set testmodule [file normalize tests/modules/stream.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Module stream add and delete} {
- r del mystream
- # add to empty key
- set streamid1 [r stream.add mystream item 1 value a]
- # add to existing stream
- set streamid2 [r stream.add mystream item 2 value b]
- # check result
- assert { [string match "*-*" $streamid1] }
- set items [r XRANGE mystream - +]
- assert_equal $items \
- "{$streamid1 {item 1 value a}} {$streamid2 {item 2 value b}}"
- # delete one of them and try deleting non-existing ID
- assert_equal OK [r stream.delete mystream $streamid1]
- assert_error "ERR StreamDelete*" {r stream.delete mystream 123-456}
- assert_error "Invalid stream ID*" {r stream.delete mystream foo}
- assert_equal "{$streamid2 {item 2 value b}}" [r XRANGE mystream - +]
- # check error condition: wrong type
- r del mystream
- r set mystream mystring
- assert_error "ERR StreamAdd*" {r stream.add mystream item 1 value a}
- assert_error "ERR StreamDelete*" {r stream.delete mystream 123-456}
- }
-
- test {Module stream add unblocks blocking xread} {
- r del mystream
-
- # Blocking XREAD on an empty key
- set rd1 [redis_deferring_client]
- $rd1 XREAD BLOCK 3000 STREAMS mystream $
- # wait until client is actually blocked
- wait_for_condition 50 100 {
- [s 0 blocked_clients] eq {1}
- } else {
- fail "Client is not blocked"
- }
- set id [r stream.add mystream field 1 value a]
- assert_equal "{mystream {{$id {field 1 value a}}}}" [$rd1 read]
-
- # Blocking XREAD on an existing stream
- set rd2 [redis_deferring_client]
- $rd2 XREAD BLOCK 3000 STREAMS mystream $
- # wait until client is actually blocked
- wait_for_condition 50 100 {
- [s 0 blocked_clients] eq {1}
- } else {
- fail "Client is not blocked"
- }
- set id [r stream.add mystream field 2 value b]
- assert_equal "{mystream {{$id {field 2 value b}}}}" [$rd2 read]
- }
-
- test {Module stream add benchmark (1M stream add)} {
- set n 1000000
- r del mystream
- set result [r stream.addn mystream $n field value]
- assert_equal $result $n
- }
-
- test {Module stream XADD big fields doesn't create empty key} {
- set original_proto [config_get_set proto-max-bulk-len 2147483647] ;#2gb
- set original_query [config_get_set client-query-buffer-limit 2147483647] ;#2gb
-
- r del mystream
- r write "*4\r\n\$10\r\nstream.add\r\n\$8\r\nmystream\r\n\$5\r\nfield\r\n"
- catch {
- write_big_bulk 1073741824 ;#1gb
- } err
- assert {$err eq "ERR StreamAdd failed"}
- assert_equal 0 [r exists mystream]
-
- # restore defaults
- r config set proto-max-bulk-len $original_proto
- r config set client-query-buffer-limit $original_query
- } {OK} {large-memory}
-
- test {Module stream iterator} {
- r del mystream
- set streamid1 [r xadd mystream * item 1 value a]
- set streamid2 [r xadd mystream * item 2 value b]
- # range result
- set result1 [r stream.range mystream "-" "+"]
- set expect1 [r xrange mystream "-" "+"]
- assert_equal $result1 $expect1
- # reverse range
- set result_rev [r stream.range mystream "+" "-"]
- set expect_rev [r xrevrange mystream "+" "-"]
- assert_equal $result_rev $expect_rev
-
- # only one item: range with startid = endid
- set result2 [r stream.range mystream "-" $streamid1]
- assert_equal $result2 "{$streamid1 {item 1 value a}}"
- assert_equal $result2 [list [list $streamid1 {item 1 value a}]]
- # only one item: range with startid = endid
- set result3 [r stream.range mystream $streamid2 $streamid2]
- assert_equal $result3 "{$streamid2 {item 2 value b}}"
- assert_equal $result3 [list [list $streamid2 {item 2 value b}]]
- }
-
- test {Module stream iterator delete} {
- r del mystream
- set id1 [r xadd mystream * normal item]
- set id2 [r xadd mystream * selfdestruct yes]
- set id3 [r xadd mystream * another item]
- # stream.range deletes the "selfdestruct" item after returning it
- assert_equal \
- "{$id1 {normal item}} {$id2 {selfdestruct yes}} {$id3 {another item}}" \
- [r stream.range mystream - +]
- # now, the "selfdestruct" item is gone
- assert_equal \
- "{$id1 {normal item}} {$id3 {another item}}" \
- [r stream.range mystream - +]
- }
-
- test {Module stream trim by length} {
- r del mystream
- # exact maxlen
- r xadd mystream * item 1 value a
- r xadd mystream * item 2 value b
- r xadd mystream * item 3 value c
- assert_equal 3 [r xlen mystream]
- assert_equal 0 [r stream.trim mystream maxlen = 5]
- assert_equal 3 [r xlen mystream]
- assert_equal 2 [r stream.trim mystream maxlen = 1]
- assert_equal 1 [r xlen mystream]
- assert_equal 1 [r stream.trim mystream maxlen = 0]
- # check that there is no limit for exact maxlen
- r stream.addn mystream 20000 item x value y
- assert_equal 20000 [r stream.trim mystream maxlen = 0]
- # approx maxlen (100 items per node implies default limit 10K items)
- r stream.addn mystream 20000 item x value y
- assert_equal 20000 [r xlen mystream]
- assert_equal 10000 [r stream.trim mystream maxlen ~ 2]
- assert_equal 9900 [r stream.trim mystream maxlen ~ 2]
- assert_equal 0 [r stream.trim mystream maxlen ~ 2]
- assert_equal 100 [r xlen mystream]
- assert_equal 100 [r stream.trim mystream maxlen ~ 0]
- assert_equal 0 [r xlen mystream]
- }
-
- test {Module stream trim by ID} {
- r del mystream
- # exact minid
- r xadd mystream * item 1 value a
- r xadd mystream * item 2 value b
- set minid [r xadd mystream * item 3 value c]
- assert_equal 3 [r xlen mystream]
- assert_equal 0 [r stream.trim mystream minid = -]
- assert_equal 3 [r xlen mystream]
- assert_equal 2 [r stream.trim mystream minid = $minid]
- assert_equal 1 [r xlen mystream]
- assert_equal 1 [r stream.trim mystream minid = +]
- # check that there is no limit for exact minid
- r stream.addn mystream 20000 item x value y
- assert_equal 20000 [r stream.trim mystream minid = +]
- # approx minid (100 items per node implies default limit 10K items)
- r stream.addn mystream 19980 item x value y
- set minid [r xadd mystream * item x value y]
- r stream.addn mystream 19 item x value y
- assert_equal 20000 [r xlen mystream]
- assert_equal 10000 [r stream.trim mystream minid ~ $minid]
- assert_equal 9900 [r stream.trim mystream minid ~ $minid]
- assert_equal 0 [r stream.trim mystream minid ~ $minid]
- assert_equal 100 [r xlen mystream]
- assert_equal 100 [r stream.trim mystream minid ~ +]
- assert_equal 0 [r xlen mystream]
- }
-
- test "Unload the module - stream" {
- assert_equal {OK} [r module unload stream]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl b/examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl
deleted file mode 100644
index f3734a8..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/subcommands.tcl
+++ /dev/null
@@ -1,61 +0,0 @@
-set testmodule [file normalize tests/modules/subcommands.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test "Module subcommands via COMMAND" {
- # Verify that module subcommands are displayed correctly in COMMAND
- set command_reply [r command info subcommands.bitarray]
- set first_cmd [lindex $command_reply 0]
- set subcmds_in_command [lsort [lindex $first_cmd 9]]
- 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}}}} {}}
-
- # 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"] {group module module subcommands}
- assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {group module module subcommands}
- }
-
- test "Module pure-container command fails on arity error" {
- catch {r subcommands.bitarray} e
- assert_match {*wrong number of arguments for 'subcommands.bitarray' command} $e
-
- # Subcommands can be called
- assert_equal [r subcommands.bitarray get k1] {OK}
-
- # Subcommand arity error
- catch {r subcommands.bitarray get k1 8 90} e
- assert_match {*wrong number of arguments for 'subcommands.bitarray|get' command} $e
- }
-
- test "Module get current command fullname" {
- assert_equal [r subcommands.parent_get_fullname] {subcommands.parent_get_fullname}
- }
-
- test "Module get current subcommand fullname" {
- assert_equal [r subcommands.sub get_fullname] {subcommands.sub|get_fullname}
- }
-
- test "COMMAND LIST FILTERBY MODULE" {
- assert_equal {} [r command list filterby module non_existing]
-
- set commands [r command list filterby module subcommands]
- assert_not_equal [lsearch $commands "subcommands.bitarray"] -1
- assert_not_equal [lsearch $commands "subcommands.bitarray|set"] -1
- assert_not_equal [lsearch $commands "subcommands.parent_get_fullname"] -1
- assert_not_equal [lsearch $commands "subcommands.sub|get_fullname"] -1
-
- assert_equal [lsearch $commands "set"] -1
- }
-
- test "Internal container command without subcommand returns missing subcommand error" {
- assert_error {*missing subcommand*} {r subcommands.internal_container}
- }
-
- test "Unload the module - subcommands" {
- assert_equal {OK} [r module unload subcommands]
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl b/examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl
deleted file mode 100644
index 4c50b65..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/test_lazyfree.tcl
+++ /dev/null
@@ -1,32 +0,0 @@
-set testmodule [file normalize tests/modules/test_lazyfree.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test "modules allocated memory can be reclaimed in the background" {
- set orig_mem [s used_memory]
- set rd [redis_deferring_client]
-
- # LAZYFREE_THRESHOLD is 64
- for {set i 0} {$i < 10000} {incr i} {
- $rd lazyfreelink.insert lazykey $i
- }
-
- for {set j 0} {$j < 10000} {incr j} {
- $rd read
- }
-
- assert {[r lazyfreelink.len lazykey] == 10000}
-
- set peak_mem [s used_memory]
- assert {[r unlink lazykey] == 1}
- assert {$peak_mem > $orig_mem+10000}
- wait_for_condition 50 100 {
- [s used_memory] < $peak_mem &&
- [s used_memory] < $orig_mem*2 &&
- [string match {*lazyfreed_objects:1*} [r info Memory]]
- } else {
- fail "Module memory is not reclaimed by UNLINK"
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl b/examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl
deleted file mode 100644
index e44cf42..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/testrdb.tcl
+++ /dev/null
@@ -1,311 +0,0 @@
-# This module can be configure with multiple options given as flags on module load time
-# 0 - not aux fields will be declared (this is the default)
-# 1 << 0 - use aux_save2 api
-# 1 << 1 - call aux callback before key space
-# 1 << 2 - call aux callback after key space
-# 1 << 3 - do not save data on aux callback
-set testmodule [file normalize tests/modules/testrdb.so]
-
-tags "modules external:skip" {
- test {modules are able to persist types} {
- start_server [list overrides [list loadmodule "$testmodule"]] {
- r testrdb.set.key key1 value1
- assert_equal "value1" [r testrdb.get.key key1]
- r debug reload
- assert_equal "value1" [r testrdb.get.key key1]
- }
- }
-
- test {modules global are lost without aux} {
- set server_path [tmpdir "server.module-testrdb"]
- start_server [list overrides [list loadmodule "$testmodule" "dir" $server_path] keep_persistence true] {
- r testrdb.set.before global1
- assert_equal "global1" [r testrdb.get.before]
- }
- start_server [list overrides [list loadmodule "$testmodule" "dir" $server_path]] {
- assert_equal "" [r testrdb.get.before]
- }
- }
-
- test {aux that saves no data are not saved to the rdb when aux_save2 is used} {
- set server_path [tmpdir "server.module-testrdb"]
- puts $server_path
- # 15 == 1111 - use aux_save2 before and after key space without data
- start_server [list overrides [list loadmodule "$testmodule 15" "dir" $server_path] keep_persistence true] {
- r set x 1
- r save
- }
- start_server [list overrides [list "dir" $server_path] keep_persistence true] {
- # make sure server started successfully without the module.
- assert_equal {1} [r get x]
- }
- }
-
- test {aux that saves no data are saved to the rdb when aux_save is used} {
- set server_path [tmpdir "server.module-testrdb"]
- puts $server_path
- # 14 == 1110 - use aux_save before and after key space without data
- start_server [list overrides [list loadmodule "$testmodule 14" "dir" $server_path] keep_persistence true] {
- r set x 1
- r save
- }
- start_server [list overrides [list loadmodule "$testmodule 14" "dir" $server_path] keep_persistence true] {
- # make sure server started successfully and aux_save was called twice.
- assert_equal {1} [r get x]
- assert_equal {2} [r testrdb.get.n_aux_load_called]
- }
- }
-
- foreach test_case {6 7} {
- # 6 == 0110 - use aux_save before and after key space with data
- # 7 == 0111 - use aux_save2 before and after key space with data
- test {modules are able to persist globals before and after} {
- set server_path [tmpdir "server.module-testrdb"]
- start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"] keep_persistence true] {
- r testrdb.set.before global1
- r testrdb.set.after global2
- assert_equal "global1" [r testrdb.get.before]
- assert_equal "global2" [r testrdb.get.after]
- }
- start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"]] {
- assert_equal "global1" [r testrdb.get.before]
- assert_equal "global2" [r testrdb.get.after]
- }
-
- }
- }
-
- foreach test_case {4 5} {
- # 4 == 0100 - use aux_save after key space with data
- # 5 == 0101 - use aux_save2 after key space with data
- test {modules are able to persist globals just after} {
- set server_path [tmpdir "server.module-testrdb"]
- start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"] keep_persistence true] {
- r testrdb.set.after global2
- assert_equal "global2" [r testrdb.get.after]
- }
- start_server [list overrides [list loadmodule "$testmodule $test_case" "dir" $server_path "save" "900 1"]] {
- assert_equal "global2" [r testrdb.get.after]
- }
- }
- }
-
- 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"]] {
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
- start_server [list overrides [list loadmodule "$testmodule"]] {
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
-
- # Set master and replica to use diskless replication
- $master config set repl-diskless-sync yes
- $master config set rdbcompression no
- $replica config set repl-diskless-load swapdb
- $master config set hz 500
- $replica config set hz 500
- $master config set dynamic-hz no
- $replica config set dynamic-hz no
- set start [clock clicks -milliseconds]
- # Generate small keys
- for {set k 0} {$k < 20000} {incr k} {
- r testrdb.set.key keysmall$k [string repeat A [expr {int(rand()*100)}]]
- }
- # Generate larger keys
- for {set k 0} {$k < 30} {incr k} {
- r testrdb.set.key key$k [string repeat A [expr {int(rand()*1000000)}]]
- }
-
- if {$::verbose} {
- set end [clock clicks -milliseconds]
- set duration [expr $end - $start]
- puts "filling took $duration ms (TODO: use pipeline)"
- set start [clock clicks -milliseconds]
- }
-
- # Start the replication process...
- set loglines [count_log_lines -1]
- $master config set repl-diskless-sync-delay 0
- $replica replicaof $master_host $master_port
-
- # kill the replication at various points
- set attempts 100
- if {$::accurate} { set attempts 500 }
- for {set i 0} {$i < $attempts} {incr i} {
- # wait for the replica to start reading the rdb
- # using the log file since the replica only responds to INFO once in 2mb
- set res [wait_for_log_messages -1 {"*Loading DB in memory*"} $loglines 2000 1]
- set loglines [lindex $res 1]
-
- # add some additional random sleep so that we kill the master on a different place each time
- after [expr {int(rand()*50)}]
-
- # kill the replica connection on the master
- set killed [$master client kill type replica]
-
- set res [wait_for_log_messages -1 {"*Internal error in RDB*" "*Finished with success*" "*Successful partial resynchronization*"} $loglines 500 10]
- if {$::verbose} { puts $res }
- set log_text [lindex $res 0]
- set loglines [lindex $res 1]
- if {![string match "*Internal error in RDB*" $log_text]} {
- # force the replica to try another full sync
- $master multi
- $master client kill type replica
- $master set asdf asdf
- # fill replication backlog with new content
- $master config set repl-backlog-size 16384
- for {set keyid 0} {$keyid < 10} {incr keyid} {
- $master set "$keyid string_$keyid" [string repeat A 16384]
- }
- $master exec
- }
-
- # wait for loading to stop (fail)
- # After a loading successfully, next loop will enter `async_loading`
- wait_for_condition 1000 1 {
- [s -1 async_loading] eq 0 &&
- [s -1 loading] eq 0
- } else {
- fail "Replica didn't disconnect"
- }
- }
- if {$::verbose} {
- set end [clock clicks -milliseconds]
- set duration [expr $end - $start]
- puts "test took $duration ms"
- }
- # enable fast shutdown
- $master config set rdb-key-save-delay 0
- }
- }
- }
-
- # Module events for diskless load swapdb when async_loading (matching master replid)
- foreach test_case {6 7} {
- # 6 == 0110 - use aux_save before and after key space with data
- # 7 == 0111 - use aux_save2 before and after key space with data
- foreach testType {Successful Aborted} {
- start_server [list overrides [list loadmodule "$testmodule $test_case"] tags [list external:skip]] {
- set replica [srv 0 client]
- set replica_host [srv 0 host]
- set replica_port [srv 0 port]
- set replica_log [srv 0 stdout]
- start_server [list overrides [list loadmodule "$testmodule $test_case"]] {
- set master [srv 0 client]
- set master_host [srv 0 host]
- set master_port [srv 0 port]
-
- set start [clock clicks -milliseconds]
-
- # Set master and replica to use diskless replication on swapdb mode
- $master config set repl-diskless-sync yes
- $master config set repl-diskless-sync-delay 0
- $master config set save ""
- $replica config set repl-diskless-load swapdb
- $replica config set save ""
-
- # Initial sync to have matching replids between master and replica
- $replica replicaof $master_host $master_port
-
- # Let replica finish initial sync with master
- wait_for_condition 100 100 {
- [s -1 master_link_status] eq "up"
- } else {
- fail "Master <-> Replica didn't finish sync"
- }
-
- # Set global values on module so we can check if module event callbacks will pick it up correctly
- $master testrdb.set.before value1_master
- $replica testrdb.set.before value1_replica
-
- # Put different data sets on the master and replica
- # We need to put large keys on the master since the replica replies to info only once in 2mb
- $replica debug populate 200 slave 10
- $master debug populate 1000 master 100000
- $master config set rdbcompression no
-
- # Force the replica to try another full sync (this time it will have matching master replid)
- $master multi
- $master client kill type replica
- # Fill replication backlog with new content
- $master config set repl-backlog-size 16384
- for {set keyid 0} {$keyid < 10} {incr keyid} {
- $master set "$keyid string_$keyid" [string repeat A 16384]
- }
- $master exec
-
- switch $testType {
- "Aborted" {
- # Set master with a slow rdb generation, so that we can easily intercept loading
- # 10ms per key, with 1000 keys is 10 seconds
- $master config set rdb-key-save-delay 10000
-
- test {Diskless load swapdb RedisModuleEvent_ReplAsyncLoad handling: during loading, can keep module variable same as before} {
- # Wait for the replica to start reading the rdb and module for acknowledgement
- # We wanna abort only after the temp db was populated by REDISMODULE_AUX_BEFORE_RDB
- wait_for_condition 100 100 {
- [s -1 async_loading] eq 1 && [$replica testrdb.async_loading.get.before] eq "value1_master"
- } else {
- fail "Module didn't receive or react to REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED"
- }
-
- assert_equal [$replica dbsize] 200
- assert_equal value1_replica [$replica testrdb.get.before]
- }
-
- # Make sure that next sync will not start immediately so that we can catch the replica in between syncs
- $master config set repl-diskless-sync-delay 5
-
- # Kill the replica connection on the master
- set killed [$master client kill type replica]
-
- test {Diskless load swapdb RedisModuleEvent_ReplAsyncLoad handling: when loading aborted, can keep module variable same as before} {
- # Wait for loading to stop (fail) and module for acknowledgement
- wait_for_condition 100 100 {
- [s -1 async_loading] eq 0 && [$replica testrdb.async_loading.get.before] eq ""
- } else {
- fail "Module didn't receive or react to REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED"
- }
-
- assert_equal [$replica dbsize] 200
- assert_equal value1_replica [$replica testrdb.get.before]
- }
-
- # Speed up shutdown
- $master config set rdb-key-save-delay 0
- }
- "Successful" {
- # Let replica finish sync with master
- wait_for_condition 100 100 {
- [s -1 master_link_status] eq "up"
- } else {
- fail "Master <-> Replica didn't finish sync"
- }
-
- test {Diskless load swapdb RedisModuleEvent_ReplAsyncLoad handling: after db loaded, can set module variable with new value} {
- assert_equal [$replica dbsize] 1010
- assert_equal value1_master [$replica testrdb.get.before]
- }
- }
- }
-
- if {$::verbose} {
- set end [clock clicks -milliseconds]
- set duration [expr $end - $start]
- puts "test took $duration ms"
- }
- }
- }
- }
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/timer.tcl b/examples/redis-unstable/tests/unit/moduleapi/timer.tcl
deleted file mode 100644
index 1e83888..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/timer.tcl
+++ /dev/null
@@ -1,99 +0,0 @@
-set testmodule [file normalize tests/modules/timer.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {RM_CreateTimer: a sequence of timers work} {
- # We can't guarantee same-ms but we try using MULTI/EXEC
- r multi
- for {set i 0} {$i < 20} {incr i} {
- r test.createtimer 10 timer-incr-key
- }
- r exec
-
- after 500
- assert_equal 20 [r get timer-incr-key]
- }
-
- test {RM_GetTimer: basic sanity} {
- # Getting non-existing timer
- assert_equal {} [r test.gettimer 0]
-
- # Getting a real timer
- set id [r test.createtimer 10000 timer-incr-key]
- set info [r test.gettimer $id]
-
- 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} {
- r set "timer-incr-key" 0
- set id [r test.createtimer 1000 timer-incr-key]
-
- assert_equal 1 [r test.stoptimer $id]
-
- # Wait to be sure timer doesn't execute
- after 2000
- assert_equal 0 [r get timer-incr-key]
-
- # Stop non-existing timer
- assert_equal 0 [r test.stoptimer $id]
- }
-
- test {Timer appears non-existing after it fires} {
- r set "timer-incr-key" 0
- set id [r test.createtimer 10 timer-incr-key]
-
- # verify timer fired
- after 500
- assert_equal 1 [r get timer-incr-key]
-
- # verify id does not exist
- assert_equal {} [r test.gettimer $id]
- }
-
- 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]
- }
-}
-
diff --git a/examples/redis-unstable/tests/unit/moduleapi/usercall.tcl b/examples/redis-unstable/tests/unit/moduleapi/usercall.tcl
deleted file mode 100644
index 16b4ff1..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/usercall.tcl
+++ /dev/null
@@ -1,186 +0,0 @@
-set testmodule [file normalize tests/modules/usercall.so]
-
-set test_script_set "#!lua
-redis.call('set','x',1)
-return 1"
-
-set test_script_get "#!lua
-redis.call('get','x')
-return 1"
-
-start_server {tags {"modules usercall external:skip"}} {
- r module load $testmodule
-
- # baseline test that module isn't doing anything weird
- test {test module check regular redis command without user/acl} {
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
- assert_equal [r usercall.call_without_user set x 5] OK
- assert_equal [r usercall.reset_user] OK
- }
-
- # call with user with acl set on it, but without testing the acl
- test {test module check regular redis command with user} {
- assert_equal [r set x 5] OK
-
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
- # off and sanitize-payload because module user / default value
- assert_equal [r usercall.get_acl] "off sanitize-payload ~* &* +@all -set"
-
- # doesn't fail for regular commands as just testing acl here
- assert_equal [r usercall.call_with_user_flag {} set x 10] OK
-
- assert_equal [r get x] 10
- assert_equal [r usercall.reset_user] OK
- }
-
- # call with user with acl set on it, but with testing the acl in rm_call (for cmd itself)
- test {test module check regular redis command with user and acl} {
- assert_equal [r set x 5] OK
-
- r ACL LOG RESET
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
- # off and sanitize-payload because module user / default value
- assert_equal [r usercall.get_acl] "off sanitize-payload ~* &* +@all -set"
-
- # fails here as testing acl in rm call
- assert_error {*NOPERM User module_user has no permissions*} {r usercall.call_with_user_flag C set x 10}
-
- assert_equal [r usercall.call_with_user_flag C get x] 5
-
- # verify that new log entry added
- set entry [lindex [r ACL LOG] 0]
- assert_equal [dict get $entry username] {module_user}
- assert_equal [dict get $entry context] {module}
- assert_equal [dict get $entry object] {set}
- assert_equal [dict get $entry reason] {command}
- assert_match {*cmd=usercall.call_with_user_flag*} [dict get $entry client-info]
-
- assert_equal [r usercall.reset_user] OK
- }
-
- # call with user with acl set on it, but with testing the acl in rm_call (for cmd itself)
- test {test module check regular redis command with user and acl from blocked background thread} {
- assert_equal [r set x 5] OK
-
- r ACL LOG RESET
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
-
- # fails here as testing acl in rm call from a background thread
- assert_error {*NOPERM User module_user has no permissions*} {r usercall.call_with_user_bg C set x 10}
-
- assert_equal [r usercall.call_with_user_bg C get x] 5
-
- # verify that new log entry added
- set entry [lindex [r ACL LOG] 0]
- assert_equal [dict get $entry username] {module_user}
- assert_equal [dict get $entry context] {module}
- assert_equal [dict get $entry object] {set}
- assert_equal [dict get $entry reason] {command}
- assert_match {*cmd=NULL*} [dict get $entry client-info]
-
- assert_equal [r usercall.reset_user] OK
- }
-
- # baseline script test, call without user on script
- test {test module check eval script without user} {
- set sha_set [r script load $test_script_set]
- set sha_get [r script load $test_script_get]
-
- assert_equal [r usercall.call_without_user evalsha $sha_set 0] 1
- assert_equal [r usercall.call_without_user evalsha $sha_get 0] 1
- }
-
- # baseline script test, call without user on script
- test {test module check eval script with user being set, but not acl testing} {
- set sha_set [r script load $test_script_set]
- set sha_get [r script load $test_script_get]
-
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
- # off and sanitize-payload because module user / default value
- assert_equal [r usercall.get_acl] "off sanitize-payload ~* &* +@all -set"
-
- # passes as not checking ACL
- assert_equal [r usercall.call_with_user_flag {} evalsha $sha_set 0] 1
- assert_equal [r usercall.call_with_user_flag {} evalsha $sha_get 0] 1
- }
-
- # call with user on script (without rm_call acl check) to ensure user carries through to script execution
- # we already tested the check in rm_call above, here we are checking the script itself will enforce ACL
- test {test module check eval script with user and acl} {
- set sha_set [r script load $test_script_set]
- set sha_get [r script load $test_script_get]
-
- r ACL LOG RESET
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
-
- # fails here in script, as rm_call will permit the eval call
- catch {r usercall.call_with_user_flag C evalsha $sha_set 0} e
- assert_match {*ERR ACL failure in script*} $e
-
- assert_equal [r usercall.call_with_user_flag C evalsha $sha_get 0] 1
-
- # verify that new log entry added
- set entry [lindex [r ACL LOG] 0]
- assert_equal [dict get $entry username] {module_user}
- assert_equal [dict get $entry context] {lua}
- assert_equal [dict get $entry object] {set}
- assert_equal [dict get $entry reason] {command}
- assert_match {*cmd=usercall.call_with_user_flag*} [dict get $entry client-info]
- }
-
- test {server not crashing when MONITOR is fed from spawned thread} {
- set rd [redis_deferring_client]
- $rd monitor
-
- r ACL LOG RESET
- assert_equal [r usercall.reset_user] OK
- assert_equal [r usercall.add_to_acl "~* &* +@all -set"] OK
-
- r flushdb
- r set x x
-
- # This is enough. This checks that we don't crash inside
- # updateClientMemUsageAndBucket
- assert_equal x [r usercall.call_with_user_bg C get x]
-
- $rd close
- }
-
- start_server {tags {"wait aof network external:skip"}} {
- set slave [srv 0 client]
- set slave_host [srv 0 host]
- set slave_port [srv 0 port]
- set slave_pid [srv 0 pid]
- set master [srv -1 client]
- set master_host [srv -1 host]
- set master_port [srv -1 port]
-
- $master config set appendonly yes
- $master config set appendfsync everysec
- $slave config set appendonly yes
- $slave config set appendfsync everysec
-
- test {Setup slave} {
- $slave slaveof $master_host $master_port
- wait_for_condition 50 100 {
- [s 0 master_link_status] eq {up}
- } else {
- fail "Replication not started."
- }
- }
-
- test {test module replicate only to replicas and WAITAOF} {
- $master set x 1
- assert_equal [$master waitaof 1 1 10000] {1 1}
- $master usercall.call_with_user_flag A! config set loglevel notice
- # Make sure WAITAOF doesn't hang
- assert_equal [$master waitaof 1 1 10000] {1 1}
- }
- }
-}
diff --git a/examples/redis-unstable/tests/unit/moduleapi/zset.tcl b/examples/redis-unstable/tests/unit/moduleapi/zset.tcl
deleted file mode 100644
index f2a324c..0000000
--- a/examples/redis-unstable/tests/unit/moduleapi/zset.tcl
+++ /dev/null
@@ -1,121 +0,0 @@
-set testmodule [file normalize tests/modules/zset.so]
-
-start_server {tags {"modules external:skip"}} {
- r module load $testmodule
-
- test {Module zset rem} {
- r del k
- r zadd k 100 hello 200 world
- assert_equal 1 [r zset.rem k hello]
- assert_equal 0 [r zset.rem k hello]
- assert_equal 1 [r exists k]
- # Check that removing the last element deletes the key
- assert_equal 1 [r zset.rem k world]
- assert_equal 0 [r exists k]
- }
-
- test {Module zset add} {
- r del k
- # Check that failure does not create empty key
- assert_error "ERR ZsetAdd failed" {r zset.add k nan hello}
- assert_equal 0 [r exists k]
-
- r zset.add k 100 hello
- assert_equal {hello 100} [r zrange k 0 -1 withscores]
- }
-
- test {Module zset incrby} {
- r del k
- # Check that failure does not create empty key
- assert_error "ERR ZsetIncrby failed" {r zset.incrby k hello nan}
- assert_equal 0 [r exists k]
-
- r zset.incrby k hello 100
- assert_equal {hello 100} [r zrange k 0 -1 withscores]
- }
-
- test {Module zset - KEYSIZES is updated as expected (like test at hash.tcl)} {
- proc run_cmd_verify_hist {cmd expOutput {retries 1}} {
- proc K {} {return [string map { "db0_distrib_zsets_items" "db0_ZSET" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]}
- uplevel 1 $cmd
- wait_for_condition 50 $retries {
- $expOutput eq [K]
- } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" }
- }
-
- r select 0
-
- #RedisModule_ZsetAdd, RedisModule_ZsetRem
- run_cmd_verify_hist {r FLUSHALL} {}
- run_cmd_verify_hist {r zset.add k 100 hello} {db0_ZSET:1=1}
- run_cmd_verify_hist {r zset.add k 101 bye} {db0_ZSET:2=1}
- run_cmd_verify_hist {r zset.rem k hello} {db0_ZSET:1=1}
- run_cmd_verify_hist {r zset.rem k bye} {}
-
- #RM_ZsetIncrby
- run_cmd_verify_hist {r FLUSHALL} {}
- run_cmd_verify_hist {r zset.incrby k hello 100} {db0_ZSET:1=1}
- run_cmd_verify_hist {r zset.incrby k hello 100} {db0_ZSET:1=1}
- run_cmd_verify_hist {r zset.rem k hello} {}
-
- # Check lazy expire
- r debug set-active-expire 0
- run_cmd_verify_hist {r zset.add k 100 hello} {db0_ZSET:1=1}
- run_cmd_verify_hist {r pexpire k 2} {db0_ZSET:1=1}
- run_cmd_verify_hist {after 5} {db0_ZSET:1=1}
- r debug set-active-expire 1
- run_cmd_verify_hist {after 5} {} 50
- }
-
- test {Module zset DELALL functionality} {
- # Clean up any existing keys
- r flushall
-
- # Create some zsets and other types of keys
- r zadd zset1 100 hello 200 world
- r zadd zset2 300 foo 400 bar
- r zadd zset3 500 baz
- r set string1 "value1"
- r hset hash1 field1 value1
- r lpush list1 item1
-
- # Verify we have the expected keys
- assert_equal 6 [r dbsize]
- assert_equal 3 [llength [r keys zset*]]
-
- # Run zset.delall
- set deleted [r zset.delall]
- assert_equal 3 $deleted
-
- # Verify only zsets were deleted
- assert_equal 3 [r dbsize]
- assert_equal 0 [llength [r keys zset*]]
- assert_equal 1 [r exists string1]
- assert_equal 1 [r exists hash1]
- assert_equal 1 [r exists list1]
-
- # Test with no zsets
- set deleted [r zset.delall]
- assert_equal 0 $deleted
- assert_equal 3 [r dbsize]
- }
-
- test {Module zset DELALL not in transaction} {
- set repl [attach_to_replication_stream]
- r zadd z1 1 e1
- r zadd z2 1 e1
- r zset.delall
- assert_replication_stream $repl {
- {select *}
- {zadd z1 1 e1}
- {zadd z2 1 e1}
- {del z*}
- {del z*}
- }
- close_replication_stream $repl
- } {} {needs:repl}
-
- test "Unload the module - zset" {
- assert_equal {OK} [r module unload zset]
- }
-}