diff options
Diffstat (limited to 'tests')
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} + } |