mock_redis 0.18.0 → 0.19.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 +24 -9
- data/.rubocop_todo.yml +35 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +7 -0
- data/Gemfile +2 -2
- data/README.md +2 -3
- data/lib/mock_redis/database.rb +2 -0
- data/lib/mock_redis/future.rb +3 -2
- data/lib/mock_redis/geospatial_methods.rb +248 -0
- data/lib/mock_redis/pipelined_wrapper.rb +8 -4
- data/lib/mock_redis/string_methods.rb +109 -0
- data/lib/mock_redis/transaction_wrapper.rb +1 -1
- data/lib/mock_redis/utility_methods.rb +31 -0
- data/lib/mock_redis/version.rb +1 -1
- data/mock_redis.gemspec +5 -3
- data/spec/commands/bitfield_spec.rb +169 -0
- data/spec/commands/geoadd_spec.rb +58 -0
- data/spec/commands/geodist_spec.rb +114 -0
- data/spec/commands/geohash_spec.rb +52 -0
- data/spec/commands/geopos_spec.rb +55 -0
- data/spec/commands/setbit_spec.rb +0 -1
- data/spec/commands/strlen_spec.rb +0 -1
- data/spec/transactions_spec.rb +12 -0
- metadata +15 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da2c2693b445e51b487e52f7c9b3986960417686792f9a5e554ca4d215c323e2
|
4
|
+
data.tar.gz: 8da2170fcc49e768133a61deb716d72cc18d66291567008d96287b8d2104b15c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15b8b59cff1120de0befaee6cf08facfb120e239d3fbc1b6b55f40bb60c526f57e5cbb61f1d9f6fd5538067122e49b80e9e782590cd98070a3d49943ec8bb929
|
7
|
+
data.tar.gz: 52a09f0896a405137be0075db930bec09898dd96e3d5366a95ef6a189268348f91244c051dbe45db5b4b37023a12f87552868fcaf2a5383316bebe9bdaf44997
|
data/.rubocop.yml
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.2
|
5
|
+
|
6
|
+
Layout/AlignParameters:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Layout/DotPosition:
|
10
|
+
Enabled: false
|
11
|
+
|
1
12
|
Lint/AssignmentInCondition:
|
2
13
|
Enabled: false
|
3
14
|
|
@@ -30,16 +41,18 @@ Metrics/ModuleLength:
|
|
30
41
|
Metrics/PerceivedComplexity:
|
31
42
|
Enabled: false
|
32
43
|
|
33
|
-
|
44
|
+
# This hides the has-a versus is-a relationship indicated by the method name
|
45
|
+
Naming/PredicateName:
|
34
46
|
Enabled: false
|
35
47
|
|
36
48
|
Style/Documentation:
|
37
49
|
Enabled: false
|
38
50
|
|
39
|
-
Style/
|
51
|
+
Style/DoubleNegation:
|
40
52
|
Enabled: false
|
41
53
|
|
42
|
-
|
54
|
+
# We have too much code that relies on modifying strings
|
55
|
+
Style/FrozenStringLiteralComment:
|
43
56
|
Enabled: false
|
44
57
|
|
45
58
|
Style/GuardClause:
|
@@ -55,7 +68,10 @@ Style/Lambda:
|
|
55
68
|
Enabled: false
|
56
69
|
|
57
70
|
# TODO: Address these at some point
|
58
|
-
Style/
|
71
|
+
Style/MethodMissingSuper:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
Style/MissingRespondToMissing:
|
59
75
|
Enabled: false
|
60
76
|
|
61
77
|
Style/MultilineBlockChain:
|
@@ -80,10 +96,6 @@ Style/PercentLiteralDelimiters:
|
|
80
96
|
Style/PerlBackrefs:
|
81
97
|
Enabled: false
|
82
98
|
|
83
|
-
# This hides the has-a versus is-a relationship indicated by the method name
|
84
|
-
Style/PredicateName:
|
85
|
-
Enabled: false
|
86
|
-
|
87
99
|
Style/RescueModifier:
|
88
100
|
Enabled: false
|
89
101
|
|
@@ -99,7 +111,10 @@ Style/SymbolArray:
|
|
99
111
|
Style/TrailingCommaInArguments:
|
100
112
|
Enabled: false
|
101
113
|
|
102
|
-
Style/
|
114
|
+
Style/TrailingCommaInArrayLiteral:
|
115
|
+
Enabled: false
|
116
|
+
|
117
|
+
Style/TrailingCommaInHashLiteral:
|
103
118
|
Enabled: false
|
104
119
|
|
105
120
|
Style/WhenThen:
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2018-08-03 11:15:53 -0700 using RuboCop version 0.58.2.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 17
|
10
|
+
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
11
|
+
# AllowedNames: io, id, to, by, on, in, at, ip
|
12
|
+
Naming/UncommunicativeMethodParamName:
|
13
|
+
Exclude:
|
14
|
+
- 'lib/mock_redis/database.rb'
|
15
|
+
- 'lib/mock_redis/expire_wrapper.rb'
|
16
|
+
- 'lib/mock_redis/geospatial_methods.rb'
|
17
|
+
- 'lib/mock_redis/multi_db_wrapper.rb'
|
18
|
+
- 'lib/mock_redis/pipelined_wrapper.rb'
|
19
|
+
- 'lib/mock_redis/string_methods.rb'
|
20
|
+
- 'lib/mock_redis/transaction_wrapper.rb'
|
21
|
+
- 'lib/mock_redis/utility_methods.rb'
|
22
|
+
- 'lib/mock_redis/zset_methods.rb'
|
23
|
+
- 'spec/support/redis_multiplexer.rb'
|
24
|
+
|
25
|
+
# Offense count: 2
|
26
|
+
# Configuration parameters: EnforcedStyle.
|
27
|
+
# SupportedStyles: inline, group
|
28
|
+
Style/AccessModifierDeclarations:
|
29
|
+
Exclude:
|
30
|
+
- 'lib/mock_redis/zset_methods.rb'
|
31
|
+
|
32
|
+
# Offense count: 1
|
33
|
+
Style/DateTime:
|
34
|
+
Exclude:
|
35
|
+
- 'spec/commands/zremrangebyscore_spec.rb'
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# MockRedis Changelog
|
2
2
|
|
3
|
+
### 0.19.0
|
4
|
+
|
5
|
+
* Require Ruby 2.2+
|
6
|
+
* Add support for `bitfield` command
|
7
|
+
* Add support for `geoadd`, `geopos`, `geohash`, and `geodist` commands
|
8
|
+
* Fix multi-nested pipeline not releasing lock issue
|
9
|
+
|
3
10
|
### 0.18.0
|
4
11
|
|
5
12
|
* Fix `hset` return value to return false when the field exists in the hash
|
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.45.0'
|
8
8
|
|
9
9
|
# Pin tool versions (which are executed by Overcommit) for Travis builds
|
10
|
-
gem 'rubocop', '0.
|
10
|
+
gem 'rubocop', '0.58.2'
|
11
11
|
|
12
12
|
gem 'coveralls', require: false
|
data/README.md
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
[](http://badge.fury.io/rb/mock_redis)
|
4
4
|
[](https://travis-ci.org/brigade/mock_redis)
|
5
5
|
[](https://coveralls.io/r/brigade/mock_redis)
|
6
|
-
[](https://gemnasium.com/brigade/mock_redis)
|
7
6
|
|
8
7
|
MockRedis provides the same interface as `redis-rb`, but it stores its
|
9
8
|
data in memory instead of talking to a Redis server. It is intended
|
@@ -93,8 +92,8 @@ please submit a pull request with your (tested!) implementation.
|
|
93
92
|
|
94
93
|
## Compatibility
|
95
94
|
|
96
|
-
As of version `0.
|
97
|
-
older versions of Ruby, use `0.
|
95
|
+
As of version `0.19.0`, Ruby 2.2 and above are supported. For
|
96
|
+
older versions of Ruby, use `0.18.0` or older.
|
98
97
|
|
99
98
|
## Running the Tests
|
100
99
|
|
data/lib/mock_redis/database.rb
CHANGED
@@ -9,6 +9,7 @@ require 'mock_redis/sort_method'
|
|
9
9
|
require 'mock_redis/indifferent_hash'
|
10
10
|
require 'mock_redis/info_method'
|
11
11
|
require 'mock_redis/utility_methods'
|
12
|
+
require 'mock_redis/geospatial_methods'
|
12
13
|
|
13
14
|
class MockRedis
|
14
15
|
class Database
|
@@ -20,6 +21,7 @@ class MockRedis
|
|
20
21
|
include SortMethod
|
21
22
|
include InfoMethod
|
22
23
|
include UtilityMethods
|
24
|
+
include GeospatialMethods
|
23
25
|
|
24
26
|
attr_reader :data, :expire_times
|
25
27
|
|
data/lib/mock_redis/future.rb
CHANGED
@@ -2,10 +2,11 @@ class MockRedis
|
|
2
2
|
class FutureNotReady < RuntimeError; end
|
3
3
|
|
4
4
|
class Future
|
5
|
-
attr_reader :command
|
5
|
+
attr_reader :command, :block
|
6
6
|
|
7
|
-
def initialize(command)
|
7
|
+
def initialize(command, block = nil)
|
8
8
|
@command = command
|
9
|
+
@block = block
|
9
10
|
@result_set = false
|
10
11
|
end
|
11
12
|
|
@@ -0,0 +1,248 @@
|
|
1
|
+
class MockRedis
|
2
|
+
module GeospatialMethods
|
3
|
+
LNG_RANGE = (-180..180)
|
4
|
+
LAT_RANGE = (-85.05112878..85.05112878)
|
5
|
+
STEP = 26
|
6
|
+
UNITS = {
|
7
|
+
m: 1,
|
8
|
+
km: 1000,
|
9
|
+
ft: 0.3048,
|
10
|
+
mi: 1609.34
|
11
|
+
}.freeze
|
12
|
+
D_R = Math::PI / 180.0
|
13
|
+
EARTH_RADIUS_IN_METERS = 6_372_797.560856
|
14
|
+
|
15
|
+
def geoadd(key, *args)
|
16
|
+
points = parse_points(args)
|
17
|
+
|
18
|
+
scored_points = points.map do |point|
|
19
|
+
score = geohash_encode(point[:lng], point[:lat])[:bits]
|
20
|
+
[score.to_s, point[:key]]
|
21
|
+
end
|
22
|
+
|
23
|
+
zadd(key, scored_points)
|
24
|
+
end
|
25
|
+
|
26
|
+
def geodist(key, *args)
|
27
|
+
if args.length < 2
|
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
|
36
|
+
|
37
|
+
return nil if zcard(key).zero?
|
38
|
+
|
39
|
+
score1 = zscore(key, args[0])
|
40
|
+
score2 = zscore(key, args[1])
|
41
|
+
return nil if score1.nil? || score2.nil?
|
42
|
+
hash1 = { bits: score1.to_i, step: STEP }
|
43
|
+
hash2 = { bits: score2.to_i, step: STEP }
|
44
|
+
|
45
|
+
lng1, lat1 = geohash_decode(hash1)
|
46
|
+
lng2, lat2 = geohash_decode(hash2)
|
47
|
+
|
48
|
+
distance = geohash_distance(lng1, lat1, lng2, lat2) / to_meter
|
49
|
+
format('%.4f', distance)
|
50
|
+
end
|
51
|
+
|
52
|
+
def geohash(key, *members)
|
53
|
+
lng_range = (-180..180)
|
54
|
+
lat_range = (-90..90)
|
55
|
+
geoalphabet = '0123456789bcdefghjkmnpqrstuvwxyz'
|
56
|
+
|
57
|
+
members.map do |member|
|
58
|
+
score = zscore(key, member)
|
59
|
+
next nil unless score
|
60
|
+
score = score.to_i
|
61
|
+
hash = { bits: score, step: STEP }
|
62
|
+
lng, lat = geohash_decode(hash)
|
63
|
+
bits = geohash_encode(lng, lat, lng_range, lat_range)[:bits]
|
64
|
+
hash = ''
|
65
|
+
11.times do |i|
|
66
|
+
shift = (52 - ((i + 1) * 5))
|
67
|
+
idx = shift > 0 ? (bits >> shift) & 0x1f : 0
|
68
|
+
hash << geoalphabet[idx]
|
69
|
+
end
|
70
|
+
hash
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def geopos(key, *members)
|
75
|
+
members.map do |member|
|
76
|
+
score = zscore(key, member)
|
77
|
+
next nil unless score
|
78
|
+
hash = { bits: score.to_i, step: STEP }
|
79
|
+
lng, lat = geohash_decode(hash)
|
80
|
+
lng = format_decoded_coord(lng)
|
81
|
+
lat = format_decoded_coord(lat)
|
82
|
+
[lng, lat]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def parse_points(args)
|
89
|
+
points = args.each_slice(3).to_a
|
90
|
+
|
91
|
+
if points.last.size != 3
|
92
|
+
raise Redis::CommandError,
|
93
|
+
"ERR wrong number of arguments for 'geoadd' command"
|
94
|
+
end
|
95
|
+
|
96
|
+
points.map do |point|
|
97
|
+
parse_point(point)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_point(point)
|
102
|
+
lng = Float(point[0])
|
103
|
+
lat = Float(point[1])
|
104
|
+
|
105
|
+
unless LNG_RANGE.include?(lng) && LAT_RANGE.include?(lat)
|
106
|
+
lng = format('%.6f', lng)
|
107
|
+
lat = format('%.6f', lat)
|
108
|
+
raise Redis::CommandError,
|
109
|
+
"ERR invalid longitude,latitude pair #{lng},#{lat}"
|
110
|
+
end
|
111
|
+
|
112
|
+
{ key: point[2], lng: lng, lat: lat }
|
113
|
+
rescue ArgumentError
|
114
|
+
raise Redis::CommandError, 'ERR value is not a valid float'
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns ZSET score for passed coordinates
|
118
|
+
def geohash_encode(lng, lat, lng_range = LNG_RANGE, lat_range = LAT_RANGE, step = STEP)
|
119
|
+
lat_offset = (lat - lat_range.min) / (lat_range.max - lat_range.min)
|
120
|
+
lng_offset = (lng - lng_range.min) / (lng_range.max - lng_range.min)
|
121
|
+
|
122
|
+
lat_offset *= (1 << step)
|
123
|
+
lng_offset *= (1 << step)
|
124
|
+
|
125
|
+
bits = interleave(lat_offset.to_i, lng_offset.to_i)
|
126
|
+
|
127
|
+
{ bits: bits, step: step }
|
128
|
+
end
|
129
|
+
|
130
|
+
def interleave(x, y)
|
131
|
+
b = [0x5555555555555555, 0x3333333333333333, 0x0F0F0F0F0F0F0F0F,
|
132
|
+
0x00FF00FF00FF00FF, 0x0000FFFF0000FFFF]
|
133
|
+
s = [1, 2, 4, 8, 16]
|
134
|
+
|
135
|
+
x = (x | (x << s[4])) & b[4]
|
136
|
+
y = (y | (y << s[4])) & b[4]
|
137
|
+
|
138
|
+
x = (x | (x << s[3])) & b[3]
|
139
|
+
y = (y | (y << s[3])) & b[3]
|
140
|
+
|
141
|
+
x = (x | (x << s[2])) & b[2]
|
142
|
+
y = (y | (y << s[2])) & b[2]
|
143
|
+
|
144
|
+
x = (x | (x << s[1])) & b[1]
|
145
|
+
y = (y | (y << s[1])) & b[1]
|
146
|
+
|
147
|
+
x = (x | (x << s[0])) & b[0]
|
148
|
+
y = (y | (y << s[0])) & b[0]
|
149
|
+
|
150
|
+
x | (y << 1)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Decodes ZSET score to coordinates pair
|
154
|
+
def geohash_decode(hash, lng_range = LNG_RANGE, lat_range = LAT_RANGE)
|
155
|
+
area = calculate_approximate_area(hash, lng_range, lat_range)
|
156
|
+
|
157
|
+
lng = (area[:lng_min] + area[:lng_max]) / 2
|
158
|
+
lat = (area[:lat_min] + area[:lat_max]) / 2
|
159
|
+
|
160
|
+
[lng, lat]
|
161
|
+
end
|
162
|
+
|
163
|
+
def calculate_approximate_area(hash, lng_range, lat_range)
|
164
|
+
bits = hash[:bits]
|
165
|
+
step = hash[:step]
|
166
|
+
hash_sep = deinterleave(bits)
|
167
|
+
|
168
|
+
lat_scale = lat_range.max - lat_range.min
|
169
|
+
lng_scale = lng_range.max - lng_range.min
|
170
|
+
|
171
|
+
ilato = hash_sep & 0xFFFFFFFF # cast int64 to int32 to get lat part of deinterleaved hash
|
172
|
+
ilngo = hash_sep >> 32 # shift over to get lng part of hash
|
173
|
+
|
174
|
+
{
|
175
|
+
lat_min: lat_range.min + (ilato * 1.0 / (1 << step)) * lat_scale,
|
176
|
+
lat_max: lat_range.min + ((ilato + 1) * 1.0 / (1 << step)) * lat_scale,
|
177
|
+
lng_min: lng_range.min + (ilngo * 1.0 / (1 << step)) * lng_scale,
|
178
|
+
lng_max: lng_range.min + ((ilngo + 1) * 1.0 / (1 << step)) * lng_scale
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def deinterleave(bits)
|
183
|
+
b = [0x5555555555555555, 0x3333333333333333, 0x0F0F0F0F0F0F0F0F,
|
184
|
+
0x00FF00FF00FF00FF, 0x0000FFFF0000FFFF, 0x00000000FFFFFFFF]
|
185
|
+
s = [0, 1, 2, 4, 8, 16]
|
186
|
+
|
187
|
+
x = bits
|
188
|
+
y = bits >> 1
|
189
|
+
|
190
|
+
x = (x | (x >> s[0])) & b[0]
|
191
|
+
y = (y | (y >> s[0])) & b[0]
|
192
|
+
|
193
|
+
x = (x | (x >> s[1])) & b[1]
|
194
|
+
y = (y | (y >> s[1])) & b[1]
|
195
|
+
|
196
|
+
x = (x | (x >> s[2])) & b[2]
|
197
|
+
y = (y | (y >> s[2])) & b[2]
|
198
|
+
|
199
|
+
x = (x | (x >> s[3])) & b[3]
|
200
|
+
y = (y | (y >> s[3])) & b[3]
|
201
|
+
|
202
|
+
x = (x | (x >> s[4])) & b[4]
|
203
|
+
y = (y | (y >> s[4])) & b[4]
|
204
|
+
|
205
|
+
x = (x | (x >> s[5])) & b[5]
|
206
|
+
y = (y | (y >> s[5])) & b[5]
|
207
|
+
|
208
|
+
x | (y << 32)
|
209
|
+
end
|
210
|
+
|
211
|
+
def format_decoded_coord(coord)
|
212
|
+
coord = format('%.17f', coord)
|
213
|
+
l = 1
|
214
|
+
l += 1 while coord[-l] == '0'
|
215
|
+
coord = coord[0..-l]
|
216
|
+
coord[-1] == '.' ? coord[0..-2] : coord
|
217
|
+
end
|
218
|
+
|
219
|
+
def parse_unit(unit)
|
220
|
+
unit = unit.to_sym
|
221
|
+
return UNITS[unit] if UNITS[unit]
|
222
|
+
|
223
|
+
raise Redis::CommandError,
|
224
|
+
'ERR unsupported unit provided. please use m, km, ft, mi'
|
225
|
+
end
|
226
|
+
|
227
|
+
def geohash_distance(lng1d, lat1d, lng2d, lat2d)
|
228
|
+
lat1r = deg_rad(lat1d)
|
229
|
+
lng1r = deg_rad(lng1d)
|
230
|
+
lat2r = deg_rad(lat2d)
|
231
|
+
lng2r = deg_rad(lng2d)
|
232
|
+
|
233
|
+
u = Math.sin((lat2r - lat1r) / 2)
|
234
|
+
v = Math.sin((lng2r - lng1r) / 2)
|
235
|
+
|
236
|
+
2.0 * EARTH_RADIUS_IN_METERS *
|
237
|
+
Math.asin(Math.sqrt(u * u + Math.cos(lat1r) * Math.cos(lat2r) * v * v))
|
238
|
+
end
|
239
|
+
|
240
|
+
def deg_rad(ang)
|
241
|
+
ang * D_R
|
242
|
+
end
|
243
|
+
|
244
|
+
def rad_deg(ang)
|
245
|
+
ang / D_R
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -20,7 +20,7 @@ class MockRedis
|
|
20
20
|
|
21
21
|
def method_missing(method, *args, &block)
|
22
22
|
if @in_pipeline
|
23
|
-
future = MockRedis::Future.new([method, *args])
|
23
|
+
future = MockRedis::Future.new([method, *args], block)
|
24
24
|
@pipelined_futures << future
|
25
25
|
future
|
26
26
|
else
|
@@ -32,12 +32,16 @@ class MockRedis
|
|
32
32
|
@in_pipeline = true
|
33
33
|
yield self
|
34
34
|
@in_pipeline = false
|
35
|
-
responses = @pipelined_futures.
|
35
|
+
responses = @pipelined_futures.flat_map do |future|
|
36
36
|
begin
|
37
|
-
result =
|
37
|
+
result = if future.block
|
38
|
+
send(*future.command, &future.block)
|
39
|
+
else
|
40
|
+
send(*future.command)
|
41
|
+
end
|
38
42
|
future.store_result(result)
|
39
43
|
result
|
40
|
-
rescue => e
|
44
|
+
rescue StandardError => e
|
41
45
|
e
|
42
46
|
end
|
43
47
|
end
|
@@ -3,6 +3,7 @@ require 'mock_redis/assertions'
|
|
3
3
|
class MockRedis
|
4
4
|
module StringMethods
|
5
5
|
include Assertions
|
6
|
+
include UtilityMethods
|
6
7
|
|
7
8
|
def append(key, value)
|
8
9
|
assert_stringy(key)
|
@@ -11,6 +12,70 @@ class MockRedis
|
|
11
12
|
data[key].length
|
12
13
|
end
|
13
14
|
|
15
|
+
def bitfield(*args)
|
16
|
+
if args.length < 4
|
17
|
+
raise Redis::CommandError, 'ERR wrong number of arguments for BITFIELD'
|
18
|
+
end
|
19
|
+
|
20
|
+
key = args.shift
|
21
|
+
output = []
|
22
|
+
overflow_method = 'wrap'
|
23
|
+
|
24
|
+
until args.empty?
|
25
|
+
command = args.shift.to_s
|
26
|
+
|
27
|
+
if command == 'overflow'
|
28
|
+
new_overflow_method = args.shift.to_s.downcase
|
29
|
+
|
30
|
+
unless %w[wrap sat fail].include? new_overflow_method
|
31
|
+
raise Redis::CommandError, 'ERR Invalid OVERFLOW type specified'
|
32
|
+
end
|
33
|
+
|
34
|
+
overflow_method = new_overflow_method
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
type, offset = args.shift(2)
|
39
|
+
|
40
|
+
is_signed = type.slice(0) == 'i'
|
41
|
+
type_size = type[1..-1].to_i
|
42
|
+
|
43
|
+
if (type_size > 64 && is_signed) || (type_size >= 64 && !is_signed)
|
44
|
+
raise Redis::CommandError,
|
45
|
+
'ERR Invalid bitfield type. Use something like i16 u8. ' \
|
46
|
+
'Note that u64 is not supported but i64 is.'
|
47
|
+
end
|
48
|
+
|
49
|
+
if offset.to_s[0] == '#'
|
50
|
+
offset = offset[1..-1].to_i * type_size
|
51
|
+
end
|
52
|
+
|
53
|
+
bits = []
|
54
|
+
|
55
|
+
type_size.times do |i|
|
56
|
+
bits.push(getbit(key, offset + i))
|
57
|
+
end
|
58
|
+
|
59
|
+
val = is_signed ? twos_complement_decode(bits) : bits.join('').to_i(2)
|
60
|
+
|
61
|
+
case command
|
62
|
+
when 'get'
|
63
|
+
output.push(val)
|
64
|
+
when 'set'
|
65
|
+
output.push(val)
|
66
|
+
|
67
|
+
set_bitfield(key, args.shift.to_i, is_signed, type_size, offset)
|
68
|
+
when 'incrby'
|
69
|
+
new_val = incr_bitfield(val, args.shift.to_i, is_signed, type_size, overflow_method)
|
70
|
+
|
71
|
+
set_bitfield(key, new_val, is_signed, type_size, offset) if new_val
|
72
|
+
output.push(new_val)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
output
|
77
|
+
end
|
78
|
+
|
14
79
|
def decr(key)
|
15
80
|
decrby(key, 1)
|
16
81
|
end
|
@@ -285,5 +350,49 @@ class MockRedis
|
|
285
350
|
raise Redis::CommandError, message
|
286
351
|
end
|
287
352
|
end
|
353
|
+
|
354
|
+
def set_bitfield(key, value, is_signed, type_size, offset)
|
355
|
+
if is_signed
|
356
|
+
val_array = twos_complement_encode(value, type_size)
|
357
|
+
else
|
358
|
+
str = left_pad(value.to_i.abs.to_s(2), type_size)
|
359
|
+
val_array = str.split('').map(&:to_i)
|
360
|
+
end
|
361
|
+
|
362
|
+
val_array.each_with_index do |bit, i|
|
363
|
+
setbit(key, offset + i, bit)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def incr_bitfield(val, incrby, is_signed, type_size, overflow_method)
|
368
|
+
new_val = val + incrby
|
369
|
+
|
370
|
+
max = is_signed ? (2**(type_size - 1)) - 1 : (2**type_size) - 1
|
371
|
+
min = is_signed ? (-2**(type_size - 1)) : 0
|
372
|
+
size = 2**type_size
|
373
|
+
|
374
|
+
return new_val if (min..max).cover?(new_val)
|
375
|
+
|
376
|
+
case overflow_method
|
377
|
+
when 'fail'
|
378
|
+
new_val = nil
|
379
|
+
when 'sat'
|
380
|
+
new_val = new_val > max ? max : min
|
381
|
+
when 'wrap'
|
382
|
+
if is_signed
|
383
|
+
if new_val > max
|
384
|
+
remainder = new_val - (max + 1)
|
385
|
+
new_val = min + remainder.abs
|
386
|
+
else
|
387
|
+
remainder = new_val - (min - 1)
|
388
|
+
new_val = max - remainder.abs
|
389
|
+
end
|
390
|
+
else
|
391
|
+
new_val = new_val > max ? new_val % size : size - new_val.abs
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
new_val
|
396
|
+
end
|
288
397
|
end
|
289
398
|
end
|
@@ -38,5 +38,36 @@ class MockRedis
|
|
38
38
|
|
39
39
|
[next_cursor, filtered_values]
|
40
40
|
end
|
41
|
+
|
42
|
+
def twos_complement_encode(n, size)
|
43
|
+
if n < 0
|
44
|
+
str = (n + 1).abs.to_s(2)
|
45
|
+
|
46
|
+
binary = left_pad(str, size - 1).chars.map { |c| c == '0' ? 1 : 0 }
|
47
|
+
binary.unshift(1)
|
48
|
+
else
|
49
|
+
binary = left_pad(n.abs.to_s(2), size - 1).chars.map(&:to_i)
|
50
|
+
binary.unshift(0)
|
51
|
+
end
|
52
|
+
|
53
|
+
binary
|
54
|
+
end
|
55
|
+
|
56
|
+
def twos_complement_decode(array)
|
57
|
+
total = 0
|
58
|
+
|
59
|
+
array.each.with_index do |bit, index|
|
60
|
+
total += 2**(array.length - index - 1) if bit == 1
|
61
|
+
total = -total if index == 0
|
62
|
+
end
|
63
|
+
|
64
|
+
total
|
65
|
+
end
|
66
|
+
|
67
|
+
def left_pad(str, size)
|
68
|
+
str = '0' + str while str.length < size
|
69
|
+
|
70
|
+
str
|
71
|
+
end
|
41
72
|
end
|
42
73
|
end
|
data/lib/mock_redis/version.rb
CHANGED
data/mock_redis.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$LOAD_PATH << File.expand_path('
|
1
|
+
$LOAD_PATH << File.expand_path('lib', __dir__)
|
2
2
|
require 'mock_redis/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
@@ -11,16 +11,18 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = 'https://github.com/brigade/mock_redis'
|
12
12
|
s.summary = 'Redis mock that just lives in memory; useful for testing.'
|
13
13
|
|
14
|
-
s.description = <<-
|
14
|
+
s.description = <<-MSG.strip.gsub(/\s+/, ' ')
|
15
15
|
Instantiate one with `redis = MockRedis.new` and treat it like you would a
|
16
16
|
normal Redis object. It supports all the usual Redis operations.
|
17
|
-
|
17
|
+
MSG
|
18
18
|
|
19
19
|
s.files = `git ls-files`.split("\n")
|
20
20
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
21
21
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
22
22
|
s.require_paths = ['lib']
|
23
23
|
|
24
|
+
s.required_ruby_version = '>= 2.2'
|
25
|
+
|
24
26
|
s.add_development_dependency 'rake', '>= 10', '< 12'
|
25
27
|
s.add_development_dependency 'redis', '~> 3.3.0'
|
26
28
|
s.add_development_dependency 'rspec', '~> 3.0'
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe '#bitfield(*args)' do
|
4
|
+
before :each do
|
5
|
+
@key = 'mock-redis-test:bitfield'
|
6
|
+
@redises.set(@key, '')
|
7
|
+
|
8
|
+
@redises.bitfield(@key, :set, 'i8', 0, 78)
|
9
|
+
@redises.bitfield(@key, :set, 'i8', 8, 104)
|
10
|
+
@redises.bitfield(@key, :set, 'i8', 16, -59)
|
11
|
+
@redises.bitfield(@key, :set, 'u8', 24, 78)
|
12
|
+
@redises.bitfield(@key, :set, 'u8', 32, 84)
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'with a :get command' do
|
16
|
+
it 'gets a signed 8 bit value' do
|
17
|
+
@redises.bitfield(@key, :get, 'i8', 0).should == [78]
|
18
|
+
@redises.bitfield(@key, :get, 'i8', 8).should == [104]
|
19
|
+
@redises.bitfield(@key, :get, 'i8', 16).should == [-59]
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'gets multiple values with multiple command args' do
|
23
|
+
@redises.bitfield(@key, :get, 'i8', 0,
|
24
|
+
:get, 'i8', 8,
|
25
|
+
:get, 'i8', 16).should == [78, 104, -59]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'gets multiple values using positional offsets' do
|
29
|
+
@redises.bitfield(@key, :get, 'i8', '#0',
|
30
|
+
:get, 'i8', '#1',
|
31
|
+
:get, 'i8', '#2').should == [78, 104, -59]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'shows an error with an invalid type' do
|
35
|
+
expect do
|
36
|
+
@redises.bitfield(@key, :get, 'u64', 0)
|
37
|
+
end.to raise_error(Redis::CommandError)
|
38
|
+
expect do
|
39
|
+
@redises.bitfield(@key, :get, 'i128', 0)
|
40
|
+
end.to raise_error(Redis::CommandError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not throw an error with i64 type' do
|
44
|
+
expect do
|
45
|
+
@redises.bitfield(@key, :get, 'i64', 0)
|
46
|
+
end.to_not raise_error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with a :set command' do
|
51
|
+
it 'sets the bit values for an 8 bit signed integer' do
|
52
|
+
@redises.bitfield(@key, :set, 'i8', 0, 63).should == [78]
|
53
|
+
@redises.bitfield(@key, :set, 'i8', 8, -1).should == [104]
|
54
|
+
@redises.bitfield(@key, :set, 'i8', 16, 123).should == [-59]
|
55
|
+
|
56
|
+
@redises.bitfield(@key, :get, 'i8', 0,
|
57
|
+
:get, 'i8', 8,
|
58
|
+
:get, 'i8', 16).should == [63, -1, 123]
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'sets multiple values with multiple command args' do
|
62
|
+
@redises.bitfield(@key, :set, 'i8', 0, 63,
|
63
|
+
:set, 'i8', 8, -1,
|
64
|
+
:set, 'i8', 16, 123).should == [78, 104, -59]
|
65
|
+
|
66
|
+
@redises.bitfield(@key, :get, 'i8', 0,
|
67
|
+
:get, 'i8', 8,
|
68
|
+
:get, 'i8', 16).should == [63, -1, 123]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with an :incrby command' do
|
73
|
+
it 'returns the incremented by value for an 8 bit signed integer' do
|
74
|
+
@redises.bitfield(@key, :incrby, 'i8', 0, 1).should == [79]
|
75
|
+
@redises.bitfield(@key, :incrby, 'i8', 8, -1).should == [103]
|
76
|
+
@redises.bitfield(@key, :incrby, 'i8', 16, 5).should == [-54]
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with an overflow of wrap (default)' do
|
80
|
+
context 'for a signed integer' do
|
81
|
+
it 'wraps the overflow to the minimum and increments from there' do
|
82
|
+
@redises.bitfield(@key, :get, 'i8', 24).should == [78]
|
83
|
+
@redises.bitfield(@key, :overflow, :wrap,
|
84
|
+
:incrby, 'i8', 0, 200).should == [22]
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'wraps the underflow to the maximum value and decrements from there' do
|
88
|
+
@redises.bitfield(@key, :overflow, :wrap,
|
89
|
+
:incrby, 'i8', 16, -200).should == [-3]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'for an unsigned integer' do
|
94
|
+
it 'wraps the overflow back to zero and increments from there' do
|
95
|
+
@redises.bitfield(@key, :get, 'u8', 24).should == [78]
|
96
|
+
@redises.bitfield(@key, :overflow, :wrap,
|
97
|
+
:incrby, 'u8', 24, 233).should == [55]
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'wraps the underflow to the maximum value and decrements from there' do
|
101
|
+
@redises.bitfield(@key, :get, 'u8', 32).should == [84]
|
102
|
+
@redises.bitfield(@key, :overflow, :wrap,
|
103
|
+
:incrby, 'u8', 32, -233).should == [107]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'with an overflow of sat' do
|
109
|
+
it 'sets the overflowed value to the maximum' do
|
110
|
+
@redises.bitfield(@key, :overflow, :sat,
|
111
|
+
:incrby, 'i8', 0, 256).should == [127]
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'sets the underflowed value to the minimum' do
|
115
|
+
@redises.bitfield(@key, :overflow, :sat,
|
116
|
+
:incrby, 'i8', 16, -256).should == [-128]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'with an overflow of fail' do
|
121
|
+
it 'raises a redis error on an out of range value' do
|
122
|
+
@redises.bitfield(@key, :overflow, :fail,
|
123
|
+
:incrby, 'i8', 0, 256).should == [nil]
|
124
|
+
|
125
|
+
@redises.bitfield(@key, :overflow, :fail,
|
126
|
+
:incrby, 'i8', 16, -256).should == [nil]
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'retains the original value after a failed increment' do
|
130
|
+
@redises.bitfield(@key, :get, 'i8', 0).should == [78]
|
131
|
+
@redises.bitfield(@key, :overflow, :fail,
|
132
|
+
:incrby, 'i8', 0, 256).should == [nil]
|
133
|
+
@redises.bitfield(@key, :get, 'i8', 0).should == [78]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'with multiple overflow commands in one transaction' do
|
138
|
+
it 'handles the overflow values correctly' do
|
139
|
+
@redises.bitfield(@key, :overflow, :sat,
|
140
|
+
:incrby, 'i8', 0, 256,
|
141
|
+
:incrby, 'i8', 8, -256,
|
142
|
+
:overflow, :wrap,
|
143
|
+
:incrby, 'i8', 0, 200,
|
144
|
+
:incrby, 'i8', 16, -200,
|
145
|
+
:overflow, :fail,
|
146
|
+
:incrby, 'i8', 0, 256,
|
147
|
+
:incrby, 'i8', 16, -256).should == [127, -128, 71, -3, nil, nil]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'with an unsupported overflow value' do
|
152
|
+
it 'raises an error' do
|
153
|
+
expect do
|
154
|
+
@redises.bitfield(@key, :overflow, :foo,
|
155
|
+
:incrby, 'i8', 0, 256)
|
156
|
+
end.to raise_error(Redis::CommandError)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'with a mixed set of commands' do
|
162
|
+
it 'returns the correct outputs' do
|
163
|
+
@redises.bitfield(@key, :set, 'i8', 0, 38,
|
164
|
+
:set, 'i8', 8, -99,
|
165
|
+
:incrby, 'i8', 16, 1,
|
166
|
+
:get, 'i8', 0).should == [78, 104, -58, 38]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe '#geoadd' do
|
4
|
+
let(:key) { 'cities' }
|
5
|
+
|
6
|
+
context 'with valid points' do
|
7
|
+
let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] }
|
8
|
+
let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] }
|
9
|
+
let(:expected_result) do
|
10
|
+
[['LA', 1.364461589564902e+15], ['SF', 1.367859319053696e+15]]
|
11
|
+
end
|
12
|
+
|
13
|
+
before { @redises.geoadd(key, *san_francisco, *los_angeles) }
|
14
|
+
|
15
|
+
after { @redises.zrem(key, %w[SF LA]) }
|
16
|
+
|
17
|
+
it 'adds members to ZSET' do
|
18
|
+
cities = @redises.zrange(key, 0, -1, with_scores: true)
|
19
|
+
expect(cities).to be == expected_result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with invalud points' do
|
24
|
+
context 'when number of arguments wrong' do
|
25
|
+
let(:message) { "ERR wrong number of arguments for 'geoadd' command" }
|
26
|
+
|
27
|
+
it 'raises Redis::CommandError' do
|
28
|
+
expect { @redises.geoadd(key, 1, 1) }
|
29
|
+
.to raise_error(Redis::CommandError, message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when coordinates are not in allowed range' do
|
34
|
+
let(:coords) { [181, 86] }
|
35
|
+
let(:message) do
|
36
|
+
formatted_coords = coords.map { |c| format('%.6f', c) }
|
37
|
+
"ERR invalid longitude,latitude pair #{formatted_coords.join(',')}"
|
38
|
+
end
|
39
|
+
|
40
|
+
after { @redises.zrem(key, 'SF') }
|
41
|
+
|
42
|
+
it 'raises Redis::CommandError' do
|
43
|
+
expect { @redises.geoadd(key, *coords, 'SF') }
|
44
|
+
.to raise_error(Redis::CommandError, message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when coordinates are not valid floats' do
|
49
|
+
let(:coords) { ['x', 35] }
|
50
|
+
let(:message) { 'ERR value is not a valid float' }
|
51
|
+
|
52
|
+
it 'raises Redis::CommandError' do
|
53
|
+
expect { @redises.geoadd key, *coords, 'SF' }
|
54
|
+
.to raise_error(Redis::CommandError, message)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples 'a distance calculator' do
|
4
|
+
it 'returns distance between two points in specified unit' do
|
5
|
+
dist = @redises.geodist(key, 'SF', 'LA', unit)
|
6
|
+
expect(dist).to be == expected_result
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#geodist' do
|
11
|
+
let(:key) { 'cities' }
|
12
|
+
|
13
|
+
context 'with existing key' do
|
14
|
+
let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] }
|
15
|
+
let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] }
|
16
|
+
|
17
|
+
before { @redises.geoadd(key, *san_francisco, *los_angeles) }
|
18
|
+
|
19
|
+
after { @redises.zrem(key, %w[SF LA]) }
|
20
|
+
|
21
|
+
context 'with existing points only' do
|
22
|
+
context 'using m as unit' do
|
23
|
+
let(:unit) { 'm' }
|
24
|
+
let(:expected_result) { '539327.9659' }
|
25
|
+
|
26
|
+
it 'returns distance between two points in meters' do
|
27
|
+
dist = @redises.geodist(key, 'SF', 'LA')
|
28
|
+
expect(dist).to be == expected_result
|
29
|
+
end
|
30
|
+
|
31
|
+
it_behaves_like 'a distance calculator'
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'using km as unit' do
|
35
|
+
let(:unit) { 'km' }
|
36
|
+
let(:expected_result) { '539.3280' }
|
37
|
+
|
38
|
+
it_behaves_like 'a distance calculator'
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'using ft as unit' do
|
42
|
+
let(:unit) { 'ft' }
|
43
|
+
let(:expected_result) { '1769448.7069' }
|
44
|
+
|
45
|
+
it_behaves_like 'a distance calculator'
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'using mi as unit' do
|
49
|
+
let(:unit) { 'mi' }
|
50
|
+
let(:expected_result) { '335.1237' }
|
51
|
+
|
52
|
+
it_behaves_like 'a distance calculator'
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with non-existing points only' do
|
56
|
+
it 'returns nil' do
|
57
|
+
dist = @redises.geodist(key, 'FF', 'FA')
|
58
|
+
expect(dist).to be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'with both existing and non-existing points' do
|
63
|
+
it 'returns nil' do
|
64
|
+
dist = @redises.geodist(key, 'SF', 'FA')
|
65
|
+
expect(dist).to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with non-existing key' do
|
72
|
+
it 'returns empty string' do
|
73
|
+
dist = @redises.geodist(key, 'SF', 'LA')
|
74
|
+
expect(dist).to be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'with wrong number of arguments' do
|
79
|
+
let(:list) { [key, 'SF', 'LA', 'm', 'smth'] }
|
80
|
+
|
81
|
+
context 'with less than 3 arguments' do
|
82
|
+
[1, 2].each do |count|
|
83
|
+
let(:message) { "ERR wrong number of arguments for 'geodist' command" }
|
84
|
+
|
85
|
+
context "with #{count} arguments" do
|
86
|
+
it 'raises an error' do
|
87
|
+
args = list.slice(0, count)
|
88
|
+
expect { @redises.geodist(*args) }
|
89
|
+
.to raise_error(Redis::CommandError, message)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with more than 3 arguments' do
|
96
|
+
let(:message) { 'ERR syntax error' }
|
97
|
+
|
98
|
+
it 'raises an error' do
|
99
|
+
args = list.slice(0, 5)
|
100
|
+
expect { @redises.geodist(*args) }
|
101
|
+
.to raise_error(Redis::CommandError, message)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'with wrong unit' do
|
107
|
+
let(:message) { 'ERR unsupported unit provided. please use m, km, ft, mi' }
|
108
|
+
|
109
|
+
it 'raises an error' do
|
110
|
+
expect { @redises.geodist(key, 'SF', 'LA', 'a') }
|
111
|
+
.to raise_error(Redis::CommandError, message)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe '#geohash' do
|
4
|
+
let(:key) { 'cities' }
|
5
|
+
|
6
|
+
context 'with existing key' do
|
7
|
+
let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] }
|
8
|
+
let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] }
|
9
|
+
|
10
|
+
before { @redises.geoadd(key, *san_francisco, *los_angeles) }
|
11
|
+
|
12
|
+
after { @redises.zrem(key, %w[SF LA]) }
|
13
|
+
|
14
|
+
context 'with existing points only' do
|
15
|
+
let(:expected_result) do
|
16
|
+
%w[9q8yu38ejp0 9q59e171je0]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns decoded coordinates pairs for each point' do
|
20
|
+
results = @redises.geohash(key, 'SF', 'LA')
|
21
|
+
expect(results).to be == expected_result
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with non-existing points only' do
|
25
|
+
it 'returns array filled with nils' do
|
26
|
+
results = @redises.geohash(key, 'FF', 'FA')
|
27
|
+
expect(results).to be == [nil, nil]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with both existing and non-existing points' do
|
32
|
+
let(:expected_result) do
|
33
|
+
['9q8yu38ejp0', nil]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns mixture of nil and coordinates pair' do
|
37
|
+
results = @redises.geohash(key, 'SF', 'FA')
|
38
|
+
expect(results).to be == expected_result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with non-existing key' do
|
45
|
+
before { @redises.del(key) }
|
46
|
+
|
47
|
+
it 'returns empty array' do
|
48
|
+
results = @redises.geohash(key, 'SF', 'LA')
|
49
|
+
expect(results).to be == [nil, nil]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe '#geopos' do
|
4
|
+
let(:key) { 'cities' }
|
5
|
+
|
6
|
+
context 'with existing key' do
|
7
|
+
let(:san_francisco) { [-122.5076404, 37.757815, 'SF'] }
|
8
|
+
let(:los_angeles) { [-118.6919259, 34.0207305, 'LA'] }
|
9
|
+
|
10
|
+
before { @redises.geoadd(key, *san_francisco, *los_angeles) }
|
11
|
+
|
12
|
+
after { @redises.zrem(key, %w[SF LA]) }
|
13
|
+
|
14
|
+
context 'with existing points only' do
|
15
|
+
let(:expected_result) do
|
16
|
+
[
|
17
|
+
%w[-122.5076410174369812 37.75781598995183685],
|
18
|
+
%w[-118.69192510843276978 34.020729570911179]
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns decoded coordinates pairs for each point' do
|
23
|
+
coords = @redises.geopos(key, 'SF', 'LA')
|
24
|
+
expect(coords).to be == expected_result
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with non-existing points only' do
|
28
|
+
it 'returns array filled with nils' do
|
29
|
+
coords = @redises.geopos(key, 'FF', 'FA')
|
30
|
+
expect(coords).to be == [nil, nil]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with both existing and non-existing points' do
|
35
|
+
let(:expected_result) do
|
36
|
+
[%w[-122.5076410174369812 37.75781598995183685], nil]
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns mixture of nil and coordinates pair' do
|
40
|
+
coords = @redises.geopos(key, 'SF', 'FA')
|
41
|
+
expect(coords).to be == expected_result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with non-existing key' do
|
48
|
+
before { @redises.del(key) }
|
49
|
+
|
50
|
+
it 'returns empty array' do
|
51
|
+
coords = @redises.geopos(key, 'SF', 'LA')
|
52
|
+
expect(coords).to be == [nil, nil]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/transactions_spec.rb
CHANGED
@@ -67,6 +67,18 @@ describe 'transactions (multi/exec/discard)' do
|
|
67
67
|
@redises.get('counter').should == '6'
|
68
68
|
@redises.get('test').should == '1'
|
69
69
|
end
|
70
|
+
|
71
|
+
it 'allows multi blocks within pipelined blocks' do
|
72
|
+
@redises.set('counter', 5)
|
73
|
+
@redises.pipelined do |pr|
|
74
|
+
pr.multi do |r|
|
75
|
+
r.set('test', 1)
|
76
|
+
r.incr('counter')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@redises.get('counter').should == '6'
|
80
|
+
@redises.get('test').should == '1'
|
81
|
+
end
|
70
82
|
end
|
71
83
|
|
72
84
|
context '#discard' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mock_redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brigade Engineering
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-03
|
12
|
+
date: 2018-08-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -86,6 +86,7 @@ files:
|
|
86
86
|
- ".overcommit.yml"
|
87
87
|
- ".rspec"
|
88
88
|
- ".rubocop.yml"
|
89
|
+
- ".rubocop_todo.yml"
|
89
90
|
- ".simplecov"
|
90
91
|
- ".travis.yml"
|
91
92
|
- CHANGELOG.md
|
@@ -99,6 +100,7 @@ files:
|
|
99
100
|
- lib/mock_redis/exceptions.rb
|
100
101
|
- lib/mock_redis/expire_wrapper.rb
|
101
102
|
- lib/mock_redis/future.rb
|
103
|
+
- lib/mock_redis/geospatial_methods.rb
|
102
104
|
- lib/mock_redis/hash_methods.rb
|
103
105
|
- lib/mock_redis/indifferent_hash.rb
|
104
106
|
- lib/mock_redis/info_method.rb
|
@@ -122,6 +124,7 @@ files:
|
|
122
124
|
- spec/commands/bgrewriteaof_spec.rb
|
123
125
|
- spec/commands/bgsave_spec.rb
|
124
126
|
- spec/commands/bitcount_spec.rb
|
127
|
+
- spec/commands/bitfield_spec.rb
|
125
128
|
- spec/commands/blpop_spec.rb
|
126
129
|
- spec/commands/brpop_spec.rb
|
127
130
|
- spec/commands/brpoplpush_spec.rb
|
@@ -140,6 +143,10 @@ files:
|
|
140
143
|
- spec/commands/flushall_spec.rb
|
141
144
|
- spec/commands/flushdb_spec.rb
|
142
145
|
- spec/commands/future_spec.rb
|
146
|
+
- spec/commands/geoadd_spec.rb
|
147
|
+
- spec/commands/geodist_spec.rb
|
148
|
+
- spec/commands/geohash_spec.rb
|
149
|
+
- spec/commands/geopos_spec.rb
|
143
150
|
- spec/commands/get_spec.rb
|
144
151
|
- spec/commands/getbit_spec.rb
|
145
152
|
- spec/commands/getrange_spec.rb
|
@@ -273,7 +280,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
273
280
|
requirements:
|
274
281
|
- - ">="
|
275
282
|
- !ruby/object:Gem::Version
|
276
|
-
version: '
|
283
|
+
version: '2.2'
|
277
284
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
278
285
|
requirements:
|
279
286
|
- - ">="
|
@@ -293,6 +300,7 @@ test_files:
|
|
293
300
|
- spec/commands/bgrewriteaof_spec.rb
|
294
301
|
- spec/commands/bgsave_spec.rb
|
295
302
|
- spec/commands/bitcount_spec.rb
|
303
|
+
- spec/commands/bitfield_spec.rb
|
296
304
|
- spec/commands/blpop_spec.rb
|
297
305
|
- spec/commands/brpop_spec.rb
|
298
306
|
- spec/commands/brpoplpush_spec.rb
|
@@ -311,6 +319,10 @@ test_files:
|
|
311
319
|
- spec/commands/flushall_spec.rb
|
312
320
|
- spec/commands/flushdb_spec.rb
|
313
321
|
- spec/commands/future_spec.rb
|
322
|
+
- spec/commands/geoadd_spec.rb
|
323
|
+
- spec/commands/geodist_spec.rb
|
324
|
+
- spec/commands/geohash_spec.rb
|
325
|
+
- spec/commands/geopos_spec.rb
|
314
326
|
- spec/commands/get_spec.rb
|
315
327
|
- spec/commands/getbit_spec.rb
|
316
328
|
- spec/commands/getrange_spec.rb
|