fakeredis 0.3.3 → 0.4.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.
- 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 [](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
|