Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test FakeRedis against real Redis #900

Merged
merged 10 commits into from
Mar 4, 2015
54 changes: 39 additions & 15 deletions vumi/persist/fake_redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ def call_to_deferred(deferred, func, *args, **kw):
execute(func, *args, **kw).chainDeferred(deferred)


class ResponseError(Exception):
"""
Exception class for things we throw to match the real Redis client
libraries.
"""


class FakeRedis(object):
"""In process and memory implementation of redis-like data store.

Expand Down Expand Up @@ -171,7 +178,7 @@ def scan(self, cursor, match=None, count=None):
else:
cursor = str(start + i + 1)

return cursor, fnmatch.filter(output, match)
return [cursor, fnmatch.filter(output, match)]

@maybe_async
def flushdb(self):
Expand All @@ -188,6 +195,7 @@ def get(self, key):
def set(self, key, value):
value = self._encode(value) # set() sets string value
self._set_key(key, value)
return True

@maybe_async
def setex(self, key, time, value):
Expand Down Expand Up @@ -272,8 +280,8 @@ def hmset(self, key, mapping):

@maybe_async
def hgetall(self, key):
return dict((self._encode(k), self._encode(v)) for k, v in
self._data.get(key, {}).items())
return dict((self._encode(k), self._encode(v))
for k, v in self._data.get(key, {}).items())

@maybe_async
def hlen(self, key):
Expand All @@ -285,9 +293,16 @@ def hvals(self, key):

@maybe_async
def hincrby(self, key, field, amount=1):
value = self._data.get(key, {}).get(field, "0")
try:
value = self._data.get(key, {}).get(field, "0")
except AttributeError:
raise ResponseError("WRONGTYPE Operation against a key holding"
" the wrong kind of value")
# the int(str(..)) coerces amount to an int but rejects floats
value = int(value) + int(str(amount))
try:
value = int(value) + int(str(amount))
except (TypeError, ValueError):
raise ResponseError("value is not an integer or out of range")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests for these cases:

  • using "0" as the default field
  • hincrby against a key holding the wrong kind of value
  • hincrby with a non-integer or out of range value

If not, could we add some?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theses cases are all covered in test_hincrby.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, missed those.

self._setdefault_key(key, {})[field] = str(value)
return value

Expand Down Expand Up @@ -376,18 +391,18 @@ def zrange(self, key, start, stop, desc=False, withscores=False,

@maybe_async
def zrangebyscore(self, key, min='-inf', max='+inf', start=0, num=None,
withscores=False, score_cast_func=float):
withscores=False, score_cast_func=float):
zval = self._data.get(key, Zset())
results = zval.zrangebyscore(min, max, start, num,
score_cast_func=score_cast_func)
results = zval.zrangebyscore(
min, max, start, num, score_cast_func=score_cast_func)
if withscores:
return results
else:
return [v for v, k in results]

@maybe_async
def zcount(self, key, min, max):
return str(len(self.zrangebyscore.sync(self, key, min, max)))
return len(self.zrangebyscore.sync(self, key, min, max))

@maybe_async
def zscore(self, key, value):
Expand Down Expand Up @@ -416,12 +431,13 @@ def rpop(self, key):

@maybe_async
def lpush(self, key, obj):
self._setdefault_key(key, []).insert(0, obj)
self._setdefault_key(key, []).insert(0, self._encode(obj))
return self.llen.sync(self, key)

@maybe_async
def rpush(self, key, obj):
self._setdefault_key(key, []).append(obj)
return self.llen.sync(self, key) - 1
self._setdefault_key(key, []).append(self._encode(obj))
return self.llen.sync(self, key)

@maybe_async
def lrange(self, key, start, end):
Expand All @@ -435,6 +451,7 @@ def lrange(self, key, start, end):
@maybe_async
def lrem(self, key, value, num=0):
removed = [0]
value = self._encode(value)

def keep(v):
if v == value and (num == 0 or removed[0] < abs(num)):
Expand Down Expand Up @@ -468,6 +485,7 @@ def ltrim(self, key, start, stop):
# want to keep.
del lval[stop + 1:]
del lval[:start]
return True

# Expiry operations

Expand All @@ -484,7 +502,7 @@ def expire(self, key, seconds):
def ttl(self, key):
delayed = self._expiries.get(key)
if delayed is not None and delayed.active():
return int(delayed.getTime() - self.clock.seconds())
return round(delayed.getTime() - self.clock.seconds())
return None

@maybe_async
Expand All @@ -508,10 +526,16 @@ def _redis_range_to_py_range(self, start, end):
end = None
return start, end

def _to_float(self, value):
try:
return float(value)
except (ValueError, TypeError):
raise ResponseError("value is not a valid float")

def zadd(self, **valscores):
new_zval = [val for val in self._zval if val[1] not in valscores]
new_zval.extend((float(score), value) for value, score
in valscores.items())
new_zval.extend((self._to_float(score), value)
for value, score in valscores.items())
new_zval.sort()
added = len(new_zval) - len(self._zval)
self._zval = new_zval
Expand Down
9 changes: 5 additions & 4 deletions vumi/persist/redis_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,9 @@ def _unkeys_scan(self, scan_results):
zcard = RedisCall(['key'])
zrange = RedisCall(['key', 'start', 'stop', 'desc', 'withscores'],
defaults=[False, False])
zrangebyscore = RedisCall(['key', 'min', 'max', 'start', 'num',
'withscores'], defaults=['-inf', '+inf', None, None, False])
zrangebyscore = RedisCall(
['key', 'min', 'max', 'start', 'num', 'withscores'],
defaults=['-inf', '+inf', None, None, False])
zscore = RedisCall(['key', 'value'])
zcount = RedisCall(['key', 'min', 'max'])
zremrangebyrank = RedisCall(['key', 'start', 'stop'])
Expand All @@ -275,8 +276,8 @@ def _unkeys_scan(self, scan_results):
rpush = RedisCall(['key', 'obj'])
lrange = RedisCall(['key', 'start', 'end'])
lrem = RedisCall(['key', 'value', 'num'], defaults=[0])
rpoplpush = RedisCall(['source'], vararg='destination',
key_args=['source', 'destination'])
rpoplpush = RedisCall(
['source'], vararg='destination', key_args=['source', 'destination'])
ltrim = RedisCall(['key', 'start', 'stop'])

# Expiry operations
Expand Down
Loading