From 210d51258b403aa3d8a6a78d9b8a22ff821f20aa Mon Sep 17 00:00:00 2001 From: holys Date: Wed, 3 Sep 2014 22:00:31 +0800 Subject: [PATCH 1/3] info bug fix --- client/ledis-py/ledis/client.py | 39 ++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/client/ledis-py/ledis/client.py b/client/ledis-py/ledis/client.py index 034f2e3..5547d6a 100644 --- a/client/ledis-py/ledis/client.py +++ b/client/ledis-py/ledis/client.py @@ -64,6 +64,30 @@ def int_or_none(response): return int(response) +def parse_info(response): + + info = {} + response = nativestr(response) + + def get_value(value): + if ',' not in value or '=' not in value: + try: + if '.' in value: + return float(value) + else: + return int(value) + except ValueError: + return value + + for line in response.splitlines(): + if line and not line.startswith('#'): + if line.find(':') != -1: + key, value = line.split(':', 1) + info[key] = get_value(value) + + return info + + class Ledis(object): """ Implementation of the Redis protocol. @@ -111,7 +135,8 @@ class Ledis(object): 'HGETALL': lambda r: r and pairs_to_dict(r) or {}, 'PING': lambda r: nativestr(r) == 'PONG', 'SET': lambda r: r and nativestr(r) == 'OK', - }, + 'INFO': parse_info, + } ) @@ -219,8 +244,15 @@ class Ledis(object): db = 0 return self.execute_command('SELECT', db) - def info(self, section): - return self.execute_command('PING', section) + def info(self, section=None): + """ + Return + """ + + if section is None: + return self.execute_command("INFO") + else: + return self.execute_command('INFO', section) def flushall(self): return self.execute_command('FLUSHALL') @@ -924,6 +956,7 @@ class Ledis(object): def scriptflush(self): return self.execute_command('SCRIPT', 'FLUSH') + class Transaction(Ledis): def __init__(self, connection_pool, response_callbacks): self.connection_pool = connection_pool From 6f3f04970703a567adc08b14771540cd9bee1dc6 Mon Sep 17 00:00:00 2001 From: holys Date: Wed, 3 Sep 2014 23:01:10 +0800 Subject: [PATCH 2/3] add tests for py client --- client/ledis-py/tests/all.sh | 7 +++++ client/ledis-py/tests/test_cmd_script.py | 36 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 client/ledis-py/tests/all.sh diff --git a/client/ledis-py/tests/all.sh b/client/ledis-py/tests/all.sh new file mode 100644 index 0000000..163ee04 --- /dev/null +++ b/client/ledis-py/tests/all.sh @@ -0,0 +1,7 @@ +dbs=(leveldb rocksdb hyperleveldb goleveldb boltdb lmdb) +for db in "${dbs[@]}" +do + ledis-server -db_name=$db & + py.test + killall ledis-server +done diff --git a/client/ledis-py/tests/test_cmd_script.py b/client/ledis-py/tests/test_cmd_script.py index 02d7630..4a08cb5 100644 --- a/client/ledis-py/tests/test_cmd_script.py +++ b/client/ledis-py/tests/test_cmd_script.py @@ -12,14 +12,44 @@ from util import expire_at, expire_at_seconds l = ledis.Ledis(port=6380) +simple_script = "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" + + class TestCmdScript(unittest.TestCase): def setUp(self): pass def tearDown(self): - pass + l.flushdb() + + def test_eval(self): + assert l.eval(simple_script, ["key1", "key2"], "first", "second") == ["key1", "key2", "first", "second"] + + def test_evalsha(self): + sha1 = l.scriptload(simple_script) + assert len(sha1) == 40 + + assert l.evalsha(sha1, ["key1", "key2"], "first", "second") == ["key1", "key2", "first", "second"] + + def test_scriptload(self): + sha1 = l.scriptload(simple_script) + assert len(sha1) == 40 + + def test_scriptexists(self): + sha1 = l.scriptload(simple_script) + assert l.scriptexists(sha1) == [1L] + + def test_scriptflush(self): + sha1 = l.scriptload(simple_script) + assert l.scriptexists(sha1) == [1L] + assert l.scriptflush() == 'OK' + + assert l.scriptexists(sha1) == [0L] + + + + + - def testEval(self): - assert l.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", ["key1", "key2"], "first", "second") == ["key1", "key2", "first", "second"] \ No newline at end of file From ba9f611488337352447b785a3f150b7fb87a9073 Mon Sep 17 00:00:00 2001 From: holys Date: Thu, 4 Sep 2014 12:53:19 +0800 Subject: [PATCH 3/3] *scan commands bug fix; add more tests --- Makefile | 3 + client/ledis-py/Makefile | 5 ++ client/ledis-py/ledis/client.py | 49 ++++++++++---- client/ledis-py/ledis/connection.py | 20 ++++++ client/ledis-py/tests/all.sh | 2 +- client/ledis-py/tests/test_cmd_bit.py | 3 +- client/ledis-py/tests/test_cmd_hash.py | 2 +- client/ledis-py/tests/test_cmd_kv.py | 2 +- client/ledis-py/tests/test_cmd_list.py | 2 +- client/ledis-py/tests/test_cmd_set.py | 2 +- client/ledis-py/tests/test_cmd_zset.py | 2 +- client/ledis-py/tests/test_others.py | 94 +++++++++++++++++++++++++- client/ledis-py/tests/test_tx.py | 12 +++- 13 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 client/ledis-py/Makefile diff --git a/Makefile b/Makefile index e2725be..d98fc4d 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,6 @@ clean: test: go test -tags '$(GO_BUILD_TAGS)' ./... + +pytest: + sh client/ledis-py/tests/all.sh diff --git a/client/ledis-py/Makefile b/client/ledis-py/Makefile new file mode 100644 index 0000000..9f53bed --- /dev/null +++ b/client/ledis-py/Makefile @@ -0,0 +1,5 @@ +.PHONY: test + + +test: + sh tests/all.sh diff --git a/client/ledis-py/ledis/client.py b/client/ledis-py/ledis/client.py index 5547d6a..6615c91 100644 --- a/client/ledis-py/ledis/client.py +++ b/client/ledis-py/ledis/client.py @@ -4,7 +4,7 @@ import time as mod_time from itertools import chain, starmap from ledis._compat import (b, izip, imap, iteritems, basestring, long, nativestr, bytes) -from ledis.connection import ConnectionPool, UnixDomainSocketConnection +from ledis.connection import ConnectionPool, UnixDomainSocketConnection, Token from ledis.exceptions import ( ConnectionError, DataError, @@ -87,6 +87,7 @@ def parse_info(response): return info +# def parse_lscan(response, ) class Ledis(object): """ @@ -138,6 +139,7 @@ class Ledis(object): 'INFO': parse_info, } + ) @classmethod @@ -382,8 +384,21 @@ class Ledis(object): "Removes an expiration on name" return self.execute_command('PERSIST', name) - def scan(self, key, match = "", count = 10): - return self.execute_command("SCAN", key, match, count) + def scan(self, key="" , match=None, count=10): + pieces = [key] + if match is not None: + pieces.extend(["MATCH", match]) + + pieces.extend(["COUNT", count]) + + return self.execute_command("SCAN", *pieces) + + def scan_iter(self, match=None, count=10): + key = "" + while key != "": + key, data = self.scan(key=key, match=match, count=count) + for item in data: + yield item #### LIST COMMANDS #### def lindex(self, name, index): @@ -460,8 +475,8 @@ class Ledis(object): "Removes an expiration on ``name``" return self.execute_command('LPERSIST', name) - def lscan(self, key, match = "", count = 10): - return self.execute_command("LSCAN", key, match, count) + def lscan(self, key="", match=None, count=10): + return self.scan_generic("LSCAN", key=key, match=match, count=count) #### SET COMMANDS #### @@ -560,8 +575,8 @@ class Ledis(object): "Removes an expiration on name" return self.execute_command('SPERSIST', name) - def sscan(self, key, match = "", count = 10): - return self.execute_command("SSCAN", key, match, count) + def sscan(self, key="", match=None, count = 10): + return self.scan_generic("SSCAN", key=key, match=match, count=count) #### SORTED SET COMMANDS #### @@ -759,9 +774,17 @@ class Ledis(object): "Removes an expiration on name" return self.execute_command('ZPERSIST', name) - def zscan(self, key, match = "", count = 10): - return self.execute_command("ZSCAN", key, match, count) + def scan_generic(self, scan_type, key="", match=None, count=10): + pieces = [key] + if match is not None: + pieces.extend([Token("MATCH"), match]) + pieces.extend([Token("count"), count]) + scan_type = scan_type.upper() + return self.execute_command(scan_type, *pieces) + + def zscan(self, key="", match=None, count=10): + return self.scan_generic("ZSCAN", key=key, match=match, count=count) #### HASH COMMANDS #### def hdel(self, name, *keys): @@ -855,8 +878,8 @@ class Ledis(object): "Removes an expiration on name" return self.execute_command('HPERSIST', name) - def hscan(self, key, match = "", count = 10): - return self.execute_command("HSCAN", key, match, count) + def hscan(self, key="", match=None, count=10): + return self.scan_generic("HSCAN", key=key, match=match, count=count) ### BIT COMMANDS @@ -934,8 +957,8 @@ class Ledis(object): "Removes an expiration on name" return self.execute_command('BPERSIST', name) - def bscan(self, key, match = "", count = 10): - return self.execute_command("BSCAN", key, match, count) + def bscan(self, key="", match=None, count=10): + return self.scan_generic("BSCAN", key=key, match=match, count=count) def eval(self, script, keys, *args): n = len(keys) diff --git a/client/ledis-py/ledis/connection.py b/client/ledis-py/ledis/connection.py index 4a39317..5372838 100644 --- a/client/ledis-py/ledis/connection.py +++ b/client/ledis-py/ledis/connection.py @@ -588,3 +588,23 @@ class BlockingConnectionPool(object): timeout=self.timeout, connection_class=self.connection_class, queue_class=self.queue_class, **self.connection_kwargs) + + +class Token(object): + """ + Literal strings in Redis commands, such as the command names and any + hard-coded arguments are wrapped in this class so we know not to apply + and encoding rules on them. + """ + def __init__(self, value): + if isinstance(value, Token): + value = value.value + self.value = value + + def __repr__(self): + return self.value + + def __str__(self): + return self.value + + diff --git a/client/ledis-py/tests/all.sh b/client/ledis-py/tests/all.sh index 163ee04..8b7ae0f 100644 --- a/client/ledis-py/tests/all.sh +++ b/client/ledis-py/tests/all.sh @@ -1,7 +1,7 @@ dbs=(leveldb rocksdb hyperleveldb goleveldb boltdb lmdb) for db in "${dbs[@]}" do + killall ledis-server ledis-server -db_name=$db & py.test - killall ledis-server done diff --git a/client/ledis-py/tests/test_cmd_bit.py b/client/ledis-py/tests/test_cmd_bit.py index c1976a7..3d8d0c5 100644 --- a/client/ledis-py/tests/test_cmd_bit.py +++ b/client/ledis-py/tests/test_cmd_bit.py @@ -17,8 +17,7 @@ class TestCmdBit(unittest.TestCase): pass def tearDown(self): - l.bdelete('a') - l.bdelete('non_exists_key') + l.flushdb() def test_bget(self): "bget is the same as get in K/V commands" diff --git a/client/ledis-py/tests/test_cmd_hash.py b/client/ledis-py/tests/test_cmd_hash.py index 8a89af2..5efc86f 100644 --- a/client/ledis-py/tests/test_cmd_hash.py +++ b/client/ledis-py/tests/test_cmd_hash.py @@ -19,7 +19,7 @@ class TestCmdHash(unittest.TestCase): pass def tearDown(self): - l.hmclear('myhash', 'a') + l.flushdb() def test_hdel(self): diff --git a/client/ledis-py/tests/test_cmd_kv.py b/client/ledis-py/tests/test_cmd_kv.py index 774b800..b556c7a 100644 --- a/client/ledis-py/tests/test_cmd_kv.py +++ b/client/ledis-py/tests/test_cmd_kv.py @@ -18,7 +18,7 @@ class TestCmdKv(unittest.TestCase): pass def tearDown(self): - l.delete('a', 'b', 'c', 'non_exist_key') + l.flushdb() def test_decr(self): assert l.delete('a') == 1 diff --git a/client/ledis-py/tests/test_cmd_list.py b/client/ledis-py/tests/test_cmd_list.py index f87b8a1..065cee5 100644 --- a/client/ledis-py/tests/test_cmd_list.py +++ b/client/ledis-py/tests/test_cmd_list.py @@ -18,7 +18,7 @@ class TestCmdList(unittest.TestCase): pass def tearDown(self): - l.lmclear('mylist', 'mylist1', 'mylist2') + l.flushdb() def test_lindex(self): l.rpush('mylist', '1', '2', '3') diff --git a/client/ledis-py/tests/test_cmd_set.py b/client/ledis-py/tests/test_cmd_set.py index 0d2eec9..e98a762 100644 --- a/client/ledis-py/tests/test_cmd_set.py +++ b/client/ledis-py/tests/test_cmd_set.py @@ -20,7 +20,7 @@ class TestCmdSet(unittest.TestCase): pass def tearDown(self): - l.smclear('a', 'b', 'c') + l.flushdb() def test_sadd(self): members = set([b('1'), b('2'), b('3')]) diff --git a/client/ledis-py/tests/test_cmd_zset.py b/client/ledis-py/tests/test_cmd_zset.py index 08233fc..9277fce 100644 --- a/client/ledis-py/tests/test_cmd_zset.py +++ b/client/ledis-py/tests/test_cmd_zset.py @@ -17,7 +17,7 @@ class TestCmdZset(unittest.TestCase): pass def tearDown(self): - l.zclear('a') + l.flushdb() def test_zadd(self): l.zadd('a', a1=1, a2=2, a3=3) diff --git a/client/ledis-py/tests/test_others.py b/client/ledis-py/tests/test_others.py index d57d332..2cd7110 100644 --- a/client/ledis-py/tests/test_others.py +++ b/client/ledis-py/tests/test_others.py @@ -10,13 +10,14 @@ from ledis._compat import b from ledis import ResponseError l = ledis.Ledis(port=6380) +dbs = ["leveldb", "rocksdb", "goleveldb", "hyperleveldb", "lmdb", "boltdb"] class TestOtherCommands(unittest.TestCase): def setUp(self): pass def tearDown(self): - pass + l.flushdb() # server information def test_echo(self): @@ -28,4 +29,93 @@ class TestOtherCommands(unittest.TestCase): def test_select(self): assert l.select('1') assert l.select('15') - self.assertRaises(ResponseError, lambda: l.select('16')) \ No newline at end of file + self.assertRaises(ResponseError, lambda: l.select('16')) + + + def test_info(self): + info1 = l.info() + assert info1.get("db_name") in dbs + info2 = l.info(section="server") + assert info2.get("os") in ["linux", "darwin"] + + def test_flushdb(self): + l.set("a", 1) + assert l.flushdb() == "OK" + assert l.get("a") is None + + def test_flushall(self): + l.select(1) + l.set("a", 1) + assert l.get("a") == b("1") + + l.select(10) + l.set("a", 1) + assert l.get("a") == b("1") + + assert l.flushall() == "OK" + + assert l.get("a") is None + l.select(1) + assert l.get("a") is None + + + # test *scan commands + + def check_keys(self, scan_type): + d = { + "scan": l.scan, + "sscan": l.sscan, + "lscan": l.lscan, + "hscan": l.hscan, + "zscan": l.zscan, + "bscan": l.bscan + } + + key, keys = d[scan_type]() + assert key == "" + assert set(keys) == set([b("a"), b("b"), b("c")]) + + _, keys = d[scan_type](match="a") + assert set(keys) == set([b("a")]) + + _, keys = d[scan_type](key="a") + assert set(keys) == set([b("b"), b("c")]) + + + def test_scan(self): + d = {"a":1, "b":2, "c": 3} + l.mset(d) + self.check_keys("scan") + + + def test_lscan(self): + l.rpush("a", 1) + l.rpush("b", 1) + l.rpush("c", 1) + self.check_keys("lscan") + + + def test_hscan(self): + l.hset("a", "hello", "world") + l.hset("b", "hello", "world") + l.hset("c", "hello", "world") + self.check_keys("hscan") + + def test_sscan(self): + l.sadd("a", 1) + l.sadd("b", 2) + l.sadd("c", 3) + self.check_keys("sscan") + + def test_zscan(self): + l.zadd("a", 1, "a") + l.zadd("b", 1, "a") + l.zadd("c", 1, "a") + self.check_keys("zscan") + + def test_bscan(self): + l.bsetbit("a", 1, 1) + l.bsetbit("b", 1, 1) + l.bsetbit("c", 1, 1) + self.check_keys("bscan") + diff --git a/client/ledis-py/tests/test_tx.py b/client/ledis-py/tests/test_tx.py index b589dc7..cfbab20 100644 --- a/client/ledis-py/tests/test_tx.py +++ b/client/ledis-py/tests/test_tx.py @@ -4,14 +4,21 @@ sys.path.append("..") import ledis +global_l = ledis.Ledis() + +#db that do not support transaction +dbs = ["leveldb", "rocksdb", "hyperleveldb", "goleveldb"] +check = global_l.info().get("db_name") in dbs + class TestTx(unittest.TestCase): def setUp(self): self.l = ledis.Ledis(port=6380) def tearDown(self): - self.l.delete("a") - + self.l.flushdb() + + @unittest.skipIf(check, reason="db not support transaction") def test_commit(self): tx = self.l.tx() self.l.set("a", "no-tx") @@ -24,6 +31,7 @@ class TestTx(unittest.TestCase): tx.commit() assert self.l.get("a") == "tx" + @unittest.skipIf(check, reason="db not support transaction") def test_rollback(self): tx = self.l.tx() self.l.set("a", "no-tx")