Hi Aurelien,

> Version 5:6.2.4-1 built fine, so it's likely a regression introduced in
> the new upstream version.

Thanks for the report. I just have a few minutes to look into this
right this second so (for my own convenience) I've attached the diff
between these two particular upstream versions.

There is this entry in the upstream changelog that might be worthy of
further investigation:

   * Fix ziplist length updates on big-endian platforms (#2080)

Do you happen to have the full log of this failing build though? I
would suspect it's something in the testsuite that's exposing this
issue, but being able to pin it down would be the ideal next step,
especially as the testsuite is so large (and there were quite a few
changes).


Regards,

--
      ,''`.
     : :'  :     Chris Lamb
     `. `'`      la...@debian.org 🍥 chris-lamb.co.uk
       `-
diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml
index 9e4630e2..432971a9 100644
--- a/.github/workflows/daily.yml
+++ b/.github/workflows/daily.yml
@@ -151,7 +151,7 @@ jobs:
       run: |
         sudo apt-get update
         sudo apt-get install tcl8.6 valgrind -y
-        ./runtest --valgrind --verbose --clients 1 --dump-logs
+        ./runtest --valgrind --verbose --clients 1 --tags -large-memory 
--dump-logs
     - name: module api test
       run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
     - name: unittest
@@ -171,7 +171,7 @@ jobs:
       run: |
         sudo apt-get update
         sudo apt-get install tcl8.6 valgrind -y
-        ./runtest --valgrind --verbose --clients 1 --dump-logs
+        ./runtest --valgrind --verbose --clients 1 --tags -large-memory 
--dump-logs
     - name: module api test
       run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
 
@@ -260,7 +260,7 @@ jobs:
         prepare: pkg install -y bash gmake lang/tcl86
         run: >
           gmake &&
-          ./runtest --accurate --verbose --no-latency --dump-logs &&
+          ./runtest --accurate --verbose --no-latency --tags -large-memory 
--dump-logs &&
           MAKE=gmake ./runtest-moduleapi --verbose &&
           ./runtest-sentinel &&
           ./runtest-cluster
diff --git a/00-RELEASENOTES b/00-RELEASENOTES
index 9411714e..a5fb897e 100644
--- a/00-RELEASENOTES
+++ b/00-RELEASENOTES
@@ -12,7 +12,54 @@ SECURITY: There are security fixes in the release.
 
--------------------------------------------------------------------------------
 
 
================================================================================
-Redis 6.2.4 Released Tue July 1 12:00:00 IST 2021
+Redis 6.2.5 Released Wed Jul 21 16:32:19 IDT 2021
+================================================================================
+
+Upgrade urgency: SECURITY, contains fixes to security issues that affect
+authenticated client connections on 32-bit versions. MODERATE otherwise.
+
+Fix integer overflow in BITFIELD on 32-bit versions (CVE-2021-32761).
+An integer overflow bug in Redis version 2.2 or newer can be exploited using 
the
+BITFIELD command to corrupt the heap and potentially result with remote code
+execution.
+
+Bug fixes that involve behavior changes:
+* Change reply type for ZPOPMAX/MIN with count in RESP3 to nested array 
(#8981).
+  Was using a flat array like in RESP2 instead of a nested array like ZRANGE 
does.
+* Fix reply type for HRANDFIELD and ZRANDMEMBER when key is missing (#9178).
+  Was using a null array instead of an empty array.
+* Fix reply type for ZRANGESTORE when source key is missing (#9089).
+  Was using an empty array like ZRANGE instead of 0 (used in the STORE 
variant).
+
+Bug fixes that are only applicable to previous releases of Redis 6.2:
+* ZRANDMEMBER WITHSCORES with negative COUNT may return bad score (#9162)
+* Fix crash after CLIENT UNPAUSE when threaded I/O config is enabled (#9041)
+* Fix XTRIM or XADD with LIMIT may delete more entries than the limit (#9048)
+* Fix build issue with OpenSSL 1.1.0 (#9233)
+
+Other bug fixes:
+* Fail EXEC command in case a watched key is expired (#9194)
+* Fix SMOVE not to invalidate dest key (WATCH and tracking) when member 
already exists (#9244)
+* Fix SINTERSTORE not to delete dest key when getting a wrong type error 
(#9032)
+* Fix overflows on 32-bit versions in GETBIT, SETBIT, BITCOUNT, BITPOS, and 
BITFIELD (#9191)
+* Improve MEMORY USAGE on stream keys (#9164)
+* Set TCP keepalive on inbound cluster bus connections (#9230)
+* Fix diskless replica loading to recover from RDB short read on module AUX 
data (#9199)
+* Fix race in client side tracking (#9116)
+* Fix ziplist length updates on big-endian platforms (#2080)
+
+CLI tools:
+* redis-cli cluster import command may issue wrong MIGRATE command, sending 
COPY instead of REPLACE (#8945)
+* redis-cli --rdb fixes when using "-" to write to stdout (#9136, #9135)
+* redis-cli support for RESP3 set type in CSV and RAW output (#7338)
+
+Modules:
+* Module API for getting current command name (#8792)
+* Fix RM_StringTruncate when newlen is 0 (#3718)
+* Fix CLIENT UNBLOCK crashing modules without timeout callback (#9167)
+
+================================================================================
+Redis 6.2.4 Released Tue June 1 12:00:00 IST 2021
 
================================================================================
 
diff --git a/runtest-moduleapi b/runtest-moduleapi
index 7c17501e..56d14960 100755
--- a/runtest-moduleapi
+++ b/runtest-moduleapi
@@ -16,6 +16,7 @@ fi
 $MAKE -C tests/modules && \
 $TCLSH tests/test_helper.tcl \
 --single unit/moduleapi/commandfilter \
+--single unit/moduleapi/basics \
 --single unit/moduleapi/fork \
 --single unit/moduleapi/testrdb \
 --single unit/moduleapi/infotest \
diff --git a/src/Makefile b/src/Makefile
index 62e37cb4..cf3e8c03 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -375,6 +375,8 @@ clean:
 
 distclean: clean
        -(cd ../deps && $(MAKE) distclean)
+       -(cd modules && $(MAKE) clean)
+       -(cd ../tests/modules && $(MAKE) clean)
        -(rm -f .make-*)
 
 .PHONY: distclean
@@ -382,6 +384,9 @@ distclean: clean
 test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) 
$(REDIS_BENCHMARK_NAME)
        @(cd ..; ./runtest)
 
+test-modules: $(REDIS_SERVER_NAME)
+       @(cd ..; ./runtest-moduleapi)
+
 test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME)
        @(cd ..; ./runtest-sentinel)
 
diff --git a/src/aof.c b/src/aof.c
index 0d2971ea..566a9864 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -162,7 +162,9 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long 
len) {
 
     /* Install a file event to send data to the rewrite child if there is
      * not one already. */
-    if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
+    if (!server.aof_stop_sending_diff &&
+        aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0)
+    {
         aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
             AE_WRITABLE, aofChildWriteDiffData, NULL);
     }
diff --git a/src/bitops.c b/src/bitops.c
index afd79ad8..f1c563a4 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -37,8 +37,8 @@
 /* Count number of bits set in the binary array pointed by 's' and long
  * 'count' bytes. The implementation of this function is required to
  * work with an input string length up to 512 MB or more 
(server.proto_max_bulk_len) */
-size_t redisPopcount(void *s, long count) {
-    size_t bits = 0;
+long long redisPopcount(void *s, long count) {
+    long long bits = 0;
     unsigned char *p = s;
     uint32_t *p4;
     static const unsigned char bitsinbyte[256] = 
{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
@@ -98,11 +98,11 @@ size_t redisPopcount(void *s, long count) {
  * no zero bit is found, it returns count*8 assuming the string is zero
  * padded on the right. However if 'bit' is 1 it is possible that there is
  * not a single set bit in the bitmap. In this special case -1 is returned. */
-long redisBitpos(void *s, unsigned long count, int bit) {
+long long redisBitpos(void *s, unsigned long count, int bit) {
     unsigned long *l;
     unsigned char *c;
     unsigned long skipval, word = 0, one;
-    long pos = 0; /* Position of bit, to return to the caller. */
+    long long pos = 0; /* Position of bit, to return to the caller. */
     unsigned long j;
     int found;
 
@@ -410,7 +410,7 @@ void printBits(unsigned char *p, unsigned long count) {
  * If the 'hash' argument is true, and 'bits is positive, then the command
  * will also parse bit offsets prefixed by "#". In such a case the offset
  * is multiplied by 'bits'. This is useful for the BITFIELD command. */
-int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int 
bits) {
+int getBitOffsetFromArgument(client *c, robj *o, uint64_t *offset, int hash, 
int bits) {
     long long loffset;
     char *err = "bit offset is not an integer or out of range";
     char *p = o->ptr;
@@ -435,7 +435,7 @@ int getBitOffsetFromArgument(client *c, robj *o, size_t 
*offset, int hash, int b
         return C_ERR;
     }
 
-    *offset = (size_t)loffset;
+    *offset = loffset;
     return C_OK;
 }
 
@@ -477,7 +477,7 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int 
*sign, int *bits) {
  * so that the 'maxbit' bit can be addressed. The object is finally
  * returned. Otherwise if the key holds a wrong type NULL is returned and
  * an error is sent to the client. */
-robj *lookupStringForBitCommand(client *c, size_t maxbit) {
+robj *lookupStringForBitCommand(client *c, uint64_t maxbit) {
     size_t byte = maxbit >> 3;
     robj *o = lookupKeyWrite(c->db,c->argv[1]);
     if (checkType(c,o,OBJ_STRING)) return NULL;
@@ -527,7 +527,7 @@ unsigned char *getObjectReadOnlyString(robj *o, long *len, 
char *llbuf) {
 void setbitCommand(client *c) {
     robj *o;
     char *err = "bit is not an integer or out of range";
-    size_t bitoffset;
+    uint64_t bitoffset;
     ssize_t byte, bit;
     int byteval, bitval;
     long on;
@@ -566,7 +566,7 @@ void setbitCommand(client *c) {
 void getbitCommand(client *c) {
     robj *o;
     char llbuf[32];
-    size_t bitoffset;
+    uint64_t bitoffset;
     size_t byte, bit;
     size_t bitval = 0;
 
@@ -888,7 +888,7 @@ void bitposCommand(client *c) {
         addReplyLongLong(c, -1);
     } else {
         long bytes = end-start+1;
-        long pos = redisBitpos(p+start,bytes,bit);
+        long long pos = redisBitpos(p+start,bytes,bit);
 
         /* If we are looking for clear bits, and the user specified an exact
          * range with start-end, we can't consider the right of the range as
@@ -897,11 +897,11 @@ void bitposCommand(client *c) {
          * So if redisBitpos() returns the first bit outside the range,
          * we return -1 to the caller, to mean, in the specified range there
          * is not a single "0" bit. */
-        if (end_given && bit == 0 && pos == bytes*8) {
+        if (end_given && bit == 0 && pos == (long long)bytes<<3) {
             addReplyLongLong(c,-1);
             return;
         }
-        if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
+        if (pos != -1) pos += (long long)start<<3; /* Adjust for the bytes we 
skipped. */
         addReplyLongLong(c,pos);
     }
 }
@@ -933,12 +933,12 @@ struct bitfieldOp {
  * GET subcommand is allowed, other subcommands will return an error. */
 void bitfieldGeneric(client *c, int flags) {
     robj *o;
-    size_t bitoffset;
+    uint64_t bitoffset;
     int j, numops = 0, changes = 0;
     struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
     int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
     int readonly = 1;
-    size_t highest_write_offset = 0;
+    uint64_t highest_write_offset = 0;
 
     for (j = 2; j < c->argc; j++) {
         int remargs = c->argc-j-1; /* Remaining args other than current. */
@@ -1128,9 +1128,9 @@ void bitfieldGeneric(client *c, int flags) {
              * object boundaries. */
             memset(buf,0,9);
             int i;
-            size_t byte = thisop->offset >> 3;
+            uint64_t byte = thisop->offset >> 3;
             for (i = 0; i < 9; i++) {
-                if (src == NULL || i+byte >= (size_t)strlen) break;
+                if (src == NULL || i+byte >= (uint64_t)strlen) break;
                 buf[i] = src[i+byte];
             }
 
diff --git a/src/cluster.c b/src/cluster.c
index 28650b32..246eb405 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -707,6 +707,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void 
*privdata, int mask) {
         }
         connNonBlock(conn);
         connEnableTcpNoDelay(conn);
+        connKeepAlive(conn,server.cluster_node_timeout * 2);
 
         /* Use non-blocking I/O for cluster messages. */
         serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", 
cip, cport);
diff --git a/src/debug.c b/src/debug.c
index 098ce6ef..f521490a 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -721,7 +721,7 @@ NULL
         } else if (!strcasecmp(name,"double")) {
             addReplyDouble(c,3.14159265359);
         } else if (!strcasecmp(name,"bignum")) {
-            addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
+            addReplyBigNum(c,"1234567999999999999999999999999999999",37);
         } else if (!strcasecmp(name,"null")) {
             addReplyNull(c);
         } else if (!strcasecmp(name,"array")) {
@@ -737,11 +737,13 @@ NULL
                 addReplyBool(c, j == 1);
             }
         } else if (!strcasecmp(name,"attrib")) {
-            addReplyAttributeLen(c,1);
-            addReplyBulkCString(c,"key-popularity");
-            addReplyArrayLen(c,2);
-            addReplyBulkCString(c,"key:123");
-            addReplyLongLong(c,90);
+            if (c->resp >= 3) {
+                addReplyAttributeLen(c,1);
+                addReplyBulkCString(c,"key-popularity");
+                addReplyArrayLen(c,2);
+                addReplyBulkCString(c,"key:123");
+                addReplyLongLong(c,90);
+            }
             /* Attributes are not real replies, so a well formed reply should
              * also have a normal reply type after the attribute. */
             addReplyBulkCString(c,"Some real reply following the attribute");
diff --git a/src/module.c b/src/module.c
index c76241bf..bf6580a6 100644
--- a/src/module.c
+++ b/src/module.c
@@ -2542,7 +2542,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) 
{
         if (newlen > curlen) {
             key->value->ptr = sdsgrowzero(key->value->ptr,newlen);
         } else if (newlen < curlen) {
-            sdsrange(key->value->ptr,0,newlen-1);
+            sdssubstr(key->value->ptr,0,newlen);
             /* If the string is too wasteful, reallocate it. */
             if (sdslen(key->value->ptr) < sdsavail(key->value->ptr))
                 key->value->ptr = sdsRemoveFreeSpace(key->value->ptr);
@@ -5362,8 +5362,8 @@ int moduleTryServeClientBlockedOnKey(client *c, robj 
*key) {
  *     reply_callback:   called after a successful RedisModule_UnblockClient()
  *                       call in order to reply to the client and unblock it.
  *
- *     timeout_callback: called when the timeout is reached in order to send an
- *                       error to the client.
+ *     timeout_callback: called when the timeout is reached or if `CLIENT 
UNBLOCK`
+ *                       is invoked, in order to send an error to the client.
  *
  *     free_privdata:    called in order to free the private data that is 
passed
  *                       by RedisModule_UnblockClient() call.
@@ -5380,6 +5380,12 @@ int moduleTryServeClientBlockedOnKey(client *c, robj 
*key) {
  * In these cases, a call to RedisModule_BlockClient() will **not** block the
  * client, but instead produce a specific error reply.
  *
+ * A module that registers a timeout_callback function can also be unblocked
+ * using the `CLIENT UNBLOCK` command, which will trigger the timeout callback.
+ * If a callback function is not registered, then the blocked client will be
+ * treated as if it is not in a blocked state and `CLIENT UNBLOCK` will return
+ * a zero value.
+ *
  * Measuring background time: By default the time spent in the blocked command
  * is not account for the total command duration. To include such time you 
should
  * use RM_BlockedClientMeasureTimeStart() and RM_BlockedClientMeasureTimeEnd() 
one,
@@ -5649,6 +5655,17 @@ void moduleHandleBlockedClients(void) {
     pthread_mutex_unlock(&moduleUnblockedClientsMutex);
 }
 
+/* Check if the specified client can be safely timed out using
+ * moduleBlockedClientTimedOut().
+ */
+int moduleBlockedClientMayTimeout(client *c) {
+    if (c->btype != BLOCKED_MODULE)
+        return 1;
+
+    RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
+    return (bc && bc->timeout_callback != NULL);
+}
+
 /* Called when our client timed out. After this function unblockClient()
  * is called, and it will invalidate the blocked client. So this function
  * does not need to do any cleanup. Eventually the module will call the
@@ -8679,8 +8696,9 @@ sds genModulesInfoStringRenderModulesList(list *l) {
     while((ln = listNext(&li))) {
         RedisModule *module = ln->value;
         output = sdscat(output,module->name);
+        if (ln != listLast(l))
+            output = sdscat(output,"|");
     }
-    output = sdstrim(output,"|");
     output = sdscat(output,"]");
     return output;
 }
@@ -9006,6 +9024,14 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, 
RedisModuleString **argv, int argc,
     return res;
 }
 
+/* Return the name of the command currently running */
+const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) {
+    if (!ctx || !ctx->client || !ctx->client->cmd)
+        return NULL;
+
+    return (const char*)ctx->client->cmd->name;
+}
+
 /* --------------------------------------------------------------------------
  * ## Defrag API
  * -------------------------------------------------------------------------- 
*/
@@ -9467,6 +9493,7 @@ void moduleRegisterCoreAPI(void) {
     REGISTER_API(GetServerVersion);
     REGISTER_API(GetClientCertificate);
     REGISTER_API(GetCommandKeys);
+    REGISTER_API(GetCurrentCommandName);
     REGISTER_API(GetTypeMethodVersion);
     REGISTER_API(RegisterDefragFunc);
     REGISTER_API(DefragAlloc);
diff --git a/src/modules/Makefile b/src/modules/Makefile
index 5e012d6f..3db19e79 100644
--- a/src/modules/Makefile
+++ b/src/modules/Makefile
@@ -13,7 +13,7 @@ endif
 
 .SUFFIXES: .c .so .xo .o
 
-all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so 
hellotimer.so hellodict.so hellohook.so helloacl.so
+all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so 
hellodict.so hellohook.so helloacl.so
 
 .c.xo:
        $(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
@@ -58,10 +58,5 @@ helloacl.xo: ../redismodule.h
 helloacl.so: helloacl.xo
        $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
 
-testmodule.xo: ../redismodule.h
-
-testmodule.so: testmodule.xo
-       $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
-
 clean:
        rm -rf *.xo *.so
diff --git a/src/multi.c b/src/multi.c
index 5fb37098..3a157bee 100644
--- a/src/multi.c
+++ b/src/multi.c
@@ -168,6 +168,11 @@ void execCommand(client *c) {
         return;
     }
 
+    /* EXEC with expired watched key is disallowed*/
+    if (isWatchedKeyExpired(c)) {
+        c->flags |= (CLIENT_DIRTY_CAS);
+    }
+
     /* Check if we need to abort the EXEC because:
      * 1) Some WATCHed key was touched.
      * 2) There was a previous error while queueing commands.
@@ -342,6 +347,22 @@ void unwatchAllKeys(client *c) {
     }
 }
 
+/* iterates over the watched_keys list and
+ * look for an expired key . */
+int isWatchedKeyExpired(client *c) {
+    listIter li;
+    listNode *ln;
+    watchedKey *wk;
+    if (listLength(c->watched_keys) == 0) return 0;
+    listRewind(c->watched_keys,&li);
+    while ((ln = listNext(&li))) {
+        wk = listNodeValue(ln);
+        if (keyIsExpired(wk->db, wk->key)) return 1;
+    }
+
+    return 0;
+}
+
 /* "Touch" a key, so that if this key is being WATCHed by some client the
  * next EXEC will fail. */
 void touchWatchedKey(redisDb *db, robj *key) {
diff --git a/src/networking.c b/src/networking.c
index 9de10598..ed092b0e 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -649,14 +649,13 @@ void setDeferredSetLen(client *c, void *node, long 
length) {
 }
 
 void setDeferredAttributeLen(client *c, void *node, long length) {
-    int prefix = c->resp == 2 ? '*' : '|';
-    if (c->resp == 2) length *= 2;
-    setDeferredAggregateLen(c,node,length,prefix);
+    serverAssert(c->resp >= 3);
+    setDeferredAggregateLen(c,node,length,'|');
 }
 
 void setDeferredPushLen(client *c, void *node, long length) {
-    int prefix = c->resp == 2 ? '*' : '>';
-    setDeferredAggregateLen(c,node,length,prefix);
+    serverAssert(c->resp >= 3);
+    setDeferredAggregateLen(c,node,length,'>');
 }
 
 /* Add a double as a bulk reply */
@@ -685,6 +684,16 @@ void addReplyDouble(client *c, double d) {
     }
 }
 
+void addReplyBigNum(client *c, const char* num, size_t len) {
+    if (c->resp == 2) {
+        addReplyBulkCBuffer(c, num, len);
+    } else {
+        addReplyProto(c,"(",1);
+        addReplyProto(c,num,len);
+        addReply(c,shared.crlf);
+    }
+}
+
 /* Add a long double as a bulk reply, but uses a human readable formatting
  * of the double instead of exposing the crude behavior of doubles to the
  * dear user. */
@@ -756,14 +765,13 @@ void addReplySetLen(client *c, long length) {
 }
 
 void addReplyAttributeLen(client *c, long length) {
-    int prefix = c->resp == 2 ? '*' : '|';
-    if (c->resp == 2) length *= 2;
-    addReplyAggregateLen(c,length,prefix);
+    serverAssert(c->resp >= 3);
+    addReplyAggregateLen(c,length,'|');
 }
 
 void addReplyPushLen(client *c, long length) {
-    int prefix = c->resp == 2 ? '*' : '>';
-    addReplyAggregateLen(c,length,prefix);
+    serverAssert(c->resp >= 3);
+    addReplyAggregateLen(c,length,'>');
 }
 
 void addReplyNull(client *c) {
@@ -2669,7 +2677,7 @@ NULL
         if (getLongLongFromObjectOrReply(c,c->argv[2],&id,NULL)
             != C_OK) return;
         struct client *target = lookupClientByID(id);
-        if (target && target->flags & CLIENT_BLOCKED) {
+        if (target && target->flags & CLIENT_BLOCKED && 
moduleBlockedClientMayTimeout(target)) {
             if (unblock_error)
                 addReplyError(target,
                     "-UNBLOCKED client unblocked via CLIENT UNBLOCK");
@@ -3654,7 +3662,7 @@ int postponeClientRead(client *c) {
     if (server.io_threads_active &&
         server.io_threads_do_reads &&
         !ProcessingEventsWhileBlocked &&
-        !(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
+        !(c->flags & 
(CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ|CLIENT_BLOCKED))) 
     {
         c->flags |= CLIENT_PENDING_READ;
         listAddNodeHead(server.clients_pending_read,c);
@@ -3718,6 +3726,7 @@ int handleClientsWithPendingReadsUsingThreads(void) {
         c->flags &= ~CLIENT_PENDING_READ;
         listDelNode(server.clients_pending_read,ln);
 
+        serverAssert(!(c->flags & CLIENT_BLOCKED));
         if (processPendingCommandsAndResetClient(c) == C_ERR) {
             /* If the client is no longer valid, we avoid
              * processing the client later. So we just go
diff --git a/src/object.c b/src/object.c
index c7b25ffd..c6381a23 100644
--- a/src/object.c
+++ b/src/object.c
@@ -881,7 +881,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
         }
     } else if (o->type == OBJ_STREAM) {
         stream *s = o->ptr;
-        asize = sizeof(*o);
+        asize = sizeof(*o)+sizeof(*s);
         asize += streamRadixTreeMemoryUsage(s->rax);
 
         /* Now we have to add the listpacks. The last listpack is often non
diff --git a/src/rdb.c b/src/rdb.c
index 3c5397d5..e902388e 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -2484,8 +2484,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) 
{
             int when_opcode = rdbLoadLen(rdb,NULL);
             int when = rdbLoadLen(rdb,NULL);
             if (rioGetReadError(rdb)) goto eoferr;
-            if (when_opcode != RDB_MODULE_OPCODE_UINT)
+            if (when_opcode != RDB_MODULE_OPCODE_UINT) {
                 rdbReportReadError("bad when_opcode");
+                goto eoferr;
+            }
             moduleType *mt = moduleTypeLookupModuleByID(moduleid);
             char name[10];
             moduleTypeNameByID(name,moduleid);
@@ -2509,7 +2511,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
                 if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || 
io.error) {
                     moduleTypeNameByID(name,moduleid);
                     serverLog(LL_WARNING,"The RDB file contains module AUX 
data for the module type '%s', that the responsible module is not able to load. 
Check for modules log above for additional clues.", name);
-                    exit(1);
+                    goto eoferr;
                 }
                 if (io.ctx) {
                     moduleFreeContext(io.ctx);
@@ -2518,7 +2520,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
                 uint64_t eof = rdbLoadLen(rdb,NULL);
                 if (eof != RDB_MODULE_OPCODE_EOF) {
                     serverLog(LL_WARNING,"The RDB file contains module AUX 
data for the module '%s' that is not terminated by the proper module value EOF 
marker", name);
-                    exit(1);
+                    goto eoferr;
                 }
                 continue;
             } else {
diff --git a/src/redis-cli.c b/src/redis-cli.c
index 81be58b1..3160ada4 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -1129,6 +1129,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
     case REDIS_REPLY_DOUBLE:
         out = sdscatprintf(out,"%s",r->str);
         break;
+    case REDIS_REPLY_SET:
     case REDIS_REPLY_ARRAY:
     case REDIS_REPLY_PUSH:
         for (i = 0; i < r->elements; i++) {
@@ -1187,6 +1188,7 @@ static sds cliFormatReplyCSV(redisReply *r) {
         out = sdscat(out,r->integer ? "true" : "false");
     break;
     case REDIS_REPLY_ARRAY:
+    case REDIS_REPLY_SET:
     case REDIS_REPLY_PUSH:
     case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
         for (i = 0; i < r->elements; i++) {
@@ -1310,6 +1312,7 @@ static int cliReadReply(int output_raw_strings) {
     if (output) {
         out = cliFormatReply(reply, config.output, output_raw_strings);
         fwrite(out,sdslen(out),1,stdout);
+        fflush(stdout);
         sdsfree(out);
     }
     freeReplyObject(reply);
@@ -1894,6 +1897,7 @@ static void usage(void) {
 "  --lru-test <keys>  Simulate a cache workload with an 80-20 distribution.\n"
 "  --replica          Simulate a replica showing commands received from the 
master.\n"
 "  --rdb <filename>   Transfer an RDB dump from remote server to local file.\n"
+"                     Use filename of \"-\" to write to stdout.\n"
 "  --pipe             Transfer raw Redis protocol from stdin to server.\n"
 "  --pipe-timeout <n> In --pipe mode, abort with error if after sending all 
data.\n"
 "                     no reply is received within <n> seconds.\n"
@@ -6536,9 +6540,9 @@ static int clusterManagerCommandImport(int argc, char 
**argv) {
     }
 
     if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COPY)
-        strcat(cmdfmt, " %s");
+        cmdfmt = sdscat(cmdfmt," COPY");
     if (config.cluster_manager_command.flags & 
CLUSTER_MANAGER_CMD_FLAG_REPLACE)
-        strcat(cmdfmt, " %s");
+        cmdfmt = sdscat(cmdfmt," REPLACE");
 
     /* Use SCAN to iterate over the keys, migrating to the
      * right node as needed. */
@@ -6570,8 +6574,7 @@ static int clusterManagerCommandImport(int argc, char 
**argv) {
             printf("Migrating %s to %s:%d: ", key, target->ip, target->port);
             redisReply *r = reconnectingRedisCommand(src_ctx, cmdfmt,
                                                      target->ip, target->port,
-                                                     key, 0, timeout,
-                                                     "COPY", "REPLACE");
+                                                     key, 0, timeout);
             if (!r || r->type == REDIS_REPLY_ERROR) {
                 if (r && r->str) {
                     clusterManagerLogErr("Source %s:%d replied with "
@@ -6987,7 +6990,7 @@ static void latencyDistMode(void) {
 #define RDB_EOF_MARK_SIZE 40
 
 void sendReplconf(const char* arg1, const char* arg2) {
-    printf("sending REPLCONF %s %s\n", arg1, arg2);
+    fprintf(stderr, "sending REPLCONF %s %s\n", arg1, arg2);
     redisReply *reply = redisCommand(context, "REPLCONF %s %s", arg1, arg2);
 
     /* Handle any error conditions */
@@ -7047,7 +7050,7 @@ unsigned long long sendSync(redisContext *c, char 
*out_eof) {
     }
     *p = '\0';
     if (buf[0] == '-') {
-        printf("SYNC with master failed: %s\n", buf);
+        fprintf(stderr, "SYNC with master failed: %s\n", buf);
         exit(1);
     }
     if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
@@ -7152,8 +7155,9 @@ static void getRDB(clusterManagerNode *node) {
             payload, filename);
     }
 
+    int write_to_stdout = !strcmp(filename,"-");
     /* Write to file. */
-    if (!strcmp(filename,"-")) {
+    if (write_to_stdout) {
         fd = STDOUT_FILENO;
     } else {
         fd = open(filename, O_CREAT|O_WRONLY, 0644);
@@ -7195,7 +7199,7 @@ static void getRDB(clusterManagerNode *node) {
     }
     if (usemark) {
         payload = ULLONG_MAX - payload - RDB_EOF_MARK_SIZE;
-        if (ftruncate(fd, payload) == -1)
+        if (!write_to_stdout && ftruncate(fd, payload) == -1)
             fprintf(stderr,"ftruncate failed: %s.\n", strerror(errno));
         fprintf(stderr,"Transfer finished with success after %llu bytes\n", 
payload);
     } else {
@@ -7204,7 +7208,7 @@ static void getRDB(clusterManagerNode *node) {
     redisFree(s); /* Close the connection ASAP as fsync() may take time. */
     if (node)
         node->context = NULL;
-    if (fsync(fd) == -1) {
+    if (!write_to_stdout && fsync(fd) == -1) {
         fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
         exit(1);
     }
diff --git a/src/redismodule.h b/src/redismodule.h
index 5520ca3c..48a3a9df 100644
--- a/src/redismodule.h
+++ b/src/redismodule.h
@@ -836,6 +836,7 @@ REDISMODULE_API int 
(*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ct
 REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx 
*ctx, uint64_t client_id) REDISMODULE_ATTR;
 REDISMODULE_API RedisModuleString * 
(*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) 
REDISMODULE_ATTR;
 REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, 
RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
+REDISMODULE_API const char 
*(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
 REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, 
RedisModuleDefragFunc func) REDISMODULE_ATTR;
 REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, 
void *ptr) REDISMODULE_ATTR;
 REDISMODULE_API RedisModuleString 
*(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, 
RedisModuleString *str) REDISMODULE_ATTR;
@@ -1108,6 +1109,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const 
char *name, int ver, int
     REDISMODULE_GET_API(AuthenticateClientWithUser);
     REDISMODULE_GET_API(GetClientCertificate);
     REDISMODULE_GET_API(GetCommandKeys);
+    REDISMODULE_GET_API(GetCurrentCommandName);
     REDISMODULE_GET_API(RegisterDefragFunc);
     REDISMODULE_GET_API(DefragAlloc);
     REDISMODULE_GET_API(DefragRedisModuleString);
diff --git a/src/sds.c b/src/sds.c
index 2ec3aa73..8d1c2dbd 100644
--- a/src/sds.c
+++ b/src/sds.c
@@ -759,6 +759,21 @@ sds sdstrim(sds s, const char *cset) {
     return s;
 }
 
+/* Changes the input string to be a subset of the original.
+ * It does not release the free space in the string, so a call to
+ * sdsRemoveFreeSpace may be wise after. */
+void sdssubstr(sds s, size_t start, size_t len) {
+    /* Clamp out of range input */
+    size_t oldlen = sdslen(s);
+    if (start >= oldlen) start = len = 0;
+    if (len > oldlen-start) len = oldlen-start;
+
+    /* Move the data */
+    if (len) memmove(s, s+start, len);
+    s[len] = 0;
+    sdssetlen(s,len);
+}
+
 /* Turn the string into a smaller (or equal) string containing only the
  * substring specified by the 'start' and 'end' indexes.
  *
@@ -770,6 +785,11 @@ sds sdstrim(sds s, const char *cset) {
  *
  * The string is modified in-place.
  *
+ * NOTE: this function can be misleading and can have unexpected behaviour,
+ * specifically when you want the length of the new string to be 0.
+ * Having start==end will result in a string with one character.
+ * please consider using sdssubstr instead.
+ *
  * Example:
  *
  * s = sdsnew("Hello World");
@@ -777,28 +797,13 @@ sds sdstrim(sds s, const char *cset) {
  */
 void sdsrange(sds s, ssize_t start, ssize_t end) {
     size_t newlen, len = sdslen(s);
-
     if (len == 0) return;
-    if (start < 0) {
-        start = len+start;
-        if (start < 0) start = 0;
-    }
-    if (end < 0) {
-        end = len+end;
-        if (end < 0) end = 0;
-    }
+    if (start < 0)
+        start = len + start;
+    if (end < 0)
+        end = len + end;
     newlen = (start > end) ? 0 : (end-start)+1;
-    if (newlen != 0) {
-        if (start >= (ssize_t)len) {
-            newlen = 0;
-        } else if (end >= (ssize_t)len) {
-            end = len-1;
-            newlen = (start > end) ? 0 : (end-start)+1;
-        }
-    }
-    if (start && newlen) memmove(s, s+start, newlen);
-    s[newlen] = 0;
-    sdssetlen(s,newlen);
+    sdssubstr(s, start, newlen);
 }
 
 /* Apply tolower() to every character of the sds string 's'. */
@@ -1353,6 +1358,18 @@ int sdsTest(int argc, char **argv, int accurate) {
         test_cond("sdsrange(...,100,100)",
             sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
 
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,4,6);
+        test_cond("sdsrange(...,4,6)",
+            sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
+
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,3,6);
+        test_cond("sdsrange(...,3,6)",
+            sdslen(y) == 1 && memcmp(y,"o\0",2) == 0);
+
         sdsfree(y);
         sdsfree(x);
         x = sdsnew("foo");
diff --git a/src/sds.h b/src/sds.h
index 7f871074..6699858c 100644
--- a/src/sds.h
+++ b/src/sds.h
@@ -238,6 +238,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
 
 sds sdscatfmt(sds s, char const *fmt, ...);
 sds sdstrim(sds s, const char *cset);
+void sdssubstr(sds s, size_t start, size_t len);
 void sdsrange(sds s, ssize_t start, ssize_t end);
 void sdsupdatelen(sds s);
 void sdsclear(sds s);
diff --git a/src/server.c b/src/server.c
index 516bcb11..6c001b26 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3697,8 +3697,6 @@ void call(client *c, int flags) {
     struct redisCommand *real_cmd = c->cmd;
     static long long prev_err_count;
 
-    server.fixed_time_expire++;
-
     /* Initialization: clear the flags that must be set by the command on
      * demand, and initialize the array for additional commands propagation. */
     c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
@@ -3708,7 +3706,13 @@ void call(client *c, int flags) {
     /* Call the command. */
     dirty = server.dirty;
     prev_err_count = server.stat_total_error_replies;
-    updateCachedTime(0);
+
+    /* Update cache time, in case we have nested calls we want to
+     * update only on the first call*/
+    if (server.fixed_time_expire++ == 0) {
+        updateCachedTime(0);
+    }
+
     elapsedStart(&call_timer);
     c->cmd->proc(c);
     const long duration = elapsedUs(call_timer);
diff --git a/src/server.h b/src/server.h
index 86c92680..caf9df31 100644
--- a/src/server.h
+++ b/src/server.h
@@ -1779,6 +1779,7 @@ void moduleFireServerEvent(uint64_t eid, int subid, void 
*data);
 void processModuleLoadingProgressEvent(int is_aof);
 int moduleTryServeClientBlockedOnKey(client *c, robj *key);
 void moduleUnblockClient(client *c);
+int moduleBlockedClientMayTimeout(client *c);
 int moduleClientIsBlockedOnKeys(client *c);
 void moduleNotifyUserChanged(client *c);
 void moduleNotifyKeyUnlink(robj *key, robj *val);
@@ -1794,7 +1795,7 @@ void getRandomHexChars(char *p, size_t len);
 void getRandomBytes(unsigned char *p, size_t len);
 uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
 void exitFromChild(int retcode);
-size_t redisPopcount(void *s, long count);
+long long redisPopcount(void *s, long count);
 int redisSetProcTitle(char *title);
 int validateProcTitleTemplate(const char *template);
 int redisCommunicateSystemd(const char *sd_notify_msg);
@@ -1840,6 +1841,7 @@ void addReplyErrorSds(client *c, sds err);
 void addReplyError(client *c, const char *err);
 void addReplyStatus(client *c, const char *status);
 void addReplyDouble(client *c, double d);
+void addReplyBigNum(client *c, const char* num, size_t len);
 void addReplyHumanLongDouble(client *c, long double d);
 void addReplyLongLong(client *c, long long ll);
 void addReplyArrayLen(client *c, long length);
@@ -1945,6 +1947,7 @@ void initClientMultiState(client *c);
 void freeClientMultiState(client *c);
 void queueMultiCommand(client *c);
 void touchWatchedKey(redisDb *db, robj *key);
+int isWatchedKeyExpired(client *c);
 void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with);
 void discardTransaction(client *c);
 void flagTransaction(client *c);
@@ -2318,6 +2321,7 @@ void initConfigValues();
 /* db.c -- Keyspace access API */
 int removeExpire(redisDb *db, robj *key);
 void propagateExpire(redisDb *db, robj *key, int lazy);
+int keyIsExpired(redisDb *db, robj *key);
 int expireIfNeeded(redisDb *db, robj *key);
 long long getExpire(redisDb *db, robj *key);
 void setExpire(client *c, redisDb *db, robj *key, long long when);
diff --git a/src/t_hash.c b/src/t_hash.c
index d88b80b0..ea0606fb 100644
--- a/src/t_hash.c
+++ b/src/t_hash.c
@@ -986,7 +986,7 @@ void hrandfieldWithCountCommand(client *c, long l, int 
withvalues) {
     int uniq = 1;
     robj *hash;
 
-    if ((hash = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
+    if ((hash = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray))
         == NULL || checkType(c,hash,OBJ_HASH)) return;
     size = hashTypeLength(hash);
 
@@ -1175,7 +1175,7 @@ void hrandfieldWithCountCommand(client *c, long l, int 
withvalues) {
     }
 }
 
-/* HRANDFIELD [<count> WITHVALUES] */
+/* HRANDFIELD key [<count> [WITHVALUES]] */
 void hrandfieldCommand(client *c) {
     long l;
     int withvalues = 0;
diff --git a/src/t_set.c b/src/t_set.c
index bf250baa..2bce6294 100644
--- a/src/t_set.c
+++ b/src/t_set.c
@@ -392,12 +392,12 @@ void smoveCommand(client *c) {
     }
 
     signalModifiedKey(c,c->db,c->argv[1]);
-    signalModifiedKey(c,c->db,c->argv[2]);
     server.dirty++;
 
     /* An extra key has changed when ele was successfully added to dstset */
     if (setTypeAdd(dstset,ele->ptr)) {
         server.dirty++;
+        signalModifiedKey(c,c->db,c->argv[2]);
         notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id);
     }
     addReply(c,shared.cone);
@@ -858,25 +858,17 @@ void sinterGenericCommand(client *c, robj **setkeys,
     int64_t intobj;
     void *replylen = NULL;
     unsigned long j, cardinality = 0;
-    int encoding;
+    int encoding, empty = 0;
 
     for (j = 0; j < setnum; j++) {
         robj *setobj = dstkey ?
             lookupKeyWrite(c->db,setkeys[j]) :
             lookupKeyRead(c->db,setkeys[j]);
         if (!setobj) {
-            zfree(sets);
-            if (dstkey) {
-                if (dbDelete(c->db,dstkey)) {
-                    signalModifiedKey(c,c->db,dstkey);
-                    notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
-                    server.dirty++;
-                }
-                addReply(c,shared.czero);
-            } else {
-                addReply(c,shared.emptyset[c->resp]);
-            }
-            return;
+            /* A NULL is considered an empty set */
+            empty += 1;
+            sets[j] = NULL;
+            continue;
         }
         if (checkType(c,setobj,OBJ_SET)) {
             zfree(sets);
@@ -884,6 +876,24 @@ void sinterGenericCommand(client *c, robj **setkeys,
         }
         sets[j] = setobj;
     }
+
+    /* Set intersection with an empty set always results in an empty set.
+     * Return ASAP if there is an empty set. */
+    if (empty > 0) {
+        zfree(sets);
+        if (dstkey) {
+            if (dbDelete(c->db,dstkey)) {
+                signalModifiedKey(c,c->db,dstkey);
+                notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
+                server.dirty++;
+            }
+            addReply(c,shared.czero);
+        } else {
+            addReply(c,shared.emptyset[c->resp]);
+        }
+        return;
+    }
+
     /* Sort sets from the smallest to largest, this will improve our
      * algorithm's performance */
     qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
@@ -977,10 +987,12 @@ void sinterGenericCommand(client *c, robj **setkeys,
     zfree(sets);
 }
 
+/* SINTER key [key ...] */
 void sinterCommand(client *c) {
     sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
 }
 
+/* SINTERSTORE destination key [key ...] */
 void sinterstoreCommand(client *c) {
     sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
 }
@@ -1150,18 +1162,22 @@ void sunionDiffGenericCommand(client *c, robj 
**setkeys, int setnum,
     zfree(sets);
 }
 
+/* SUNION key [key ...] */
 void sunionCommand(client *c) {
     sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_UNION);
 }
 
+/* SUNIONSTORE destination key [key ...] */
 void sunionstoreCommand(client *c) {
     sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_UNION);
 }
 
+/* SDIFF key [key ...] */
 void sdiffCommand(client *c) {
     sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_DIFF);
 }
 
+/* SDIFFSTORE destination key [key ...] */
 void sdiffstoreCommand(client *c) {
     sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_DIFF);
 }
diff --git a/src/t_stream.c b/src/t_stream.c
index 1b2fe326..2c30faa0 100644
--- a/src/t_stream.c
+++ b/src/t_stream.c
@@ -702,16 +702,16 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args) {
 
     int64_t deleted = 0;
     while (raxNext(&ri)) {
-        /* Check if we exceeded the amount of work we could do */
-        if (limit && deleted >= limit)
-            break;
-
         if (trim_strategy == TRIM_STRATEGY_MAXLEN && s->length <= maxlen)
             break;
 
         unsigned char *lp = ri.data, *p = lpFirst(lp);
         int64_t entries = lpGetInteger(p);
 
+        /* Check if we exceeded the amount of work we could do */
+        if (limit && (deleted + entries) > limit)
+            break;
+
         /* Check if we can remove the whole node. */
         int remove_node;
         streamID master_id = {0}; /* For MINID */
@@ -3233,7 +3233,7 @@ void xtrimCommand(client *c) {
 
     /* Argument parsing. */
     streamAddTrimArgs parsed_args;
-    if (streamParseAddOrTrimArgsOrReply(c, &parsed_args, 1) < 0)
+    if (streamParseAddOrTrimArgsOrReply(c, &parsed_args, 0) < 0)
         return; /* streamParseAddOrTrimArgsOrReply already replied. */
 
     /* If the key does not exist, we are ok returning zero, that is, the
diff --git a/src/t_zset.c b/src/t_zset.c
index fb402816..3b9ebd2b 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -3663,7 +3663,12 @@ void zrangeGenericCommand(zrange_result_handler 
*handler, int argc_start, int st
         lookupKeyWrite(c->db,key) :
         lookupKeyRead(c->db,key);
     if (zobj == NULL) {
-        addReply(c,shared.emptyarray);
+        if (store) {
+            handler->beginResultEmission(handler);
+            handler->finalizeResultEmission(handler, 0);
+        } else {
+            addReply(c, shared.emptyarray);
+        }
         goto cleanup;
     }
 
@@ -3820,11 +3825,16 @@ void genericZpopCommand(client *c, robj **keyv, int 
keyc, int where, int emitkey
     }
 
     void *arraylen_ptr = addReplyDeferredLen(c);
-    long arraylen = 0;
+    long result_count = 0;
 
     /* We emit the key only for the blocking variant. */
     if (emitkey) addReplyBulk(c,key);
 
+    /* Respond with a single (flat) array in RESP2 or if countarg is not
+     * provided (returning a single element). In RESP3, when countarg is
+     * provided, use nested array.  */
+    int use_nested_array = c->resp > 2 && countarg != NULL;
+
     /* Remove the element. */
     do {
         if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
@@ -3867,16 +3877,19 @@ void genericZpopCommand(client *c, robj **keyv, int 
keyc, int where, int emitkey
         serverAssertWithInfo(c,zobj,zsetDel(zobj,ele));
         server.dirty++;
 
-        if (arraylen == 0) { /* Do this only for the first iteration. */
+        if (result_count == 0) { /* Do this only for the first iteration. */
             char *events[2] = {"zpopmin","zpopmax"};
             notifyKeyspaceEvent(NOTIFY_ZSET,events[where],key,c->db->id);
             signalModifiedKey(c,c->db,key);
         }
 
+        if (use_nested_array) {
+            addReplyArrayLen(c,2);
+        }
         addReplyBulkCBuffer(c,ele,sdslen(ele));
         addReplyDouble(c,score);
         sdsfree(ele);
-        arraylen += 2;
+        ++result_count;
 
         /* Remove the key, if indeed needed. */
         if (zsetLength(zobj) == 0) {
@@ -3886,7 +3899,10 @@ void genericZpopCommand(client *c, robj **keyv, int 
keyc, int where, int emitkey
         }
     } while(--count);
 
-    setDeferredArrayLen(c,arraylen_ptr,arraylen + (emitkey != 0));
+    if (!use_nested_array) {
+        result_count *= 2;
+    }
+    setDeferredArrayLen(c,arraylen_ptr,result_count + (emitkey != 0));
 }
 
 /* ZPOPMIN key [<count>] */
@@ -3987,7 +4003,7 @@ void zrandmemberWithCountCommand(client *c, long l, int 
withscores) {
     int uniq = 1;
     robj *zsetobj;
 
-    if ((zsetobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
+    if ((zsetobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptyarray))
         == NULL || checkType(c, zsetobj, OBJ_ZSET)) return;
     size = zsetLength(zsetobj);
 
@@ -4023,7 +4039,7 @@ void zrandmemberWithCountCommand(client *c, long l, int 
withscores) {
                     addReplyArrayLen(c,2);
                 addReplyBulkCBuffer(c, key, sdslen(key));
                 if (withscores)
-                    addReplyDouble(c, dictGetDoubleVal(de));
+                    addReplyDouble(c, *(double*)dictGetVal(de));
             }
         } else if (zsetobj->encoding == OBJ_ENCODING_ZIPLIST) {
             ziplistEntry *keys, *vals = NULL;
@@ -4172,7 +4188,7 @@ void zrandmemberWithCountCommand(client *c, long l, int 
withscores) {
     }
 }
 
-/* ZRANDMEMBER [<count> WITHSCORES] */
+/* ZRANDMEMBER key [<count> [WITHSCORES]] */
 void zrandmemberCommand(client *c) {
     long l;
     int withscores = 0;
diff --git a/src/tls.c b/src/tls.c
index ffd3b0ad..0f76256d 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -146,6 +146,8 @@ void tlsInit(void) {
      */
     #if OPENSSL_VERSION_NUMBER < 0x10100000L
     OPENSSL_config(NULL);
+    #elif OPENSSL_VERSION_NUMBER < 0x10101000L
+    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
     #else
     OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_ATFORK, NULL);
     #endif
diff --git a/src/tracking.c b/src/tracking.c
index a11e4b7d..a57f062c 100644
--- a/src/tracking.c
+++ b/src/tracking.c
@@ -326,7 +326,7 @@ void trackingRememberKeyToBroadcast(client *c, char 
*keyname, size_t keylen) {
          * tree. This way we know who was the client that did the last
          * change to the key, and can avoid sending the notification in the
          * case the client is in NOLOOP mode. */
-        raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
+        raxInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
     }
     raxStop(&ri);
 }
diff --git a/src/version.h b/src/version.h
index c355ecfe..cd2ff3a6 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,2 +1,2 @@
-#define REDIS_VERSION "6.2.4"
-#define REDIS_VERSION_NUM 0x00060204
+#define REDIS_VERSION "6.2.5"
+#define REDIS_VERSION_NUM 0x00060205
diff --git a/src/ziplist.c b/src/ziplist.c
index 85cb5099..aae86c1f 100644
--- a/src/ziplist.c
+++ b/src/ziplist.c
@@ -263,7 +263,7 @@
  * to stay there to signal that a full scan is needed to get the number of
  * items inside the ziplist. */
 #define ZIPLIST_INCR_LENGTH(zl,incr) { \
-    if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
+    if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) \
         ZIPLIST_LENGTH(zl) = 
intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
 }
 
diff --git a/tests/helpers/bg_block_op.tcl b/tests/helpers/bg_block_op.tcl
index c8b32330..dc4e1a99 100644
--- a/tests/helpers/bg_block_op.tcl
+++ b/tests/helpers/bg_block_op.tcl
@@ -12,6 +12,7 @@ set ::tlsdir "tests/tls"
 # blocking.
 proc bg_block_op {host port db ops tls} {
     set r [redis $host $port 0 $tls]
+    $r client setname LOAD_HANDLER
     $r select $db
 
     for {set j 0} {$j < $ops} {incr j} {
diff --git a/tests/helpers/bg_complex_data.tcl 
b/tests/helpers/bg_complex_data.tcl
index e888748a..9c0044e7 100644
--- a/tests/helpers/bg_complex_data.tcl
+++ b/tests/helpers/bg_complex_data.tcl
@@ -5,6 +5,7 @@ set ::tlsdir "tests/tls"
 
 proc bg_complex_data {host port db ops tls} {
     set r [redis $host $port 0 $tls]
+    $r client setname LOAD_HANDLER
     $r select $db
     createComplexDataset $r $ops
 }
diff --git a/tests/helpers/gen_write_load.tcl b/tests/helpers/gen_write_load.tcl
index cbf6651b..568f5cde 100644
--- a/tests/helpers/gen_write_load.tcl
+++ b/tests/helpers/gen_write_load.tcl
@@ -5,6 +5,7 @@ set ::tlsdir "tests/tls"
 proc gen_write_load {host port seconds tls} {
     set start_time [clock seconds]
     set r [redis $host $port 1 $tls]
+    $r client setname LOAD_HANDLER
     $r select 9
     while 1 {
         $r set [expr rand()] [expr rand()]
diff --git a/tests/integration/block-repl.tcl b/tests/integration/block-repl.tcl
index 07eceb22..7c2ba840 100644
--- a/tests/integration/block-repl.tcl
+++ b/tests/integration/block-repl.tcl
@@ -33,14 +33,9 @@ start_server {tags {"repl"}} {
             stop_bg_block_op $load_handle0
             stop_bg_block_op $load_handle1
             stop_bg_block_op $load_handle2
-            set retry 10
-            while {$retry && ([$master debug digest] ne [$slave debug 
digest])}\
-            {
-                after 1000
-                incr retry -1
-            }
-
-            if {[$master debug digest] ne [$slave debug digest]} {
+            wait_for_condition 100 100 {
+                [$master debug digest] == [$slave debug digest]
+            } else {
                 set csv1 [csvdump r]
                 set csv2 [csvdump {r -1}]
                 set fd [open /tmp/repldump1.txt w]
@@ -49,10 +44,8 @@ start_server {tags {"repl"}} {
                 set fd [open /tmp/repldump2.txt w]
                 puts -nonewline $fd $csv2
                 close $fd
-                puts "Master - Replica inconsistency"
-                puts "Run diff -u against /tmp/repldump*.txt for more info"
+                fail "Master - Replica inconsistency, Run diff -u against 
/tmp/repldump*.txt for more info"
             }
-            assert_equal [r debug digest] [r -1 debug digest]
         }
     }
 }
diff --git a/tests/integration/failover.tcl b/tests/integration/failover.tcl
index c6818700..10642eb3 100644
--- a/tests/integration/failover.tcl
+++ b/tests/integration/failover.tcl
@@ -83,7 +83,11 @@ start_server {} {
         } else {
             fail "Failover from node 0 to node 1 did not finish"
         }
+
+        # stop the write load and make sure no more commands processed
         stop_write_load $load_handler
+        wait_load_handlers_disconnected
+
         $node_2 replicaof $node_1_host $node_1_port
         wait_for_sync $node_0
         wait_for_sync $node_2
diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl
index 7e8b41fc..d58229bb 100644
--- a/tests/integration/redis-cli.tcl
+++ b/tests/integration/redis-cli.tcl
@@ -283,9 +283,9 @@ start_server {tags {"cli"}} {
         assert_equal {key:2} [run_cli --scan --quoted-pattern {"*:\x32"}]
     }
 
-    test "Connecting as a replica" {
+    proc test_redis_cli_repl {} {
         set fd [open_cli "--replica"]
-        wait_for_condition 500 500 {
+        wait_for_condition 500 100 {
             [string match {*slave0:*state=online*} [r info]]
         } else {
             fail "redis-cli --replica did not connect"
@@ -294,14 +294,30 @@ start_server {tags {"cli"}} {
         for {set i 0} {$i < 100} {incr i} {
            r set test-key test-value-$i
         }
-        r client kill type slave
-        catch {
-            assert_match {*SET*key-a*} [read_cli $fd]
+
+        wait_for_condition 500 100 {
+            [string match {*test-value-99*} [read_cli $fd]]
+        } else {
+            fail "redis-cli --replica didn't read commands"
         }
 
-        close_cli $fd
+        fconfigure $fd -blocking true
+        r client kill type slave
+        catch { close_cli $fd } err
+        assert_match {*Server closed the connection*} $err
     }
 
+    test "Connecting as a replica" {
+        # Disk-based master
+        assert_match "OK" [r config set repl-diskless-sync no]
+        test_redis_cli_repl
+
+        # Disk-less master
+        assert_match "OK" [r config set repl-diskless-sync yes]
+        assert_match "OK" [r config set repl-diskless-sync-delay 0]
+        test_redis_cli_repl
+    } {}
+
     test "Piping raw protocol" {
         set cmds [tmpfile "cli_cmds"]
         set cmds_fd [open $cmds "w"]
diff --git a/tests/integration/replication-4.tcl 
b/tests/integration/replication-4.tcl
index 8715ae99..e4ac83e1 100644
--- a/tests/integration/replication-4.tcl
+++ b/tests/integration/replication-4.tcl
@@ -21,15 +21,9 @@ start_server {tags {"repl network"}} {
             stop_bg_complex_data $load_handle0
             stop_bg_complex_data $load_handle1
             stop_bg_complex_data $load_handle2
-            set retry 10
-            while {$retry && ([$master debug digest] ne [$slave debug 
digest])}\
-            {
-                after 1000
-                incr retry -1
-            }
-            assert {[$master dbsize] > 0}
-
-            if {[$master debug digest] ne [$slave debug digest]} {
+            wait_for_condition 100 100 {
+                [$master debug digest] == [$slave debug digest]
+            } else {
                 set csv1 [csvdump r]
                 set csv2 [csvdump {r -1}]
                 set fd [open /tmp/repldump1.txt w]
@@ -38,10 +32,9 @@ start_server {tags {"repl network"}} {
                 set fd [open /tmp/repldump2.txt w]
                 puts -nonewline $fd $csv2
                 close $fd
-                puts "Master - Replica inconsistency"
-                puts "Run diff -u against /tmp/repldump*.txt for more info"
+                fail "Master - Replica inconsistency, Run diff -u against 
/tmp/repldump*.txt for more info"
             }
-            assert_equal [r debug digest] [r -1 debug digest]
+            assert {[$master dbsize] > 0}
         }
     }
 }
diff --git a/tests/integration/replication-psync.tcl 
b/tests/integration/replication-psync.tcl
index 3c98723a..08e21d31 100644
--- a/tests/integration/replication-psync.tcl
+++ b/tests/integration/replication-psync.tcl
@@ -97,15 +97,9 @@ proc test_psync {descr duration backlog_size backlog_ttl 
delay cond mdl sdl reco
                     fail "Slave still not connected after some time"
                 }  
 
-                set retry 10
-                while {$retry && ([$master debug digest] ne [$slave debug 
digest])}\
-                {
-                    after 1000
-                    incr retry -1
-                }
-                assert {[$master dbsize] > 0}
-
-                if {[$master debug digest] ne [$slave debug digest]} {
+                wait_for_condition 100 100 {
+                    [$master debug digest] == [$slave debug digest]
+                } else {
                     set csv1 [csvdump r]
                     set csv2 [csvdump {r -1}]
                     set fd [open /tmp/repldump1.txt w]
@@ -114,10 +108,9 @@ proc test_psync {descr duration backlog_size backlog_ttl 
delay cond mdl sdl reco
                     set fd [open /tmp/repldump2.txt w]
                     puts -nonewline $fd $csv2
                     close $fd
-                    puts "Master - Replica inconsistency"
-                    puts "Run diff -u against /tmp/repldump*.txt for more info"
+                    fail "Master - Replica inconsistency, Run diff -u against 
/tmp/repldump*.txt for more info"
                 }
-                assert_equal [r debug digest] [r -1 debug digest]
+                assert {[$master dbsize] > 0}
                 eval $cond
             }
         }
diff --git a/tests/integration/replication.tcl 
b/tests/integration/replication.tcl
index fcef2e9e..916dd324 100644
--- a/tests/integration/replication.tcl
+++ b/tests/integration/replication.tcl
@@ -316,15 +316,12 @@ foreach mdl {no yes} {
                             stop_write_load $load_handle3
                             stop_write_load $load_handle4
 
-                            # Make sure that slaves and master have same
-                            # number of keys
-                            wait_for_condition 500 100 {
-                                [$master dbsize] == [[lindex $slaves 0] 
dbsize] &&
-                                [$master dbsize] == [[lindex $slaves 1] 
dbsize] &&
-                                [$master dbsize] == [[lindex $slaves 2] dbsize]
-                            } else {
-                                fail "Different number of keys between master 
and replica after too long time."
-                            }
+                            # Make sure no more commands processed
+                            wait_load_handlers_disconnected
+
+                            wait_for_ofs_sync $master [lindex $slaves 0]
+                            wait_for_ofs_sync $master [lindex $slaves 1]
+                            wait_for_ofs_sync $master [lindex $slaves 2]
 
                             # Check digests
                             set digest [$master debug digest]
@@ -903,7 +900,7 @@ test {Kill rdb child process if its dumping RDB is not 
useful} {
                 # Slave2 disconnect with master
                 $slave2 slaveof no one
                 # Should kill child
-                wait_for_condition 20 10 {
+                wait_for_condition 100 10 {
                     [s 0 rdb_bgsave_in_progress] eq 0
                 } else {
                     fail "can't kill rdb child"
diff --git a/tests/modules/Makefile b/tests/modules/Makefile
index f5631396..ae611de8 100644
--- a/tests/modules/Makefile
+++ b/tests/modules/Makefile
@@ -18,6 +18,7 @@ endif
 
 TEST_MODULES = \
     commandfilter.so \
+    basics.so \
     testrdb.so \
     fork.so \
     infotest.so \
diff --git a/src/modules/testmodule.c b/tests/modules/basics.c
similarity index 81%
rename from src/modules/testmodule.c
rename to tests/modules/basics.c
index 078c02c5..59ceb2d1 100644
--- a/src/modules/testmodule.c
+++ b/tests/modules/basics.c
@@ -31,7 +31,7 @@
  */
 
 #define REDISMODULE_EXPERIMENTAL_API
-#include "../redismodule.h"
+#include "redismodule.h"
 #include <string.h>
 
 /* --------------------------------- Helpers -------------------------------- 
*/
@@ -152,11 +152,64 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString 
**argv, int argc) {
         return failTest(ctx, "Could not verify key to be unlinked");
     }
     return RedisModule_ReplyWithSimpleString(ctx, "OK");
+}
+
+/* TEST.STRING.TRUNCATE -- Test truncating an existing string object. */
+int TestStringTruncate(RedisModuleCtx *ctx, RedisModuleString **argv, int 
argc) {
+    RedisModule_AutoMemory(ctx);
+    REDISMODULE_NOT_USED(argv);
+    REDISMODULE_NOT_USED(argc);
+
+    RedisModule_Call(ctx, "SET", "cc", "foo", "abcde");
+    RedisModuleKey *k = RedisModule_OpenKey(ctx, 
RedisModule_CreateStringPrintf(ctx, "foo"), REDISMODULE_READ | 
REDISMODULE_WRITE);
+    if (!k) return failTest(ctx, "Could not create key");
+
+    size_t len = 0;
+    char* s;
 
+    /* expand from 5 to 8 and check null pad */
+    if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 8)) {
+        return failTest(ctx, "Could not truncate string value (8)");
+    }
+    s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
+    if (!s) {
+        return failTest(ctx, "Failed to read truncated string (8)");
+    } else if (len != 8) {
+        return failTest(ctx, "Failed to expand string value (8)");
+    } else if (0 != strncmp(s, "abcde\0\0\0", 8)) {
+        return failTest(ctx, "Failed to null pad string value (8)");
+    }
+
+    /* shrink from 8 to 4 */
+    if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 4)) {
+        return failTest(ctx, "Could not truncate string value (4)");
+    }
+    s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
+    if (!s) {
+        return failTest(ctx, "Failed to read truncated string (4)");
+    } else if (len != 4) {
+        return failTest(ctx, "Failed to shrink string value (4)");
+    } else if (0 != strncmp(s, "abcd", 4)) {
+        return failTest(ctx, "Failed to truncate string value (4)");
+    }
+
+    /* shrink to 0 */
+    if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 0)) {
+        return failTest(ctx, "Could not truncate string value (0)");
+    }
+    s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
+    if (!s) {
+        return failTest(ctx, "Failed to read truncated string (0)");
+    } else if (len != 0) {
+        return failTest(ctx, "Failed to shrink string value to (0)");
+    }
+
+    return RedisModule_ReplyWithSimpleString(ctx, "OK");
 }
 
 int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
                    RedisModuleString *key) {
+  RedisModule_AutoMemory(ctx);
   /* Increment a counter on the notifications: for each key notified we
    * increment a counter */
   RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type,
@@ -168,6 +221,7 @@ int NotifyCallback(RedisModuleCtx *ctx, int type, const 
char *event,
 
 /* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */
 int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) 
{
+    RedisModule_AutoMemory(ctx);
     REDISMODULE_NOT_USED(argv);
     REDISMODULE_NOT_USED(argc);
 
@@ -279,6 +333,9 @@ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString 
**argv, int argc) {
     flags = RedisModule_GetContextFlags(ctx);
     if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) FAIL("AOF Flag not set after 
config set");
 
+    /* Disable RDB saving and test the flag. */
+    RedisModule_Call(ctx, "config", "ccc", "set", "save", "");
+    flags = RedisModule_GetContextFlags(ctx);
     if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set");
     /* Enable RDB to test RDB flags */
     RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1");
@@ -290,8 +347,12 @@ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString 
**argv, int argc) {
     if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set");
     if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set");
 
+    /* Disable maxmemory and test the flag. (it is implicitly set in 32bit 
builds. */
+    RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0");
+    flags = RedisModule_GetContextFlags(ctx);
     if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was 
set");
 
+    /* Enable maxmemory and test the flag. */
     RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000");
     flags = RedisModule_GetContextFlags(ctx);
     if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY))
@@ -324,7 +385,11 @@ end:
 int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, 
char *str, size_t len) {
     RedisModuleString *mystr, *expected;
 
-    if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
+    if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
+        RedisModule_Log(ctx,"warning","Test error reply: %s",
+            RedisModule_CallReplyStringPtr(reply, NULL));
+        return 0;
+    } else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
         RedisModule_Log(ctx,"warning","Unexpected reply type %d",
             RedisModule_CallReplyType(reply));
         return 0;
@@ -345,7 +410,11 @@ int TestAssertStringReply(RedisModuleCtx *ctx, 
RedisModuleCallReply *reply, char
 /* Return 1 if the reply matches the specified integer, otherwise log errors
  * in the server log and return 0. */
 int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, 
long long expected) {
-    if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) {
+    if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
+        RedisModule_Log(ctx,"warning","Test error reply: %s",
+            RedisModule_CallReplyStringPtr(reply, NULL));
+        return 0;
+    } else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) {
         RedisModule_Log(ctx,"warning","Unexpected reply type %d",
             RedisModule_CallReplyType(reply));
         return 0;
@@ -366,8 +435,11 @@ int TestAssertIntegerReply(RedisModuleCtx *ctx, 
RedisModuleCallReply *reply, lon
         reply = RedisModule_Call(ctx,name,__VA_ARGS__); \
     } while (0)
 
-/* TEST.IT -- Run all the tests. */
-int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+/* TEST.BASICS -- Run all the tests.
+ * Note: it is useful to run these tests from the module rather than TCL
+ * since it's easier to check the reply types like that (make a distinction
+ * between 0 and "0", etc. */
+int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
     REDISMODULE_NOT_USED(argv);
     REDISMODULE_NOT_USED(argc);
 
@@ -390,6 +462,9 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, 
int argc) {
     T("test.string.append","");
     if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
 
+    T("test.string.truncate","");
+    if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
+
     T("test.unlink","");
     if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
 
@@ -407,7 +482,7 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, 
int argc) {
 
 fail:
     RedisModule_ReplyWithSimpleString(ctx,
-        "SOME TEST NOT PASSED! Check server logs");
+        "SOME TEST DID NOT PASS! Check server logs");
     return REDISMODULE_OK;
 }
 
@@ -430,6 +505,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, 
RedisModuleString **argv, int argc)
         TestStringAppendAM,"write deny-oom",1,1,1) == REDISMODULE_ERR)
         return REDISMODULE_ERR;
 
+    if (RedisModule_CreateCommand(ctx,"test.string.truncate",
+        TestStringTruncate,"write deny-oom",1,1,1) == REDISMODULE_ERR)
+        return REDISMODULE_ERR;
+
     if (RedisModule_CreateCommand(ctx,"test.string.printf",
         TestStringPrintf,"write deny-oom",1,1,1) == REDISMODULE_ERR)
         return REDISMODULE_ERR;
@@ -442,8 +521,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, 
RedisModuleString **argv, int argc)
         TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
         return REDISMODULE_ERR;
 
-    if (RedisModule_CreateCommand(ctx,"test.it",
-        TestIt,"readonly",1,1,1) == REDISMODULE_ERR)
+    if (RedisModule_CreateCommand(ctx,"test.basics",
+        TestBasics,"readonly",1,1,1) == REDISMODULE_ERR)
         return REDISMODULE_ERR;
 
     RedisModule_SubscribeToKeyspaceEvents(ctx,
diff --git a/tests/modules/blockonbackground.c 
b/tests/modules/blockonbackground.c
index 855fef9d..68875630 100644
--- a/tests/modules/blockonbackground.c
+++ b/tests/modules/blockonbackground.c
@@ -195,6 +195,66 @@ int HelloDoubleBlock_RedisCommand(RedisModuleCtx *ctx, 
RedisModuleString **argv,
     return REDISMODULE_OK;
 }
 
+RedisModuleBlockedClient *blocked_client = NULL;
+
+/* BLOCK.BLOCK [TIMEOUT] -- Blocks the current client until released
+ * or TIMEOUT seconds. If TIMEOUT is zero, no timeout function is
+ * registered.
+ */
+int Block_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int 
argc) {
+    if (RedisModule_IsBlockedReplyRequest(ctx)) {
+        RedisModuleString *r = RedisModule_GetBlockedClientPrivateData(ctx);
+        return RedisModule_ReplyWithString(ctx, r);
+    } else if (RedisModule_IsBlockedTimeoutRequest(ctx)) {
+        RedisModule_UnblockClient(blocked_client, NULL); /* Must be called to 
avoid leaks. */
+        blocked_client = NULL;
+        return RedisModule_ReplyWithSimpleString(ctx, "Timed out");
+    }
+
+    if (argc != 2) return RedisModule_WrongArity(ctx);
+    long long timeout;
+
+    if (RedisModule_StringToLongLong(argv[1], &timeout) != REDISMODULE_OK) {
+        return RedisModule_ReplyWithError(ctx, "ERR invalid timeout");
+    }
+    if (blocked_client) {
+        return RedisModule_ReplyWithError(ctx, "ERR another client already 
blocked");
+    }
+
+    /* Block client. We use this function as both a reply and optional timeout
+     * callback and differentiate the different code flows above.
+     */
+    blocked_client = RedisModule_BlockClient(ctx, Block_RedisCommand,
+            timeout > 0 ? Block_RedisCommand : NULL, NULL, timeout);
+    return REDISMODULE_OK;
+}
+
+/* BLOCK.IS_BLOCKED -- Returns 1 if we have a blocked client, or 0 otherwise.
+ */
+int IsBlocked_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int 
argc) {
+    UNUSED(argv);
+    UNUSED(argc);
+    RedisModule_ReplyWithLongLong(ctx, blocked_client ? 1 : 0);
+    return REDISMODULE_OK;
+}
+
+/* BLOCK.RELEASE [reply] -- Releases the blocked client and produce the 
specified reply.
+ */
+int Release_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int 
argc) {
+    if (argc != 2) return RedisModule_WrongArity(ctx);
+    if (!blocked_client) {
+        return RedisModule_ReplyWithError(ctx, "ERR No blocked client");
+    }
+
+    RedisModuleString *replystr = argv[1];
+    RedisModule_RetainString(ctx, replystr);
+    int err = RedisModule_UnblockClient(blocked_client, replystr);
+    blocked_client = NULL;
+
+    RedisModule_ReplyWithSimpleString(ctx, "OK");
+
+    return REDISMODULE_OK;
+}
 
 int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int 
argc) {
     UNUSED(argv);
@@ -215,5 +275,17 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, 
RedisModuleString **argv, int argc)
         HelloBlockNoTracking_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
         return REDISMODULE_ERR;
 
+    if (RedisModule_CreateCommand(ctx, "block.block",
+        Block_RedisCommand, "", 0, 0, 0) == REDISMODULE_ERR)
+        return REDISMODULE_ERR;
+
+    if (RedisModule_CreateCommand(ctx,"block.is_blocked",
+        IsBlocked_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
+        return REDISMODULE_ERR;
+
+    if (RedisModule_CreateCommand(ctx,"block.release",
+        Release_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
+        return REDISMODULE_ERR;
+
     return REDISMODULE_OK;
 }
diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl
index 4d321c97..978163e9 100644
--- a/tests/support/redis.tcl
+++ b/tests/support/redis.tcl
@@ -34,13 +34,14 @@ array set ::redis::fd {}
 array set ::redis::addr {}
 array set ::redis::blocking {}
 array set ::redis::deferred {}
+array set ::redis::readraw {}
 array set ::redis::reconnect {}
 array set ::redis::tls {}
 array set ::redis::callback {}
 array set ::redis::state {} ;# State in non-blocking reply reading
 array set ::redis::statestack {} ;# Stack of states, for nested mbulks
 
-proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} {
+proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}} 
{readraw 0}} {
     if {$tls} {
         package require tls
         ::tls::init \
@@ -58,6 +59,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} 
{tlsoptions {}}} {
     set ::redis::addr($id) [list $server $port]
     set ::redis::blocking($id) 1
     set ::redis::deferred($id) $defer
+    set ::redis::readraw($id) $readraw
     set ::redis::reconnect($id) 0
     set ::redis::tls($id) $tls
     ::redis::redis_reset_state $id
@@ -158,6 +160,7 @@ proc ::redis::__method__close {id fd} {
     catch {unset ::redis::addr($id)}
     catch {unset ::redis::blocking($id)}
     catch {unset ::redis::deferred($id)}
+    catch {unset ::redis::readraw($id)}
     catch {unset ::redis::reconnect($id)}
     catch {unset ::redis::tls($id)}
     catch {unset ::redis::state($id)}
@@ -174,6 +177,10 @@ proc ::redis::__method__deferred {id fd val} {
     set ::redis::deferred($id) $val
 }
 
+proc ::redis::__method__readraw {id fd val} {
+    set ::redis::readraw($id) $val
+}
+
 proc ::redis::redis_write {fd buf} {
     puts -nonewline $fd $buf
 }
@@ -240,26 +247,46 @@ proc ::redis::redis_read_null fd {
     return {}
 }
 
+proc ::redis::redis_read_bool fd {
+    set v [redis_read_line $fd]
+    if {$v == "t"} {return 1}
+    if {$v == "f"} {return 0}
+    return -code error "Bad protocol, '$v' as bool type"
+}
+
 proc ::redis::redis_read_reply {id fd} {
-    set type [read $fd 1]
-    switch -exact -- $type {
-        _ {redis_read_null $fd}
-        : -
-        + {redis_read_line $fd}
-        , {expr {double([redis_read_line $fd])}}
-        - {return -code error [redis_read_line $fd]}
-        $ {redis_bulk_read $fd}
-        > -
-        ~ -
-        * {redis_multi_bulk_read $id $fd}
-        % {redis_read_map $id $fd}
-        default {
-            if {$type eq {}} {
-                catch {close $fd}
-                set ::redis::fd($id) {}
-                return -code error "I/O error reading reply"
+    if {$::redis::readraw($id)} {
+        return [redis_read_line $fd]
+    }
+
+    while {1} {
+        set type [read $fd 1]
+        switch -exact -- $type {
+            _ {return [redis_read_null $fd]}
+            : -
+            ( -
+            + {return [redis_read_line $fd]}
+            , {return [expr {double([redis_read_line $fd])}]}
+            # {return [redis_read_bool $fd]}
+            - {return -code error [redis_read_line $fd]}
+            $ {return [redis_bulk_read $fd]}
+            > -
+            ~ -
+            * {return [redis_multi_bulk_read $id $fd]}
+            % {return [redis_read_map $id $fd]}
+            | {
+                # ignore attributes for now (nowhere to store them)
+                redis_read_map $id $fd
+                continue
+            }
+            default {
+                if {$type eq {}} {
+                    catch {close $fd}
+                    set ::redis::fd($id) {}
+                    return -code error "I/O error reading reply"
+                }
+                return -code error "Bad protocol, '$type' as reply type byte"
             }
-            return -code error "Bad protocol, '$type' as reply type byte"
         }
     }
 }
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
index b00aa159..d6717f6e 100644
--- a/tests/support/util.tcl
+++ b/tests/support/util.tcl
@@ -504,6 +504,14 @@ proc stop_write_load {handle} {
     catch {exec /bin/kill -9 $handle}
 }
 
+proc wait_load_handlers_disconnected {{level 0}} {
+    wait_for_condition 50 100 {
+        ![string match {*name=LOAD_HANDLER*} [r $level client list]]
+    } else {
+        fail "load_handler(s) still connected after too long time."
+    }
+}
+
 proc K { x y } { set x } 
 
 # Shuffle a list with Fisher-Yates algorithm.
diff --git a/tests/unit/aofrw.tcl b/tests/unit/aofrw.tcl
index 1a686a2f..5bdf8725 100644
--- a/tests/unit/aofrw.tcl
+++ b/tests/unit/aofrw.tcl
@@ -41,15 +41,8 @@ start_server {tags {"aofrw"}} {
             stop_write_load $load_handle3
             stop_write_load $load_handle4
 
-            # Make sure that we remain the only connected client.
-            # This step is needed to make sure there are no pending writes
-            # that will be processed between the two "debug digest" calls.
-            wait_for_condition 50 100 {
-                [llength [split [string trim [r client list]] "\n"]] == 1
-            } else {
-                puts [r client list]
-                fail "Clients generating loads are not disconnecting"
-            }
+            # Make sure no more commands processed, before taking debug digest
+            wait_load_handlers_disconnected
 
             # Get the data set digest
             set d1 [r debug digest]
diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl
index 926f3829..53483297 100644
--- a/tests/unit/bitops.tcl
+++ b/tests/unit/bitops.tcl
@@ -349,3 +349,31 @@ start_server {tags {"bitops"}} {
         }
     }
 }
+
+start_server {tags {"bitops large-memory"}} {
+    test "BIT pos larger than UINT_MAX" {
+        set bytes [expr (1 << 29) + 1]
+        set bitpos [expr (1 << 32)]
+        set oldval [lindex [r config get proto-max-bulk-len] 1]
+        r config set proto-max-bulk-len $bytes
+        r setbit mykey $bitpos 1
+        assert_equal $bytes [r strlen mykey]
+        assert_equal 1 [r getbit mykey $bitpos]
+        assert_equal [list 128 128 -1] [r bitfield mykey get u8 $bitpos set u8 
$bitpos 255 get i8 $bitpos]
+        assert_equal $bitpos [r bitpos mykey 1]
+        assert_equal $bitpos [r bitpos mykey 1 [expr $bytes - 1]]
+        if {$::accurate} {
+            # set all bits to 1
+            set mega [expr (1 << 23)]
+            set part [string repeat "\xFF" $mega]
+            for {set i 0} {$i < 64} {incr i} {
+                r setrange mykey [expr $i * $mega] $part
+            }
+            r setrange mykey [expr $bytes - 1] "\xFF"
+            assert_equal [expr $bitpos + 8] [r bitcount mykey]
+            assert_equal -1 [r bitpos mykey 0 0 [expr $bytes - 1]]
+        }
+        r config set proto-max-bulk-len $oldval
+        r del mykey
+    } {1}
+}
diff --git a/tests/unit/moduleapi/basics.tcl b/tests/unit/moduleapi/basics.tcl
new file mode 100644
index 00000000..96683f4c
--- /dev/null
+++ b/tests/unit/moduleapi/basics.tcl
@@ -0,0 +1,12 @@
+set testmodule [file normalize tests/modules/basics.so]
+
+
+start_server {tags {"modules"}} {
+    r module load $testmodule
+
+    test {test module api basics} {
+        r test.basics
+    } {ALL TESTS PASSED}
+
+    r module unload test
+}
diff --git a/tests/unit/moduleapi/blockonbackground.tcl 
b/tests/unit/moduleapi/blockonbackground.tcl
index 66a232fa..79ca5214 100644
--- a/tests/unit/moduleapi/blockonbackground.tcl
+++ b/tests/unit/moduleapi/blockonbackground.tcl
@@ -85,4 +85,33 @@ start_server {tags {"modules"}} {
             assert_equal [r slowlog len] 0
         }
     }
+
+    test "client unblock works only for modules with timeout support" {
+        set rd [redis_deferring_client]
+        $rd client id
+        set id [$rd read]
+
+        # Block with a timeout function - may unblock
+        $rd block.block 20000
+        wait_for_condition 50 100 {
+            [r block.is_blocked] == 1
+        } else {
+            fail "Module did not block"
+        }
+
+        assert_equal 1 [r client unblock $id]
+        assert_match {*Timed out*} [$rd read]
+
+        # Block without a timeout function - cannot unblock
+        $rd block.block 0
+        wait_for_condition 50 100 {
+            [r block.is_blocked] == 1
+        } else {
+            fail "Module did not block"
+        }
+
+        assert_equal 0 [r client unblock $id]
+        assert_equal "OK" [r block.release foobar]
+        assert_equal "foobar" [$rd read]
+    }
 }
diff --git a/tests/unit/multi.tcl b/tests/unit/multi.tcl
index e22b6d43..d33f9451 100644
--- a/tests/unit/multi.tcl
+++ b/tests/unit/multi.tcl
@@ -121,6 +121,22 @@ start_server {tags {"multi"}} {
         r exec
     } {}
 
+    test {EXEC fail on lazy expired WATCHed key} {
+        r flushall
+        r debug set-active-expire 0
+
+        r del key
+        r set key 1 px 2
+        r watch key
+
+        after 100
+
+        r multi
+        r incr key
+        assert_equal [r exec] {}
+        r debug set-active-expire 1
+    } {OK} {needs:debug}
+
     test {After successful EXEC key is no longer watched} {
         r set x 30
         r watch x
diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl
index 442c23de..a3d8f7e8 100644
--- a/tests/unit/protocol.tcl
+++ b/tests/unit/protocol.tcl
@@ -102,6 +102,94 @@ start_server {tags {"protocol network"}} {
         } {*Protocol error*}
     }
     unset c
+
+    # recover the broken connection
+    reconnect
+    r ping
+
+    # raw RESP response tests
+    r readraw 1
+
+    test "raw protocol response" {
+        r srandmember nonexisting_key
+    } {*-1}
+
+    r deferred 1
+
+    test "raw protocol response - deferred" {
+        r srandmember nonexisting_key
+        r read
+    } {*-1}
+
+    test "raw protocol response - multiline" {
+        r sadd ss a
+        assert_equal [r read] {:1}
+        r srandmember ss 100
+        assert_equal [r read] {*1}
+        assert_equal [r read] {$1}
+        assert_equal [r read] {a}
+    }
+
+    # restore connection settings
+    r readraw 0
+    r deferred 0
+
+    # check the connection still works
+    assert_equal [r ping] {PONG}
+
+    test {RESP3 attributes} {
+        r hello 3
+        set res [r debug protocol attrib]
+        # currently the parser in redis.tcl ignores the attributes
+
+        # restore state
+        r hello 2
+        set _ $res
+    } {Some real reply following the attribute}
+
+    test {RESP3 attributes readraw} {
+        r hello 3
+        r readraw 1
+        r deferred 1
+
+        r debug protocol attrib
+        assert_equal [r read] {|1}
+        assert_equal [r read] {$14}
+        assert_equal [r read] {key-popularity}
+        assert_equal [r read] {*2}
+        assert_equal [r read] {$7}
+        assert_equal [r read] {key:123}
+        assert_equal [r read] {:90}
+        assert_equal [r read] {$39}
+        assert_equal [r read] {Some real reply following the attribute}
+
+        # restore state
+        r readraw 0
+        r deferred 0
+        r hello 2
+        set _ {}
+    } {}
+
+    test {RESP3 attributes on RESP2} {
+        r hello 2
+        set res [r debug protocol attrib]
+        set _ $res
+    } {Some real reply following the attribute}
+
+    test "test big number parsing" {
+        r hello 3
+        r debug protocol bignum
+    } {1234567999999999999999999999999999999}
+
+    test "test bool parsing" {
+        r hello 3
+        assert_equal [r debug protocol true] 1
+        assert_equal [r debug protocol false] 0
+        r hello 2
+        assert_equal [r debug protocol true] 1
+        assert_equal [r debug protocol false] 0
+        set _ {}
+    } {}
 }
 
 start_server {tags {"regression"}} {
diff --git a/tests/unit/tracking.tcl b/tests/unit/tracking.tcl
index 4c75b6f4..217a057d 100644
--- a/tests/unit/tracking.tcl
+++ b/tests/unit/tracking.tcl
@@ -132,6 +132,22 @@ start_server {tags {"tracking network"}} {
         assert {$keys eq {mykey}}
     }
 
+    test {Tracking gets notification of lazy expired keys} {
+        r CLIENT TRACKING off
+        r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
+        # Use multi-exec to expose a race where the key gets an two 
invalidations
+        # in the same event loop, once by the client so filtered by NOLOOP, and
+        # the second one by the lazy expire
+        r MULTI
+        r SET mykey{t} myval px 1
+        r SET mykeyotherkey{t} myval ; # We should not get it
+        r DEBUG SLEEP 0.1
+        r GET mykey{t}
+        r EXEC
+        set keys [lsort [lindex [$rd_redirection read] 2]]
+        assert {$keys eq {mykey{t}}}
+    } {} {needs:debug}
+
     test {HELLO 3 reply is correct} {
         set reply [r HELLO 3]
         assert_equal [dict get $reply proto] 3
diff --git a/tests/unit/type/hash.tcl b/tests/unit/type/hash.tcl
index fcf97eed..f2a50372 100644
--- a/tests/unit/type/hash.tcl
+++ b/tests/unit/type/hash.tcl
@@ -72,6 +72,19 @@ start_server {tags {"hash"}} {
         r hrandfield nonexisting_key 100
     } {}
 
+    # Make sure we can distinguish between an empty array and a null response
+    r readraw 1
+
+    test "HRANDFIELD count of 0 is handled correctly - emptyarray" {
+        r hrandfield myhash 0
+    } {*0}
+
+    test "HRANDFIELD with <count> against non existing key - emptyarray" {
+        r hrandfield nonexisting_key 100
+    } {*0}
+
+    r readraw 0
+
     foreach {type contents} "
         hashtable {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} 
{[randstring 70 90 alpha] 10}}
         ziplist {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {10 j}} 
" {
diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl
index 5548ca3a..ee7b936b 100644
--- a/tests/unit/type/set.tcl
+++ b/tests/unit/type/set.tcl
@@ -276,14 +276,86 @@ start_server {
         }
     }
 
-    test "SINTER against non-set should throw error" {
-        r set key1 x
-        assert_error "WRONGTYPE*" {r sinter key1 noset}
+    test "SDIFF against non-set should throw error" {
+        # with an empty set
+        r set key1{t} x
+        assert_error "WRONGTYPE*" {r sdiff key1{t} noset{t}}
+        # different order
+        assert_error "WRONGTYPE*" {r sdiff noset{t} key1{t}}
+
+        # with a legal set
+        r del set1{t}
+        r sadd set1{t} a b c
+        assert_error "WRONGTYPE*" {r sdiff key1{t} set1{t}}
+        # different order
+        assert_error "WRONGTYPE*" {r sdiff set1{t} key1{t}}
     }
 
-    test "SUNION against non-set should throw error" {
-        r set key1 x
-        assert_error "WRONGTYPE*" {r sunion key1 noset}
+    test "SDIFF should handle non existing key as empty" {
+        r del set1{t} set2{t} set3{t}
+
+        r sadd set1{t} a b c
+        r sadd set2{t} b c d
+        assert_equal {a} [lsort [r sdiff set1{t} set2{t} set3{t}]]
+        assert_equal {} [lsort [r sdiff set3{t} set2{t} set1{t}]]
+    }
+
+    test "SDIFFSTORE against non-set should throw error" {
+        r del set1{t} set2{t} set3{t} key1{t}
+        r set key1{t} x
+
+        # with en empty dstkey
+        assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} key1{t} noset{t}}
+        assert_equal 0 [r exists set3{t}]
+        assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} noset{t} key1{t}}
+        assert_equal 0 [r exists set3{t}]
+
+        # with a legal dstkey
+        r sadd set1{t} a b c
+        r sadd set2{t} b c d
+        r sadd set3{t} e
+        assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} key1{t} set1{t} 
noset{t}}
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {e} [lsort [r smembers set3{t}]]
+
+        assert_error "WRONGTYPE*" {r SDIFFSTORE set3{t} set1{t} key1{t} 
set2{t}}
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {e} [lsort [r smembers set3{t}]]
+    }
+
+    test "SDIFFSTORE should handle non existing key as empty" {
+        r del set1{t} set2{t} set3{t}
+
+        r set setres{t} xxx
+        assert_equal 0 [r sdiffstore setres{t} foo111{t} bar222{t}]
+        assert_equal 0 [r exists setres{t}]
+
+        # with a legal dstkey, should delete dstkey
+        r sadd set3{t} a b c
+        assert_equal 0 [r sdiffstore set3{t} set1{t} set2{t}]
+        assert_equal 0 [r exists set3{t}]
+
+        r sadd set1{t} a b c
+        assert_equal 3 [r sdiffstore set3{t} set1{t} set2{t}]
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {a b c} [lsort [r smembers set3{t}]]
+
+        # with a legal dstkey and empty set2, should delete the dstkey
+        r sadd set3{t} a b c
+        assert_equal 0 [r sdiffstore set3{t} set2{t} set1{t}]
+        assert_equal 0 [r exists set3{t}]
+    }
+
+    test "SINTER against non-set should throw error" {
+        r set key1{t} x
+        assert_error "WRONGTYPE*" {r sinter key1{t} noset{t}}
+        # different order
+        assert_error "WRONGTYPE*" {r sinter noset{t} key1{t}}
+
+        r sadd set1{t} a b c
+        assert_error "WRONGTYPE*" {r sinter key1{t} set1{t}}
+        # different order
+        assert_error "WRONGTYPE*" {r sinter set1{t} key1{t}}
     }
 
     test "SINTER should handle non existing key as empty" {
@@ -303,10 +375,115 @@ start_server {
         lsort [r sinter set1 set2]
     } {1 2 3}
 
+    test "SINTERSTORE against non-set should throw error" {
+        r del set1{t} set2{t} set3{t} key1{t}
+        r set key1{t} x
+
+        # with en empty dstkey
+        assert_error "WRONGTYPE*" {r sinterstore set3{t} key1{t} noset{t}}
+        assert_equal 0 [r exists set3{t}]
+        assert_error "WRONGTYPE*" {r sinterstore set3{t} noset{t} key1{t}}
+        assert_equal 0 [r exists set3{t}]
+
+        # with a legal dstkey
+        r sadd set1{t} a b c
+        r sadd set2{t} b c d
+        r sadd set3{t} e
+        assert_error "WRONGTYPE*" {r sinterstore set3{t} key1{t} set2{t} 
noset{t}}
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {e} [lsort [r smembers set3{t}]]
+
+        assert_error "WRONGTYPE*" {r sinterstore set3{t} noset{t} key1{t} 
set2{t}}
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {e} [lsort [r smembers set3{t}]]
+    }
+
     test "SINTERSTORE against non existing keys should delete dstkey" {
-        r set setres xxx
-        assert_equal 0 [r sinterstore setres foo111 bar222]
-        assert_equal 0 [r exists setres]
+        r del set1{t} set2{t} set3{t}
+
+        r set setres{t} xxx
+        assert_equal 0 [r sinterstore setres{t} foo111{t} bar222{t}]
+        assert_equal 0 [r exists setres{t}]
+
+        # with a legal dstkey
+        r sadd set3{t} a b c
+        assert_equal 0 [r sinterstore set3{t} set1{t} set2{t}]
+        assert_equal 0 [r exists set3{t}]
+
+        r sadd set1{t} a b c
+        assert_equal 0 [r sinterstore set3{t} set1{t} set2{t}]
+        assert_equal 0 [r exists set3{t}]
+
+        assert_equal 0 [r sinterstore set3{t} set2{t} set1{t}]
+        assert_equal 0 [r exists set3{t}]
+    }
+
+    test "SUNION against non-set should throw error" {
+        r set key1{t} x
+        assert_error "WRONGTYPE*" {r sunion key1{t} noset{t}}
+        # different order
+        assert_error "WRONGTYPE*" {r sunion noset{t} key1{t}}
+
+        r del set1{t}
+        r sadd set1{t} a b c
+        assert_error "WRONGTYPE*" {r sunion key1{t} set1{t}}
+        # different order
+        assert_error "WRONGTYPE*" {r sunion set1{t} key1{t}}
+    }
+
+    test "SUNION should handle non existing key as empty" {
+        r del set1{t} set2{t} set3{t}
+
+        r sadd set1{t} a b c
+        r sadd set2{t} b c d
+        assert_equal {a b c d} [lsort [r sunion set1{t} set2{t} set3{t}]]
+    }
+
+    test "SUNIONSTORE against non-set should throw error" {
+        r del set1{t} set2{t} set3{t} key1{t}
+        r set key1{t} x
+
+        # with en empty dstkey
+        assert_error "WRONGTYPE*" {r sunionstore set3{t} key1{t} noset{t}}
+        assert_equal 0 [r exists set3{t}]
+        assert_error "WRONGTYPE*" {r sunionstore set3{t} noset{t} key1{t}}
+        assert_equal 0 [r exists set3{t}]
+
+        # with a legal dstkey
+        r sadd set1{t} a b c
+        r sadd set2{t} b c d
+        r sadd set3{t} e
+        assert_error "WRONGTYPE*" {r sunionstore set3{t} key1{t} key2{t} 
noset{t}}
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {e} [lsort [r smembers set3{t}]]
+
+        assert_error "WRONGTYPE*" {r sunionstore set3{t} noset{t} key1{t} 
key2{t}}
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {e} [lsort [r smembers set3{t}]]
+    }
+
+    test "SUNIONSTORE should handle non existing key as empty" {
+        r del set1{t} set2{t} set3{t}
+
+        r set setres{t} xxx
+        assert_equal 0 [r sunionstore setres{t} foo111{t} bar222{t}]
+        assert_equal 0 [r exists setres{t}]
+
+        # set1 set2 both empty, should delete the dstkey
+        r sadd set3{t} a b c
+        assert_equal 0 [r sunionstore set3{t} set1{t} set2{t}]
+        assert_equal 0 [r exists set3{t}]
+
+        r sadd set1{t} a b c
+        r sadd set3{t} e f
+        assert_equal 3 [r sunionstore set3{t} set1{t} set2{t}]
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {a b c} [lsort [r smembers set3{t}]]
+
+        r sadd set3{t} d
+        assert_equal 3 [r sunionstore set3{t} set2{t} set1{t}]
+        assert_equal 1 [r exists set3{t}]
+        assert_equal {a b c} [lsort [r smembers set3{t}]]
     }
 
     test "SUNIONSTORE against non existing keys should delete dstkey" {
@@ -403,10 +580,27 @@ start_server {
         assert {[lsort $union] eq [lsort $content]}
     }
 
+    test "SRANDMEMBER count of 0 is handled correctly" {
+        r srandmember myset 0
+    } {}
+
     test "SRANDMEMBER with <count> against non existing key" {
         r srandmember nonexisting_key 100
     } {}
 
+    # Make sure we can distinguish between an empty array and a null response
+    r readraw 1
+
+    test "SRANDMEMBER count of 0 is handled correctly - emptyarray" {
+        r srandmember myset 0
+    } {*0}
+
+    test "SRANDMEMBER with <count> against non existing key - emptyarray" {
+        r srandmember nonexisting_key 100
+    } {*0}
+
+    r readraw 0
+
     foreach {type contents} {
         hashtable {
             1 5 10 50 125 50000 33959417 4775547 65434162
@@ -632,6 +826,28 @@ start_server {
         lsort [r smembers set]
     } {a b c}
 
+    test "SMOVE only notify dstset when the addition is successful" {
+        r del srcset{t}
+        r del dstset{t}
+
+        r sadd srcset{t} a b
+        r sadd dstset{t} a
+
+        r watch dstset{t}
+
+        r multi
+        r sadd dstset{t} c
+
+        set r2 [redis_client]
+        $r2 smove srcset{t} dstset{t} a
+
+        # The dstset is actually unchanged, multi should success
+        r exec
+        set res [r scard dstset{t}]
+        assert_equal $res 2
+        $r2 close
+    }
+
     tags {slow} {
         test {intsets implementation stress testing} {
             for {set j 0} {$j < 20} {incr j} {
diff --git a/tests/unit/type/stream.tcl b/tests/unit/type/stream.tcl
index a89a6529..f1ee56a8 100644
--- a/tests/unit/type/stream.tcl
+++ b/tests/unit/type/stream.tcl
@@ -199,6 +199,15 @@ start_server {
         assert {[r EXISTS otherstream] == 0}
     }
 
+    test {XADD with LIMIT delete entries no more than limit} {
+        r del yourstream
+        for {set j 0} {$j < 3} {incr j} {
+            r XADD yourstream * xitem v
+        }
+        r XADD yourstream MAXLEN ~ 0 limit 1 * xitem v
+        assert {[r XLEN yourstream] == 4}
+    }
+
     test {XRANGE COUNT works as expected} {
         assert {[llength [r xrange mystream - + COUNT 10]] == 10}
     }
@@ -525,6 +534,16 @@ start_server {
         }
         assert_error ERR* {r XTRIM mystream MAXLEN 1 LIMIT 30}
     }
+
+    test {XTRIM with LIMIT delete entries no more than limit} {
+        r del mystream
+        r config set stream-node-max-entries 2
+        for {set j 0} {$j < 3} {incr j} {
+            r XADD mystream * xitem v
+        }
+        assert {[r XTRIM mystream MAXLEN ~ 0 LIMIT 1] == 0}
+        assert {[r XTRIM mystream MAXLEN ~ 0 LIMIT 2] == 2}
+    }
 }
 
 start_server {tags {"stream"} overrides {appendonly yes}} {
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 96647f77..94b2ab48 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -960,6 +960,39 @@ start_server {tags {"zset"}} {
             assert_equal 1 [r zcard z2]
         }
 
+        test "Basic ZPOP - $encoding RESP3" {
+            r hello 3
+            r del z1
+            create_zset z1 {0 a 1 b 2 c 3 d}
+            assert_equal {a 0.0} [r zpopmin z1]
+            assert_equal {d 3.0} [r zpopmax z1]
+            r hello 2
+        }
+
+        test "ZPOP with count - $encoding RESP3" {
+            r hello 3
+            r del z1
+            create_zset z1 {0 a 1 b 2 c 3 d}
+            assert_equal {{a 0.0} {b 1.0}} [r zpopmin z1 2]
+            assert_equal {{d 3.0} {c 2.0}} [r zpopmax z1 2]
+            r hello 2
+        }
+
+        test "BZPOP - $encoding RESP3" {
+            r hello 3
+            set rd [redis_deferring_client]
+            create_zset zset {0 a 1 b 2 c}
+
+            $rd bzpopmin zset 5
+            assert_equal {zset a 0} [$rd read]
+            $rd bzpopmin zset 5
+            assert_equal {zset b 1} [$rd read]
+            $rd bzpopmax zset 5
+            assert_equal {zset c 2} [$rd read]
+            assert_equal 0 [r exists zset]
+            r hello 2
+        }
+
         r config set zset-max-ziplist-entries $original_max_entries
         r config set zset-max-ziplist-value $original_max_value
     }
@@ -1568,6 +1601,19 @@ start_server {tags {"zset"}} {
         r zrange z1 5 0 BYSCORE REV LIMIT 0 2 WITHSCORES
     } {d 4 c 3}
 
+    test {ZRANGESTORE - src key missing} {
+        set res [r zrangestore z2{t} missing{t} 0 -1]
+        assert_equal $res 0
+        r exists z2{t}
+    } {0}
+
+    test {ZRANGESTORE - src key wrong type} {
+        r zadd z2{t} 1 a
+        r set foo{t} bar
+        assert_error "*WRONGTYPE*" {r zrangestore z2{t} foo{t} 0 -1}
+        r zrange z2{t} 0 -1
+    } {a}
+
     test {ZRANGESTORE - empty range} {
         set res [r zrangestore z2 z1 5 6]
         assert_equal $res 0
@@ -1616,6 +1662,20 @@ start_server {tags {"zset"}} {
         return $res
     }
 
+    # Check whether the zset members belong to the zset
+    proc check_member {mydict res} {
+        foreach ele $res {
+            assert {[dict exists $mydict $ele]}
+        }
+    }
+
+    # Check whether the zset members and score belong to the zset
+    proc check_member_and_score {mydict res} {
+       foreach {key val} $res {
+            assert_equal $val [dict get $mydict $key]
+        }
+    }
+
     foreach {type contents} "ziplist {1 a 2 b 3 c} skiplist {1 a 2 b 3 
[randstring 70 90 alpha]}" {
         set original_max_value [lindex [r config get zset-max-ziplist-value] 1]
         r config set zset-max-ziplist-value 10
@@ -1654,6 +1714,19 @@ start_server {tags {"zset"}} {
         r zrandmember nonexisting_key 100
     } {}
 
+    # Make sure we can distinguish between an empty array and a null response
+    r readraw 1
+
+    test "ZRANDMEMBER count of 0 is handled correctly - emptyarray" {
+        r zrandmember myzset 0
+    } {*0}
+
+    test "ZRANDMEMBER with <count> against non existing key - emptyarray" {
+        r zrandmember nonexisting_key 100
+    } {*0}
+
+    r readraw 0
+
     foreach {type contents} "
         skiplist {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 [randstring 70 90 
alpha]}
         ziplist {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 j} " {
@@ -1676,25 +1749,29 @@ start_server {tags {"zset"}} {
             # PATH 1: Use negative count.
 
             # 1) Check that it returns repeated elements with and without 
values.
+            # 2) Check that all the elements actually belong to the original 
zset.
             set res [r zrandmember myzset -20]
             assert_equal [llength $res] 20
+            check_member $mydict $res
+
             set res [r zrandmember myzset -1001]
             assert_equal [llength $res] 1001
+            check_member $mydict $res
+
             # again with WITHSCORES
             set res [r zrandmember myzset -20 withscores]
             assert_equal [llength $res] 40
+            check_member_and_score $mydict $res
+
             set res [r zrandmember myzset -1001 withscores]
             assert_equal [llength $res] 2002
+            check_member_and_score $mydict $res
 
             # Test random uniform distribution
             # df = 9, 40 means 0.00001 probability
             set res [r zrandmember myzset -1000]
             assert_lessthan [chi_square_value $res] 40
-
-            # 2) Check that all the elements actually belong to the original 
zset.
-            foreach {key val} $res {
-                assert {[dict exists $mydict $key]}
-            }
+            check_member $mydict $res
 
             # 3) Check that eventually all the elements are returned.
             #    Use both WITHSCORES and without
@@ -1710,7 +1787,7 @@ start_server {tags {"zset"}} {
                 } else {
                     set res [r zrandmember myzset -3]
                     foreach key $res {
-                        dict append auxset $key $val
+                        dict append auxset $key
                     }
                 }
                 if {[lsort [dict keys $mydict]] eq
@@ -1726,11 +1803,13 @@ start_server {tags {"zset"}} {
                 set res [r zrandmember myzset $size]
                 assert_equal [llength $res] 10
                 assert_equal [lsort $res] [lsort [dict keys $mydict]]
+                check_member $mydict $res
 
                 # again with WITHSCORES
                 set res [r zrandmember myzset $size withscores]
                 assert_equal [llength $res] 20
                 assert_equal [lsort $res] [lsort $mydict]
+                check_member_and_score $mydict $res
             }
 
             # PATH 3: Ask almost as elements as there are in the set.
@@ -1742,18 +1821,17 @@ start_server {tags {"zset"}} {
             #
             # We can test both the code paths just changing the size but
             # using the same code.
-            foreach size {8 2} {
+            foreach size {1 2 8} {
+                # 1) Check that all the elements actually belong to the
+                # original set.
                 set res [r zrandmember myzset $size]
                 assert_equal [llength $res] $size
+                check_member $mydict $res
+
                 # again with WITHSCORES
                 set res [r zrandmember myzset $size withscores]
                 assert_equal [llength $res] [expr {$size * 2}]
-
-                # 1) Check that all the elements actually belong to the
-                # original set.
-                foreach ele [dict keys $res] {
-                    assert {[dict exists $mydict $ele]}
-                }
+                check_member_and_score $mydict $res
 
                 # 2) Check that eventually all the elements are returned.
                 #    Use both WITHSCORES and without

Reply via email to