mock_redis 0.19.0 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/.travis.yml +9 -10
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -2
- data/README.md +2 -2
- data/lib/mock_redis/database.rb +6 -5
- data/lib/mock_redis/geospatial_methods.rb +10 -18
- data/lib/mock_redis/hash_methods.rb +4 -4
- data/lib/mock_redis/indifferent_hash.rb +0 -8
- data/lib/mock_redis/list_methods.rb +2 -2
- data/lib/mock_redis/pipelined_wrapper.rb +25 -6
- data/lib/mock_redis/set_methods.rb +15 -4
- data/lib/mock_redis/stream.rb +62 -0
- data/lib/mock_redis/stream/id.rb +53 -0
- data/lib/mock_redis/stream_methods.rb +87 -0
- data/lib/mock_redis/string_methods.rb +5 -8
- data/lib/mock_redis/transaction_wrapper.rb +25 -12
- data/lib/mock_redis/utility_methods.rb +1 -1
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset_methods.rb +2 -2
- data/mock_redis.gemspec +6 -5
- data/spec/commands/geodist_spec.rb +8 -4
- data/spec/commands/geohash_spec.rb +4 -4
- data/spec/commands/geopos_spec.rb +4 -4
- data/spec/commands/get_spec.rb +1 -0
- data/spec/commands/hdel_spec.rb +2 -2
- data/spec/commands/mget_spec.rb +34 -15
- data/spec/commands/mset_spec.rb +14 -0
- data/spec/commands/pipelined_spec.rb +52 -0
- data/spec/commands/spop_spec.rb +15 -0
- data/spec/commands/watch_spec.rb +8 -3
- data/spec/commands/xadd_spec.rb +102 -0
- data/spec/commands/xlen_spec.rb +20 -0
- data/spec/commands/xrange_spec.rb +141 -0
- data/spec/commands/xrevrange_spec.rb +130 -0
- data/spec/commands/xtrim_spec.rb +30 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/redis_multiplexer.rb +17 -1
- data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_lists.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_sets.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_zsets.rb +2 -0
- data/spec/transactions_spec.rb +17 -29
- metadata +38 -12
- data/spec/commands/hash_operator_spec.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e3dccfea232f8d47d28dc16362a79232cd69a2a12e6d8127a43c83c03287ef4
|
4
|
+
data.tar.gz: a2918811810246e6ddf92643e88121550310a7628f396aba12ed163b61c979a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8233e6e66afbaa2f7801e33f7b16a7bbbb2eb4cb3caab73a0c19e94857542fe49a96a9c45e8e995b54bf71d50c90ef78cfb5e293e85d91a3ade26f0e3a3e4d8
|
7
|
+
data.tar.gz: e30d5a2656ef3fb793cb9a0a2df9e1e24cd78aaa83a6cf5a3483353075f2a475379e294fc6b9c5905229db1a453d16cc7fa02095aed3ad59799312ff5d76ced5
|
data/.rubocop.yml
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
2
2
|
|
3
3
|
AllCops:
|
4
|
-
TargetRubyVersion: 2.
|
4
|
+
TargetRubyVersion: 2.4
|
5
|
+
|
6
|
+
Layout/AlignArguments:
|
7
|
+
Enabled: false
|
5
8
|
|
6
9
|
Layout/AlignParameters:
|
7
10
|
Enabled: false
|
@@ -9,6 +12,9 @@ Layout/AlignParameters:
|
|
9
12
|
Layout/DotPosition:
|
10
13
|
Enabled: false
|
11
14
|
|
15
|
+
Layout/EmptyLineAfterGuardClause:
|
16
|
+
Enabled: false
|
17
|
+
|
12
18
|
Lint/AssignmentInCondition:
|
13
19
|
Enabled: false
|
14
20
|
|
data/.travis.yml
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
|
-
sudo: false
|
4
|
-
|
5
3
|
cache: bundler
|
6
4
|
|
7
5
|
addons:
|
@@ -12,11 +10,16 @@ addons:
|
|
12
10
|
services:
|
13
11
|
- redis-server
|
14
12
|
|
13
|
+
before_install:
|
14
|
+
- sudo sed -e 's/^bind.*/bind 127.0.0.1/' /etc/redis/redis.conf > redis.conf
|
15
|
+
- sudo mv redis.conf /etc/redis
|
16
|
+
- sudo service redis-server start
|
17
|
+
- echo PING | nc localhost 6379
|
18
|
+
|
15
19
|
rvm:
|
16
|
-
- 2.
|
17
|
-
- 2.
|
18
|
-
- 2.
|
19
|
-
- 2.5.1
|
20
|
+
- 2.4.6
|
21
|
+
- 2.5.5
|
22
|
+
- 2.6.3
|
20
23
|
|
21
24
|
before_script:
|
22
25
|
- git config --local user.email "travis@travis.ci"
|
@@ -27,7 +30,3 @@ script:
|
|
27
30
|
- bundle exec rspec
|
28
31
|
- bundle exec overcommit --sign
|
29
32
|
- bundle exec overcommit --run
|
30
|
-
|
31
|
-
matrix:
|
32
|
-
allow_failures:
|
33
|
-
- rvm: 2.1.0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# MockRedis Changelog
|
2
2
|
|
3
|
+
### 0.20.0
|
4
|
+
|
5
|
+
* Add support for `count` parameter of `spop`
|
6
|
+
* Fix `mget` and `mset` to accept array as parameters
|
7
|
+
* Fix pipelined array replies
|
8
|
+
* Fix nested pipelining
|
9
|
+
* Allow nested multi
|
10
|
+
* Require Redis gem 4.0.1 or newer
|
11
|
+
* Add support for stream commands on Redis 5
|
12
|
+
* Keep empty strings on type mismatch
|
13
|
+
* Improve performance of `set_expiration`
|
14
|
+
* Fix `watch` to allow multiple keys
|
15
|
+
* Add `unlink` alias for `del`
|
16
|
+
* Drop support for Ruby 2.3 or older
|
17
|
+
|
3
18
|
### 0.19.0
|
4
19
|
|
5
20
|
* Require Ruby 2.2+
|
data/Gemfile
CHANGED
@@ -4,9 +4,9 @@ source 'http://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
# Run all pre-commit hooks via Overcommit during CI runs
|
7
|
-
gem 'overcommit', '0.
|
7
|
+
gem 'overcommit', '0.48.0'
|
8
8
|
|
9
9
|
# Pin tool versions (which are executed by Overcommit) for Travis builds
|
10
|
-
gem 'rubocop', '0.
|
10
|
+
gem 'rubocop', '0.68.1'
|
11
11
|
|
12
12
|
gem 'coveralls', require: false
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# MockRedis
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/mock_redis.svg)](http://badge.fury.io/rb/mock_redis)
|
4
|
-
[![Build Status](https://travis-ci.org/
|
5
|
-
[![Coverage Status](https://coveralls.io/repos/
|
4
|
+
[![Build Status](https://travis-ci.org/sds/mock_redis.svg)](https://travis-ci.org/sds/mock_redis)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/sds/mock_redis/badge.svg)](https://coveralls.io/r/sds/mock_redis)
|
6
6
|
|
7
7
|
MockRedis provides the same interface as `redis-rb`, but it stores its
|
8
8
|
data in memory instead of talking to a Redis server. It is intended
|
data/lib/mock_redis/database.rb
CHANGED
@@ -10,6 +10,7 @@ require 'mock_redis/indifferent_hash'
|
|
10
10
|
require 'mock_redis/info_method'
|
11
11
|
require 'mock_redis/utility_methods'
|
12
12
|
require 'mock_redis/geospatial_methods'
|
13
|
+
require 'mock_redis/stream_methods'
|
13
14
|
|
14
15
|
class MockRedis
|
15
16
|
class Database
|
@@ -22,6 +23,7 @@ class MockRedis
|
|
22
23
|
include InfoMethod
|
23
24
|
include UtilityMethods
|
24
25
|
include GeospatialMethods
|
26
|
+
include StreamMethods
|
25
27
|
|
26
28
|
attr_reader :data, :expire_times
|
27
29
|
|
@@ -73,6 +75,7 @@ class MockRedis
|
|
73
75
|
each { |k| data.delete(k) }.
|
74
76
|
length
|
75
77
|
end
|
78
|
+
alias unlink del
|
76
79
|
|
77
80
|
def echo(msg)
|
78
81
|
msg.to_s
|
@@ -303,11 +306,9 @@ class MockRedis
|
|
303
306
|
|
304
307
|
def set_expiration(key, time)
|
305
308
|
remove_expiration(key)
|
306
|
-
|
307
|
-
|
308
|
-
expire_times.
|
309
|
-
a.first <=> b.first
|
310
|
-
end
|
309
|
+
found = expire_times.each_with_index.to_a.bsearch { |item, _| item.first >= time }
|
310
|
+
index = found ? found.last : -1
|
311
|
+
expire_times.insert(index, [time, key.to_s])
|
311
312
|
end
|
312
313
|
|
313
314
|
def zero_pad(string, desired_length)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class MockRedis
|
2
2
|
module GeospatialMethods
|
3
|
-
LNG_RANGE = (-180..180)
|
4
|
-
LAT_RANGE = (-85.05112878..85.05112878)
|
3
|
+
LNG_RANGE = (-180..180).freeze
|
4
|
+
LAT_RANGE = (-85.05112878..85.05112878).freeze
|
5
5
|
STEP = 26
|
6
6
|
UNITS = {
|
7
7
|
m: 1,
|
@@ -23,21 +23,13 @@ class MockRedis
|
|
23
23
|
zadd(key, scored_points)
|
24
24
|
end
|
25
25
|
|
26
|
-
def geodist(key,
|
27
|
-
|
28
|
-
raise Redis::CommandError,
|
29
|
-
"ERR wrong number of arguments for 'geodist' command"
|
30
|
-
end
|
31
|
-
|
32
|
-
raise Redis::CommandError, 'ERR syntax error' if args.length > 3
|
33
|
-
|
34
|
-
to_meter = 1
|
35
|
-
to_meter = parse_unit(args[2]) if args.length == 3
|
26
|
+
def geodist(key, member1, member2, unit = 'm')
|
27
|
+
to_meter = parse_unit(unit)
|
36
28
|
|
37
29
|
return nil if zcard(key).zero?
|
38
30
|
|
39
|
-
score1 = zscore(key,
|
40
|
-
score2 = zscore(key,
|
31
|
+
score1 = zscore(key, member1)
|
32
|
+
score2 = zscore(key, member2)
|
41
33
|
return nil if score1.nil? || score2.nil?
|
42
34
|
hash1 = { bits: score1.to_i, step: STEP }
|
43
35
|
hash2 = { bits: score2.to_i, step: STEP }
|
@@ -49,12 +41,12 @@ class MockRedis
|
|
49
41
|
format('%.4f', distance)
|
50
42
|
end
|
51
43
|
|
52
|
-
def geohash(key,
|
44
|
+
def geohash(key, members)
|
53
45
|
lng_range = (-180..180)
|
54
46
|
lat_range = (-90..90)
|
55
47
|
geoalphabet = '0123456789bcdefghjkmnpqrstuvwxyz'
|
56
48
|
|
57
|
-
members.map do |member|
|
49
|
+
Array(members).map do |member|
|
58
50
|
score = zscore(key, member)
|
59
51
|
next nil unless score
|
60
52
|
score = score.to_i
|
@@ -71,8 +63,8 @@ class MockRedis
|
|
71
63
|
end
|
72
64
|
end
|
73
65
|
|
74
|
-
def geopos(key,
|
75
|
-
members.map do |member|
|
66
|
+
def geopos(key, members)
|
67
|
+
Array(members).map do |member|
|
76
68
|
score = zscore(key, member)
|
77
69
|
next nil unless score
|
78
70
|
hash = { bits: score.to_i, step: STEP }
|
@@ -6,15 +6,15 @@ class MockRedis
|
|
6
6
|
include Assertions
|
7
7
|
include UtilityMethods
|
8
8
|
|
9
|
-
def hdel(key,
|
9
|
+
def hdel(key, *fields)
|
10
10
|
with_hash_at(key) do |hash|
|
11
|
-
if
|
11
|
+
if fields.is_a?(Array)
|
12
12
|
orig_size = hash.size
|
13
|
-
fields =
|
13
|
+
fields = fields.map(&:to_s)
|
14
14
|
hash.delete_if { |k, _v| fields.include?(k) }
|
15
15
|
orig_size - hash.size
|
16
16
|
else
|
17
|
-
hash.delete(
|
17
|
+
hash.delete(fields[0].to_s) ? 1 : 0
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -146,13 +146,13 @@ class MockRedis
|
|
146
146
|
|
147
147
|
def ltrim(key, start, stop)
|
148
148
|
with_list_at(key) do |list|
|
149
|
-
list
|
149
|
+
list&.replace(list[[start.to_i, -list.length].max..stop.to_i] || [])
|
150
150
|
'OK'
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
154
154
|
def rpop(key)
|
155
|
-
with_list_at(key) { |list| list
|
155
|
+
with_list_at(key) { |list| list&.pop }
|
156
156
|
end
|
157
157
|
|
158
158
|
def rpoplpush(source, destination)
|
@@ -9,7 +9,7 @@ class MockRedis
|
|
9
9
|
def initialize(db)
|
10
10
|
@db = db
|
11
11
|
@pipelined_futures = []
|
12
|
-
@
|
12
|
+
@nesting_level = 0
|
13
13
|
end
|
14
14
|
|
15
15
|
def initialize_copy(source)
|
@@ -19,7 +19,7 @@ class MockRedis
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def method_missing(method, *args, &block)
|
22
|
-
if
|
22
|
+
if in_pipeline?
|
23
23
|
future = MockRedis::Future.new([method, *args], block)
|
24
24
|
@pipelined_futures << future
|
25
25
|
future
|
@@ -29,9 +29,17 @@ class MockRedis
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def pipelined(_options = {})
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
begin
|
33
|
+
@nesting_level += 1
|
34
|
+
yield self
|
35
|
+
ensure
|
36
|
+
@nesting_level -= 1
|
37
|
+
end
|
38
|
+
|
39
|
+
if in_pipeline?
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
35
43
|
responses = @pipelined_futures.flat_map do |future|
|
36
44
|
begin
|
37
45
|
result = if future.block
|
@@ -40,7 +48,12 @@ class MockRedis
|
|
40
48
|
send(*future.command)
|
41
49
|
end
|
42
50
|
future.store_result(result)
|
43
|
-
|
51
|
+
|
52
|
+
if future.block
|
53
|
+
result
|
54
|
+
else
|
55
|
+
[result]
|
56
|
+
end
|
44
57
|
rescue StandardError => e
|
45
58
|
e
|
46
59
|
end
|
@@ -48,5 +61,11 @@ class MockRedis
|
|
48
61
|
@pipelined_futures = []
|
49
62
|
responses
|
50
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def in_pipeline?
|
68
|
+
@nesting_level > 0
|
69
|
+
end
|
51
70
|
end
|
52
71
|
end
|
@@ -81,11 +81,22 @@ class MockRedis
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
def spop(key)
|
84
|
+
def spop(key, count = nil)
|
85
85
|
with_set_at(key) do |set|
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
if count.nil?
|
87
|
+
member = set.first
|
88
|
+
set.delete(member)
|
89
|
+
member
|
90
|
+
else
|
91
|
+
members = []
|
92
|
+
count.times do
|
93
|
+
member = set.first
|
94
|
+
break if member.nil?
|
95
|
+
set.delete(member)
|
96
|
+
members << member
|
97
|
+
end
|
98
|
+
members
|
99
|
+
end
|
89
100
|
end
|
90
101
|
end
|
91
102
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'set'
|
3
|
+
require 'date'
|
4
|
+
require 'mock_redis/stream/id'
|
5
|
+
|
6
|
+
class MockRedis
|
7
|
+
class Stream
|
8
|
+
include Enumerable
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
attr_accessor :members
|
12
|
+
|
13
|
+
def_delegators :members, :empty?
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@members = Set.new
|
17
|
+
@last_id = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def last_id
|
21
|
+
@last_id.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(id, values)
|
25
|
+
@last_id = MockRedis::Stream::Id.new(id, min: @last_id)
|
26
|
+
members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]]
|
27
|
+
@last_id.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def trim(count)
|
31
|
+
deleted = @members.size - count
|
32
|
+
@members = @members.to_a[-count..-1].to_set
|
33
|
+
deleted
|
34
|
+
end
|
35
|
+
|
36
|
+
def range(start, finish, reversed, *opts_in)
|
37
|
+
opts = options opts_in, ['count']
|
38
|
+
start_id = MockRedis::Stream::Id.new(start)
|
39
|
+
finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY)
|
40
|
+
items = members
|
41
|
+
.select { |m| (start_id <= m[0]) && (finish_id >= m[0]) }
|
42
|
+
.map { |m| [m[0].to_s, m[1]] }
|
43
|
+
items.reverse! if reversed
|
44
|
+
return items.first(opts['count'].to_i) if opts.key?('count')
|
45
|
+
items
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
members.each { |m| yield m }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def options(opts_in, permitted)
|
55
|
+
opts_out = {}
|
56
|
+
raise Redis::CommandError, 'ERR syntax error' unless (opts_in.length % 2).zero?
|
57
|
+
opts_in.each_slice(2).map { |pair| opts_out[pair[0].downcase] = pair[1] }
|
58
|
+
raise Redis::CommandError, 'ERR syntax error' unless (opts_out.keys - permitted).empty?
|
59
|
+
opts_out
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class MockRedis
|
2
|
+
class Stream
|
3
|
+
class Id
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_accessor :timestamp, :sequence
|
7
|
+
|
8
|
+
def initialize(id, min: nil, sequence: 0)
|
9
|
+
case id
|
10
|
+
when '*'
|
11
|
+
@timestamp = (Time.now.to_f * 1000).to_i
|
12
|
+
@sequence = 0
|
13
|
+
if self <= min
|
14
|
+
@timestamp = min.timestamp
|
15
|
+
@sequence = min.sequence + 1
|
16
|
+
end
|
17
|
+
when '-'
|
18
|
+
@timestamp = @sequence = 0
|
19
|
+
when '+'
|
20
|
+
@timestamp = @sequence = Float::INFINITY
|
21
|
+
else
|
22
|
+
if id.is_a? String
|
23
|
+
(_, @timestamp, @sequence) = id.match(/^(\d+)-?(\d+)?$/)
|
24
|
+
.to_a
|
25
|
+
if @timestamp.nil?
|
26
|
+
raise Redis::CommandError,
|
27
|
+
'ERR Invalid stream ID specified as stream command argument'
|
28
|
+
end
|
29
|
+
@timestamp = @timestamp.to_i
|
30
|
+
else
|
31
|
+
@timestamp = id
|
32
|
+
end
|
33
|
+
@sequence = @sequence.nil? ? sequence : @sequence.to_i
|
34
|
+
if (@timestamp == 0 && @sequence == 0) || self <= min
|
35
|
+
raise Redis::CommandError,
|
36
|
+
'ERR The ID specified in XADD is equal or smaller than ' \
|
37
|
+
'the target stream top item'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"#{@timestamp}-#{@sequence}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def <=>(other)
|
47
|
+
return 1 if other.nil?
|
48
|
+
return @sequence <=> other.sequence if @timestamp == other.timestamp
|
49
|
+
@timestamp <=> other.timestamp
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|