From: Heinonen Arvo Date: Thu, 16 Jan 2020 15:23:07 +0000 (+0200) Subject: Add refactored nget commands and UT X-Git-Tag: 0.4.0~7 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=dc8b09a63af0b4cba51b49b758a999aeeac1630e;p=ric-plt%2Fdbaas.git Add refactored nget commands and UT Add refactored nget commands 'nget.atomic' and 'nget.noatomic'. Add new unit tests for these commands. Signed-off-by: Arvo Heinonen Change-Id: Icbb116bf59f3e84dfdcee1a55d4209affa6b4244 --- diff --git a/redismodule/Makefile.am b/redismodule/Makefile.am index 6faacad..4ba5411 100755 --- a/redismodule/Makefile.am +++ b/redismodule/Makefile.am @@ -27,7 +27,7 @@ clean-local: if UNIT_TEST_ENABLED # UT CPP_U_TEST=$(CPP_U_TEST_LATEST) -check_PROGRAMS = redismodule_ut +check_PROGRAMS = redismodule_ut redismodule_ut2 #TESTS = ${check_PROGRAMS} redismodule_ut_SOURCES = \ src/exstrings.c \ @@ -67,7 +67,48 @@ redismodule_ut_LDFLAGS = -Wl,-rpath=${libdir} ${UT_COVERAGE_LDFLAGS} redismodule_ut_LDADD = -L${libdir} $(LIBCPPUTEST_LIBS) -lgcov -test: redismodule_ut +redismodule_ut2_SOURCES = \ + src/exstrings.c \ + tst/include/ut_helpers.hpp \ + tst/mock/include/commonStub.h \ + tst/mock/include/exstringsStub.h \ + tst/mock/include/redismodule.h \ + tst/mock/src/commonStub.cpp \ + tst/mock/src/redismoduleNewStub.cpp \ + tst/src/exstrings_nget_test.cpp \ + tst/src/main.cpp \ + tst/src/ut_helpers.cpp + + +redismodule_ut2_CFLAGS = \ + -std=c11 -g -Wall \ + -fprofile-arcs -ftest-coverage \ + -D__UT__ \ + $(LIBCPPUTEST_CFLAGS) \ + -I${top_srcdir}/tst/mock/include \ + -I${includedir} \ + -I${top_srcdir}/include \ + -I${CPP_U_TEST_LATEST}/include \ + -Wall -Werror -Wextra + +redismodule_ut2_CXXFLAGS = \ + -std=c++11 -g -Wall \ + -fprofile-arcs -ftest-coverage \ + -D__UT__ \ + $(LIBCPPUTEST_CFLAGS) \ + -I${top_srcdir}/tst/mock/include \ + -I${top_srcdir}/tst/include \ + -I${includedir} \ + -I${top_srcdir}/include \ + -I${CPP_U_TEST_LATEST}/include \ + -Wall -Werror -Wextra + + +redismodule_ut2_LDFLAGS = -Wl,-rpath=${libdir} ${UT_COVERAGE_LDFLAGS} +redismodule_ut2_LDADD = -L${libdir} $(LIBCPPUTEST_LIBS) -lgcov + + +test: redismodule_ut redismodule_ut2 ./run-tests.sh TESTS = run-tests.sh diff --git a/redismodule/run-tests.sh.in b/redismodule/run-tests.sh.in index 194095d..4de7716 100755 --- a/redismodule/run-tests.sh.in +++ b/redismodule/run-tests.sh.in @@ -1,6 +1,7 @@ #!/bin/sh valgrind --leak-check=full ./redismodule_ut +valgrind --leak-check=full ./redismodule_ut2 if [ $? != 0 ] then exit -1 diff --git a/redismodule/src/exstrings.c b/redismodule/src/exstrings.c index c1fe8d1..6f57753 100755 --- a/redismodule/src/exstrings.c +++ b/redismodule/src/exstrings.c @@ -20,9 +20,11 @@ */ #include "redismodule.h" -#include #include #include +#include +#include +#include #ifdef __UT__ #include "exstringsStub.h" @@ -53,6 +55,16 @@ sends the error to the client and exit the current function if its */ RedisModuleString *def_count_str = NULL, *match_str = NULL, *count_str = NULL, *zero_str = NULL; +typedef struct _NgetArgs { + RedisModuleString *key; + RedisModuleString *count; +} NgetArgs; + +typedef struct RedisModuleBlockedClientArgs { + RedisModuleBlockedClient *bc; + NgetArgs nget_args; +} RedisModuleBlockedClientArgs; + void InitStaticVariable() { if (def_count_str == NULL) @@ -100,6 +112,165 @@ typedef struct _DelParams { size_t length; } DelParams; +typedef enum _ExstringsStatus { + EXSTRINGS_STATUS_NO_ERRORS = 0, + EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT, + EXSTRINGS_STATUS_NOT_SET +} ExstringsStatus; + +void readNgetArgs(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, + NgetArgs* nget_args, ExstringsStatus* status) +{ + size_t str_len; + long long number; + + if(argc == 2) { + nget_args->key = argv[1]; + nget_args->count = def_count_str; + } else if (argc == 4) { + if (strcasecmp(RedisModule_StringPtrLen(argv[2], &str_len), "count")) { + RedisModule_ReplyWithError(ctx,"-ERR syntax error"); + *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT; + return; + } + + int ret = RedisModule_StringToLongLong(argv[3], &number) != REDISMODULE_OK; + if (ret != REDISMODULE_OK || number < 1) { + RedisModule_ReplyWithError(ctx,"-ERR value is not an integer or out of range"); + *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT; + return; + } + + nget_args->key = argv[1]; + nget_args->count = argv[3]; + } else { + /* In redis there is a bug (or undocumented feature see link) + * where calling 'RedisModule_WrongArity' + * within a blocked client will crash redis. + * + * Therefore we need to call this function to validate args + * before putting the client into blocking mode. + * + * Link to issue: + * https://github.com/antirez/redis/issues/6382 + * 'If any thread tries to access the command arguments from + * within the ThreadSafeContext they will crash redis' */ + RedisModule_WrongArity(ctx); + *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT; + return; + } + + *status = EXSTRINGS_STATUS_NO_ERRORS; + return; +} + +long long callReplyLongLong(RedisModuleCallReply* reply) +{ + const char* cursor_str_ptr = RedisModule_CallReplyStringPtr(reply, NULL); + return strtoll(cursor_str_ptr, NULL, 10); +} + +void forwardIfError(RedisModuleCtx *ctx, RedisModuleCallReply *reply, ExstringsStatus* status) +{ + if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) { + RedisModule_ReplyWithCallReply(ctx, reply); + RedisModule_FreeCallReply(reply); + *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT; + } + *status = EXSTRINGS_STATUS_NO_ERRORS; +} + +typedef struct _ScannedKeys { + RedisModuleString **keys; + size_t len; +} ScannedKeys; + +ScannedKeys* allocScannedKeys(size_t len) +{ + ScannedKeys *sk = RedisModule_Alloc(sizeof(ScannedKeys)); + if (sk) { + sk->len = len; + sk->keys = RedisModule_Alloc(sizeof(RedisModuleString *)*len); + } + return sk; +} + +void freeScannedKeys(RedisModuleCtx *ctx, ScannedKeys* sk) +{ + if (sk) { + size_t j; + for (j = 0; j < sk->len; j++) + RedisModule_FreeString(ctx, sk->keys[j]); + RedisModule_Free(sk->keys); + } + RedisModule_Free(sk); +} + +typedef struct _ScanSomeState { + RedisModuleString *key; + RedisModuleString *count; + long long cursor; +} ScanSomeState; + +ScannedKeys *scanSome(RedisModuleCtx* ctx, ScanSomeState* state, ExstringsStatus* status) +{ + RedisModuleString *scanargv[SCANARGC] = {NULL}; + + scanargv[0] = RedisModule_CreateStringFromLongLong(ctx, state->cursor); + scanargv[1] = match_str; + scanargv[2] = state->key; + scanargv[3] = count_str; + scanargv[4] = state->count; + + RedisModuleCallReply *reply; + reply = RedisModule_Call(ctx, "SCAN", "v", scanargv, SCANARGC); + RedisModule_FreeString(ctx, scanargv[0]); + forwardIfError(ctx, reply, status); + if (*status == EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT) + return NULL; + + state->cursor = callReplyLongLong(RedisModule_CallReplyArrayElement(reply, 0)); + RedisModuleCallReply *cr_keys = + RedisModule_CallReplyArrayElement(reply, 1); + + size_t scanned_keys_len = RedisModule_CallReplyLength(cr_keys); + if (scanned_keys_len == 0) { + RedisModule_FreeCallReply(reply); + *status = EXSTRINGS_STATUS_NO_ERRORS; + return NULL; + } + + ScannedKeys *scanned_keys = allocScannedKeys(scanned_keys_len); + if (scanned_keys == NULL) { + RedisModule_FreeCallReply(reply); + RedisModule_ReplyWithError(ctx,"-ERR Out of memory"); + *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT; + return NULL; + } + + scanned_keys->len = scanned_keys_len; + size_t j; + for (j = 0; j < scanned_keys_len; j++) { + RedisModuleString *rms = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(cr_keys,j)); + scanned_keys->keys[j] = rms; + } + RedisModule_FreeCallReply(reply); + *status = EXSTRINGS_STATUS_NO_ERRORS; + return scanned_keys; +} + +inline void unlockThreadsafeContext(RedisModuleCtx *ctx, bool using_threadsafe_context) +{ + if (using_threadsafe_context) + RedisModule_ThreadSafeContextUnlock(ctx); +} + +inline void lockThreadsafeContext(RedisModuleCtx *ctx, bool using_threadsafe_context) +{ + if (using_threadsafe_context) + RedisModule_ThreadSafeContextLock(ctx); +} + void multiPubCommand(RedisModuleCtx *ctx, PubParams* pubParams) { RedisModuleCallReply *reply = NULL; @@ -539,6 +710,136 @@ int DelNEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int arg return delIENEPubStringCommon(ctx, argv, argc, OBJ_OP_NE); } +int Nget_RedisCommand(RedisModuleCtx *ctx, NgetArgs* nget_args, bool using_threadsafe_context) +{ + int ret = REDISMODULE_OK; + size_t replylen = 0; + RedisModuleCallReply *reply = NULL; + ExstringsStatus status = EXSTRINGS_STATUS_NOT_SET; + ScanSomeState scan_state; + ScannedKeys *scanned_keys; + + scan_state.key = nget_args->key; + scan_state.count = nget_args->count; + scan_state.cursor = 0; + + RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); + do { + lockThreadsafeContext(ctx, using_threadsafe_context); + + status = EXSTRINGS_STATUS_NOT_SET; + scanned_keys = scanSome(ctx, &scan_state, &status); + + if (status != EXSTRINGS_STATUS_NO_ERRORS) { + unlockThreadsafeContext(ctx, using_threadsafe_context); + ret = REDISMODULE_ERR; + break; + } else if (scanned_keys == NULL) { + unlockThreadsafeContext(ctx, using_threadsafe_context); + continue; + } + + reply = RedisModule_Call(ctx, "MGET", "v", scanned_keys->keys, scanned_keys->len); + + unlockThreadsafeContext(ctx, using_threadsafe_context); + + status = EXSTRINGS_STATUS_NOT_SET; + forwardIfError(ctx, reply, &status); + if (status != EXSTRINGS_STATUS_NO_ERRORS) { + freeScannedKeys(ctx, scanned_keys); + ret = REDISMODULE_ERR; + break; + } + + size_t i; + for (i = 0; i < scanned_keys->len; i++) { + RedisModuleString *rms = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(reply, i)); + if (rms) { + RedisModule_ReplyWithString(ctx, scanned_keys->keys[i]); + RedisModule_ReplyWithString(ctx, rms); + RedisModule_FreeString(ctx, rms); + replylen += 2; + } + } + RedisModule_FreeCallReply(reply); + freeScannedKeys(ctx, scanned_keys); + } while (scan_state.cursor != 0); + + RedisModule_ReplySetArrayLength(ctx,replylen); + return ret; +} + +/* The thread entry point that actually executes the blocking part + * of the command nget.noatomic + */ +void *NGet_NoAtomic_ThreadMain(void *arg) +{ + RedisModuleBlockedClientArgs *bca = arg; + RedisModuleBlockedClient *bc = bca->bc; + RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc); + + Nget_RedisCommand(ctx, &bca->nget_args, true); + RedisModule_FreeThreadSafeContext(ctx); + RedisModule_UnblockClient(bc, NULL); + RedisModule_Free(bca); + return NULL; +} + +int NGet_NoAtomic_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + RedisModule_AutoMemory(ctx); + pthread_t tid; + + InitStaticVariable(); + + RedisModuleBlockedClientArgs *bca = RedisModule_Alloc(sizeof(RedisModuleBlockedClientArgs)); + if (bca == NULL) { + RedisModule_ReplyWithError(ctx,"-ERR Out of memory"); + return REDISMODULE_ERR; + } + + ExstringsStatus status = EXSTRINGS_STATUS_NOT_SET; + readNgetArgs(ctx, argv, argc, &bca->nget_args, &status); + if (status != EXSTRINGS_STATUS_NO_ERRORS) { + RedisModule_Free(bca); + return REDISMODULE_ERR; + } + + /* Note that when blocking the client we do not set any callback: no + * timeout is possible since we passed '0', nor we need a reply callback + * because we'll use the thread safe context to accumulate a reply. */ + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,NULL,NULL,NULL,0); + + bca->bc = bc; + + /* Now that we setup a blocking client, we need to pass the control + * to the thread. However we need to pass arguments to the thread: + * the reference to the blocked client handle. */ + if (pthread_create(&tid,NULL,NGet_NoAtomic_ThreadMain,bca) != 0) { + RedisModule_AbortBlock(bc); + RedisModule_Free(bca); + return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread"); + } + + return REDISMODULE_OK; +} + +int NGet_Atomic_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + RedisModule_AutoMemory(ctx); + NgetArgs nget_args; + ExstringsStatus status = EXSTRINGS_STATUS_NOT_SET; + + InitStaticVariable(); + + readNgetArgs(ctx, argv, argc, &nget_args, &status); + if (status != EXSTRINGS_STATUS_NO_ERRORS) { + return REDISMODULE_ERR; + } + + return Nget_RedisCommand(ctx, &nget_args, false); +} + /* 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) { @@ -564,6 +865,14 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) DelNE_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"nget.atomic", + NGet_Atomic_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"nget.noatomic", + NGet_NoAtomic_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"msetpub", SetPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; diff --git a/redismodule/tst/include/ut_helpers.hpp b/redismodule/tst/include/ut_helpers.hpp new file mode 100644 index 0000000..c183f66 --- /dev/null +++ b/redismodule/tst/include/ut_helpers.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2020 Nokia. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +#ifndef EXSTRING_UT_HELPERS_H_ +#define EXSTRING_UT_HELPERS_H_ + +extern "C" { +#include "exstringsStub.h" +#include "redismodule.h" +} + +#define UT_DUMMY_BUFFER_SIZE 1 +#define UT_DUMMY_PTR_ADDRESS 1234 + +RedisModuleString **createRedisStrVec(size_t size); + +void returnNKeysFromScanSome(long keys); + +#endif diff --git a/redismodule/tst/mock/include/exstringsStub.h b/redismodule/tst/mock/include/exstringsStub.h index 756e5de..fd90662 100755 --- a/redismodule/tst/mock/include/exstringsStub.h +++ b/redismodule/tst/mock/include/exstringsStub.h @@ -49,5 +49,8 @@ int DelMPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc int DelIEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); int DelIEMPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); int DelNEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +int NGet_Atomic_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +int NGet_NoAtomic_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +void *NGet_NoAtomic_ThreadMain(void *arg); #endif diff --git a/redismodule/tst/mock/src/redismoduleNewStub.cpp b/redismodule/tst/mock/src/redismoduleNewStub.cpp new file mode 100755 index 0000000..5a25867 --- /dev/null +++ b/redismodule/tst/mock/src/redismoduleNewStub.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2018-2020 Nokia. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + + +#include +#include + +extern "C" { +#include "redismodule.h" +#include +#include +#include +} + +#include +#include + +#include "ut_helpers.hpp" + +RedisModuleCallReply *RedisModule_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) +{ + (void)ctx; + (void)fmt; + return (RedisModuleCallReply *)mock().actualCall("RedisModule_Call") + .withParameter("cmdname", cmdname) + .returnPointerValueOrDefault(malloc(UT_DUMMY_BUFFER_SIZE)); +} + +int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) +{ + (void)ctx; + (void)str; + return mock() + .actualCall("RedisModule_ReplyWithString") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +RedisModuleString *RedisModule_CreateStringFromCallReply(RedisModuleCallReply *reply) +{ + (void)reply; + return (RedisModuleString *)mock().actualCall("RedisModule_CreateStringFromCallReply") + .returnPointerValue(); +} + +void RedisModule_CloseKey(RedisModuleKey *kp) +{ + (void)kp; + mock().actualCall("RedisModule_CloseKey"); +} + +size_t RedisModule_CallReplyLength(RedisModuleCallReply *reply) +{ + (void)reply; + return (size_t)mock().actualCall("RedisModule_CallReplyLength") + .returnIntValue(); +} + +int RedisModule_ReplyWithArray(RedisModuleCtx *ctx, long len) +{ + (void)ctx; + return (int)mock().actualCall("RedisModule_ReplyWithArray") + .withParameter("len", len) + .returnIntValueOrDefault(REDISMODULE_OK); +} + +void RedisModule_ReplySetArrayLength(RedisModuleCtx *ctx, long len) +{ + (void)ctx; + mock().actualCall("RedisModule_ReplySetArrayLength") + .withParameter("len", len); +} + +RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len) +{ + (void)ctx; + (void)ptr; + (void)len; + void* buf = malloc(UT_DUMMY_BUFFER_SIZE); + return (RedisModuleString *) mock() + .actualCall("RedisModule_CreateString") + .returnPointerValueOrDefault(buf); +} + +RedisModuleString *RedisModule_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll) +{ + (void)ctx; + (void)ll; + void* buf = malloc(UT_DUMMY_BUFFER_SIZE); + return (RedisModuleString *)mock() + .actualCall("RedisModule_CreateStringFromLongLong") + .returnPointerValueOrDefault(buf); +} + +void RedisModule_AutoMemory(RedisModuleCtx *ctx) +{ + (void)ctx; + mock().actualCall("RedisModule_AutoMemory"); +} + +void RedisModule_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) +{ + (void)ctx; + free(str); + mock().actualCall("RedisModule_FreeString"); +} + +int RedisModule_StringToLongLong(const RedisModuleString *str, long long *ll) +{ + (void)str; + return (int)mock().actualCall("RedisModule_StringToLongLong") + .withOutputParameter("ll", ll) + .returnIntValueOrDefault(REDISMODULE_OK); +} + +void RedisModule_FreeCallReply(RedisModuleCallReply *reply) +{ + free(reply); + mock().actualCall("RedisModule_FreeCallReply"); +} + +RedisModuleCallReply *RedisModule_CallReplyArrayElement(RedisModuleCallReply *reply, size_t idx) +{ + (void)reply; + (void)idx; + return (RedisModuleCallReply *)mock() + .actualCall("RedisModule_CallReplyArrayElement") + .returnPointerValueOrDefault(NULL); +} + +int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) +{ + (void)ctx; + return (int)mock() + .actualCall("RedisModule_ReplyWithLongLong") + .withParameter("ll", (int)ll) + .returnIntValueOrDefault(REDISMODULE_OK); +} + +long long RedisModule_CallReplyInteger(RedisModuleCallReply *reply) +{ + (void)reply; + return (long long)mock() + .actualCall("RedisModule_CallReplyInteger") + .returnIntValue(); +} + +int RedisModule_CallReplyType(RedisModuleCallReply *reply) +{ + (void)reply; + return (int)mock() + .actualCall("RedisModule_CallReplyType") + .returnIntValue(); +} + +int RedisModule_WrongArity(RedisModuleCtx *ctx) +{ + (void)ctx; + return (int)mock() + .actualCall("RedisModule_WrongArity") + .returnIntValueOrDefault(REDISMODULE_ERR); +} + +int RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err) +{ + (void)ctx; + (void)err; + return (int)mock() + .actualCall("RedisModule_ReplyWithError") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +int RedisModule_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) +{ + (void)ctx; + (void)reply; + return (int)mock() + .actualCall("RedisModule_ReplyWithCallReply") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +void *RedisModule_OpenKey(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) +{ + (void)ctx; + (void)keyname; + (void)mode; + return (void *)mock() + .actualCall("RedisModule_OpenKey") + .returnPointerValue(); +} + +int RedisModule_KeyType(RedisModuleKey *kp) +{ + (void)kp; + return (int)mock() + .actualCall("RedisModule_KeyType") + .returnIntValue(); +} + +const char *RedisModule_StringPtrLen(const RedisModuleString *str, size_t *len) +{ + (void)str; + if (len != NULL) { + return (const char *)mock() + .actualCall("RedisModule_StringPtrLen") + .withOutputParameter("len", len) + .returnPointerValue(); + } else { + return (const char *)mock() + .actualCall("RedisModule_StringPtrLen") + .returnPointerValue(); + } +} + +RedisModuleBlockedClient *RedisModule_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, + RedisModuleCmdFunc timeout_callback, + void (*free_privdata)(RedisModuleCtx*,void*), + long long timeout_ms) +{ + (void)ctx; + (void)reply_callback; + (void)timeout_callback; + (void)free_privdata; + (void)timeout_ms; + + void *buf = malloc(UT_DUMMY_BUFFER_SIZE); + return (RedisModuleBlockedClient *)mock() + .actualCall("RedisModule_BlockClient") + .returnPointerValueOrDefault(buf); +} + +int RedisModule_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) +{ + (void)privdata; + + free(bc); + return (int)mock() + .actualCall("RedisModule_UnblockClient") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +const char *RedisModule_CallReplyStringPtr(RedisModuleCallReply *reply, size_t *len) +{ + (void)reply; + (void)len; + + static char cursor_zero_literal[] = "0"; + return (const char *)mock() + .actualCall("RedisModule_CallReplyStringPtr") + .returnPointerValueOrDefault(cursor_zero_literal); +} + +int RedisModule_AbortBlock(RedisModuleBlockedClient *bc) +{ + free(bc); + return mock() + .actualCall("RedisModule_AbortBlock") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +int RedisModule_ReplyWithNull(RedisModuleCtx *ctx) +{ + (void)ctx; + return mock() + .actualCall("RedisModule_ReplyWithNull") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +void RedisModule_ThreadSafeContextUnlock(RedisModuleCtx *ctx) +{ + (void)ctx; + int tmp = mock().getData("TimesThreadSafeContextWasUnlocked").getIntValue(); + mock().setData("TimesThreadSafeContextWasUnlocked", tmp + 1); + mock() + .actualCall("RedisModule_ThreadSafeContextUnlock"); +} + +void RedisModule_ThreadSafeContextLock(RedisModuleCtx *ctx) +{ + (void)ctx; + int tmp = mock().getData("TimesThreadSafeContextWasLocked").getIntValue(); + mock().setData("TimesThreadSafeContextWasLocked", tmp + 1); + mock() + .actualCall("RedisModule_ThreadSafeContextLock"); +} + +RedisModuleCtx *RedisModule_GetThreadSafeContext(RedisModuleBlockedClient *bc) +{ + (void)bc; + return (RedisModuleCtx *)mock() + .actualCall("RedisModule_GetThreadSafeContext") + .returnPointerValueOrDefault(0); +} + +void RedisModule_FreeThreadSafeContext(RedisModuleCtx *ctx) +{ + (void)ctx; + mock() + .actualCall("RedisModule_FreeThreadSafeContext"); +} + +/* This is included inline inside each Redis module. */ +int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) +{ + (void)ctx; + (void)name; + (void)ver; + (void)apiver; + + return mock() + .actualCall("RedisModule_Init") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) +{ + (void)ctx; + (void)name; + (void)cmdfunc; + (void)strflags; + (void)firstkey; + (void)lastkey; + (void)keystep; + + return mock() + .actualCall("RedisModule_CreateCommand") + .returnIntValueOrDefault(REDISMODULE_OK); +} + +void *RedisModule_Alloc(size_t bytes) +{ + void *buf = malloc(bytes); + return mock() + .actualCall("RedisModule_Alloc") + .returnPointerValueOrDefault(buf); +} + +void RedisModule_Free(void *ptr) +{ + free(ptr); + mock() + .actualCall("RedisModule_Free"); +} diff --git a/redismodule/tst/src/exstrings_nget_test.cpp b/redismodule/tst/src/exstrings_nget_test.cpp new file mode 100644 index 0000000..317769f --- /dev/null +++ b/redismodule/tst/src/exstrings_nget_test.cpp @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2018-2020 Nokia. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +extern "C" { +#include "exstringsStub.h" +#include "redismodule.h" +} + +#include "CppUTest/TestHarness.h" +#include "CppUTestExt/MockSupport.h" + +#include "ut_helpers.hpp" + +TEST_GROUP(exstrings_nget) +{ + void setup() + { + mock().enable(); + mock().ignoreOtherCalls(); + } + + void teardown() + { + mock().clear(); + mock().disable(); + } + +}; + +void nKeysFoundMget(long keys) +{ + for (long i = 0 ; i < keys ; i++) { + mock().expectOneCall("RedisModule_CreateStringFromCallReply") + .andReturnValue(malloc(UT_DUMMY_BUFFER_SIZE)); + mock().expectNCalls(2, "RedisModule_ReplyWithString"); + } +} + +void nKeysNotFoundMget(long keys) +{ + void* ptr = NULL; + mock().expectNCalls(keys, "RedisModule_CreateStringFromCallReply") + .andReturnValue(ptr); + mock().expectNoCall("RedisModule_ReplyWithString"); +} + +void expectNReplies(long count) +{ + mock().expectOneCall("RedisModule_ReplySetArrayLength") + .withParameter("len", 2*count); +} + +void threadSafeContextLockedAndUnlockedEqualTimes() +{ + int locked = mock().getData("TimesThreadSafeContextWasLocked").getIntValue(); + int unlocked = mock().getData("TimesThreadSafeContextWasUnlocked").getIntValue(); + CHECK_EQUAL(locked, unlocked); +} + +TEST(exstrings_nget, nget_atomic_automemory_enabled) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + long keys_found_with_scan = 0; + + mock().expectOneCall("RedisModule_AutoMemory"); + mock().expectOneCall("RedisModule_CallReplyLength") + .andReturnValue((int)keys_found_with_scan); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 2); + mock().checkExpectations(); + CHECK_EQUAL(ret, REDISMODULE_OK); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_parameter_number_incorrect) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().expectOneCall("RedisModule_WrongArity"); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 3); + CHECK_EQUAL(ret, REDISMODULE_ERR); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_3rd_parameter_was_not_equal_to_COUNT) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(4); + const char * not_count_literal = "NOT_COUNT"; + size_t not_count_len = strlen(not_count_literal); + + mock().expectOneCall("RedisModule_StringPtrLen") + .withOutputParameterReturning("len", ¬_count_len, sizeof(size_t)) + .andReturnValue((void*)not_count_literal); + mock().expectOneCall("RedisModule_ReplyWithError"); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 4); + CHECK_EQUAL(ret, REDISMODULE_ERR); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_4th_parameter_was_not_integer) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(4); + const char * count_literal = "COUNT"; + size_t count_len = strlen(count_literal); + size_t count_number = 123; + + mock().expectOneCall("RedisModule_StringPtrLen") + .withOutputParameterReturning("len", &count_len, sizeof(size_t)) + .andReturnValue((void*)count_literal); + mock().expectOneCall("RedisModule_StringToLongLong") + .withOutputParameterReturning("ll", &count_number, sizeof(size_t)) + .andReturnValue(REDISMODULE_ERR); + mock().expectOneCall("RedisModule_ReplyWithError"); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 4); + CHECK_EQUAL(ret, REDISMODULE_ERR); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_4th_parameter_was_negative) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(4); + const char * count_literal = "COUNT"; + size_t count_len = strlen(count_literal); + size_t count_number = -123; + + mock().expectOneCall("RedisModule_StringPtrLen") + .withOutputParameterReturning("len", &count_len, sizeof(size_t)) + .andReturnValue((void*)count_literal); + mock().expectOneCall("RedisModule_StringToLongLong") + .withOutputParameterReturning("ll", &count_number, sizeof(size_t)) + .andReturnValue(REDISMODULE_OK); + mock().expectOneCall("RedisModule_ReplyWithError"); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 4); + CHECK_EQUAL(ret, REDISMODULE_ERR); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_scan_returned_zero_keys) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(0); + expectNReplies(0); + mock().expectNoCall("RedisModule_Call"); + + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_3_keys_scanned_0_keys_mget) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "MGET"); + nKeysNotFoundMget(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + expectNReplies(0); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_3_keys_scanned_3_keys_mget) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "MGET"); + nKeysFoundMget(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + expectNReplies(3); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_atomic_command_3_keys_scanned_2_keys_mget) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "MGET"); + nKeysFoundMget(2); + nKeysNotFoundMget(1); + mock().expectOneCall("RedisModule_FreeCallReply"); + expectNReplies(2); + int ret = NGet_Atomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_automemory_enabled) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().setData("pthread_create_free_block_client_args", 1); + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_AutoMemory"); + + int ret = NGet_NoAtomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_thread_create_success) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().setData("pthread_create_free_block_client_args", 1); + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_BlockClient"); + mock().expectOneCall("pthread_create"); + mock().expectNoCall("RedisModule_AbortBlock"); + + int ret = NGet_NoAtomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_thread_create_fail) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_BlockClient"); + mock().expectOneCall("pthread_create") + .andReturnValue(1); + mock().expectOneCall("RedisModule_AbortBlock"); + + int ret = NGet_NoAtomic_RedisCommand(&ctx, redisStrVec, 2); + CHECK_EQUAL(ret, REDISMODULE_OK); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_parameter_number_incorrect) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(4); + + mock().expectOneCall("RedisModule_WrongArity"); + mock().expectNoCall("RedisModule_BlockClient"); + + int ret = NGet_NoAtomic_RedisCommand(&ctx, redisStrVec, 3); + + CHECK_EQUAL(ret, REDISMODULE_ERR); + mock().checkExpectations(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_threadmain_3rd_parameter_was_not_equal_to_COUNT) +{ + RedisModuleCtx ctx; + RedisModuleString ** redisStrVec = createRedisStrVec(4); + const char * not_count_literal = "NOT_COUNT"; + size_t not_count_len = strlen(not_count_literal); + + mock().expectOneCall("RedisModule_StringPtrLen") + .withOutputParameterReturning("len", ¬_count_len, sizeof(size_t)) + .andReturnValue((void*)not_count_literal); + mock().expectOneCall("RedisModule_ReplyWithError"); + mock().expectNoCall("RedisModule_BlockClient"); + + int ret = NGet_NoAtomic_RedisCommand(&ctx, redisStrVec, 4); + + CHECK_EQUAL(ret, REDISMODULE_ERR); + + mock().checkExpectations(); + threadSafeContextLockedAndUnlockedEqualTimes(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_4th_parameter_was_not_integer) +{ + RedisModuleCtx ctx; + const char * count_literal = "COUNT"; + size_t count_len = strlen(count_literal); + size_t count_number = -123; + RedisModuleString ** redisStrVec = createRedisStrVec(4); + + mock().expectOneCall("RedisModule_StringPtrLen") + .withOutputParameterReturning("len", &count_len, sizeof(size_t)) + .andReturnValue((void*)count_literal); + mock().expectOneCall("RedisModule_StringToLongLong") + .withOutputParameterReturning("ll", &count_number, sizeof(size_t)) + .andReturnValue(REDISMODULE_OK); + mock().expectOneCall("RedisModule_ReplyWithError"); + + int ret = NGet_NoAtomic_RedisCommand(&ctx, redisStrVec, 4); + + CHECK_EQUAL(ret, REDISMODULE_ERR); + mock().checkExpectations(); + threadSafeContextLockedAndUnlockedEqualTimes(); + + delete []redisStrVec; +} + +typedef struct RedisModuleBlockedClientArgs { + RedisModuleBlockedClient *bc; + RedisModuleString **argv; + int argc; +} RedisModuleBlockedClientArgs; + +TEST(exstrings_nget, nget_noatomic_threadmain_3_keys_scanned_3_keys_mget) +{ + RedisModuleCtx ctx; + RedisModuleBlockedClientArgs *bca = + (RedisModuleBlockedClientArgs*)RedisModule_Alloc(sizeof(RedisModuleBlockedClientArgs)); + RedisModuleBlockedClient *bc = RedisModule_BlockClient(&ctx,NULL,NULL,NULL,0); + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + bca->bc = bc; + bca->argv = redisStrVec; + bca->argc = 2; + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "MGET"); + nKeysFoundMget(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + expectNReplies(3); + mock().expectOneCall("RedisModule_FreeThreadSafeContext"); + mock().expectOneCall("RedisModule_UnblockClient"); + + NGet_NoAtomic_ThreadMain((void*)bca); + + mock().checkExpectations(); + threadSafeContextLockedAndUnlockedEqualTimes(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_threadmain_3_keys_scanned_0_keys_mget) +{ + RedisModuleCtx ctx; + RedisModuleBlockedClientArgs *bca = (RedisModuleBlockedClientArgs*)malloc(sizeof(RedisModuleBlockedClientArgs)); + RedisModuleBlockedClient *bc = RedisModule_BlockClient(&ctx,NULL,NULL,NULL,0); + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + bca->bc = bc; + bca->argv = redisStrVec; + bca->argc = 2; + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "MGET"); + nKeysNotFoundMget(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + expectNReplies(0); + mock().expectOneCall("RedisModule_FreeThreadSafeContext"); + mock().expectOneCall("RedisModule_UnblockClient"); + + NGet_NoAtomic_ThreadMain((void*)bca); + + mock().checkExpectations(); + threadSafeContextLockedAndUnlockedEqualTimes(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_threadmain_3_keys_scanned_2_keys_mget) +{ + RedisModuleCtx ctx; + RedisModuleBlockedClientArgs *bca = (RedisModuleBlockedClientArgs*)malloc(sizeof(RedisModuleBlockedClientArgs)); + RedisModuleBlockedClient *bc = RedisModule_BlockClient(&ctx,NULL,NULL,NULL,0); + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + bca->bc = bc; + bca->argv = redisStrVec; + bca->argc = 2; + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(3); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "MGET"); + nKeysNotFoundMget(1); + nKeysFoundMget(2); + mock().expectOneCall("RedisModule_FreeCallReply"); + expectNReplies(2); + mock().expectOneCall("RedisModule_FreeThreadSafeContext"); + mock().expectOneCall("RedisModule_UnblockClient"); + + NGet_NoAtomic_ThreadMain((void*)bca); + + mock().checkExpectations(); + threadSafeContextLockedAndUnlockedEqualTimes(); + + delete []redisStrVec; +} + +TEST(exstrings_nget, nget_noatomic_threadmain_scan_returned_zero_keys) +{ + RedisModuleCtx ctx; + RedisModuleBlockedClientArgs *bca = (RedisModuleBlockedClientArgs*)malloc(sizeof(RedisModuleBlockedClientArgs)); + RedisModuleBlockedClient *bc = RedisModule_BlockClient(&ctx,NULL,NULL,NULL,0); + RedisModuleString ** redisStrVec = createRedisStrVec(2); + + bca->bc = bc; + bca->argv = redisStrVec; + bca->argc = 2; + + mock().ignoreOtherCalls(); + mock().expectOneCall("RedisModule_ReplyWithArray") + .withParameter("len", (long)REDISMODULE_POSTPONED_ARRAY_LEN); + mock().expectOneCall("RedisModule_Call") + .withParameter("cmdname", "SCAN"); + returnNKeysFromScanSome(0); + mock().expectOneCall("RedisModule_FreeCallReply"); + mock().expectNoCall("RedisModule_Call"); + mock().expectOneCall("RedisModule_FreeThreadSafeContext"); + mock().expectOneCall("RedisModule_UnblockClient"); + + NGet_NoAtomic_ThreadMain((void*)bca); + + mock().checkExpectations(); + threadSafeContextLockedAndUnlockedEqualTimes(); + + delete []redisStrVec; +} diff --git a/redismodule/tst/src/ut_helpers.cpp b/redismodule/tst/src/ut_helpers.cpp new file mode 100644 index 0000000..2d63393 --- /dev/null +++ b/redismodule/tst/src/ut_helpers.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020 Nokia. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +#include + +#include "redismodule.h" +#include "ut_helpers.hpp" + +#include +#include + +RedisModuleString **createRedisStrVec(size_t size) +{ + RedisModuleString ** redisStrVec = new RedisModuleString*[size]; + for (size_t i = 0 ; i < size ; i++) { + redisStrVec[i] = (RedisModuleString *)UT_DUMMY_PTR_ADDRESS; + } + return redisStrVec; +} + +void returnNKeysFromScanSome(long keys) +{ + mock().expectOneCall("RedisModule_CallReplyLength") + .andReturnValue((int)keys); + for (long i = 0 ; i < keys ; i++) { + mock().expectOneCall("RedisModule_CreateStringFromCallReply") + .andReturnValue(malloc(UT_DUMMY_BUFFER_SIZE)); + } +} +