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 CHANGED
@@ -1,4 +1,4 @@
1
- # FakeRedis [![Build Status](https://secure.travis-ci.org/guilleiguaran/fakeredis.png)](http://travis-ci.org/guilleiguaran/fakeredis)
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://guilleiguaran.github.com/fakeredis"
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>, ["~> 2.2.0"])
22
+ s.add_runtime_dependency(%q<redis>, ["~> 3.0.0"])
22
23
  s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
23
24
  end
@@ -1,3 +1,3 @@
1
1
  module FakeRedis
2
- VERSION = "0.3.3"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -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 = false
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(host, port, timeout)
27
- @connected = true
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
- meffod = command.shift
44
- if respond_to?(meffod)
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 && meffod != :multi
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
- data_type_check(key, String)
132
- @data[key].tap do
133
- set(key, value)
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 RuntimeError, "ERR wrong number of arguments for 'mget' command" if keys.empty?
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, "ERR index out of range" if index >= @data[key].size
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.to_s)
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.to_s)
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).tap do |elem|
296
- lpush(key2, elem)
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
- # mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that.
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 RuntimeError, "ERR wrong number of arguments for 'hmget' command" if fields.empty?
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 false if keys.any? {|key| @data.key?(key) }
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.merge!({ key => (@data[key].to_i + 1).to_s || "1"})
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.merge!({ key => (@data[key].to_i + by.to_i).to_s || by })
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.merge!({ key => (@data[key].to_i - 1).to_s || "-1"})
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.merge!({ key => ((@data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
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
- result = @data[key] && @data[key][value.to_s]
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
- if %w(+inf -inf).include?(num)
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
- # Sort by score, or if scores are equal, key alphanum
713
- results = @data[key].sort do |(k1, v1), (k2, v2)|
714
- if v1 == v2
715
- k1 <=> k2
716
- else
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, *args)
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
- def zunionstore(out, *args)
787
- data_type_check(out, ZSet)
788
- args_handler = SortedSetArgumentHandler.new(args)
789
- @data[out] = SortedSetUnionStore.new(args_handler, @data).call
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 { |(_, r_a), (_, r_b)| r_a <=> r_b }
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
- warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
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