Commit 9e67df2a authored by Oran Agra's avatar Oran Agra
Browse files

rebase from unstable

parents f472bb10 bcb4d091
...@@ -209,7 +209,7 @@ void sortCommand(redisClient *c) { ...@@ -209,7 +209,7 @@ void sortCommand(redisClient *c) {
} }
/* Create a list of operations to perform for every sorted element. /* Create a list of operations to perform for every sorted element.
* Operations can be GET/DEL/INCR/DECR */ * Operations can be GET */
operations = listCreate(); operations = listCreate();
listSetFreeMethod(operations,zfree); listSetFreeMethod(operations,zfree);
j = 2; /* options start at argv[2] */ j = 2; /* options start at argv[2] */
......
...@@ -144,7 +144,11 @@ void setTypeReleaseIterator(setTypeIterator *si) { ...@@ -144,7 +144,11 @@ void setTypeReleaseIterator(setTypeIterator *si) {
* Since set elements can be internally be stored as redis objects or * Since set elements can be internally be stored as redis objects or
* simple arrays of integers, setTypeNext returns the encoding of the * simple arrays of integers, setTypeNext returns the encoding of the
* set object you are iterating, and will populate the appropriate pointer * set object you are iterating, and will populate the appropriate pointer
* (eobj) or (llobj) accordingly. * (objele) or (llele) accordingly.
*
* Note that both the objele and llele pointers should be passed and cannot
* be NULL since the function will try to defensively populate the non
* used field with values which are easy to trap if misused.
* *
* When there are no longer elements -1 is returned. * When there are no longer elements -1 is returned.
* Returned objects ref count is not incremented, so this function is * Returned objects ref count is not incremented, so this function is
...@@ -154,9 +158,13 @@ int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) { ...@@ -154,9 +158,13 @@ int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
dictEntry *de = dictNext(si->di); dictEntry *de = dictNext(si->di);
if (de == NULL) return -1; if (de == NULL) return -1;
*objele = dictGetKey(de); *objele = dictGetKey(de);
*llele = -123456789; /* Not needed. Defensive. */
} else if (si->encoding == REDIS_ENCODING_INTSET) { } else if (si->encoding == REDIS_ENCODING_INTSET) {
if (!intsetGet(si->subject->ptr,si->ii++,llele)) if (!intsetGet(si->subject->ptr,si->ii++,llele))
return -1; return -1;
*objele = NULL; /* Not needed. Defensive. */
} else {
redisPanic("Wrong set encoding in setTypeNext");
} }
return si->encoding; return si->encoding;
} }
...@@ -197,6 +205,10 @@ robj *setTypeNextObject(setTypeIterator *si) { ...@@ -197,6 +205,10 @@ robj *setTypeNextObject(setTypeIterator *si) {
* field of the object and is used by the caller to check if the * field of the object and is used by the caller to check if the
* int64_t pointer or the redis object pointer was populated. * int64_t pointer or the redis object pointer was populated.
* *
* Note that both the objele and llele pointers should be passed and cannot
* be NULL since the function will try to defensively populate the non
* used field with values which are easy to trap if misused.
*
* When an object is returned (the set was a real set) the ref count * When an object is returned (the set was a real set) the ref count
* of the object is not incremented so this function can be considered * of the object is not incremented so this function can be considered
* copy on write friendly. */ * copy on write friendly. */
...@@ -204,8 +216,10 @@ int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) { ...@@ -204,8 +216,10 @@ int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
if (setobj->encoding == REDIS_ENCODING_HT) { if (setobj->encoding == REDIS_ENCODING_HT) {
dictEntry *de = dictGetRandomKey(setobj->ptr); dictEntry *de = dictGetRandomKey(setobj->ptr);
*objele = dictGetKey(de); *objele = dictGetKey(de);
*llele = -123456789; /* Not needed. Defensive. */
} else if (setobj->encoding == REDIS_ENCODING_INTSET) { } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
*llele = intsetRandom(setobj->ptr); *llele = intsetRandom(setobj->ptr);
*objele = NULL; /* Not needed. Defensive. */
} else { } else {
redisPanic("Unknown set encoding"); redisPanic("Unknown set encoding");
} }
...@@ -240,7 +254,7 @@ void setTypeConvert(robj *setobj, int enc) { ...@@ -240,7 +254,7 @@ void setTypeConvert(robj *setobj, int enc) {
/* To add the elements we extract integers and create redis objects */ /* To add the elements we extract integers and create redis objects */
si = setTypeInitIterator(setobj); si = setTypeInitIterator(setobj);
while (setTypeNext(si,NULL,&intele) != -1) { while (setTypeNext(si,&element,&intele) != -1) {
element = createStringObjectFromLongLong(intele); element = createStringObjectFromLongLong(intele);
redisAssertWithInfo(NULL,element, redisAssertWithInfo(NULL,element,
dictAdd(d,element,NULL) == DICT_OK); dictAdd(d,element,NULL) == DICT_OK);
...@@ -329,7 +343,7 @@ void smoveCommand(redisClient *c) { ...@@ -329,7 +343,7 @@ void smoveCommand(redisClient *c) {
/* If srcset and dstset are equal, SMOVE is a no-op */ /* If srcset and dstset are equal, SMOVE is a no-op */
if (srcset == dstset) { if (srcset == dstset) {
addReply(c,shared.cone); addReply(c,setTypeIsMember(srcset,ele) ? shared.cone : shared.czero);
return; return;
} }
......
...@@ -213,7 +213,7 @@ static int zslValueGteMin(double value, zrangespec *spec) { ...@@ -213,7 +213,7 @@ static int zslValueGteMin(double value, zrangespec *spec) {
return spec->minex ? (value > spec->min) : (value >= spec->min); return spec->minex ? (value > spec->min) : (value >= spec->min);
} }
static int zslValueLteMax(double value, zrangespec *spec) { int zslValueLteMax(double value, zrangespec *spec) {
return spec->maxex ? (value < spec->max) : (value <= spec->max); return spec->maxex ? (value < spec->max) : (value <= spec->max);
} }
...@@ -1166,40 +1166,109 @@ void zsetConvert(robj *zobj, int encoding) { ...@@ -1166,40 +1166,109 @@ void zsetConvert(robj *zobj, int encoding) {
} }
} }
/* Return (by reference) the score of the specified member of the sorted set
* storing it into *score. If the element does not exist REDIS_ERR is returned
* otherwise REDIS_OK is returned and *score is correctly populated.
* If 'zobj' or 'member' is NULL, REDIS_ERR is returned. */
int zsetScore(robj *zobj, robj *member, double *score) {
if (!zobj || !member) return REDIS_ERR;
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
if (zzlFind(zobj->ptr, member, score) == NULL) return REDIS_ERR;
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
dictEntry *de = dictFind(zs->dict, member);
if (de == NULL) return REDIS_ERR;
*score = *(double*)dictGetVal(de);
} else {
redisPanic("Unknown sorted set encoding");
}
return REDIS_OK;
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Sorted set commands * Sorted set commands
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* This generic command implements both ZADD and ZINCRBY. */ /* This generic command implements both ZADD and ZINCRBY. */
void zaddGenericCommand(redisClient *c, int incr) { #define ZADD_NONE 0
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
#define ZADD_XX (1<<2) /* Only touch elements already exisitng. */
#define ZADD_CH (1<<3) /* Return num of elements added or updated. */
void zaddGenericCommand(redisClient *c, int flags) {
static char *nanerr = "resulting score is not a number (NaN)"; static char *nanerr = "resulting score is not a number (NaN)";
robj *key = c->argv[1]; robj *key = c->argv[1];
robj *ele; robj *ele;
robj *zobj; robj *zobj;
robj *curobj; robj *curobj;
double score = 0, *scores = NULL, curscore = 0.0; double score = 0, *scores = NULL, curscore = 0.0;
int j, elements = (c->argc-2)/2; int j, elements;
int added = 0, updated = 0; int scoreidx = 0;
/* The following vars are used in order to track what the command actually
if (c->argc % 2) { * did during the execution, to reply to the client and to trigger the
* notification of keyspace change. */
int added = 0; /* Number of new elements added. */
int updated = 0; /* Number of elements with updated score. */
int processed = 0; /* Number of elements processed, may remain zero with
options like XX. */
/* Parse options. At the end 'scoreidx' is set to the argument position
* of the score of the first score-element pair. */
scoreidx = 2;
while(scoreidx < c->argc) {
char *opt = c->argv[scoreidx]->ptr;
if (!strcasecmp(opt,"nx")) flags |= ZADD_NX;
else if (!strcasecmp(opt,"xx")) flags |= ZADD_XX;
else if (!strcasecmp(opt,"ch")) flags |= ZADD_CH;
else if (!strcasecmp(opt,"incr")) flags |= ZADD_INCR;
else break;
scoreidx++;
}
/* Turn options into simple to check vars. */
int incr = (flags & ZADD_INCR) != 0;
int nx = (flags & ZADD_NX) != 0;
int xx = (flags & ZADD_XX) != 0;
int ch = (flags & ZADD_CH) != 0;
/* After the options, we expect to have an even number of args, since
* we expect any number of score-element pairs. */
elements = c->argc-scoreidx;
if (elements % 2) {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
return; return;
} }
elements /= 2; /* Now this holds the number of score-element pairs. */
/* Check for incompatible options. */
if (nx && xx) {
addReplyError(c,
"XX and NX options at the same time are not compatible");
return;
}
if (incr && elements > 1) {
addReplyError(c,
"INCR option supports a single increment-element pair");
return;
}
/* Start parsing all the scores, we need to emit any syntax error /* Start parsing all the scores, we need to emit any syntax error
* before executing additions to the sorted set, as the command should * before executing additions to the sorted set, as the command should
* either execute fully or nothing at all. */ * either execute fully or nothing at all. */
scores = zmalloc(sizeof(double)*elements); scores = zmalloc(sizeof(double)*elements);
for (j = 0; j < elements; j++) { for (j = 0; j < elements; j++) {
if (getDoubleFromObjectOrReply(c,c->argv[2+j*2],&scores[j],NULL) if (getDoubleFromObjectOrReply(c,c->argv[scoreidx+j*2],&scores[j],NULL)
!= REDIS_OK) goto cleanup; != REDIS_OK) goto cleanup;
} }
/* Lookup the key and create the sorted set if does not exist. */ /* Lookup the key and create the sorted set if does not exist. */
zobj = lookupKeyWrite(c->db,key); zobj = lookupKeyWrite(c->db,key);
if (zobj == NULL) { if (zobj == NULL) {
if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
if (server.zset_max_ziplist_entries == 0 || if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr)) server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
{ {
zobj = createZsetObject(); zobj = createZsetObject();
} else { } else {
...@@ -1220,8 +1289,9 @@ void zaddGenericCommand(redisClient *c, int incr) { ...@@ -1220,8 +1289,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
unsigned char *eptr; unsigned char *eptr;
/* Prefer non-encoded element when dealing with ziplists. */ /* Prefer non-encoded element when dealing with ziplists. */
ele = c->argv[3+j*2]; ele = c->argv[scoreidx+1+j*2];
if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) { if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
if (nx) continue;
if (incr) { if (incr) {
score += curscore; score += curscore;
if (isnan(score)) { if (isnan(score)) {
...@@ -1237,7 +1307,8 @@ void zaddGenericCommand(redisClient *c, int incr) { ...@@ -1237,7 +1307,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
server.dirty++; server.dirty++;
updated++; updated++;
} }
} else { processed++;
} else if (!xx) {
/* Optimize: check if the element is too large or the list /* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */ * becomes too long *before* executing zzlInsert. */
zobj->ptr = zzlInsert(zobj->ptr,ele,score); zobj->ptr = zzlInsert(zobj->ptr,ele,score);
...@@ -1247,15 +1318,18 @@ void zaddGenericCommand(redisClient *c, int incr) { ...@@ -1247,15 +1318,18 @@ void zaddGenericCommand(redisClient *c, int incr) {
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST); zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
server.dirty++; server.dirty++;
added++; added++;
processed++;
} }
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) { } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr; zset *zs = zobj->ptr;
zskiplistNode *znode; zskiplistNode *znode;
dictEntry *de; dictEntry *de;
ele = c->argv[3+j*2] = tryObjectEncoding(c->argv[3+j*2]); ele = c->argv[scoreidx+1+j*2] =
tryObjectEncoding(c->argv[scoreidx+1+j*2]);
de = dictFind(zs->dict,ele); de = dictFind(zs->dict,ele);
if (de != NULL) { if (de != NULL) {
if (nx) continue;
curobj = dictGetKey(de); curobj = dictGetKey(de);
curscore = *(double*)dictGetVal(de); curscore = *(double*)dictGetVal(de);
...@@ -1280,22 +1354,30 @@ void zaddGenericCommand(redisClient *c, int incr) { ...@@ -1280,22 +1354,30 @@ void zaddGenericCommand(redisClient *c, int incr) {
server.dirty++; server.dirty++;
updated++; updated++;
} }
} else { processed++;
} else if (!xx) {
znode = zslInsert(zs->zsl,score,ele); znode = zslInsert(zs->zsl,score,ele);
incrRefCount(ele); /* Inserted in skiplist. */ incrRefCount(ele); /* Inserted in skiplist. */
redisAssertWithInfo(c,NULL,dictAdd(zs->dict,ele,&znode->score) == DICT_OK); redisAssertWithInfo(c,NULL,dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
incrRefCount(ele); /* Added to dictionary. */ incrRefCount(ele); /* Added to dictionary. */
server.dirty++; server.dirty++;
added++; added++;
processed++;
} }
} else { } else {
redisPanic("Unknown sorted set encoding"); redisPanic("Unknown sorted set encoding");
} }
} }
if (incr) /* ZINCRBY */
reply_to_client:
if (incr) { /* ZINCRBY or INCR option. */
if (processed)
addReplyDouble(c,score); addReplyDouble(c,score);
else /* ZADD */ else
addReplyLongLong(c,added); addReply(c,shared.nullbulk);
} else { /* ZADD. */
addReplyLongLong(c,ch ? added+updated : added);
}
cleanup: cleanup:
zfree(scores); zfree(scores);
...@@ -1307,11 +1389,11 @@ cleanup: ...@@ -1307,11 +1389,11 @@ cleanup:
} }
void zaddCommand(redisClient *c) { void zaddCommand(redisClient *c) {
zaddGenericCommand(c,0); zaddGenericCommand(c,ZADD_NONE);
} }
void zincrbyCommand(redisClient *c) { void zincrbyCommand(redisClient *c) {
zaddGenericCommand(c,1); zaddGenericCommand(c,ZADD_INCR);
} }
void zremCommand(redisClient *c) { void zremCommand(redisClient *c) {
...@@ -2753,25 +2835,10 @@ void zscoreCommand(redisClient *c) { ...@@ -2753,25 +2835,10 @@ void zscoreCommand(redisClient *c) {
if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
checkType(c,zobj,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) { if (zsetScore(zobj,c->argv[2],&score) == REDIS_ERR) {
if (zzlFind(zobj->ptr,c->argv[2],&score) != NULL)
addReplyDouble(c,score);
else
addReply(c,shared.nullbulk);
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
dictEntry *de;
c->argv[2] = tryObjectEncoding(c->argv[2]);
de = dictFind(zs->dict,c->argv[2]);
if (de != NULL) {
score = *(double*)dictGetVal(de);
addReplyDouble(c,score);
} else {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
}
} else { } else {
redisPanic("Unknown sorted set encoding"); addReplyDouble(c,score);
} }
} }
...@@ -2819,7 +2886,7 @@ void zrankGenericCommand(redisClient *c, int reverse) { ...@@ -2819,7 +2886,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
dictEntry *de; dictEntry *de;
double score; double score;
ele = c->argv[2] = tryObjectEncoding(c->argv[2]); ele = c->argv[2];
de = dictFind(zs->dict,ele); de = dictFind(zs->dict,ele);
if (de != NULL) { if (de != NULL) {
score = *(double*)dictGetVal(de); score = *(double*)dictGetVal(de);
......
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#ifndef _ZIPLIST_H
#define _ZIPLIST_H
#define ZIPLIST_HEAD 0 #define ZIPLIST_HEAD 0
#define ZIPLIST_TAIL 1 #define ZIPLIST_TAIL 1
...@@ -49,3 +52,5 @@ size_t ziplistBlobLen(unsigned char *zl); ...@@ -49,3 +52,5 @@ size_t ziplistBlobLen(unsigned char *zl);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int ziplistTest(int argc, char *argv[]); int ziplistTest(int argc, char *argv[]);
#endif #endif
#endif /* _ZIPLIST_H */
...@@ -17,6 +17,7 @@ proc main {} { ...@@ -17,6 +17,7 @@ proc main {} {
} }
run_tests run_tests
cleanup cleanup
end_tests
} }
if {[catch main e]} { if {[catch main e]} {
......
# Check the manual failover
source "../tests/includes/init-tests.tcl"
test "Create a 5 nodes cluster" {
create_cluster 5 5
}
test "Cluster is up" {
assert_cluster_state ok
}
test "Cluster is writable" {
cluster_write_test 0
}
test "Instance #5 is a slave" {
assert {[RI 5 role] eq {slave}}
}
test "Instance #5 synced with the master" {
wait_for_condition 1000 50 {
[RI 5 master_link_status] eq {up}
} else {
fail "Instance #5 master link status is not up"
}
}
set current_epoch [CI 1 cluster_current_epoch]
set numkeys 50000
set numops 10000
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]]
catch {unset content}
array set content {}
test "Send CLUSTER FAILOVER to #5, during load" {
for {set j 0} {$j < $numops} {incr j} {
# Write random data to random list.
set listid [randomInt $numkeys]
set key "key:$listid"
set ele [randomValue]
# We write both with Lua scripts and with plain commands.
# This way we are able to stress Lua -> Redis command invocation
# as well, that has tests to prevent Lua to write into wrong
# hash slots.
if {$listid % 2} {
$cluster rpush $key $ele
} else {
$cluster eval {redis.call("rpush",KEYS[1],ARGV[1])} 1 $key $ele
}
lappend content($key) $ele
if {($j % 1000) == 0} {
puts -nonewline W; flush stdout
}
if {$j == $numops/2} {R 5 cluster failover}
}
}
test "Wait for failover" {
wait_for_condition 1000 50 {
[CI 1 cluster_current_epoch] > $current_epoch
} else {
fail "No failover detected"
}
}
test "Cluster should eventually be up again" {
assert_cluster_state ok
}
test "Cluster is writable" {
cluster_write_test 1
}
test "Instance #5 is now a master" {
assert {[RI 5 role] eq {master}}
}
test "Verify $numkeys keys for consistency with logical content" {
# Check that the Redis Cluster content matches our logical content.
foreach {key value} [array get content] {
assert {[$cluster lrange $key 0 -1] eq $value}
}
}
test "Instance #0 gets converted into a slave" {
wait_for_condition 1000 50 {
[RI 0 role] eq {slave}
} else {
fail "Old master was not converted into slave"
}
}
## Check that manual failover does not happen if we can't talk with the master.
source "../tests/includes/init-tests.tcl"
test "Create a 5 nodes cluster" {
create_cluster 5 5
}
test "Cluster is up" {
assert_cluster_state ok
}
test "Cluster is writable" {
cluster_write_test 0
}
test "Instance #5 is a slave" {
assert {[RI 5 role] eq {slave}}
}
test "Instance #5 synced with the master" {
wait_for_condition 1000 50 {
[RI 5 master_link_status] eq {up}
} else {
fail "Instance #5 master link status is not up"
}
}
test "Make instance #0 unreachable without killing it" {
R 0 deferred 1
R 0 DEBUG SLEEP 10
}
test "Send CLUSTER FAILOVER to instance #5" {
R 5 cluster failover
}
test "Instance #5 is still a slave after some time (no failover)" {
after 5000
assert {[RI 5 role] eq {master}}
}
test "Wait for instance #0 to return back alive" {
R 0 deferred 0
assert {[R 0 read] eq {OK}}
}
## Check with "force" failover happens anyway.
source "../tests/includes/init-tests.tcl"
test "Create a 5 nodes cluster" {
create_cluster 5 5
}
test "Cluster is up" {
assert_cluster_state ok
}
test "Cluster is writable" {
cluster_write_test 0
}
test "Instance #5 is a slave" {
assert {[RI 5 role] eq {slave}}
}
test "Instance #5 synced with the master" {
wait_for_condition 1000 50 {
[RI 5 master_link_status] eq {up}
} else {
fail "Instance #5 master link status is not up"
}
}
test "Make instance #0 unreachable without killing it" {
R 0 deferred 1
R 0 DEBUG SLEEP 10
}
test "Send CLUSTER FAILOVER to instance #5" {
R 5 cluster failover force
}
test "Instance #5 is a master after some time" {
wait_for_condition 1000 50 {
[RI 5 role] eq {master}
} else {
fail "Instance #5 is not a master after some time regardless of FORCE"
}
}
test "Wait for instance #0 to return back alive" {
R 0 deferred 0
assert {[R 0 read] eq {OK}}
}
This diff is collapsed.
...@@ -19,6 +19,7 @@ set ::verbose 0 ...@@ -19,6 +19,7 @@ set ::verbose 0
set ::valgrind 0 set ::valgrind 0
set ::pause_on_error 0 set ::pause_on_error 0
set ::simulate_error 0 set ::simulate_error 0
set ::failed 0
set ::sentinel_instances {} set ::sentinel_instances {}
set ::redis_instances {} set ::redis_instances {}
set ::sentinel_base_port 20000 set ::sentinel_base_port 20000
...@@ -231,6 +232,7 @@ proc test {descr code} { ...@@ -231,6 +232,7 @@ proc test {descr code} {
flush stdout flush stdout
if {[catch {set retval [uplevel 1 $code]} error]} { if {[catch {set retval [uplevel 1 $code]} error]} {
incr ::failed
if {[string match "assertion:*" $error]} { if {[string match "assertion:*" $error]} {
set msg [string range $error 10 end] set msg [string range $error 10 end]
puts [colorstr red $msg] puts [colorstr red $msg]
...@@ -246,6 +248,7 @@ proc test {descr code} { ...@@ -246,6 +248,7 @@ proc test {descr code} {
} }
} }
# Execute all the units inside the 'tests' directory.
proc run_tests {} { proc run_tests {} {
set tests [lsort [glob ../tests/*]] set tests [lsort [glob ../tests/*]]
foreach test $tests { foreach test $tests {
...@@ -258,6 +261,17 @@ proc run_tests {} { ...@@ -258,6 +261,17 @@ proc run_tests {} {
} }
} }
# Print a message and exists with 0 / 1 according to zero or more failures.
proc end_tests {} {
if {$::failed == 0} {
puts "GOOD! No errors."
exit 0
} else {
puts "WARNING $::failed tests faield."
exit 1
}
}
# The "S" command is used to interact with the N-th Sentinel. # The "S" command is used to interact with the N-th Sentinel.
# The general form is: # The general form is:
# #
......
...@@ -126,7 +126,7 @@ start_server {tags {"repl"}} { ...@@ -126,7 +126,7 @@ start_server {tags {"repl"}} {
test {Replication of SPOP command -- alsoPropagate() API} { test {Replication of SPOP command -- alsoPropagate() API} {
$master del myset $master del myset
set size [randomInt 100] set size [expr 1+[randomInt 100]]
set content {} set content {}
for {set j 0} {$j < $size} {incr j} { for {set j 0} {$j < $size} {incr j} {
lappend content [randomValue] lappend content [randomValue]
......
This diff is collapsed.
...@@ -13,6 +13,7 @@ proc main {} { ...@@ -13,6 +13,7 @@ proc main {} {
spawn_instance redis $::redis_base_port $::instances_count spawn_instance redis $::redis_base_port $::instances_count
run_tests run_tests
cleanup cleanup
end_tests
} }
if {[catch main e]} { if {[catch main e]} {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment