From 123b8478d293cb9a1dda1a6f21fde9ad6d9c4cff Mon Sep 17 00:00:00 2001 From: Benedikt Groever Date: Sun, 22 May 2022 10:20:42 -0700 Subject: [PATCH 1/2] add lmove operation --- fakeredis/_server.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fakeredis/_server.py b/fakeredis/_server.py index 99d75adf..3f0a812c 100644 --- a/fakeredis/_server.py +++ b/fakeredis/_server.py @@ -1779,6 +1779,16 @@ def linsert(self, key, where, pivot, value): def llen(self, key): return len(key.value) + @command((Key(list, None), Key(list))) + def lmove(self, first_list, second_list, src, dst): + if src not in ['LEFT', 'RIGHT']: + raise SimpleError(SYNTAX_ERROR_MSG) + if dst not in ['LEFT', 'RIGHT']: + raise SimpleError(SYNTAX_ERROR_MSG) + el = self.rpop(src) if src == 'RIGHT' else self.lpop(src) + self.lpush(dst, el) if dst == 'LEFT' else self.rpush(dst, el) + return el + def _list_pop(self, get_slice, key, *args): """Implements lpop and rpop. From ff6967c5f9f538d8c1f3dabdd0ee092d2de6429a Mon Sep 17 00:00:00 2001 From: Benedikt Groever Date: Sun, 22 May 2022 22:57:49 -0700 Subject: [PATCH 2/2] add tests for lmove operation --- fakeredis/_server.py | 14 +++++++---- test/test_fakeredis.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/fakeredis/_server.py b/fakeredis/_server.py index 3f0a812c..ec029b54 100644 --- a/fakeredis/_server.py +++ b/fakeredis/_server.py @@ -95,6 +95,10 @@ def __init__(self, value): assert isinstance(value, bytes) self.value = value + @classmethod + def decode(cls, value): + return value + class SimpleError(Exception): """Exception that will be turned into a frontend-specific exception.""" @@ -1779,14 +1783,14 @@ def linsert(self, key, where, pivot, value): def llen(self, key): return len(key.value) - @command((Key(list, None), Key(list))) + @command((Key(list, None), Key(list), SimpleString, SimpleString)) def lmove(self, first_list, second_list, src, dst): - if src not in ['LEFT', 'RIGHT']: + if src not in [b'LEFT', b'RIGHT']: raise SimpleError(SYNTAX_ERROR_MSG) - if dst not in ['LEFT', 'RIGHT']: + if dst not in [b'LEFT', b'RIGHT']: raise SimpleError(SYNTAX_ERROR_MSG) - el = self.rpop(src) if src == 'RIGHT' else self.lpop(src) - self.lpush(dst, el) if dst == 'LEFT' else self.rpush(dst, el) + el = self.rpop(first_list) if src == b'RIGHT' else self.lpop(first_list) + self.lpush(second_list, el) if dst == b'LEFT' else self.rpush(second_list, el) return el def _list_pop(self, get_slice, key, *args): diff --git a/test/test_fakeredis.py b/test/test_fakeredis.py index bf7e86c9..58d26b07 100644 --- a/test/test_fakeredis.py +++ b/test/test_fakeredis.py @@ -1256,6 +1256,57 @@ def test_linsert_wrong_type(r): with pytest.raises(redis.ResponseError): r.linsert('foo', 'after', 'bar', 'element') +def test_lmove(r): + assert r.lmove('foo', 'bar', 'RIGHT', 'LEFT') is None + assert r.lpop('bar') is None + r.rpush('foo', 'one') + r.rpush('foo', 'two') + r.rpush('bar', 'one') + + # RPOPLPUSH + assert r.lmove('foo', 'bar', 'RIGHT', 'LEFT') == b'two' + assert r.lrange('foo', 0, -1) == [b'one'] + assert r.lrange('bar', 0, -1) == [b'two', b'one'] + # LPOPRPUSH + assert r.lmove('bar', 'bar', 'LEFT', 'RIGHT') == b'two' + assert r.lrange('bar', 0, -1) == [b'one', b'two'] + # RPOPRPUSH + r.rpush('foo', 'three') + assert r.lmove('foo', 'bar', 'RIGHT', 'RIGHT') == b'three' + assert r.lrange('foo', 0, -1) == [b'one'] + assert r.lrange('bar', 0, -1) == [b'one', b'two', b'three'] + # LPOPLPUSH + assert r.lmove('bar', 'foo', 'LEFT', 'LEFT') == b'one' + assert r.lrange('foo', 0, -1) == [b'one', b'one'] + assert r.lrange('bar', 0, -1) == [b'two', b'three'] + + # Catch instances where we store bytes and strings inconsistently + # and thus bar = ['two', b'one'] + assert r.lrem('bar', -1, 'two') == 1 + +def test_lmove_to_nonexistent_destination(r): + r.rpush('foo', 'one') + assert r.lmove('foo', 'bar', 'RIGHT', 'LEFT') == b'one' + assert r.rpop('bar') == b'one' + +def test_lmove_expiry(r): + r.rpush('foo', 'one') + r.rpush('bar', 'two') + r.expire('bar', 10) + r.lmove('foo', 'bar', 'RIGHT', 'LEFT') + assert r.ttl('bar') > 0 + +def test_lmove_wrong_type(r): + r.set('foo', 'bar') + r.rpush('list', 'element') + with pytest.raises(redis.ResponseError): + r.lmove('foo', 'list', 'RIGHT', 'LEFT') + assert r.get('foo') == b'bar' + assert r.lrange('list', 0, -1) == [b'element'] + with pytest.raises(redis.ResponseError): + r.lmove('list', 'foo', 'RIGHT', 'LEFT') + assert r.get('foo') == b'bar' + assert r.lrange('list', 0, -1) == [b'element'] def test_rpoplpush(r): assert r.rpoplpush('foo', 'bar') is None @@ -5273,6 +5324,10 @@ def test_llen(self, r): with pytest.raises(redis.ConnectionError): r.llen('name') + def test_lmove(self, r): + with pytest.raises(redis.ConnectionError): + r.lmove(1, 2, 'LEFT', 'RIGHT') + def test_lrem(self, r): with pytest.raises(redis.ConnectionError): r.lrem('name', 2, 2)