fakeredis 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -3
- data/fakeredis.gemspec +4 -3
- data/lib/fakeredis/version.rb +1 -1
- data/lib/redis/connection/memory.rb +119 -87
- data/spec/compatibility_spec.rb +2 -2
- data/spec/connection_spec.rb +3 -13
- data/spec/hashes_spec.rb +33 -44
- data/spec/keys_spec.rb +27 -40
- data/spec/lists_spec.rb +27 -42
- data/spec/server_spec.rb +2 -39
- data/spec/sets_spec.rb +16 -22
- data/spec/sorted_sets_spec.rb +58 -229
- data/spec/spec_helper.rb +0 -4
- data/spec/strings_spec.rb +45 -92
- metadata +9 -16
- data/lib/fake_redis.rb +0 -1
- data/lib/fakeredis/expiring_hash.rb +0 -56
- data/lib/fakeredis/sorted_set_argument_handler.rb +0 -74
- data/lib/fakeredis/sorted_set_store.rb +0 -80
- data/lib/fakeredis/zset.rb +0 -4
- data/spec/spec_helper_live_redis.rb +0 -14
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# FakeRedis [![Build Status](
|
1
|
+
# FakeRedis [![Build Status](http://travis-ci.org/guilleiguaran/fakeredis.png)](http://travis-ci.org/guilleiguaran/fakeredis)
|
2
2
|
This a fake implementation of redis-rb for machines without Redis or test environments
|
3
3
|
|
4
4
|
|
@@ -64,8 +64,6 @@ Or:
|
|
64
64
|
* [obrie](https://github.com/obrie)
|
65
65
|
* [jredville](https://github.com/jredville)
|
66
66
|
* [redsquirrel](https://github.com/redsquirrel)
|
67
|
-
* [dpick](https://github.com/dpick)
|
68
|
-
* [caius](https://github.com/caius)
|
69
67
|
* [Travis-CI](http://travis-ci.org/) (Travis-CI also uses Fakeredis in its tests!!!)
|
70
68
|
|
71
69
|
|
data/fakeredis.gemspec
CHANGED
@@ -8,16 +8,17 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Guillermo Iguaran"]
|
10
10
|
s.email = ["guilleiguaran@gmail.com"]
|
11
|
-
s.homepage = "https://
|
12
|
-
s.license = "MIT"
|
11
|
+
s.homepage = "https://github.com/guilleiguaran/fakeredis"
|
13
12
|
s.summary = %q{Fake (In-memory) driver for redis-rb.}
|
14
13
|
s.description = %q{Fake (In-memory) driver for redis-rb. Useful for testing environment and machines without Redis.}
|
15
14
|
|
15
|
+
s.rubyforge_project = "fakeredis"
|
16
|
+
|
16
17
|
s.files = `git ls-files`.split("\n")
|
17
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
20
|
s.require_paths = ["lib"]
|
20
21
|
|
21
|
-
s.add_runtime_dependency(%q<redis>, ["~>
|
22
|
+
s.add_runtime_dependency(%q<redis>, ["~> 3.0.0"])
|
22
23
|
s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
|
23
24
|
end
|
data/lib/fakeredis/version.rb
CHANGED
@@ -1,20 +1,73 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'redis/connection/registry'
|
3
3
|
require 'redis/connection/command_helper'
|
4
|
-
require "fakeredis/expiring_hash"
|
5
|
-
require "fakeredis/sorted_set_argument_handler"
|
6
|
-
require "fakeredis/sorted_set_store"
|
7
|
-
require "fakeredis/zset"
|
8
4
|
|
9
5
|
class Redis
|
10
6
|
module Connection
|
11
7
|
class Memory
|
8
|
+
# Represents a normal hash with some additional expiration information
|
9
|
+
# associated with each key
|
10
|
+
class ExpiringHash < Hash
|
11
|
+
attr_reader :expires
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
@expires = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
delete(key) if expired?(key)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(key, val)
|
24
|
+
expire(key)
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(key)
|
29
|
+
expire(key)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def expire(key)
|
34
|
+
expires.delete(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def expired?(key)
|
38
|
+
expires.include?(key) && expires[key] < Time.now
|
39
|
+
end
|
40
|
+
|
41
|
+
def key?(key)
|
42
|
+
delete(key) if expired?(key)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def values_at(*keys)
|
47
|
+
keys.each {|key| delete(key) if expired?(key)}
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def keys
|
52
|
+
super.select do |key|
|
53
|
+
if expired?(key)
|
54
|
+
delete(key)
|
55
|
+
false
|
56
|
+
else
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ZSet < Hash
|
64
|
+
end
|
65
|
+
|
12
66
|
include Redis::Connection::CommandHelper
|
13
|
-
include FakeRedis
|
14
67
|
|
15
|
-
def initialize
|
68
|
+
def initialize(connected = false)
|
16
69
|
@data = ExpiringHash.new
|
17
|
-
@connected =
|
70
|
+
@connected = connected
|
18
71
|
@replies = []
|
19
72
|
@buffer = nil
|
20
73
|
end
|
@@ -23,8 +76,8 @@ class Redis
|
|
23
76
|
@connected
|
24
77
|
end
|
25
78
|
|
26
|
-
def connect(
|
27
|
-
|
79
|
+
def self.connect(options = {})
|
80
|
+
self.new(true)
|
28
81
|
end
|
29
82
|
|
30
83
|
def connect_unix(path, timeout)
|
@@ -40,12 +93,8 @@ class Redis
|
|
40
93
|
end
|
41
94
|
|
42
95
|
def write(command)
|
43
|
-
|
44
|
-
|
45
|
-
reply = send(meffod, *command)
|
46
|
-
else
|
47
|
-
raise RuntimeError, "ERR unknown command '#{meffod}'"
|
48
|
-
end
|
96
|
+
method = command.shift
|
97
|
+
reply = send(method, *command)
|
49
98
|
|
50
99
|
if reply == true
|
51
100
|
reply = 1
|
@@ -54,7 +103,7 @@ class Redis
|
|
54
103
|
end
|
55
104
|
|
56
105
|
@replies << reply
|
57
|
-
@buffer << reply if @buffer &&
|
106
|
+
@buffer << reply if @buffer && method != :multi
|
58
107
|
nil
|
59
108
|
end
|
60
109
|
|
@@ -68,14 +117,13 @@ class Redis
|
|
68
117
|
# * brpoplpush
|
69
118
|
# * discard
|
70
119
|
# * move
|
71
|
-
# * sort
|
72
120
|
# * subscribe
|
73
121
|
# * psubscribe
|
74
122
|
# * publish
|
75
|
-
|
123
|
+
# * zremrangebyrank
|
124
|
+
# * zunionstore
|
76
125
|
def flushdb
|
77
126
|
@data = ExpiringHash.new
|
78
|
-
"OK"
|
79
127
|
end
|
80
128
|
|
81
129
|
def flushall
|
@@ -112,7 +160,6 @@ class Redis
|
|
112
160
|
def bgreriteaof ; end
|
113
161
|
|
114
162
|
def get(key)
|
115
|
-
data_type_check(key, String)
|
116
163
|
@data[key]
|
117
164
|
end
|
118
165
|
|
@@ -128,14 +175,13 @@ class Redis
|
|
128
175
|
alias :substr :getrange
|
129
176
|
|
130
177
|
def getset(key, value)
|
131
|
-
|
132
|
-
@data[key]
|
133
|
-
|
134
|
-
end
|
178
|
+
old_value = @data[key]
|
179
|
+
@data[key] = value
|
180
|
+
return old_value
|
135
181
|
end
|
136
182
|
|
137
183
|
def mget(*keys)
|
138
|
-
raise
|
184
|
+
raise ArgumentError, "wrong number of arguments for 'mget' command" if keys.empty?
|
139
185
|
@data.values_at(*keys)
|
140
186
|
end
|
141
187
|
|
@@ -151,7 +197,7 @@ class Redis
|
|
151
197
|
|
152
198
|
def hgetall(key)
|
153
199
|
data_type_check(key, Hash)
|
154
|
-
@data[key] || {}
|
200
|
+
@data[key].to_a.flatten || {}
|
155
201
|
end
|
156
202
|
|
157
203
|
def hget(key, field)
|
@@ -208,7 +254,7 @@ class Redis
|
|
208
254
|
|
209
255
|
def lrange(key, startidx, endidx)
|
210
256
|
data_type_check(key, Array)
|
211
|
-
@data[key] && @data[key][startidx..endidx]
|
257
|
+
@data[key] && @data[key][startidx..endidx]
|
212
258
|
end
|
213
259
|
|
214
260
|
def ltrim(key, start, stop)
|
@@ -236,7 +282,7 @@ class Redis
|
|
236
282
|
def lset(key, index, value)
|
237
283
|
data_type_check(key, Array)
|
238
284
|
return unless @data[key]
|
239
|
-
raise RuntimeError
|
285
|
+
raise RuntimeError if index >= @data[key].size
|
240
286
|
@data[key][index] = value
|
241
287
|
end
|
242
288
|
|
@@ -261,7 +307,7 @@ class Redis
|
|
261
307
|
def rpush(key, value)
|
262
308
|
data_type_check(key, Array)
|
263
309
|
@data[key] ||= []
|
264
|
-
@data[key].push(value
|
310
|
+
@data[key].push(value)
|
265
311
|
@data[key].size
|
266
312
|
end
|
267
313
|
|
@@ -274,7 +320,7 @@ class Redis
|
|
274
320
|
def lpush(key, value)
|
275
321
|
data_type_check(key, Array)
|
276
322
|
@data[key] ||= []
|
277
|
-
@data[key].unshift(value
|
323
|
+
@data[key].unshift(value)
|
278
324
|
@data[key].size
|
279
325
|
end
|
280
326
|
|
@@ -292,9 +338,8 @@ class Redis
|
|
292
338
|
|
293
339
|
def rpoplpush(key1, key2)
|
294
340
|
data_type_check(key1, Array)
|
295
|
-
rpop(key1)
|
296
|
-
|
297
|
-
end
|
341
|
+
elem = rpop(key1)
|
342
|
+
lpush(key2, elem)
|
298
343
|
end
|
299
344
|
|
300
345
|
def lpop(key)
|
@@ -406,7 +451,7 @@ class Redis
|
|
406
451
|
keys.flatten.each do |key|
|
407
452
|
@data.delete(key)
|
408
453
|
end
|
409
|
-
old_count - @data.keys.size
|
454
|
+
deleted_count = old_count - @data.keys.size
|
410
455
|
end
|
411
456
|
|
412
457
|
def setnx(key, value)
|
@@ -478,11 +523,7 @@ class Redis
|
|
478
523
|
end
|
479
524
|
|
480
525
|
def hmset(key, *fields)
|
481
|
-
|
482
|
-
fields = fields[0] if fields.size == 1 && fields[0].is_a?(Array)
|
483
|
-
fields = fields[0] if mapped_param?(fields)
|
484
|
-
raise RuntimeError, "ERR wrong number of arguments for HMSET" if fields.size > 2 && fields.size.odd?
|
485
|
-
raise RuntimeError, "ERR wrong number of arguments for 'hmset' command" if fields.empty? || fields.size.odd?
|
526
|
+
raise ArgumentError, "wrong number of arguments for 'hmset' command" if fields.empty? || fields.size.odd?
|
486
527
|
data_type_check(key, Hash)
|
487
528
|
@data[key] ||= {}
|
488
529
|
fields.each_slice(2) do |field|
|
@@ -491,8 +532,9 @@ class Redis
|
|
491
532
|
end
|
492
533
|
|
493
534
|
def hmget(key, *fields)
|
494
|
-
raise
|
535
|
+
raise ArgumentError, "wrong number of arguments for 'hmget' command" if fields.empty?
|
495
536
|
data_type_check(key, Hash)
|
537
|
+
values = []
|
496
538
|
fields.map do |field|
|
497
539
|
field = field.to_s
|
498
540
|
if @data[key]
|
@@ -570,8 +612,6 @@ class Redis
|
|
570
612
|
end
|
571
613
|
|
572
614
|
def mset(*pairs)
|
573
|
-
# Handle pairs for mapped_mset command
|
574
|
-
pairs = pairs[0] if mapped_param?(pairs)
|
575
615
|
pairs.each_slice(2) do |pair|
|
576
616
|
@data[pair[0].to_s] = pair[1].to_s
|
577
617
|
end
|
@@ -579,11 +619,9 @@ class Redis
|
|
579
619
|
end
|
580
620
|
|
581
621
|
def msetnx(*pairs)
|
582
|
-
# Handle pairs for mapped_mset command
|
583
|
-
pairs = pairs[0] if mapped_param?(pairs)
|
584
622
|
keys = []
|
585
623
|
pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
|
586
|
-
return
|
624
|
+
return if keys.any?{|key| @data.key?(key) }
|
587
625
|
mset(*pairs)
|
588
626
|
true
|
589
627
|
end
|
@@ -593,27 +631,31 @@ class Redis
|
|
593
631
|
end
|
594
632
|
|
595
633
|
def incr(key)
|
596
|
-
@data
|
634
|
+
@data[key] = (@data[key] || "0")
|
635
|
+
@data[key] = (@data[key].to_i + 1).to_s
|
597
636
|
@data[key].to_i
|
598
637
|
end
|
599
638
|
|
600
639
|
def incrby(key, by)
|
601
|
-
@data
|
640
|
+
@data[key] = (@data[key] || "0")
|
641
|
+
@data[key] = (@data[key].to_i + by.to_i).to_s
|
602
642
|
@data[key].to_i
|
603
643
|
end
|
604
644
|
|
605
645
|
def decr(key)
|
606
|
-
@data
|
646
|
+
@data[key] = (@data[key] || "0")
|
647
|
+
@data[key] = (@data[key].to_i - 1).to_s
|
607
648
|
@data[key].to_i
|
608
649
|
end
|
609
650
|
|
610
651
|
def decrby(key, by)
|
611
|
-
@data
|
652
|
+
@data[key] = (@data[key] || "0")
|
653
|
+
@data[key] = (@data[key].to_i - by.to_i).to_s
|
612
654
|
@data[key].to_i
|
613
655
|
end
|
614
656
|
|
615
657
|
def type(key)
|
616
|
-
case @data[key]
|
658
|
+
case value = @data[key]
|
617
659
|
when nil then "none"
|
618
660
|
when String then "string"
|
619
661
|
when Hash then "hash"
|
@@ -652,7 +694,6 @@ class Redis
|
|
652
694
|
data_type_check(key, ZSet)
|
653
695
|
@data[key] ||= ZSet.new
|
654
696
|
exists = @data[key].key?(value.to_s)
|
655
|
-
score = "inf" if score == "+inf"
|
656
697
|
@data[key][value.to_s] = score
|
657
698
|
!exists
|
658
699
|
end
|
@@ -672,8 +713,7 @@ class Redis
|
|
672
713
|
|
673
714
|
def zscore(key, value)
|
674
715
|
data_type_check(key, ZSet)
|
675
|
-
|
676
|
-
result.to_s if result
|
716
|
+
@data[key] && @data[key][value.to_s].to_s
|
677
717
|
end
|
678
718
|
|
679
719
|
def zcount(key, min, max)
|
@@ -686,12 +726,7 @@ class Redis
|
|
686
726
|
data_type_check(key, ZSet)
|
687
727
|
@data[key] ||= ZSet.new
|
688
728
|
@data[key][value.to_s] ||= 0
|
689
|
-
|
690
|
-
num = "inf" if num == "+inf"
|
691
|
-
@data[key][value.to_s] = num
|
692
|
-
elsif ! %w(+inf -inf).include?(@data[key][value.to_s])
|
693
|
-
@data[key][value.to_s] += num
|
694
|
-
end
|
729
|
+
@data[key][value.to_s] += num
|
695
730
|
@data[key][value.to_s].to_s
|
696
731
|
end
|
697
732
|
|
@@ -709,17 +744,11 @@ class Redis
|
|
709
744
|
data_type_check(key, ZSet)
|
710
745
|
return [] unless @data[key]
|
711
746
|
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
v1 <=> v2
|
718
|
-
end
|
719
|
-
end
|
720
|
-
# Select just the keys unless we want scores
|
721
|
-
results = results.map(&:first) unless with_scores
|
722
|
-
results[start..stop].flatten.map(&:to_s)
|
747
|
+
if with_scores
|
748
|
+
@data[key].sort_by {|_,v| v }
|
749
|
+
else
|
750
|
+
@data[key].keys.sort_by {|k| @data[key][k] }
|
751
|
+
end[start..stop].flatten.map(&:to_s)
|
723
752
|
end
|
724
753
|
|
725
754
|
def zrevrange(key, start, stop, with_scores = nil)
|
@@ -776,23 +805,31 @@ class Redis
|
|
776
805
|
range.size
|
777
806
|
end
|
778
807
|
|
779
|
-
def zinterstore(out, *
|
808
|
+
def zinterstore(out, _, *keys)
|
780
809
|
data_type_check(out, ZSet)
|
781
|
-
args_handler = SortedSetArgumentHandler.new(args)
|
782
|
-
@data[out] = SortedSetIntersectStore.new(args_handler, @data).call
|
783
|
-
@data[out].size
|
784
|
-
end
|
785
810
|
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
811
|
+
hashes = keys.map do |src|
|
812
|
+
case @data[src]
|
813
|
+
when ::Set
|
814
|
+
Hash[@data[src].zip([0] * @data[src].size)]
|
815
|
+
when Hash
|
816
|
+
@data[src]
|
817
|
+
else
|
818
|
+
{}
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
@data[out] = ZSet.new
|
823
|
+
values = hashes.inject([]) {|r, h| r.empty? ? h.keys : r & h.keys }
|
824
|
+
values.each do |value|
|
825
|
+
@data[out][value] = hashes.inject(0) {|n, h| n + h[value].to_i }
|
826
|
+
end
|
827
|
+
|
790
828
|
@data[out].size
|
791
829
|
end
|
792
830
|
|
793
831
|
def zremrangebyrank(key, start, stop)
|
794
|
-
sorted_elements = @data[key].sort { |(
|
795
|
-
start = sorted_elements.length if start > sorted_elements.length
|
832
|
+
sorted_elements = @data[key].sort { |(v_a, r_a), (v_b, r_b)| r_a <=> r_b }
|
796
833
|
elements_to_delete = sorted_elements[start..stop]
|
797
834
|
elements_to_delete.each { |elem, rank| @data[key].delete(elem) }
|
798
835
|
elements_to_delete.size
|
@@ -810,8 +847,7 @@ class Redis
|
|
810
847
|
|
811
848
|
def data_type_check(key, klass)
|
812
849
|
if @data[key] && !@data[key].is_a?(klass)
|
813
|
-
|
814
|
-
raise RuntimeError.new("ERR Operation against a key holding the wrong kind of value")
|
850
|
+
fail "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
|
815
851
|
end
|
816
852
|
end
|
817
853
|
|
@@ -827,10 +863,6 @@ class Redis
|
|
827
863
|
[offset, count]
|
828
864
|
end
|
829
865
|
end
|
830
|
-
|
831
|
-
def mapped_param? param
|
832
|
-
param.size == 1 && param[0].is_a?(Array)
|
833
|
-
end
|
834
866
|
end
|
835
867
|
end
|
836
868
|
end
|