diff options
Diffstat (limited to 'tests')
48 files changed, 1048 insertions, 236 deletions
diff --git a/tests/assets/default.conf b/tests/assets/default.conf index 42297903f..227ab5e7f 100644 --- a/tests/assets/default.conf +++ b/tests/assets/default.conf @@ -30,3 +30,5 @@ activerehashing yes enable-protected-configs yes enable-debug-command yes enable-module-command yes + +propagation-error-behavior panic
\ No newline at end of file diff --git a/tests/integration/aof-multi-part.tcl b/tests/integration/aof-multi-part.tcl index 982b6907b..74f6b4949 100644 --- a/tests/integration/aof-multi-part.tcl +++ b/tests/integration/aof-multi-part.tcl @@ -1104,7 +1104,11 @@ tags {"external:skip"} { # Set a key so that AOFRW can be delayed r set k v - # Let AOFRW fail two times, this will trigger AOFRW limit + # Let AOFRW fail 3 times, this will trigger AOFRW limit + r bgrewriteaof + catch {exec kill -9 [get_child_pid 0]} + waitForBgrewriteaof r + r bgrewriteaof catch {exec kill -9 [get_child_pid 0]} waitForBgrewriteaof r @@ -1118,6 +1122,7 @@ tags {"external:skip"} { {file appendonly.aof.6.incr.aof seq 6 type i} {file appendonly.aof.7.incr.aof seq 7 type i} {file appendonly.aof.8.incr.aof seq 8 type i} + {file appendonly.aof.9.incr.aof seq 9 type i} } # Write 1KB data to trigger AOFRW @@ -1137,6 +1142,7 @@ tags {"external:skip"} { {file appendonly.aof.6.incr.aof seq 6 type i} {file appendonly.aof.7.incr.aof seq 7 type i} {file appendonly.aof.8.incr.aof seq 8 type i} + {file appendonly.aof.9.incr.aof seq 9 type i} } # Turn off auto rewrite @@ -1154,11 +1160,11 @@ tags {"external:skip"} { waitForBgrewriteaof r # Can create New INCR AOF - assert_equal 1 [check_file_exist $aof_dirpath "${aof_basename}.9${::incr_aof_sufix}${::aof_format_suffix}"] + assert_equal 1 [check_file_exist $aof_dirpath "${aof_basename}.10${::incr_aof_sufix}${::aof_format_suffix}"] assert_aof_manifest_content $aof_manifest_file { {file appendonly.aof.11.base.rdb seq 11 type b} - {file appendonly.aof.9.incr.aof seq 9 type i} + {file appendonly.aof.10.incr.aof seq 10 type i} } set d1 [r debug digest] @@ -1166,5 +1172,161 @@ tags {"external:skip"} { set d2 [r debug digest] assert {$d1 eq $d2} } + + start_server {overrides {aof-use-rdb-preamble {yes} appendonly {no}}} { + set dir [get_redis_dir] + set aof_basename "appendonly.aof" + set aof_dirname "appendonlydir" + set aof_dirpath "$dir/$aof_dirname" + set aof_manifest_name "$aof_basename$::manifest_suffix" + set aof_manifest_file "$dir/$aof_dirname/$aof_manifest_name" + + set master [srv 0 client] + set master_host [srv 0 host] + set master_port [srv 0 port] + + test "AOF will open a temporary INCR AOF to accumulate data until the first AOFRW success when AOF is dynamically enabled" { + r config set save "" + # Increase AOFRW execution time to give us enough time to kill it + r config set rdb-key-save-delay 10000000 + + # Start write load + set load_handle0 [start_write_load $master_host $master_port 10] + + wait_for_condition 50 100 { + [r dbsize] > 0 + } else { + fail "No write load detected." + } + + # Enable AOF will trigger an initialized AOFRW + r config set appendonly yes + # Let AOFRW fail + assert_equal 1 [s aof_rewrite_in_progress] + set pid1 [get_child_pid 0] + catch {exec kill -9 $pid1} + + # Wait for AOFRW to exit and delete temp incr aof + wait_for_condition 1000 100 { + [count_log_message 0 "Removing the temp incr aof file"] == 1 + } else { + fail "temp aof did not delete" + } + + # Make sure manifest file is not created + assert_equal 0 [check_file_exist $aof_dirpath $aof_manifest_name] + # Make sure BASE AOF is not created + assert_equal 0 [check_file_exist $aof_dirpath "${aof_basename}.1${::base_aof_sufix}${::rdb_format_suffix}"] + + # Make sure the next AOFRW has started + wait_for_condition 1000 50 { + [s aof_rewrite_in_progress] == 1 + } else { + fail "aof rewrite did not scheduled" + } + + # Do a successful AOFRW + set total_forks [s total_forks] + r config set rdb-key-save-delay 0 + catch {exec kill -9 [get_child_pid 0]} + + # Make sure the next AOFRW has started + wait_for_condition 1000 10 { + [s total_forks] == [expr $total_forks + 1] + } else { + fail "aof rewrite did not scheduled" + } + waitForBgrewriteaof r + + assert_equal 2 [count_log_message 0 "Removing the temp incr aof file"] + + # BASE and INCR AOF are successfully created + assert_aof_manifest_content $aof_manifest_file { + {file appendonly.aof.1.base.rdb seq 1 type b} + {file appendonly.aof.1.incr.aof seq 1 type i} + } + + stop_write_load $load_handle0 + wait_load_handlers_disconnected + + set d1 [r debug digest] + r debug loadaof + set d2 [r debug digest] + assert {$d1 eq $d2} + + # Dynamic disable AOF again + r config set appendonly no + + # Disabling AOF does not delete previous AOF files + r debug loadaof + set d2 [r debug digest] + assert {$d1 eq $d2} + + assert_equal 0 [s rdb_changes_since_last_save] + r config set rdb-key-save-delay 10000000 + set load_handle0 [start_write_load $master_host $master_port 10] + wait_for_condition 50 100 { + [s rdb_changes_since_last_save] > 0 + } else { + fail "No write load detected." + } + + # Re-enable AOF + r config set appendonly yes + + # Let AOFRW fail + assert_equal 1 [s aof_rewrite_in_progress] + set pid1 [get_child_pid 0] + catch {exec kill -9 $pid1} + + # Wait for AOFRW to exit and delete temp incr aof + wait_for_condition 1000 100 { + [count_log_message 0 "Removing the temp incr aof file"] == 3 + } else { + fail "temp aof did not delete 3 times" + } + + # Make sure no new incr AOF was created + assert_aof_manifest_content $aof_manifest_file { + {file appendonly.aof.1.base.rdb seq 1 type b} + {file appendonly.aof.1.incr.aof seq 1 type i} + } + + # Make sure the next AOFRW has started + wait_for_condition 1000 50 { + [s aof_rewrite_in_progress] == 1 + } else { + fail "aof rewrite did not scheduled" + } + + # Do a successful AOFRW + set total_forks [s total_forks] + r config set rdb-key-save-delay 0 + catch {exec kill -9 [get_child_pid 0]} + + wait_for_condition 1000 10 { + [s total_forks] == [expr $total_forks + 1] + } else { + fail "aof rewrite did not scheduled" + } + waitForBgrewriteaof r + + assert_equal 4 [count_log_message 0 "Removing the temp incr aof file"] + + # New BASE and INCR AOF are successfully created + assert_aof_manifest_content $aof_manifest_file { + {file appendonly.aof.2.base.rdb seq 2 type b} + {file appendonly.aof.2.incr.aof seq 2 type i} + } + + stop_write_load $load_handle0 + wait_load_handlers_disconnected + + set d1 [r debug digest] + r debug loadaof + set d2 [r debug digest] + assert {$d1 eq $d2} + } + } } } diff --git a/tests/integration/corrupt-dump.tcl b/tests/integration/corrupt-dump.tcl index d2491306a..1e54f26a1 100644 --- a/tests/integration/corrupt-dump.tcl +++ b/tests/integration/corrupt-dump.tcl @@ -632,7 +632,6 @@ test {corrupt payload: fuzzer findings - stream PEL without consumer} { r debug set-skip-checksum-validation 1 catch {r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x3B\x40\x42\x19\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x20\x10\x00\x00\x20\x01\x00\x01\x20\x03\x02\x05\x01\x03\x20\x05\x40\x00\x04\x82\x5F\x31\x03\x05\x60\x19\x80\x32\x02\x05\x01\xFF\x02\x81\x00\x00\x01\x7B\x08\xF0\xB2\x34\x02\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x7B\x08\xF0\xB2\x34\x01\x01\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x01\x35\xB2\xF0\x08\x7B\x01\x00\x00\x01\x01\x13\x41\x6C\x69\x63\x65\x35\xB2\xF0\x08\x7B\x01\x00\x00\x01\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x01\x09\x00\x28\x2F\xE0\xC5\x04\xBB\xA7\x31"} err assert_match "*Bad data format*" $err - #catch {r XINFO STREAM _stream FULL } r ping } } @@ -674,7 +673,6 @@ test {corrupt payload: fuzzer findings - stream with non-integer entry id} { r config set sanitize-dump-payload yes r debug set-skip-checksum-validation 1 catch {r restore _streambig 0 "\x0F\x03\x10\x00\x00\x01\x7B\x13\x34\xC3\xB2\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4F\x40\x5C\x18\x5C\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x80\x20\x01\x00\x01\x20\x03\x00\x05\x20\x1C\x40\x09\x05\x01\x01\x82\x5F\x31\x03\x80\x0D\x00\x02\x20\x0D\x00\x02\xA0\x19\x00\x03\x20\x0B\x02\x82\x5F\x33\xA0\x19\x00\x04\x20\x0D\x00\x04\x20\x19\x00\xFF\x10\x00\x00\x01\x7B\x13\x34\xC3\xB2\x00\x00\x00\x00\x00\x00\x00\x05\xC3\x40\x56\x40\x61\x18\x61\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x06\x01\x01\x82\x5F\x35\x03\x05\x20\x1E\x40\x0B\x03\x01\x01\x06\x01\x40\x0B\x03\x01\x01\xDF\xFB\x20\x05\x02\x82\x5F\x37\x60\x1A\x20\x0E\x00\xFC\x20\x05\x00\x08\xC0\x1B\x00\xFD\x20\x0C\x02\x82\x5F\x39\x20\x1B\x00\xFF\x10\x00\x00\x01\x7B\x13\x34\xC3\xB3\x00\x00\x00\x00\x00\x00\x00\x03\xC3\x3D\x40\x4A\x18\x4A\x00\x00\x00\x15\x00\x02\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x40\x00\x00\x05\x60\x07\x02\xDF\xFD\x02\xC0\x23\x09\x01\x01\x86\x75\x6E\x69\x71\x75\x65\x07\xA0\x2D\x02\x08\x01\xFF\x0C\x81\x00\x00\x01\x7B\x13\x34\xC3\xB4\x00\x00\x09\x00\x9D\xBD\xD5\xB9\x33\xC4\xC5\xFF"} err - #catch {r XINFO STREAM _streambig FULL } assert_match "*Bad data format*" $err r ping } @@ -782,5 +780,15 @@ test {corrupt payload: fuzzer findings - zset zslInsert with a NAN score} { } } +test {corrupt payload: fuzzer findings - streamLastValidID panic} { + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { + r config set sanitize-dump-payload yes + r debug set-skip-checksum-validation 1 + catch {r restore _streambig 0 "\x13\xC0\x10\x00\x00\x01\x80\x20\x48\xA0\x33\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4F\x40\x5C\x18\x5C\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x00\x01\x20\x03\x00\x05\x20\x1C\x40\x09\x05\x01\x01\x82\x5F\x31\x03\x80\x0D\x00\x02\x20\x0D\x00\x02\xA0\x19\x00\x03\x20\x0B\x02\x82\x5F\x33\x60\x19\x40\x2F\x02\x01\x01\x04\x20\x19\x00\xFF\x10\x00\x00\x01\x80\x20\x48\xA0\x34\x00\x00\x00\x00\x00\x00\x00\x01\xC3\x40\x51\x40\x5E\x18\x5E\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x06\x01\x01\x82\x5F\x35\x03\x05\x20\x1E\x40\x0B\x03\x01\x01\x06\x01\x80\x0B\x00\x02\x20\x0B\x02\x82\x5F\x37\xA0\x19\x00\x03\x20\x0D\x00\x08\xA0\x19\x00\x04\x20\x0B\x02\x82\x5F\x39\x20\x19\x00\xFF\x10\x00\x00\x01\x80\x20\x48\xA0\x34\x00\x00\x00\x00\x00\x00\x00\x06\xC3\x3D\x40\x4A\x18\x4A\x00\x00\x00\x15\x00\x02\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x40\x00\x00\x05\x60\x07\x02\xDF\xFA\x02\xC0\x23\x09\x01\x01\x86\x75\x6E\x69\x71\x75\x65\x07\xA0\x2D\x02\x08\x01\xFF\x0C\x81\x00\x00\x01\x80\x20\x48\xA0\x35\x00\x81\x00\x00\x01\x80\x20\x48\xA0\x33\x00\x00\x00\x0C\x00\x0A\x00\x34\x8B\x0E\x5B\x42\xCD\xD6\x08"} err + assert_match "*Bad data format*" $err + r ping + } +} + } ;# tags diff --git a/tests/integration/replication-4.tcl b/tests/integration/replication-4.tcl index 281d5a8eb..9f4281f7c 100644 --- a/tests/integration/replication-4.tcl +++ b/tests/integration/replication-4.tcl @@ -195,3 +195,48 @@ start_server {tags {"repl external:skip"}} { } } } + +start_server {tags {"repl external:skip"}} { + start_server {} { + set master [srv -1 client] + set master_host [srv -1 host] + set master_port [srv -1 port] + set replica [srv 0 client] + + test {First server should have role slave after SLAVEOF} { + $replica slaveof $master_host $master_port + wait_for_condition 50 100 { + [s 0 role] eq {slave} + } else { + fail "Replication not started." + } + wait_for_sync $replica + } + + test {Data divergence can happen under default conditions} { + $replica config set propagation-error-behavior ignore + $master debug replicate fake-command-1 + + # Wait for replication to normalize + $master set foo bar2 + $master wait 1 2000 + + # Make sure we triggered the error, by finding the critical + # message and the fake command. + assert_equal [count_log_message 0 "fake-command-1"] 1 + assert_equal [count_log_message 0 "== CRITICAL =="] 1 + } + + test {Data divergence is allowed on writable replicas} { + $replica config set replica-read-only no + $replica set number2 foo + $master incrby number2 1 + $master wait 1 2000 + + assert_equal [$master get number2] 1 + assert_equal [$replica get number2] foo + + assert_equal [count_log_message 0 "incrby"] 1 + } + } +} diff --git a/tests/modules/Makefile b/tests/modules/Makefile index 1b7159c89..ac4c3e27b 100644 --- a/tests/modules/Makefile +++ b/tests/modules/Makefile @@ -49,6 +49,7 @@ TEST_MODULES = \ hash.so \ zset.so \ stream.so \ + mallocsize.so \ aclcheck.so \ list.so \ subcommands.so \ @@ -56,7 +57,8 @@ TEST_MODULES = \ cmdintrospection.so \ eventloop.so \ moduleconfigs.so \ - moduleconfigstwo.so + moduleconfigstwo.so \ + publish.so .PHONY: all @@ -69,7 +71,7 @@ all: $(TEST_MODULES) $(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@ %.so: %.xo - $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LDFLAGS) $(LIBS) + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LDFLAGS) $(LIBS) .PHONY: clean diff --git a/tests/modules/aclcheck.c b/tests/modules/aclcheck.c index 8a9d468a6..9f4564d27 100644 --- a/tests/modules/aclcheck.c +++ b/tests/modules/aclcheck.c @@ -92,7 +92,7 @@ int rm_call_aclcheck_cmd(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModule if (ret != 0) { RedisModule_ReplyWithError(ctx, "DENIED CMD"); /* Add entry to ACL log */ - RedisModule_ACLAddLogEntry(ctx, user, argv[1]); + RedisModule_ACLAddLogEntry(ctx, user, argv[1], REDISMODULE_ACL_LOG_CMD); return REDISMODULE_OK; } diff --git a/tests/modules/basics.c b/tests/modules/basics.c index ecd1b8852..9dbb5a9d4 100644 --- a/tests/modules/basics.c +++ b/tests/modules/basics.c @@ -866,10 +866,10 @@ int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 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; + if (!TestAssertErrorReply(ctx,reply,"ERR unknown command 'foo', with args beginning with: ",53)) goto fail; T("set", "Ec", "x"); - if (!TestAssertErrorReply(ctx,reply,"ERR Wrong number of args calling Redis command 'set'.",53)) goto fail; + if (!TestAssertErrorReply(ctx,reply,"ERR wrong number of arguments for 'set' command",47)) goto fail; T("shutdown", "SE"); if (!TestAssertErrorReply(ctx,reply,"ERR command 'shutdown' is not allowed on script mode",52)) goto fail; diff --git a/tests/modules/keyspace_events.c b/tests/modules/keyspace_events.c index 152a2c48c..58670164d 100644 --- a/tests/modules/keyspace_events.c +++ b/tests/modules/keyspace_events.c @@ -79,6 +79,17 @@ static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const cha return REDISMODULE_OK; } +static int KeySpace_NotificationExpired(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { + REDISMODULE_NOT_USED(type); + REDISMODULE_NOT_USED(event); + REDISMODULE_NOT_USED(key); + + RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "c!", "testkeyspace:expired"); + RedisModule_FreeCallReply(rep); + + return REDISMODULE_OK; +} + static int KeySpace_NotificationModule(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(type); @@ -233,6 +244,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) return REDISMODULE_ERR; } + if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_EXPIRED, KeySpace_NotificationExpired) != REDISMODULE_OK){ + return REDISMODULE_ERR; + } + if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_MODULE, KeySpace_NotificationModule) != REDISMODULE_OK){ return REDISMODULE_ERR; } diff --git a/tests/modules/mallocsize.c b/tests/modules/mallocsize.c new file mode 100644 index 000000000..a1d31c136 --- /dev/null +++ b/tests/modules/mallocsize.c @@ -0,0 +1,237 @@ +#include "redismodule.h" +#include <string.h> +#include <assert.h> +#include <unistd.h> + +#define UNUSED(V) ((void) V) + +/* Registered type */ +RedisModuleType *mallocsize_type = NULL; + +typedef enum { + UDT_RAW, + UDT_STRING, + UDT_DICT +} udt_type_t; + +typedef struct { + void *ptr; + size_t len; +} raw_t; + +typedef struct { + udt_type_t type; + union { + raw_t raw; + RedisModuleString *str; + RedisModuleDict *dict; + } data; +} udt_t; + +void udt_free(void *value) { + udt_t *udt = value; + switch (udt->type) { + case (UDT_RAW): { + RedisModule_Free(udt->data.raw.ptr); + break; + } + case (UDT_STRING): { + RedisModule_FreeString(NULL, udt->data.str); + break; + } + case (UDT_DICT): { + RedisModuleString *dk, *dv; + RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(udt->data.dict, "^", NULL, 0); + while((dk = RedisModule_DictNext(NULL, iter, (void **)&dv)) != NULL) { + RedisModule_FreeString(NULL, dk); + RedisModule_FreeString(NULL, dv); + } + RedisModule_DictIteratorStop(iter); + RedisModule_FreeDict(NULL, udt->data.dict); + break; + } + } + RedisModule_Free(udt); +} + +void udt_rdb_save(RedisModuleIO *rdb, void *value) { + udt_t *udt = value; + RedisModule_SaveUnsigned(rdb, udt->type); + switch (udt->type) { + case (UDT_RAW): { + RedisModule_SaveStringBuffer(rdb, udt->data.raw.ptr, udt->data.raw.len); + break; + } + case (UDT_STRING): { + RedisModule_SaveString(rdb, udt->data.str); + break; + } + case (UDT_DICT): { + RedisModule_SaveUnsigned(rdb, RedisModule_DictSize(udt->data.dict)); + RedisModuleString *dk, *dv; + RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(udt->data.dict, "^", NULL, 0); + while((dk = RedisModule_DictNext(NULL, iter, (void **)&dv)) != NULL) { + RedisModule_SaveString(rdb, dk); + RedisModule_SaveString(rdb, dv); + RedisModule_FreeString(NULL, dk); /* Allocated by RedisModule_DictNext */ + } + RedisModule_DictIteratorStop(iter); + break; + } + } +} + +void *udt_rdb_load(RedisModuleIO *rdb, int encver) { + if (encver != 0) + return NULL; + udt_t *udt = RedisModule_Alloc(sizeof(*udt)); + udt->type = RedisModule_LoadUnsigned(rdb); + switch (udt->type) { + case (UDT_RAW): { + udt->data.raw.ptr = RedisModule_LoadStringBuffer(rdb, &udt->data.raw.len); + break; + } + case (UDT_STRING): { + udt->data.str = RedisModule_LoadString(rdb); + break; + } + case (UDT_DICT): { + long long dict_len = RedisModule_LoadUnsigned(rdb); + udt->data.dict = RedisModule_CreateDict(NULL); + for (int i = 0; i < dict_len; i += 2) { + RedisModuleString *key = RedisModule_LoadString(rdb); + RedisModuleString *val = RedisModule_LoadString(rdb); + RedisModule_DictSet(udt->data.dict, key, val); + } + break; + } + } + + return udt; +} + +size_t udt_mem_usage(RedisModuleKeyOptCtx *ctx, const void *value, size_t sample_size) { + UNUSED(ctx); + UNUSED(sample_size); + + const udt_t *udt = value; + size_t size = sizeof(*udt); + + switch (udt->type) { + case (UDT_RAW): { + size += RedisModule_MallocSize(udt->data.raw.ptr); + break; + } + case (UDT_STRING): { + size += RedisModule_MallocSizeString(udt->data.str); + break; + } + case (UDT_DICT): { + void *dk; + size_t keylen; + RedisModuleString *dv; + RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(udt->data.dict, "^", NULL, 0); + while((dk = RedisModule_DictNextC(iter, &keylen, (void **)&dv)) != NULL) { + size += keylen; + size += RedisModule_MallocSizeString(dv); + } + RedisModule_DictIteratorStop(iter); + break; + } + } + + return size; +} + +/* MALLOCSIZE.SETRAW key len */ +int cmd_setraw(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 3) + return RedisModule_WrongArity(ctx); + + RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE); + + udt_t *udt = RedisModule_Alloc(sizeof(*udt)); + udt->type = UDT_RAW; + + long long raw_len; + RedisModule_StringToLongLong(argv[2], &raw_len); + udt->data.raw.ptr = RedisModule_Alloc(raw_len); + udt->data.raw.len = raw_len; + + RedisModule_ModuleTypeSetValue(key, mallocsize_type, udt); + RedisModule_CloseKey(key); + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* MALLOCSIZE.SETSTR key string */ +int cmd_setstr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 3) + return RedisModule_WrongArity(ctx); + + RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE); + + udt_t *udt = RedisModule_Alloc(sizeof(*udt)); + udt->type = UDT_STRING; + + udt->data.str = argv[2]; + RedisModule_RetainString(ctx, argv[2]); + + RedisModule_ModuleTypeSetValue(key, mallocsize_type, udt); + RedisModule_CloseKey(key); + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* MALLOCSIZE.SETDICT key field value [field value ...] */ +int cmd_setdict(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc < 4 || argc % 2) + return RedisModule_WrongArity(ctx); + + RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE); + + udt_t *udt = RedisModule_Alloc(sizeof(*udt)); + udt->type = UDT_DICT; + + udt->data.dict = RedisModule_CreateDict(ctx); + for (int i = 2; i < argc; i += 2) { + RedisModule_DictSet(udt->data.dict, argv[i], argv[i+1]); + /* No need to retain argv[i], it is copied as the rax key */ + RedisModule_RetainString(ctx, argv[i+1]); + } + + RedisModule_ModuleTypeSetValue(key, mallocsize_type, udt); + RedisModule_CloseKey(key); + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + UNUSED(argv); + UNUSED(argc); + if (RedisModule_Init(ctx,"mallocsize",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR) + return REDISMODULE_ERR; + + RedisModuleTypeMethods tm = { + .version = REDISMODULE_TYPE_METHOD_VERSION, + .rdb_load = udt_rdb_load, + .rdb_save = udt_rdb_save, + .free = udt_free, + .mem_usage2 = udt_mem_usage, + }; + + mallocsize_type = RedisModule_CreateDataType(ctx, "allocsize", 0, &tm); + if (mallocsize_type == NULL) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "mallocsize.setraw", cmd_setraw, "", 1, 1, 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "mallocsize.setstr", cmd_setstr, "", 1, 1, 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "mallocsize.setdict", cmd_setdict, "", 1, 1, 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/tests/modules/moduleconfigs.c b/tests/modules/moduleconfigs.c index a9e434a7b..0a6380461 100644 --- a/tests/modules/moduleconfigs.c +++ b/tests/modules/moduleconfigs.c @@ -6,6 +6,7 @@ long long longval; long long memval; RedisModuleString *strval = NULL; int enumval; +int flagsval; /* 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 @@ -68,6 +69,20 @@ int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleS return REDISMODULE_OK; } +int getFlagsConfigCommand(const char *name, void *privdata) { + REDISMODULE_NOT_USED(name); + REDISMODULE_NOT_USED(privdata); + return flagsval; +} + +int setFlagsConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) { + REDISMODULE_NOT_USED(name); + REDISMODULE_NOT_USED(err); + REDISMODULE_NOT_USED(privdata); + flagsval = val; + return REDISMODULE_OK; +} + int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) { REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(privdata); @@ -106,10 +121,13 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) } /* 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}; + const char *enum_vals[] = {"none", "one", "two", "three"}; + const int int_vals[] = {0, 1, 2, 4}; - if (RedisModule_RegisterEnumConfig(ctx, "enum", 0, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) { + if (RedisModule_RegisterEnumConfig(ctx, "enum", 1, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 4, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) { + return REDISMODULE_ERR; + } + if (RedisModule_RegisterEnumConfig(ctx, "flags", 3, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_BITFLAGS, enum_vals, int_vals, 4, getFlagsConfigCommand, setFlagsConfigCommand, NULL, NULL) == REDISMODULE_ERR) { return REDISMODULE_ERR; } /* Memory config here. */ @@ -139,4 +157,4 @@ int RedisModule_OnUnload(RedisModuleCtx *ctx) { strval = NULL; } return REDISMODULE_OK; -}
\ No newline at end of file +} diff --git a/tests/modules/publish.c b/tests/modules/publish.c new file mode 100644 index 000000000..eee96d689 --- /dev/null +++ b/tests/modules/publish.c @@ -0,0 +1,42 @@ +#include "redismodule.h" +#include <string.h> +#include <assert.h> +#include <unistd.h> + +#define UNUSED(V) ((void) V) + +int cmd_publish_classic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc != 3) + return RedisModule_WrongArity(ctx); + + int receivers = RedisModule_PublishMessage(ctx, argv[1], argv[2]); + RedisModule_ReplyWithLongLong(ctx, receivers); + return REDISMODULE_OK; +} + +int cmd_publish_shard(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc != 3) + return RedisModule_WrongArity(ctx); + + int receivers = RedisModule_PublishMessageShard(ctx, argv[1], argv[2]); + RedisModule_ReplyWithLongLong(ctx, receivers); + return REDISMODULE_OK; +} + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + UNUSED(argv); + UNUSED(argc); + + if (RedisModule_Init(ctx,"publish",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"publish.classic",cmd_publish_classic,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"publish.shard",cmd_publish_shard,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/tests/modules/subcommands.c b/tests/modules/subcommands.c index 7cb337331..3486e86b4 100644 --- a/tests/modules/subcommands.c +++ b/tests/modules/subcommands.c @@ -11,7 +11,10 @@ int cmd_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int cmd_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { UNUSED(argv); - UNUSED(argc); + + if (argc > 4) /* For testing */ + return RedisModule_WrongArity(ctx); + RedisModule_ReplyWithSimpleString(ctx, "OK"); return REDISMODULE_OK; } diff --git a/tests/sentinel/tests/05-manual.tcl b/tests/sentinel/tests/05-manual.tcl index 28c5560a6..a0004eb75 100644 --- a/tests/sentinel/tests/05-manual.tcl +++ b/tests/sentinel/tests/05-manual.tcl @@ -3,9 +3,9 @@ source "../tests/includes/init-tests.tcl" foreach_sentinel_id id { - S $id sentinel debug info-period 1000 - S $id sentinel debug default-down-after 3000 - S $id sentinel debug publish-period 500 + S $id sentinel debug info-period 2000 + S $id sentinel debug default-down-after 6000 + S $id sentinel debug publish-period 1000 } test "Manual failover works" { diff --git a/tests/sentinel/tests/includes/init-tests.tcl b/tests/sentinel/tests/includes/init-tests.tcl index fe9a61815..ddb131903 100644 --- a/tests/sentinel/tests/includes/init-tests.tcl +++ b/tests/sentinel/tests/includes/init-tests.tcl @@ -28,7 +28,8 @@ test "(init) Sentinels can start monitoring a master" { foreach_sentinel_id id { assert {[S $id sentinel master mymaster] ne {}} S $id SENTINEL SET mymaster down-after-milliseconds 2000 - S $id SENTINEL SET mymaster failover-timeout 20000 + S $id SENTINEL SET mymaster failover-timeout 10000 + S $id SENTINEL debug tilt-period 5000 S $id SENTINEL SET mymaster parallel-syncs 10 if {$::leaked_fds_file != "" && [exec uname] == "Linux"} { S $id SENTINEL SET mymaster notification-script ../../tests/helpers/check_leaked_fds.tcl diff --git a/tests/support/util.tcl b/tests/support/util.tcl index 4ad96ab10..6741b719a 100644 --- a/tests/support/util.tcl +++ b/tests/support/util.tcl @@ -77,6 +77,12 @@ proc getInfoProperty {infostr property} { } } +proc cluster_info {r field} { + if {[regexp "^$field:(.*?)\r\n" [$r cluster info] _ value]} { + set _ $value + } +} + # Return value for INFO property proc status {r property} { set _ [getInfoProperty [{*}$r info] $property] @@ -823,11 +829,21 @@ proc subscribe {client channels} { consume_subscribe_messages $client subscribe $channels } +proc ssubscribe {client channels} { + $client ssubscribe {*}$channels + consume_subscribe_messages $client ssubscribe $channels +} + proc unsubscribe {client {channels {}}} { $client unsubscribe {*}$channels consume_subscribe_messages $client unsubscribe $channels } +proc sunsubscribe {client {channels {}}} { + $client sunsubscribe {*}$channels + consume_subscribe_messages $client sunsubscribe $channels +} + proc psubscribe {client channels} { $client psubscribe {*}$channels consume_subscribe_messages $client psubscribe $channels diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index 277fa3803..134c5c46b 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -94,6 +94,7 @@ set ::all_tests { unit/client-eviction unit/violations unit/replybufsize + unit/cluster-scripting } # Index to the next test to run in the ::all_tests list. set ::next_test 0 @@ -274,6 +275,16 @@ proc s {args} { status [srv $level "client"] [lindex $args 0] } +# Provide easy access to CLUSTER INFO properties. Same semantic as "proc s". +proc csi {args} { + set level 0 + if {[string is integer [lindex $args 0]]} { + set level [lindex $args 0] + set args [lrange $args 1 end] + } + cluster_info [srv $level "client"] [lindex $args 0] +} + # Test wrapped into run_solo are sent back from the client to the # test server, so that the test server will send them again to # clients once the clients are idle. diff --git a/tests/unit/acl-v2.tcl b/tests/unit/acl-v2.tcl index 12eb5a3be..500a7c729 100644 --- a/tests/unit/acl-v2.tcl +++ b/tests/unit/acl-v2.tcl @@ -173,7 +173,7 @@ start_server {tags {"acl external:skip"}} { assert_equal PONG [$r2 PING] assert_equal {} [$r2 get readwrite_str] - assert_error {ERR* not an integer *} {$r2 set readwrite_str bar ex get} + assert_error {ERR * not an integer *} {$r2 set readwrite_str bar ex get} assert_equal {OK} [$r2 set readwrite_str bar] assert_equal {bar} [$r2 get readwrite_str] diff --git a/tests/unit/acl.tcl b/tests/unit/acl.tcl index 0a9ffb250..c252de031 100644 --- a/tests/unit/acl.tcl +++ b/tests/unit/acl.tcl @@ -511,7 +511,7 @@ start_server {tags {"acl external:skip"}} { test "ACL CAT with illegal arguments" { assert_error {*Unknown category 'NON_EXISTS'} {r ACL CAT NON_EXISTS} - assert_error {*Unknown subcommand or wrong number of arguments for 'CAT'*} {r ACL CAT NON_EXISTS NON_EXISTS2} + assert_error {*unknown subcommand or wrong number of arguments for 'CAT'*} {r ACL CAT NON_EXISTS NON_EXISTS2} } test "ACL CAT without category - list all categories" { diff --git a/tests/unit/auth.tcl b/tests/unit/auth.tcl index 4a4d7564c..26d125579 100644 --- a/tests/unit/auth.tcl +++ b/tests/unit/auth.tcl @@ -2,7 +2,7 @@ start_server {tags {"auth external:skip"}} { test {AUTH fails if there is no password configured server side} { catch {r auth foo} err set _ $err - } {ERR*any password*} + } {ERR *any password*} test {Arity check for auth command} { catch {r auth a b c} err diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl index ec08a060d..1b7db407a 100644 --- a/tests/unit/bitops.tcl +++ b/tests/unit/bitops.tcl @@ -133,12 +133,12 @@ start_server {tags {"bitops"}} { test {BITCOUNT syntax error #1} { catch {r bitcount s 0} e set e - } {ERR*syntax*} + } {ERR *syntax*} test {BITCOUNT syntax error #2} { catch {r bitcount s 0 1 hello} e set e - } {ERR*syntax*} + } {ERR *syntax*} test {BITCOUNT regression test for github issue #582} { r del foo @@ -546,7 +546,10 @@ start_server {tags {"bitops"}} { } } } +} +run_solo {bitops-large-memory} { +start_server {tags {"bitops"}} { test "BIT pos larger than UINT_MAX" { set bytes [expr (1 << 29) + 1] set bitpos [expr (1 << 32)] @@ -587,3 +590,4 @@ start_server {tags {"bitops"}} { r del mykey } {1} {large-memory needs:debug} } +} ;#run_solo diff --git a/tests/unit/cluster-scripting.tcl b/tests/unit/cluster-scripting.tcl new file mode 100644 index 000000000..72fc028c3 --- /dev/null +++ b/tests/unit/cluster-scripting.tcl @@ -0,0 +1,64 @@ +# make sure the test infra won't use SELECT +set old_singledb $::singledb +set ::singledb 1 + +start_server {overrides {cluster-enabled yes} tags {external:skip cluster}} { + r 0 cluster addslotsrange 0 16383 + wait_for_condition 50 100 { + [csi 0 cluster_state] eq "ok" + } else { + fail "Cluster never became 'ok'" + } + + test {Eval scripts with shebangs and functions default to no cross slots} { + # Test that scripts with shebang block cross slot operations + assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { + r 0 eval {#!lua + redis.call('set', 'foo', 'bar') + redis.call('set', 'bar', 'foo') + return 'OK' + } 0} + + # Test the functions by default block cross slot operations + r 0 function load REPLACE {#!lua name=crossslot + local function test_cross_slot(keys, args) + redis.call('set', 'foo', 'bar') + redis.call('set', 'bar', 'foo') + return 'OK' + end + + redis.register_function('test_cross_slot', test_cross_slot)} + assert_error "ERR Script attempted to access keys that do not hash to the same slot*" {r FCALL test_cross_slot 0} + } + + test {Cross slot commands are allowed by default for eval scripts and with allow-cross-slot-keys flag} { + # Old style lua scripts are allowed to access cross slot operations + r 0 eval "redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo')" 0 + + # scripts with allow-cross-slot-keys flag are allowed + r 0 eval {#!lua flags=allow-cross-slot-keys + redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo') + } 0 + + # Functions with allow-cross-slot-keys flag are allowed + r 0 function load REPLACE {#!lua name=crossslot + local function test_cross_slot(keys, args) + redis.call('set', 'foo', 'bar') + redis.call('set', 'bar', 'foo') + return 'OK' + end + + redis.register_function{function_name='test_cross_slot', callback=test_cross_slot, flags={ 'allow-cross-slot-keys' }}} + r FCALL test_cross_slot 0 + } + + test {Cross slot commands are also blocked if they disagree with pre-declared keys} { + assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { + r 0 eval {#!lua + redis.call('set', 'foo', 'bar') + return 'OK' + } 1 bar} + } +} + +set ::singledb $old_singledb diff --git a/tests/unit/functions.tcl b/tests/unit/functions.tcl index 4c08261ed..62c070f90 100644 --- a/tests/unit/functions.tcl +++ b/tests/unit/functions.tcl @@ -117,7 +117,7 @@ start_server {tags {"scripting"}} { r function bad_subcommand } e set _ $e - } {*Unknown subcommand*} + } {*unknown subcommand*} test {FUNCTION - test loading from rdb} { r debug reload @@ -205,7 +205,7 @@ start_server {tags {"scripting"}} { test {FUNCTION - test function restore with wrong number of arguments} { catch {r function restore arg1 args2 arg3} e set _ $e - } {*Unknown subcommand or wrong number of arguments for 'restore'. Try FUNCTION HELP.} + } {*unknown subcommand or wrong number of arguments for 'restore'. Try FUNCTION HELP.} test {FUNCTION - test fcall_ro with write command} { r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('set', 'x', '1')}] @@ -298,7 +298,7 @@ start_server {tags {"scripting"}} { assert_match {*only supports SYNC|ASYNC*} $e catch {r function flush sync extra_arg} e - assert_match {*Unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.} $e + assert_match {*unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.} $e } } @@ -624,16 +624,16 @@ start_server {tags {"scripting"}} { } } e set _ $e - } {*attempt to call field 'call' (a nil value)*} + } {*attempted to access nonexistent global variable 'call'*} - test {LIBRARIES - redis.call from function load} { + test {LIBRARIES - redis.setresp from function load} { catch { r function load replace {#!lua name=lib2 return redis.setresp(3) } } e set _ $e - } {*attempt to call field 'setresp' (a nil value)*} + } {*attempted to access nonexistent global variable 'setresp'*} test {LIBRARIES - redis.set_repl from function load} { catch { @@ -642,7 +642,7 @@ start_server {tags {"scripting"}} { } } e set _ $e - } {*attempt to call field 'set_repl' (a nil value)*} + } {*attempted to access nonexistent global variable 'set_repl'*} test {LIBRARIES - malicious access test} { # the 'library' API is not exposed inside a @@ -669,37 +669,18 @@ start_server {tags {"scripting"}} { end) end) } - assert_equal {OK} [r fcall f1 0] + catch {[r fcall f1 0]} e + assert_match {*Attempt to modify a readonly table*} $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 name=lib2 - redis.math.randomseed() - }]} e - assert_match {*can only be called inside a script invocation*} $e + assert_match {*Script attempted to access nonexistent global variable 'math'*} $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 name=lib2 - redis.redis.pcall('ping') - }]} e - assert_match {*can only be called inside a script invocation*} $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 name=lib2 - redis.redis.set_repl(redis.redis.REPL_NONE) - }]} e - assert_match {*can only be called inside a script invocation*} $e + assert_match {*Script attempted to access nonexistent global variable 'redis'*} $e catch {[r fcall f2 0]} e assert_match {*can only be called on FUNCTION LOAD command*} $e @@ -756,7 +737,7 @@ start_server {tags {"scripting"}} { } } e set _ $e - } {*attempted to create global variable 'a'*} + } {*Attempt to modify a readonly table*} test {LIBRARIES - named arguments} { r function load {#!lua name=lib @@ -986,7 +967,7 @@ start_server {tags {"scripting"}} { assert_match {*command not allowed when used memory*} $e r config set maxmemory 0 - } + } {OK} {needs:config-maxmemory} test {FUNCTION - verify allow-omm allows running any command} { r FUNCTION load replace {#!lua name=f1 @@ -999,11 +980,11 @@ start_server {tags {"scripting"}} { r config set maxmemory 1 - assert_match {OK} [r fcall f1 1 k] + assert_match {OK} [r fcall f1 1 x] assert_match {1} [r get x] r config set maxmemory 0 - } + } {OK} {needs:config-maxmemory} } start_server {tags {"scripting"}} { @@ -1074,7 +1055,7 @@ start_server {tags {"scripting"}} { assert_match {*can not run it when used memory > 'maxmemory'*} $e r config set maxmemory 0 - } + } {OK} {needs:config-maxmemory} test {FUNCTION - deny oom on no-writes function} { r FUNCTION load replace {#!lua name=test @@ -1090,7 +1071,7 @@ start_server {tags {"scripting"}} { assert_match {*can not run it when used memory > 'maxmemory'*} $e r config set maxmemory 0 - } + } {OK} {needs:config-maxmemory} test {FUNCTION - allow stale} { r FUNCTION load replace {#!lua name=test @@ -1198,4 +1179,32 @@ start_server {tags {"scripting"}} { redis.register_function('foo', function() return 1 end) } } {foo} + + test {FUNCTION - trick global protection 1} { + r FUNCTION FLUSH + + r FUNCTION load {#!lua name=test1 + redis.register_function('f1', function() + mt = getmetatable(_G) + original_globals = mt.__index + original_globals['redis'] = function() return 1 end + end) + } + + catch {[r fcall f1 0]} e + set _ $e + } {*Attempt to modify a readonly table*} + + test {FUNCTION - test getmetatable on script load} { + r FUNCTION FLUSH + + catch { + r FUNCTION load {#!lua name=test1 + mt = getmetatable(_G) + } + } e + + set _ $e + } {*Script attempted to access nonexistent global variable 'getmetatable'*} + } diff --git a/tests/unit/geo.tcl b/tests/unit/geo.tcl index e6afb211b..1c7564430 100644 --- a/tests/unit/geo.tcl +++ b/tests/unit/geo.tcl @@ -193,14 +193,14 @@ start_server {tags {"geo"}} { r geoadd nyc xx nx -73.9454966 40.747533 "lic market" } err set err - } {ERR*syntax*} + } {ERR *syntax*} test {GEOADD update with invalid option} { catch { r geoadd nyc ch xx foo -73.9454966 40.747533 "lic market" } err set err - } {ERR*syntax*} + } {ERR *syntax*} test {GEOADD invalid coordinates} { catch { @@ -229,27 +229,27 @@ start_server {tags {"geo"}} { test {GEOSEARCH FROMLONLAT and FROMMEMBER cannot exist at the same time} { catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 frommember xxx bybox 6 6 km asc} e set e - } {ERR*syntax*} + } {ERR *syntax*} test {GEOSEARCH FROMLONLAT and FROMMEMBER one must exist} { catch {r geosearch nyc bybox 3 3 km asc desc withhash withdist withcoord} e set e - } {ERR*exactly one of FROMMEMBER or FROMLONLAT*} + } {ERR *exactly one of FROMMEMBER or FROMLONLAT*} test {GEOSEARCH BYRADIUS and BYBOX cannot exist at the same time} { catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 byradius 3 km bybox 3 3 km asc} e set e - } {ERR*syntax*} + } {ERR *syntax*} test {GEOSEARCH BYRADIUS and BYBOX one must exist} { catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 asc desc withhash withdist withcoord} e set e - } {ERR*exactly one of BYRADIUS and BYBOX*} + } {ERR *exactly one of BYRADIUS and BYBOX*} test {GEOSEARCH with STOREDIST option} { catch {r geosearch nyc fromlonlat -73.9798091 40.7598464 bybox 6 6 km asc storedist} e set e - } {ERR*syntax*} + } {ERR *syntax*} test {GEORADIUS withdist (sorted)} { r georadius nyc -73.9798091 40.7598464 3 km withdist asc @@ -274,12 +274,12 @@ start_server {tags {"geo"}} { test {GEORADIUS with ANY but no COUNT} { catch {r georadius nyc -73.9798091 40.7598464 10 km ANY ASC} e set e - } {ERR*ANY*requires*COUNT*} + } {ERR *ANY*requires*COUNT*} test {GEORADIUS with COUNT but missing integer argument} { catch {r georadius nyc -73.9798091 40.7598464 10 km COUNT} e set e - } {ERR*syntax*} + } {ERR *syntax*} test {GEORADIUS with COUNT DESC} { r georadius nyc -73.9798091 40.7598464 10 km COUNT 2 DESC diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl index c530a31d3..4b4b8f56b 100644 --- a/tests/unit/introspection.tcl +++ b/tests/unit/introspection.tcl @@ -23,9 +23,9 @@ start_server {tags {"introspection"}} { assert_error "ERR wrong number of arguments for 'client|kill' command" {r client kill} assert_error "ERR syntax error*" {r client kill id 10 wrong_arg} - assert_error "ERR*greater than 0*" {r client kill id str} - assert_error "ERR*greater than 0*" {r client kill id -1} - assert_error "ERR*greater than 0*" {r client kill id 0} + assert_error "ERR *greater than 0*" {r client kill id str} + assert_error "ERR *greater than 0*" {r client kill id -1} + assert_error "ERR *greater than 0*" {r client kill id 0} assert_error "ERR Unknown client type*" {r client kill type wrong_type} @@ -215,6 +215,7 @@ start_server {tags {"introspection"}} { dbfilename logfile dir + socket-mark-id } if {!$::tls} { @@ -285,16 +286,22 @@ start_server {tags {"introspection"}} { } } {} {external:skip} - test {CONFIG REWRITE handles save properly} { + test {CONFIG REWRITE handles save and shutdown properly} { r config set save "3600 1 300 100 60 10000" + r config set shutdown-on-sigterm "nosave now" + r config set shutdown-on-sigint "save" r config rewrite restart_server 0 true false assert_equal [r config get save] {save {3600 1 300 100 60 10000}} + assert_equal [r config get shutdown-on-sigterm] {shutdown-on-sigterm {nosave now}} + assert_equal [r config get shutdown-on-sigint] {shutdown-on-sigint save} r config set save "" + r config set shutdown-on-sigterm "default" r config rewrite restart_server 0 true false assert_equal [r config get save] {save {}} + assert_equal [r config get shutdown-on-sigterm] {shutdown-on-sigterm default} start_server {config "minimal.conf"} { assert_equal [r config get save] {save {3600 1 300 100 60 10000}} @@ -409,11 +416,11 @@ start_server {tags {"introspection"}} { } test {CONFIG SET duplicate configs} { - assert_error "ERR*duplicate*" {r config set maxmemory 10000001 maxmemory 10000002} + assert_error "ERR *duplicate*" {r config set maxmemory 10000001 maxmemory 10000002} } test {CONFIG SET set immutable} { - assert_error "ERR*immutable*" {r config set daemonize yes} + assert_error "ERR *immutable*" {r config set daemonize yes} } test {CONFIG GET hidden configs} { @@ -448,8 +455,8 @@ start_server {tags {"introspection"}} { start_server {tags {"introspection external:skip"} overrides {enable-protected-configs {no} enable-debug-command {no}}} { test {cannot modify protected configuration - no} { - assert_error "ERR*protected*" {r config set dir somedir} - assert_error "ERR*DEBUG command not allowed*" {r DEBUG HELP} + assert_error "ERR *protected*" {r config set dir somedir} + assert_error "ERR *DEBUG command not allowed*" {r DEBUG HELP} } {} {needs:debug} } @@ -464,8 +471,8 @@ start_server {config "minimal.conf" tags {"introspection external:skip"} overrid if {$myaddr != "" && ![string match {127.*} $myaddr]} { # Non-loopback client should fail set r2 [get_nonloopback_client] - assert_error "ERR*protected*" {$r2 config set dir somedir} - assert_error "ERR*DEBUG command not allowed*" {$r2 DEBUG HELP} + assert_error "ERR *protected*" {$r2 config set dir somedir} + assert_error "ERR *DEBUG command not allowed*" {$r2 DEBUG HELP} } } {} {needs:debug} } diff --git a/tests/unit/moduleapi/aclcheck.tcl b/tests/unit/moduleapi/aclcheck.tcl index 5adf65371..d96ea89cf 100644 --- a/tests/unit/moduleapi/aclcheck.tcl +++ b/tests/unit/moduleapi/aclcheck.tcl @@ -16,6 +16,7 @@ start_server {tags {"modules acl"}} { assert {[dict get $entry username] eq {default}} assert {[dict get $entry context] eq {module}} assert {[dict get $entry object] eq {set}} + assert {[dict get $entry reason] eq {command}} } test {test module check acl for key perm} { @@ -75,6 +76,7 @@ start_server {tags {"modules acl"}} { assert {[dict get $entry username] eq {default}} assert {[dict get $entry context] eq {module}} assert {[dict get $entry object] eq {z}} + assert {[dict get $entry reason] eq {key}} # rm call check for command permission r acl setuser default -set @@ -88,6 +90,7 @@ start_server {tags {"modules acl"}} { assert {[dict get $entry username] eq {default}} assert {[dict get $entry context] eq {module}} assert {[dict get $entry object] eq {set}} + assert {[dict get $entry reason] eq {command}} } test "Unload the module - aclcheck" { diff --git a/tests/unit/moduleapi/basics.tcl b/tests/unit/moduleapi/basics.tcl index b858c344a..040d9eb3c 100644 --- a/tests/unit/moduleapi/basics.tcl +++ b/tests/unit/moduleapi/basics.tcl @@ -36,6 +36,6 @@ start_server {tags {"modules"}} { start_server {tags {"modules external:skip"} overrides {enable-module-command no}} { test {module command disabled} { - assert_error "ERR*MODULE command not allowed*" {r module load $testmodule} + assert_error "ERR *MODULE command not allowed*" {r module load $testmodule} } }
\ No newline at end of file diff --git a/tests/unit/moduleapi/blockedclient.tcl b/tests/unit/moduleapi/blockedclient.tcl index de3cf5946..dd6085c81 100644 --- a/tests/unit/moduleapi/blockedclient.tcl +++ b/tests/unit/moduleapi/blockedclient.tcl @@ -90,7 +90,8 @@ start_server {tags {"modules"}} { } } - test {Busy module command} { +foreach call_type {nested normal} { + test "Busy module command - $call_type" { set busy_time_limit 50 set old_time_limit [lindex [r config get busy-reply-threshold] 1] r config set busy-reply-threshold $busy_time_limit @@ -98,7 +99,15 @@ start_server {tags {"modules"}} { # run command that blocks until released set start [clock clicks -milliseconds] - $rd slow_fg_command 0 + if {$call_type == "nested"} { + $rd do_rm_call slow_fg_command 0 + } else { + $rd slow_fg_command 0 + } + $rd flush + + # send another command after the blocked one, to make sure we don't attempt to process it + $rd ping $rd flush # make sure we get BUSY error, and that we didn't get it too early @@ -112,11 +121,16 @@ start_server {tags {"modules"}} { } else { fail "Failed waiting for busy command to end" } - $rd read + assert_equal [$rd read] "1" + assert_equal [$rd read] "PONG" - #run command that blocks for 200ms + # run command that blocks for 200ms set start [clock clicks -milliseconds] - $rd slow_fg_command 200000 + if {$call_type == "nested"} { + $rd do_rm_call slow_fg_command 200000 + } else { + $rd slow_fg_command 200000 + } $rd flush after 10 ;# try to make sure redis started running the command before we proceed @@ -128,6 +142,7 @@ start_server {tags {"modules"}} { $rd close r config set busy-reply-threshold $old_time_limit } +} test {RM_Call from blocked client} { set busy_time_limit 50 @@ -141,6 +156,10 @@ start_server {tags {"modules"}} { set start [clock clicks -milliseconds] $rd do_bg_rm_call hgetall hash + # send another command after the blocked one, to make sure we don't attempt to process it + $rd ping + $rd flush + # wait till we know we're blocked inside the module wait_for_condition 50 100 { [r is_in_slow_bg_operation] eq 1 @@ -162,10 +181,10 @@ start_server {tags {"modules"}} { assert_equal [r ping] {PONG} r config set busy-reply-threshold $old_time_limit - set res [$rd read] + assert_equal [$rd read] {foo bar} + assert_equal [$rd read] {PONG} $rd close - set _ $res - } {foo bar} + } test {blocked client reaches client output buffer limit} { r hset hash big [string repeat x 50000] @@ -184,9 +203,13 @@ start_server {tags {"modules"}} { r config resetstat # simple module command that replies with string error - assert_error "ERR Unknown Redis command 'hgetalllll'." {r do_rm_call hgetalllll} + assert_error "ERR unknown command 'hgetalllll', with args beginning with:" {r do_rm_call hgetalllll} assert_equal [errorrstat ERR r] {count=1} + # simple module command that replies with string error + assert_error "ERR unknown subcommand 'bla'. Try CONFIG HELP." {r do_rm_call config bla} + assert_equal [errorrstat ERR r] {count=2} + # module command that replies with string error from bg thread assert_error "NULL reply returned" {r do_bg_rm_call hgetalllll} assert_equal [errorrstat NULL r] {count=1} @@ -194,7 +217,7 @@ start_server {tags {"modules"}} { # 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=2} + assert_equal [errorrstat ERR r] {count=3} # RM_Call that propagates an error assert_error "WRONGTYPE*" {r do_rm_call hgetall x} @@ -206,8 +229,8 @@ start_server {tags {"modules"}} { assert_equal [errorrstat WRONGTYPE r] {count=2} assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat hgetall r] - assert_equal [s total_error_replies] 5 - assert_match {*calls=4,*,rejected_calls=0,failed_calls=3} [cmdrstat do_rm_call r] + assert_equal [s total_error_replies] 6 + assert_match {*calls=5,*,rejected_calls=0,failed_calls=4} [cmdrstat do_rm_call r] assert_match {*calls=2,*,rejected_calls=0,failed_calls=2} [cmdrstat do_bg_rm_call r] } diff --git a/tests/unit/moduleapi/cluster.tcl b/tests/unit/moduleapi/cluster.tcl index f1238992d..f4ebaab1b 100644 --- a/tests/unit/moduleapi/cluster.tcl +++ b/tests/unit/moduleapi/cluster.tcl @@ -2,22 +2,6 @@ source tests/support/cli.tcl -proc cluster_info {r field} { - if {[regexp "^$field:(.*?)\r\n" [$r cluster info] _ value]} { - set _ $value - } -} - -# Provide easy access to CLUSTER INFO properties. Same semantic as "proc s". -proc csi {args} { - set level 0 - if {[string is integer [lindex $args 0]]} { - set level [lindex $args 0] - set args [lrange $args 1 end] - } - cluster_info [srv $level "client"] [lindex $args 0] -} - 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] diff --git a/tests/unit/moduleapi/cmdintrospection.tcl b/tests/unit/moduleapi/cmdintrospection.tcl index 375b3e406..8ac49ed9f 100644 --- a/tests/unit/moduleapi/cmdintrospection.tcl +++ b/tests/unit/moduleapi/cmdintrospection.tcl @@ -36,6 +36,7 @@ start_server {tags {"modules"}} { # Compare the maps. We need to pop "group" first. dict unset redis_reply group dict unset module_reply group + dict unset module_reply module assert_equal $redis_reply $module_reply } diff --git a/tests/unit/moduleapi/keyspace_events.tcl b/tests/unit/moduleapi/keyspace_events.tcl index 39350e518..ceec6fdf3 100644 --- a/tests/unit/moduleapi/keyspace_events.tcl +++ b/tests/unit/moduleapi/keyspace_events.tcl @@ -83,6 +83,17 @@ tags "modules" { $rd1 close } + test {Test expired key space event} { + set prev_expired [s expired_keys] + r set exp 1 PX 10 + wait_for_condition 100 10 { + [s expired_keys] eq $prev_expired + 1 + } else { + fail "key not expired" + } + assert_equal [r get testkeyspace:expired] 1 + } + test "Unload the module - testkeyspace" { assert_equal {OK} [r module unload testkeyspace] } diff --git a/tests/unit/moduleapi/mallocsize.tcl b/tests/unit/moduleapi/mallocsize.tcl new file mode 100644 index 000000000..359a7ae44 --- /dev/null +++ b/tests/unit/moduleapi/mallocsize.tcl @@ -0,0 +1,21 @@ +set testmodule [file normalize tests/modules/mallocsize.so] + + +start_server {tags {"modules"}} { + r module load $testmodule + + test {MallocSize of raw bytes} { + assert_equal [r mallocsize.setraw key 40] {OK} + assert_morethan [r memory usage key] 40 + } + + test {MallocSize of string} { + assert_equal [r mallocsize.setstr key abcdefg] {OK} + assert_morethan [r memory usage key] 7 ;# Length of "abcdefg" + } + + test {MallocSize of dict} { + assert_equal [r mallocsize.setdict key f1 v1 f2 v2] {OK} + assert_morethan [r memory usage key] 8 ;# Length of "f1v1f2v2" + } +} diff --git a/tests/unit/moduleapi/moduleconfigs.tcl b/tests/unit/moduleapi/moduleconfigs.tcl index 01aa1e88e..f731d1bd9 100644 --- a/tests/unit/moduleapi/moduleconfigs.tcl +++ b/tests/unit/moduleapi/moduleconfigs.tcl @@ -11,6 +11,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" } @@ -29,6 +30,10 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" r config set moduleconfigs.enum two assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" + r config set moduleconfigs.flags two + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags two" + r config set moduleconfigs.flags "two three" + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two three}" r config set moduleconfigs.numeric -2 assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2" } @@ -54,7 +59,7 @@ start_server {tags {"modules"}} { 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 + assert_match {*must be one of the following*} $e } test {Unload removes module configs} { @@ -67,6 +72,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" r module unload moduleconfigs } @@ -80,6 +86,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.string] "moduleconfigs.string tclortickle" # Configs that were not changed should still be their module specified value assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" } @@ -140,6 +147,7 @@ start_server {tags {"modules"}} { r config set moduleconfigs.mutable_bool yes r config set moduleconfigs.memory_numeric 750 r config set moduleconfigs.enum two + r config set moduleconfigs.flags "two three" r config rewrite restart_server 0 true false # Ensure configs we rewrote are present and that the conf file is readable @@ -147,6 +155,7 @@ start_server {tags {"modules"}} { assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 750" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two three}" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" r module unload moduleconfigs } @@ -221,11 +230,12 @@ start_server {tags {"modules"}} { 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]] { + start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two moduleconfigs.flags "two three"]] { assert_equal [r config get moduleconfigs.string] "moduleconfigs.string bootedup" assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" + assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two three}" 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/moduleapi/propagate.tcl b/tests/unit/moduleapi/propagate.tcl index bbc9d3800..846d938fb 100644 --- a/tests/unit/moduleapi/propagate.tcl +++ b/tests/unit/moduleapi/propagate.tcl @@ -108,10 +108,16 @@ tags "modules" { {set asdf3 3 PXAT *} {exec} {incr notifications} + {incr notifications} + {incr testkeyspace:expired} {del asdf*} {incr notifications} + {incr notifications} + {incr testkeyspace:expired} {del asdf*} {incr notifications} + {incr notifications} + {incr testkeyspace:expired} {del asdf*} } close_replication_stream $repl diff --git a/tests/unit/moduleapi/publish.tcl b/tests/unit/moduleapi/publish.tcl new file mode 100644 index 000000000..ab3611093 --- /dev/null +++ b/tests/unit/moduleapi/publish.tcl @@ -0,0 +1,17 @@ +set testmodule [file normalize tests/modules/publish.so] + +start_server {tags {"modules"}} { + r module load $testmodule + + test {PUBLISH and SPUBLISH via a module} { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + assert_equal {1} [ssubscribe $rd1 {chan1}] + assert_equal {1} [subscribe $rd2 {chan1}] + assert_equal 1 [r publish.shard chan1 hello] + assert_equal 1 [r publish.classic chan1 world] + assert_equal {message chan1 hello} [$rd1 read] + assert_equal {message chan1 world} [$rd2 read] + } +} diff --git a/tests/unit/moduleapi/subcommands.tcl b/tests/unit/moduleapi/subcommands.tcl index 11d243243..62de593e7 100644 --- a/tests/unit/moduleapi/subcommands.tcl +++ b/tests/unit/moduleapi/subcommands.tcl @@ -15,8 +15,8 @@ start_server {tags {"modules"}} { set docs_reply [r command docs subcommands.bitarray] set docs [dict create {*}[lindex $docs_reply 1]] set subcmds_in_cmd_docs [dict create {*}[dict get $docs subcommands]] - assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|get"] {group module} - assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {group module} + assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|get"] {group module module subcommands} + assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {group module module subcommands} } test "Module pure-container command fails on arity error" { @@ -25,6 +25,10 @@ start_server {tags {"modules"}} { # Subcommands can be called assert_equal [r subcommands.bitarray get k1] {OK} + + # Subcommand arity error + catch {r subcommands.bitarray get k1 8 90} e + assert_match {*wrong number of arguments for 'subcommands.bitarray|get' command} $e } test "Module get current command fullname" { diff --git a/tests/unit/oom-score-adj.tcl b/tests/unit/oom-score-adj.tcl index b226a266a..6c7b71392 100644 --- a/tests/unit/oom-score-adj.tcl +++ b/tests/unit/oom-score-adj.tcl @@ -122,5 +122,10 @@ if {$system_name eq {linux}} { r config set oom-score-adj absolute assert_equal [get_oom_score_adj] $custom_oom } + + test {CONFIG SET out-of-range oom score} { + assert_error {ERR *must be between -2000 and 2000*} {r config set oom-score-adj-values "-2001 -2001 -2001"} + assert_error {ERR *must be between -2000 and 2000*} {r config set oom-score-adj-values "2001 2001 2001"} + } } } diff --git a/tests/unit/other.tcl b/tests/unit/other.tcl index 258ef2f6e..bd220a145 100644 --- a/tests/unit/other.tcl +++ b/tests/unit/other.tcl @@ -311,7 +311,7 @@ start_server {tags {"other"}} { assert_error {*unknown command*} {r GET|SET} assert_error {*unknown command*} {r GET|SET|OTHER} assert_error {*unknown command*} {r CONFIG|GET GET_XX} - assert_error {*Unknown subcommand*} {r CONFIG GET_XX} + assert_error {*unknown subcommand*} {r CONFIG GET_XX} } } diff --git a/tests/unit/pubsub.tcl b/tests/unit/pubsub.tcl index 4435a9b1d..ea8964cf3 100644 --- a/tests/unit/pubsub.tcl +++ b/tests/unit/pubsub.tcl @@ -390,4 +390,17 @@ start_server {tags {"pubsub network"}} { r config set notify-keyspace-events EA assert_equal {AE} [lindex [r config get notify-keyspace-events] 1] } + + test "Keyspace notifications: new key test" { + r config set notify-keyspace-events En + set rd1 [redis_deferring_client] + assert_equal {1} [psubscribe $rd1 *] + r set foo bar + # second set of foo should not cause a 'new' event + r set foo baz + r set bar bar + assert_equal "pmessage * __keyevent@${db}__:new foo" [$rd1 read] + assert_equal "pmessage * __keyevent@${db}__:new bar" [$rd1 read] + $rd1 close + } } diff --git a/tests/unit/pubsubshard.tcl b/tests/unit/pubsubshard.tcl index 5c3564afe..d0023a841 100644 --- a/tests/unit/pubsubshard.tcl +++ b/tests/unit/pubsubshard.tcl @@ -1,80 +1,4 @@ start_server {tags {"pubsubshard external:skip"}} { - proc __consume_ssubscribe_messages {client type channels} { - set numsub -1 - set counts {} - - for {set i [llength $channels]} {$i > 0} {incr i -1} { - set msg [$client read] - assert_equal $type [lindex $msg 0] - - # when receiving subscribe messages the channels names - # are ordered. when receiving unsubscribe messages - # they are unordered - set idx [lsearch -exact $channels [lindex $msg 1]] - if {[string match "sunsubscribe" $type]} { - assert {$idx >= 0} - } else { - assert {$idx == 0} - } - set channels [lreplace $channels $idx $idx] - - # aggregate the subscription count to return to the caller - lappend counts [lindex $msg 2] - } - - # we should have received messages for channels - assert {[llength $channels] == 0} - return $counts - } - - proc __consume_subscribe_messages {client type channels} { - set numsub -1 - set counts {} - - for {set i [llength $channels]} {$i > 0} {incr i -1} { - set msg [$client read] - assert_equal $type [lindex $msg 0] - - # when receiving subscribe messages the channels names - # are ordered. when receiving unsubscribe messages - # they are unordered - set idx [lsearch -exact $channels [lindex $msg 1]] - if {[string match "unsubscribe" $type]} { - assert {$idx >= 0} - } else { - assert {$idx == 0} - } - set channels [lreplace $channels $idx $idx] - - # aggregate the subscription count to return to the caller - lappend counts [lindex $msg 2] - } - - # we should have received messages for channels - assert {[llength $channels] == 0} - return $counts - } - - proc ssubscribe {client channels} { - $client ssubscribe {*}$channels - __consume_ssubscribe_messages $client ssubscribe $channels - } - - proc subscribe {client channels} { - $client subscribe {*}$channels - __consume_subscribe_messages $client subscribe $channels - } - - proc sunsubscribe {client {channels {}}} { - $client sunsubscribe {*}$channels - __consume_subscribe_messages $client sunsubscribe $channels - } - - proc unsubscribe {client {channels {}}} { - $client unsubscribe {*}$channels - __consume_subscribe_messages $client unsubscribe $channels - } - test "SPUBLISH/SSUBSCRIBE basics" { set rd1 [redis_deferring_client] diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index d9729b7bd..439a1e71a 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -52,7 +52,7 @@ start_server {tags {"scripting"}} { assert_match {*command not allowed when used memory*} $e r config set maxmemory 0 - } + } {OK} {needs:config-maxmemory} } ;# is_eval test {EVAL - Does Lua interpreter replies to our requests?} { @@ -447,12 +447,12 @@ start_server {tags {"scripting"}} { test {Globals protection reading an undeclared global variable} { catch {run_script {return a} 0} e set e - } {ERR*attempted to access * global*} + } {ERR *attempted to access * global*} test {Globals protection setting an undeclared global*} { catch {run_script {a=10} 0} e set e - } {ERR*attempted to create global*} + } {ERR *Attempt to modify a readonly table*} test {Test an example script DECR_IF_GT} { set decr_if_gt { @@ -735,6 +735,112 @@ start_server {tags {"scripting"}} { return redis.acl_check_cmd('invalid-cmd','arg') } 0} } + + test "Binary code loading failed" { + assert_error {ERR *attempt to call a nil value*} {run_script { + return loadstring(string.dump(function() return 1 end))() + } 0} + } + + test "Try trick global protection 1" { + catch { + run_script { + setmetatable(_G, {}) + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick global protection 2" { + catch { + run_script { + local g = getmetatable(_G) + g.__index = {} + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick global protection 3" { + catch { + run_script { + redis = function() return 1 end + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick global protection 4" { + catch { + run_script { + _G = {} + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick readonly table on redis table" { + catch { + run_script { + redis.call = function() return 1 end + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick readonly table on json table" { + catch { + run_script { + cjson.encode = function() return 1 end + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick readonly table on cmsgpack table" { + catch { + run_script { + cmsgpack.pack = function() return 1 end + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Try trick readonly table on bit table" { + catch { + run_script { + bit.lshift = function() return 1 end + } 0 + } e + set _ $e + } {*Attempt to modify a readonly table*} + + test "Test loadfile are not available" { + catch { + run_script { + loadfile('some file') + } 0 + } e + set _ $e + } {*Script attempted to access nonexistent global variable 'loadfile'*} + + test "Test dofile are not available" { + catch { + run_script { + dofile('some file') + } 0 + } e + set _ $e + } {*Script attempted to access nonexistent global variable 'dofile'*} + + test "Test print are not available" { + catch { + run_script { + print('some data') + } 0 + } e + set _ $e + } {*Script attempted to access nonexistent global variable 'print'*} } # Start a new server since the last test in this stanza will kill the @@ -1324,7 +1430,7 @@ start_server {tags {"scripting"}} { ] 1 r config set maxmemory 0 - } + } {OK} {needs:config-maxmemory} test "no-writes shebang flag" { assert_error {ERR Write commands are not allowed from read-only scripts*} { diff --git a/tests/unit/slowlog.tcl b/tests/unit/slowlog.tcl index 2f4fb35e3..8ce1b1c27 100644 --- a/tests/unit/slowlog.tcl +++ b/tests/unit/slowlog.tcl @@ -53,17 +53,17 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} { r config set masterauth "" r acl setuser slowlog-test-user +get +set r config set slowlog-log-slower-than 0 - r config set slowlog-log-slower-than 10000 + r config set slowlog-log-slower-than -1 set slowlog_resp [r slowlog get] # Make sure normal configs work, but the two sensitive # commands are omitted or redacted assert_equal 5 [llength $slowlog_resp] - assert_equal {slowlog reset} [lindex [lindex [r slowlog get] 4] 3] - assert_equal {acl setuser (redacted) (redacted) (redacted)} [lindex [lindex [r slowlog get] 3] 3] - assert_equal {config set masterauth (redacted)} [lindex [lindex [r slowlog get] 2] 3] - assert_equal {acl setuser (redacted) (redacted) (redacted)} [lindex [lindex [r slowlog get] 1] 3] - assert_equal {config set slowlog-log-slower-than 0} [lindex [lindex [r slowlog get] 0] 3] + assert_equal {slowlog reset} [lindex [lindex $slowlog_resp 4] 3] + assert_equal {acl setuser (redacted) (redacted) (redacted)} [lindex [lindex $slowlog_resp 3] 3] + assert_equal {config set masterauth (redacted)} [lindex [lindex $slowlog_resp 2] 3] + assert_equal {acl setuser (redacted) (redacted) (redacted)} [lindex [lindex $slowlog_resp 1] 3] + assert_equal {config set slowlog-log-slower-than 0} [lindex [lindex $slowlog_resp 0] 3] } {} {needs:repl} test {SLOWLOG - Some commands can redact sensitive fields} { @@ -72,13 +72,14 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} { r migrate [srv 0 host] [srv 0 port] key 9 5000 r migrate [srv 0 host] [srv 0 port] key 9 5000 AUTH user r migrate [srv 0 host] [srv 0 port] key 9 5000 AUTH2 user password + r config set slowlog-log-slower-than -1 + set slowlog_resp [r slowlog get] - r config set slowlog-log-slower-than 10000 # Make sure all 3 commands were logged, but the sensitive fields are omitted - assert_equal 4 [llength [r slowlog get]] - assert_match {* key 9 5000} [lindex [lindex [r slowlog get] 2] 3] - assert_match {* key 9 5000 AUTH (redacted)} [lindex [lindex [r slowlog get] 1] 3] - assert_match {* key 9 5000 AUTH2 (redacted) (redacted)} [lindex [lindex [r slowlog get] 0] 3] + assert_equal 4 [llength $slowlog_resp] + assert_match {* key 9 5000} [lindex [lindex $slowlog_resp 2] 3] + assert_match {* key 9 5000 AUTH (redacted)} [lindex [lindex $slowlog_resp 1] 3] + assert_match {* key 9 5000 AUTH2 (redacted) (redacted)} [lindex [lindex $slowlog_resp 0] 3] } {} {needs:repl} test {SLOWLOG - Rewritten commands are logged as their original command} { @@ -199,4 +200,4 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} { assert_equal 3 [llength [r slowlog get -1]] assert_equal 3 [llength [r slowlog get 3]] } -}
\ No newline at end of file +} diff --git a/tests/unit/type/hash.tcl b/tests/unit/type/hash.tcl index 75ba29f77..ae5677383 100644 --- a/tests/unit/type/hash.tcl +++ b/tests/unit/type/hash.tcl @@ -507,8 +507,8 @@ start_server {tags {"hash"}} { catch {r hincrby smallhash str 1} smallerr catch {r hincrby bighash str 1} bigerr set rv {} - lappend rv [string match "ERR*not an integer*" $smallerr] - lappend rv [string match "ERR*not an integer*" $bigerr] + lappend rv [string match "ERR *not an integer*" $smallerr] + lappend rv [string match "ERR *not an integer*" $bigerr] } {1 1} test {HINCRBY fails against hash value with spaces (right)} { @@ -517,8 +517,8 @@ start_server {tags {"hash"}} { catch {r hincrby smallhash str 1} smallerr catch {r hincrby bighash str 1} bigerr set rv {} - lappend rv [string match "ERR*not an integer*" $smallerr] - lappend rv [string match "ERR*not an integer*" $bigerr] + lappend rv [string match "ERR *not an integer*" $smallerr] + lappend rv [string match "ERR *not an integer*" $bigerr] } {1 1} test {HINCRBY can detect overflows} { @@ -579,8 +579,8 @@ start_server {tags {"hash"}} { catch {r hincrbyfloat smallhash str 1} smallerr catch {r hincrbyfloat bighash str 1} bigerr set rv {} - lappend rv [string match "ERR*not*float*" $smallerr] - lappend rv [string match "ERR*not*float*" $bigerr] + lappend rv [string match "ERR *not*float*" $smallerr] + lappend rv [string match "ERR *not*float*" $bigerr] } {1 1} test {HINCRBYFLOAT fails against hash value with spaces (right)} { @@ -589,15 +589,15 @@ start_server {tags {"hash"}} { catch {r hincrbyfloat smallhash str 1} smallerr catch {r hincrbyfloat bighash str 1} bigerr set rv {} - lappend rv [string match "ERR*not*float*" $smallerr] - lappend rv [string match "ERR*not*float*" $bigerr] + lappend rv [string match "ERR *not*float*" $smallerr] + lappend rv [string match "ERR *not*float*" $bigerr] } {1 1} test {HINCRBYFLOAT fails against hash value that contains a null-terminator in the middle} { r hset h f "1\x002" catch {r hincrbyfloat h f 1} err set rv {} - lappend rv [string match "ERR*not*float*" $err] + lappend rv [string match "ERR *not*float*" $err] } {1} test {HSTRLEN against the small hash} { @@ -648,6 +648,31 @@ start_server {tags {"hash"}} { } } + test {HINCRBYFLOAT over hash-max-listpack-value encoded with a listpack} { + set original_max_value [lindex [r config get hash-max-ziplist-value] 1] + r config set hash-max-listpack-value 8 + + # hash's value exceeds hash-max-listpack-value + r del smallhash + r del bighash + r hset smallhash tmp 0 + r hset bighash tmp 0 + r hincrbyfloat smallhash tmp 0.000005 + r hincrbyfloat bighash tmp 0.0000005 + assert_encoding listpack smallhash + assert_encoding hashtable bighash + + # hash's field exceeds hash-max-listpack-value + r del smallhash + r del bighash + r hincrbyfloat smallhash abcdefgh 1 + r hincrbyfloat bighash abcdefghi 1 + assert_encoding listpack smallhash + assert_encoding hashtable bighash + + r config set hash-max-listpack-value $original_max_value + } + test {Hash ziplist regression test for large keys} { r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk a r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk b diff --git a/tests/unit/type/incr.tcl b/tests/unit/type/incr.tcl index b6aaa1112..b7446850e 100644 --- a/tests/unit/type/incr.tcl +++ b/tests/unit/type/incr.tcl @@ -111,21 +111,21 @@ start_server {tags {"incr"}} { r set novar " 11" catch {r incrbyfloat novar 1.0} err format $err - } {ERR*valid*} + } {ERR *valid*} test {INCRBYFLOAT fails against key with spaces (right)} { set err {} r set novar "11 " catch {r incrbyfloat novar 1.0} err format $err - } {ERR*valid*} + } {ERR *valid*} test {INCRBYFLOAT fails against key with spaces (both)} { set err {} r set novar " 11 " catch {r incrbyfloat novar 1.0} err format $err - } {ERR*valid*} + } {ERR *valid*} test {INCRBYFLOAT fails against a key holding a list} { r del mylist @@ -146,7 +146,7 @@ start_server {tags {"incr"}} { # p.s. no way I can force NaN to test it from the API because # there is no way to increment / decrement by infinity nor to # perform divisions. - } {ERR*would produce*} + } {ERR *would produce*} } test {INCRBYFLOAT decrement} { @@ -159,7 +159,7 @@ start_server {tags {"incr"}} { r setrange foo 2 2 catch {r incrbyfloat foo 1} err format $err - } {ERR*valid*} + } {ERR *valid*} test {No negative zero} { r del foo diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl index 8cff56c6a..ecf23bdac 100644 --- a/tests/unit/type/list.tcl +++ b/tests/unit/type/list.tcl @@ -252,6 +252,7 @@ start_server [list overrides [list save ""] ] { } {} {needs:debug} } +run_solo {list-large-memory} { start_server [list overrides [list save ""] ] { # test if the server supports such large configs (avoid 32 bit builds) @@ -349,6 +350,7 @@ if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { } {} {large-memory} } ;# skip 32bit builds } +} ;# run_solo start_server { tags {"list"} @@ -510,7 +512,11 @@ start_server { } foreach resp {3 2} { - r hello $resp + if {[lsearch $::denytags "resp3"] >= 0} { + if {$resp == 3} {continue} + } else { + r hello $resp + } # Make sure we can distinguish between an empty array and a null response r readraw 1 @@ -1173,7 +1179,7 @@ foreach {pop} {BLPOP BLMPOP_LEFT} { test "$pop: with negative timeout" { set rd [redis_deferring_client] bpop_command $rd $pop blist1 -1 - assert_error "ERR*is negative*" {$rd read} + assert_error "ERR *is negative*" {$rd read} $rd close } diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl index 93f7311b1..4f207b727 100644 --- a/tests/unit/type/set.tcl +++ b/tests/unit/type/set.tcl @@ -935,6 +935,7 @@ start_server { } } +run_solo {set-large-memory} { start_server [list overrides [list save ""] ] { # test if the server supports such large configs (avoid 32 bit builds) @@ -969,3 +970,4 @@ if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { } {} {large-memory} } ;# skip 32bit builds } +} ;# run_solo diff --git a/tests/unit/type/stream.tcl b/tests/unit/type/stream.tcl index bd689cd29..83d29bbbf 100644 --- a/tests/unit/type/stream.tcl +++ b/tests/unit/type/stream.tcl @@ -770,7 +770,7 @@ start_server {tags {"stream xsetid"}} { catch {r XSETID mystream "1-1"} err r XADD mystream MAXLEN 0 * a b set err - } {ERR*smaller*} + } {ERR *smaller*} test {XSETID cannot SETID on non-existent key} { catch {r XSETID stream 1-1} err @@ -790,7 +790,7 @@ start_server {tags {"stream xsetid"}} { test {XSETID errors on negstive offset} { catch {r XSETID stream 1-1 ENTRIESADDED -1 MAXDELETEDID 0-0} err set _ $err - } {ERR*must be positive} + } {ERR *must be positive} test {XSETID cannot set the maximal tombstone with larger ID} { r DEL x @@ -799,7 +799,7 @@ start_server {tags {"stream xsetid"}} { catch {r XSETID x "1-0" ENTRIESADDED 1 MAXDELETEDID "2-0" } err r XADD mystream MAXLEN 0 * a b set err - } {ERR*smaller*} + } {ERR *smaller*} test {XSETID cannot set the offset to less than the length} { r DEL x @@ -808,7 +808,7 @@ start_server {tags {"stream xsetid"}} { catch {r XSETID x "1-0" ENTRIESADDED 0 MAXDELETEDID "0-0" } err r XADD mystream MAXLEN 0 * a b set err - } {ERR*smaller*} + } {ERR *smaller*} } start_server {tags {"stream offset"}} { diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index 3ccfa61ab..6df856e31 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -1223,11 +1223,20 @@ start_server {tags {"zset"}} { } {} {needs:repl} foreach resp {3 2} { + set rd [redis_deferring_client] + + if {[lsearch $::denytags "resp3"] >= 0} { + if {$resp == 3} {continue} + } else { + r hello $resp + $rd hello $resp + $rd read + } + test "ZPOPMIN/ZPOPMAX readraw in RESP$resp" { r del zset{t} create_zset zset2{t} {1 a 2 b 3 c 4 d 5 e} - r hello $resp r readraw 1 # ZPOP against non existing key. @@ -1260,9 +1269,6 @@ start_server {tags {"zset"}} { r del zset{t} create_zset zset2{t} {1 a 2 b 3 c 4 d 5 e} - set rd [redis_deferring_client] - $rd hello $resp - $rd read $rd readraw 1 # BZPOP released on timeout. @@ -1291,7 +1297,7 @@ start_server {tags {"zset"}} { assert_equal [$rd read] {a} verify_score_response $rd $resp 1 - $rd close + $rd readraw 0 } test "ZMPOP readraw in RESP$resp" { @@ -1299,7 +1305,6 @@ start_server {tags {"zset"}} { create_zset zset3{t} {1 a} create_zset zset4{t} {1 a 2 b 3 c 4 d 5 e} - r hello $resp r readraw 1 # ZMPOP against non existing key. @@ -1339,9 +1344,6 @@ start_server {tags {"zset"}} { r del zset{t} zset2{t} create_zset zset3{t} {1 a 2 b 3 c 4 d 5 e} - set rd [redis_deferring_client] - $rd hello $resp - $rd read $rd readraw 1 # BZMPOP released on timeout. @@ -1380,8 +1382,9 @@ start_server {tags {"zset"}} { assert_equal [$rd read] {b} verify_score_response $rd $resp 2 - $rd close } + + $rd close } test {ZINTERSTORE regression with two sets, intset+hashtable} { diff --git a/tests/unit/violations.tcl b/tests/unit/violations.tcl index 716edf8ac..783f306d1 100644 --- a/tests/unit/violations.tcl +++ b/tests/unit/violations.tcl @@ -1,5 +1,6 @@ # One XADD with one huge 5GB field # Expected to fail resulting in an empty stream +run_solo {violations} { start_server [list overrides [list save ""] ] { test {XADD one huge field} { r config set proto-max-bulk-len 10000000000 ;#10gb @@ -85,7 +86,7 @@ start_server [list overrides [list save ""] ] { r object encoding H1 } {hashtable} {large-memory} } - +} ;# run_solo # SORT which stores an integer encoded element into a list. # Just for coverage, no news here. |