diff --git a/README.md b/README.md index a4a5a41..303f14e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ Use pip: ## Usage Both `mockredis.mock_redis_client` and `mockredis.mock_strict_redis_client` can be -used to patch instances of the *redis client*. +used to patch instances of the *redis client*, and `get_mock_redis_client_creator` +can be used to create a generator for more flexible mocks. For example, using the [mock][mock] library: @@ -24,6 +25,11 @@ Or: @patch('redis.StrictRedis', mock_strict_redis_client) +Or, for more control: + + @patch('redis.Redis', get_mock_redis_client_creator(load_lua_dependencies=False)) + @patch('redis.StrictRedis', get_mock_redis_client_creator(strict=True, clock=my_frozen_clock)) + ## Testing Many unit tests exist to verify correctness of mock functionality. In addition, most diff --git a/mockredis/client.py b/mockredis/client.py index 926e048..e447535 100644 --- a/mockredis/client.py +++ b/mockredis/client.py @@ -1561,7 +1561,7 @@ def get_total_milliseconds(td): return int((td.days * 24 * 60 * 60 + td.seconds) * 1000 + td.microseconds / 1000.0) -def mock_redis_client(**kwargs): +def mock_redis_client(*_, **__): """ Mock common.util.redis_client so we can return a MockRedis object @@ -1572,7 +1572,7 @@ def mock_redis_client(**kwargs): mock_redis_client.from_url = mock_redis_client -def mock_strict_redis_client(**kwargs): +def mock_strict_redis_client(*_, **__): """ Mock common.util.redis_client so we can return a MockRedis object @@ -1581,3 +1581,22 @@ def mock_strict_redis_client(**kwargs): return MockRedis(strict=True) mock_strict_redis_client.from_url = mock_strict_redis_client + + +def get_mock_redis_client_creator(**kwargs): + """ + Generate a getter for a MockRedis + object that passes the given kwargs + to each MockRedis object instantiated + by the getter returned. Sample usage: + + @mock.patch('redis.Redis', get_mock_redis_client_creator(load_lua_dependencies=False)) + @mock.patch('redis.StrictRedis', get_mock_redis_client_creator(strict=True, clock=frozen_clock)) + """ + + def _getter(*_, **__): + return MockRedis(**kwargs) + + _getter.from_url = _getter + + return _getter diff --git a/mockredis/script.py b/mockredis/script.py index c0a3d48..cfdb679 100644 --- a/mockredis/script.py +++ b/mockredis/script.py @@ -2,8 +2,26 @@ import threading from mockredis.exceptions import ResponseError + LuaLock = threading.Lock() +if sys.version_info[0] == 3: + _string_types = (str, ) + _integer_types = (int, ) + _number_types = (int, float) + _string_or_binary_types = (str, bytes) + _binary_type = bytes + _long_type = int + _iteritems = lambda d, **kw: iter(d.items(**kw)) +else: + _string_types = (basestring, ) + _integer_types = (int, long) + _number_types = (int, long, float) + _string_or_binary_types = (basestring, ) + _binary_type = str + _long_type = long + _iteritems = lambda d, **kw: d.iteritems(**kw) + class Script(object): """ @@ -47,7 +65,14 @@ def _call(*call_args): response = client.call(*call_args) return self._python_to_lua(response) - lua_globals.redis = {"call": _call} + def _reply_table(field, message): + return lua.eval("{{{field}='{message}'}}".format(field=field, message=message)) + + lua_globals.redis = { + 'call': _call, + 'status_reply': lambda status: _reply_table('ok', status), + 'error_reply': lambda error: _reply_table('err', error), + } return self._lua_to_python(lua.execute(self.script), return_status=True) @staticmethod @@ -117,9 +142,9 @@ def _lua_to_python(lval, return_status=False): raise ResponseError(lval[i]) pval.append(Script._lua_to_python(lval[i])) return pval - elif isinstance(lval, long): + elif isinstance(lval, _integer_types): # Lua number --> Python long - return long(lval) + return _long_type(lval) elif isinstance(lval, float): # Lua number --> Python float return float(lval) @@ -161,17 +186,17 @@ def _python_to_lua(pval): # in Lua returns: {k1, v1, k2, v2, k3, v3} lua_dict = lua.eval("{}") lua_table = lua.eval("table") - for k, v in pval.iteritems(): + for k, v in _iteritems(pval): lua_table.insert(lua_dict, Script._python_to_lua(k)) lua_table.insert(lua_dict, Script._python_to_lua(v)) return lua_dict - elif isinstance(pval, str): + elif isinstance(pval, _string_or_binary_types): # Python string --> Lua userdata return pval elif isinstance(pval, bool): # Python bool--> Lua boolean return lua.eval(str(pval).lower()) - elif isinstance(pval, (int, long, float)): + elif isinstance(pval, _number_types): # Python int --> Lua number lua_globals = lua.globals() return lua_globals.tonumber(str(pval)) diff --git a/mockredis/tests/test_script.py b/mockredis/tests/test_script.py index bb000b6..55aab19 100644 --- a/mockredis/tests/test_script.py +++ b/mockredis/tests/test_script.py @@ -17,7 +17,6 @@ VAL1, VAL2, VAL3, VAL4, LPOP_SCRIPT ) -from mockredis.tests.fixtures import raises_response_error if sys.version_info >= (3, 0): @@ -383,11 +382,24 @@ def test_lua_ok_return(self): 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() + with assert_raises(Exception) as error_context: + script() + eq_('ERROR Some message', error_context.exception.args[0]) + + def test_lua_redis_status_reply(self): + script_content = "return redis.status_reply('OK')" + script = self.redis.register_script(script_content) + eq_('OK', script()) + + def test_lua_redis_error_reply(self): + script_content = "return redis.error_reply('my error')" + script = self.redis.register_script(script_content) + with assert_raises(Exception) as error_context: + script() + eq_('my error', error_context.exception.args[0]) def test_concurrent_lua(self): script_content = """ diff --git a/setup.py b/setup.py index aa59785..aec7508 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ 'nose' ], extras_require={ - 'lua': ['lunatic-python-bugfix==1.1.1'], + 'lua': ['lunatic-python-universal~=2.0'], }, tests_require=[ 'redis>=2.9.0'