From 017ae6461b285fb83d4c48935a28ff814a929d84 Mon Sep 17 00:00:00 2001 From: fortrue Date: Sun, 3 Sep 2017 19:53:04 +0800 Subject: [PATCH] 1.fix parse error for multi-key command with single key 2.correct some commands property 3.add test cases --- src/Command.cpp | 16 +- src/Command.h | 9 +- src/Handler.cpp | 8 +- src/Request.cpp | 22 +- src/Request.h | 2 + src/RequestParser.cpp | 10 +- src/RequestParser.h | 20 +- test/basic.py | 573 ++++++++++++++++++++++++++++++++++++++++++ test/pubsub.py | 91 +++++++ 9 files changed, 721 insertions(+), 30 deletions(-) create mode 100755 test/basic.py create mode 100755 test/pubsub.py diff --git a/src/Command.cpp b/src/Command.cpp index 6a9ef0d..a61d286 100644 --- a/src/Command.cpp +++ b/src/Command.cpp @@ -36,8 +36,8 @@ const Command Command::CmdPool[Sentinel] = { {Exec, "exec", 1, 1, Write|NoKey}, {Discard, "discard", 1, 1, Write|NoKey}, {DiscardServ, "discard", 1, 1, Inner|NoKey}, - {Eval, "eval", 4, MaxArgs, Write|KeyAt3}, - {Evalsha, "evalsha", 4, MaxArgs, Write|KeyAt3}, + {Eval, "eval", 3, MaxArgs, Write|KeyAt3}, + {Evalsha, "evalsha", 3, MaxArgs, Write|KeyAt3}, {Script, "script", 2, MaxArgs, Write}, {ScriptLoad, "script", 3, 3, Write|SubCmd}, {Del, "del", 2, MaxArgs, Write|MultiKey}, @@ -54,7 +54,7 @@ const Command Command::CmdPool[Sentinel] = { {Rename, "rename", 3, 3, Write}, {Renamenx, "renamenx", 3, 3, Write}, {Restore, "restore", 4, 5, Write}, - {Sort, "sort", 2, MaxArgs, Read}, + {Sort, "sort", 2, MaxArgs, Write}, {Touch, "touch", 2, MaxArgs, Write|MultiKey}, {Ttl, "ttl", 2, 2, Read}, {TypeCmd, "type", 2, 2, Read}, @@ -63,7 +63,7 @@ const Command Command::CmdPool[Sentinel] = { {Append, "append", 3, 3, Write}, {Bitcount, "bitcount", 2, 4, Read}, {Bitfield, "bitfield", 2, MaxArgs, Write}, - {Bitop, "bitop", 4, MaxArgs, Write}, + {Bitop, "bitop", 4, MaxArgs, Write|KeyAt2}, {Bitpos, "bitpos", 3, 5, Read}, {Decr, "decr", 2, 2, Write}, {Decrby, "decrby", 3, 3, Write}, @@ -159,14 +159,14 @@ const Command Command::CmdPool[Sentinel] = { {Geodist, "geodist", 4, 5, Read}, {Geohash, "geohash", 3, MaxArgs, Read}, {Geopos, "geopos", 3, MaxArgs, Read}, - {Georadius, "georadius", 6, 16, Read}, - {Georadiusbymember, "georadiusbymember",5, 15, Read}, + {Georadius, "georadius", 6, 16, Write}, + {Georadiusbymember, "georadiusbymember",5, 15, Write}, {Psubscribe, "psubscribe", 2, MaxArgs, Write|SMultiKey|Private}, {Publish, "publish", 3, 3, Write}, {Pubsub, "pubsub", 2, MaxArgs, Read}, - {Punsubscribe, "punsubscribe", 1, MaxArgs, Write}, + {Punsubscribe, "punsubscribe", 1, MaxArgs, Write|SMultiKey}, {Subscribe, "subscribe", 2, MaxArgs, Write|SMultiKey|Private}, - {Unsubscribe, "unsubscribe", 1, MaxArgs, Write}, + {Unsubscribe, "unsubscribe", 1, MaxArgs, Write|SMultiKey}, {SubMsg, "\000SubMsg", 0, 0, Admin} }; diff --git a/src/Command.h b/src/Command.h index f15e624..a6e5b61 100644 --- a/src/Command.h +++ b/src/Command.h @@ -201,12 +201,13 @@ class Command MultiKey = 1<<5, SMultiKey = 1<<6, MultiKeyVal = 1<<7, - KeyAt3 = 1<<8, - SubCmd = 1<<9, - Inner = 1<<10 //proxy use only + KeyAt2 = 1<<8, + KeyAt3 = 1<<9, + SubCmd = 1<<10, + Inner = 1<<11 //proxy use only }; static const int AuthMask = Read|Write|Admin; - static const int KeyMask = NoKey|MultiKey|SMultiKey|MultiKeyVal|KeyAt3; + static const int KeyMask = NoKey|MultiKey|SMultiKey|MultiKeyVal|KeyAt2|KeyAt3; public: Type type; const char* name; diff --git a/src/Handler.cpp b/src/Handler.cpp index 0ceae95..e43f36b 100644 --- a/src/Handler.cpp +++ b/src/Handler.cpp @@ -710,11 +710,11 @@ void Handler::directResponse(Request* req, Response::GenericCode code, ConnectCo id(), c->peer(), c->fd(), req->id(), code, excp.what()); } } else { - logInfo("h %d ignore req %ld res code %d c %s %d status %d %s", + logDebug("h %d ignore req %ld res code %d c %s %d status %d %s", id(), req->id(), code, c->peer(), c->fd(), c->status(), c->statusStr()); } } else { - logInfo("h %d ignore req %ld res code %d without accept connection", + logDebug("h %d ignore req %ld res code %d without accept connection", id(), req->id(), code); } } @@ -741,7 +741,7 @@ void Handler::handleResponse(ConnectConnection* s, Request* req, Response* res) auto sp = mProxy->serverPool(); AcceptConnection* c = req->connection(); if (!c) { - logInfo("h %d ignore req %ld res %ld", id(), req->id(), res->id()); + logDebug("h %d ignore req %ld res %ld", id(), req->id(), res->id()); return; } else if (!c->good()) { logWarn("h %d ignore req %ld res %ld for c %s %d with status %d %s", @@ -1288,7 +1288,7 @@ void Handler::configSetRequest(Request* req) void Handler::innerResponse(ConnectConnection* s, Request* req, Response* res) { - logInfo("h %d s %s %d inner req %ld %s res %ld %s", + logDebug("h %d s %s %d inner req %ld %s res %ld %s", id(), (s ? s->peer() : "None"), (s ? s->fd() : -1), req->id(), req->cmd(), res->id(), res->typeStr()); diff --git a/src/Request.cpp b/src/Request.cpp index de1a1c9..0133443 100644 --- a/src/Request.cpp +++ b/src/Request.cpp @@ -31,7 +31,9 @@ static const GenericRequest GenericRequestDefs[] = { {Request::DelHead, Command::Del, "*2\r\n$3\r\ndel\r\n"}, {Request::UnlinkHead, Command::Unlink, "*2\r\n$6\r\nunlink\r\n"}, {Request::PsubscribeHead,Command::Psubscribe, "*2\r\n$10\r\npsubscribe\r\n"}, - {Request::SubscribeHead,Command::Subscribe, "*2\r\n$9\r\nsubscribe\r\n"} + {Request::SubscribeHead,Command::Subscribe, "*2\r\n$9\r\nsubscribe\r\n"}, + {Request::PunsubscribeHead,Command::Punsubscribe, "*2\r\n$12\r\npunsubscribe\r\n"}, + {Request::UnsubscribeHead,Command::Unsubscribe, "*2\r\n$11\r\nunsubscribe\r\n"} }; thread_local static Request* GenericRequests[Request::CodeSentinel]; @@ -137,8 +139,15 @@ void Request::set(const RequestParser& p, Request* leader) case Command::Subscribe: r = GenericRequests[SubscribeHead]; break; + case Command::Punsubscribe: + r = GenericRequests[PunsubscribeHead]; + break; + case Command::Unsubscribe: + r = GenericRequests[UnsubscribeHead]; + break; default: //should never reach + abort(); break; } mHead = r->mReq; @@ -341,6 +350,15 @@ void Request::setResponse(Response* res) } break; case Command::Msetnx: + if (Response* leaderRes = mLeader->getResponse()) { + if (!leaderRes->isError() && + (res->isError() || res->integer() == 0)) { + mLeader->mRes = res; + } + } else { + mLeader->mRes = res; + } + break; case Command::Touch: case Command::Exists: case Command::Del: @@ -378,6 +396,8 @@ bool Request::isDone() const case Command::Mget: case Command::Psubscribe: case Command::Subscribe: + case Command::Punsubscribe: + case Command::Unsubscribe: return mDone; default: break; diff --git a/src/Request.h b/src/Request.h index 4428286..37696b7 100644 --- a/src/Request.h +++ b/src/Request.h @@ -44,6 +44,8 @@ class Request : UnlinkHead, PsubscribeHead, SubscribeHead, + PunsubscribeHead, + UnsubscribeHead, CodeSentinel }; diff --git a/src/RequestParser.cpp b/src/RequestParser.cpp index 8ffc458..7e12438 100644 --- a/src/RequestParser.cpp +++ b/src/RequestParser.cpp @@ -43,6 +43,8 @@ inline bool RequestParser::isKey(bool split) const return mArgCnt > 0; case Command::MultiKeyVal: return split ? (mArgCnt & 1) : mArgCnt == 1; + case Command::KeyAt2: + return mArgCnt == 2; case Command::KeyAt3: return mArgCnt == 3; default: @@ -53,10 +55,12 @@ inline bool RequestParser::isKey(bool split) const inline bool RequestParser::isSplit(bool split) const { - if (mCommand->mode & (Command::MultiKey|Command::MultiKeyVal)) { - return split && mStatus == Normal && isKey(true); + if (mCommand->mode & Command::MultiKey) { + return split && mStatus == Normal && mArgNum > 2 && isKey(true); + } else if (mCommand->mode & Command::MultiKeyVal) { + return split && mStatus == Normal && mArgNum > 3 && isKey(true); } else if (mCommand->mode & Command::SMultiKey) { - return mStatus == Normal; + return mStatus == Normal && mArgNum > 2; } return false; } diff --git a/src/RequestParser.h b/src/RequestParser.h index bb7b687..58c028c 100644 --- a/src/RequestParser.h +++ b/src/RequestParser.h @@ -34,16 +34,16 @@ class RequestParser KeyLenLF, KeyBody, KeyBodyLF, - ArgTag, // $ $ - ArgLen, // 3 5 - ArgLenLF, // \r\n \r\n - ArgBody, // get hello - ArgBodyLF, // \r\n \r\n - SArgTag, // $ $ - SArgLen, // 3 5 - SArgLenLF, // \r\n \r\n - SArgBody, // get hello - SArgBodyLF, // \r\n \r\n + ArgTag, + ArgLen, + ArgLenLF, + ArgBody, + ArgBodyLF, + SArgTag, + SArgLen, + SArgLenLF, + SArgBody, + SArgBodyLF, Finished, Error diff --git a/test/basic.py b/test/basic.py new file mode 100755 index 0000000..20da613 --- /dev/null +++ b/test/basic.py @@ -0,0 +1,573 @@ +#!/usr/bin/env python +# +# predixy - A high performance and full features proxy for redis. +# Copyright (C) 2017 Joyield, Inc. +# All rights reserved. +# + +import time +import redis +import sys +import argparse + +c = None + +Cases = [ + ('ping', [ + [('ping',), 'PONG'], + ]), + ('echo', [ + [('echo', 'hello'), 'hello'], + ]), + ('del', [ + [('set', 'key', 'val'), 'OK'], + [('del', 'key'), 1], + [('del', 'key'), 0], + [('mset', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'), 'OK'], + [('del', 'a', 'b', 'c'), 2], + [('del', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'), 2], + ]), + ('dump', [ + [('set', 'k', 'v'), 'OK'], + [('dump', 'k'), lambda x:len(x)>10], + [('del', 'k'), 1], + ]), + ('exists', [ + [('set', 'k', 'v'), 'OK'], + [('exists', 'k'), 1], + [('del', 'k'), 1], + [('exists', 'k'), 0], + [('mset', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'), 'OK'], + [('exists', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'), 4], + [('del', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'), 4], + ]), + ('rename', [ + [('del', '{k}1', '{k}2'), ], + [('set', '{k}1', 'v'), 'OK'], + [('rename', '{k}1', '{k}2'), 'OK'], + [('get', '{k}2'), 'v'], + ]), + ('renamenx', [ + [('del', '{k}1', '{k}2'), ], + [('set', '{k}1', 'v'), 'OK'], + [('renamenx', '{k}1', '{k}2'), 1], + [('get', '{k}2'), 'v'], + [('set', '{k}1', 'new'), 'OK'], + [('renamenx', '{k}1', '{k}2'), 0], + ]), + ('expire', [ + [('set', 'k', 'v'), 'OK'], + [('ttl', 'k'), -1], + [('expire', 'k', 10), 1], + [('ttl', 'k'), lambda x: x>0], + [('del', 'k'), 1], + ]), + ('pexpire', [ + [('set', 'k', 'v'), 'OK'], + [('ttl', 'k'), -1], + [('pexpire', 'k', 10000), 1], + [('ttl', 'k'), lambda x: x>0], + [('del', 'k'), 1], + ]), + ('expireat', [ + [('set', 'k', 'v'), 'OK'], + [('ttl', 'k'), -1], + [('expireat', 'k', int(time.time()) + 10), 1], + [('ttl', 'k'), lambda x: x>0], + [('del', 'k'), 1], + ]), + ('pexpireat', [ + [('set', 'k', 'v'), 'OK'], + [('pttl', 'k'), -1], + [('pexpireat', 'k', (int(time.time()) + 10) * 1000) , 1], + [('pttl', 'k'), lambda x: x>0], + [('del', 'k'), 1], + ]), + ('persist', [ + [('setex', 'k', 10, 'v'), 'OK'], + [('ttl', 'k'), lambda x: x>0], + [('persist', 'k'), 1], + [('ttl', 'k'), -1], + [('del', 'k'), 1], + ]), + ('pttl', [ + [('set', 'k', 'v'), 'OK'], + [('pttl', 'k'), -1], + [('setex', 'k', 10, 'v'), 'OK'], + [('pttl', 'k'), lambda x: x>0], + [('del', 'k'), 1], + ]), + ('ttl', [ + [('set', 'k', 'v'), 'OK'], + [('ttl', 'k'), -1], + [('setex', 'k', 10, 'v'), 'OK'], + [('ttl', 'k'), lambda x: x>0], + [('del', 'k'), 1], + ]), + ('type', [ + [('set', 'k', 'v'), 'OK'], + [('del', 'h', 'l', 's', 'z'),], + [('type', 'k'), 'string'], + [('hset', 'h', 'k', 'v'), 1], + [('type', 'h'), 'hash'], + [('lpush', 'l', 'k'), 1], + [('type', 'l'), 'list'], + [('sadd', 's', 'k'), 1], + [('type', 's'), 'set'], + [('zadd', 'z', 10, 'k'), 1], + [('type', 'z'), 'zset'], + [('del', 'k', 'h', 'l', 's', 'z'), 5], + ]), + ('sort', [ + [('del', 'list'), ], + [('lpush', 'list', 6, 3, 1, 2, 5, 4), 6], + [('sort', 'list'), ['1', '2', '3', '4', '5', '6']], + [('sort', 'list', 'ASC'), ['1', '2', '3', '4', '5', '6']], + [('sort', 'list', 'DESC'), ['6', '5', '4', '3', '2', '1']], + [('sort', 'list', 'LIMIT', 1, 2), ['2', '3']], + [('mset', 'u1', -1, 'u2', -2, 'u3', -3, 'u4', -4, 'u5', -5, 'u6', -6), 'OK'], + [('del', 'list'), 1], + [('lpush', 'list', 'c++', 'java', 'c', 'javascript', 'python'), 5], + [('sort', 'list', 'ALPHA'), ['c', 'c++', 'java', 'javascript', 'python']], + [('sort', 'list', 'DESC', 'ALPHA'), ['python', 'javascript', 'java', 'c++', 'c']], + [('del', 'list', 'u1', 'u2', 'u3', 'u4', 'u5', 'u6'), 7], + ]), + ('touch', [ + [('mset', 'k1', 'v1', 'k2', 'v2', 'k3', 'v3'), 'OK'], + [('touch', 'k1'), 1], + [('touch', 'k1', 'k2', 'k3', 'k4'), 3], + [('del', 'k1', 'k2', 'k3'), 3], + [('touch', 'k1', 'k2', 'k3', 'k4'), 0], + ]), + ('scan', [ + [('mset', 'k1', 'v1', 'k2', 'v2', 'k3', 'v3'), 'OK'], + [('scan', '0'), lambda x: x[0] != 0], + [('scan', '0', 'count', 1), lambda x: x[0] != 0], + [('del', 'k1', 'k2', 'k3'), 3], + ]), + ('append', [ + [('set', 'k', ''), 'OK'], + [('strlen', 'k'), 0], + [('append', 'k', '1'), 1], + [('get', 'k'), '1'], + [('append', 'k', '2'), 2], + [('get', 'k'), '12'], + ]), + ('bitcount', [ + [('set', 'k', '\x0f\x07\x03\x01'), 'OK'], + [('bitcount', 'k'), 10], + [('bitcount', 'k', 0, 1), 7], + [('bitcount', 'k', -2, -1), 3], + ]), + ('bitfield', [ + [('set', 'k', 123), 'OK'], + [('bitfield', 'k', 'INCRBY', 'i5', 2, 3), [-5]], + ]), + ('bitop', [ + [('del', '{k}1', '{k}2', '{k}3'), ], + [('set', '{k}1', '\x0f'), 'OK'], + [('set', '{k}2', '\xf1'), 'OK'], + [('bitop', 'NOT', '{k}3', '{k}1'), 1], + [('bitop', 'AND', '{k}3', '{k}1', '{k}2'), 1], + ]), + ('bitpos', [ + [('set', 'k', '\x0f'), 'OK'], + [('bitpos', 'k', 0), 0], + [('bitpos', 'k', 1, 0, -1), 4], + ]), + ('decr', [ + [('set', 'k', 10), 'OK'], + [('decr', 'k'), 9], + [('decr', 'k'), 8], + ]), + ('decrby', [ + [('set', 'k', 10), 'OK'], + [('decrby', 'k', 2), 8], + [('decrby', 'k', 3), 5], + ]), + ('getbit', [ + [('set', 'k', '\x0f'), 'OK'], + [('getbit', 'k', 0), 0], + [('getbit', 'k', 4), 1], + ]), + ('getrange', [ + [('set', 'k', '0123456'), 'OK'], + [('getrange', 'k', 0, 2), '012'], + [('getrange', 'k', -2, -1), '56'], + ]), + ('getset', [ + [('set', 'k', 'v'), 'OK'], + [('getset', 'k', 'value'), 'v'], + [('get', 'k'), 'value'], + ]), + ('incr', [ + [('set', 'k', 10), 'OK'], + [('incr', 'k'), 11], + [('incr', 'k'), 12], + ]), + ('incrby', [ + [('set', 'k', 10), 'OK'], + [('incrby', 'k', 2), 12], + [('incrby', 'k', 3), 15], + ]), + ('incrbyfloat', [ + [('set', 'k', 10), 'OK'], + [('incrbyfloat', 'k', 2.5), '12.5'], + [('incrbyfloat', 'k', 3.5), '16'], + ]), + ('mget', [ + [('mset', 'k', 'v'), 'OK'], + [('mget', 'k'), ['v']], + [('mget', 'k', 'k'), ['v', 'v']], + [('mset', 'k1', 'v1', 'k2', 'v2'), 'OK'], + [('mget', 'k1', 'v1', 'k2', 'v2'), ['v1', None, 'v2', None]], + [('del', 'k1', 'k2'), 2], + ]), + ('msetnx', [ + [('del', 'k1', 'k2', 'k3'), ], + [('msetnx', 'k1', 'v1', 'k2', 'v2', 'k3', 'v3'), 1], + [('mget', 'k1', 'k2', 'k3'), ['v1', 'v2', 'v3']], + [('msetnx', 'k1', 'v1', 'k2', 'v2', 'k3', 'v3'), 0], + [('del', 'k1', 'k2', 'k3'), 3], + ]), + ('psetex', [ + [('del', 'k'), ], + [('psetex', 'k', 10000, 'v'), 'OK'], + [('get', 'k'), 'v'], + [('pttl', 'k'), lambda x: x>0], + ]), + ('set', [ + [('set', 'k', 'v'), 'OK'], + [('get', 'k'), 'v'], + [('ttl', 'k'), -1], + [('set', 'k', 'vex', 'EX', 10), 'OK'], + [('get', 'k'), 'vex'], + [('ttl', 'k'), lambda x: x>0], + [('set', 'k', 'vpx', 'PX', 20000), 'OK'], + [('get', 'k'), 'vpx'], + [('pttl', 'k'), lambda x: x>10000], + [('set', 'k', 'val', 'NX'), None], + [('get', 'k'), 'vpx'], + [('set', 'k', 'val', 'XX'), 'OK'], + [('get', 'k'), 'val'], + ]), + ('setbit', [ + [('set', 'k', '\x00'), 'OK'], + [('setbit', 'k', 1, 1), 0], + [('setbit', 'k', 1, 0), 1], + ]), + ('setex', [ + [('del', 'k'), ], + [('setex', 'k', 10, 'v'), 'OK'], + [('get', 'k'), 'v'], + [('ttl', 'k'), lambda x: x>0], + ]), + ('setnx', [ + [('del', 'k'), ], + [('setnx', 'k', 'v'), 1], + [('get', 'k'), 'v'], + [('setnx', 'k', 'v'), 0], + ]), + ('setrange', [ + [('set', 'k', 'hello world'), 'OK'], + [('setrange', 'k', 6, 'predixy'), 13], + ]), + ('strlen', [ + [('set', 'k', '123456'), 'OK'], + [('strlen', 'k'), 6], + ]), + ('script', [ + [('del', 'k'), ], + [('eval', 'return "hello"', 0), 'hello'], + [('eval', 'return KEYS[1]', 1, 'k'), 'k'], + [('eval', 'return KEYS[1]', 3, '{k}1', '{k}2', '{k}3'), '{k}1'], + [('eval', 'return redis.call("set", KEYS[1], ARGV[1])', 1, 'k', 'v'), 'OK'], + [('eval', 'return redis.call("get", KEYS[1])', 1, 'k'), 'v'], + [('script', 'load', 'return redis.call("get", KEYS[1])'), 'a5260dd66ce02462c5b5231c727b3f7772c0bcc5'], + [('evalsha', 'a5260dd66ce02462c5b5231c727b3f7772c0bcc5', 1, 'k'), 'v'], + ]), + ('hash', [ + [('del', 'k'), ], + [('hset', 'k', 'name', 'hash'), 1], + [('hget', 'k', 'name'), 'hash'], + [('hexists', 'k', 'name'), 1], + [('hlen', 'k'), 1], + [('hkeys', 'k'), ['name']], + [('hgetall', 'k'), ['name', 'hash']], + [('hmget', 'k', 'name'), ['hash']], + [('hscan', 'k', 0), ['0', ['name', 'hash']]], + [('hstrlen', 'k', 'name'), 4], + [('hvals', 'k'), ['hash']], + [('hsetnx', 'k', 'name', 'other'), 0], + [('hget', 'k', 'name'), 'hash'], + [('hsetnx', 'k', 'age', 5), 1], + [('hget', 'k', 'age'), '5'], + [('hincrby', 'k', 'age', 3), 8], + [('hincrbyfloat', 'k', 'age', 1.5), '9.5'], + [('hmset', 'k', 'sex', 'F'), 'OK'], + [('hget', 'k', 'sex'), 'F'], + [('hmset', 'k', 'height', 180, 'weight', 80, 'zone', 'cn'), 'OK'], + [('hlen', 'k'), 6], + [('hmget', 'k', 'name', 'age', 'sex', 'height', 'weight', 'zone'), ['hash', '9.5', 'F', '180', '80', 'cn']], + [('hscan', 'k', 0, 'match', '*eight'), lambda x:False if len(x)!=2 else len(x[1])==4], + [('hscan', 'k', 0, 'count', 2), lambda x:len(x)==2], + [('hkeys', 'k'), lambda x:len(x)==6], + [('hvals', 'k'), lambda x:len(x)==6], + [('hgetall', 'k'), lambda x:len(x)==12], + ]), + ('list', [ + [('del', 'k'), ], + [('lpush', 'k', 'apple'), 1], + [('llen', 'k'), 1], + [('lindex', 'k', 0), 'apple'], + [('lindex', 'k', -1), 'apple'], + [('lindex', 'k', -2), None], + [('lpush', 'k', 'pear', 'orange'), 3], + [('llen', 'k'), 3], + [('lrange', 'k', 0, 3), ['orange', 'pear', 'apple']], + [('lrange', 'k', -2, -1), ['pear', 'apple']], + [('lset', 'k', 0, 'peach'), 'OK'], + [('lindex', 'k', 0), 'peach'], + [('rpush', 'k', 'orange'), 4], + [('lrange', 'k', 0, 3), ['peach', 'pear', 'apple', 'orange']], + [('rpush', 'k', 'grape', 'banana', 'tomato'), 7], + [('lrange', 'k', 0, 7), ['peach', 'pear', 'apple', 'orange', 'grape', 'banana', 'tomato']], + [('lpop', 'k'), 'peach'], + [('rpop', 'k'), 'tomato'], + [('rpoplpush', 'k', 'k'), 'banana'], + [('lpushx', 'k', 'peach'), 6], + [('rpushx', 'k', 'peach'), 7], + [('lrem', 'k', 1, 'apple'), 1], + [('lrem', 'k', 5, 'peach'), 2], + [('lrange', 'k', 0, 7), ['banana', 'pear', 'orange', 'grape']], + [('linsert', 'k', 'BEFORE', 'pear', 'peach'), 5], + [('linsert', 'k', 'AFTER', 'orange', 'tomato'), 6], + [('linsert', 'k', 'AFTER', 'apple', 'tomato'), -1], + [('lrange', 'k', 0, 7), ['banana', 'peach', 'pear', 'orange', 'tomato', 'grape']], + [('ltrim', 'k', 0, 4), 'OK'], + [('ltrim', 'k', 1, -1), 'OK'], + [('lrange', 'k', 0, 7), ['peach', 'pear', 'orange', 'tomato']], + [('blpop', 'k', 0), ['k', 'peach']], + [('brpop', 'k', 0), ['k', 'tomato']], + [('brpoplpush', 'k', 'k', 0), 'orange'], + [('lrange', 'k', 0, 7), ['orange', 'pear']], + [('del', 'k'), 1], + [('lpushx', 'k', 'peach'), 0], + [('rpushx', 'k', 'peach'), 0], + ]), + ('set', [ + [('del', 'k', '{k}2', '{k}3', '{k}4', '{k}5', '{k}6'), ], + [('sadd', 'k', 'apple'), 1], + [('scard', 'k'), 1], + [('sadd', 'k', 'apple'), 0], + [('scard', 'k'), 1], + [('sadd', 'k', 'apple', 'pear', 'orange', 'banana'), 3], + [('scard', 'k'), 4], + [('sismember', 'k', 'apple'), 1], + [('sismember', 'k', 'grape'), 0], + [('smembers', 'k'), lambda x:len(x)==4], + [('srandmember', 'k'), lambda x:x in ['apple', 'pear', 'orange', 'banana']], + [('srandmember', 'k', 2), lambda x:len(x)==2], + [('sscan', 'k', 0), lambda x:len(x)==2], + [('sscan', 'k', 0, 'match', 'a*'), lambda x:len(x)==2 and x[1][0]=='apple'], + [('sscan', 'k', 0, 'count', 2), lambda x:len(x)==2 and len(x[1])>=2], + [('srem', 'k', 'apple'), 1], + [('srem', 'k', 'apple'), 0], + [('scard', 'k'), 3], + [('srem', 'k', 'pear', 'orange'), 2], + [('scard', 'k'), 1], + [('sadd', '{k}2', 'apple', 'pear', 'orange', 'banana'), 4], + [('sdiff', '{k}2', 'k'), lambda x:len(x)==3], + [('sadd', '{k}3', 'apple', 'pear'), 2], + [('sdiff', '{k}2', 'k', '{k}3'), ['orange']], + [('sdiffstore', '{k}4', '{k}2', 'k', '{k}3'), 1], + [('sinter', '{k}2', 'k'), ['banana']], + [('sinterstore', '{k}5', '{k}2', 'k'), 1], + [('sunion', '{k}3', 'k'), lambda x:len(x)==3], + [('sunionstore', '{k}6', '{k}3', 'k'), 3], + [('smove', '{k}2', 'k', 'apple'), 1], + [('scard', 'k'), 2], + [('scard', '{k}2'), 3], + ]), + ('zset', [ + [('del', 'k', '{k}2', '{k}3', '{k}4'), ], + [('zadd', 'k', 10, 'apple'), 1], + [('zcard', 'k'), 1], + [('zincrby', 'k', 2, 'apple'), '12'], + [('zincrby', 'k', -2, 'apple'), '10'], + [('zadd', 'k', 15, 'pear', 20, 'orange', 30, 'banana'), 3], + [('zcard', 'k'), 4], + [('zscore', 'k', 'pear'), '15'], + [('zrank', 'k', 'apple'), 0], + [('zrank', 'k', 'orange'), 2], + [('zcount', 'k', '-inf', '+inf'), 4], + [('zcount', 'k', 1, 10), 1], + [('zcount', 'k', 15, 20), 2], + [('zlexcount', 'k', '[a', '[z'), 4], + [('zscan', 'k', 0), lambda x:len(x)==2 and len(x[1])==8], + [('zscan', 'k', 0, 'MATCH', 'o*'), ['0', ['orange', '20']]], + [('zrange', 'k', 0, 2), ['apple', 'pear', 'orange']], + [('zrange', 'k', -2, -1), ['orange', 'banana']], + [('zrange', 'k', 0, 2, 'WITHSCORES'), ['apple', '10', 'pear', '15', 'orange', '20']], + [('zrangebylex', 'k', '-', '+'), lambda x:len(x)==4], + [('zrangebylex', 'k', '-', '+', 'LIMIT', 1, 2), lambda x:len(x)==2], + [('zrangebyscore', 'k', '10', '(20'), ['apple', 'pear']], + [('zrangebyscore', 'k', '-inf', '+inf', 'LIMIT', 1, 2), ['pear', 'orange']], + [('zrangebyscore', 'k', '-inf', '+inf', 'WITHSCORES', 'LIMIT', 1, 2), ['pear', '15', 'orange', '20']], + [('zrevrange', 'k', 0, 2), ['banana', 'orange', 'pear']], + [('zrevrange', 'k', -2, -1), ['pear', 'apple']], + [('zrevrange', 'k', 0, 2, 'WITHSCORES'), ['banana', '30', 'orange', '20', 'pear', '15']], + [('zrevrangebylex', 'k', '+', '-'), lambda x:len(x)==4], + [('zrevrangebylex', 'k', '+', '-', 'LIMIT', 1, 2), lambda x:len(x)==2], + [('zrevrangebyscore', 'k', '(20', '10'), ['pear', 'apple']], + [('zrevrangebyscore', 'k', '+inf', '-inf', 'LIMIT', 1, 2), ['orange', 'pear']], + [('zrevrangebyscore', 'k', '+inf', '-inf', 'WITHSCORES', 'LIMIT', 1, 2), ['orange', '20', 'pear', '15']], + [('zrem', 'k', 'apple'), 1], + [('zrem', 'k', 'apple'), 0], + [('zremrangebyrank', 'k', '0', '1'), 2], + [('zadd', 'k', 15, 'pear', 20, 'orange', 30, 'banana'), 2], + [('zremrangebyscore', 'k', '20', '30'), 2], + [('zadd', 'k', 'NX', 0, 'pear', 0, 'orange', 0, 'banana'), 2], + [('zremrangebylex', 'k', '[banana', '(cat'), 1], + [('zadd', 'k', 15, 'pear', 20, 'orange', 30, 'banana'), 1], + [('zadd', '{k}2', 10, 'apple', 15, 'pear'), 2], + [('zinterstore', '{k}3', 2, 'k', '{k}2'), 1], + [('zinterstore', '{k}3', 2, 'k', '{k}2', 'AGGREGATE', 'MAX'), 1], + [('zinterstore', '{k}3', 2, 'k', '{k}2', 'WEIGHTS', 0.5, 1.2, 'AGGREGATE', 'MAX'), 1], + [('zunionstore', '{k}3', 2, 'k', '{k}2'), 4], + [('zunionstore', '{k}3', 2, 'k', '{k}2', 'AGGREGATE', 'MAX'), 4], + [('zunionstore', '{k}3', 2, 'k', '{k}2', 'WEIGHTS', 0.5, 1.2, 'AGGREGATE', 'MAX'), 4], + ]), + ('hyperloglog', [ + [('del', 'k', '{k}2', '{k}3'), ], + [('pfadd', 'k', 'a', 'b', 'c', 'd'), 1], + [('pfcount', 'k'), 4], + [('pfadd', '{k}2', 'c', 'd', 'e', 'f'), 1], + [('pfcount', '{k}2'), 4], + [('pfmerge', '{k}3', 'k', '{k}2'), 'OK'], + [('pfcount', '{k}3'), 6], + ]), + ('geo', [ + [('del', 'k'), ], + [('geoadd', 'k', 116, 40, 'beijing'), 1], + [('geoadd', 'k', 121.5, 30.8, 'shanghai', 114, 22.3, 'shenzhen'), 2], + [('geoadd', 'k', -74, 40.3, 'new york', 151.2, -33.9, 'sydney'), 2], + [('geodist', 'k', 'beijing', 'shanghai'), lambda x:x>1000000], + [('geodist', 'k', 'beijing', 'shanghai', 'km'), lambda x:x>1000], + [('geohash', 'k', 'beijing', 'shanghai'), lambda x:len(x)==2], + [('geopos', 'k', 'beijing'), lambda x:len(x)==1 and len(x[0])==2], + [('geopos', 'k', 'beijing', 'shanghai'), lambda x:len(x)==2 and len(x[1])==2], + [('georadius', 'k', 140, 35, 3000, 'km'), lambda x:len(x)==3], + [('georadius', 'k', 140, 35, 3000, 'km', 'WITHDIST', 'ASC'), lambda x:len(x)==3 and x[0][0]=='shanghai' and x[1][0]=='beijing' and x[2][0]=='shenzhen'], + [('georadiusbymember', 'k', 'shanghai', 2000, 'km'), lambda x:len(x)==3], + [('georadiusbymember', 'k', 'shanghai', 3000, 'km', 'WITHDIST', 'ASC'), lambda x:len(x)==3 and x[0][0]=='shanghai' and x[1][0]=='beijing' and x[2][0]=='shenzhen'], + ]), + ('clean', [ + [('del', 'k'), ], + ]), +] + +TransactionCases = [ + ('multi-exec', [ + [('multi',), 'OK'], + [('set', 'k', 'v'), 'QUEUED'], + [('get', 'k'), 'QUEUED'], + [('exec',), ['OK', 'v']], + ]), + ('multi-discard', [ + [('multi',), 'OK'], + [('set', 'k', 'v'), 'QUEUED'], + [('get', 'k'), 'QUEUED'], + [('discard',), 'OK'], + ]), + ('watch-multi-exec', [ + [('watch', 'k'), 'OK'], + [('watch', '{k}2', '{k}3'), 'OK'], + [('multi',), 'OK'], + [('set', 'k', 'v'), 'QUEUED'], + [('get', 'k'), 'QUEUED'], + [('exec',), ['OK', 'v']], + ]), +] + +def check(cmd, r): + if len(cmd) == 1: + print('EXEC %s' % (str(cmd[0]),)) + return True + if hasattr(cmd[1], '__call__'): + isPass = cmd[1](r) + else: + isPass = r == cmd[1] + if isPass: + print('PASS %s:%s' % (str(cmd[0]), repr(r))) + else: + print('FAIL %s:%s != %s' % (str(cmd[0]), repr(r), repr(cmd[1]))) + return False + return True + + +def testCase(name, cmds): + print('---------- %s --------' % name) + succ = True + for cmd in cmds: + try: + r = c.execute_command(*cmd[0]) + if not check(cmd, r): + succ = False + except Exception as excp: + succ = False + if len(cmd) > 1: + print('EXCP %s:%s %s' % (str(cmd[0]), str(cmd[1]), str(excp))) + else: + print('EXCP %s %s' % (str(cmd[0]), str(excp))) + return succ + +def pipelineTestCase(name, cmds): + print('---------- %s pipeline --------' % name) + succ = True + p = c.pipeline(transaction=False) + try: + for cmd in cmds: + p.execute_command(*cmd[0]) + res = p.execute() + for i in xrange(0, len(cmds)): + if not check(cmds[i], res[i]): + succ = False + except Exception as excp: + succ = False + print('EXCP %s' % str(excp)) + return succ + +if __name__ == '__main__': + parser = argparse.ArgumentParser(conflict_handler='resolve') + parser.add_argument('-t', default=False, action='store_true', help='enable transaction test') + parser.add_argument('-h', nargs='?', default='127.0.0.1', help='host') + parser.add_argument('-p', nargs='?', default=7617, type=int, help='port') + parser.add_argument('case', nargs='*', default=None, help='specify test case') + args = parser.parse_args() + a = set() + host = '127.0.0.1' if not args.h else args.h + port = 7617 if not args.p else args.p + c = redis.StrictRedis(host=host, port=port) + if args.case: + a = set(args.case) + fails = [] + for case in Cases: + if len(a) == 0 or case[0] in a: + if not testCase(case[0], case[1]) or not pipelineTestCase(case[0], case[1]): + fails.append(case[0]) + if args.t or 'transaction' in a: + succ = True + for case in TransactionCases: + if not pipelineTestCase(case[0], case[1]): + succ = False + if not succ: + fails.append('transaction') + print('--------------------------------------------') + if len(fails) > 0: + print('******* Some case test fail *****') + for cmd in fails: + print cmd + else: + print('Good! all Case Pass.') + diff --git a/test/pubsub.py b/test/pubsub.py new file mode 100755 index 0000000..a5c5bfb --- /dev/null +++ b/test/pubsub.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# predixy - A high performance and full features proxy for redis. +# Copyright (C) 2017 Joyield, Inc. +# All rights reserved. + +import time +import redis +import sys +import argparse + +c1 = None +c2 = None + +def test(): + ps = c1.pubsub() + stats = [ + [ps, 'subscribe', ['ch']], + [ps, 'get_message', [], {'pattern': None, 'type': 'subscribe', 'channel': 'ch', 'data': 1L}], + [c2, 'publish', ['ch', 'hello'], 1], + [ps, 'get_message', [], {'pattern': None, 'type': 'message', 'channel': 'ch', 'data': 'hello'}], + [ps, 'subscribe', ['ch1', 'ch2']], + [ps, 'get_message', [], {'pattern': None, 'type': 'subscribe', 'channel': 'ch1', 'data': 2L}], + [ps, 'get_message', [], {'pattern': None, 'type': 'subscribe', 'channel': 'ch2', 'data': 3L}], + [c2, 'publish', ['ch1', 'channel1'], lambda x:True], + [c2, 'publish', ['ch2', 'channel2'], lambda x:True], + [ps, 'get_message', [], {'pattern': None, 'type': 'message', 'channel': 'ch1', 'data': 'channel1'}], + [ps, 'get_message', [], {'pattern': None, 'type': 'message', 'channel': 'ch2', 'data': 'channel2'}], + [ps, 'psubscribe', ['ch*']], + [ps, 'get_message', [], {'pattern': None, 'type': 'psubscribe', 'channel': 'ch*', 'data': 4L}], + [c2, 'publish', ['ch', 'hello'], 2], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['data']=='hello'], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['data']=='hello'], + [ps, 'psubscribe', ['ch1*', 'ch2*']], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='psubscribe'], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='psubscribe'], + [ps, 'unsubscribe', ['ch']], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='unsubscribe'], + [c2, 'publish', ['ch', 'hello'], 1], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['data']=='hello'], + [ps, 'punsubscribe', ['ch*']], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='punsubscribe'], + [ps, 'unsubscribe', ['ch1', 'ch2']], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='unsubscribe'], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='unsubscribe'], + [ps, 'punsubscribe', ['ch1*', 'ch2*']], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='punsubscribe'], + [ps, 'get_message', [], lambda x:type(x)==type({}) and x['type']=='punsubscribe'], + ] + def run(stat): + func = getattr(stat[0], stat[1]) + r = func(*stat[2]) + if len(stat) == 3: + print('EXEC %s(*%s)' % (stat[1], repr(stat[2]))) + return True + if hasattr(stat[3], '__call__'): + isPass = stat[3](r) + else: + isPass = r == stat[3] + if isPass: + print('PASS %s(*%s):%s' % (stat[1], repr(stat[2]), repr(r))) + return True + else: + print('FAIL %s(*%s):%s != %s' % (stat[1], repr(stat[2]), repr(r), repr(stat[3]))) + return False + + succ = True + for stat in stats: + if not run(stat): + succ = False + time.sleep(0.2) + print '---------------------------------' + if succ: + print 'Good! PubSub test pass' + else: + print 'Oh! PubSub some case fail' + + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(conflict_handler='resolve') + parser.add_argument('-h', nargs='?', default='127.0.0.1', help='host') + parser.add_argument('-p', nargs='?', default=7617, type=int, help='port') + args = parser.parse_args() + host = '127.0.0.1' if not args.h else args.h + port = 7617 if not args.p else args.p + c1 = redis.StrictRedis(host=host, port=port) + c2 = redis.StrictRedis(host=host, port=port) + test() + +