mock_redis 0.18.0 → 0.19.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 +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
|
[![Gem Version](https://badge.fury.io/rb/mock_redis.svg)](http://badge.fury.io/rb/mock_redis)
|
4
4
|
[![Build Status](https://travis-ci.org/brigade/mock_redis.svg)](https://travis-ci.org/brigade/mock_redis)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/brigade/mock_redis/badge.svg)](https://coveralls.io/r/brigade/mock_redis)
|
6
|
-
[![Dependency Status](https://gemnasium.com/brigade/mock_redis.svg)](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
|