From 30a92b6c76e81d2ad2ecf5d6adb39909b560c565 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 09:46:20 +0100 Subject: [PATCH 1/9] Fix misaligned word access in redisPopcount(). --- src/bitops.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/bitops.c b/src/bitops.c index c96a9e3c..bfeccfb9 100644 --- a/src/bitops.c +++ b/src/bitops.c @@ -60,11 +60,18 @@ static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) { * work with a input string length up to 512 MB. */ size_t redisPopcount(void *s, long count) { size_t bits = 0; - unsigned char *p; - uint32_t *p4 = s; + 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}; + /* Count initial bytes not aligned to 32 bit. */ + while((unsigned long)p & 3 && count) { + bits += bitsinbyte[*p++]; + count--; + } + /* Count bits 16 bytes at a time */ + p4 = (uint32_t*)p; while(count>=16) { uint32_t aux1, aux2, aux3, aux4; From a3eb3f9c3b1215e2f3a00f591547f91a0880b79f Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 10:00:17 +0100 Subject: [PATCH 2/9] BITCOUNT fuzzy test with random start/end added. It was verified in practice that this test is able to stress much more the implementation by introducing errors that were only trivially to detect with different offsets but impossible to detect starting always at zero and counting bits the full length of the string. --- tests/unit/bitops.tcl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl index dade8923..a145199a 100644 --- a/tests/unit/bitops.tcl +++ b/tests/unit/bitops.tcl @@ -52,7 +52,7 @@ start_server {tags {"bitops"}} { } } - test {BITCOUNT fuzzing} { + test {BITCOUNT fuzzing without start/end} { for {set j 0} {$j < 100} {incr j} { set str [randstring 0 3000] r set str $str @@ -60,6 +60,20 @@ start_server {tags {"bitops"}} { } } + test {BITCOUNT fuzzing with start/end} { + for {set j 0} {$j < 100} {incr j} { + set str [randstring 0 3000] + r set str $str + set l [string length $str] + set start [randomInt $l] + set end [randomInt $l] + if {$start > $end} { + lassign [list $end $start] start end + } + assert {[r bitcount str $start $end] == [count_bits [string range $str $start $end]]} + } + } + test {BITCOUNT with start, end} { r set s "foobar" assert_equal [r bitcount s 0 -1] [count_bits "foobar"] From 82d2e295b85a74f945f01ccd43eea4f12ed379c7 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 10:07:29 +0100 Subject: [PATCH 3/9] Added two more BITCOUNT tests stressing misaligned access. --- tests/unit/bitops.tcl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl index a145199a..e7f289f9 100644 --- a/tests/unit/bitops.tcl +++ b/tests/unit/bitops.tcl @@ -98,6 +98,18 @@ start_server {tags {"bitops"}} { } } {1} + test {BITCOUNT misaligned prefix} { + r del str + r set str ab + r bitcount str 1 -1 + } {3} + + test {BITCOUNT misaligned prefix + full words + remainder} { + r del str + r set str __PPxxxxxxxxxxxxxxxxRR__ + r bitcount str 2 -3 + } {74} + test {BITOP NOT (empty string)} { r set s "" r bitop not dest s From 3294f74fefb0f1056570af8803c6f90c07a15700 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 12:40:58 +0100 Subject: [PATCH 4/9] Initial implementation of BITPOS. It appears to work but more stress testing, and both unit tests and fuzzy testing, is needed in order to ensure the implementation is sane. --- src/bitops.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/redis.c | 3 +- src/redis.h | 1 + 3 files changed, 172 insertions(+), 2 deletions(-) diff --git a/src/bitops.c b/src/bitops.c index bfeccfb9..260de26e 100644 --- a/src/bitops.c +++ b/src/bitops.c @@ -94,12 +94,99 @@ size_t redisPopcount(void *s, long count) { ((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) + ((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24); } - /* Count the remaining bytes */ + /* Count the remaining bytes. */ p = (unsigned char*)p4; while(count--) bits += bitsinbyte[*p++]; return bits; } +/* Return the position of the first bit set to one (if 'bit' is 1) or + * zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes. + * + * The function is guaranteed to return a value >= 0 if 'bit' is 0 since if + * 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, 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. */ + int j; + + /* Process whole words first, seeking for first word that is not + * all ones or all zeros respectively if we are lookig for zeros + * or ones. This is much faster with large strings having contiguous + * blocks of 1 or 0 bits compared to the vanilla bit per bit processing. + * + * Note that if we start from an address that is not aligned + * to sizeof(unsigned long) we consume it byte by byte until it is + * aligned. */ + + /* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */ + skipval = bit ? 0 : UCHAR_MAX; + c = (unsigned char*) s; + while((unsigned long)c & (sizeof(*l)-1) && count) { + if (*c != skipval) break; + c++; + count--; + pos += 8; + } + + /* Skip bits with full word step. */ + skipval = bit ? 0 : ULONG_MAX; + l = (unsigned long*) c; + while (count >= sizeof(*l)) { + if (*l != skipval) break; + l++; + count -= sizeof(*l); + pos += sizeof(*l)*8; + } + + /* Load bytes into "word" considering the first byte as the most significant + * (we basically consider it as written in big endian, since we consider the + * string as a set of bits from left to right, with the first bit at position + * zero. + * + * Note that the loading is designed to work even when the bytes left + * (count) are less than a full word. We pad it with zero on the right. */ + c = (unsigned char*)l; + for (j = 0; j < sizeof(*l); j++) { + word <<= 8; + if (count) { + word |= *c; + c++; + count--; + } + } + + /* Special case: + * If bits in the string are all zero and we are looking for one, + * return -1 to signal that there is not a single "1" in the whole + * string. This can't happen when we are looking for "0" as we assume + * that the right of the string is zero padded. */ + if (bit == 1 && word == 0) return -1; + + /* Last word left, scan bit by bit. The first thing we need is to + * have a single "1" set in the most significant position in an + * unsigned long. We don't know the size of the long so we use a + * simple trick. */ + one = ULONG_MAX; /* All bits set to 1.*/ + one >>= 1; /* All bits set to 1 but the MSB. */ + one = ~one; /* All bits set to 0 but the MSB. */ + + while(one) { + if (((one & word) != 0) == bit) return pos; + pos++; + one >>= 1; + } + + /* If we reached this point, there is a bug in the algorithm, since + * the case of no match is handled as a special case before. */ + redisPanic("End of redisBitpos() reached."); + return 0; /* Just to avoid warnigns. */ +} + /* ----------------------------------------------------------------------------- * Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP. * -------------------------------------------------------------------------- */ @@ -417,3 +504,84 @@ void bitcountCommand(redisClient *c) { addReplyLongLong(c,redisPopcount(p+start,bytes)); } } + +/* BITPOS key bit [start end] */ +void bitposCommand(redisClient *c) { + robj *o; + long bit, start, end, strlen; + unsigned char *p; + char llbuf[32]; + + /* Parse the bit argument to understand what we are looking for, set + * or clear bits. */ + if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK) + return; + if (bit != 0 && bit != 1) { + addReplyError(c, "The bit argument must be 1 or 0."); + return; + } + + /* If the key does not exist, from our point of view it is an infinite + * array of 0 bits. If the user is looking for the fist clear bit return 0, + * If the user is looking for the first set bit, return -1. */ + if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { + addReplyLongLong(c, bit ? -1 : 0); + return; + } + if (checkType(c,o,REDIS_STRING)) return; + + /* Set the 'p' pointer to the string, that can be just a stack allocated + * array if our string was integer encoded. */ + if (o->encoding == REDIS_ENCODING_INT) { + p = (unsigned char*) llbuf; + strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); + } else { + p = (unsigned char*) o->ptr; + strlen = sdslen(o->ptr); + } + + /* Parse start/end range if any. */ + if (c->argc == 5) { + if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK) + return; + if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK) + return; + /* Convert negative indexes */ + if (start < 0) start = strlen+start; + if (end < 0) end = strlen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; + if (end >= strlen) end = strlen-1; + } else if (c->argc == 3) { + /* The whole string. */ + start = 0; + end = strlen-1; + } else { + /* Syntax error. */ + addReply(c,shared.syntaxerr); + return; + } + + /* For empty ranges (start > end) we return -1 as an empty range does + * not contain a 0 nor a 1. */ + if (start > end) { + addReplyLongLong(c, -1); + } else { + long bytes = end-start+1; + long pos = redisBitpos(p+start,bytes,bit); + + /* If we are looking for clear bits, and our range does not includes + * the end of the string, but terminates before, we can't consider the + * right of the range as zero padded. + * + * so if redisBitpos() returns the first bit outside the string, + * we return -1 to the caller, to mean, in the specified range there + * is not a single "0" bit. */ + if (end != strlen-1 && bit == 0 && pos == bytes*8) { + addReplyLongLong(c,-1); + return; + } + if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */ + addReplyLongLong(c,pos); + } +} diff --git a/src/redis.c b/src/redis.c index d8a1e5ea..e214032a 100644 --- a/src/redis.c +++ b/src/redis.c @@ -257,7 +257,8 @@ struct redisCommand redisCommandTable[] = { {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0}, {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0}, {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0}, - {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0} + {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}, + {"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0} }; /*============================ Utility functions ============================ */ diff --git a/src/redis.h b/src/redis.h index 0c8487f1..3dbdcd68 100644 --- a/src/redis.h +++ b/src/redis.h @@ -1346,6 +1346,7 @@ void scriptCommand(redisClient *c); void timeCommand(redisClient *c); void bitopCommand(redisClient *c); void bitcountCommand(redisClient *c); +void bitposCommand(redisClient *c); void replconfCommand(redisClient *c); #if defined(__GNUC__) From 2c8036f7b28aaf352a0cee301ac74499d6e3663b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 12:53:03 +0100 Subject: [PATCH 5/9] More consistent BITPOS behavior with bit=0 and ranges. With the new behavior it is possible to specify just the start in the range (the end will be assumed to be the first byte), or it is possible to specify both start and end. This is useful to change the behavior of the command when looking for zeros inside a string. 1) If the user specifies both start and end, and no 0 is found inside the range, the command returns -1. 2) If instead no range is specified, or just the start is given, even if in the actual string no 0 bit is found, the command returns the first bit on the right after the end of the string. So for example if the string stored at key foo is "\xff\xff": BITPOS foo (returns 16) BITPOS foo 0 -1 (returns -1) BITPOS foo 0 (returns 16) The idea is that when no end is given the user is just looking for the first bit that is zero and can be set to 1 with SETBIT, as it is "available". Instead when a specific range is given, we just look for a zero within the boundaries of the range. --- src/bitops.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/bitops.c b/src/bitops.c index 260de26e..7aff6b76 100644 --- a/src/bitops.c +++ b/src/bitops.c @@ -505,12 +505,13 @@ void bitcountCommand(redisClient *c) { } } -/* BITPOS key bit [start end] */ +/* BITPOS key bit [start [end]] */ void bitposCommand(redisClient *c) { robj *o; long bit, start, end, strlen; unsigned char *p; char llbuf[32]; + int end_given = 0; /* Parse the bit argument to understand what we are looking for, set * or clear bits. */ @@ -541,11 +542,16 @@ void bitposCommand(redisClient *c) { } /* Parse start/end range if any. */ - if (c->argc == 5) { + if (c->argc == 4 || c->argc == 5) { if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK) return; - if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK) - return; + if (c->argc == 5) { + if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK) + return; + end_given = 1; + } else { + end = strlen-1; + } /* Convert negative indexes */ if (start < 0) start = strlen+start; if (end < 0) end = strlen+end; @@ -570,14 +576,14 @@ void bitposCommand(redisClient *c) { long bytes = end-start+1; long pos = redisBitpos(p+start,bytes,bit); - /* If we are looking for clear bits, and our range does not includes - * the end of the string, but terminates before, we can't consider the - * right of the range as zero padded. + /* 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 + * zero padded (as we do when no explicit end is given). * - * so if redisBitpos() returns the first bit outside the string, + * 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 != strlen-1 && bit == 0 && pos == bytes*8) { + if (end_given && bit == 0 && pos == bytes*8) { addReplyLongLong(c,-1); return; } From 1892b562241d858b3d1edca0790ed6068c9fc068 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 13:17:23 +0100 Subject: [PATCH 6/9] warnigns -> warnings in redisBitpos(). --- src/bitops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitops.c b/src/bitops.c index 7aff6b76..af46b63c 100644 --- a/src/bitops.c +++ b/src/bitops.c @@ -184,7 +184,7 @@ long redisBitpos(void *s, long count, int bit) { /* If we reached this point, there is a bug in the algorithm, since * the case of no match is handled as a special case before. */ redisPanic("End of redisBitpos() reached."); - return 0; /* Just to avoid warnigns. */ + return 0; /* Just to avoid warnings. */ } /* ----------------------------------------------------------------------------- From 42e3630d77c63336968cafbf72d0612cfa035ab5 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 15:01:45 +0100 Subject: [PATCH 7/9] Basic BITPOS tests. --- tests/unit/bitops.tcl | 102 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl index e7f289f9..a32ce26c 100644 --- a/tests/unit/bitops.tcl +++ b/tests/unit/bitops.tcl @@ -203,4 +203,106 @@ start_server {tags {"bitops"}} { r set a "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" r bitop or x a b } {32} + + test {BITPOS bit=0 with empty key returns 0} { + r del str + r bitpos str 0 + } {0} + + test {BITPOS bit=1 with empty key returns -1} { + r del str + r bitpos str 1 + } {-1} + + test {BITPOS bit=0 with string less than 1 word works} { + r set str "\xff\xf0\x00" + r bitpos str 0 + } {12} + + test {BITPOS bit=1 with string less than 1 word works} { + r set str "\x00\x0f\x00" + r bitpos str 1 + } {12} + + test {BITPOS bit=0 starting at unaligned address} { + r set str "\xff\xf0\x00" + r bitpos str 0 1 + } {12} + + test {BITPOS bit=1 starting at unaligned address} { + r set str "\x00\x0f\xff" + r bitpos str 1 1 + } {12} + + test {BITPOS bit=0 unaligned+full word+reminder} { + r del str + r set str "\xff\xff\xff" ; # Prefix + # Followed by two (or four in 32 bit systems) full words + r append str "\xff\xff\xff\xff\xff\xff\xff\xff" + r append str "\xff\xff\xff\xff\xff\xff\xff\xff" + r append str "\xff\xff\xff\xff\xff\xff\xff\xff" + # First zero bit. + r append str "\x0f" + assert {[r bitpos str 0] == 216} + assert {[r bitpos str 0 1] == 216} + assert {[r bitpos str 0 2] == 216} + assert {[r bitpos str 0 3] == 216} + assert {[r bitpos str 0 4] == 216} + assert {[r bitpos str 0 5] == 216} + assert {[r bitpos str 0 6] == 216} + assert {[r bitpos str 0 7] == 216} + assert {[r bitpos str 0 8] == 216} + } + + test {BITPOS bit=1 unaligned+full word+reminder} { + r del str + r set str "\x00\x00\x00" ; # Prefix + # Followed by two (or four in 32 bit systems) full words + r append str "\x00\x00\x00\x00\x00\x00\x00\x00" + r append str "\x00\x00\x00\x00\x00\x00\x00\x00" + r append str "\x00\x00\x00\x00\x00\x00\x00\x00" + # First zero bit. + r append str "\xf0" + assert {[r bitpos str 1] == 216} + assert {[r bitpos str 1 1] == 216} + assert {[r bitpos str 1 2] == 216} + assert {[r bitpos str 1 3] == 216} + assert {[r bitpos str 1 4] == 216} + assert {[r bitpos str 1 5] == 216} + assert {[r bitpos str 1 6] == 216} + assert {[r bitpos str 1 7] == 216} + assert {[r bitpos str 1 8] == 216} + } + + test {BITPOS bit=1 returns -1 if string is all 0 bits} { + r set str "" + for {set j 0} {$j < 20} {incr j} { + assert {[r bitpos str 1] == -1} + r append str "\x00" + } + } + + test {BITPOS bit=0 works with intervals} { + r set str "\x00\xff\x00" + assert {[r bitpos str 0 0 -1] == 0} + assert {[r bitpos str 0 1 -1] == 16} + assert {[r bitpos str 0 2 -1] == 16} + assert {[r bitpos str 0 2 200] == 16} + assert {[r bitpos str 0 1 1] == -1} + } + + test {BITPOS bit=1 works with intervals} { + r set str "\x00\xff\x00" + assert {[r bitpos str 1 0 -1] == 8} + assert {[r bitpos str 1 1 -1] == 8} + assert {[r bitpos str 1 2 -1] == -1} + assert {[r bitpos str 1 2 200] == -1} + assert {[r bitpos str 1 1 1] == 8} + } + + test {BITPOS bit=0 changes behavior if end is given} { + r set str "\xff\xff\xff" + assert {[r bitpos str 0] == 24} + assert {[r bitpos str 0 0 -1] == -1} + } } From 950cb76e7b9d6daa6aaf970eb45837dafad4025a Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Feb 2014 15:27:05 +0100 Subject: [PATCH 8/9] BITPOS fuzzy testing. --- tests/unit/bitops.tcl | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl index a32ce26c..89631098 100644 --- a/tests/unit/bitops.tcl +++ b/tests/unit/bitops.tcl @@ -303,6 +303,39 @@ start_server {tags {"bitops"}} { test {BITPOS bit=0 changes behavior if end is given} { r set str "\xff\xff\xff" assert {[r bitpos str 0] == 24} + assert {[r bitpos str 0 0] == 24} assert {[r bitpos str 0 0 -1] == -1} } + + test {BITPOS bit=1 fuzzy testing using SETBIT} { + r del str + set max 524288; # 64k + set first_one_pos -1 + for {set j 0} {$j < 1000} {incr j} { + assert {[r bitpos str 1] == $first_one_pos} + set pos [randomInt $max] + r setbit str $pos 1 + if {$first_one_pos == -1 || $first_one_pos > $pos} { + # Update the position of the first 1 bit in the array + # if the bit we set is on the left of the previous one. + set first_one_pos $pos + } + } + } + + test {BITPOS bit=0 fuzzy testing using SETBIT} { + set max 524288; # 64k + set first_zero_pos $max + r set str [string repeat "\xff" [expr $max/8]] + for {set j 0} {$j < 1000} {incr j} { + assert {[r bitpos str 0] == $first_zero_pos} + set pos [randomInt $max] + r setbit str $pos 0 + if {$first_zero_pos > $pos} { + # Update the position of the first 0 bit in the array + # if the bit we clear is on the left of the previous one. + set first_zero_pos $pos + } + } + } } From a0335e18ba84d80410570ea76aff5faf70161d9a Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 28 Feb 2014 16:00:00 +0100 Subject: [PATCH 9/9] Sentinel test: Makefile target added. --- runtest-sentinel | 14 ++++++++++++++ src/Makefile | 3 +++ 2 files changed, 17 insertions(+) create mode 100755 runtest-sentinel diff --git a/runtest-sentinel b/runtest-sentinel new file mode 100755 index 00000000..1650eea7 --- /dev/null +++ b/runtest-sentinel @@ -0,0 +1,14 @@ +#!/bin/sh +TCL_VERSIONS="8.5 8.6" +TCLSH="" + +for VERSION in $TCL_VERSIONS; do + TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL +done + +if [ -z $TCLSH ] +then + echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test" + exit 1 +fi +$TCLSH tests/sentinel.tcl $* diff --git a/src/Makefile b/src/Makefile index a8631e02..89f607c3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -204,6 +204,9 @@ distclean: clean test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) @(cd ..; ./runtest) +test-sentinel: $(REDIS_SENTINEL_NAME) + @(cd ..; ./runtest-sentinel) + check: test lcov: