summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/cluster/cluster.tcl15
-rw-r--r--tests/cluster/tests/00-base.tcl2
-rw-r--r--tests/cluster/tests/11-manual-takeover.tcl24
-rw-r--r--tests/cluster/tests/12-replica-migration-2.tcl7
-rw-r--r--tests/cluster/tests/27-endpoints.tcl18
-rw-r--r--tests/cluster/tests/28-cluster-shards.tcl185
-rw-r--r--tests/integration/redis-cli.tcl30
-rw-r--r--tests/integration/replication-4.tcl2
-rw-r--r--tests/integration/replication.tcl12
-rw-r--r--tests/modules/Makefile4
-rw-r--r--tests/modules/aclcheck.c20
-rw-r--r--tests/modules/auth.c14
-rw-r--r--tests/modules/basics.c31
-rw-r--r--tests/modules/blockedclient.c2
-rw-r--r--tests/modules/blockonkeys.c13
-rw-r--r--tests/modules/eventloop.c1
-rw-r--r--tests/modules/hooks.c15
-rw-r--r--tests/modules/moduleconfigs.c142
-rw-r--r--tests/modules/moduleconfigstwo.c39
-rw-r--r--tests/sentinel/tests/03-runtime-reconf.tcl156
-rw-r--r--tests/sentinel/tests/07-down-conditions.tcl9
-rw-r--r--tests/sentinel/tests/08-hostname-conf.tcl2
-rw-r--r--tests/sentinel/tests/09-acl-support.tcl15
-rw-r--r--tests/sentinel/tests/includes/init-tests.tcl22
-rw-r--r--tests/sentinel/tests/includes/utils.tcl22
-rw-r--r--tests/support/redis.tcl4
-rw-r--r--tests/support/server.tcl8
-rw-r--r--tests/support/util.tcl4
-rw-r--r--tests/unit/acl-v2.tcl56
-rw-r--r--tests/unit/aofrw.tcl4
-rw-r--r--tests/unit/bitops.tcl6
-rw-r--r--tests/unit/client-eviction.tcl34
-rw-r--r--tests/unit/cluster.tcl147
-rw-r--r--tests/unit/functions.tcl348
-rw-r--r--tests/unit/introspection.tcl1
-rw-r--r--tests/unit/memefficiency.tcl10
-rw-r--r--tests/unit/moduleapi/aclcheck.tcl5
-rw-r--r--tests/unit/moduleapi/auth.tcl16
-rw-r--r--tests/unit/moduleapi/blockedclient.tcl8
-rw-r--r--tests/unit/moduleapi/blockonkeys.tcl32
-rw-r--r--tests/unit/moduleapi/cluster.tcl14
-rw-r--r--tests/unit/moduleapi/hooks.tcl6
-rw-r--r--tests/unit/moduleapi/moduleconfigs.tcl234
-rw-r--r--tests/unit/other.tcl3
-rw-r--r--tests/unit/pause.tcl8
-rw-r--r--tests/unit/replybufsize.tcl8
-rw-r--r--tests/unit/scripting.tcl35
-rw-r--r--tests/unit/shutdown.tcl36
-rw-r--r--tests/unit/type/stream-cgroups.tcl107
-rw-r--r--tests/unit/type/zset.tcl8
50 files changed, 1669 insertions, 275 deletions
diff --git a/tests/cluster/cluster.tcl b/tests/cluster/cluster.tcl
index 531e90d6c..9c669e128 100644
--- a/tests/cluster/cluster.tcl
+++ b/tests/cluster/cluster.tcl
@@ -203,6 +203,21 @@ proc wait_for_cluster_propagation {} {
}
}
+# Check if cluster's view of hostnames is consistent
+proc are_hostnames_propagated {match_string} {
+ for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
+ set cfg [R $j cluster slots]
+ foreach node $cfg {
+ for {set i 2} {$i < [llength $node]} {incr i} {
+ if {! [string match $match_string [lindex [lindex [lindex $node $i] 3] 1]] } {
+ return 0
+ }
+ }
+ }
+ }
+ return 1
+}
+
# Returns a parsed CLUSTER LINKS output of the instance identified
# by the given `id` as a list of dictionaries, with each dictionary
# corresponds to a link.
diff --git a/tests/cluster/tests/00-base.tcl b/tests/cluster/tests/00-base.tcl
index 656128e53..12d8244a8 100644
--- a/tests/cluster/tests/00-base.tcl
+++ b/tests/cluster/tests/00-base.tcl
@@ -64,7 +64,7 @@ test "It is possible to write and read from the cluster" {
}
test "Function no-cluster flag" {
- R 1 function load lua test {
+ R 1 function load {#!lua name=test
redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}}
}
catch {R 1 fcall f1 0} e
diff --git a/tests/cluster/tests/11-manual-takeover.tcl b/tests/cluster/tests/11-manual-takeover.tcl
index f567c6962..78a0f858b 100644
--- a/tests/cluster/tests/11-manual-takeover.tcl
+++ b/tests/cluster/tests/11-manual-takeover.tcl
@@ -14,20 +14,32 @@ test "Cluster is writable" {
cluster_write_test 0
}
+# For this test, disable replica failover until
+# all of the primaries are confirmed killed. Otherwise
+# there might be enough time to elect a replica.
+set replica_ids { 5 6 7 }
+foreach id $replica_ids {
+ R $id config set cluster-replica-no-failover yes
+}
+
test "Killing majority of master nodes" {
kill_instance redis 0
kill_instance redis 1
kill_instance redis 2
}
+foreach id $replica_ids {
+ R $id config set cluster-replica-no-failover no
+}
+
test "Cluster should eventually be down" {
assert_cluster_state fail
}
test "Use takeover to bring slaves back" {
- R 5 cluster failover takeover
- R 6 cluster failover takeover
- R 7 cluster failover takeover
+ foreach id $replica_ids {
+ R $id cluster failover takeover
+ }
}
test "Cluster should eventually be up again" {
@@ -39,9 +51,9 @@ test "Cluster is writable" {
}
test "Instance #5, #6, #7 are now masters" {
- assert {[RI 5 role] eq {master}}
- assert {[RI 6 role] eq {master}}
- assert {[RI 7 role] eq {master}}
+ foreach id $replica_ids {
+ assert {[RI $id role] eq {master}}
+ }
}
test "Restarting the previously killed master nodes" {
diff --git a/tests/cluster/tests/12-replica-migration-2.tcl b/tests/cluster/tests/12-replica-migration-2.tcl
index f0493e57e..ed680061c 100644
--- a/tests/cluster/tests/12-replica-migration-2.tcl
+++ b/tests/cluster/tests/12-replica-migration-2.tcl
@@ -45,11 +45,12 @@ test "Resharding all the master #0 slots away from it" {
}
-test "Master #0 should lose its replicas" {
+test "Master #0 who lost all slots should turn into a replica without replicas" {
wait_for_condition 1000 50 {
- [llength [lindex [R 0 role] 2]] == 0
+ [RI 0 role] == "slave" && [RI 0 connected_slaves] == 0
} else {
- fail "Master #0 still has replicas"
+ puts [R 0 info replication]
+ fail "Master #0 didn't turn itself into a replica"
}
}
diff --git a/tests/cluster/tests/27-endpoints.tcl b/tests/cluster/tests/27-endpoints.tcl
index 4010b92ed..32e3e794d 100644
--- a/tests/cluster/tests/27-endpoints.tcl
+++ b/tests/cluster/tests/27-endpoints.tcl
@@ -1,20 +1,5 @@
source "../tests/includes/init-tests.tcl"
-# Check if cluster's view of hostnames is consistent
-proc are_hostnames_propagated {match_string} {
- for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
- set cfg [R $j cluster slots]
- foreach node $cfg {
- for {set i 2} {$i < [llength $node]} {incr i} {
- if {! [string match $match_string [lindex [lindex [lindex $node $i] 3] 1]] } {
- return 0
- }
- }
- }
- }
- return 1
-}
-
# Isolate a node from the cluster and give it a new nodeid
proc isolate_node {id} {
set node_id [R $id CLUSTER MYID]
@@ -212,6 +197,9 @@ test "Verify the nodes configured with prefer hostname only show hostname for ne
test "Test restart will keep hostname information" {
# Set a new hostname, reboot and make sure it sticks
R 0 config set cluster-announce-hostname "restart-1.com"
+ # Store the hostname in the config
+ R 0 config rewrite
+ kill_instance redis 0
restart_instance redis 0
set slot_result [R 0 CLUSTER SLOTS]
assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "restart-1.com"
diff --git a/tests/cluster/tests/28-cluster-shards.tcl b/tests/cluster/tests/28-cluster-shards.tcl
new file mode 100644
index 000000000..fe794f2b7
--- /dev/null
+++ b/tests/cluster/tests/28-cluster-shards.tcl
@@ -0,0 +1,185 @@
+source "../tests/includes/init-tests.tcl"
+
+# Initial slot distribution.
+set ::slot0 [list 0 1000 1002 5459 5461 5461 10926 10926]
+set ::slot1 [list 5460 5460 5462 10922 10925 10925]
+set ::slot2 [list 10923 10924 10927 16383]
+set ::slot3 [list 1001 1001]
+
+proc cluster_create_with_split_slots {masters replicas} {
+ for {set j 0} {$j < $masters} {incr j} {
+ R $j cluster ADDSLOTSRANGE {*}[set ::slot${j}]
+ }
+ if {$replicas} {
+ cluster_allocate_slaves $masters $replicas
+ }
+ set ::cluster_master_nodes $masters
+ set ::cluster_replica_nodes $replicas
+}
+
+# Get the node info with the specific node_id from the
+# given reference node. Valid type options are "node" and "shard"
+proc get_node_info_from_shard {id reference {type node}} {
+ set shards_response [R $reference CLUSTER SHARDS]
+ foreach shard_response $shards_response {
+ set nodes [dict get $shard_response nodes]
+ foreach node $nodes {
+ if {[dict get $node id] eq $id} {
+ if {$type eq "node"} {
+ return $node
+ } elseif {$type eq "shard"} {
+ return $shard_response
+ } else {
+ return {}
+ }
+ }
+ }
+ }
+ # No shard found, return nothing
+ return {}
+}
+
+test "Create a 8 nodes cluster with 4 shards" {
+ cluster_create_with_split_slots 4 4
+}
+
+test "Cluster should start ok" {
+ assert_cluster_state ok
+}
+
+test "Set cluster hostnames and verify they are propagated" {
+ for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
+ R $j config set cluster-announce-hostname "host-$j.com"
+ }
+
+ # Wait for everyone to agree about the state
+ wait_for_cluster_propagation
+}
+
+test "Verify information about the shards" {
+ set ids {}
+ for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
+ lappend ids [R $j CLUSTER MYID]
+ }
+ set slots [list $::slot0 $::slot1 $::slot2 $::slot3 $::slot0 $::slot1 $::slot2 $::slot3]
+
+ # Verify on each node (primary/replica), the response of the `CLUSTER SLOTS` command is consistent.
+ for {set ref 0} {$ref < $::cluster_master_nodes + $::cluster_replica_nodes} {incr ref} {
+ for {set i 0} {$i < $::cluster_master_nodes + $::cluster_replica_nodes} {incr i} {
+ assert_equal [lindex $slots $i] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "shard"] slots]
+ assert_equal "host-$i.com" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] hostname]
+ assert_equal "127.0.0.1" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] ip]
+ # Default value of 'cluster-preferred-endpoint-type' is ip.
+ assert_equal "127.0.0.1" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] endpoint]
+
+ if {$::tls} {
+ assert_equal [get_instance_attrib redis $i plaintext-port] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] port]
+ assert_equal [get_instance_attrib redis $i port] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] tls-port]
+ } else {
+ assert_equal [get_instance_attrib redis $i port] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] port]
+ }
+
+ if {$i < 4} {
+ assert_equal "master" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] role]
+ assert_equal "online" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] health]
+ } else {
+ assert_equal "replica" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] role]
+ # Replica could be in online or loading
+ }
+ }
+ }
+}
+
+test "Verify no slot shard" {
+ # Node 8 has no slots assigned
+ set node_8_id [R 8 CLUSTER MYID]
+ assert_equal {} [dict get [get_node_info_from_shard $node_8_id 8 "shard"] slots]
+ assert_equal {} [dict get [get_node_info_from_shard $node_8_id 0 "shard"] slots]
+}
+
+set node_0_id [R 0 CLUSTER MYID]
+
+test "Kill a node and tell the replica to immediately takeover" {
+ kill_instance redis 0
+ R 4 cluster failover force
+}
+
+# Primary 0 node should report as fail, wait until the new primary acknowledges it.
+test "Verify health as fail for killed node" {
+ wait_for_condition 50 100 {
+ "fail" eq [dict get [get_node_info_from_shard $node_0_id 4 "node"] "health"]
+ } else {
+ fail "New primary never detected the node failed"
+ }
+}
+
+set primary_id 4
+set replica_id 0
+
+test "Restarting primary node" {
+ restart_instance redis $replica_id
+}
+
+test "Instance #0 gets converted into a replica" {
+ wait_for_condition 1000 50 {
+ [RI $replica_id role] eq {slave}
+ } else {
+ fail "Old primary was not converted into replica"
+ }
+}
+
+test "Test the replica reports a loading state while it's loading" {
+ # Test the command is good for verifying everything moves to a happy state
+ set replica_cluster_id [R $replica_id CLUSTER MYID]
+ wait_for_condition 50 1000 {
+ [dict get [get_node_info_from_shard $replica_cluster_id $primary_id "node"] health] eq "online"
+ } else {
+ fail "Replica never transitioned to online"
+ }
+
+ # Set 1 MB of data, so there is something to load on full sync
+ R $primary_id debug populate 1000 key 1000
+
+ # Kill replica client for primary and load new data to the primary
+ R $primary_id config set repl-backlog-size 100
+
+ # Set the key load delay so that it will take at least
+ # 2 seconds to fully load the data.
+ R $replica_id config set key-load-delay 4000
+
+ # Trigger event loop processing every 1024 bytes, this trigger
+ # allows us to send and receive cluster messages, so we are setting
+ # it low so that the cluster messages are sent more frequently.
+ R $replica_id config set loading-process-events-interval-bytes 1024
+
+ R $primary_id multi
+ R $primary_id client kill type replica
+ # populate the correct data
+ set num 100
+ set value [string repeat A 1024]
+ for {set j 0} {$j < $num} {incr j} {
+ # Use hashtag valid for shard #0
+ set key "{ch3}$j"
+ R $primary_id set $key $value
+ }
+ R $primary_id exec
+
+ # The replica should reconnect and start a full sync, it will gossip about it's health to the primary.
+ wait_for_condition 50 1000 {
+ "loading" eq [dict get [get_node_info_from_shard $replica_cluster_id $primary_id "node"] health]
+ } else {
+ fail "Replica never transitioned to loading"
+ }
+
+ # Speed up the key loading and verify everything resumes
+ R $replica_id config set key-load-delay 0
+
+ wait_for_condition 50 1000 {
+ "online" eq [dict get [get_node_info_from_shard $replica_cluster_id $primary_id "node"] health]
+ } else {
+ fail "Replica never transitioned to online"
+ }
+
+ # Final sanity, the replica agrees it is online.
+ assert_equal "online" [dict get [get_node_info_from_shard $replica_cluster_id $replica_id "node"] health]
+} \ No newline at end of file
diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl
index 88b189ac0..e159fb17d 100644
--- a/tests/integration/redis-cli.tcl
+++ b/tests/integration/redis-cli.tcl
@@ -228,6 +228,30 @@ start_server {tags {"cli"}} {
file delete $tmpfile
}
+ test_tty_cli "Escape character in JSON mode" {
+ # reverse solidus
+ r hset solidus \/ \/
+ assert_equal \/ \/ [run_cli hgetall solidus]
+ set escaped_reverse_solidus \"\\"
+ assert_equal $escaped_reverse_solidus $escaped_reverse_solidus [run_cli --json hgetall \/]
+ # non printable (0xF0 in ISO-8859-1, not UTF-8(0xC3 0xB0))
+ set eth "\u00f0\u0065"
+ r hset eth test $eth
+ assert_equal \"\\xf0e\" [run_cli hget eth test]
+ assert_equal \"\u00f0e\" [run_cli --json hget eth test]
+ assert_equal \"\\\\xf0e\" [run_cli --quoted-json hget eth test]
+ # control characters
+ r hset control test "Hello\x00\x01\x02\x03World"
+ assert_equal \"Hello\\u0000\\u0001\\u0002\\u0003World" [run_cli --json hget control test]
+ # non-string keys
+ r hset numkey 1 One
+ assert_equal \{\"1\":\"One\"\} [run_cli --json hgetall numkey]
+ # non-string, non-printable keys
+ r hset npkey "K\u0000\u0001ey" "V\u0000\u0001alue"
+ assert_equal \{\"K\\u0000\\u0001ey\":\"V\\u0000\\u0001alue\"\} [run_cli --json hgetall npkey]
+ assert_equal \{\"K\\\\x00\\\\x01ey\":\"V\\\\x00\\\\x01alue\"\} [run_cli --quoted-json hgetall npkey]
+ }
+
test_nontty_cli "Status reply" {
assert_equal "OK" [run_cli set key bar]
assert_equal "bar" [r get key]
@@ -322,7 +346,7 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
set dir [lindex [r config get dir] 1]
assert_equal "OK" [r debug populate 100000 key 1000]
- assert_equal "OK" [r function load lua lib1 "redis.register_function('func1', function() return 123 end)"]
+ assert_equal "lib1" [r function load "#!lua name=lib1\nredis.register_function('func1', function() return 123 end)"]
if {$functions_only} {
set args "--functions-rdb $dir/cli.rdb"
} else {
@@ -335,10 +359,10 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
file rename "$dir/cli.rdb" "$dir/dump.rdb"
assert_equal "OK" [r set should-not-exist 1]
- assert_equal "OK" [r function load lua should_not_exist_func "redis.register_function('should_not_exist_func', function() return 456 end)"]
+ assert_equal "should_not_exist_func" [r function load "#!lua name=should_not_exist_func\nredis.register_function('should_not_exist_func', function() return 456 end)"]
assert_equal "OK" [r debug reload nosave]
assert_equal {} [r get should-not-exist]
- assert_equal {{library_name lib1 engine LUA description {} functions {{name func1 description {} flags {}}}}} [r function list]
+ assert_equal {{library_name lib1 engine LUA functions {{name func1 description {} flags {}}}}} [r function list]
if {$functions_only} {
assert_equal 0 [r dbsize]
} else {
diff --git a/tests/integration/replication-4.tcl b/tests/integration/replication-4.tcl
index b8c50308a..281d5a8eb 100644
--- a/tests/integration/replication-4.tcl
+++ b/tests/integration/replication-4.tcl
@@ -47,7 +47,7 @@ start_server {tags {"repl external:skip"}} {
set slave [srv 0 client]
# Load some functions to be used later
- $master FUNCTION load lua test replace {
+ $master FUNCTION load replace {#!lua name=test
redis.register_function{function_name='f_default_flags', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={}}
redis.register_function{function_name='f_no_writes', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={'no-writes'}}
}
diff --git a/tests/integration/replication.tcl b/tests/integration/replication.tcl
index 05f62d5e8..44915be1b 100644
--- a/tests/integration/replication.tcl
+++ b/tests/integration/replication.tcl
@@ -523,10 +523,14 @@ foreach testType {Successful Aborted} {
$replica set mykey myvalue
# Set a function value on replica to check status during loading, on failure and after swapping db
- $replica function load LUA test {redis.register_function('test', function() return 'hello1' end)}
+ $replica function load {#!lua name=test
+ redis.register_function('test', function() return 'hello1' end)
+ }
# Set a function value on master to check it reaches the replica when replication ends
- $master function load LUA test {redis.register_function('test', function() return 'hello2' end)}
+ $master function load {#!lua name=test
+ redis.register_function('test', function() return 'hello2' end)
+ }
# Force the replica to try another full sync (this time it will have matching master replid)
$master multi
@@ -659,7 +663,9 @@ test {diskless loading short read} {
set start [clock clicks -milliseconds]
# Set a function value to check short read handling on functions
- r function load LUA test {redis.register_function('test', function() return 'hello1' end)}
+ r function load {#!lua name=test
+ redis.register_function('test', function() return 'hello1' end)
+ }
for {set k 0} {$k < 3} {incr k} {
for {set i 0} {$i < 10} {incr i} {
diff --git a/tests/modules/Makefile b/tests/modules/Makefile
index ce842f3af..1b7159c89 100644
--- a/tests/modules/Makefile
+++ b/tests/modules/Makefile
@@ -54,7 +54,9 @@ TEST_MODULES = \
subcommands.so \
reply.so \
cmdintrospection.so \
- eventloop.so
+ eventloop.so \
+ moduleconfigs.so \
+ moduleconfigstwo.so
.PHONY: all
diff --git a/tests/modules/aclcheck.c b/tests/modules/aclcheck.c
index 0e9c9af29..8a9d468a6 100644
--- a/tests/modules/aclcheck.c
+++ b/tests/modules/aclcheck.c
@@ -136,6 +136,22 @@ int rm_call_aclcheck_cmd_module_user(RedisModuleCtx *ctx, RedisModuleString **ar
return res;
}
+int rm_call_aclcheck_with_errors(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if(argc < 2){
+ return RedisModule_WrongArity(ctx);
+ }
+
+ const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
+
+ RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "vEC", argv + 2, argc - 2);
+ RedisModule_ReplyWithCallReply(ctx, rep);
+ RedisModule_FreeCallReply(rep);
+ return REDISMODULE_OK;
+}
+
/* A wrap for RM_Call that pass the 'C' flag to do ACL check on the command. */
int rm_call_aclcheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
REDISMODULE_NOT_USED(argv);
@@ -190,5 +206,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"write",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
+ if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call_with_errors", rm_call_aclcheck_with_errors,
+ "write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
return REDISMODULE_OK;
}
diff --git a/tests/modules/auth.c b/tests/modules/auth.c
index 040a447ec..612320dbc 100644
--- a/tests/modules/auth.c
+++ b/tests/modules/auth.c
@@ -54,6 +54,16 @@ int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
}
+/* This command redacts every other arguments and returns OK */
+int Auth_RedactedAPI(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ for(int i = argc - 1; i > 0; i -= 2) {
+ int result = RedisModule_RedactClientCommandArgument(ctx, i);
+ RedisModule_Assert(result == REDISMODULE_OK);
+ }
+ return RedisModule_ReplyWithSimpleString(ctx, "OK");
+}
+
int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
@@ -87,6 +97,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
+ if (RedisModule_CreateCommand(ctx,"auth.redact",
+ Auth_RedactedAPI,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
return REDISMODULE_OK;
}
diff --git a/tests/modules/basics.c b/tests/modules/basics.c
index 4d639d682..ecd1b8852 100644
--- a/tests/modules/basics.c
+++ b/tests/modules/basics.c
@@ -718,6 +718,25 @@ end:
/* Return 1 if the reply matches the specified string, otherwise log errors
* in the server log and return 0. */
+int TestAssertErrorReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
+ RedisModuleString *mystr, *expected;
+ if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ERROR) {
+ return 0;
+ }
+
+ mystr = RedisModule_CreateStringFromCallReply(reply);
+ expected = RedisModule_CreateString(ctx,str,len);
+ if (RedisModule_StringCompare(mystr,expected) != 0) {
+ const char *mystr_ptr = RedisModule_StringPtrLen(mystr,NULL);
+ const char *expected_ptr = RedisModule_StringPtrLen(expected,NULL);
+ RedisModule_Log(ctx,"warning",
+ "Unexpected Error reply reply '%s' (instead of '%s')",
+ mystr_ptr, expected_ptr);
+ return 0;
+ }
+ return 1;
+}
+
int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
RedisModuleString *mystr, *expected;
@@ -846,6 +865,18 @@ int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 0),"test",4)) goto fail;
if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 1),"1234",4)) goto fail;
+ T("foo", "E");
+ if (!TestAssertErrorReply(ctx,reply,"ERR Unknown Redis command 'foo'.",32)) goto fail;
+
+ T("set", "Ec", "x");
+ if (!TestAssertErrorReply(ctx,reply,"ERR Wrong number of args calling Redis command 'set'.",53)) goto fail;
+
+ T("shutdown", "SE");
+ if (!TestAssertErrorReply(ctx,reply,"ERR command 'shutdown' is not allowed on script mode",52)) goto fail;
+
+ T("set", "WEcc", "x", "1");
+ if (!TestAssertErrorReply(ctx,reply,"ERR Write command 'set' was called while write is not allowed.",62)) goto fail;
+
RedisModule_ReplyWithSimpleString(ctx,"ALL TESTS PASSED");
return REDISMODULE_OK;
diff --git a/tests/modules/blockedclient.c b/tests/modules/blockedclient.c
index a2d7c6d00..9c2598a34 100644
--- a/tests/modules/blockedclient.c
+++ b/tests/modules/blockedclient.c
@@ -195,7 +195,7 @@ int do_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
- RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "v", argv + 2, argc - 2);
+ RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "Ev", argv + 2, argc - 2);
if(!rep){
RedisModule_ReplyWithError(ctx, "NULL reply returned");
}else{
diff --git a/tests/modules/blockonkeys.c b/tests/modules/blockonkeys.c
index 4568b9fa8..1aa576489 100644
--- a/tests/modules/blockonkeys.c
+++ b/tests/modules/blockonkeys.c
@@ -135,22 +135,29 @@ int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
}
-/* FSL.BPOP <key> <timeout> - Block clients until list has two or more elements.
+/* FSL.BPOP <key> <timeout> [NO_TO_CB]- Block clients until list has two or more elements.
* When that happens, unblock client and pop the last two elements (from the right). */
int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
- if (argc != 3)
+ if (argc < 3)
return RedisModule_WrongArity(ctx);
long long timeout;
if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK || timeout < 0)
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
+ int to_cb = 1;
+ if (argc == 4) {
+ if (strcasecmp("NO_TO_CB", RedisModule_StringPtrLen(argv[3], NULL)))
+ return RedisModule_ReplyWithError(ctx,"ERR invalid argument");
+ to_cb = 0;
+ }
+
fsl_t *fsl;
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
return REDISMODULE_OK;
if (!fsl) {
- RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, bpop_timeout_callback,
+ RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, to_cb ? bpop_timeout_callback : NULL,
NULL, timeout, &argv[1], 1, NULL);
} else {
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
diff --git a/tests/modules/eventloop.c b/tests/modules/eventloop.c
index 50d3bc052..c0cfdf04f 100644
--- a/tests/modules/eventloop.c
+++ b/tests/modules/eventloop.c
@@ -11,7 +11,6 @@
* 4- test.oneshot : Test for oneshot API
*/
-#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"
#include <stdlib.h>
#include <unistd.h>
diff --git a/tests/modules/hooks.c b/tests/modules/hooks.c
index af4681cf9..94d902d22 100644
--- a/tests/modules/hooks.c
+++ b/tests/modules/hooks.c
@@ -267,6 +267,18 @@ void swapDbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void
LogNumericEvent(ctx, "swapdb-second", ei->dbnum_second);
}
+void configChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
+{
+ REDISMODULE_NOT_USED(e);
+ if (sub != REDISMODULE_SUBEVENT_CONFIG_CHANGE) {
+ return;
+ }
+
+ RedisModuleConfigChangeV1 *ei = data;
+ LogNumericEvent(ctx, "config-change-count", ei->num_changes);
+ LogStringEvent(ctx, "config-change-first", ei->config_names[0]);
+}
+
/* This function must be present on each Redis module. It is used in order to
* register the commands into the Redis server. */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
@@ -317,6 +329,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_SubscribeToServerEvent(ctx,
RedisModuleEvent_SwapDB, swapDbCallback);
+ RedisModule_SubscribeToServerEvent(ctx,
+ RedisModuleEvent_Config, configChangeCallback);
+
event_log = RedisModule_CreateDict(ctx);
if (RedisModule_CreateCommand(ctx,"hooks.event_count", cmdEventCount,"",0,0,0) == REDISMODULE_ERR)
diff --git a/tests/modules/moduleconfigs.c b/tests/modules/moduleconfigs.c
new file mode 100644
index 000000000..a9e434a7b
--- /dev/null
+++ b/tests/modules/moduleconfigs.c
@@ -0,0 +1,142 @@
+#include "redismodule.h"
+#include <strings.h>
+int mutable_bool_val;
+int immutable_bool_val;
+long long longval;
+long long memval;
+RedisModuleString *strval = NULL;
+int enumval;
+
+/* Series of get and set callbacks for each type of config, these rely on the privdata ptr
+ * to point to the config, and they register the configs as such. Note that one could also just
+ * use names if they wanted, and store anything in privdata. */
+int getBoolConfigCommand(const char *name, void *privdata) {
+ REDISMODULE_NOT_USED(name);
+ return (*(int *)privdata);
+}
+
+int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(name);
+ REDISMODULE_NOT_USED(err);
+ *(int *)privdata = new;
+ return REDISMODULE_OK;
+}
+
+long long getNumericConfigCommand(const char *name, void *privdata) {
+ REDISMODULE_NOT_USED(name);
+ return (*(long long *) privdata);
+}
+
+int setNumericConfigCommand(const char *name, long long new, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(name);
+ REDISMODULE_NOT_USED(err);
+ *(long long *)privdata = new;
+ return REDISMODULE_OK;
+}
+
+RedisModuleString *getStringConfigCommand(const char *name, void *privdata) {
+ REDISMODULE_NOT_USED(name);
+ REDISMODULE_NOT_USED(privdata);
+ return strval;
+}
+int setStringConfigCommand(const char *name, RedisModuleString *new, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(name);
+ REDISMODULE_NOT_USED(err);
+ REDISMODULE_NOT_USED(privdata);
+ size_t len;
+ if (!strcasecmp(RedisModule_StringPtrLen(new, &len), "rejectisfreed")) {
+ *err = RedisModule_CreateString(NULL, "Cannot set string to 'rejectisfreed'", 36);
+ return REDISMODULE_ERR;
+ }
+ if (strval) RedisModule_FreeString(NULL, strval);
+ RedisModule_RetainString(NULL, new);
+ strval = new;
+ return REDISMODULE_OK;
+}
+
+int getEnumConfigCommand(const char *name, void *privdata) {
+ REDISMODULE_NOT_USED(name);
+ REDISMODULE_NOT_USED(privdata);
+ return enumval;
+}
+
+int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(name);
+ REDISMODULE_NOT_USED(err);
+ REDISMODULE_NOT_USED(privdata);
+ enumval = val;
+ return REDISMODULE_OK;
+}
+
+int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(privdata);
+ if (mutable_bool_val && immutable_bool_val) {
+ *err = RedisModule_CreateString(NULL, "Bool configs cannot both be yes.", 32);
+ return REDISMODULE_ERR;
+ }
+ return REDISMODULE_OK;
+}
+
+int longlongApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(privdata);
+ if (longval == memval) {
+ *err = RedisModule_CreateString(NULL, "These configs cannot equal each other.", 38);
+ return REDISMODULE_ERR;
+ }
+ return REDISMODULE_OK;
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx, "moduleconfigs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_RegisterBoolConfig(ctx, "mutable_bool", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &mutable_bool_val) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ /* Immutable config here. */
+ if (RedisModule_RegisterBoolConfig(ctx, "immutable_bool", 0, REDISMODULE_CONFIG_IMMUTABLE, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &immutable_bool_val) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ if (RedisModule_RegisterStringConfig(ctx, "string", "secret password", REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+
+ /* On the stack to make sure we're copying them. */
+ const char *enum_vals[3] = {"one", "two", "three"};
+ const int int_vals[3] = {0, 2, 4};
+
+ if (RedisModule_RegisterEnumConfig(ctx, "enum", 0, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ /* Memory config here. */
+ if (RedisModule_RegisterNumericConfig(ctx, "memory_numeric", 1024, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_MEMORY, 0, 3000000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &memval) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ if (RedisModule_RegisterNumericConfig(ctx, "numeric", -1, REDISMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ size_t len;
+ if (argc && !strcasecmp(RedisModule_StringPtrLen(argv[0], &len), "noload")) {
+ return REDISMODULE_OK;
+ } else if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
+ if (strval) {
+ RedisModule_FreeString(ctx, strval);
+ strval = NULL;
+ }
+ return REDISMODULE_ERR;
+ }
+ return REDISMODULE_OK;
+}
+
+int RedisModule_OnUnload(RedisModuleCtx *ctx) {
+ REDISMODULE_NOT_USED(ctx);
+ if (strval) {
+ RedisModule_FreeString(ctx, strval);
+ strval = NULL;
+ }
+ return REDISMODULE_OK;
+} \ No newline at end of file
diff --git a/tests/modules/moduleconfigstwo.c b/tests/modules/moduleconfigstwo.c
new file mode 100644
index 000000000..c0e8f9136
--- /dev/null
+++ b/tests/modules/moduleconfigstwo.c
@@ -0,0 +1,39 @@
+#include "redismodule.h"
+#include <strings.h>
+
+/* Second module configs module, for testing.
+ * Need to make sure that multiple modules with configs don't interfere with each other */
+int bool_config;
+
+int getBoolConfigCommand(const char *name, void *privdata) {
+ REDISMODULE_NOT_USED(privdata);
+ if (!strcasecmp(name, "test")) {
+ return bool_config;
+ }
+ return 0;
+}
+
+int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
+ REDISMODULE_NOT_USED(privdata);
+ REDISMODULE_NOT_USED(err);
+ if (!strcasecmp(name, "test")) {
+ bool_config = new;
+ return REDISMODULE_OK;
+ }
+ return REDISMODULE_ERR;
+}
+
+/* No arguments are expected */
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+ if (RedisModule_Init(ctx, "configs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_RegisterBoolConfig(ctx, "test", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, NULL, &argc) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
+ return REDISMODULE_ERR;
+ }
+ return REDISMODULE_OK;
+} \ No newline at end of file
diff --git a/tests/sentinel/tests/03-runtime-reconf.tcl b/tests/sentinel/tests/03-runtime-reconf.tcl
index 3e930646a..71525fc7c 100644
--- a/tests/sentinel/tests/03-runtime-reconf.tcl
+++ b/tests/sentinel/tests/03-runtime-reconf.tcl
@@ -2,6 +2,162 @@
source "../tests/includes/init-tests.tcl"
set num_sentinels [llength $::sentinel_instances]
+set ::user "testuser"
+set ::password "secret"
+
+proc server_set_password {} {
+ foreach_redis_id id {
+ assert_equal {OK} [R $id CONFIG SET requirepass $::password]
+ assert_equal {OK} [R $id AUTH $::password]
+ assert_equal {OK} [R $id CONFIG SET masterauth $::password]
+ }
+}
+
+proc server_reset_password {} {
+ foreach_redis_id id {
+ assert_equal {OK} [R $id CONFIG SET requirepass ""]
+ assert_equal {OK} [R $id CONFIG SET masterauth ""]
+ }
+}
+
+proc server_set_acl {id} {
+ assert_equal {OK} [R $id ACL SETUSER $::user on >$::password allchannels +@all]
+ assert_equal {OK} [R $id ACL SETUSER default off]
+
+ R $id CLIENT KILL USER default SKIPME no
+ assert_equal {OK} [R $id AUTH $::user $::password]
+ assert_equal {OK} [R $id CONFIG SET masteruser $::user]
+ assert_equal {OK} [R $id CONFIG SET masterauth $::password]
+}
+
+proc server_reset_acl {id} {
+ assert_equal {OK} [R $id ACL SETUSER default on]
+ assert_equal {1} [R $id ACL DELUSER $::user]
+
+ assert_equal {OK} [R $id CONFIG SET masteruser ""]
+ assert_equal {OK} [R $id CONFIG SET masterauth ""]
+}
+
+proc verify_sentinel_connect_replicas {id} {
+ foreach replica [S $id SENTINEL REPLICAS mymaster] {
+ if {[string match "*disconnected*" [dict get $replica flags]]} {
+ return 0
+ }
+ }
+ return 1
+}
+
+proc wait_for_sentinels_connect_servers { {is_connect 1} } {
+ foreach_sentinel_id id {
+ wait_for_condition 1000 50 {
+ [string match "*disconnected*" [dict get [S $id SENTINEL MASTER mymaster] flags]] != $is_connect
+ } else {
+ fail "At least some sentinel can't connect to master"
+ }
+
+ wait_for_condition 1000 50 {
+ [verify_sentinel_connect_replicas $id] == $is_connect
+ } else {
+ fail "At least some sentinel can't connect to replica"
+ }
+ }
+}
+
+test "Sentinels (re)connection following SENTINEL SET mymaster auth-pass" {
+ # 3 types of sentinels to test:
+ # (re)started while master changed pwd. Manage to connect only after setting pwd
+ set sent2re 0
+ # (up)dated in advance with master new password
+ set sent2up 1
+ # (un)touched. Yet manage to maintain (old) connection
+ set sent2un 2
+
+ wait_for_sentinels_connect_servers
+ kill_instance sentinel $sent2re
+ server_set_password
+ assert_equal {OK} [S $sent2up SENTINEL SET mymaster auth-pass $::password]
+ restart_instance sentinel $sent2re
+
+ # Verify sentinel that restarted failed to connect master
+ if {![string match "*disconnected*" [dict get [S $sent2re SENTINEL MASTER mymaster] flags]]} {
+ fail "Expected to be disconnected from master due to wrong password"
+ }
+
+ # Update restarted sentinel with master password
+ assert_equal {OK} [S $sent2re SENTINEL SET mymaster auth-pass $::password]
+
+ # All sentinels expected to connect successfully
+ wait_for_sentinels_connect_servers
+
+ # remove requirepass and verify sentinels manage to connect servers
+ server_reset_password
+ wait_for_sentinels_connect_servers
+ # Sanity check
+ verify_sentinel_auto_discovery
+}
+
+test "Sentinels (re)connection following master ACL change" {
+ # Three types of sentinels to test during ACL change:
+ # 1. (re)started Sentinel. Manage to connect only after setting new pwd
+ # 2. (up)dated Sentinel, get just before ACL change the new password
+ # 3. (un)touched Sentinel that kept old connection with master and didn't
+ # set new ACL password won't persist ACL pwd change (unlike legacy auth-pass)
+ set sent2re 0
+ set sent2up 1
+ set sent2un 2
+
+ wait_for_sentinels_connect_servers
+ # kill sentinel 'sent2re' and restart it after ACL change
+ kill_instance sentinel $sent2re
+
+ # Update sentinel 'sent2up' with new user and pwd
+ assert_equal {OK} [S $sent2up SENTINEL SET mymaster auth-user $::user]
+ assert_equal {OK} [S $sent2up SENTINEL SET mymaster auth-pass $::password]
+
+ foreach_redis_id id {
+ server_set_acl $id
+ }
+
+ restart_instance sentinel $sent2re
+
+ # Verify sentinel that restarted failed to reconnect master
+ wait_for_condition 100 50 {
+ [string match "*disconnected*" [dict get [S $sent2re SENTINEL MASTER mymaster] flags]] != 0
+ } else {
+ fail "Expected: Sentinel to be disconnected from master due to wrong password"
+ }
+
+ # Verify sentinel with updated password managed to connect (wait for sentinelTimer to reconnect)
+ wait_for_condition 100 50 {
+ [string match "*disconnected*" [dict get [S $sent2up SENTINEL MASTER mymaster] flags]] == 0
+ } else {
+ fail "Expected: Sentinel to be connected to master"
+ }
+
+ # Verify sentinel untouched gets failed to connect master
+ if {![string match "*disconnected*" [dict get [S $sent2un SENTINEL MASTER mymaster] flags]]} {
+ fail "Expected: Sentinel to be disconnected from master due to wrong password"
+ }
+
+ # Now update all sentinels with new password
+ foreach_sentinel_id id {
+ assert_equal {OK} [S $id SENTINEL SET mymaster auth-user $::user]
+ assert_equal {OK} [S $id SENTINEL SET mymaster auth-pass $::password]
+ }
+
+ # All sentinels expected to connect successfully
+ wait_for_sentinels_connect_servers
+
+ # remove requirepass and verify sentinels manage to connect servers
+ foreach_redis_id id {
+ server_reset_acl $id
+ }
+
+ wait_for_sentinels_connect_servers
+ # Sanity check
+ verify_sentinel_auto_discovery
+}
+
test "Set parameters in normal case" {
set info [S 0 SENTINEL master mymaster]
diff --git a/tests/sentinel/tests/07-down-conditions.tcl b/tests/sentinel/tests/07-down-conditions.tcl
index e7e33a29a..bb24d6dff 100644
--- a/tests/sentinel/tests/07-down-conditions.tcl
+++ b/tests/sentinel/tests/07-down-conditions.tcl
@@ -24,7 +24,6 @@ proc ensure_master_up {} {
}
}
-
proc ensure_master_down {} {
S $::alive_sentinel sentinel debug info-period 1000
S $::alive_sentinel sentinel debug ping-period 100
@@ -45,10 +44,14 @@ test "Crash the majority of Sentinels to prevent failovers for this unit" {
}
test "SDOWN is triggered by non-responding but not crashed instance" {
- lassign [S $::alive_sentinel SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port
ensure_master_up
- exec ../../../src/redis-cli -h $host -p $port {*}[rediscli_tls_config "../../../tests"] debug sleep 3 > /dev/null &
+ set master_addr [S $::alive_sentinel SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
+ set master_id [get_instance_id_by_port redis [lindex $master_addr 1]]
+
+ set pid [get_instance_attrib redis $master_id pid]
+ exec kill -SIGSTOP $pid
ensure_master_down
+ exec kill -SIGCONT $pid
ensure_master_up
}
diff --git a/tests/sentinel/tests/08-hostname-conf.tcl b/tests/sentinel/tests/08-hostname-conf.tcl
index be6e42cb0..263b06fca 100644
--- a/tests/sentinel/tests/08-hostname-conf.tcl
+++ b/tests/sentinel/tests/08-hostname-conf.tcl
@@ -1,3 +1,5 @@
+source "../tests/includes/utils.tcl"
+
proc set_redis_announce_ip {addr} {
foreach_redis_id id {
R $id config set replica-announce-ip $addr
diff --git a/tests/sentinel/tests/09-acl-support.tcl b/tests/sentinel/tests/09-acl-support.tcl
index 8c967f0bc..a754dacf5 100644
--- a/tests/sentinel/tests/09-acl-support.tcl
+++ b/tests/sentinel/tests/09-acl-support.tcl
@@ -31,16 +31,23 @@ test "(post-init) Set up ACL configuration" {
test "SENTINEL CONFIG SET handles on-the-fly credentials reconfiguration" {
# Make sure we're starting with a broken state...
- after 5000
- catch {S 1 SENTINEL CKQUORUM mymaster} err
- assert_match {*NOQUORUM*} $err
+ wait_for_condition 200 50 {
+ [catch {S 1 SENTINEL CKQUORUM mymaster}] == 1
+ } else {
+ fail "Expected: Sentinel to be disconnected from master due to wrong password"
+ }
+ assert_error "*NOQUORUM*" {S 1 SENTINEL CKQUORUM mymaster}
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET sentinel-user $::user]
assert_equal {OK} [S $id SENTINEL CONFIG SET sentinel-pass $::password]
}
- after 5000
+ wait_for_condition 200 50 {
+ [catch {S 1 SENTINEL CKQUORUM mymaster}] == 0
+ } else {
+ fail "Expected: Sentinel to be connected to master after setting password"
+ }
assert_match {*OK*} [S 1 SENTINEL CKQUORUM mymaster]
}
diff --git a/tests/sentinel/tests/includes/init-tests.tcl b/tests/sentinel/tests/includes/init-tests.tcl
index b5baa256f..fe9a61815 100644
--- a/tests/sentinel/tests/includes/init-tests.tcl
+++ b/tests/sentinel/tests/includes/init-tests.tcl
@@ -1,16 +1,5 @@
# Initialization tests -- most units will start including this.
-
-proc restart_killed_instances {} {
- foreach type {redis sentinel} {
- foreach_${type}_id id {
- if {[get_instance_attrib $type $id pid] == -1} {
- puts -nonewline "$type/$id "
- flush stdout
- restart_instance $type $id
- }
- }
- }
-}
+source "../tests/includes/utils.tcl"
test "(init) Restart killed instances" {
restart_killed_instances
@@ -59,14 +48,7 @@ test "(init) Sentinels can talk with the master" {
}
test "(init) Sentinels are able to auto-discover other sentinels" {
- set sentinels [llength $::sentinel_instances]
- foreach_sentinel_id id {
- wait_for_condition 1000 50 {
- [dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
- } else {
- fail "At least some sentinel can't detect some other sentinel"
- }
- }
+ verify_sentinel_auto_discovery
}
test "(init) Sentinels are able to auto-discover slaves" {
diff --git a/tests/sentinel/tests/includes/utils.tcl b/tests/sentinel/tests/includes/utils.tcl
new file mode 100644
index 000000000..adfd91c09
--- /dev/null
+++ b/tests/sentinel/tests/includes/utils.tcl
@@ -0,0 +1,22 @@
+proc restart_killed_instances {} {
+ foreach type {redis sentinel} {
+ foreach_${type}_id id {
+ if {[get_instance_attrib $type $id pid] == -1} {
+ puts -nonewline "$type/$id "
+ flush stdout
+ restart_instance $type $id
+ }
+ }
+ }
+}
+
+proc verify_sentinel_auto_discovery {} {
+ set sentinels [llength $::sentinel_instances]
+ foreach_sentinel_id id {
+ wait_for_condition 1000 50 {
+ [dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
+ } else {
+ fail "At least some sentinel can't detect some other sentinel"
+ }
+ }
+}
diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl
index 5743be5f4..edcc1fb48 100644
--- a/tests/support/redis.tcl
+++ b/tests/support/redis.tcl
@@ -188,6 +188,10 @@ proc ::redis::__method__readraw {id fd val} {
set ::redis::readraw($id) $val
}
+proc ::redis::__method__readingraw {id fd} {
+ return $::redis::readraw($id)
+}
+
proc ::redis::__method__attributes {id fd} {
set _ $::redis::attributes($id)
}
diff --git a/tests/support/server.tcl b/tests/support/server.tcl
index b06bd73ba..9d0c4510d 100644
--- a/tests/support/server.tcl
+++ b/tests/support/server.tcl
@@ -684,6 +684,14 @@ proc start_server {options {code undefined}} {
}
}
+# Start multiple servers with the same options, run code, then stop them.
+proc start_multiple_servers {num options code} {
+ for {set i 0} {$i < $num} {incr i} {
+ set code [list start_server $options $code]
+ }
+ uplevel 1 $code
+}
+
proc restart_server {level wait_ready rotate_logs {reconnect 1} {shutdown sigterm}} {
set srv [lindex $::servers end+$level]
if {$shutdown ne {sigterm}} {
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
index 46c9654c8..4ad96ab10 100644
--- a/tests/support/util.tcl
+++ b/tests/support/util.tcl
@@ -122,7 +122,7 @@ proc wait_replica_online r {
wait_for_condition 50 100 {
[string match "*slave0:*,state=online*" [$r info replication]]
} else {
- fail "replica didn't sync in time"
+ fail "replica didn't online in time"
}
}
@@ -130,7 +130,7 @@ proc wait_for_ofs_sync {r1 r2} {
wait_for_condition 50 100 {
[status $r1 master_repl_offset] eq [status $r2 master_repl_offset]
} else {
- fail "replica didn't sync in time"
+ fail "replica offset didn't match in time"
}
}
diff --git a/tests/unit/acl-v2.tcl b/tests/unit/acl-v2.tcl
index 72ea44c3a..12eb5a3be 100644
--- a/tests/unit/acl-v2.tcl
+++ b/tests/unit/acl-v2.tcl
@@ -305,13 +305,19 @@ start_server {tags {"acl external:skip"}} {
assert_equal "ERR Command 'not-a-command' not found" $e
}
+ test {Test various commands for command permissions} {
+ r ACL setuser command-test -@all
+ assert_equal "This user has no permissions to run the 'set' command" [r ACL DRYRUN command-test set somekey somevalue]
+ assert_equal "This user has no permissions to run the 'get' command" [r ACL DRYRUN command-test get somekey]
+ }
+
test {Test various odd commands for key permissions} {
r ACL setuser command-test +@all %R~read* %W~write* %RW~rw*
# Test migrate, which is marked with incomplete keys
- assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever rw]
- assert_equal "This user has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever read]
- assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever write]
+ assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever rw 0 500]
+ assert_equal "This user has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever read 0 500]
+ assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever write 0 500]
assert_equal "OK" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 KEYS rw]
assert_equal "This user has no permissions to access the 'read' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 KEYS read]
assert_equal "This user has no permissions to access the 'write' key" [r ACL DRYRUN command-test MIGRATE whatever whatever "" 0 5000 KEYS write]
@@ -428,6 +434,50 @@ start_server {tags {"acl external:skip"}} {
assert_equal "This user has no permissions to access the 'otherchannel' channel" [r ACL DRYRUN test-channels ssubscribe otherchannel foo]
}
+ test {Test sort with ACL permissions} {
+ r set v1 1
+ r lpush mylist 1
+
+ r ACL setuser test-sort-acl on nopass (+sort ~mylist)
+ $r2 auth test-sort-acl nopass
+
+ catch {$r2 sort mylist by v*} e
+ assert_equal "ERR BY option of SORT denied due to insufficient ACL permissions." $e
+ catch {$r2 sort mylist get v*} e
+ assert_equal "ERR GET option of SORT denied due to insufficient ACL permissions." $e
+
+ r ACL setuser test-sort-acl (+sort ~mylist ~v*)
+ catch {$r2 sort mylist by v*} e
+ assert_equal "ERR BY option of SORT denied due to insufficient ACL permissions." $e
+ catch {$r2 sort mylist get v*} e
+ assert_equal "ERR GET option of SORT denied due to insufficient ACL permissions." $e
+
+ r ACL setuser test-sort-acl (+sort ~mylist %W~*)
+ catch {$r2 sort mylist by v*} e
+ assert_equal "ERR BY option of SORT denied due to insufficient ACL permissions." $e
+ catch {$r2 sort mylist get v*} e
+ assert_equal "ERR GET option of SORT denied due to insufficient ACL permissions." $e
+
+ r ACL setuser test-sort-acl (+sort ~mylist %R~*)
+ assert_equal "1" [$r2 sort mylist by v*]
+
+ # cleanup
+ r ACL deluser test-sort-acl
+ r del v1 mylist
+ }
+
+ test {Test DRYRUN with wrong number of arguments} {
+ r ACL setuser test-dry-run +@all ~v*
+
+ assert_equal "OK" [r ACL DRYRUN test-dry-run SET v v]
+
+ catch {r ACL DRYRUN test-dry-run SET v} e
+ assert_equal "ERR wrong number of arguments for 'set' command" $e
+
+ catch {r ACL DRYRUN test-dry-run SET} e
+ assert_equal "ERR wrong number of arguments for 'set' command" $e
+ }
+
$r2 close
}
diff --git a/tests/unit/aofrw.tcl b/tests/unit/aofrw.tcl
index ac861a653..45db1939f 100644
--- a/tests/unit/aofrw.tcl
+++ b/tests/unit/aofrw.tcl
@@ -185,7 +185,7 @@ start_server {tags {"aofrw external:skip"} overrides {aof-use-rdb-preamble no}}
test "AOF rewrite functions" {
r flushall
- r FUNCTION LOAD LUA test DESCRIPTION {desc} {
+ r FUNCTION LOAD {#!lua name=test
redis.register_function('test', function() return 1 end)
}
r bgrewriteaof
@@ -194,7 +194,7 @@ start_server {tags {"aofrw external:skip"} overrides {aof-use-rdb-preamble no}}
r debug loadaof
assert_equal [r fcall test 0] 1
r FUNCTION LIST
- } {{library_name test engine LUA description desc functions {{name test description {} flags {}}}}}
+ } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
test {BGREWRITEAOF is delayed if BGSAVE is in progress} {
r flushall
diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl
index 89431c81f..ec08a060d 100644
--- a/tests/unit/bitops.tcl
+++ b/tests/unit/bitops.tcl
@@ -433,6 +433,12 @@ start_server {tags {"bitops"}} {
r bitfield foo3{t} incrby i5 0 1
set dirty5 [s rdb_changes_since_last_save]
assert {$dirty5 == $dirty4 + 2}
+
+ # Change length only
+ r setbit foo{t} 90 0
+ r bitfield foo2{t} set i5 90 0
+ set dirty6 [s rdb_changes_since_last_save]
+ assert {$dirty6 == $dirty5 + 2}
}
test {BITPOS bit=1 fuzzy testing using SETBIT} {
diff --git a/tests/unit/client-eviction.tcl b/tests/unit/client-eviction.tcl
index 949ac8f3d..469828473 100644
--- a/tests/unit/client-eviction.tcl
+++ b/tests/unit/client-eviction.tcl
@@ -395,13 +395,14 @@ start_server {} {
test "evict clients only until below limit" {
set client_count 10
set client_mem [mb 1]
- r debug replybuffer-peak-reset-time never
+ r debug replybuffer resizing 0
r config set maxmemory-clients 0
r client setname control
r client no-evict on
# Make multiple clients consume together roughly 1mb less than maxmemory_clients
set total_client_mem 0
+ set max_client_mem 0
set rrs {}
for {set j 0} {$j < $client_count} {incr j} {
set rr [redis_client]
@@ -414,20 +415,27 @@ start_server {} {
} else {
fail "Failed to fill qbuf for test"
}
- incr total_client_mem [client_field client$j tot-mem]
+ # In theory all these clients should use the same amount of memory (~1mb). But in practice
+ # some allocators (libc) can return different allocation sizes for the same malloc argument causing
+ # some clients to use slightly more memory than others. We find the largest client and make sure
+ # all clients are roughly the same size (+-1%). Then we can safely set the client eviction limit and
+ # expect consistent results in the test.
+ set cmem [client_field client$j tot-mem]
+ if {$max_client_mem > 0} {
+ set size_ratio [expr $max_client_mem.0/$cmem.0]
+ assert_range $size_ratio 0.99 1.01
+ }
+ if {$cmem > $max_client_mem} {
+ set max_client_mem $cmem
+ }
}
- set client_actual_mem [expr $total_client_mem / $client_count]
-
- # Make sure client_acutal_mem is more or equal to what we intended
- assert {$client_actual_mem >= $client_mem}
-
# Make sure all clients are still connected
set connected_clients [llength [lsearch -all [split [string trim [r client list]] "\r\n"] *name=client*]]
assert {$connected_clients == $client_count}
# Set maxmemory-clients to accommodate half our clients (taking into account the control client)
- set maxmemory_clients [expr ($client_actual_mem * $client_count) / 2 + [client_field control tot-mem]]
+ set maxmemory_clients [expr ($max_client_mem * $client_count) / 2 + [client_field control tot-mem]]
r config set maxmemory-clients $maxmemory_clients
# Make sure total used memory is below maxmemory_clients
@@ -438,8 +446,8 @@ start_server {} {
set connected_clients [llength [lsearch -all [split [string trim [r client list]] "\r\n"] *name=client*]]
assert {$connected_clients == [expr $client_count / 2]}
- # Restore the peak reset time to default
- r debug replybuffer-peak-reset-time reset
+ # Restore the reply buffer resize to default
+ r debug replybuffer resizing 1
foreach rr $rrs {$rr close}
} {} {needs:debug}
@@ -454,7 +462,7 @@ start_server {} {
r client setname control
r client no-evict on
r config set maxmemory-clients 0
- r debug replybuffer-peak-reset-time never
+ r debug replybuffer resizing 0
# Run over all sizes and create some clients using up that size
set total_client_mem 0
@@ -505,8 +513,8 @@ start_server {} {
}
}
- # Restore the peak reset time to default
- r debug replybuffer-peak-reset-time reset
+ # Restore the reply buffer resize to default
+ r debug replybuffer resizing 1
foreach rr $rrs {$rr close}
} {} {needs:debug}
diff --git a/tests/unit/cluster.tcl b/tests/unit/cluster.tcl
index 99925688c..9d49a2dee 100644
--- a/tests/unit/cluster.tcl
+++ b/tests/unit/cluster.tcl
@@ -19,6 +19,7 @@ proc csi {args} {
}
# make sure the test infra won't use SELECT
+set old_singledb $::singledb
set ::singledb 1
# cluster creation is complicated with TLS, and the current tests don't really need that coverage
@@ -26,14 +27,13 @@ tags {tls:skip external:skip cluster} {
# start three servers
set base_conf [list cluster-enabled yes cluster-node-timeout 1]
-start_server [list overrides $base_conf] {
-start_server [list overrides $base_conf] {
-start_server [list overrides $base_conf] {
+start_multiple_servers 3 [list overrides $base_conf] {
set node1 [srv 0 client]
set node2 [srv -1 client]
set node3 [srv -2 client]
set node3_pid [srv -2 pid]
+ set node3_rd [redis_deferring_client -2]
test {Create 3 node cluster} {
exec src/redis-cli --cluster-yes --cluster create \
@@ -52,7 +52,6 @@ start_server [list overrides $base_conf] {
test "Run blocking command on cluster node3" {
# key9184688 is mapped to slot 10923 (first slot of node 3)
- set node3_rd [redis_deferring_client -2]
$node3_rd brpop key9184688 0
$node3_rd flush
@@ -90,10 +89,11 @@ start_server [list overrides $base_conf] {
}
}
+ set node1_rd [redis_deferring_client 0]
+
test "Sanity test push cmd after resharding" {
assert_error {*MOVED*} {$node3 lpush key9184688 v1}
- set node1_rd [redis_deferring_client 0]
$node1_rd brpop key9184688 0
$node1_rd flush
@@ -109,13 +109,11 @@ start_server [list overrides $base_conf] {
assert_equal {key9184688 v2} [$node1_rd read]
}
- $node1_rd close
$node3_rd close
test "Run blocking command 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 brpop key9184688 0
$node1_rd flush
@@ -149,18 +147,11 @@ start_server [list overrides $base_conf] {
exec kill -SIGCONT $node3_pid
$node1_rd close
-# stop three servers
-}
-}
-}
+} ;# stop servers
# Test redis-cli -- cluster create, add-node, call.
# Test that functions are propagated on add-node
-start_server [list overrides $base_conf] {
-start_server [list overrides $base_conf] {
-start_server [list overrides $base_conf] {
-start_server [list overrides $base_conf] {
-start_server [list overrides $base_conf] {
+start_multiple_servers 5 [list overrides $base_conf] {
set node4_rd [redis_client -3]
set node5_rd [redis_client -4]
@@ -182,7 +173,9 @@ start_server [list overrides $base_conf] {
# upload a function to all the cluster
exec src/redis-cli --cluster-yes --cluster call 127.0.0.1:[srv 0 port] \
- FUNCTION LOAD LUA TEST {redis.register_function('test', function() return 'hello' end)}
+ FUNCTION LOAD {#!lua name=TEST
+ redis.register_function('test', function() return 'hello' end)
+ }
# adding node to the cluster
exec src/redis-cli --cluster-yes --cluster add-node \
@@ -199,13 +192,15 @@ start_server [list overrides $base_conf] {
}
# make sure 'test' function was added to the new node
- assert_equal {{library_name TEST engine LUA description {} functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST]
+ assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST]
# add function to node 5
- assert_equal {OK} [$node5_rd FUNCTION LOAD LUA TEST {redis.register_function('test', function() return 'hello' end)}]
+ assert_equal {TEST} [$node5_rd FUNCTION LOAD {#!lua name=TEST
+ redis.register_function('test', function() return 'hello' end)
+ }]
# make sure functions was added to node 5
- assert_equal {{library_name TEST engine LUA description {} functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST]
+ assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST]
# adding node 5 to the cluster should failed because it already contains the 'test' function
catch {
@@ -215,11 +210,111 @@ start_server [list overrides $base_conf] {
} e
assert_match {*node already contains functions*} $e
}
-# stop 5 servers
-}
-}
-}
-}
+} ;# stop servers
+
+# Test redis-cli --cluster create, add-node.
+# Test that one slot can be migrated to and then away from the new node.
+test {Migrate the last slot away from a node using redis-cli} {
+ start_multiple_servers 4 [list overrides $base_conf] {
+
+ # Create a cluster of 3 nodes
+ exec src/redis-cli --cluster-yes --cluster create \
+ 127.0.0.1:[srv 0 port] \
+ 127.0.0.1:[srv -1 port] \
+ 127.0.0.1:[srv -2 port]
+
+ wait_for_condition 1000 50 {
+ [csi 0 cluster_state] eq {ok} &&
+ [csi -1 cluster_state] eq {ok} &&
+ [csi -2 cluster_state] eq {ok}
+ } else {
+ fail "Cluster doesn't stabilize"
+ }
+
+ # Insert some data
+ assert_equal OK [exec src/redis-cli -c -p [srv 0 port] SET foo bar]
+ set slot [exec src/redis-cli -c -p [srv 0 port] CLUSTER KEYSLOT foo]
+
+ # Add new node to the cluster
+ exec src/redis-cli --cluster-yes --cluster add-node \
+ 127.0.0.1:[srv -3 port] \
+ 127.0.0.1:[srv 0 port]
+
+ wait_for_condition 1000 50 {
+ [csi 0 cluster_state] eq {ok} &&
+ [csi -1 cluster_state] eq {ok} &&
+ [csi -2 cluster_state] eq {ok} &&
+ [csi -3 cluster_state] eq {ok}
+ } else {
+ fail "Cluster doesn't stabilize"
+ }
+
+ set newnode_r [redis_client -3]
+ set newnode_id [$newnode_r CLUSTER MYID]
+
+ # Find out which node has the key "foo" by asking the new node for a
+ # redirect.
+ catch { $newnode_r get foo } e
+ assert_match "MOVED $slot *" $e
+ lassign [split [lindex $e 2] :] owner_host owner_port
+ set owner_r [redis $owner_host $owner_port 0 $::tls]
+ set owner_id [$owner_r CLUSTER MYID]
+
+ # Move slot to new node using plain Redis commands
+ assert_equal OK [$newnode_r CLUSTER SETSLOT $slot IMPORTING $owner_id]
+ assert_equal OK [$owner_r CLUSTER SETSLOT $slot MIGRATING $newnode_id]
+ assert_equal {foo} [$owner_r CLUSTER GETKEYSINSLOT $slot 10]
+ assert_equal OK [$owner_r MIGRATE 127.0.0.1 [srv -3 port] "" 0 5000 KEYS foo]
+ assert_equal OK [$newnode_r CLUSTER SETSLOT $slot NODE $newnode_id]
+ assert_equal OK [$owner_r CLUSTER SETSLOT $slot NODE $newnode_id]
+
+ # Using --cluster check make sure we won't get `Not all slots are covered by nodes`.
+ # Wait for the cluster to become stable make sure the cluster is up during MIGRATE.
+ 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 &&
+ [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -3 port]}] == 0 &&
+ [csi 0 cluster_state] eq {ok} &&
+ [csi -1 cluster_state] eq {ok} &&
+ [csi -2 cluster_state] eq {ok} &&
+ [csi -3 cluster_state] eq {ok}
+ } else {
+ fail "Cluster doesn't stabilize"
+ }
+
+ # Move the only slot back to original node using redis-cli
+ exec src/redis-cli --cluster reshard 127.0.0.1:[srv -3 port] \
+ --cluster-from $newnode_id \
+ --cluster-to $owner_id \
+ --cluster-slots 1 \
+ --cluster-yes
+
+ # The empty node will become a replica of the new owner before the
+ # `MOVED` check, so let's wait for the cluster to become stable.
+ wait_for_condition 1000 50 {
+ [csi 0 cluster_state] eq {ok} &&
+ [csi -1 cluster_state] eq {ok} &&
+ [csi -2 cluster_state] eq {ok} &&
+ [csi -3 cluster_state] eq {ok}
+ } else {
+ fail "Cluster doesn't stabilize"
+ }
+
+ # Check that the key foo has been migrated back to the original owner.
+ catch { $newnode_r get foo } e
+ assert_equal "MOVED $slot $owner_host:$owner_port" $e
+
+ # Check that the empty node has turned itself into a replica of the new
+ # owner and that the new owner knows that.
+ wait_for_condition 1000 50 {
+ [string match "*slave*" [$owner_r CLUSTER REPLICAS $owner_id]]
+ } else {
+ fail "Empty node didn't turn itself into a replica."
+ }
+ }
}
-} ;# tags \ No newline at end of file
+} ;# tags
+
+set ::singledb $old_singledb
diff --git a/tests/unit/functions.tcl b/tests/unit/functions.tcl
index 65e99e9e7..4c08261ed 100644
--- a/tests/unit/functions.tcl
+++ b/tests/unit/functions.tcl
@@ -1,61 +1,61 @@
proc get_function_code {args} {
- return [format "redis.register_function('%s', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0] [lindex $args 1]]
+ return [format "#!%s name=%s\nredis.register_function('%s', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]]
}
proc get_no_writes_function_code {args} {
- return [format "redis.register_function{function_name='%s', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0] [lindex $args 1]]
+ return [format "#!%s name=%s\nredis.register_function{function_name='%s', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]]
}
start_server {tags {"scripting"}} {
test {FUNCTION - Basic usage} {
- r function load LUA test [get_function_code test {return 'hello'}]
+ r function load [get_function_code LUA test test {return 'hello'}]
r fcall test 0
} {hello}
test {FUNCTION - Load with unknown argument} {
catch {
- r function load LUA test foo bar [get_function_code test {return 'hello'}]
+ r function load foo bar [get_function_code LUA test test {return 'hello'}]
} e
set _ $e
} {*Unknown option given*}
test {FUNCTION - Create an already exiting library raise error} {
catch {
- r function load LUA test [get_function_code test {return 'hello1'}]
+ r function load [get_function_code LUA test test {return 'hello1'}]
} e
set _ $e
} {*already exists*}
test {FUNCTION - Create an already exiting library raise error (case insensitive)} {
catch {
- r function load LUA TEST [get_function_code test {return 'hello1'}]
+ r function load [get_function_code LUA test test {return 'hello1'}]
} e
set _ $e
} {*already exists*}
test {FUNCTION - Create a library with wrong name format} {
catch {
- r function load LUA {bad\0foramat} [get_function_code test {return 'hello1'}]
+ r function load [get_function_code LUA {bad\0foramat} test {return 'hello1'}]
} e
set _ $e
} {*Library names can only contain letters and numbers*}
test {FUNCTION - Create library with unexisting engine} {
catch {
- r function load bad_engine test [get_function_code test {return 'hello1'}]
+ r function load [get_function_code bad_engine test test {return 'hello1'}]
} e
set _ $e
- } {*Engine not found*}
+ } {*Engine 'bad_engine' not found*}
test {FUNCTION - Test uncompiled script} {
catch {
- r function load LUA test1 {bad script}
+ r function load replace [get_function_code LUA test test {bad script}]
} e
set _ $e
} {*Error compiling function*}
test {FUNCTION - test replace argument} {
- r function load LUA test REPLACE [get_function_code test {return 'hello1'}]
+ r function load REPLACE [get_function_code LUA test test {return 'hello1'}]
r fcall test 0
} {hello1}
@@ -76,12 +76,8 @@ start_server {tags {"scripting"}} {
set _ $e
} {*Function not found*}
- test {FUNCTION - test description argument} {
- r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}]
- r function list
- } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}}
-
test {FUNCTION - test fcall bad arguments} {
+ r function load [get_function_code LUA test test {return 'hello'}]
catch {
r fcall test bad_arg
} e
@@ -133,14 +129,14 @@ start_server {tags {"scripting"}} {
assert_match "*Error trying to load the RDB*" $e
r debug reload noflush merge
r function list
- } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} {needs:debug}
+ } {{library_name test engine LUA functions {{name test description {} flags {}}}}} {needs:debug}
test {FUNCTION - test debug reload with nosave and noflush} {
r function delete test
r set x 1
- r function load LUA test1 DESCRIPTION {some description} [get_function_code test1 {return 'hello'}]
+ r function load [get_function_code LUA test1 test1 {return 'hello'}]
r debug reload
- r function load LUA test2 DESCRIPTION {some description} [get_function_code test2 {return 'hello'}]
+ r function load [get_function_code LUA test2 test2 {return 'hello'}]
r debug reload nosave noflush merge
assert_equal [r fcall test1 0] {hello}
assert_equal [r fcall test2 0] {hello}
@@ -148,21 +144,21 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test flushall and flushdb do not clean functions} {
r function flush
- r function load lua test REPLACE [get_function_code test {return redis.call('set', 'x', '1')}]
+ r function load REPLACE [get_function_code lua test test {return redis.call('set', 'x', '1')}]
r flushall
r flushdb
r function list
- } {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}}
+ } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
test {FUNCTION - test function dump and restore} {
r function flush
- r function load lua test description {some description} [get_function_code test {return 'hello'}]
+ r function load [get_function_code lua test test {return 'hello'}]
set e [r function dump]
r function delete test
assert_match {} [r function list]
r function restore $e
r function list
- } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}}
+ } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
test {FUNCTION - test function dump and restore with flush argument} {
set e [r function dump]
@@ -170,17 +166,17 @@ start_server {tags {"scripting"}} {
assert_match {} [r function list]
r function restore $e FLUSH
r function list
- } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}}
+ } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
test {FUNCTION - test function dump and restore with append argument} {
set e [r function dump]
r function flush
assert_match {} [r function list]
- r function load lua test [get_function_code test {return 'hello1'}]
+ r function load [get_function_code lua test test {return 'hello1'}]
catch {r function restore $e APPEND} err
assert_match {*already exists*} $err
r function flush
- r function load lua test1 [get_function_code test1 {return 'hello1'}]
+ r function load [get_function_code lua test1 test1 {return 'hello1'}]
r function restore $e APPEND
assert_match {hello} [r fcall test 0]
assert_match {hello1} [r fcall test1 0]
@@ -188,11 +184,11 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test function dump and restore with replace argument} {
r function flush
- r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}]
+ r function load [get_function_code LUA test test {return 'hello'}]
set e [r function dump]
r function flush
assert_match {} [r function list]
- r function load lua test [get_function_code test {return 'hello1'}]
+ r function load [get_function_code lua test test {return 'hello1'}]
assert_match {hello1} [r fcall test 0]
r function restore $e REPLACE
assert_match {hello} [r fcall test 0]
@@ -200,11 +196,11 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test function restore with bad payload do not drop existing functions} {
r function flush
- r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}]
+ r function load [get_function_code LUA test test {return 'hello'}]
catch {r function restore bad_payload} e
assert_match {*payload version or checksum are wrong*} $e
r function list
- } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}}
+ } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
test {FUNCTION - test function restore with wrong number of arguments} {
catch {r function restore arg1 args2 arg3} e
@@ -212,19 +208,19 @@ start_server {tags {"scripting"}} {
} {*Unknown subcommand or wrong number of arguments for 'restore'. Try FUNCTION HELP.}
test {FUNCTION - test fcall_ro with write command} {
- r function load lua test REPLACE [get_no_writes_function_code test {return redis.call('set', 'x', '1')}]
+ r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('set', 'x', '1')}]
catch { r fcall_ro test 0 } e
set _ $e
} {*Write commands are not allowed from read-only scripts*}
test {FUNCTION - test fcall_ro with read only commands} {
- r function load lua test REPLACE [get_no_writes_function_code test {return redis.call('get', 'x')}]
+ r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('get', 'x')}]
r set x 1
r fcall_ro test 0
} {1}
test {FUNCTION - test keys and argv} {
- r function load lua test REPLACE [get_function_code test {return redis.call('set', KEYS[1], ARGV[1])}]
+ r function load REPLACE [get_function_code lua test test {return redis.call('set', KEYS[1], ARGV[1])}]
r fcall test 1 x foo
r get x
} {foo}
@@ -240,7 +236,7 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test function kill} {
set rd [redis_deferring_client]
r config set busy-reply-threshold 10
- r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}]
+ r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
$rd fcall test 0
after 200
catch {r ping} e
@@ -254,7 +250,7 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test script kill not working on function} {
set rd [redis_deferring_client]
r config set busy-reply-threshold 10
- r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}]
+ r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
$rd fcall test 0
after 200
catch {r ping} e
@@ -281,18 +277,18 @@ start_server {tags {"scripting"}} {
}
test {FUNCTION - test function flush} {
- r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}]
- assert_match {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} [r function list]
+ r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
+ assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list]
r function flush
assert_match {} [r function list]
- r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}]
- assert_match {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} [r function list]
+ r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
+ assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list]
r function flush async
assert_match {} [r function list]
- r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}]
- assert_match {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} [r function list]
+ r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
+ assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list]
r function flush sync
assert_match {} [r function list]
}
@@ -310,7 +306,7 @@ start_server {tags {"scripting repl external:skip"}} {
start_server {} {
test "Connect a replica to the master instance" {
r -1 slaveof [srv 0 host] [srv 0 port]
- wait_for_condition 50 100 {
+ wait_for_condition 150 100 {
[s -1 role] eq {slave} &&
[string match {*master_link_status:up*} [r -1 info replication]]
} else {
@@ -319,9 +315,9 @@ start_server {tags {"scripting repl external:skip"}} {
}
test {FUNCTION - creation is replicated to replica} {
- r function load LUA test DESCRIPTION {some description} [get_no_writes_function_code test {return 'hello'}]
- wait_for_condition 50 100 {
- [r -1 function list] eq {{library_name test engine LUA description {some description} functions {{name test description {} flags no-writes}}}}
+ r function load [get_no_writes_function_code LUA test test {return 'hello'}]
+ wait_for_condition 150 100 {
+ [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}}
} else {
fail "Failed waiting for function to replicate to replica"
}
@@ -335,7 +331,7 @@ start_server {tags {"scripting repl external:skip"}} {
set e [r function dump]
r function delete test
- wait_for_condition 50 100 {
+ wait_for_condition 150 100 {
[r -1 function list] eq {}
} else {
fail "Failed waiting for function to replicate to replica"
@@ -343,8 +339,8 @@ start_server {tags {"scripting repl external:skip"}} {
assert_equal [r function restore $e] {OK}
- wait_for_condition 50 100 {
- [r -1 function list] eq {{library_name test engine LUA description {some description} functions {{name test description {} flags no-writes}}}}
+ wait_for_condition 150 100 {
+ [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}}
} else {
fail "Failed waiting for function to replicate to replica"
}
@@ -352,7 +348,7 @@ start_server {tags {"scripting repl external:skip"}} {
test {FUNCTION - delete is replicated to replica} {
r function delete test
- wait_for_condition 50 100 {
+ wait_for_condition 150 100 {
[r -1 function list] eq {}
} else {
fail "Failed waiting for function to replicate to replica"
@@ -360,14 +356,14 @@ start_server {tags {"scripting repl external:skip"}} {
}
test {FUNCTION - flush is replicated to replica} {
- r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}]
- wait_for_condition 50 100 {
- [r -1 function list] eq {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}}
+ r function load [get_function_code LUA test test {return 'hello'}]
+ wait_for_condition 150 100 {
+ [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags {}}}}}
} else {
fail "Failed waiting for function to replicate to replica"
}
r function flush
- wait_for_condition 50 100 {
+ wait_for_condition 150 100 {
[r -1 function list] eq {}
} else {
fail "Failed waiting for function to replicate to replica"
@@ -378,11 +374,11 @@ start_server {tags {"scripting repl external:skip"}} {
r -1 slaveof no one
# creating a function after disconnect to make sure function
# is replicated on rdb phase
- r function load LUA test DESCRIPTION {some description} [get_no_writes_function_code test {return 'hello'}]
+ r function load [get_no_writes_function_code LUA test test {return 'hello'}]
# reconnect the replica
r -1 slaveof [srv 0 host] [srv 0 port]
- wait_for_condition 50 100 {
+ wait_for_condition 150 100 {
[s -1 role] eq {slave} &&
[string match {*master_link_status:up*} [r -1 info replication]]
} else {
@@ -396,11 +392,11 @@ start_server {tags {"scripting repl external:skip"}} {
test "FUNCTION - test replication to replica on rdb phase info command" {
r -1 function list
- } {{library_name test engine LUA description {some description} functions {{name test description {} flags no-writes}}}}
+ } {{library_name test engine LUA functions {{name test description {} flags no-writes}}}}
test "FUNCTION - create on read only replica" {
catch {
- r -1 function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}]
+ r -1 function load [get_function_code LUA test test {return 'hello'}]
} e
set _ $e
} {*can't write against a read only replica*}
@@ -413,10 +409,10 @@ start_server {tags {"scripting repl external:skip"}} {
} {*can't write against a read only replica*}
test "FUNCTION - function effect is replicated to replica" {
- r function load LUA test REPLACE [get_function_code test {return redis.call('set', 'x', '1')}]
+ r function load REPLACE [get_function_code LUA test test {return redis.call('set', 'x', '1')}]
r fcall test 0
assert {[r get x] eq {1}}
- wait_for_condition 50 100 {
+ wait_for_condition 150 100 {
[r -1 get x] eq {1}
} else {
fail "Failed waiting function effect to be replicated to replica"
@@ -436,12 +432,12 @@ test {FUNCTION can processes create, delete and flush commands in AOF when doing
start_server {} {
r config set appendonly yes
waitForBgrewriteaof r
- r FUNCTION LOAD lua test "redis.register_function('test', function() return 'hello' end)"
+ r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)"
r config set slave-read-only yes
r slaveof 127.0.0.1 0
r debug loadaof
r slaveof no one
- assert_equal [r function list] {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}}
+ assert_equal [r function list] {{library_name test engine LUA functions {{name test description {} flags {}}}}}
r FUNCTION DELETE test
@@ -450,7 +446,7 @@ test {FUNCTION can processes create, delete and flush commands in AOF when doing
r slaveof no one
assert_equal [r function list] {}
- r FUNCTION LOAD lua test "redis.register_function('test', function() return 'hello' end)"
+ r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)"
r FUNCTION FLUSH
r slaveof 127.0.0.1 0
@@ -462,7 +458,7 @@ test {FUNCTION can processes create, delete and flush commands in AOF when doing
start_server {tags {"scripting"}} {
test {LIBRARIES - test shared function can access default globals} {
- r function load LUA lib1 {
+ r function load {#!lua name=lib1
local function ping()
return redis.call('ping')
end
@@ -477,7 +473,7 @@ start_server {tags {"scripting"}} {
} {PONG}
test {LIBRARIES - usage and code sharing} {
- r function load LUA lib1 REPLACE {
+ r function load REPLACE {#!lua name=lib1
local function add1(a)
return a + 1
end
@@ -497,11 +493,11 @@ start_server {tags {"scripting"}} {
assert_equal [r fcall f1 0] {2}
assert_equal [r fcall f2 0] {3}
r function list
- } {{library_name lib1 engine LUA description {} functions {*}}}
+ } {{library_name lib1 engine LUA functions {*}}}
test {LIBRARIES - test registration failure revert the entire load} {
catch {
- r function load LUA lib1 replace {
+ r function load replace {#!lua name=lib1
local function add1(a)
return a + 2
end
@@ -524,7 +520,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration function name collision} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function(
'f1',
function(keys, args)
@@ -540,7 +536,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration function name collision on same library} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function(
'f1',
function(keys, args)
@@ -560,7 +556,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration with no argument} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function()
}
} e
@@ -569,7 +565,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration with only name} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function('f1')
}
} e
@@ -578,7 +574,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration with to many arguments} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function('f1', function() return 1 end, {}, 'description', 'extra arg')
}
} e
@@ -587,7 +583,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration with no string name} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function(nil, function() return 1 end)
}
} e
@@ -596,7 +592,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration with wrong name format} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function('test\0test', function() return 1 end)
}
} e
@@ -605,7 +601,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - test registration with empty name} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
redis.register_function('', function() return 1 end)
}
} e
@@ -614,7 +610,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - math.random from function load} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
return math.random()
}
} e
@@ -623,7 +619,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - redis.call from function load} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
return redis.call('ping')
}
} e
@@ -632,7 +628,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - redis.call from function load} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
return redis.setresp(3)
}
} e
@@ -641,7 +637,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - redis.set_repl from function load} {
catch {
- r function load LUA lib2 replace {
+ r function load replace {#!lua name=lib2
return redis.set_repl(redis.REPL_NONE)
}
} e
@@ -657,7 +653,7 @@ start_server {tags {"scripting"}} {
# have another level of protection on the C
# code itself and we want to test it and verify
# that it works properly.
- r function load LUA lib1 replace {
+ r function load replace {#!lua name=lib1
local lib = redis
lib.register_function('f1', function ()
lib.redis = redis
@@ -675,22 +671,34 @@ start_server {tags {"scripting"}} {
}
assert_equal {OK} [r fcall f1 0]
- catch {[r function load LUA lib2 {redis.math.random()}]} e
+ catch {[r function load {#!lua name=lib2
+ redis.math.random()
+ }]} e
assert_match {*can only be called inside a script invocation*} $e
- catch {[r function load LUA lib2 {redis.math.randomseed()}]} e
+ catch {[r function load {#!lua name=lib2
+ redis.math.randomseed()
+ }]} e
assert_match {*can only be called inside a script invocation*} $e
- catch {[r function load LUA lib2 {redis.redis.call('ping')}]} e
+ catch {[r function load {#!lua name=lib2
+ redis.redis.call('ping')
+ }]} e
assert_match {*can only be called inside a script invocation*} $e
- catch {[r function load LUA lib2 {redis.redis.pcall('ping')}]} e
+ catch {[r function load {#!lua name=lib2
+ redis.redis.pcall('ping')
+ }]} e
assert_match {*can only be called inside a script invocation*} $e
- catch {[r function load LUA lib2 {redis.redis.setresp(3)}]} e
+ catch {[r function load {#!lua name=lib2
+ redis.redis.setresp(3)
+ }]} e
assert_match {*can only be called inside a script invocation*} $e
- catch {[r function load LUA lib2 {redis.redis.set_repl(redis.redis.REPL_NONE)}]} e
+ catch {[r function load {#!lua name=lib2
+ redis.redis.set_repl(redis.redis.REPL_NONE)
+ }]} e
assert_match {*can only be called inside a script invocation*} $e
catch {[r fcall f2 0]} e
@@ -703,7 +711,7 @@ start_server {tags {"scripting"}} {
} {}
test {LIBRARIES - register function inside a function} {
- r function load LUA lib {
+ r function load {#!lua name=lib
redis.register_function(
'f1',
function(keys, args)
@@ -724,7 +732,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - register library with no functions} {
r function flush
catch {
- r function load LUA lib {
+ r function load {#!lua name=lib
return 1
}
} e
@@ -733,7 +741,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - load timeout} {
catch {
- r function load LUA lib {
+ r function load {#!lua name=lib
local a = 1
while 1 do a = a + 1 end
}
@@ -743,7 +751,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - verify global protection on the load run} {
catch {
- r function load LUA lib {
+ r function load {#!lua name=lib
a = 1
}
} e
@@ -751,7 +759,7 @@ start_server {tags {"scripting"}} {
} {*attempted to create global variable 'a'*}
test {LIBRARIES - named arguments} {
- r function load LUA lib {
+ r function load {#!lua name=lib
redis.register_function{
function_name='f1',
callback=function()
@@ -761,11 +769,11 @@ start_server {tags {"scripting"}} {
}
}
r function list
- } {{library_name lib engine LUA description {} functions {{name f1 description {some desc} flags {}}}}}
+ } {{library_name lib engine LUA functions {{name f1 description {some desc} flags {}}}}}
test {LIBRARIES - named arguments, bad function name} {
catch {
- r function load LUA lib replace {
+ r function load replace {#!lua name=lib
redis.register_function{
function_name=function() return 1 end,
callback=function()
@@ -780,7 +788,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - named arguments, bad callback type} {
catch {
- r function load LUA lib replace {
+ r function load replace {#!lua name=lib
redis.register_function{
function_name='f1',
callback='bad',
@@ -793,7 +801,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - named arguments, bad description} {
catch {
- r function load LUA lib replace {
+ r function load replace {#!lua name=lib
redis.register_function{
function_name='f1',
callback=function()
@@ -808,7 +816,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - named arguments, unknown argument} {
catch {
- r function load LUA lib replace {
+ r function load replace {#!lua name=lib
redis.register_function{
function_name='f1',
callback=function()
@@ -824,7 +832,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - named arguments, missing function name} {
catch {
- r function load LUA lib replace {
+ r function load replace {#!lua name=lib
redis.register_function{
callback=function()
return 'hello'
@@ -838,7 +846,7 @@ start_server {tags {"scripting"}} {
test {LIBRARIES - named arguments, missing callback} {
catch {
- r function load LUA lib replace {
+ r function load replace {#!lua name=lib
redis.register_function{
function_name='f1',
description='desc'
@@ -850,7 +858,7 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test function restore with function name collision} {
r function flush
- r function load lua lib1 {
+ r function load {#!lua name=lib1
local function add1(a)
return a + 1
end
@@ -877,7 +885,7 @@ start_server {tags {"scripting"}} {
r function flush
# load a library with different name but with the same function name
- r function load lua lib1 {
+ r function load {#!lua name=lib1
redis.register_function(
'f6',
function(keys, args)
@@ -885,7 +893,7 @@ start_server {tags {"scripting"}} {
end
)
}
- r function load lua lib2 {
+ r function load {#!lua name=lib2
local function add1(a)
return a + 1
end
@@ -926,14 +934,18 @@ start_server {tags {"scripting"}} {
test {FUNCTION - test function list with code} {
r function flush
- r function load lua library1 {redis.register_function('f6', function(keys, args) return 7 end)}
+ r function load {#!lua name=library1
+ redis.register_function('f6', function(keys, args) return 7 end)
+ }
r function list withcode
- } {{library_name library1 engine LUA description {} functions {{name f6 description {} flags {}}} library_code {redis.register_function('f6', function(keys, args) return 7 end)}}}
+ } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}} library_code {*redis.register_function('f6', function(keys, args) return 7 end)*}}}
test {FUNCTION - test function list with pattern} {
- r function load lua lib1 {redis.register_function('f7', function(keys, args) return 7 end)}
+ r function load {#!lua name=lib1
+ redis.register_function('f7', function(keys, args) return 7 end)
+ }
r function list libraryname library*
- } {{library_name library1 engine LUA description {} functions {{name f6 description {} flags {}}}}}
+ } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}}}}
test {FUNCTION - test function list wrong argument} {
catch {r function list bad_argument} e
@@ -957,12 +969,16 @@ start_server {tags {"scripting"}} {
test {FUNCTION - verify OOM on function load and function restore} {
r function flush
- r function load lua test replace {redis.register_function('f1', function() return 1 end)}
+ r function load replace {#!lua name=test
+ redis.register_function('f1', function() return 1 end)
+ }
set payload [r function dump]
r config set maxmemory 1
r function flush
- catch {r function load lua test replace {redis.register_function('f1', function() return 1 end)}} e
+ catch {r function load replace {#!lua name=test
+ redis.register_function('f1', function() return 1 end)
+ }} e
assert_match {*command not allowed when used memory*} $e
r function flush
@@ -973,11 +989,13 @@ start_server {tags {"scripting"}} {
}
test {FUNCTION - verify allow-omm allows running any command} {
- r FUNCTION load lua f1 replace { redis.register_function{
- function_name='f1',
- callback=function() return redis.call('set', 'x', '1') end,
- flags={'allow-oom'}
- }}
+ r FUNCTION load replace {#!lua name=f1
+ redis.register_function{
+ function_name='f1',
+ callback=function() return redis.call('set', 'x', '1') end,
+ flags={'allow-oom'}
+ }
+ }
r config set maxmemory 1
@@ -990,53 +1008,65 @@ start_server {tags {"scripting"}} {
start_server {tags {"scripting"}} {
test {FUNCTION - wrong flags type named arguments} {
- catch {r function load lua test replace {redis.register_function{
- function_name = 'f1',
- callback = function() return 1 end,
- flags = 'bad flags type'
- }}} e
+ catch {r function load replace {#!lua name=test
+ redis.register_function{
+ function_name = 'f1',
+ callback = function() return 1 end,
+ flags = 'bad flags type'
+ }
+ }} e
set _ $e
} {*flags argument to redis.register_function must be a table representing function flags*}
test {FUNCTION - wrong flag type} {
- catch {r function load lua test replace {redis.register_function{
- function_name = 'f1',
- callback = function() return 1 end,
- flags = {function() return 1 end}
- }}} e
+ catch {r function load replace {#!lua name=test
+ redis.register_function{
+ function_name = 'f1',
+ callback = function() return 1 end,
+ flags = {function() return 1 end}
+ }
+ }} e
set _ $e
} {*unknown flag given*}
test {FUNCTION - unknown flag} {
- catch {r function load lua test replace {redis.register_function{
- function_name = 'f1',
- callback = function() return 1 end,
- flags = {'unknown'}
- }}} e
+ catch {r function load replace {#!lua name=test
+ redis.register_function{
+ function_name = 'f1',
+ callback = function() return 1 end,
+ flags = {'unknown'}
+ }
+ }} e
set _ $e
} {*unknown flag given*}
test {FUNCTION - write script on fcall_ro} {
- r function load lua test replace {redis.register_function{
- function_name = 'f1',
- callback = function() return redis.call('set', 'x', 1) end
- }}
+ r function load replace {#!lua name=test
+ redis.register_function{
+ function_name = 'f1',
+ callback = function() return redis.call('set', 'x', 1) end
+ }
+ }
catch {r fcall_ro f1 0} e
set _ $e
} {*Can not execute a script with write flag using \*_ro command*}
test {FUNCTION - write script with no-writes flag} {
- r function load lua test replace {redis.register_function{
- function_name = 'f1',
- callback = function() return redis.call('set', 'x', 1) end,
- flags = {'no-writes'}
- }}
+ r function load replace {#!lua name=test
+ redis.register_function{
+ function_name = 'f1',
+ callback = function() return redis.call('set', 'x', 1) end,
+ flags = {'no-writes'}
+ }
+ }
catch {r fcall f1 0} e
set _ $e
} {*Write commands are not allowed from read-only scripts*}
test {FUNCTION - deny oom} {
- r FUNCTION load lua test replace { redis.register_function('f1', function() return redis.call('set', 'x', '1') end) }
+ r FUNCTION load replace {#!lua name=test
+ redis.register_function('f1', function() return redis.call('set', 'x', '1') end)
+ }
r config set maxmemory 1
@@ -1047,7 +1077,9 @@ start_server {tags {"scripting"}} {
}
test {FUNCTION - deny oom on no-writes function} {
- r FUNCTION load lua test replace {redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}}}
+ r FUNCTION load replace {#!lua name=test
+ redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}}
+ }
r config set maxmemory 1
@@ -1061,7 +1093,7 @@ start_server {tags {"scripting"}} {
}
test {FUNCTION - allow stale} {
- r FUNCTION load lua test replace {
+ r FUNCTION load replace {#!lua name=test
redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}}
redis.register_function{function_name='f2', callback=function() return 'hello' end, flags={'allow-stale', 'no-writes'}}
redis.register_function{function_name='f3', callback=function() return redis.call('get', 'x') end, flags={'allow-stale', 'no-writes'}}
@@ -1087,7 +1119,7 @@ start_server {tags {"scripting"}} {
} {} {external:skip}
test {FUNCTION - redis version api} {
- r FUNCTION load lua test replace {
+ r FUNCTION load replace {#!lua name=test
local version = redis.REDIS_VERSION_NUM
redis.register_function{function_name='get_version_v1', callback=function()
@@ -1106,12 +1138,12 @@ start_server {tags {"scripting"}} {
test {FUNCTION - function stats} {
r FUNCTION FLUSH
- r FUNCTION load lua test1 {
+ r FUNCTION load {#!lua name=test1
redis.register_function('f1', function() return 1 end)
redis.register_function('f2', function() return 1 end)
}
- r FUNCTION load lua test2 {
+ r FUNCTION load {#!lua name=test2
redis.register_function('f3', function() return 1 end)
}
@@ -1132,4 +1164,38 @@ start_server {tags {"scripting"}} {
r function flush
r function stats
} {running_script {} engines {LUA {libraries_count 0 functions_count 0}}}
+
+ test {FUNCTION - function test empty engine} {
+ catch {r function load replace {#! name=test
+ redis.register_function('foo', function() return 1 end)
+ }} e
+ set _ $e
+ } {ERR Engine '' not found}
+
+ test {FUNCTION - function test unknown metadata value} {
+ catch {r function load replace {#!lua name=test foo=bar
+ redis.register_function('foo', function() return 1 end)
+ }} e
+ set _ $e
+ } {ERR Invalid metadata value given: foo=bar}
+
+ test {FUNCTION - function test no name} {
+ catch {r function load replace {#!lua
+ redis.register_function('foo', function() return 1 end)
+ }} e
+ set _ $e
+ } {ERR Library name was not given}
+
+ test {FUNCTION - function test multiple names} {
+ catch {r function load replace {#!lua name=foo name=bar
+ redis.register_function('foo', function() return 1 end)
+ }} e
+ set _ $e
+ } {ERR Invalid metadata value, name argument was given multiple times}
+
+ test {FUNCTION - function test name with quotes} {
+ r function load replace {#!lua name="foo"
+ redis.register_function('foo', function() return 1 end)
+ }
+ } {foo}
}
diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl
index 59c96b5ad..c530a31d3 100644
--- a/tests/unit/introspection.tcl
+++ b/tests/unit/introspection.tcl
@@ -184,6 +184,7 @@ start_server {tags {"introspection"}} {
always-show-logo
syslog-enabled
cluster-enabled
+ disable-thp
aclfile
unixsocket
pidfile
diff --git a/tests/unit/memefficiency.tcl b/tests/unit/memefficiency.tcl
index e6663ce06..cef4b8fdf 100644
--- a/tests/unit/memefficiency.tcl
+++ b/tests/unit/memefficiency.tcl
@@ -82,7 +82,7 @@ start_server {tags {"defrag external:skip"} overrides {appendonly yes auto-aof-r
fail "defrag didn't stop."
}
- # Test the the fragmentation is lower.
+ # Test the fragmentation is lower.
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
set max_latency 0
@@ -226,7 +226,7 @@ start_server {tags {"defrag external:skip"} overrides {appendonly yes auto-aof-r
fail "defrag didn't stop."
}
- # test the the fragmentation is lower
+ # test the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
if {$::verbose} {
puts "used [s allocator_allocated]"
@@ -336,7 +336,7 @@ start_server {tags {"defrag external:skip"} overrides {appendonly yes auto-aof-r
fail "defrag didn't stop."
}
- # test the the fragmentation is lower
+ # test the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
set max_latency 0
@@ -433,7 +433,7 @@ start_server {tags {"defrag external:skip"} overrides {appendonly yes auto-aof-r
fail "defrag didn't stop."
}
- # test the the fragmentation is lower
+ # test the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
@@ -553,7 +553,7 @@ start_server {tags {"defrag external:skip"} overrides {appendonly yes auto-aof-r
fail "defrag didn't stop."
}
- # test the the fragmentation is lower
+ # test the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
diff --git a/tests/unit/moduleapi/aclcheck.tcl b/tests/unit/moduleapi/aclcheck.tcl
index 953f4bf05..5adf65371 100644
--- a/tests/unit/moduleapi/aclcheck.tcl
+++ b/tests/unit/moduleapi/aclcheck.tcl
@@ -62,10 +62,13 @@ start_server {tags {"modules acl"}} {
# 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 {ERR acl verification failed, can't access at least one of the keys mentioned in the command arguments.} {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}
+ assert_error {ERR acl verification failed, can't access at least one of the keys mentioned in the command arguments.} {r aclcheck.rm_call_with_errors set z 5}
assert_error {*NOPERM*} {r aclcheck.rm_call set z 6 get}
+ assert_error {ERR acl verification failed, can't access at least one of the keys mentioned in the command arguments.} {r aclcheck.rm_call_with_errors set z 6 get}
# verify that new log entry added
set entry [lindex [r ACL LOG] 0]
@@ -77,6 +80,8 @@ start_server {tags {"modules acl"}} {
r acl setuser default -set
catch {r aclcheck.rm_call set x 5} e
assert_match {*NOPERM*} $e
+ catch {r aclcheck.rm_call_with_errors set x 5} e
+ assert_match {ERR acl verification failed, can't run this command or subcommand.} $e
# verify that new log entry added
set entry [lindex [r ACL LOG] 0]
diff --git a/tests/unit/moduleapi/auth.tcl b/tests/unit/moduleapi/auth.tcl
index 6d8c3bd6a..c7c2def77 100644
--- a/tests/unit/moduleapi/auth.tcl
+++ b/tests/unit/moduleapi/auth.tcl
@@ -68,6 +68,22 @@ start_server {tags {"modules"}} {
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/tests/unit/moduleapi/blockedclient.tcl b/tests/unit/moduleapi/blockedclient.tcl
index ea2d6f5a4..de3cf5946 100644
--- a/tests/unit/moduleapi/blockedclient.tcl
+++ b/tests/unit/moduleapi/blockedclient.tcl
@@ -184,17 +184,17 @@ start_server {tags {"modules"}} {
r config resetstat
# simple module command that replies with string error
- assert_error "NULL reply returned" {r do_rm_call hgetalllll}
- assert_equal [errorrstat NULL r] {count=1}
+ assert_error "ERR Unknown Redis command 'hgetalllll'." {r do_rm_call hgetalllll}
+ assert_equal [errorrstat ERR r] {count=1}
# module command that replies with string error from bg thread
assert_error "NULL reply returned" {r do_bg_rm_call hgetalllll}
- assert_equal [errorrstat NULL r] {count=2}
+ 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=1}
+ assert_equal [errorrstat ERR r] {count=2}
# RM_Call that propagates an error
assert_error "WRONGTYPE*" {r do_rm_call hgetall x}
diff --git a/tests/unit/moduleapi/blockonkeys.tcl b/tests/unit/moduleapi/blockonkeys.tcl
index 75191b3c7..094bcc0c0 100644
--- a/tests/unit/moduleapi/blockonkeys.tcl
+++ b/tests/unit/moduleapi/blockonkeys.tcl
@@ -168,6 +168,38 @@ start_server {tags {"modules"}} {
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 until clients are actually blocked
+ wait_for_condition 50 100 {
+ [s 0 blocked_clients] eq {1}
+ } else {
+ fail "Clients are not blocked"
+ }
+ 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 until clients are actually blocked
+ wait_for_condition 50 100 {
+ [s 0 blocked_clients] eq {1}
+ } else {
+ fail "Clients are not blocked"
+ }
+ 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]
diff --git a/tests/unit/moduleapi/cluster.tcl b/tests/unit/moduleapi/cluster.tcl
index b2d2df899..f1238992d 100644
--- a/tests/unit/moduleapi/cluster.tcl
+++ b/tests/unit/moduleapi/cluster.tcl
@@ -20,8 +20,10 @@ proc csi {args} {
set testmodule [file normalize tests/modules/blockonkeys.so]
set testmodule_nokey [file normalize tests/modules/blockonbackground.so]
+set testmodule_blockedclient [file normalize tests/modules/blockedclient.so]
# make sure the test infra won't use SELECT
+set old_singledb $::singledb
set ::singledb 1
# cluster creation is complicated with TLS, and the current tests don't really need that coverage
@@ -43,6 +45,10 @@ start_server [list overrides $base_conf] {
$node2 module load $testmodule_nokey
$node3 module load $testmodule_nokey
+ $node1 module load $testmodule_blockedclient
+ $node2 module load $testmodule_blockedclient
+ $node3 module load $testmodule_blockedclient
+
test {Create 3 node cluster} {
exec src/redis-cli --cluster-yes --cluster create \
127.0.0.1:[srv 0 port] \
@@ -193,6 +199,10 @@ start_server [list overrides $base_conf] {
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}
+ }
+
exec kill -SIGCONT $node3_pid
$node1_rd close
$node2_rd close
@@ -202,4 +212,6 @@ start_server [list overrides $base_conf] {
}
}
-} ;# tags \ No newline at end of file
+} ;# tags
+
+set ::singledb $old_singledb
diff --git a/tests/unit/moduleapi/hooks.tcl b/tests/unit/moduleapi/hooks.tcl
index cb36c9f71..814f31bc0 100644
--- a/tests/unit/moduleapi/hooks.tcl
+++ b/tests/unit/moduleapi/hooks.tcl
@@ -150,13 +150,17 @@ tags "modules" {
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
}
-
}
}
diff --git a/tests/unit/moduleapi/moduleconfigs.tcl b/tests/unit/moduleapi/moduleconfigs.tcl
new file mode 100644
index 000000000..01aa1e88e
--- /dev/null
+++ b/tests/unit/moduleapi/moduleconfigs.tcl
@@ -0,0 +1,234 @@
+set testmodule [file normalize tests/modules/moduleconfigs.so]
+set testmoduletwo [file normalize tests/modules/moduleconfigstwo.so]
+
+start_server {tags {"modules"}} {
+ r module load $testmodule
+ test {Config get commands work} {
+ # Make sure config get module config works
+ assert_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
+ 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"
+ }
+
+ 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.numeric -2
+ assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2"
+ }
+
+ 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 four]} e
+ assert_match {*argument must be one of the following*} $e
+ }
+
+ 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.numeric] "moduleconfigs.numeric -1"
+ 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
+ assert_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
+ 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.numeric] "moduleconfigs.numeric -1"
+ }
+
+ 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
+ assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
+ 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
+ }
+
+ 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_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
+ 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 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.numeric] "moduleconfigs.numeric -1"
+ 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_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
+ 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
+ set nomodload [start_server [list overrides [list moduleconfigs.string "hello"]]]
+ wait_for_condition 100 50 {
+ ! [is_alive $nomodload]
+ } else {
+ fail "startup should've failed with no load and module configs supplied"
+ }
+ set stdout [dict get $nomodload stdout]
+ assert_equal [count_message_lines $stdout "Module Configuration detected without loadmodule directive or no ApplyConfig call: aborting"] 1
+
+ # Bad config value
+ set badconfig [start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "rejectisfreed"]]]
+ wait_for_condition 100 50 {
+ ! [is_alive $badconfig]
+ } else {
+ fail "startup with bad moduleconfigs should've failed"
+ }
+ set stdout [dict get $badconfig stdout]
+ assert_equal [count_message_lines $stdout "Issue during loading of configuration moduleconfigs.string : Cannot set string to 'rejectisfreed'"] 1
+
+ set noload [start_server [list overrides [list loadmodule "$testmodule noload" moduleconfigs.string "hello"]]]
+ wait_for_condition 100 50 {
+ ! [is_alive $noload]
+ } else {
+ fail "startup with moduleconfigs and no loadconfigs call should've failed"
+ }
+ set stdout [dict get $noload stdout]
+ assert_equal [count_message_lines $stdout "Module Configurations were not set, likely a missing LoadConfigs call. Unloading the module."] 1
+
+ start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two]] {
+ 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.numeric] "moduleconfigs.numeric -1"
+ assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
+ }
+ }
+}
+
diff --git a/tests/unit/other.tcl b/tests/unit/other.tcl
index f4d540fcf..258ef2f6e 100644
--- a/tests/unit/other.tcl
+++ b/tests/unit/other.tcl
@@ -332,7 +332,8 @@ start_server {tags {"other external:skip"}} {
# Hash table should not rehash
assert_no_match "*table size: 8192*" [r debug HTSTATS 9]
exec kill -9 [get_child_pid 0]
- after 200
+ waitForBgsave r
+ after 200 ;# waiting for serverCron
# Hash table should rehash since there is no child process,
# size is power of two and over 4098, so it is 8192
diff --git a/tests/unit/pause.tcl b/tests/unit/pause.tcl
index 99fc7214d..f7ade2a10 100644
--- a/tests/unit/pause.tcl
+++ b/tests/unit/pause.tcl
@@ -86,6 +86,14 @@ start_server {tags {"pause network"}} {
$rd close
}
+ test "Test may-replicate commands are rejected in ro script by pause RO" {
+ r client PAUSE 60000 WRITE
+ assert_error {ERR May-replicate commands are not allowed when client pause write*} {
+ r EVAL_RO "return redis.call('publish','ch','msg')" 0
+ }
+ r client unpause
+ }
+
test "Test multiple clients can be queued up and unblocked" {
r client PAUSE 60000 WRITE
set clients [list [redis_deferring_client] [redis_deferring_client] [redis_deferring_client]]
diff --git a/tests/unit/replybufsize.tcl b/tests/unit/replybufsize.tcl
index 9377a8fd3..933189eb3 100644
--- a/tests/unit/replybufsize.tcl
+++ b/tests/unit/replybufsize.tcl
@@ -3,7 +3,7 @@ proc get_reply_buffer_size {cname} {
set clients [split [string trim [r client list]] "\r\n"]
set c [lsearch -inline $clients *name=$cname*]
if {![regexp rbs=(\[a-zA-Z0-9-\]+) $c - rbufsize]} {
- error "field rbus not found in $c"
+ error "field rbs not found in $c"
}
return $rbufsize
}
@@ -12,7 +12,7 @@ start_server {tags {"replybufsize"}} {
test {verify reply buffer limits} {
# In order to reduce test time we can set the peak reset time very low
- r debug replybuffer-peak-reset-time 100
+ r debug replybuffer peak-reset-time 100
# Create a simple idle test client
variable tc [redis_client]
@@ -29,7 +29,7 @@ start_server {tags {"replybufsize"}} {
r set bigval [string repeat x 32768]
# In order to reduce test time we can set the peak reset time very low
- r debug replybuffer-peak-reset-time never
+ r debug replybuffer peak-reset-time never
wait_for_condition 10 100 {
[$tc get bigval ; get_reply_buffer_size test_client] >= 16384 && [get_reply_buffer_size test_client] < 32768
@@ -39,7 +39,7 @@ start_server {tags {"replybufsize"}} {
}
# Restore the peak reset time to default
- r debug replybuffer-peak-reset-time reset
+ r debug replybuffer peak-reset-time reset
$tc close
} {0} {needs:debug}
diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl
index 6c40844c3..d9729b7bd 100644
--- a/tests/unit/scripting.tcl
+++ b/tests/unit/scripting.tcl
@@ -15,17 +15,25 @@ if {$is_eval == 1} {
}
} else {
proc run_script {args} {
- r function load LUA test replace [format "redis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0]]
+ r function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0]]
+ if {[r readingraw] eq 1} {
+ # read name
+ assert_equal {test} [r read]
+ }
r fcall test {*}[lrange $args 1 end]
}
proc run_script_ro {args} {
- r function load LUA test replace [format "redis.register_function{function_name='test', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0]]
+ r function load replace [format "#!lua name=test\nredis.register_function{function_name='test', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0]]
+ if {[r readingraw] eq 1} {
+ # read name
+ assert_equal {test} [r read]
+ }
r fcall_ro test {*}[lrange $args 1 end]
}
proc run_script_on_connection {args} {
set rd [lindex $args 0]
- $rd function load LUA test replace [format "redis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 1]]
- # read the ok reply of function create
+ $rd function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 1]]
+ # read name
$rd read
$rd fcall test {*}[lrange $args 2 end]
}
@@ -784,7 +792,7 @@ start_server {tags {"scripting"}} {
set buf "*3\r\n\$4\r\neval\r\n\$33\r\nwhile 1 do redis.call('ping') end\r\n\$1\r\n0\r\n"
append buf "*1\r\n\$4\r\nping\r\n"
} else {
- set buf "*6\r\n\$8\r\nfunction\r\n\$4\r\nload\r\n\$3\r\nlua\r\n\$4\r\ntest\r\n\$7\r\nreplace\r\n\$81\r\nredis.register_function('test', function() while 1 do redis.call('ping') end end)\r\n"
+ set buf "*4\r\n\$8\r\nfunction\r\n\$4\r\nload\r\n\$7\r\nreplace\r\n\$97\r\n#!lua name=test\nredis.register_function('test', function() while 1 do redis.call('ping') end end)\r\n"
append buf "*3\r\n\$5\r\nfcall\r\n\$4\r\ntest\r\n\$1\r\n0\r\n"
append buf "*1\r\n\$4\r\nping\r\n"
}
@@ -808,8 +816,8 @@ start_server {tags {"scripting"}} {
assert_equal [r ping] "PONG"
if {$is_eval == 0} {
- # read the ok reply of function create
- assert_match {OK} [$rd read]
+ # read the function name
+ assert_match {test} [$rd read]
}
catch {$rd read} res
@@ -1399,6 +1407,19 @@ start_server {tags {"scripting"}} {
r config set replica-serve-stale-data yes
set _ {}
} {} {external:skip}
+
+ test "reject script do not cause a Lua stack leak" {
+ r config set maxmemory 1
+ for {set i 0} {$i < 50} {incr i} {
+ assert_error {OOM allow-oom flag is not set on the script, can not run it when used memory > 'maxmemory'} {r eval {#!lua
+ return 1
+ } 0}
+ }
+ r config set maxmemory 0
+ assert_equal [r eval {#!lua
+ return 1
+ } 0] 1
+ }
}
# Additional eval only tests
diff --git a/tests/unit/shutdown.tcl b/tests/unit/shutdown.tcl
index 5c618d285..d0a8ffb6d 100644
--- a/tests/unit/shutdown.tcl
+++ b/tests/unit/shutdown.tcl
@@ -66,3 +66,39 @@ start_server {tags {"shutdown external:skip"}} {
}
}
}
+
+start_server {tags {"shutdown external:skip"}} {
+ set pid [s process_id]
+ set dump_rdb [file join [lindex [r config get dir] 1] dump.rdb]
+
+ test {RDB save will be failed in shutdown} {
+ for {set i 0} {$i < 20} {incr i} {
+ r set $i $i
+ }
+
+ # create a folder called 'dump.rdb' to trigger temp-rdb rename failure
+ # and it will cause rdb save to fail eventually.
+ if {[file exists $dump_rdb]} {
+ exec rm -f $dump_rdb
+ }
+ exec mkdir -p $dump_rdb
+ }
+ test {SHUTDOWN will abort if rdb save failed on signal} {
+ # trigger a shutdown which will save an rdb
+ exec kill -SIGINT $pid
+ wait_for_log_messages 0 {"*Error trying to save the DB, can't exit*"} 0 100 10
+ }
+ test {SHUTDOWN will abort if rdb save failed on shutdown command} {
+ catch {[r shutdown]} err
+ assert_match {*Errors trying to SHUTDOWN*} $err
+ # make sure the server is still alive
+ assert_equal [r ping] {PONG}
+ }
+ test {SHUTDOWN can proceed if shutdown command was with nosave} {
+ catch {[r shutdown nosave]}
+ wait_for_log_messages 0 {"*ready to exit, bye bye*"} 0 100 10
+ }
+ test {Clean up rdb same named folder} {
+ exec rm -r $dump_rdb
+ }
+}
diff --git a/tests/unit/type/stream-cgroups.tcl b/tests/unit/type/stream-cgroups.tcl
index 27cbc686e..d9bb4e760 100644
--- a/tests/unit/type/stream-cgroups.tcl
+++ b/tests/unit/type/stream-cgroups.tcl
@@ -205,6 +205,113 @@ start_server {
$rd close
}
+ test {Blocking XREADGROUP: key deleted} {
+ r DEL mystream
+ r XADD mystream 666 f v
+ r XGROUP CREATE mystream mygroup $
+ set rd [redis_deferring_client]
+ $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
+ r DEL mystream
+ assert_error "*no longer exists*" {$rd read}
+ $rd close
+ }
+
+ test {Blocking XREADGROUP: key type changed with SET} {
+ r DEL mystream
+ r XADD mystream 666 f v
+ r XGROUP CREATE mystream mygroup $
+ set rd [redis_deferring_client]
+ $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
+ r SET mystream val1
+ assert_error "*no longer exists*" {$rd read}
+ $rd close
+ }
+
+ test {Blocking XREADGROUP: key type changed with transaction} {
+ r DEL mystream
+ r XADD mystream 666 f v
+ r XGROUP CREATE mystream mygroup $
+ set rd [redis_deferring_client]
+ $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
+ r MULTI
+ r DEL mystream
+ r SADD mystream e1
+ r EXEC
+ assert_error "*no longer exists*" {$rd read}
+ $rd close
+ }
+
+ test {Blocking XREADGROUP: flushed DB} {
+ r DEL mystream
+ r XADD mystream 666 f v
+ r XGROUP CREATE mystream mygroup $
+ set rd [redis_deferring_client]
+ $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
+ r FLUSHALL
+ assert_error "*no longer exists*" {$rd read}
+ $rd close
+ }
+
+ test {Blocking XREADGROUP: swapped DB, key doesn't exist} {
+ r SELECT 4
+ r FLUSHDB
+ r SELECT 9
+ r DEL mystream
+ r XADD mystream 666 f v
+ r XGROUP CREATE mystream mygroup $
+ set rd [redis_deferring_client]
+ $rd SELECT 9
+ $rd read
+ $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
+ r SWAPDB 4 9
+ assert_error "*no longer exists*" {$rd read}
+ $rd close
+ } {0} {external:skip}
+
+ test {Blocking XREADGROUP: swapped DB, key is not a stream} {
+ r SELECT 4
+ r FLUSHDB
+ r LPUSH mystream e1
+ r SELECT 9
+ r DEL mystream
+ r XADD mystream 666 f v
+ r XGROUP CREATE mystream mygroup $
+ set rd [redis_deferring_client]
+ $rd SELECT 9
+ $rd read
+ $rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
+ r SWAPDB 4 9
+ assert_error "*no longer exists*" {$rd read}
+ $rd close
+ } {0} {external:skip}
+
+ test {Blocking XREAD: key deleted} {
+ r DEL mystream
+ r XADD mystream 666 f v
+ set rd [redis_deferring_client]
+ $rd XREAD BLOCK 0 STREAMS mystream "$"
+ r DEL mystream
+
+ r XADD mystream 667 f v
+ set res [$rd read]
+ assert_equal [lindex $res 0 1 0] {667-0 {f v}}
+ $rd close
+ }
+
+ test {Blocking XREAD: key type changed with SET} {
+ r DEL mystream
+ r XADD mystream 666 f v
+ set rd [redis_deferring_client]
+ $rd XREAD BLOCK 0 STREAMS mystream "$"
+ r SET mystream val1
+
+ r DEL mystream
+ r XADD mystream 667 f v
+ set res [$rd read]
+ assert_equal [lindex $res 0 1 0] {667-0 {f v}}
+ $rd close
+ }
+
test {Blocking XREADGROUP for stream that ran dry (issue #5299)} {
set rd [redis_deferring_client]
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 10945674e..3ccfa61ab 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -2399,4 +2399,12 @@ start_server {tags {"zset"}} {
r config set zset-max-ziplist-value $original_max_value
}
+ test {zset score double range} {
+ set dblmax 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.00000000000000000
+ r del zz
+ r zadd zz $dblmax dblmax
+ assert_encoding listpack zz
+ r zscore zz dblmax
+ } {1.7976931348623157e+308}
+
}