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 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