mock_redis 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](http://badge.fury.io/rb/mock_redis)
|
4
|
-
[](https://travis-ci.org/sds/mock_redis)
|
5
|
+
[](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
|