From ba9f611488337352447b785a3f150b7fb87a9073 Mon Sep 17 00:00:00 2001 From: holys Date: Thu, 4 Sep 2014 12:53:19 +0800 Subject: [PATCH] *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")