From 6e068ef40d10614c60c67b277323d7247a4cbbf5 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Mon, 15 Dec 2014 12:41:02 -0800 Subject: [PATCH 01/27] Bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9499424..db7d98c 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages # Match releases to redis-py versions -__version__ = '2.9.0.10' +__version__ = '2.9.0.11' # Jenkins will replace __build__ with a unique value. __build__ = '' From 8748e663cebf4e4995d4ce769b8843c94c564404 Mon Sep 17 00:00:00 2001 From: xiaogaozi Date: Tue, 23 Dec 2014 21:03:54 +0800 Subject: [PATCH 02/27] Support `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter` --- CHANGES.md | 3 ++ README.md | 8 ++-- mockredis/client.py | 35 +++++++++++++++ mockredis/tests/test_scan.py | 84 ++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 12be2ff..20de6a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +Version 2.9.0.11 + - Support: `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter` + Version 2.9.0.10 - Return & store byte strings everywhere (unicode turns into utf-8 by default) - Fix *SCAN returning non-long values. diff --git a/README.md b/README.md index c7786fc..67129e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mock for the redis-py client library -Supports writing tests for code using the [redis-py][redis-py] library +Supports writing tests for code using the [redis-py][redis-py] library without requiring a [redis-server][redis] install. [![Build Status](https://travis-ci.org/locationlabs/mockredis.png)](https://travis-ci.org/locationlabs/mockredis) @@ -17,11 +17,11 @@ Both `mockredis.mock_redis_client` and `mockredis.mock_strict_redis_client` can used to patch instances of the *redis client*. For example, using the [mock][mock] library: - + @patch('redis.Redis', mock_redis_client) - + Or: - + @patch('redis.StrictRedis', mock_strict_redis_client) ## Testing diff --git a/mockredis/client.py b/mockredis/client.py index 32ef6fc..43c2b08 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -820,6 +820,14 @@ def value_function(): return sorted(self.redis.keys()) # sorted list for consistent order return self._common_scan(value_function, cursor=cursor, match=match, count=count) + def scan_iter(self, match=None, count=10): + """Emulate scan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.scan(cursor=cursor, match=match, count=count) + for item in data: + yield item + def sscan(self, name, cursor='0', match=None, count=10): """Emulate sscan.""" def value_function(): @@ -828,6 +836,15 @@ def value_function(): return members return self._common_scan(value_function, cursor=cursor, match=match, count=count) + def sscan_iter(self, name, match=None, count=10): + """Emulate sscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.sscan(name, cursor=cursor, + match=match, count=count) + for item in data: + yield item + def zscan(self, name, cursor='0', match=None, count=10): """Emulate zscan.""" def value_function(): @@ -836,6 +853,15 @@ def value_function(): return values return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) + def zscan_iter(self, name, match=None, count=10): + """Emulate zscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.zscan(name, cursor=cursor, match=match, + count=count) + for item in data: + yield item + def hscan(self, name, cursor='0', match=None, count=10): """Emulate hscan.""" def value_function(): @@ -847,6 +873,15 @@ def value_function(): scanned[1] = dict(scanned[1]) # from list of tuples back to dict return scanned + def hscan_iter(self, name, match=None, count=10): + """Emulate hscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.hscan(name, cursor=cursor, + match=match, count=count) + for item in data.items(): + yield item + #### SET COMMANDS #### def sadd(self, key, *values): diff --git a/mockredis/tests/test_scan.py b/mockredis/tests/test_scan.py index bb74833..7fe572a 100644 --- a/mockredis/tests/test_scan.py +++ b/mockredis/tests/test_scan.py @@ -25,6 +25,26 @@ def eq_scan(results, cursor, elements): eq_scan(self.redis.zscan("foo"), 0, []) eq_scan(self.redis.hscan("foo"), 0, {}) + keys = [] + for k in self.redis.scan_iter(): + keys.append(k) + eq_(keys, []) + + members = [] + for m in self.redis.sscan_iter('foo'): + members.append(m) + eq_(members, []) + + members = [] + for m in self.redis.zscan_iter('foo'): + members.append(m) + eq_(members, []) + + members = [] + for m in self.redis.hscan_iter('foo'): + members.append(m) + eq_(members, []) + class TestRedisScan(object): """SCAN tests""" @@ -60,21 +80,37 @@ def do_full_scan(match, count): eq_(do_full_scan('*abc*', 1), abc_keys) eq_(do_full_scan('*abc*', 2), abc_keys) eq_(do_full_scan('*abc*', 10), abc_keys) + keys = set() + for k in self.redis.scan_iter('*abc*'): + keys.add(k) + eq_(keys, abc_keys) xyz_keys = set([b'key_xyz_1', b'key_xyz_2', b'key_xyz_3', b'key_xyz_4', b'key_xyz_5']) eq_(do_full_scan('*xyz*', 1), xyz_keys) eq_(do_full_scan('*xyz*', 2), xyz_keys) eq_(do_full_scan('*xyz*', 10), xyz_keys) + keys = set() + for k in self.redis.scan_iter('*xyz*'): + keys.add(k) + eq_(keys, xyz_keys) one_keys = set([b'key_abc_1', b'key_xyz_1']) eq_(do_full_scan('*_1', 1), one_keys) eq_(do_full_scan('*_1', 2), one_keys) eq_(do_full_scan('*_1', 10), one_keys) + keys = set() + for k in self.redis.scan_iter('*_1'): + keys.add(k) + eq_(keys, one_keys) all_keys = abc_keys.union(xyz_keys) eq_(do_full_scan('*', 1), all_keys) eq_(do_full_scan('*', 2), all_keys) eq_(do_full_scan('*', 10), all_keys) + keys = set() + for k in self.redis.scan_iter('*'): + keys.add(k) + eq_(keys, all_keys) class TestRedisSScan(object): @@ -111,21 +147,37 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) + members = set() + for m in self.redis.sscan_iter('key', '*abc*'): + members.add(m) + eq_(members, abc_members) xyz_members = set([b'xyz_1', b'xyz_2', b'xyz_3', b'xyz_4', b'xyz_5']) eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) + members = set() + for m in self.redis.sscan_iter('key', '*xyz*'): + members.add(m) + eq_(members, xyz_members) one_members = set([b'abc_1', b'xyz_1']) eq_(do_full_scan('key', '*_1', 1), one_members) eq_(do_full_scan('key', '*_1', 2), one_members) eq_(do_full_scan('key', '*_1', 10), one_members) + members = set() + for m in self.redis.sscan_iter('key', '*_1'): + members.add(m) + eq_(members, one_members) all_members = abc_members.union(xyz_members) eq_(do_full_scan('key', '*', 1), all_members) eq_(do_full_scan('key', '*', 2), all_members) eq_(do_full_scan('key', '*', 10), all_members) + members = set() + for m in self.redis.sscan_iter('key', '*'): + members.add(m) + eq_(members, all_members) class TestRedisZScan(object): @@ -162,21 +214,37 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) + members = set() + for m in self.redis.zscan_iter('key', '*abc*'): + members.add(m) + eq_(members, abc_members) xyz_members = set([(b'xyz_1', 1), (b'xyz_2', 2), (b'xyz_3', 3), (b'xyz_4', 4), (b'xyz_5', 5)]) eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) + members = set() + for m in self.redis.zscan_iter('key', '*xyz*'): + members.add(m) + eq_(members, xyz_members) one_members = set([(b'abc_1', 1), (b'xyz_1', 1)]) eq_(do_full_scan('key', '*_1', 1), one_members) eq_(do_full_scan('key', '*_1', 2), one_members) eq_(do_full_scan('key', '*_1', 10), one_members) + members = set() + for m in self.redis.zscan_iter('key', '*_1'): + members.add(m) + eq_(members, one_members) all_members = abc_members.union(xyz_members) eq_(do_full_scan('key', '*', 1), all_members) eq_(do_full_scan('key', '*', 2), all_members) eq_(do_full_scan('key', '*', 10), all_members) + members = set() + for m in self.redis.zscan_iter('key', '*'): + members.add(m) + eq_(members, all_members) class TestRedisHScan(object): @@ -213,19 +281,35 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc) eq_(do_full_scan('key', '*abc*', 2), abc) eq_(do_full_scan('key', '*abc*', 10), abc) + data = {} + for k, v in self.redis.hscan_iter('key', '*abc*'): + data[k] = v + eq_(data, abc) xyz = {b'xyz_1': b'1', b'xyz_2': b'2', b'xyz_3': b'3', b'xyz_4': b'4', b'xyz_5': b'5'} eq_(do_full_scan('key', '*xyz*', 1), xyz) eq_(do_full_scan('key', '*xyz*', 2), xyz) eq_(do_full_scan('key', '*xyz*', 10), xyz) + data = {} + for k, v in self.redis.hscan_iter('key', '*xyz*'): + data[k] = v + eq_(data, xyz) all_1 = {b'abc_1': b'1', b'xyz_1': b'1'} eq_(do_full_scan('key', '*_1', 1), all_1) eq_(do_full_scan('key', '*_1', 2), all_1) eq_(do_full_scan('key', '*_1', 10), all_1) + data = {} + for k, v in self.redis.hscan_iter('key', '*_1'): + data[k] = v + eq_(data, all_1) abcxyz = abc abcxyz.update(xyz) eq_(do_full_scan('key', '*', 1), abcxyz) eq_(do_full_scan('key', '*', 2), abcxyz) eq_(do_full_scan('key', '*', 10), abcxyz) + data = {} + for k, v in self.redis.hscan_iter('key', '*'): + data[k] = v + eq_(data, abcxyz) From 2727118dedd36b8ee01131d6b8a9e0457b5d21ff Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:43:05 -0500 Subject: [PATCH 03/27] Added get/set bit operations --- mockredis/client.py | 34 ++++++++++++++++++++++++++++++++++ mockredis/tests/test_string.py | 26 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 32ef6fc..9cf1faa 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -413,6 +413,40 @@ def incr(self, key, amount=1): incrby = incr + def setbit(self, key, offset, value): + key = self._encode(key) + index, bits, mask = self._get_bits_and_offset(key, offset) + + if index >= len(bits): + bits.extend(b"\x00" * (index + 1 - len(bits))) + + prev_val = 1 if (bits[index] & mask) else 0 + + if value: + bits[index] |= mask + else: + bits[index] &= ~mask + + self.redis[key] = bytes(bits) + + return prev_val + + def getbit(self, key, offset): + key = self._encode(key) + index, bits, mask = self._get_bits_and_offset(key, offset) + + if index >= len(bits): + return 0 + + return 1 if (bits[index] & mask) else 0 + + def _get_bits_and_offset(self, key, offset): + bits = bytearray(self.redis.get(key, b"")) + index, position = divmod(offset, 8) + mask = 128 >> position + return index, bits, mask + + #### Hash Functions #### def hexists(self, hashkey, attribute): diff --git a/mockredis/tests/test_string.py b/mockredis/tests/test_string.py index 2948a94..95a16d0 100644 --- a/mockredis/tests/test_string.py +++ b/mockredis/tests/test_string.py @@ -277,3 +277,29 @@ def test_getset(self): eq_(None, self.redis.getset('getset_key', '1')) eq_(b'1', self.redis.getset('getset_key', '2')) eq_(b'2', self.redis.get('getset_key')) + + + def test_setbit(self): + + # test behavior on empty keys + eq_(0, self.redis.getbit("setbit_key", 0)) + eq_(0, self.redis.getbit("setbit_key", 1024)) + eq_(None, self.redis.get("setbit_key")) + + # test setting bits and getting bits + for x in range(64): + eq_(0, self.redis.setbit("setbit_key", x, 1)) + eq_(1, self.redis.getbit("setbit_key", x)) + eq_(1, self.redis.setbit("setbit_key", x, 0)) + eq_(0, self.redis.getbit("setbit_key", x)) + + # test setting string and getting bits + eq_(True, self.redis.set("setbit_key", b"\xaa\xaa")) + for x in range(0, 16, 2): + eq_(1, self.redis.getbit("setbit_key", x)) + eq_(0, self.redis.getbit("setbit_key", x+1)) + + # test setting bits and getting string + for x in range(16, 32, 2): + eq_(0, self.redis.setbit("setbit_key", x, 1)) + eq_(b"\xaa\xaa\xaa\xaa", self.redis.get("setbit_key")) From 63d1404b3e1322ddf740f6e43d3f016b7ecdb5d6 Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:50:12 -0500 Subject: [PATCH 04/27] Added doc strings --- mockredis/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 9cf1faa..b48baa7 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -414,6 +414,9 @@ def incr(self, key, amount=1): incrby = incr def setbit(self, key, offset, value): + """ + Set the bit at ``offset`` in ``key`` to ``value``. + """ key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) @@ -432,6 +435,9 @@ def setbit(self, key, offset, value): return prev_val def getbit(self, key, offset): + """ + Returns the bit value at ``offset`` in ``key``. + """ key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) From bf951e390b97780c9c8e899076092f62011340a3 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Thu, 30 Apr 2015 17:05:36 +0300 Subject: [PATCH 05/27] Fix a few issues with Lua compatibility. Calling LREM from Lua is not handled properly because of Redis LREM and redis-py different order of arguments. Returning {err='error'} or {ok=''} from a Lua script does not get treated the same as in a real Redis server which returns a Status response. --- mockredis/script.py | 20 +++++++++++++++++--- mockredis/tests/test_script.py | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/mockredis/script.py b/mockredis/script.py index 00460f6..091d99f 100644 --- a/mockredis/script.py +++ b/mockredis/script.py @@ -1,4 +1,5 @@ import sys +from mockredis.exceptions import ResponseError class Script(object): """ @@ -30,11 +31,19 @@ def _execute_lua(self, keys, args, client): lua_globals.ARGV = self._python_to_lua(args) def _call(*call_args): - response = client.call(*call_args) + # redis-py and native redis commands are mostly compatible argument + # wise, but some exceptions need to be handled here: + if str(call_args[0]).lower() == 'lrem': + response = client.call( + call_args[0], call_args[1], + call_args[3], # "count", default is 0 + call_args[2]) + else: + response = client.call(*call_args) return self._python_to_lua(response) lua_globals.redis = {"call": _call} - return self._lua_to_python(lua.execute(self.script)) + return self._lua_to_python(lua.execute(self.script), return_status=True) @staticmethod def _import_lua(load_dependencies=True): @@ -82,7 +91,7 @@ def _import_lua_dependencies(lua, lua_globals): raise RuntimeError("cjson not installed") @staticmethod - def _lua_to_python(lval): + def _lua_to_python(lval, return_status=False): """ Convert Lua object(s) into Python object(s), as at times Lua object(s) are not compatible with Python functions @@ -96,6 +105,11 @@ def _lua_to_python(lval): # Lua table --> Python list pval = [] for i in lval: + if return_status: + if i == 'ok': + return lval[i] + if i == 'err': + raise ResponseError(lval[i]) pval.append(Script._lua_to_python(lval[i])) return pval elif isinstance(lval, long): diff --git a/mockredis/tests/test_script.py b/mockredis/tests/test_script.py index 4a45603..73eaae1 100644 --- a/mockredis/tests/test_script.py +++ b/mockredis/tests/test_script.py @@ -16,6 +16,7 @@ VAL1, VAL2, VAL3, VAL4, LPOP_SCRIPT ) +from mockredis.tests.fixtures import raises_response_error if sys.version_info >= (3, 0): @@ -188,6 +189,16 @@ def test_eval_lpop(self): eq_(VAL1, list_item) eq_([VAL2], self.redis.lrange(LIST1, 0, -1)) + def test_eval_lrem(self): + self.redis.delete(LIST1) + self.redis.lpush(LIST1, VAL1) + + # lrem one value + script_content = "return redis.call('LREM', KEYS[1], 0, ARGV[1])" + value = self.redis.eval(script_content, 1, LIST1, VAL1) + + eq_(value, 1) + def test_eval_zadd(self): # The score and member are reversed when the client is not strict. self.redis.strict = False @@ -365,3 +376,15 @@ def test_python_to_lua_boolean(self): lval = MockRedisScript._python_to_lua(pval) eq_("boolean", self.lua_globals.type(lval)) ok_(MockRedisScript._lua_to_python(lval)) + + def test_lua_ok_return(self): + script_content = "return {ok='OK'}" + script = self.redis.register_script(script_content) + eq_('OK', script()) + + @raises_response_error + def test_lua_err_return(self): + script_content = "return {err='ERROR Some message'}" + script = self.redis.register_script(script_content) + script() + From 75de47134005c169f19802054c0256f8594eaabe Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Mon, 4 May 2015 23:54:09 +0300 Subject: [PATCH 06/27] Add a global Lua lock to prevent crashes and better simulate Redis when calling a script from multiple threads. --- mockredis/script.py | 12 ++++++++---- mockredis/tests/test_script.py | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/mockredis/script.py b/mockredis/script.py index 091d99f..39eb840 100644 --- a/mockredis/script.py +++ b/mockredis/script.py @@ -1,6 +1,9 @@ import sys +import threading from mockredis.exceptions import ResponseError +LuaLock = threading.Lock() + class Script(object): """ An executable Lua script object returned by ``MockRedis.register_script``. @@ -14,12 +17,13 @@ def __init__(self, registered_client, script, load_dependencies=True): def __call__(self, keys=[], args=[], client=None): """Execute the script, passing any required ``args``""" - client = client or self.registered_client + with LuaLock: + client = client or self.registered_client - if not client.script_exists(self.sha)[0]: - self.sha = client.script_load(self.script) + if not client.script_exists(self.sha)[0]: + self.sha = client.script_load(self.script) - return self._execute_lua(keys, args, client) + return self._execute_lua(keys, args, client) def _execute_lua(self, keys, args, client): """ diff --git a/mockredis/tests/test_script.py b/mockredis/tests/test_script.py index 73eaae1..84fc3a5 100644 --- a/mockredis/tests/test_script.py +++ b/mockredis/tests/test_script.py @@ -4,6 +4,7 @@ from hashlib import sha1 from unittest.case import SkipTest import sys +import threading from nose.tools import assert_raises, eq_, ok_ @@ -388,3 +389,26 @@ def test_lua_err_return(self): script = self.redis.register_script(script_content) script() + def test_concurrent_lua(self): + script_content = """ +local entry = redis.call('HGETALL', ARGV[1]) +redis.call('HSET', ARGV[1], 'kk', 'vv') +return entry +""" + script = self.redis.register_script(script_content) + + for i in range(500): + self.redis.hmset(i, {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}) + + def lua_thread(): + for i in range(500): + result = script(args=[i]) + + active_threads = [] + for i in range(10): + thread = threading.Thread(target=lua_thread) + active_threads.append(thread) + thread.start() + + for thread in active_threads: + thread.join() From dfbd2cd6b8d6c1c6aef793759d219b94c53b972a Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 08:28:49 -0700 Subject: [PATCH 07/27] Merge pull request #79 from frewsxcv/patch-1 Test on PyPy 3 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 68eb8f4..b999356 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - '3.3' - '3.4' - pypy +- pypy3 install: pip install . --use-mirrors script: python setup.py nosetests deploy: From 60261a612d1d527874f532d0a70fb5e8e2f9ab5b Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Mon, 15 Dec 2014 12:41:02 -0800 Subject: [PATCH 08/27] Bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9499424..db7d98c 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages # Match releases to redis-py versions -__version__ = '2.9.0.10' +__version__ = '2.9.0.11' # Jenkins will replace __build__ with a unique value. __build__ = '' From 340d6d429810b0c4edc93efc8f24e4d976aaadf7 Mon Sep 17 00:00:00 2001 From: xiaogaozi Date: Tue, 23 Dec 2014 21:03:54 +0800 Subject: [PATCH 09/27] Support `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter` --- CHANGES.md | 3 ++ README.md | 8 ++-- mockredis/client.py | 35 +++++++++++++++ mockredis/tests/test_scan.py | 84 ++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 12be2ff..20de6a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +Version 2.9.0.11 + - Support: `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter` + Version 2.9.0.10 - Return & store byte strings everywhere (unicode turns into utf-8 by default) - Fix *SCAN returning non-long values. diff --git a/README.md b/README.md index c7786fc..67129e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mock for the redis-py client library -Supports writing tests for code using the [redis-py][redis-py] library +Supports writing tests for code using the [redis-py][redis-py] library without requiring a [redis-server][redis] install. [![Build Status](https://travis-ci.org/locationlabs/mockredis.png)](https://travis-ci.org/locationlabs/mockredis) @@ -17,11 +17,11 @@ Both `mockredis.mock_redis_client` and `mockredis.mock_strict_redis_client` can used to patch instances of the *redis client*. For example, using the [mock][mock] library: - + @patch('redis.Redis', mock_redis_client) - + Or: - + @patch('redis.StrictRedis', mock_strict_redis_client) ## Testing diff --git a/mockredis/client.py b/mockredis/client.py index 32ef6fc..43c2b08 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -820,6 +820,14 @@ def value_function(): return sorted(self.redis.keys()) # sorted list for consistent order return self._common_scan(value_function, cursor=cursor, match=match, count=count) + def scan_iter(self, match=None, count=10): + """Emulate scan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.scan(cursor=cursor, match=match, count=count) + for item in data: + yield item + def sscan(self, name, cursor='0', match=None, count=10): """Emulate sscan.""" def value_function(): @@ -828,6 +836,15 @@ def value_function(): return members return self._common_scan(value_function, cursor=cursor, match=match, count=count) + def sscan_iter(self, name, match=None, count=10): + """Emulate sscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.sscan(name, cursor=cursor, + match=match, count=count) + for item in data: + yield item + def zscan(self, name, cursor='0', match=None, count=10): """Emulate zscan.""" def value_function(): @@ -836,6 +853,15 @@ def value_function(): return values return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) + def zscan_iter(self, name, match=None, count=10): + """Emulate zscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.zscan(name, cursor=cursor, match=match, + count=count) + for item in data: + yield item + def hscan(self, name, cursor='0', match=None, count=10): """Emulate hscan.""" def value_function(): @@ -847,6 +873,15 @@ def value_function(): scanned[1] = dict(scanned[1]) # from list of tuples back to dict return scanned + def hscan_iter(self, name, match=None, count=10): + """Emulate hscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.hscan(name, cursor=cursor, + match=match, count=count) + for item in data.items(): + yield item + #### SET COMMANDS #### def sadd(self, key, *values): diff --git a/mockredis/tests/test_scan.py b/mockredis/tests/test_scan.py index bb74833..7fe572a 100644 --- a/mockredis/tests/test_scan.py +++ b/mockredis/tests/test_scan.py @@ -25,6 +25,26 @@ def eq_scan(results, cursor, elements): eq_scan(self.redis.zscan("foo"), 0, []) eq_scan(self.redis.hscan("foo"), 0, {}) + keys = [] + for k in self.redis.scan_iter(): + keys.append(k) + eq_(keys, []) + + members = [] + for m in self.redis.sscan_iter('foo'): + members.append(m) + eq_(members, []) + + members = [] + for m in self.redis.zscan_iter('foo'): + members.append(m) + eq_(members, []) + + members = [] + for m in self.redis.hscan_iter('foo'): + members.append(m) + eq_(members, []) + class TestRedisScan(object): """SCAN tests""" @@ -60,21 +80,37 @@ def do_full_scan(match, count): eq_(do_full_scan('*abc*', 1), abc_keys) eq_(do_full_scan('*abc*', 2), abc_keys) eq_(do_full_scan('*abc*', 10), abc_keys) + keys = set() + for k in self.redis.scan_iter('*abc*'): + keys.add(k) + eq_(keys, abc_keys) xyz_keys = set([b'key_xyz_1', b'key_xyz_2', b'key_xyz_3', b'key_xyz_4', b'key_xyz_5']) eq_(do_full_scan('*xyz*', 1), xyz_keys) eq_(do_full_scan('*xyz*', 2), xyz_keys) eq_(do_full_scan('*xyz*', 10), xyz_keys) + keys = set() + for k in self.redis.scan_iter('*xyz*'): + keys.add(k) + eq_(keys, xyz_keys) one_keys = set([b'key_abc_1', b'key_xyz_1']) eq_(do_full_scan('*_1', 1), one_keys) eq_(do_full_scan('*_1', 2), one_keys) eq_(do_full_scan('*_1', 10), one_keys) + keys = set() + for k in self.redis.scan_iter('*_1'): + keys.add(k) + eq_(keys, one_keys) all_keys = abc_keys.union(xyz_keys) eq_(do_full_scan('*', 1), all_keys) eq_(do_full_scan('*', 2), all_keys) eq_(do_full_scan('*', 10), all_keys) + keys = set() + for k in self.redis.scan_iter('*'): + keys.add(k) + eq_(keys, all_keys) class TestRedisSScan(object): @@ -111,21 +147,37 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) + members = set() + for m in self.redis.sscan_iter('key', '*abc*'): + members.add(m) + eq_(members, abc_members) xyz_members = set([b'xyz_1', b'xyz_2', b'xyz_3', b'xyz_4', b'xyz_5']) eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) + members = set() + for m in self.redis.sscan_iter('key', '*xyz*'): + members.add(m) + eq_(members, xyz_members) one_members = set([b'abc_1', b'xyz_1']) eq_(do_full_scan('key', '*_1', 1), one_members) eq_(do_full_scan('key', '*_1', 2), one_members) eq_(do_full_scan('key', '*_1', 10), one_members) + members = set() + for m in self.redis.sscan_iter('key', '*_1'): + members.add(m) + eq_(members, one_members) all_members = abc_members.union(xyz_members) eq_(do_full_scan('key', '*', 1), all_members) eq_(do_full_scan('key', '*', 2), all_members) eq_(do_full_scan('key', '*', 10), all_members) + members = set() + for m in self.redis.sscan_iter('key', '*'): + members.add(m) + eq_(members, all_members) class TestRedisZScan(object): @@ -162,21 +214,37 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) + members = set() + for m in self.redis.zscan_iter('key', '*abc*'): + members.add(m) + eq_(members, abc_members) xyz_members = set([(b'xyz_1', 1), (b'xyz_2', 2), (b'xyz_3', 3), (b'xyz_4', 4), (b'xyz_5', 5)]) eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) + members = set() + for m in self.redis.zscan_iter('key', '*xyz*'): + members.add(m) + eq_(members, xyz_members) one_members = set([(b'abc_1', 1), (b'xyz_1', 1)]) eq_(do_full_scan('key', '*_1', 1), one_members) eq_(do_full_scan('key', '*_1', 2), one_members) eq_(do_full_scan('key', '*_1', 10), one_members) + members = set() + for m in self.redis.zscan_iter('key', '*_1'): + members.add(m) + eq_(members, one_members) all_members = abc_members.union(xyz_members) eq_(do_full_scan('key', '*', 1), all_members) eq_(do_full_scan('key', '*', 2), all_members) eq_(do_full_scan('key', '*', 10), all_members) + members = set() + for m in self.redis.zscan_iter('key', '*'): + members.add(m) + eq_(members, all_members) class TestRedisHScan(object): @@ -213,19 +281,35 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc) eq_(do_full_scan('key', '*abc*', 2), abc) eq_(do_full_scan('key', '*abc*', 10), abc) + data = {} + for k, v in self.redis.hscan_iter('key', '*abc*'): + data[k] = v + eq_(data, abc) xyz = {b'xyz_1': b'1', b'xyz_2': b'2', b'xyz_3': b'3', b'xyz_4': b'4', b'xyz_5': b'5'} eq_(do_full_scan('key', '*xyz*', 1), xyz) eq_(do_full_scan('key', '*xyz*', 2), xyz) eq_(do_full_scan('key', '*xyz*', 10), xyz) + data = {} + for k, v in self.redis.hscan_iter('key', '*xyz*'): + data[k] = v + eq_(data, xyz) all_1 = {b'abc_1': b'1', b'xyz_1': b'1'} eq_(do_full_scan('key', '*_1', 1), all_1) eq_(do_full_scan('key', '*_1', 2), all_1) eq_(do_full_scan('key', '*_1', 10), all_1) + data = {} + for k, v in self.redis.hscan_iter('key', '*_1'): + data[k] = v + eq_(data, all_1) abcxyz = abc abcxyz.update(xyz) eq_(do_full_scan('key', '*', 1), abcxyz) eq_(do_full_scan('key', '*', 2), abcxyz) eq_(do_full_scan('key', '*', 10), abcxyz) + data = {} + for k, v in self.redis.hscan_iter('key', '*'): + data[k] = v + eq_(data, abcxyz) From 764cb996e8e68d202b9251aa433652d2a34960d6 Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:43:05 -0500 Subject: [PATCH 10/27] Added get/set bit operations --- mockredis/client.py | 34 ++++++++++++++++++++++++++++++++++ mockredis/tests/test_string.py | 26 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 43c2b08..54972ba 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -413,6 +413,40 @@ def incr(self, key, amount=1): incrby = incr + def setbit(self, key, offset, value): + key = self._encode(key) + index, bits, mask = self._get_bits_and_offset(key, offset) + + if index >= len(bits): + bits.extend(b"\x00" * (index + 1 - len(bits))) + + prev_val = 1 if (bits[index] & mask) else 0 + + if value: + bits[index] |= mask + else: + bits[index] &= ~mask + + self.redis[key] = bytes(bits) + + return prev_val + + def getbit(self, key, offset): + key = self._encode(key) + index, bits, mask = self._get_bits_and_offset(key, offset) + + if index >= len(bits): + return 0 + + return 1 if (bits[index] & mask) else 0 + + def _get_bits_and_offset(self, key, offset): + bits = bytearray(self.redis.get(key, b"")) + index, position = divmod(offset, 8) + mask = 128 >> position + return index, bits, mask + + #### Hash Functions #### def hexists(self, hashkey, attribute): diff --git a/mockredis/tests/test_string.py b/mockredis/tests/test_string.py index 2948a94..95a16d0 100644 --- a/mockredis/tests/test_string.py +++ b/mockredis/tests/test_string.py @@ -277,3 +277,29 @@ def test_getset(self): eq_(None, self.redis.getset('getset_key', '1')) eq_(b'1', self.redis.getset('getset_key', '2')) eq_(b'2', self.redis.get('getset_key')) + + + def test_setbit(self): + + # test behavior on empty keys + eq_(0, self.redis.getbit("setbit_key", 0)) + eq_(0, self.redis.getbit("setbit_key", 1024)) + eq_(None, self.redis.get("setbit_key")) + + # test setting bits and getting bits + for x in range(64): + eq_(0, self.redis.setbit("setbit_key", x, 1)) + eq_(1, self.redis.getbit("setbit_key", x)) + eq_(1, self.redis.setbit("setbit_key", x, 0)) + eq_(0, self.redis.getbit("setbit_key", x)) + + # test setting string and getting bits + eq_(True, self.redis.set("setbit_key", b"\xaa\xaa")) + for x in range(0, 16, 2): + eq_(1, self.redis.getbit("setbit_key", x)) + eq_(0, self.redis.getbit("setbit_key", x+1)) + + # test setting bits and getting string + for x in range(16, 32, 2): + eq_(0, self.redis.setbit("setbit_key", x, 1)) + eq_(b"\xaa\xaa\xaa\xaa", self.redis.get("setbit_key")) From 4901644fe3a496cb72404e37bd6415e2ca677479 Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:50:12 -0500 Subject: [PATCH 11/27] Added doc strings --- mockredis/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 54972ba..91dc353 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -414,6 +414,9 @@ def incr(self, key, amount=1): incrby = incr def setbit(self, key, offset, value): + """ + Set the bit at ``offset`` in ``key`` to ``value``. + """ key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) @@ -432,6 +435,9 @@ def setbit(self, key, offset, value): return prev_val def getbit(self, key, offset): + """ + Returns the bit value at ``offset`` in ``key``. + """ key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) From b9992d7081ade4d4572971ca1c21143ae09639e0 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 08:28:49 -0700 Subject: [PATCH 12/27] Merge pull request #79 from frewsxcv/patch-1 Test on PyPy 3 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 68eb8f4..b999356 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - '3.3' - '3.4' - pypy +- pypy3 install: pip install . --use-mirrors script: python setup.py nosetests deploy: From b1a1780049992a3ee1309db9c6c873d5672e93b0 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Mon, 15 Dec 2014 12:41:02 -0800 Subject: [PATCH 13/27] Bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9499424..db7d98c 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages # Match releases to redis-py versions -__version__ = '2.9.0.10' +__version__ = '2.9.0.11' # Jenkins will replace __build__ with a unique value. __build__ = '' From a7107af1b098cc17345b36e11608f0f93de4ea4d Mon Sep 17 00:00:00 2001 From: xiaogaozi Date: Tue, 23 Dec 2014 21:03:54 +0800 Subject: [PATCH 14/27] Support `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter` --- CHANGES.md | 3 ++ README.md | 8 ++-- mockredis/client.py | 35 +++++++++++++++ mockredis/tests/test_scan.py | 84 ++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 12be2ff..20de6a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +Version 2.9.0.11 + - Support: `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter` + Version 2.9.0.10 - Return & store byte strings everywhere (unicode turns into utf-8 by default) - Fix *SCAN returning non-long values. diff --git a/README.md b/README.md index c7786fc..67129e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mock for the redis-py client library -Supports writing tests for code using the [redis-py][redis-py] library +Supports writing tests for code using the [redis-py][redis-py] library without requiring a [redis-server][redis] install. [![Build Status](https://travis-ci.org/locationlabs/mockredis.png)](https://travis-ci.org/locationlabs/mockredis) @@ -17,11 +17,11 @@ Both `mockredis.mock_redis_client` and `mockredis.mock_strict_redis_client` can used to patch instances of the *redis client*. For example, using the [mock][mock] library: - + @patch('redis.Redis', mock_redis_client) - + Or: - + @patch('redis.StrictRedis', mock_strict_redis_client) ## Testing diff --git a/mockredis/client.py b/mockredis/client.py index 32ef6fc..43c2b08 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -820,6 +820,14 @@ def value_function(): return sorted(self.redis.keys()) # sorted list for consistent order return self._common_scan(value_function, cursor=cursor, match=match, count=count) + def scan_iter(self, match=None, count=10): + """Emulate scan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.scan(cursor=cursor, match=match, count=count) + for item in data: + yield item + def sscan(self, name, cursor='0', match=None, count=10): """Emulate sscan.""" def value_function(): @@ -828,6 +836,15 @@ def value_function(): return members return self._common_scan(value_function, cursor=cursor, match=match, count=count) + def sscan_iter(self, name, match=None, count=10): + """Emulate sscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.sscan(name, cursor=cursor, + match=match, count=count) + for item in data: + yield item + def zscan(self, name, cursor='0', match=None, count=10): """Emulate zscan.""" def value_function(): @@ -836,6 +853,15 @@ def value_function(): return values return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) + def zscan_iter(self, name, match=None, count=10): + """Emulate zscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.zscan(name, cursor=cursor, match=match, + count=count) + for item in data: + yield item + def hscan(self, name, cursor='0', match=None, count=10): """Emulate hscan.""" def value_function(): @@ -847,6 +873,15 @@ def value_function(): scanned[1] = dict(scanned[1]) # from list of tuples back to dict return scanned + def hscan_iter(self, name, match=None, count=10): + """Emulate hscan_iter.""" + cursor = '0' + while cursor != 0: + cursor, data = self.hscan(name, cursor=cursor, + match=match, count=count) + for item in data.items(): + yield item + #### SET COMMANDS #### def sadd(self, key, *values): diff --git a/mockredis/tests/test_scan.py b/mockredis/tests/test_scan.py index bb74833..7fe572a 100644 --- a/mockredis/tests/test_scan.py +++ b/mockredis/tests/test_scan.py @@ -25,6 +25,26 @@ def eq_scan(results, cursor, elements): eq_scan(self.redis.zscan("foo"), 0, []) eq_scan(self.redis.hscan("foo"), 0, {}) + keys = [] + for k in self.redis.scan_iter(): + keys.append(k) + eq_(keys, []) + + members = [] + for m in self.redis.sscan_iter('foo'): + members.append(m) + eq_(members, []) + + members = [] + for m in self.redis.zscan_iter('foo'): + members.append(m) + eq_(members, []) + + members = [] + for m in self.redis.hscan_iter('foo'): + members.append(m) + eq_(members, []) + class TestRedisScan(object): """SCAN tests""" @@ -60,21 +80,37 @@ def do_full_scan(match, count): eq_(do_full_scan('*abc*', 1), abc_keys) eq_(do_full_scan('*abc*', 2), abc_keys) eq_(do_full_scan('*abc*', 10), abc_keys) + keys = set() + for k in self.redis.scan_iter('*abc*'): + keys.add(k) + eq_(keys, abc_keys) xyz_keys = set([b'key_xyz_1', b'key_xyz_2', b'key_xyz_3', b'key_xyz_4', b'key_xyz_5']) eq_(do_full_scan('*xyz*', 1), xyz_keys) eq_(do_full_scan('*xyz*', 2), xyz_keys) eq_(do_full_scan('*xyz*', 10), xyz_keys) + keys = set() + for k in self.redis.scan_iter('*xyz*'): + keys.add(k) + eq_(keys, xyz_keys) one_keys = set([b'key_abc_1', b'key_xyz_1']) eq_(do_full_scan('*_1', 1), one_keys) eq_(do_full_scan('*_1', 2), one_keys) eq_(do_full_scan('*_1', 10), one_keys) + keys = set() + for k in self.redis.scan_iter('*_1'): + keys.add(k) + eq_(keys, one_keys) all_keys = abc_keys.union(xyz_keys) eq_(do_full_scan('*', 1), all_keys) eq_(do_full_scan('*', 2), all_keys) eq_(do_full_scan('*', 10), all_keys) + keys = set() + for k in self.redis.scan_iter('*'): + keys.add(k) + eq_(keys, all_keys) class TestRedisSScan(object): @@ -111,21 +147,37 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) + members = set() + for m in self.redis.sscan_iter('key', '*abc*'): + members.add(m) + eq_(members, abc_members) xyz_members = set([b'xyz_1', b'xyz_2', b'xyz_3', b'xyz_4', b'xyz_5']) eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) + members = set() + for m in self.redis.sscan_iter('key', '*xyz*'): + members.add(m) + eq_(members, xyz_members) one_members = set([b'abc_1', b'xyz_1']) eq_(do_full_scan('key', '*_1', 1), one_members) eq_(do_full_scan('key', '*_1', 2), one_members) eq_(do_full_scan('key', '*_1', 10), one_members) + members = set() + for m in self.redis.sscan_iter('key', '*_1'): + members.add(m) + eq_(members, one_members) all_members = abc_members.union(xyz_members) eq_(do_full_scan('key', '*', 1), all_members) eq_(do_full_scan('key', '*', 2), all_members) eq_(do_full_scan('key', '*', 10), all_members) + members = set() + for m in self.redis.sscan_iter('key', '*'): + members.add(m) + eq_(members, all_members) class TestRedisZScan(object): @@ -162,21 +214,37 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) + members = set() + for m in self.redis.zscan_iter('key', '*abc*'): + members.add(m) + eq_(members, abc_members) xyz_members = set([(b'xyz_1', 1), (b'xyz_2', 2), (b'xyz_3', 3), (b'xyz_4', 4), (b'xyz_5', 5)]) eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) + members = set() + for m in self.redis.zscan_iter('key', '*xyz*'): + members.add(m) + eq_(members, xyz_members) one_members = set([(b'abc_1', 1), (b'xyz_1', 1)]) eq_(do_full_scan('key', '*_1', 1), one_members) eq_(do_full_scan('key', '*_1', 2), one_members) eq_(do_full_scan('key', '*_1', 10), one_members) + members = set() + for m in self.redis.zscan_iter('key', '*_1'): + members.add(m) + eq_(members, one_members) all_members = abc_members.union(xyz_members) eq_(do_full_scan('key', '*', 1), all_members) eq_(do_full_scan('key', '*', 2), all_members) eq_(do_full_scan('key', '*', 10), all_members) + members = set() + for m in self.redis.zscan_iter('key', '*'): + members.add(m) + eq_(members, all_members) class TestRedisHScan(object): @@ -213,19 +281,35 @@ def do_full_scan(name, match, count): eq_(do_full_scan('key', '*abc*', 1), abc) eq_(do_full_scan('key', '*abc*', 2), abc) eq_(do_full_scan('key', '*abc*', 10), abc) + data = {} + for k, v in self.redis.hscan_iter('key', '*abc*'): + data[k] = v + eq_(data, abc) xyz = {b'xyz_1': b'1', b'xyz_2': b'2', b'xyz_3': b'3', b'xyz_4': b'4', b'xyz_5': b'5'} eq_(do_full_scan('key', '*xyz*', 1), xyz) eq_(do_full_scan('key', '*xyz*', 2), xyz) eq_(do_full_scan('key', '*xyz*', 10), xyz) + data = {} + for k, v in self.redis.hscan_iter('key', '*xyz*'): + data[k] = v + eq_(data, xyz) all_1 = {b'abc_1': b'1', b'xyz_1': b'1'} eq_(do_full_scan('key', '*_1', 1), all_1) eq_(do_full_scan('key', '*_1', 2), all_1) eq_(do_full_scan('key', '*_1', 10), all_1) + data = {} + for k, v in self.redis.hscan_iter('key', '*_1'): + data[k] = v + eq_(data, all_1) abcxyz = abc abcxyz.update(xyz) eq_(do_full_scan('key', '*', 1), abcxyz) eq_(do_full_scan('key', '*', 2), abcxyz) eq_(do_full_scan('key', '*', 10), abcxyz) + data = {} + for k, v in self.redis.hscan_iter('key', '*'): + data[k] = v + eq_(data, abcxyz) From 89ae242dbe2ab79a408acc3acdee0871fe25a319 Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:43:05 -0500 Subject: [PATCH 15/27] Added get/set bit operations --- mockredis/client.py | 34 ++++++++++++++++++++++++++++++++++ mockredis/tests/test_string.py | 26 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 43c2b08..54972ba 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -413,6 +413,40 @@ def incr(self, key, amount=1): incrby = incr + def setbit(self, key, offset, value): + key = self._encode(key) + index, bits, mask = self._get_bits_and_offset(key, offset) + + if index >= len(bits): + bits.extend(b"\x00" * (index + 1 - len(bits))) + + prev_val = 1 if (bits[index] & mask) else 0 + + if value: + bits[index] |= mask + else: + bits[index] &= ~mask + + self.redis[key] = bytes(bits) + + return prev_val + + def getbit(self, key, offset): + key = self._encode(key) + index, bits, mask = self._get_bits_and_offset(key, offset) + + if index >= len(bits): + return 0 + + return 1 if (bits[index] & mask) else 0 + + def _get_bits_and_offset(self, key, offset): + bits = bytearray(self.redis.get(key, b"")) + index, position = divmod(offset, 8) + mask = 128 >> position + return index, bits, mask + + #### Hash Functions #### def hexists(self, hashkey, attribute): diff --git a/mockredis/tests/test_string.py b/mockredis/tests/test_string.py index 2948a94..95a16d0 100644 --- a/mockredis/tests/test_string.py +++ b/mockredis/tests/test_string.py @@ -277,3 +277,29 @@ def test_getset(self): eq_(None, self.redis.getset('getset_key', '1')) eq_(b'1', self.redis.getset('getset_key', '2')) eq_(b'2', self.redis.get('getset_key')) + + + def test_setbit(self): + + # test behavior on empty keys + eq_(0, self.redis.getbit("setbit_key", 0)) + eq_(0, self.redis.getbit("setbit_key", 1024)) + eq_(None, self.redis.get("setbit_key")) + + # test setting bits and getting bits + for x in range(64): + eq_(0, self.redis.setbit("setbit_key", x, 1)) + eq_(1, self.redis.getbit("setbit_key", x)) + eq_(1, self.redis.setbit("setbit_key", x, 0)) + eq_(0, self.redis.getbit("setbit_key", x)) + + # test setting string and getting bits + eq_(True, self.redis.set("setbit_key", b"\xaa\xaa")) + for x in range(0, 16, 2): + eq_(1, self.redis.getbit("setbit_key", x)) + eq_(0, self.redis.getbit("setbit_key", x+1)) + + # test setting bits and getting string + for x in range(16, 32, 2): + eq_(0, self.redis.setbit("setbit_key", x, 1)) + eq_(b"\xaa\xaa\xaa\xaa", self.redis.get("setbit_key")) From 2b192ebe8168bfe65dc58463ec966c6d2cf28a5f Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:50:12 -0500 Subject: [PATCH 16/27] Added doc strings --- mockredis/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 54972ba..91dc353 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -414,6 +414,9 @@ def incr(self, key, amount=1): incrby = incr def setbit(self, key, offset, value): + """ + Set the bit at ``offset`` in ``key`` to ``value``. + """ key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) @@ -432,6 +435,9 @@ def setbit(self, key, offset, value): return prev_val def getbit(self, key, offset): + """ + Returns the bit value at ``offset`` in ``key``. + """ key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) From 2fedef37515da6de33ee1c9927c01becc3941e4b Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 08:28:49 -0700 Subject: [PATCH 17/27] Merge pull request #79 from frewsxcv/patch-1 Test on PyPy 3 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 68eb8f4..b999356 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - '3.3' - '3.4' - pypy +- pypy3 install: pip install . --use-mirrors script: python setup.py nosetests deploy: From 1f4f478287390286d5a718a724313e0593fdfdc4 Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:43:05 -0500 Subject: [PATCH 18/27] Added get/set bit operations --- mockredis/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mockredis/client.py b/mockredis/client.py index 91dc353..238d407 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -417,6 +417,7 @@ def setbit(self, key, offset, value): """ Set the bit at ``offset`` in ``key`` to ``value``. """ + key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) @@ -438,6 +439,7 @@ def getbit(self, key, offset): """ Returns the bit value at ``offset`` in ``key``. """ + key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) From a2e19d0b0f896860b87b319bbfe573cbb4e2f84d Mon Sep 17 00:00:00 2001 From: Joshua Bryan Date: Thu, 9 Apr 2015 13:50:12 -0500 Subject: [PATCH 19/27] Added doc strings --- mockredis/client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mockredis/client.py b/mockredis/client.py index 238d407..91dc353 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -417,7 +417,6 @@ def setbit(self, key, offset, value): """ Set the bit at ``offset`` in ``key`` to ``value``. """ - key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) @@ -439,7 +438,6 @@ def getbit(self, key, offset): """ Returns the bit value at ``offset`` in ``key``. """ - key = self._encode(key) index, bits, mask = self._get_bits_and_offset(key, offset) From 26ab9d559ec87c49c531df4acd4b404eaca223e8 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 08:58:42 -0700 Subject: [PATCH 20/27] Use min/max instead of min_/max_ (#65) While the underscore notation is more pythonic and avoids overloading built-ins, it is not compatible with the arguments used in redis-py --- mockredis/client.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mockredis/client.py b/mockredis/client.py index 91dc353..ce7eed2 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -1064,13 +1064,13 @@ def zcard(self, name): return len(zset) if zset is not None else 0 - def zcount(self, name, min_, max_): + def zcount(self, name, min, max): zset = self._get_zset(name, "ZCOUNT") if not zset: return 0 - return len(zset.scorerange(float(min_), float(max_))) + return len(zset.scorerange(float(min), float(max))) def zincrby(self, name, value, amount=1): zset = self._get_zset(name, "ZINCRBY", create=True) @@ -1116,7 +1116,7 @@ def zrange(self, name, start, end, desc=False, withscores=False, func = self._range_func(withscores, score_cast_func) return [func(item) for item in zset.range(start, end, desc)] - def zrangebyscore(self, name, min_, max_, start=None, num=None, + def zrangebyscore(self, name, min, max, start=None, num=None, withscores=False, score_cast_func=float): if (start is None) ^ (num is None): raise RedisError('`start` and `num` must both be specified') @@ -1127,9 +1127,9 @@ def zrangebyscore(self, name, min_, max_, start=None, num=None, return [] func = self._range_func(withscores, score_cast_func) - include_start, min_ = self._score_inclusive(min_) - include_end, max_ = self._score_inclusive(max_) - scorerange = zset.scorerange(min_, max_, start_inclusive=include_start, end_inclusive=include_end) + include_start, min = self._score_inclusive(min) + include_end, max = self._score_inclusive(max) + scorerange = zset.scorerange(min, max, start_inclusive=include_start, end_inclusive=include_end) if start is not None and num is not None: start, num = self._translate_limit(len(scorerange), int(start), int(num)) scorerange = scorerange[start:start + num] @@ -1165,18 +1165,18 @@ def zremrangebyrank(self, name, start, end): self.delete(name) return removal_count - def zremrangebyscore(self, name, min_, max_): + def zremrangebyscore(self, name, min, max): zset = self._get_zset(name, "ZREMRANGEBYSCORE") if not zset: return 0 count_removals = lambda score, member: 1 if zset.remove(member) else 0 - include_start, min_ = self._score_inclusive(min_) - include_end, max_ = self._score_inclusive(max_) + include_start, min = self._score_inclusive(min) + include_end, max = self._score_inclusive(max) removal_count = sum((count_removals(score, member) - for score, member in zset.scorerange(min_, max_, + for score, member in zset.scorerange(min, max, start_inclusive=include_start, end_inclusive=include_end))) if removal_count > 0 and len(zset) == 0: @@ -1188,7 +1188,7 @@ def zrevrange(self, name, start, end, withscores=False, return self.zrange(name, start, end, desc=True, withscores=withscores, score_cast_func=score_cast_func) - def zrevrangebyscore(self, name, max_, min_, start=None, num=None, + def zrevrangebyscore(self, name, max, min, start=None, num=None, withscores=False, score_cast_func=float): if (start is None) ^ (num is None): @@ -1199,10 +1199,10 @@ def zrevrangebyscore(self, name, max_, min_, start=None, num=None, return [] func = self._range_func(withscores, score_cast_func) - include_start, min_ = self._score_inclusive(min_) - include_end, max_ = self._score_inclusive(max_) + include_start, min = self._score_inclusive(min) + include_end, max = self._score_inclusive(max) - scorerange = [x for x in reversed(zset.scorerange(float(min_), float(max_), + scorerange = [x for x in reversed(zset.scorerange(float(min), float(max), start_inclusive=include_start, end_inclusive=include_end))] if start is not None and num is not None: start, num = self._translate_limit(len(scorerange), int(start), int(num)) From 999f2cd618d9870891c78e9e50b390350eddac76 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 09:06:07 -0700 Subject: [PATCH 21/27] Document PyPy3 --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 67129e1..a4a5a41 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,12 @@ against ground truth. See `mockredis.tests.fixtures` for more details and discla ## Supported python versions -- Python 2.7 -- Python 3.2 -- Python 3.3 -- Python 3.4 -- PyPy + - Python 2.7 + - Python 3.2 + - Python 3.3 + - Python 3.4 + - PyPy + - PyPy3 ## Attribution From 009bc6b5ddf134a3fcb3d7bf7208fd92f17f6489 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 13:04:21 -0700 Subject: [PATCH 22/27] Improved testing against a real redis server: - Support testing against an alternate host (or VM) - Close the client instance (by deleting it) to avoid creating too many connections (especially on OSX) --- mockredis/noseplugin.py | 12 ++++++++++-- mockredis/tests/fixtures.py | 10 ++++++++++ mockredis/tests/test_hash.py | 5 ++++- mockredis/tests/test_list.py | 6 +++++- mockredis/tests/test_pipeline.py | 6 +++++- mockredis/tests/test_redis.py | 5 ++++- mockredis/tests/test_scan.py | 17 ++++++++++++++++- mockredis/tests/test_set.py | 5 ++++- mockredis/tests/test_string.py | 6 ++++-- mockredis/tests/test_zset.py | 5 ++++- 10 files changed, 66 insertions(+), 11 deletions(-) diff --git a/mockredis/noseplugin.py b/mockredis/noseplugin.py index fcb9e99..2eaf495 100644 --- a/mockredis/noseplugin.py +++ b/mockredis/noseplugin.py @@ -42,6 +42,10 @@ def options(self, parser, env=os.environ): action="store_true", default=False, help="Use a local redis instance to validate tests.") + parser.add_option("--redis-host", + dest="redis_host", + default="localhost", + help="Run tests against redis database on another host") parser.add_option("--redis-database", dest="redis_database", default=15, @@ -51,8 +55,12 @@ def configure(self, options, conf): if options.use_redis: from redis import Redis, RedisError, ResponseError, StrictRedis, WatchError - WithRedis.Redis = partial(Redis, db=options.redis_database) - WithRedis.StrictRedis = partial(StrictRedis, db=options.redis_database) + WithRedis.Redis = partial(Redis, + db=options.redis_database, + host=options.redis_host) + WithRedis.StrictRedis = partial(StrictRedis, + db=options.redis_database, + host=options.redis_host) WithRedis.ResponseError = ResponseError WithRedis.RedisError = RedisError WithRedis.WatchError = WatchError diff --git a/mockredis/tests/fixtures.py b/mockredis/tests/fixtures.py index 7fe96fc..980c07b 100644 --- a/mockredis/tests/fixtures.py +++ b/mockredis/tests/fixtures.py @@ -18,6 +18,16 @@ def setup(self): self.redis_strict.flushdb() +def teardown(self): + """ + Test teardown fixtures. + """ + if self.redis: + del self.redis + if self.redis_strict: + del self.redis_strict + + def raises_response_error(func): """ Test decorator that handles ResponseError or its mock equivalent diff --git a/mockredis/tests/test_hash.py b/mockredis/tests/test_hash.py index 1a807bb..8ecce36 100644 --- a/mockredis/tests/test_hash.py +++ b/mockredis/tests/test_hash.py @@ -1,6 +1,6 @@ from nose.tools import eq_, ok_ -from mockredis.tests.fixtures import setup +from mockredis.tests.fixtures import setup, teardown class TestRedisHash(object): @@ -9,6 +9,9 @@ class TestRedisHash(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_hexists(self): hashkey = "hash" ok_(not self.redis.hexists(hashkey, "key")) diff --git a/mockredis/tests/test_list.py b/mockredis/tests/test_list.py index b5bbd61..c5fcf3c 100644 --- a/mockredis/tests/test_list.py +++ b/mockredis/tests/test_list.py @@ -2,18 +2,22 @@ from nose.tools import assert_raises, eq_ -from mockredis.tests.fixtures import setup +from mockredis.tests.fixtures import setup, teardown from mockredis.tests.test_constants import ( LIST1, LIST2, VAL1, VAL2, VAL3, VAL4, bLIST1, bLIST2, bVAL1, bVAL2, bVAL3, bVAL4, ) + class TestRedisList(object): """list tests""" def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_initially_empty(self): """ List is created empty. diff --git a/mockredis/tests/test_pipeline.py b/mockredis/tests/test_pipeline.py index c78c330..9082e73 100644 --- a/mockredis/tests/test_pipeline.py +++ b/mockredis/tests/test_pipeline.py @@ -4,7 +4,8 @@ from mockredis.tests.fixtures import (assert_raises_redis_error, assert_raises_watch_error, - setup) + setup, + teardown) class TestPipeline(object): @@ -12,6 +13,9 @@ class TestPipeline(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_pipeline(self): """ Pipeline execution returns all of the saved up values. diff --git a/mockredis/tests/test_redis.py b/mockredis/tests/test_redis.py index 718a5db..f502db7 100644 --- a/mockredis/tests/test_redis.py +++ b/mockredis/tests/test_redis.py @@ -4,7 +4,7 @@ from nose.tools import assert_raises, eq_, ok_ -from mockredis.tests.fixtures import setup +from mockredis.tests.fixtures import setup, teardown if sys.version_info >= (3, 0): long = int @@ -15,6 +15,9 @@ class TestRedis(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_get_types(self): ''' testing type conversions for set/get, hset/hget, sadd/smembers diff --git a/mockredis/tests/test_scan.py b/mockredis/tests/test_scan.py index 7fe572a..cd3a173 100644 --- a/mockredis/tests/test_scan.py +++ b/mockredis/tests/test_scan.py @@ -1,6 +1,6 @@ from nose.tools import eq_ -from mockredis.tests.fixtures import setup +from mockredis.tests.fixtures import setup, teardown class TestRedisEmptyScans(object): @@ -9,6 +9,9 @@ class TestRedisEmptyScans(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_scans(self): def eq_scan(results, cursor, elements): """ @@ -64,6 +67,9 @@ def setup(self): self.redis.set('key_xyz_4', '4') self.redis.set('key_xyz_5', '5') + def teardown(self): + teardown(self) + def test_scan(self): def do_full_scan(match, count): keys = set() # technically redis SCAN can return duplicate keys @@ -131,6 +137,9 @@ def setup(self): self.redis.sadd('key', 'xyz_4') self.redis.sadd('key', 'xyz_5') + def teardown(self): + teardown(self) + def test_scan(self): def do_full_scan(name, match, count): keys = set() # technically redis SCAN can return duplicate keys @@ -198,6 +207,9 @@ def setup(self): self.redis.zadd('key', 'xyz_4', 4) self.redis.zadd('key', 'xyz_5', 5) + def teardown(self): + teardown(self) + def test_scan(self): def do_full_scan(name, match, count): keys = set() # technically redis SCAN can return duplicate keys @@ -265,6 +277,9 @@ def setup(self): self.redis.hset('key', 'xyz_4', 4) self.redis.hset('key', 'xyz_5', 5) + def teardown(self): + teardown(self) + def test_scan(self): def do_full_scan(name, match, count): keys = {} diff --git a/mockredis/tests/test_set.py b/mockredis/tests/test_set.py index a2d39fa..c34f297 100644 --- a/mockredis/tests/test_set.py +++ b/mockredis/tests/test_set.py @@ -1,7 +1,7 @@ from nose.tools import assert_raises, eq_, ok_ from mockredis.exceptions import ResponseError -from mockredis.tests.fixtures import setup +from mockredis.tests.fixtures import setup, teardown class TestRedisSet(object): @@ -10,6 +10,9 @@ class TestRedisSet(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_sadd_empty(self): key = "set" values = [] diff --git a/mockredis/tests/test_string.py b/mockredis/tests/test_string.py index 95a16d0..1ae94fa 100644 --- a/mockredis/tests/test_string.py +++ b/mockredis/tests/test_string.py @@ -1,10 +1,9 @@ from datetime import timedelta -import sys from nose.tools import eq_, ok_ from mockredis.client import get_total_milliseconds -from mockredis.tests.fixtures import raises_response_error, setup +from mockredis.tests.fixtures import raises_response_error, setup, teardown class TestRedisString(object): @@ -13,6 +12,9 @@ class TestRedisString(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_get(self): eq_(None, self.redis.get('key')) self.redis.set('key', 'value') diff --git a/mockredis/tests/test_zset.py b/mockredis/tests/test_zset.py index 72f4cef..6e967e2 100644 --- a/mockredis/tests/test_zset.py +++ b/mockredis/tests/test_zset.py @@ -1,6 +1,6 @@ from nose.tools import assert_raises, eq_, ok_ -from mockredis.tests.fixtures import setup +from mockredis.tests.fixtures import setup, teardown class TestRedisZset(object): @@ -9,6 +9,9 @@ class TestRedisZset(object): def setup(self): setup(self) + def teardown(self): + teardown(self) + def test_zadd(self): key = "zset" values = [("one", 1), ("uno", 1), ("two", 2), ("three", 3)] From 375930de386b41dc1128ccf867e83e377ece2c97 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 13:18:18 -0700 Subject: [PATCH 23/27] Make elapsed time tests more flexible. When testing against a real redis server, these tests are subject to round trip times against the server and won't be quite as predictable. --- mockredis/tests/test_list.py | 38 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/mockredis/tests/test_list.py b/mockredis/tests/test_list.py index c5fcf3c..e1c10d3 100644 --- a/mockredis/tests/test_list.py +++ b/mockredis/tests/test_list.py @@ -1,14 +1,28 @@ +from contextlib import contextmanager import time -from nose.tools import assert_raises, eq_ +from nose.tools import assert_less, assert_raises, eq_ from mockredis.tests.fixtures import setup, teardown from mockredis.tests.test_constants import ( LIST1, LIST2, VAL1, VAL2, VAL3, VAL4, - bLIST1, bLIST2, bVAL1, bVAL2, bVAL3, bVAL4, + bLIST1, bVAL1, bVAL2, bVAL3, bVAL4, ) +@contextmanager +def assert_elapsed_time(expected=1.0, delta=2.0): + """ + Validate that work encapsulated by this context manager + takes at least and is within a delta of the expected amount of time. + """ + start = time.time() + yield + diff = time.time() - start + assert_less(expected, diff) + assert_less(diff, expected + delta) + + class TestRedisList(object): """list tests""" @@ -62,11 +76,10 @@ def test_blpop(self): eq_(1, len(self.redis.lrange(LIST1, 0, -1))) eq_((bLIST1, bVAL2), self.redis.blpop(LIST1)) eq_(0, len(self.redis.lrange(LIST1, 0, -1))) + timeout = 1 - start = time.time() - eq_(None, self.redis.blpop(LIST1, timeout)) - eq_(timeout, int(time.time() - start)) - eq_([], self.redis.keys("*")) + with assert_elapsed_time(expected=timeout): + eq_(None, self.redis.blpop(LIST1, timeout)) def test_lpush(self): """ @@ -103,10 +116,11 @@ def test_brpop(self): eq_(1, len(self.redis.lrange(LIST1, 0, -1))) eq_((bLIST1, bVAL1), self.redis.brpop(LIST1)) eq_(0, len(self.redis.lrange(LIST1, 0, -1))) + timeout = 1 - start = time.time() - eq_(None, self.redis.brpop(LIST1, timeout)) - eq_(timeout, int(time.time() - start)) + with assert_elapsed_time(expected=timeout): + eq_(None, self.redis.brpop(LIST1, timeout)) + eq_([], self.redis.keys("*")) def test_rpush(self): @@ -193,10 +207,10 @@ def test_brpoplpush(self): eq_([], self.redis.lrange(LIST1, 0, -1)) eq_([bVAL1, bVAL2, bVAL3, bVAL4], self.redis.lrange(LIST2, 0, -1)) + timeout = 1 - start = time.time() - eq_(None, self.redis.brpoplpush(LIST1, LIST2, timeout)) - eq_(timeout, int(time.time() - start)) + with assert_elapsed_time(expected=timeout): + eq_(None, self.redis.brpoplpush(LIST1, LIST2, timeout)) def test_rpoplpush(self): self.redis.rpush(LIST1, VAL1, VAL2) From 58244d71f2ed6ed85aab4e831e2f5611b4cb3fea Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 13:41:18 -0700 Subject: [PATCH 24/27] register_script() doesn't actually register a script until the Script object is called Use load_script() instead --- mockredis/tests/test_pipeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mockredis/tests/test_pipeline.py b/mockredis/tests/test_pipeline.py index 9082e73..47eb00d 100644 --- a/mockredis/tests/test_pipeline.py +++ b/mockredis/tests/test_pipeline.py @@ -53,7 +53,8 @@ def test_scripts(self): script_content = "redis.call('PING')" sha = sha1(script_content.encode("utf-8")).hexdigest() - self.redis.register_script(script_content) + script_sha = self.redis.script_load(script_content) + eq_(script_sha, sha) # Script exists in mock redis eq_([True], self.redis.script_exists(sha)) From c0332f7af1fca59805a967182b37b41eb0006e27 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 13:56:52 -0700 Subject: [PATCH 25/27] Fix flake8 and enforce linting via tox --- mockredis/client.py | 44 ++++----- mockredis/exceptions.py | 2 - mockredis/pipeline.py | 2 +- mockredis/script.py | 3 +- mockredis/sortedset.py | 8 +- mockredis/tests/test_list.py | 6 +- mockredis/tests/test_normalize.py | 1 - mockredis/tests/test_scan.py | 8 +- mockredis/tests/test_script.py | 6 +- mockredis/tests/test_set.py | 4 +- mockredis/tests/test_sortedset.py | 10 +- mockredis/tests/test_string.py | 150 +++++++++++++++--------------- mockredis/tests/test_zset.py | 2 +- tox.ini | 9 +- 14 files changed, 132 insertions(+), 123 deletions(-) diff --git a/mockredis/client.py b/mockredis/client.py index ce7eed2..998d3c5 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -57,7 +57,7 @@ def __init__(self, # Dictionary from script to sha ''Script'' self.shas = dict() - #### Connection Functions #### + # Connection Functions # def echo(self, msg): return self._encode(msg) @@ -65,7 +65,7 @@ def echo(self, msg): def ping(self): return b'PONG' - #### Transactions Functions #### + # Transactions Functions # def lock(self, key, timeout=0, sleep=0): """Emulate lock.""" @@ -101,7 +101,7 @@ def execute(self): in this mock, so this is a no-op.""" pass - #### Keys Functions #### + # Keys Functions # def type(self, key): key = self._encode(key) @@ -247,7 +247,7 @@ def _rename(self, old_key, new_key, nx=False): return True return False - #### String Functions #### + # String Functions # def get(self, key): key = self._encode(key) @@ -452,8 +452,7 @@ def _get_bits_and_offset(self, key, offset): mask = 128 >> position return index, bits, mask - - #### Hash Functions #### + # Hash Functions # def hexists(self, hashkey, attribute): """Emulate hexists.""" @@ -558,7 +557,7 @@ def hvals(self, hashkey): redis_hash = self._get_hash(hashkey, 'HVALS') return redis_hash.values() - #### List Functions #### + # List Functions # def lrange(self, key, start, stop): """Emulate lrange.""" @@ -766,7 +765,7 @@ def sort(self, name, return [] by = self._encode(by) if by is not None else by - # always organize the items as tuples of the value from the list itself and the value to sort by + # always organize the items as tuples of the value from the list and the sort key if by and b'*' in by: items = [(i, self.get(by.replace(b'*', self._encode(i)))) for i in items] elif by in [None, b'nosort']: @@ -822,7 +821,7 @@ def sort(self, name, else: return results - #### SCAN COMMANDS #### + # SCAN COMMANDS # def _common_scan(self, values_function, cursor='0', match=None, count=10, key=None): """ @@ -891,7 +890,7 @@ def value_function(): values = self.zrange(name, 0, -1, withscores=True) values.sort(key=lambda x: x[1]) # sort for consistent order return values - return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) + return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) # noqa def zscan_iter(self, name, match=None, count=10): """Emulate zscan_iter.""" @@ -909,7 +908,7 @@ def value_function(): values = list(values.items()) # list of tuples for sorting and matching values.sort(key=lambda x: x[0]) # sort for consistent order return values - scanned = self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) + scanned = self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) # noqa scanned[1] = dict(scanned[1]) # from list of tuples back to dict return scanned @@ -922,7 +921,7 @@ def hscan_iter(self, name, match=None, count=10): for item in data.items(): yield item - #### SET COMMANDS #### + # SET COMMANDS # def sadd(self, key, *values): """Emulate sadd.""" @@ -1035,7 +1034,7 @@ def sunionstore(self, dest, keys, *args): self.redis[self._encode(dest)] = result return len(result) - #### SORTED SET COMMANDS #### + # SORTED SET COMMANDS # def zadd(self, name, *args, **kwargs): zset = self._get_zset(name, "ZADD", create=True) @@ -1056,7 +1055,7 @@ def zadd(self, name, *args, **kwargs): # kwargs pieces.extend(kwargs.items()) - insert_count = lambda member, score: 1 if zset.insert(self._encode(member), float(score)) else 0 + insert_count = lambda member, score: 1 if zset.insert(self._encode(member), float(score)) else 0 # noqa return sum((insert_count(member, score) for member, score in pieces)) def zcard(self, name): @@ -1129,7 +1128,7 @@ def zrangebyscore(self, name, min, max, start=None, num=None, func = self._range_func(withscores, score_cast_func) include_start, min = self._score_inclusive(min) include_end, max = self._score_inclusive(max) - scorerange = zset.scorerange(min, max, start_inclusive=include_start, end_inclusive=include_end) + scorerange = zset.scorerange(min, max, start_inclusive=include_start, end_inclusive=include_end) # noqa if start is not None and num is not None: start, num = self._translate_limit(len(scorerange), int(start), int(num)) scorerange = scorerange[start:start + num] @@ -1160,7 +1159,7 @@ def zremrangebyrank(self, name, start, end): start, end = self._translate_range(len(zset), start, end) count_removals = lambda score, member: 1 if zset.remove(member) else 0 - removal_count = sum((count_removals(score, member) for score, member in zset.range(start, end))) + removal_count = sum((count_removals(score, member) for score, member in zset.range(start, end))) # noqa if removal_count > 0 and len(zset) == 0: self.delete(name) return removal_count @@ -1203,7 +1202,8 @@ def zrevrangebyscore(self, name, max, min, start=None, num=None, include_end, max = self._score_inclusive(max) scorerange = [x for x in reversed(zset.scorerange(float(min), float(max), - start_inclusive=include_start, end_inclusive=include_end))] + start_inclusive=include_start, + end_inclusive=include_end))] if start is not None and num is not None: start, num = self._translate_limit(len(scorerange), int(start), int(num)) scorerange = scorerange[start:start + num] @@ -1245,7 +1245,7 @@ def zunionstore(self, dest, keys, aggregate=None): self.redis[self._encode(dest)] = union return len(union) - #### Script Commands #### + # Script Commands # def eval(self, script, numkeys, *keys_and_args): """Emulate eval""" @@ -1354,12 +1354,12 @@ def _normalize_command_response(self, command, response): return response - #### PubSub commands #### + # PubSub commands # def publish(self, channel, message): self.pubsub[channel].append(message) - #### Internal #### + # Internal # def _get_list(self, key, operation, create=False): """ @@ -1383,7 +1383,7 @@ def _get_zset(self, name, operation, create=False): """ Get (and maybe create) a sorted set by name. """ - return self._get_by_type(name, operation, create, b'zset', SortedSet(), return_default=False) + return self._get_by_type(name, operation, create, b'zset', SortedSet(), return_default=False) # noqa def _get_by_type(self, key, operation, create, type_, default, return_default=True): """ @@ -1423,7 +1423,7 @@ def _range_func(self, withscores, score_cast_func): Return a suitable function from (score, member) """ if withscores: - return lambda score_member: (score_member[1], score_cast_func(self._encode(score_member[0]))) + return lambda score_member: (score_member[1], score_cast_func(self._encode(score_member[0]))) # noqa else: return lambda score_member: score_member[1] diff --git a/mockredis/exceptions.py b/mockredis/exceptions.py index acae1fd..ddda94c 100644 --- a/mockredis/exceptions.py +++ b/mockredis/exceptions.py @@ -11,10 +11,8 @@ class RedisError(Exception): pass - class ResponseError(RedisError): pass - class WatchError(RedisError): pass diff --git a/mockredis/pipeline.py b/mockredis/pipeline.py index 179391a..f178570 100644 --- a/mockredis/pipeline.py +++ b/mockredis/pipeline.py @@ -39,7 +39,7 @@ def watch(self, *keys): raise RedisError("Cannot issue a WATCH after a MULTI") self.watching = True for key in keys: - self._watched_keys[key] = deepcopy(self.mock_redis.redis.get(self.mock_redis._encode(key))) + self._watched_keys[key] = deepcopy(self.mock_redis.redis.get(self.mock_redis._encode(key))) # noqa def multi(self): """ diff --git a/mockredis/script.py b/mockredis/script.py index 39eb840..c0a3d48 100644 --- a/mockredis/script.py +++ b/mockredis/script.py @@ -4,6 +4,7 @@ LuaLock = threading.Lock() + class Script(object): """ An executable Lua script object returned by ``MockRedis.register_script``. @@ -40,7 +41,7 @@ def _call(*call_args): if str(call_args[0]).lower() == 'lrem': response = client.call( call_args[0], call_args[1], - call_args[3], # "count", default is 0 + call_args[3], # "count", default is 0 call_args[2]) else: response = client.call(*call_args) diff --git a/mockredis/sortedset.py b/mockredis/sortedset.py index 8e9290f..397a59a 100644 --- a/mockredis/sortedset.py +++ b/mockredis/sortedset.py @@ -10,10 +10,10 @@ class SortedSet(object): 1. A multimap from score to member 2. A dictionary from member to score. - The multimap is implemented using a sorted list of (score, member) pairs. The bisect operations - used to maintain the multimap are O(log N), but insertion into and removal from a list are O(N), - so insertion and removal O(N). It should be possible to swap in an indexable skip list to get - the expected O(log N) behavior. + The multimap is implemented using a sorted list of (score, member) pairs. The bisect + operations used to maintain the multimap are O(log N), but insertion into and removal + from a list are O(N), so insertion and removal O(N). It should be possible to swap in + an indexable skip list to get the expected O(log N) behavior. """ def __init__(self): """ diff --git a/mockredis/tests/test_list.py b/mockredis/tests/test_list.py index e1c10d3..5269d10 100644 --- a/mockredis/tests/test_list.py +++ b/mockredis/tests/test_list.py @@ -383,11 +383,11 @@ def test_sort(self): eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], start=1, num=1), [b'c', b'z']) # test multiple gets with grouping - eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], groups=True), [(b'a', b'x'), (b'c', b'z'), (b'b', b'y')]) + eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], groups=True), [(b'a', b'x'), (b'c', b'z'), (b'b', b'y')]) # noqa # test start and num - eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], groups=True, start=1, num=1), [(b'c', b'z')]) - eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], groups=True, start=1, num=2), [(b'c', b'z'), (b'b', b'y')]) + eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], groups=True, start=1, num=1), [(b'c', b'z')]) # noqa + eq_(self.redis.sort(LIST1, get=['get1_*', 'get2_*'], groups=True, start=1, num=2), [(b'c', b'z'), (b'b', b'y')]) # noqa def test_lset(self): with assert_raises(Exception): diff --git a/mockredis/tests/test_normalize.py b/mockredis/tests/test_normalize.py index d3f7a2b..4e47543 100644 --- a/mockredis/tests/test_normalize.py +++ b/mockredis/tests/test_normalize.py @@ -70,4 +70,3 @@ def _test(command, response, expected): for command, response, expected in cases: yield _test, command, response, expected - diff --git a/mockredis/tests/test_scan.py b/mockredis/tests/test_scan.py index cd3a173..8664214 100644 --- a/mockredis/tests/test_scan.py +++ b/mockredis/tests/test_scan.py @@ -82,7 +82,7 @@ def do_full_scan(match, count): result_cursor = cursor return keys - abc_keys = set([b'key_abc_1', b'key_abc_2', b'key_abc_3', b'key_abc_4', b'key_abc_5', b'key_abc_6']) + abc_keys = set([b'key_abc_1', b'key_abc_2', b'key_abc_3', b'key_abc_4', b'key_abc_5', b'key_abc_6']) # noqa eq_(do_full_scan('*abc*', 1), abc_keys) eq_(do_full_scan('*abc*', 2), abc_keys) eq_(do_full_scan('*abc*', 10), abc_keys) @@ -222,7 +222,7 @@ def do_full_scan(name, match, count): result_cursor = cursor return keys - abc_members = set([(b'abc_1', 1), (b'abc_2', 2), (b'abc_3', 3), (b'abc_4', 4), (b'abc_5', 5), (b'abc_6', 6)]) + abc_members = set([(b'abc_1', 1), (b'abc_2', 2), (b'abc_3', 3), (b'abc_4', 4), (b'abc_5', 5), (b'abc_6', 6)]) # noqa eq_(do_full_scan('key', '*abc*', 1), abc_members) eq_(do_full_scan('key', '*abc*', 2), abc_members) eq_(do_full_scan('key', '*abc*', 10), abc_members) @@ -231,7 +231,7 @@ def do_full_scan(name, match, count): members.add(m) eq_(members, abc_members) - xyz_members = set([(b'xyz_1', 1), (b'xyz_2', 2), (b'xyz_3', 3), (b'xyz_4', 4), (b'xyz_5', 5)]) + xyz_members = set([(b'xyz_1', 1), (b'xyz_2', 2), (b'xyz_3', 3), (b'xyz_4', 4), (b'xyz_5', 5)]) # noqa eq_(do_full_scan('key', '*xyz*', 1), xyz_members) eq_(do_full_scan('key', '*xyz*', 2), xyz_members) eq_(do_full_scan('key', '*xyz*', 10), xyz_members) @@ -292,7 +292,7 @@ def do_full_scan(name, match, count): result_cursor = cursor return keys - abc = {b'abc_1': b'1', b'abc_2': b'2', b'abc_3': b'3', b'abc_4': b'4', b'abc_5': b'5', b'abc_6': b'6'} + abc = {b'abc_1': b'1', b'abc_2': b'2', b'abc_3': b'3', b'abc_4': b'4', b'abc_5': b'5', b'abc_6': b'6'} # noqa eq_(do_full_scan('key', '*abc*', 1), abc) eq_(do_full_scan('key', '*abc*', 2), abc) eq_(do_full_scan('key', '*abc*', 10), abc) diff --git a/mockredis/tests/test_script.py b/mockredis/tests/test_script.py index 84fc3a5..b1d4f81 100644 --- a/mockredis/tests/test_script.py +++ b/mockredis/tests/test_script.py @@ -131,7 +131,7 @@ def test_register_script_rpoplpush(self): script = self.redis.register_script(script_content) script(keys=[LIST1, LIST2]) - #validate rpoplpush + # validate rpoplpush eq_([VAL1], self.redis.lrange(LIST1, 0, -1)) eq_([VAL2, VAL3, VAL4], self.redis.lrange(LIST2, 0, -1)) @@ -147,7 +147,7 @@ def test_register_script_rpop_lpush(self): script = self.redis.register_script(script_content) script(keys=[LIST1, LIST2]) - #validate rpop and then lpush + # validate rpop and then lpush eq_([VAL1], self.redis.lrange(LIST1, 0, -1)) eq_([VAL2, VAL3, VAL4], self.redis.lrange(LIST2, 0, -1)) @@ -402,7 +402,7 @@ def test_concurrent_lua(self): def lua_thread(): for i in range(500): - result = script(args=[i]) + script(args=[i]) active_threads = [] for i in range(10): diff --git a/mockredis/tests/test_set.py b/mockredis/tests/test_set.py index c34f297..33205d1 100644 --- a/mockredis/tests/test_set.py +++ b/mockredis/tests/test_set.py @@ -38,7 +38,7 @@ def test_sadd_duplicate_key(self): def test_scard(self): key = "set" eq_(0, self.redis.scard(key)) - ok_(not key in self.redis) + ok_(key not in self.redis) values = ["one", "uno", "two", "three"] eq_(4, self.redis.sadd(key, *values)) eq_(4, self.redis.scard(key)) @@ -117,7 +117,7 @@ def test_sismember(self): key = "set" ok_(not self.redis.sismember(key, "one")) ok_(key not in self.redis) - + eq_(1, self.redis.sadd(key, "one")) ok_(self.redis.sismember(key, "one")) ok_(not self.redis.sismember(key, "two")) diff --git a/mockredis/tests/test_sortedset.py b/mockredis/tests/test_sortedset.py index e6bfa8a..8e38c43 100644 --- a/mockredis/tests/test_sortedset.py +++ b/mockredis/tests/test_sortedset.py @@ -29,8 +29,8 @@ def test_insert(self): eq_(2, len(self.zset)) ok_("one" in self.zset) ok_("two" in self.zset) - ok_(not 1.0 in self.zset) - ok_(not 2.0 in self.zset) + ok_(1.0 not in self.zset) + ok_(2.0 not in self.zset) eq_(1.0, self.zset["one"]) eq_(2.0, self.zset["two"]) with assert_raises(KeyError): @@ -96,9 +96,9 @@ def test_scoremap_inclusive(self): self.zset["two"] = 2.0 self.zset["three"] = 3.0 eq_([], self.zset.scorerange(1.0, 1.1, start_inclusive=False, end_inclusive=False)) - eq_([(1.0, "one"), (1.0, "uno")], self.zset.scorerange(1.0, 1.1, start_inclusive=True, end_inclusive=False)) - eq_([(1.1, "uno_dot_one")], self.zset.scorerange(1.0, 1.1, start_inclusive=False, end_inclusive=True)) - eq_([(1.1, "uno_dot_one")], self.zset.scorerange(1.0, 2.0, start_inclusive=False, end_inclusive=False)) + eq_([(1.0, "one"), (1.0, "uno")], self.zset.scorerange(1.0, 1.1, start_inclusive=True, end_inclusive=False)) # noqa + eq_([(1.1, "uno_dot_one")], self.zset.scorerange(1.0, 1.1, start_inclusive=False, end_inclusive=True)) # noqa + eq_([(1.1, "uno_dot_one")], self.zset.scorerange(1.0, 2.0, start_inclusive=False, end_inclusive=False)) # noqa eq_([(1.1, "uno_dot_one"), (2.0, "two")], self.zset.scorerange(1.0, 3.0, start_inclusive=False, end_inclusive=False)) diff --git a/mockredis/tests/test_string.py b/mockredis/tests/test_string.py index 1ae94fa..4934921 100644 --- a/mockredis/tests/test_string.py +++ b/mockredis/tests/test_string.py @@ -39,8 +39,10 @@ def _assert_set_with_options(self, test_cases): """ Assert conditions for setting a key on the set function. - The set function can take px, ex, nx and xx kwargs, this function asserts various conditions - on set depending on the combinations of kwargs: creation mode(nx,xx) and expiration(ex,px). + The set function can take px, ex, nx and xx kwargs, this function asserts various + conditions on the set depending on the combinations of kwargs: creation mode(nx,xx) + and expiration(ex,px). + E.g. verifying that a non-existent key does not get set if xx=True or gets set with nx=True iff it is absent. """ @@ -83,81 +85,84 @@ def _assert_was_set(self, key, value, config, msg, delta=1): def test_set_with_options(self): """Test the set function with various combinations of arguments""" - test_cases = [("1. px and ex are set, nx is always true & set on non-existing key", - False, - [(('key1', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), - (('key2', 'value1', True), dict(ex=20, px=70000, xx=False, nx=True)), - (('key3', 'value2', True), dict(ex=20, px=70000, nx=True))]), - - ("2. px and ex are set, nx is always true & set on existing key", - True, - [(('key', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), - (('key', 'value1', None), dict(ex=20, px=7000, xx=False, nx=True)), - (('key', 'value1', None), dict(ex=20, px=70000, nx=True))]), - - ("3. px and ex are set, xx is always true & set on existing key", - True, - [(('key', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), - (('key', 'value1', True), dict(ex=20, px=70000, xx=True, nx=False)), - (('key', 'value4', True), dict(ex=20, px=70000, xx=True))]), - - ("4. px and ex are set, xx is always true & set on non-existing key", - False, - [(('key1', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), - (('key2', 'value2', None), dict(ex=20, px=70000, xx=True, nx=False)), - (('key3', 'value3', None), dict(ex=20, px=70000, xx=True))]), - - ("5. either nx or xx defined and set to false or none defined" + - " & set on existing key", - True, - [(('key', 'value1', True), dict(ex=20, px=70000, xx=False)), - (('key', 'value2', True), dict(ex=20, px=70000, nx=False)), - (('key', 'value3', True), dict(ex=20, px=70000))]), - - ("6. either nx or xx defined and set to false or none defined" + - " & set on non-existing key", - False, - [(('key1', 'value1', True), dict(ex=20, px=70000, xx=False)), - (('key2', 'value2', True), dict(ex=20, px=70000, nx=False)), - (('key3', 'value3', True), dict(ex=20, px=70000))]), - - ("7: where neither px nor ex defined + set on existing key", - True, - [(('key', 'value2', None), dict(xx=True, nx=True)), - (('key', 'value2', None), dict(xx=False, nx=True)), - (('key', 'value2', True), dict(xx=True, nx=False)), - (('key', 'value3', True), dict(xx=True)), - (('key', 'value4', None), dict(nx=True)), - (('key', 'value4', True), dict(xx=False)), - (('key', 'value5', True), dict(nx=False))]), - - ("8: where neither px nor ex defined + set on non-existing key", - False, - [(('key1', 'value1', None), dict(xx=True, nx=True)), - (('key2', 'value1', True), dict(xx=False, nx=True)), - (('key3', 'value2', None), dict(xx=True, nx=False)), - (('key4', 'value3', None), dict(xx=True)), - (('key5', 'value4', True), dict(nx=True)), - (('key6', 'value4', True), dict(xx=False)), - (('key7', 'value5', True), dict(nx=False))]), - - ("9: where neither nx nor xx defined + set on existing key", - True, - [(('key', 'value1', True), dict(ex=20, px=70000)), - (('key1', 'value12', True), dict(ex=20)), - (('key1', 'value11', True), dict(px=20000))]), - - ("10: where neither nx nor xx is defined + set on non-existing key", - False, - [(('key1', 'value1', True), dict(ex=20, px=70000)), - (('key2', 'value2', True), dict(ex=20)), - (('key3', 'value3', True), dict(px=20000))])] + test_cases = [ + ("1. px and ex are set, nx is always true & set on non-existing key", + False, + [(('key1', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), + (('key2', 'value1', True), dict(ex=20, px=70000, xx=False, nx=True)), + (('key3', 'value2', True), dict(ex=20, px=70000, nx=True))]), + + ("2. px and ex are set, nx is always true & set on existing key", + True, + [(('key', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), + (('key', 'value1', None), dict(ex=20, px=7000, xx=False, nx=True)), + (('key', 'value1', None), dict(ex=20, px=70000, nx=True))]), + + ("3. px and ex are set, xx is always true & set on existing key", + True, + [(('key', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), + (('key', 'value1', True), dict(ex=20, px=70000, xx=True, nx=False)), + (('key', 'value4', True), dict(ex=20, px=70000, xx=True))]), + + ("4. px and ex are set, xx is always true & set on non-existing key", + False, + [(('key1', 'value1', None), dict(ex=20, px=70000, xx=True, nx=True)), + (('key2', 'value2', None), dict(ex=20, px=70000, xx=True, nx=False)), + (('key3', 'value3', None), dict(ex=20, px=70000, xx=True))]), + + ("5. either nx or xx defined and set to false or none defined" + + " & set on existing key", + True, + [(('key', 'value1', True), dict(ex=20, px=70000, xx=False)), + (('key', 'value2', True), dict(ex=20, px=70000, nx=False)), + (('key', 'value3', True), dict(ex=20, px=70000))]), + + ("6. either nx or xx defined and set to false or none defined" + + " & set on non-existing key", + False, + [(('key1', 'value1', True), dict(ex=20, px=70000, xx=False)), + (('key2', 'value2', True), dict(ex=20, px=70000, nx=False)), + (('key3', 'value3', True), dict(ex=20, px=70000))]), + + ("7: where neither px nor ex defined + set on existing key", + True, + [(('key', 'value2', None), dict(xx=True, nx=True)), + (('key', 'value2', None), dict(xx=False, nx=True)), + (('key', 'value2', True), dict(xx=True, nx=False)), + (('key', 'value3', True), dict(xx=True)), + (('key', 'value4', None), dict(nx=True)), + (('key', 'value4', True), dict(xx=False)), + (('key', 'value5', True), dict(nx=False))]), + + ("8: where neither px nor ex defined + set on non-existing key", + False, + [(('key1', 'value1', None), dict(xx=True, nx=True)), + (('key2', 'value1', True), dict(xx=False, nx=True)), + (('key3', 'value2', None), dict(xx=True, nx=False)), + (('key4', 'value3', None), dict(xx=True)), + (('key5', 'value4', True), dict(nx=True)), + (('key6', 'value4', True), dict(xx=False)), + (('key7', 'value5', True), dict(nx=False))]), + + ("9: where neither nx nor xx defined + set on existing key", + True, + [(('key', 'value1', True), dict(ex=20, px=70000)), + (('key1', 'value12', True), dict(ex=20)), + (('key1', 'value11', True), dict(px=20000))]), + + ("10: where neither nx nor xx is defined + set on non-existing key", + False, + [(('key1', 'value1', True), dict(ex=20, px=70000)), + (('key2', 'value2', True), dict(ex=20)), + (('key3', 'value3', True), dict(px=20000))])] for cases in test_cases: yield self._assert_set_with_options, cases def _assert_set_with_timeout(self, seconds): - """Assert both strict and non-strict that setex sets a key with a value along with a timeout""" + """ + Assert both strict and non-strict that setex sets a key with a value along with a timeout. + """ eq_(None, self.redis_strict.get('key')) eq_(None, self.redis.get('key')) @@ -280,7 +285,6 @@ def test_getset(self): eq_(b'1', self.redis.getset('getset_key', '2')) eq_(b'2', self.redis.get('getset_key')) - def test_setbit(self): # test behavior on empty keys diff --git a/mockredis/tests/test_zset.py b/mockredis/tests/test_zset.py index 6e967e2..7393c06 100644 --- a/mockredis/tests/test_zset.py +++ b/mockredis/tests/test_zset.py @@ -373,7 +373,7 @@ def test_zremrangebyscore_inclusive(self): eq_([b"two", b"three", b"four", b"five"], self.redis.zrange(key, 0, -1)) - eq_(2, self.redis.zremrangebyscore(key, '(2', 4)) #remove "three" & "four" + eq_(2, self.redis.zremrangebyscore(key, '(2', 4)) # remove "three" & "four" eq_([b"two", b"five"], self.redis.zrange(key, 0, -1)) eq_(1, self.redis.zremrangebyscore(key, "(2.0", "inf")) diff --git a/tox.ini b/tox.ini index c225109..fa74442 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,12 @@ [tox] -envlist = py27, py32, py33, py34, pypy +envlist = py27, py32, py33, py34, pypy, lint [testenv] commands = python setup.py nosetests + +[testenv:lint] +commands=flake8 --max-line-length 99 mockredis +basepython=python2.7 +deps= + flake8 + flake8-print From 86548c62468281bbd37b1d53665875b428ea9374 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 15:38:39 -0700 Subject: [PATCH 26/27] LUA dependencies are not required to run tests. --- mockredis/tests/test_script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mockredis/tests/test_script.py b/mockredis/tests/test_script.py index b1d4f81..bb000b6 100644 --- a/mockredis/tests/test_script.py +++ b/mockredis/tests/test_script.py @@ -30,11 +30,11 @@ class TestScript(object): """ def setup(self): - self.redis = MockRedis() + self.redis = MockRedis(load_lua_dependencies=False) self.LPOP_SCRIPT_SHA = sha1(LPOP_SCRIPT.encode("utf-8")).hexdigest() try: - lua, lua_globals = MockRedisScript._import_lua() + lua, lua_globals = MockRedisScript._import_lua(load_dependencies=False) except RuntimeError: raise SkipTest("mockredispy was not installed with lua support") From 8d1ba33ae7f68a2c12aa3d5d36f985aa87658037 Mon Sep 17 00:00:00 2001 From: Jesse Myers Date: Wed, 6 May 2015 15:44:07 -0700 Subject: [PATCH 27/27] Document noseplugin with example in source. (#69) --- mockredis/noseplugin.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mockredis/noseplugin.py b/mockredis/noseplugin.py index 2eaf495..009c614 100644 --- a/mockredis/noseplugin.py +++ b/mockredis/noseplugin.py @@ -1,8 +1,13 @@ """ This module includes a nose plugin that allows unit tests to be run with a real -redis-server instance running locally (assuming redis-py) is installed. This provides -a simple way to verify that mockredis tests are accurate (at least for a particular -version of redis-server and redis-py). +redis-server instance, as long as redis-py is installed. + +This provides a simple way to verify that mockredis tests are accurate (at least +for a particular version of redis-server and redis-py). + +Usage: + + nosetests --use-redis [--redis-host ] [--redis-database ] [args] For this plugin to work, several things need to be true: