diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:52:54 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:52:54 +0100 |
| commit | dcacc00e3750300617ba6e16eb346713f91a783a (patch) | |
| tree | 38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/redis-unstable/tests/modules/basics.c | |
| parent | 58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff) | |
| download | crep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz | |
Remove testing data
Diffstat (limited to 'examples/redis-unstable/tests/modules/basics.c')
| -rw-r--r-- | examples/redis-unstable/tests/modules/basics.c | 1051 |
1 files changed, 0 insertions, 1051 deletions
diff --git a/examples/redis-unstable/tests/modules/basics.c b/examples/redis-unstable/tests/modules/basics.c deleted file mode 100644 index 85c6196..0000000 --- a/examples/redis-unstable/tests/modules/basics.c +++ /dev/null | |||
| @@ -1,1051 +0,0 @@ | |||
| 1 | /* Module designed to test the Redis modules subsystem. | ||
| 2 | * | ||
| 3 | * ----------------------------------------------------------------------------- | ||
| 4 | * | ||
| 5 | * Copyright (c) 2016-Present, Redis Ltd. | ||
| 6 | * All rights reserved. | ||
| 7 | * | ||
| 8 | * Licensed under your choice of (a) the Redis Source Available License 2.0 | ||
| 9 | * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the | ||
| 10 | * GNU Affero General Public License v3 (AGPLv3). | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "redismodule.h" | ||
| 14 | #include <string.h> | ||
| 15 | #include <stdlib.h> | ||
| 16 | |||
| 17 | /* --------------------------------- Helpers -------------------------------- */ | ||
| 18 | |||
| 19 | /* Return true if the reply and the C null term string matches. */ | ||
| 20 | int TestMatchReply(RedisModuleCallReply *reply, char *str) { | ||
| 21 | RedisModuleString *mystr; | ||
| 22 | mystr = RedisModule_CreateStringFromCallReply(reply); | ||
| 23 | if (!mystr) return 0; | ||
| 24 | const char *ptr = RedisModule_StringPtrLen(mystr,NULL); | ||
| 25 | return strcmp(ptr,str) == 0; | ||
| 26 | } | ||
| 27 | |||
| 28 | /* ------------------------------- Test units ------------------------------- */ | ||
| 29 | |||
| 30 | /* TEST.CALL -- Test Call() API. */ | ||
| 31 | int TestCall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 32 | REDISMODULE_NOT_USED(argv); | ||
| 33 | REDISMODULE_NOT_USED(argc); | ||
| 34 | |||
| 35 | RedisModule_AutoMemory(ctx); | ||
| 36 | RedisModuleCallReply *reply; | ||
| 37 | |||
| 38 | RedisModule_Call(ctx,"DEL","c","mylist"); | ||
| 39 | RedisModuleString *mystr = RedisModule_CreateString(ctx,"foo",3); | ||
| 40 | RedisModule_Call(ctx,"RPUSH","csl","mylist",mystr,(long long)1234); | ||
| 41 | reply = RedisModule_Call(ctx,"LRANGE","ccc","mylist","0","-1"); | ||
| 42 | long long items = RedisModule_CallReplyLength(reply); | ||
| 43 | if (items != 2) goto fail; | ||
| 44 | |||
| 45 | RedisModuleCallReply *item0, *item1; | ||
| 46 | |||
| 47 | item0 = RedisModule_CallReplyArrayElement(reply,0); | ||
| 48 | item1 = RedisModule_CallReplyArrayElement(reply,1); | ||
| 49 | if (!TestMatchReply(item0,"foo")) goto fail; | ||
| 50 | if (!TestMatchReply(item1,"1234")) goto fail; | ||
| 51 | |||
| 52 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 53 | return REDISMODULE_OK; | ||
| 54 | |||
| 55 | fail: | ||
| 56 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 57 | return REDISMODULE_OK; | ||
| 58 | } | ||
| 59 | |||
| 60 | int TestCallResp3Attribute(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 61 | REDISMODULE_NOT_USED(argv); | ||
| 62 | REDISMODULE_NOT_USED(argc); | ||
| 63 | |||
| 64 | RedisModule_AutoMemory(ctx); | ||
| 65 | RedisModuleCallReply *reply; | ||
| 66 | |||
| 67 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "attrib"); /* 3 stands for resp 3 reply */ | ||
| 68 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) goto fail; | ||
| 69 | |||
| 70 | /* make sure we can not reply to resp2 client with resp3 (it might be a string but it contains attribute) */ | ||
| 71 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 72 | |||
| 73 | if (!TestMatchReply(reply,"Some real reply following the attribute")) goto fail; | ||
| 74 | |||
| 75 | reply = RedisModule_CallReplyAttribute(reply); | ||
| 76 | if (!reply || RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ATTRIBUTE) goto fail; | ||
| 77 | /* make sure we can not reply to resp2 client with resp3 attribute */ | ||
| 78 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 79 | if (RedisModule_CallReplyLength(reply) != 1) goto fail; | ||
| 80 | |||
| 81 | RedisModuleCallReply *key, *val; | ||
| 82 | if (RedisModule_CallReplyAttributeElement(reply,0,&key,&val) != REDISMODULE_OK) goto fail; | ||
| 83 | if (!TestMatchReply(key,"key-popularity")) goto fail; | ||
| 84 | if (RedisModule_CallReplyType(val) != REDISMODULE_REPLY_ARRAY) goto fail; | ||
| 85 | if (RedisModule_CallReplyLength(val) != 2) goto fail; | ||
| 86 | if (!TestMatchReply(RedisModule_CallReplyArrayElement(val, 0),"key:123")) goto fail; | ||
| 87 | if (!TestMatchReply(RedisModule_CallReplyArrayElement(val, 1),"90")) goto fail; | ||
| 88 | |||
| 89 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 90 | return REDISMODULE_OK; | ||
| 91 | |||
| 92 | fail: | ||
| 93 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 94 | return REDISMODULE_OK; | ||
| 95 | } | ||
| 96 | |||
| 97 | int TestGetResp(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 98 | REDISMODULE_NOT_USED(argv); | ||
| 99 | REDISMODULE_NOT_USED(argc); | ||
| 100 | |||
| 101 | int flags = RedisModule_GetContextFlags(ctx); | ||
| 102 | |||
| 103 | if (flags & REDISMODULE_CTX_FLAGS_RESP3) { | ||
| 104 | RedisModule_ReplyWithLongLong(ctx, 3); | ||
| 105 | } else { | ||
| 106 | RedisModule_ReplyWithLongLong(ctx, 2); | ||
| 107 | } | ||
| 108 | |||
| 109 | return REDISMODULE_OK; | ||
| 110 | } | ||
| 111 | |||
| 112 | int TestCallRespAutoMode(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 113 | REDISMODULE_NOT_USED(argv); | ||
| 114 | REDISMODULE_NOT_USED(argc); | ||
| 115 | |||
| 116 | RedisModule_AutoMemory(ctx); | ||
| 117 | RedisModuleCallReply *reply; | ||
| 118 | |||
| 119 | RedisModule_Call(ctx,"DEL","c","myhash"); | ||
| 120 | RedisModule_Call(ctx,"HSET","ccccc","myhash", "f1", "v1", "f2", "v2"); | ||
| 121 | /* 0 stands for auto mode, we will get the reply in the same format as the client */ | ||
| 122 | reply = RedisModule_Call(ctx,"HGETALL","0c" ,"myhash"); | ||
| 123 | RedisModule_ReplyWithCallReply(ctx, reply); | ||
| 124 | return REDISMODULE_OK; | ||
| 125 | } | ||
| 126 | |||
| 127 | int TestCallResp3Map(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 128 | REDISMODULE_NOT_USED(argv); | ||
| 129 | REDISMODULE_NOT_USED(argc); | ||
| 130 | |||
| 131 | RedisModule_AutoMemory(ctx); | ||
| 132 | RedisModuleCallReply *reply; | ||
| 133 | |||
| 134 | RedisModule_Call(ctx,"DEL","c","myhash"); | ||
| 135 | RedisModule_Call(ctx,"HSET","ccccc","myhash", "f1", "v1", "f2", "v2"); | ||
| 136 | reply = RedisModule_Call(ctx,"HGETALL","3c" ,"myhash"); /* 3 stands for resp 3 reply */ | ||
| 137 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_MAP) goto fail; | ||
| 138 | |||
| 139 | /* make sure we can not reply to resp2 client with resp3 map */ | ||
| 140 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 141 | |||
| 142 | long long items = RedisModule_CallReplyLength(reply); | ||
| 143 | if (items != 2) goto fail; | ||
| 144 | |||
| 145 | RedisModuleCallReply *key0, *key1; | ||
| 146 | RedisModuleCallReply *val0, *val1; | ||
| 147 | if (RedisModule_CallReplyMapElement(reply,0,&key0,&val0) != REDISMODULE_OK) goto fail; | ||
| 148 | if (RedisModule_CallReplyMapElement(reply,1,&key1,&val1) != REDISMODULE_OK) goto fail; | ||
| 149 | if (!TestMatchReply(key0,"f1")) goto fail; | ||
| 150 | if (!TestMatchReply(key1,"f2")) goto fail; | ||
| 151 | if (!TestMatchReply(val0,"v1")) goto fail; | ||
| 152 | if (!TestMatchReply(val1,"v2")) goto fail; | ||
| 153 | |||
| 154 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 155 | return REDISMODULE_OK; | ||
| 156 | |||
| 157 | fail: | ||
| 158 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 159 | return REDISMODULE_OK; | ||
| 160 | } | ||
| 161 | |||
| 162 | int TestCallResp3Bool(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 163 | REDISMODULE_NOT_USED(argv); | ||
| 164 | REDISMODULE_NOT_USED(argc); | ||
| 165 | |||
| 166 | RedisModule_AutoMemory(ctx); | ||
| 167 | RedisModuleCallReply *reply; | ||
| 168 | |||
| 169 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "true"); /* 3 stands for resp 3 reply */ | ||
| 170 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_BOOL) goto fail; | ||
| 171 | /* make sure we can not reply to resp2 client with resp3 bool */ | ||
| 172 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 173 | |||
| 174 | if (!RedisModule_CallReplyBool(reply)) goto fail; | ||
| 175 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "false"); /* 3 stands for resp 3 reply */ | ||
| 176 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_BOOL) goto fail; | ||
| 177 | if (RedisModule_CallReplyBool(reply)) goto fail; | ||
| 178 | |||
| 179 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 180 | return REDISMODULE_OK; | ||
| 181 | |||
| 182 | fail: | ||
| 183 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 184 | return REDISMODULE_OK; | ||
| 185 | } | ||
| 186 | |||
| 187 | int TestCallResp3Null(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 188 | REDISMODULE_NOT_USED(argv); | ||
| 189 | REDISMODULE_NOT_USED(argc); | ||
| 190 | |||
| 191 | RedisModule_AutoMemory(ctx); | ||
| 192 | RedisModuleCallReply *reply; | ||
| 193 | |||
| 194 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "null"); /* 3 stands for resp 3 reply */ | ||
| 195 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_NULL) goto fail; | ||
| 196 | |||
| 197 | /* make sure we can not reply to resp2 client with resp3 null */ | ||
| 198 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 199 | |||
| 200 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 201 | return REDISMODULE_OK; | ||
| 202 | |||
| 203 | fail: | ||
| 204 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 205 | return REDISMODULE_OK; | ||
| 206 | } | ||
| 207 | |||
| 208 | int TestCallReplyWithNestedReply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 209 | REDISMODULE_NOT_USED(argv); | ||
| 210 | REDISMODULE_NOT_USED(argc); | ||
| 211 | |||
| 212 | RedisModule_AutoMemory(ctx); | ||
| 213 | RedisModuleCallReply *reply; | ||
| 214 | |||
| 215 | RedisModule_Call(ctx,"DEL","c","mylist"); | ||
| 216 | RedisModule_Call(ctx,"RPUSH","ccl","mylist","test",(long long)1234); | ||
| 217 | reply = RedisModule_Call(ctx,"LRANGE","ccc","mylist","0","-1"); | ||
| 218 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) goto fail; | ||
| 219 | if (RedisModule_CallReplyLength(reply) < 1) goto fail; | ||
| 220 | RedisModuleCallReply *nestedReply = RedisModule_CallReplyArrayElement(reply, 0); | ||
| 221 | |||
| 222 | RedisModule_ReplyWithCallReply(ctx,nestedReply); | ||
| 223 | return REDISMODULE_OK; | ||
| 224 | |||
| 225 | fail: | ||
| 226 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 227 | return REDISMODULE_OK; | ||
| 228 | } | ||
| 229 | |||
| 230 | int TestCallReplyWithArrayReply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 231 | REDISMODULE_NOT_USED(argv); | ||
| 232 | REDISMODULE_NOT_USED(argc); | ||
| 233 | |||
| 234 | RedisModule_AutoMemory(ctx); | ||
| 235 | RedisModuleCallReply *reply; | ||
| 236 | |||
| 237 | RedisModule_Call(ctx,"DEL","c","mylist"); | ||
| 238 | RedisModule_Call(ctx,"RPUSH","ccl","mylist","test",(long long)1234); | ||
| 239 | reply = RedisModule_Call(ctx,"LRANGE","ccc","mylist","0","-1"); | ||
| 240 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) goto fail; | ||
| 241 | |||
| 242 | RedisModule_ReplyWithCallReply(ctx,reply); | ||
| 243 | return REDISMODULE_OK; | ||
| 244 | |||
| 245 | fail: | ||
| 246 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 247 | return REDISMODULE_OK; | ||
| 248 | } | ||
| 249 | |||
| 250 | int TestCallResp3Double(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 251 | REDISMODULE_NOT_USED(argv); | ||
| 252 | REDISMODULE_NOT_USED(argc); | ||
| 253 | |||
| 254 | RedisModule_AutoMemory(ctx); | ||
| 255 | RedisModuleCallReply *reply; | ||
| 256 | |||
| 257 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "double"); /* 3 stands for resp 3 reply */ | ||
| 258 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_DOUBLE) goto fail; | ||
| 259 | |||
| 260 | /* make sure we can not reply to resp2 client with resp3 double*/ | ||
| 261 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 262 | |||
| 263 | double d = RedisModule_CallReplyDouble(reply); | ||
| 264 | /* we compare strings, since comparing doubles directly can fail in various architectures, e.g. 32bit */ | ||
| 265 | char got[30], expected[30]; | ||
| 266 | snprintf(got, sizeof(got), "%.17g", d); | ||
| 267 | snprintf(expected, sizeof(expected), "%.17g", 3.141); | ||
| 268 | if (strcmp(got, expected) != 0) goto fail; | ||
| 269 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 270 | return REDISMODULE_OK; | ||
| 271 | |||
| 272 | fail: | ||
| 273 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 274 | return REDISMODULE_OK; | ||
| 275 | } | ||
| 276 | |||
| 277 | int TestCallResp3BigNumber(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 278 | REDISMODULE_NOT_USED(argv); | ||
| 279 | REDISMODULE_NOT_USED(argc); | ||
| 280 | |||
| 281 | RedisModule_AutoMemory(ctx); | ||
| 282 | RedisModuleCallReply *reply; | ||
| 283 | |||
| 284 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "bignum"); /* 3 stands for resp 3 reply */ | ||
| 285 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_BIG_NUMBER) goto fail; | ||
| 286 | |||
| 287 | /* make sure we can not reply to resp2 client with resp3 big number */ | ||
| 288 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 289 | |||
| 290 | size_t len; | ||
| 291 | const char* big_num = RedisModule_CallReplyBigNumber(reply, &len); | ||
| 292 | RedisModule_ReplyWithStringBuffer(ctx,big_num,len); | ||
| 293 | return REDISMODULE_OK; | ||
| 294 | |||
| 295 | fail: | ||
| 296 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 297 | return REDISMODULE_OK; | ||
| 298 | } | ||
| 299 | |||
| 300 | int TestCallResp3Verbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 301 | REDISMODULE_NOT_USED(argv); | ||
| 302 | REDISMODULE_NOT_USED(argc); | ||
| 303 | |||
| 304 | RedisModule_AutoMemory(ctx); | ||
| 305 | RedisModuleCallReply *reply; | ||
| 306 | |||
| 307 | reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "verbatim"); /* 3 stands for resp 3 reply */ | ||
| 308 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_VERBATIM_STRING) goto fail; | ||
| 309 | |||
| 310 | /* make sure we can not reply to resp2 client with resp3 verbatim string */ | ||
| 311 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 312 | |||
| 313 | const char* format; | ||
| 314 | size_t len; | ||
| 315 | const char* str = RedisModule_CallReplyVerbatim(reply, &len, &format); | ||
| 316 | RedisModuleString *s = RedisModule_CreateStringPrintf(ctx, "%.*s:%.*s", 3, format, (int)len, str); | ||
| 317 | RedisModule_ReplyWithString(ctx,s); | ||
| 318 | return REDISMODULE_OK; | ||
| 319 | |||
| 320 | fail: | ||
| 321 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 322 | return REDISMODULE_OK; | ||
| 323 | } | ||
| 324 | |||
| 325 | int TestCallResp3Set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 326 | REDISMODULE_NOT_USED(argv); | ||
| 327 | REDISMODULE_NOT_USED(argc); | ||
| 328 | |||
| 329 | RedisModule_AutoMemory(ctx); | ||
| 330 | RedisModuleCallReply *reply; | ||
| 331 | |||
| 332 | RedisModule_Call(ctx,"DEL","c","myset"); | ||
| 333 | RedisModule_Call(ctx,"sadd","ccc","myset", "v1", "v2"); | ||
| 334 | reply = RedisModule_Call(ctx,"smembers","3c" ,"myset"); // N stands for resp 3 reply | ||
| 335 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_SET) goto fail; | ||
| 336 | |||
| 337 | /* make sure we can not reply to resp2 client with resp3 set */ | ||
| 338 | if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail; | ||
| 339 | |||
| 340 | long long items = RedisModule_CallReplyLength(reply); | ||
| 341 | if (items != 2) goto fail; | ||
| 342 | |||
| 343 | RedisModuleCallReply *val0, *val1; | ||
| 344 | |||
| 345 | val0 = RedisModule_CallReplySetElement(reply,0); | ||
| 346 | val1 = RedisModule_CallReplySetElement(reply,1); | ||
| 347 | |||
| 348 | /* | ||
| 349 | * The order of elements on sets are not promised so we just | ||
| 350 | * veridy that the reply matches one of the elements. | ||
| 351 | */ | ||
| 352 | if (!TestMatchReply(val0,"v1") && !TestMatchReply(val0,"v2")) goto fail; | ||
| 353 | if (!TestMatchReply(val1,"v1") && !TestMatchReply(val1,"v2")) goto fail; | ||
| 354 | |||
| 355 | RedisModule_ReplyWithSimpleString(ctx,"OK"); | ||
| 356 | return REDISMODULE_OK; | ||
| 357 | |||
| 358 | fail: | ||
| 359 | RedisModule_ReplyWithSimpleString(ctx,"ERR"); | ||
| 360 | return REDISMODULE_OK; | ||
| 361 | } | ||
| 362 | |||
| 363 | /* TEST.STRING.APPEND -- Test appending to an existing string object. */ | ||
| 364 | int TestStringAppend(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 365 | REDISMODULE_NOT_USED(argv); | ||
| 366 | REDISMODULE_NOT_USED(argc); | ||
| 367 | |||
| 368 | RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3); | ||
| 369 | RedisModule_StringAppendBuffer(ctx,s,"bar",3); | ||
| 370 | RedisModule_ReplyWithString(ctx,s); | ||
| 371 | RedisModule_FreeString(ctx,s); | ||
| 372 | return REDISMODULE_OK; | ||
| 373 | } | ||
| 374 | |||
| 375 | /* TEST.STRING.APPEND.AM -- Test append with retain when auto memory is on. */ | ||
| 376 | int TestStringAppendAM(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 377 | REDISMODULE_NOT_USED(argv); | ||
| 378 | REDISMODULE_NOT_USED(argc); | ||
| 379 | |||
| 380 | RedisModule_AutoMemory(ctx); | ||
| 381 | RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3); | ||
| 382 | RedisModule_RetainString(ctx,s); | ||
| 383 | RedisModule_TrimStringAllocation(s); /* Mostly NOP, but exercises the API function */ | ||
| 384 | RedisModule_StringAppendBuffer(ctx,s,"bar",3); | ||
| 385 | RedisModule_ReplyWithString(ctx,s); | ||
| 386 | RedisModule_FreeString(ctx,s); | ||
| 387 | return REDISMODULE_OK; | ||
| 388 | } | ||
| 389 | |||
| 390 | /* TEST.STRING.TRIM -- Test we trim a string with free space. */ | ||
| 391 | int TestTrimString(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 392 | REDISMODULE_NOT_USED(argv); | ||
| 393 | REDISMODULE_NOT_USED(argc); | ||
| 394 | RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3); | ||
| 395 | char *tmp = RedisModule_Alloc(1024); | ||
| 396 | RedisModule_StringAppendBuffer(ctx,s,tmp,1024); | ||
| 397 | size_t string_len = RedisModule_MallocSizeString(s); | ||
| 398 | RedisModule_TrimStringAllocation(s); | ||
| 399 | size_t len_after_trim = RedisModule_MallocSizeString(s); | ||
| 400 | |||
| 401 | /* Determine if using jemalloc memory allocator. */ | ||
| 402 | RedisModuleServerInfoData *info = RedisModule_GetServerInfo(ctx, "memory"); | ||
| 403 | const char *field = RedisModule_ServerInfoGetFieldC(info, "mem_allocator"); | ||
| 404 | int use_jemalloc = !strncmp(field, "jemalloc", 8); | ||
| 405 | |||
| 406 | /* Jemalloc will reallocate `s` from 2k to 1k after RedisModule_TrimStringAllocation(), | ||
| 407 | * but non-jemalloc memory allocators may keep the old size. */ | ||
| 408 | if ((use_jemalloc && len_after_trim < string_len) || | ||
| 409 | (!use_jemalloc && len_after_trim <= string_len)) | ||
| 410 | { | ||
| 411 | RedisModule_ReplyWithSimpleString(ctx, "OK"); | ||
| 412 | } else { | ||
| 413 | RedisModule_ReplyWithError(ctx, "String was not trimmed as expected."); | ||
| 414 | } | ||
| 415 | RedisModule_FreeServerInfo(ctx, info); | ||
| 416 | RedisModule_Free(tmp); | ||
| 417 | RedisModule_FreeString(ctx,s); | ||
| 418 | return REDISMODULE_OK; | ||
| 419 | } | ||
| 420 | |||
| 421 | /* TEST.STRING.PRINTF -- Test string formatting. */ | ||
| 422 | int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 423 | RedisModule_AutoMemory(ctx); | ||
| 424 | if (argc < 3) { | ||
| 425 | return RedisModule_WrongArity(ctx); | ||
| 426 | } | ||
| 427 | RedisModuleString *s = RedisModule_CreateStringPrintf(ctx, | ||
| 428 | "Got %d args. argv[1]: %s, argv[2]: %s", | ||
| 429 | argc, | ||
| 430 | RedisModule_StringPtrLen(argv[1], NULL), | ||
| 431 | RedisModule_StringPtrLen(argv[2], NULL) | ||
| 432 | ); | ||
| 433 | |||
| 434 | RedisModule_ReplyWithString(ctx,s); | ||
| 435 | |||
| 436 | return REDISMODULE_OK; | ||
| 437 | } | ||
| 438 | |||
| 439 | int failTest(RedisModuleCtx *ctx, const char *msg) { | ||
| 440 | RedisModule_ReplyWithError(ctx, msg); | ||
| 441 | return REDISMODULE_ERR; | ||
| 442 | } | ||
| 443 | |||
| 444 | int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 445 | RedisModule_AutoMemory(ctx); | ||
| 446 | REDISMODULE_NOT_USED(argv); | ||
| 447 | REDISMODULE_NOT_USED(argc); | ||
| 448 | |||
| 449 | RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ); | ||
| 450 | if (!k) return failTest(ctx, "Could not create key"); | ||
| 451 | |||
| 452 | if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) { | ||
| 453 | return failTest(ctx, "Could not set string value"); | ||
| 454 | } | ||
| 455 | |||
| 456 | RedisModuleCallReply *rep = RedisModule_Call(ctx, "EXISTS", "c", "unlinked"); | ||
| 457 | if (!rep || RedisModule_CallReplyInteger(rep) != 1) { | ||
| 458 | return failTest(ctx, "Key does not exist before unlink"); | ||
| 459 | } | ||
| 460 | |||
| 461 | if (REDISMODULE_ERR == RedisModule_UnlinkKey(k)) { | ||
| 462 | return failTest(ctx, "Could not unlink key"); | ||
| 463 | } | ||
| 464 | |||
| 465 | rep = RedisModule_Call(ctx, "EXISTS", "c", "unlinked"); | ||
| 466 | if (!rep || RedisModule_CallReplyInteger(rep) != 0) { | ||
| 467 | return failTest(ctx, "Could not verify key to be unlinked"); | ||
| 468 | } | ||
| 469 | return RedisModule_ReplyWithSimpleString(ctx, "OK"); | ||
| 470 | } | ||
| 471 | |||
| 472 | int TestNestedCallReplyArrayElement(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 473 | RedisModule_AutoMemory(ctx); | ||
| 474 | REDISMODULE_NOT_USED(argv); | ||
| 475 | REDISMODULE_NOT_USED(argc); | ||
| 476 | |||
| 477 | RedisModuleString *expect_key = RedisModule_CreateString(ctx, "mykey", strlen("mykey")); | ||
| 478 | RedisModule_SelectDb(ctx, 1); | ||
| 479 | RedisModule_Call(ctx, "LPUSH", "sc", expect_key, "myvalue"); | ||
| 480 | |||
| 481 | RedisModuleCallReply *scan_reply = RedisModule_Call(ctx, "SCAN", "l", (long long)0); | ||
| 482 | RedisModule_Assert(scan_reply != NULL && RedisModule_CallReplyType(scan_reply) == REDISMODULE_REPLY_ARRAY); | ||
| 483 | RedisModule_Assert(RedisModule_CallReplyLength(scan_reply) == 2); | ||
| 484 | |||
| 485 | long long scan_cursor; | ||
| 486 | RedisModuleCallReply *cursor_reply = RedisModule_CallReplyArrayElement(scan_reply, 0); | ||
| 487 | RedisModule_Assert(RedisModule_CallReplyType(cursor_reply) == REDISMODULE_REPLY_STRING); | ||
| 488 | RedisModule_Assert(RedisModule_StringToLongLong(RedisModule_CreateStringFromCallReply(cursor_reply), &scan_cursor) == REDISMODULE_OK); | ||
| 489 | RedisModule_Assert(scan_cursor == 0); | ||
| 490 | |||
| 491 | RedisModuleCallReply *keys_reply = RedisModule_CallReplyArrayElement(scan_reply, 1); | ||
| 492 | RedisModule_Assert(RedisModule_CallReplyType(keys_reply) == REDISMODULE_REPLY_ARRAY); | ||
| 493 | RedisModule_Assert( RedisModule_CallReplyLength(keys_reply) == 1); | ||
| 494 | |||
| 495 | RedisModuleCallReply *key_reply = RedisModule_CallReplyArrayElement(keys_reply, 0); | ||
| 496 | RedisModule_Assert(RedisModule_CallReplyType(key_reply) == REDISMODULE_REPLY_STRING); | ||
| 497 | RedisModuleString *key = RedisModule_CreateStringFromCallReply(key_reply); | ||
| 498 | RedisModule_Assert(RedisModule_StringCompare(key, expect_key) == 0); | ||
| 499 | |||
| 500 | RedisModule_ReplyWithSimpleString(ctx, "OK"); | ||
| 501 | return REDISMODULE_OK; | ||
| 502 | } | ||
| 503 | |||
| 504 | /* TEST.STRING.TRUNCATE -- Test truncating an existing string object. */ | ||
| 505 | int TestStringTruncate(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 506 | RedisModule_AutoMemory(ctx); | ||
| 507 | REDISMODULE_NOT_USED(argv); | ||
| 508 | REDISMODULE_NOT_USED(argc); | ||
| 509 | |||
| 510 | RedisModule_Call(ctx, "SET", "cc", "foo", "abcde"); | ||
| 511 | RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "foo"), REDISMODULE_READ | REDISMODULE_WRITE); | ||
| 512 | if (!k) return failTest(ctx, "Could not create key"); | ||
| 513 | |||
| 514 | size_t len = 0; | ||
| 515 | char* s; | ||
| 516 | |||
| 517 | /* expand from 5 to 8 and check null pad */ | ||
| 518 | if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 8)) { | ||
| 519 | return failTest(ctx, "Could not truncate string value (8)"); | ||
| 520 | } | ||
| 521 | s = RedisModule_StringDMA(k, &len, REDISMODULE_READ); | ||
| 522 | if (!s) { | ||
| 523 | return failTest(ctx, "Failed to read truncated string (8)"); | ||
| 524 | } else if (len != 8) { | ||
| 525 | return failTest(ctx, "Failed to expand string value (8)"); | ||
| 526 | } else if (0 != strncmp(s, "abcde\0\0\0", 8)) { | ||
| 527 | return failTest(ctx, "Failed to null pad string value (8)"); | ||
| 528 | } | ||
| 529 | |||
| 530 | /* shrink from 8 to 4 */ | ||
| 531 | if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 4)) { | ||
| 532 | return failTest(ctx, "Could not truncate string value (4)"); | ||
| 533 | } | ||
| 534 | s = RedisModule_StringDMA(k, &len, REDISMODULE_READ); | ||
| 535 | if (!s) { | ||
| 536 | return failTest(ctx, "Failed to read truncated string (4)"); | ||
| 537 | } else if (len != 4) { | ||
| 538 | return failTest(ctx, "Failed to shrink string value (4)"); | ||
| 539 | } else if (0 != strncmp(s, "abcd", 4)) { | ||
| 540 | return failTest(ctx, "Failed to truncate string value (4)"); | ||
| 541 | } | ||
| 542 | |||
| 543 | /* shrink to 0 */ | ||
| 544 | if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 0)) { | ||
| 545 | return failTest(ctx, "Could not truncate string value (0)"); | ||
| 546 | } | ||
| 547 | s = RedisModule_StringDMA(k, &len, REDISMODULE_READ); | ||
| 548 | if (!s) { | ||
| 549 | return failTest(ctx, "Failed to read truncated string (0)"); | ||
| 550 | } else if (len != 0) { | ||
| 551 | return failTest(ctx, "Failed to shrink string value to (0)"); | ||
| 552 | } | ||
| 553 | |||
| 554 | return RedisModule_ReplyWithSimpleString(ctx, "OK"); | ||
| 555 | } | ||
| 556 | |||
| 557 | int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, | ||
| 558 | RedisModuleString *key) { | ||
| 559 | RedisModule_AutoMemory(ctx); | ||
| 560 | /* Increment a counter on the notifications: for each key notified we | ||
| 561 | * increment a counter */ | ||
| 562 | RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type, | ||
| 563 | event, RedisModule_StringPtrLen(key, NULL)); | ||
| 564 | |||
| 565 | RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1"); | ||
| 566 | return REDISMODULE_OK; | ||
| 567 | } | ||
| 568 | |||
| 569 | /* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */ | ||
| 570 | int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 571 | RedisModule_AutoMemory(ctx); | ||
| 572 | REDISMODULE_NOT_USED(argv); | ||
| 573 | REDISMODULE_NOT_USED(argc); | ||
| 574 | |||
| 575 | #define FAIL(msg, ...) \ | ||
| 576 | { \ | ||
| 577 | RedisModule_Log(ctx, "warning", "Failed NOTIFY Test. Reason: " #msg, ##__VA_ARGS__); \ | ||
| 578 | goto err; \ | ||
| 579 | } | ||
| 580 | RedisModule_Call(ctx, "FLUSHDB", ""); | ||
| 581 | |||
| 582 | RedisModule_Call(ctx, "SET", "cc", "foo", "bar"); | ||
| 583 | RedisModule_Call(ctx, "SET", "cc", "foo", "baz"); | ||
| 584 | RedisModule_Call(ctx, "SADD", "cc", "bar", "x"); | ||
| 585 | RedisModule_Call(ctx, "SADD", "cc", "bar", "y"); | ||
| 586 | |||
| 587 | RedisModule_Call(ctx, "HSET", "ccc", "baz", "x", "y"); | ||
| 588 | /* LPUSH should be ignored and not increment any counters */ | ||
| 589 | RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); | ||
| 590 | RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); | ||
| 591 | |||
| 592 | /* Miss some keys intentionally so we will get a "keymiss" notification. */ | ||
| 593 | RedisModule_Call(ctx, "GET", "c", "nosuchkey"); | ||
| 594 | RedisModule_Call(ctx, "SMEMBERS", "c", "nosuchkey"); | ||
| 595 | |||
| 596 | size_t sz; | ||
| 597 | const char *rep; | ||
| 598 | RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo"); | ||
| 599 | if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { | ||
| 600 | FAIL("Wrong or no reply for foo"); | ||
| 601 | } else { | ||
| 602 | rep = RedisModule_CallReplyStringPtr(r, &sz); | ||
| 603 | if (sz != 1 || *rep != '2') { | ||
| 604 | FAIL("Got reply '%s'. expected '2'", RedisModule_CallReplyStringPtr(r, NULL)); | ||
| 605 | } | ||
| 606 | } | ||
| 607 | |||
| 608 | r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "bar"); | ||
| 609 | if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { | ||
| 610 | FAIL("Wrong or no reply for bar"); | ||
| 611 | } else { | ||
| 612 | rep = RedisModule_CallReplyStringPtr(r, &sz); | ||
| 613 | if (sz != 1 || *rep != '2') { | ||
| 614 | FAIL("Got reply '%s'. expected '2'", rep); | ||
| 615 | } | ||
| 616 | } | ||
| 617 | |||
| 618 | r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "baz"); | ||
| 619 | if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { | ||
| 620 | FAIL("Wrong or no reply for baz"); | ||
| 621 | } else { | ||
| 622 | rep = RedisModule_CallReplyStringPtr(r, &sz); | ||
| 623 | if (sz != 1 || *rep != '1') { | ||
| 624 | FAIL("Got reply '%.*s'. expected '1'", (int)sz, rep); | ||
| 625 | } | ||
| 626 | } | ||
| 627 | /* For l we expect nothing since we didn't subscribe to list events */ | ||
| 628 | r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l"); | ||
| 629 | if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) { | ||
| 630 | FAIL("Wrong reply for l"); | ||
| 631 | } | ||
| 632 | |||
| 633 | r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "nosuchkey"); | ||
| 634 | if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { | ||
| 635 | FAIL("Wrong or no reply for nosuchkey"); | ||
| 636 | } else { | ||
| 637 | rep = RedisModule_CallReplyStringPtr(r, &sz); | ||
| 638 | if (sz != 1 || *rep != '2') { | ||
| 639 | FAIL("Got reply '%.*s'. expected '2'", (int)sz, rep); | ||
| 640 | } | ||
| 641 | } | ||
| 642 | |||
| 643 | RedisModule_Call(ctx, "FLUSHDB", ""); | ||
| 644 | |||
| 645 | return RedisModule_ReplyWithSimpleString(ctx, "OK"); | ||
| 646 | err: | ||
| 647 | RedisModule_Call(ctx, "FLUSHDB", ""); | ||
| 648 | |||
| 649 | return RedisModule_ReplyWithSimpleString(ctx, "ERR"); | ||
| 650 | } | ||
| 651 | |||
| 652 | /* TEST.CTXFLAGS -- Test GetContextFlags. */ | ||
| 653 | int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 654 | REDISMODULE_NOT_USED(argc); | ||
| 655 | REDISMODULE_NOT_USED(argv); | ||
| 656 | |||
| 657 | RedisModule_AutoMemory(ctx); | ||
| 658 | |||
| 659 | int ok = 1; | ||
| 660 | const char *errString = NULL; | ||
| 661 | #undef FAIL | ||
| 662 | #define FAIL(msg) \ | ||
| 663 | { \ | ||
| 664 | ok = 0; \ | ||
| 665 | errString = msg; \ | ||
| 666 | goto end; \ | ||
| 667 | } | ||
| 668 | |||
| 669 | int flags = RedisModule_GetContextFlags(ctx); | ||
| 670 | if (flags == 0) { | ||
| 671 | FAIL("Got no flags"); | ||
| 672 | } | ||
| 673 | |||
| 674 | if (flags & REDISMODULE_CTX_FLAGS_LUA) FAIL("Lua flag was set"); | ||
| 675 | if (flags & REDISMODULE_CTX_FLAGS_MULTI) FAIL("Multi flag was set"); | ||
| 676 | |||
| 677 | if (flags & REDISMODULE_CTX_FLAGS_AOF) FAIL("AOF Flag was set") | ||
| 678 | /* Enable AOF to test AOF flags */ | ||
| 679 | RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "yes"); | ||
| 680 | flags = RedisModule_GetContextFlags(ctx); | ||
| 681 | if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) FAIL("AOF Flag not set after config set"); | ||
| 682 | |||
| 683 | /* Disable RDB saving and test the flag. */ | ||
| 684 | RedisModule_Call(ctx, "config", "ccc", "set", "save", ""); | ||
| 685 | flags = RedisModule_GetContextFlags(ctx); | ||
| 686 | if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set"); | ||
| 687 | /* Enable RDB to test RDB flags */ | ||
| 688 | RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1"); | ||
| 689 | flags = RedisModule_GetContextFlags(ctx); | ||
| 690 | if (!(flags & REDISMODULE_CTX_FLAGS_RDB)) FAIL("RDB Flag was not set after config set"); | ||
| 691 | |||
| 692 | if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) FAIL("Master flag was not set"); | ||
| 693 | if (flags & REDISMODULE_CTX_FLAGS_SLAVE) FAIL("Slave flag was set"); | ||
| 694 | if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set"); | ||
| 695 | if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set"); | ||
| 696 | |||
| 697 | /* Disable maxmemory and test the flag. (it is implicitly set in 32bit builds. */ | ||
| 698 | RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0"); | ||
| 699 | flags = RedisModule_GetContextFlags(ctx); | ||
| 700 | if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was set"); | ||
| 701 | |||
| 702 | /* Enable maxmemory and test the flag. */ | ||
| 703 | RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000"); | ||
| 704 | flags = RedisModule_GetContextFlags(ctx); | ||
| 705 | if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY)) | ||
| 706 | FAIL("Maxmemory flag was not set after config set"); | ||
| 707 | |||
| 708 | if (flags & REDISMODULE_CTX_FLAGS_EVICT) FAIL("Eviction flag was set"); | ||
| 709 | RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "allkeys-lru"); | ||
| 710 | flags = RedisModule_GetContextFlags(ctx); | ||
| 711 | if (!(flags & REDISMODULE_CTX_FLAGS_EVICT)) FAIL("Eviction flag was not set after config set"); | ||
| 712 | |||
| 713 | end: | ||
| 714 | /* Revert config changes */ | ||
| 715 | RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "no"); | ||
| 716 | RedisModule_Call(ctx, "config", "ccc", "set", "save", ""); | ||
| 717 | RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0"); | ||
| 718 | RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "noeviction"); | ||
| 719 | |||
| 720 | if (!ok) { | ||
| 721 | RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s", errString); | ||
| 722 | return RedisModule_ReplyWithSimpleString(ctx, "ERR"); | ||
| 723 | } | ||
| 724 | |||
| 725 | return RedisModule_ReplyWithSimpleString(ctx, "OK"); | ||
| 726 | } | ||
| 727 | |||
| 728 | /* ----------------------------- Test framework ----------------------------- */ | ||
| 729 | |||
| 730 | /* Return 1 if the reply matches the specified string, otherwise log errors | ||
| 731 | * in the server log and return 0. */ | ||
| 732 | int TestAssertErrorReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) { | ||
| 733 | RedisModuleString *mystr, *expected; | ||
| 734 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ERROR) { | ||
| 735 | return 0; | ||
| 736 | } | ||
| 737 | |||
| 738 | mystr = RedisModule_CreateStringFromCallReply(reply); | ||
| 739 | expected = RedisModule_CreateString(ctx,str,len); | ||
| 740 | if (RedisModule_StringCompare(mystr,expected) != 0) { | ||
| 741 | const char *mystr_ptr = RedisModule_StringPtrLen(mystr,NULL); | ||
| 742 | const char *expected_ptr = RedisModule_StringPtrLen(expected,NULL); | ||
| 743 | RedisModule_Log(ctx,"warning", | ||
| 744 | "Unexpected Error reply reply '%s' (instead of '%s')", | ||
| 745 | mystr_ptr, expected_ptr); | ||
| 746 | return 0; | ||
| 747 | } | ||
| 748 | return 1; | ||
| 749 | } | ||
| 750 | |||
| 751 | int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) { | ||
| 752 | RedisModuleString *mystr, *expected; | ||
| 753 | |||
| 754 | if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) { | ||
| 755 | RedisModule_Log(ctx,"warning","Test error reply: %s", | ||
| 756 | RedisModule_CallReplyStringPtr(reply, NULL)); | ||
| 757 | return 0; | ||
| 758 | } else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) { | ||
| 759 | RedisModule_Log(ctx,"warning","Unexpected reply type %d", | ||
| 760 | RedisModule_CallReplyType(reply)); | ||
| 761 | return 0; | ||
| 762 | } | ||
| 763 | mystr = RedisModule_CreateStringFromCallReply(reply); | ||
| 764 | expected = RedisModule_CreateString(ctx,str,len); | ||
| 765 | if (RedisModule_StringCompare(mystr,expected) != 0) { | ||
| 766 | const char *mystr_ptr = RedisModule_StringPtrLen(mystr,NULL); | ||
| 767 | const char *expected_ptr = RedisModule_StringPtrLen(expected,NULL); | ||
| 768 | RedisModule_Log(ctx,"warning", | ||
| 769 | "Unexpected string reply '%s' (instead of '%s')", | ||
| 770 | mystr_ptr, expected_ptr); | ||
| 771 | return 0; | ||
| 772 | } | ||
| 773 | return 1; | ||
| 774 | } | ||
| 775 | |||
| 776 | /* Return 1 if the reply matches the specified integer, otherwise log errors | ||
| 777 | * in the server log and return 0. */ | ||
| 778 | int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, long long expected) { | ||
| 779 | if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) { | ||
| 780 | RedisModule_Log(ctx,"warning","Test error reply: %s", | ||
| 781 | RedisModule_CallReplyStringPtr(reply, NULL)); | ||
| 782 | return 0; | ||
| 783 | } else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) { | ||
| 784 | RedisModule_Log(ctx,"warning","Unexpected reply type %d", | ||
| 785 | RedisModule_CallReplyType(reply)); | ||
| 786 | return 0; | ||
| 787 | } | ||
| 788 | long long val = RedisModule_CallReplyInteger(reply); | ||
| 789 | if (val != expected) { | ||
| 790 | RedisModule_Log(ctx,"warning", | ||
| 791 | "Unexpected integer reply '%lld' (instead of '%lld')", | ||
| 792 | val, expected); | ||
| 793 | return 0; | ||
| 794 | } | ||
| 795 | return 1; | ||
| 796 | } | ||
| 797 | |||
| 798 | /* Replies "yes", "no" otherwise if the context may execute debug commands */ | ||
| 799 | int TestCanDebug(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 800 | REDISMODULE_NOT_USED(argv); | ||
| 801 | REDISMODULE_NOT_USED(argc); | ||
| 802 | int flags = RedisModule_GetContextFlags(ctx); | ||
| 803 | int allFlags = RedisModule_GetContextFlagsAll(); | ||
| 804 | if ((allFlags & REDISMODULE_CTX_FLAGS_DEBUG_ENABLED) && | ||
| 805 | (flags & REDISMODULE_CTX_FLAGS_DEBUG_ENABLED)) { | ||
| 806 | RedisModule_ReplyWithSimpleString(ctx, "yes"); | ||
| 807 | } else { | ||
| 808 | RedisModule_ReplyWithSimpleString(ctx, "no"); | ||
| 809 | } | ||
| 810 | return REDISMODULE_OK; | ||
| 811 | } | ||
| 812 | |||
| 813 | #define T(name,...) \ | ||
| 814 | do { \ | ||
| 815 | RedisModule_Log(ctx,"warning","Testing %s", name); \ | ||
| 816 | reply = RedisModule_Call(ctx,name,__VA_ARGS__); \ | ||
| 817 | } while (0) | ||
| 818 | |||
| 819 | /* TEST.BASICS -- Run all the tests. | ||
| 820 | * Note: it is useful to run these tests from the module rather than TCL | ||
| 821 | * since it's easier to check the reply types like that make a distinction | ||
| 822 | * between 0 and "0", etc. */ | ||
| 823 | int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 824 | REDISMODULE_NOT_USED(argv); | ||
| 825 | REDISMODULE_NOT_USED(argc); | ||
| 826 | |||
| 827 | RedisModule_AutoMemory(ctx); | ||
| 828 | RedisModuleCallReply *reply; | ||
| 829 | |||
| 830 | /* Make sure the DB is empty before to proceed. */ | ||
| 831 | T("dbsize",""); | ||
| 832 | if (!TestAssertIntegerReply(ctx,reply,0)) goto fail; | ||
| 833 | |||
| 834 | T("ping",""); | ||
| 835 | if (!TestAssertStringReply(ctx,reply,"PONG",4)) goto fail; | ||
| 836 | |||
| 837 | T("test.call",""); | ||
| 838 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 839 | |||
| 840 | T("test.callresp3map",""); | ||
| 841 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 842 | |||
| 843 | T("test.callresp3set",""); | ||
| 844 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 845 | |||
| 846 | T("test.callresp3double",""); | ||
| 847 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 848 | |||
| 849 | T("test.callresp3bool",""); | ||
| 850 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 851 | |||
| 852 | T("test.callresp3null",""); | ||
| 853 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 854 | |||
| 855 | T("test.callreplywithnestedreply",""); | ||
| 856 | if (!TestAssertStringReply(ctx,reply,"test",4)) goto fail; | ||
| 857 | |||
| 858 | T("test.callreplywithbignumberreply",""); | ||
| 859 | if (!TestAssertStringReply(ctx,reply,"1234567999999999999999999999999999999",37)) goto fail; | ||
| 860 | |||
| 861 | T("test.callreplywithverbatimstringreply",""); | ||
| 862 | if (!TestAssertStringReply(ctx,reply,"txt:This is a verbatim\nstring",29)) goto fail; | ||
| 863 | |||
| 864 | T("test.ctxflags",""); | ||
| 865 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 866 | |||
| 867 | T("test.string.append",""); | ||
| 868 | if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail; | ||
| 869 | |||
| 870 | T("test.string.truncate",""); | ||
| 871 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 872 | |||
| 873 | T("test.unlink",""); | ||
| 874 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 875 | |||
| 876 | T("test.nestedcallreplyarray",""); | ||
| 877 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 878 | |||
| 879 | T("test.string.append.am",""); | ||
| 880 | if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail; | ||
| 881 | |||
| 882 | T("test.string.trim",""); | ||
| 883 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 884 | |||
| 885 | T("test.string.printf", "cc", "foo", "bar"); | ||
| 886 | if (!TestAssertStringReply(ctx,reply,"Got 3 args. argv[1]: foo, argv[2]: bar",38)) goto fail; | ||
| 887 | |||
| 888 | T("test.notify", ""); | ||
| 889 | if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; | ||
| 890 | |||
| 891 | T("test.callreplywitharrayreply", ""); | ||
| 892 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) goto fail; | ||
| 893 | if (RedisModule_CallReplyLength(reply) != 2) goto fail; | ||
| 894 | if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 0),"test",4)) goto fail; | ||
| 895 | if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 1),"1234",4)) goto fail; | ||
| 896 | |||
| 897 | T("foo", "E"); | ||
| 898 | if (!TestAssertErrorReply(ctx,reply,"ERR unknown command 'foo'",25)) goto fail; | ||
| 899 | |||
| 900 | T("set", "Ec", "x"); | ||
| 901 | if (!TestAssertErrorReply(ctx,reply,"ERR wrong number of arguments for 'set' command",47)) goto fail; | ||
| 902 | |||
| 903 | T("shutdown", "SE"); | ||
| 904 | if (!TestAssertErrorReply(ctx,reply,"ERR command 'shutdown' is not allowed on script mode",52)) goto fail; | ||
| 905 | |||
| 906 | T("set", "WEcc", "x", "1"); | ||
| 907 | if (!TestAssertErrorReply(ctx,reply,"ERR Write command 'set' was called while write is not allowed.",62)) goto fail; | ||
| 908 | |||
| 909 | RedisModule_ReplyWithSimpleString(ctx,"ALL TESTS PASSED"); | ||
| 910 | return REDISMODULE_OK; | ||
| 911 | |||
| 912 | fail: | ||
| 913 | RedisModule_ReplyWithSimpleString(ctx, | ||
| 914 | "SOME TEST DID NOT PASS! Check server logs"); | ||
| 915 | return REDISMODULE_OK; | ||
| 916 | } | ||
| 917 | |||
| 918 | int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | ||
| 919 | REDISMODULE_NOT_USED(argv); | ||
| 920 | REDISMODULE_NOT_USED(argc); | ||
| 921 | |||
| 922 | if (RedisModule_Init(ctx,"test",1,REDISMODULE_APIVER_1) | ||
| 923 | == REDISMODULE_ERR) return REDISMODULE_ERR; | ||
| 924 | |||
| 925 | /* Perform RM_Call inside the RedisModule_OnLoad | ||
| 926 | * to verify that it works as expected without crashing. | ||
| 927 | * The tests will verify it on different configurations | ||
| 928 | * options (cluster/no cluster). A simple ping command | ||
| 929 | * is enough for this test. */ | ||
| 930 | RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", ""); | ||
| 931 | if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) { | ||
| 932 | RedisModule_FreeCallReply(reply); | ||
| 933 | return REDISMODULE_ERR; | ||
| 934 | } | ||
| 935 | size_t len; | ||
| 936 | const char *reply_str = RedisModule_CallReplyStringPtr(reply, &len); | ||
| 937 | if (len != 4) { | ||
| 938 | RedisModule_FreeCallReply(reply); | ||
| 939 | return REDISMODULE_ERR; | ||
| 940 | } | ||
| 941 | if (memcmp(reply_str, "PONG", 4) != 0) { | ||
| 942 | RedisModule_FreeCallReply(reply); | ||
| 943 | return REDISMODULE_ERR; | ||
| 944 | } | ||
| 945 | RedisModule_FreeCallReply(reply); | ||
| 946 | |||
| 947 | if (RedisModule_CreateCommand(ctx,"test.call", | ||
| 948 | TestCall,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 949 | return REDISMODULE_ERR; | ||
| 950 | |||
| 951 | if (RedisModule_CreateCommand(ctx,"test.callresp3map", | ||
| 952 | TestCallResp3Map,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 953 | return REDISMODULE_ERR; | ||
| 954 | |||
| 955 | if (RedisModule_CreateCommand(ctx,"test.callresp3attribute", | ||
| 956 | TestCallResp3Attribute,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 957 | return REDISMODULE_ERR; | ||
| 958 | |||
| 959 | if (RedisModule_CreateCommand(ctx,"test.callresp3set", | ||
| 960 | TestCallResp3Set,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 961 | return REDISMODULE_ERR; | ||
| 962 | |||
| 963 | if (RedisModule_CreateCommand(ctx,"test.callresp3double", | ||
| 964 | TestCallResp3Double,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 965 | return REDISMODULE_ERR; | ||
| 966 | |||
| 967 | if (RedisModule_CreateCommand(ctx,"test.callresp3bool", | ||
| 968 | TestCallResp3Bool,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 969 | return REDISMODULE_ERR; | ||
| 970 | |||
| 971 | if (RedisModule_CreateCommand(ctx,"test.callresp3null", | ||
| 972 | TestCallResp3Null,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 973 | return REDISMODULE_ERR; | ||
| 974 | |||
| 975 | if (RedisModule_CreateCommand(ctx,"test.callreplywitharrayreply", | ||
| 976 | TestCallReplyWithArrayReply,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 977 | return REDISMODULE_ERR; | ||
| 978 | |||
| 979 | if (RedisModule_CreateCommand(ctx,"test.callreplywithnestedreply", | ||
| 980 | TestCallReplyWithNestedReply,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 981 | return REDISMODULE_ERR; | ||
| 982 | |||
| 983 | if (RedisModule_CreateCommand(ctx,"test.callreplywithbignumberreply", | ||
| 984 | TestCallResp3BigNumber,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 985 | return REDISMODULE_ERR; | ||
| 986 | |||
| 987 | if (RedisModule_CreateCommand(ctx,"test.callreplywithverbatimstringreply", | ||
| 988 | TestCallResp3Verbatim,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 989 | return REDISMODULE_ERR; | ||
| 990 | |||
| 991 | if (RedisModule_CreateCommand(ctx,"test.string.append", | ||
| 992 | TestStringAppend,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 993 | return REDISMODULE_ERR; | ||
| 994 | |||
| 995 | if (RedisModule_CreateCommand(ctx,"test.string.trim", | ||
| 996 | TestTrimString,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 997 | return REDISMODULE_ERR; | ||
| 998 | |||
| 999 | if (RedisModule_CreateCommand(ctx,"test.string.append.am", | ||
| 1000 | TestStringAppendAM,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 1001 | return REDISMODULE_ERR; | ||
| 1002 | |||
| 1003 | if (RedisModule_CreateCommand(ctx,"test.string.truncate", | ||
| 1004 | TestStringTruncate,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 1005 | return REDISMODULE_ERR; | ||
| 1006 | |||
| 1007 | if (RedisModule_CreateCommand(ctx,"test.string.printf", | ||
| 1008 | TestStringPrintf,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 1009 | return REDISMODULE_ERR; | ||
| 1010 | |||
| 1011 | if (RedisModule_CreateCommand(ctx,"test.ctxflags", | ||
| 1012 | TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR) | ||
| 1013 | return REDISMODULE_ERR; | ||
| 1014 | |||
| 1015 | if (RedisModule_CreateCommand(ctx,"test.unlink", | ||
| 1016 | TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 1017 | return REDISMODULE_ERR; | ||
| 1018 | |||
| 1019 | if (RedisModule_CreateCommand(ctx,"test.nestedcallreplyarray", | ||
| 1020 | TestNestedCallReplyArrayElement,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 1021 | return REDISMODULE_ERR; | ||
| 1022 | |||
| 1023 | if (RedisModule_CreateCommand(ctx,"test.basics", | ||
| 1024 | TestBasics,"write",1,1,1) == REDISMODULE_ERR) | ||
| 1025 | return REDISMODULE_ERR; | ||
| 1026 | |||
| 1027 | /* the following commands are used by an external test and should not be added to TestBasics */ | ||
| 1028 | if (RedisModule_CreateCommand(ctx,"test.rmcallautomode", | ||
| 1029 | TestCallRespAutoMode,"write",1,1,1) == REDISMODULE_ERR) | ||
| 1030 | return REDISMODULE_ERR; | ||
| 1031 | |||
| 1032 | if (RedisModule_CreateCommand(ctx,"test.getresp", | ||
| 1033 | TestGetResp,"readonly",1,1,1) == REDISMODULE_ERR) | ||
| 1034 | return REDISMODULE_ERR; | ||
| 1035 | |||
| 1036 | if (RedisModule_CreateCommand(ctx,"test.candebug", | ||
| 1037 | TestCanDebug,"readonly",1,1,1) == REDISMODULE_ERR) | ||
| 1038 | return REDISMODULE_ERR; | ||
| 1039 | |||
| 1040 | RedisModule_SubscribeToKeyspaceEvents(ctx, | ||
| 1041 | REDISMODULE_NOTIFY_HASH | | ||
| 1042 | REDISMODULE_NOTIFY_SET | | ||
| 1043 | REDISMODULE_NOTIFY_STRING | | ||
| 1044 | REDISMODULE_NOTIFY_KEY_MISS, | ||
| 1045 | NotifyCallback); | ||
| 1046 | if (RedisModule_CreateCommand(ctx,"test.notify", | ||
| 1047 | TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR) | ||
| 1048 | return REDISMODULE_ERR; | ||
| 1049 | |||
| 1050 | return REDISMODULE_OK; | ||
| 1051 | } | ||
