ryansch-mock_redis 0.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +9 -0
  5. data/LICENSE +19 -0
  6. data/README.md +94 -0
  7. data/Rakefile +10 -0
  8. data/lib/mock_redis.rb +32 -0
  9. data/lib/mock_redis/assertions.rb +13 -0
  10. data/lib/mock_redis/database.rb +432 -0
  11. data/lib/mock_redis/exceptions.rb +3 -0
  12. data/lib/mock_redis/expire_wrapper.rb +25 -0
  13. data/lib/mock_redis/hash_methods.rb +102 -0
  14. data/lib/mock_redis/list_methods.rb +187 -0
  15. data/lib/mock_redis/multi_db_wrapper.rb +86 -0
  16. data/lib/mock_redis/set_methods.rb +125 -0
  17. data/lib/mock_redis/string_methods.rb +195 -0
  18. data/lib/mock_redis/transaction_wrapper.rb +80 -0
  19. data/lib/mock_redis/undef_redis_methods.rb +11 -0
  20. data/lib/mock_redis/utility_methods.rb +22 -0
  21. data/lib/mock_redis/version.rb +3 -0
  22. data/lib/mock_redis/zset.rb +110 -0
  23. data/lib/mock_redis/zset_methods.rb +209 -0
  24. data/mock_redis.gemspec +24 -0
  25. data/spec/cloning_spec.rb +96 -0
  26. data/spec/commands/append_spec.rb +24 -0
  27. data/spec/commands/auth_spec.rb +7 -0
  28. data/spec/commands/bgrewriteaof_spec.rb +7 -0
  29. data/spec/commands/bgsave_spec.rb +7 -0
  30. data/spec/commands/blpop_spec.rb +55 -0
  31. data/spec/commands/brpop_spec.rb +54 -0
  32. data/spec/commands/brpoplpush_spec.rb +53 -0
  33. data/spec/commands/dbsize_spec.rb +18 -0
  34. data/spec/commands/decr_spec.rb +34 -0
  35. data/spec/commands/decrby_spec.rb +34 -0
  36. data/spec/commands/del_spec.rb +20 -0
  37. data/spec/commands/echo_spec.rb +11 -0
  38. data/spec/commands/exists_spec.rb +14 -0
  39. data/spec/commands/expire_spec.rb +83 -0
  40. data/spec/commands/expireat_spec.rb +48 -0
  41. data/spec/commands/flushall_spec.rb +38 -0
  42. data/spec/commands/flushdb_spec.rb +38 -0
  43. data/spec/commands/get_spec.rb +23 -0
  44. data/spec/commands/getbit_spec.rb +34 -0
  45. data/spec/commands/getrange_spec.rb +22 -0
  46. data/spec/commands/getset_spec.rb +23 -0
  47. data/spec/commands/hdel_spec.rb +35 -0
  48. data/spec/commands/hexists_spec.rb +22 -0
  49. data/spec/commands/hget_spec.rb +23 -0
  50. data/spec/commands/hgetall_spec.rb +22 -0
  51. data/spec/commands/hincrby_spec.rb +52 -0
  52. data/spec/commands/hkeys_spec.rb +19 -0
  53. data/spec/commands/hlen_spec.rb +19 -0
  54. data/spec/commands/hmget_spec.rb +30 -0
  55. data/spec/commands/hmset_spec.rb +43 -0
  56. data/spec/commands/hset_spec.rb +23 -0
  57. data/spec/commands/hsetnx_spec.rb +39 -0
  58. data/spec/commands/hvals_spec.rb +19 -0
  59. data/spec/commands/incr_spec.rb +34 -0
  60. data/spec/commands/incrby_spec.rb +44 -0
  61. data/spec/commands/info_spec.rb +13 -0
  62. data/spec/commands/keys_spec.rb +87 -0
  63. data/spec/commands/lastsave_spec.rb +8 -0
  64. data/spec/commands/lindex_spec.rb +39 -0
  65. data/spec/commands/linsert_spec.rb +68 -0
  66. data/spec/commands/llen_spec.rb +16 -0
  67. data/spec/commands/lpop_spec.rb +34 -0
  68. data/spec/commands/lpush_spec.rb +30 -0
  69. data/spec/commands/lpushx_spec.rb +33 -0
  70. data/spec/commands/lrange_spec.rb +35 -0
  71. data/spec/commands/lrem_spec.rb +79 -0
  72. data/spec/commands/lset_spec.rb +38 -0
  73. data/spec/commands/ltrim_spec.rb +35 -0
  74. data/spec/commands/mget_spec.rb +34 -0
  75. data/spec/commands/move_spec.rb +147 -0
  76. data/spec/commands/mset_spec.rb +29 -0
  77. data/spec/commands/msetnx_spec.rb +40 -0
  78. data/spec/commands/persist_spec.rb +49 -0
  79. data/spec/commands/ping_spec.rb +7 -0
  80. data/spec/commands/quit_spec.rb +7 -0
  81. data/spec/commands/randomkey_spec.rb +20 -0
  82. data/spec/commands/rename_spec.rb +31 -0
  83. data/spec/commands/renamenx_spec.rb +36 -0
  84. data/spec/commands/rpop_spec.rb +34 -0
  85. data/spec/commands/rpoplpush_spec.rb +45 -0
  86. data/spec/commands/rpush_spec.rb +30 -0
  87. data/spec/commands/rpushx_spec.rb +33 -0
  88. data/spec/commands/sadd_spec.rb +22 -0
  89. data/spec/commands/save_spec.rb +7 -0
  90. data/spec/commands/scard_spec.rb +18 -0
  91. data/spec/commands/sdiff_spec.rb +47 -0
  92. data/spec/commands/sdiffstore_spec.rb +58 -0
  93. data/spec/commands/select_spec.rb +53 -0
  94. data/spec/commands/set_spec.rb +7 -0
  95. data/spec/commands/setbit_spec.rb +46 -0
  96. data/spec/commands/setex_spec.rb +22 -0
  97. data/spec/commands/setnx_spec.rb +25 -0
  98. data/spec/commands/setrange_spec.rb +30 -0
  99. data/spec/commands/sinter_spec.rb +41 -0
  100. data/spec/commands/sinterstore_spec.rb +53 -0
  101. data/spec/commands/sismember_spec.rb +29 -0
  102. data/spec/commands/smembers_spec.rb +18 -0
  103. data/spec/commands/smove_spec.rb +41 -0
  104. data/spec/commands/spop_spec.rb +25 -0
  105. data/spec/commands/srandmember_spec.rb +25 -0
  106. data/spec/commands/srem_spec.rb +35 -0
  107. data/spec/commands/strlen_spec.rb +19 -0
  108. data/spec/commands/sunion_spec.rb +40 -0
  109. data/spec/commands/sunionstore_spec.rb +53 -0
  110. data/spec/commands/ttl_spec.rb +36 -0
  111. data/spec/commands/type_spec.rb +36 -0
  112. data/spec/commands/unwatch_spec.rb +7 -0
  113. data/spec/commands/watch_spec.rb +7 -0
  114. data/spec/commands/zadd_spec.rb +29 -0
  115. data/spec/commands/zcard_spec.rb +19 -0
  116. data/spec/commands/zcount_spec.rb +23 -0
  117. data/spec/commands/zincrby_spec.rb +24 -0
  118. data/spec/commands/zinterstore_spec.rb +96 -0
  119. data/spec/commands/zrange_spec.rb +31 -0
  120. data/spec/commands/zrangebyscore_spec.rb +68 -0
  121. data/spec/commands/zrank_spec.rb +23 -0
  122. data/spec/commands/zrem_spec.rb +25 -0
  123. data/spec/commands/zremrangebyrank_spec.rb +22 -0
  124. data/spec/commands/zremrangebyscore_spec.rb +28 -0
  125. data/spec/commands/zrevrange_spec.rb +31 -0
  126. data/spec/commands/zrevrangebyscore_spec.rb +47 -0
  127. data/spec/commands/zrevrank_spec.rb +23 -0
  128. data/spec/commands/zscore_spec.rb +16 -0
  129. data/spec/commands/zunionstore_spec.rb +104 -0
  130. data/spec/spec_helper.rb +44 -0
  131. data/spec/support/redis_multiplexer.rb +91 -0
  132. data/spec/support/shared_examples/only_operates_on_hashes.rb +13 -0
  133. data/spec/support/shared_examples/only_operates_on_lists.rb +13 -0
  134. data/spec/support/shared_examples/only_operates_on_sets.rb +13 -0
  135. data/spec/support/shared_examples/only_operates_on_strings.rb +13 -0
  136. data/spec/support/shared_examples/only_operates_on_zsets.rb +57 -0
  137. data/spec/transactions_spec.rb +96 -0
  138. metadata +361 -0
@@ -0,0 +1,3 @@
1
+ class MockRedis
2
+ WouldBlock = Class.new(StandardError)
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'mock_redis/undef_redis_methods'
2
+
3
+ class MockRedis
4
+ class ExpireWrapper
5
+ include UndefRedisMethods
6
+
7
+ def respond_to?(method, include_private=false)
8
+ super || @db.respond_to?(method)
9
+ end
10
+
11
+ def initialize(db)
12
+ @db = db
13
+ end
14
+
15
+ def method_missing(method, *args)
16
+ @db.expire_keys
17
+ @db.send(method, *args)
18
+ end
19
+
20
+ def initialize_copy(source)
21
+ super
22
+ @db = @db.clone
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,102 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+
4
+ class MockRedis
5
+ module HashMethods
6
+ include Assertions
7
+ include UtilityMethods
8
+
9
+ def hdel(key, field)
10
+ with_hash_at(key) do |hash|
11
+ hash.delete(field) ? 1 : 0
12
+ end
13
+ end
14
+
15
+ def hexists(key, field)
16
+ with_hash_at(key) {|h| h.has_key?(field)}
17
+ end
18
+
19
+ def hget(key, field)
20
+ with_hash_at(key) {|h| h[field]}
21
+ end
22
+
23
+ def hgetall(key)
24
+ with_hash_at(key) {|h| h}
25
+ end
26
+
27
+ def hincrby(key, field, increment)
28
+ with_hash_at(key) do |hash|
29
+ unless can_incr?(data[key][field])
30
+ raise RuntimeError, "ERR hash value is not an integer"
31
+ end
32
+ unless looks_like_integer?(increment.to_s)
33
+ raise RuntimeError, "ERR value is not an integer or out of range"
34
+ end
35
+
36
+ new_value = (hash[field] || "0").to_i + increment.to_i
37
+ hash[field] = new_value.to_s
38
+ new_value
39
+ end
40
+ end
41
+
42
+ def hkeys(key)
43
+ with_hash_at(key, &:keys)
44
+ end
45
+
46
+ def hlen(key)
47
+ hkeys(key).length
48
+ end
49
+
50
+ def hmget(key, *fields)
51
+ assert_has_args(fields, 'hmget')
52
+ fields.map{|f| hget(key, f)}
53
+ end
54
+
55
+ def hmset(key, *kvpairs)
56
+ assert_has_args(kvpairs, 'hmset')
57
+ if kvpairs.length.odd?
58
+ raise RuntimeError, "ERR wrong number of arguments for HMSET"
59
+ end
60
+
61
+ kvpairs.each_slice(2) do |(k,v)|
62
+ hset(key, k, v)
63
+ end
64
+ 'OK'
65
+ end
66
+
67
+ def hset(key, field, value)
68
+ with_hash_at(key) {|h| h[field] = value.to_s}
69
+ true
70
+ end
71
+
72
+ def hsetnx(key, field, value)
73
+ if hget(key, field)
74
+ false
75
+ else
76
+ hset(key, field, value)
77
+ true
78
+ end
79
+ end
80
+
81
+ def hvals(key)
82
+ with_hash_at(key, &:values)
83
+ end
84
+
85
+ private
86
+
87
+ def with_hash_at(key, &blk)
88
+ with_thing_at(key, :assert_hashy, proc {{}}, &blk)
89
+ end
90
+
91
+ def hashy?(key)
92
+ data[key].nil? || data[key].kind_of?(Hash)
93
+ end
94
+
95
+ def assert_hashy(key)
96
+ unless hashy?(key)
97
+ raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,187 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+
4
+ class MockRedis
5
+ module ListMethods
6
+ include Assertions
7
+ include UtilityMethods
8
+
9
+ def blpop(*args)
10
+ lists, timeout = extract_timeout(args)
11
+ nonempty_list = first_nonempty_list(lists)
12
+
13
+ if nonempty_list
14
+ [nonempty_list, lpop(nonempty_list)]
15
+ elsif timeout > 0
16
+ nil
17
+ else
18
+ raise MockRedis::WouldBlock, "Can't block forever"
19
+ end
20
+ end
21
+
22
+ def brpop(*args)
23
+ lists, timeout = extract_timeout(args)
24
+ nonempty_list = first_nonempty_list(lists)
25
+
26
+ if nonempty_list
27
+ [nonempty_list, rpop(nonempty_list)]
28
+ elsif timeout > 0
29
+ nil
30
+ else
31
+ raise MockRedis::WouldBlock, "Can't block forever"
32
+ end
33
+ end
34
+
35
+ def brpoplpush(source, destination, timeout)
36
+ assert_valid_timeout(timeout)
37
+
38
+ if llen(source) > 0
39
+ rpoplpush(source, destination)
40
+ elsif timeout > 0
41
+ nil
42
+ else
43
+ raise MockRedis::WouldBlock, "Can't block forever"
44
+ end
45
+ end
46
+
47
+ def lindex(key, index)
48
+ with_list_at(key) {|l| l[index]}
49
+ end
50
+
51
+ def linsert(key, position, pivot, value)
52
+ unless %w[before after].include?(position.to_s)
53
+ raise RuntimeError, "ERR syntax error"
54
+ end
55
+
56
+ assert_listy(key)
57
+ return 0 unless data[key]
58
+
59
+ pivot_position = (0..llen(key) - 1).find do |i|
60
+ data[key][i] == pivot.to_s
61
+ end
62
+
63
+ return -1 unless pivot_position
64
+
65
+ insertion_index = if position.to_s == 'before'
66
+ pivot_position
67
+ else
68
+ pivot_position + 1
69
+ end
70
+
71
+ data[key].insert(insertion_index, value.to_s)
72
+ llen(key)
73
+ end
74
+
75
+ def llen(key)
76
+ with_list_at(key, &:length)
77
+ end
78
+
79
+ def lpop(key)
80
+ with_list_at(key, &:shift)
81
+ end
82
+
83
+ def lpush(key, value)
84
+ with_list_at(key) {|l| l.unshift(value.to_s)}
85
+ llen(key)
86
+ end
87
+
88
+ def lpushx(key, value)
89
+ assert_listy(key)
90
+ return 0 unless list_at?(key)
91
+ lpush(key, value)
92
+ end
93
+
94
+ def lrange(key, start, stop)
95
+ with_list_at(key) {|l| l[start..stop]}
96
+ end
97
+
98
+ def lrem(key, count, value)
99
+ count = count.to_i
100
+ value = value.to_s
101
+
102
+ with_list_at(key) do |list|
103
+ indices_with_value = (0..(llen(key) - 1)).find_all do |i|
104
+ list[i] == value
105
+ end
106
+
107
+ indices_to_delete = if count == 0
108
+ indices_with_value.reverse
109
+ elsif count > 0
110
+ indices_with_value.take(count).reverse
111
+ else
112
+ indices_with_value.reverse.take(-count)
113
+ end
114
+
115
+ indices_to_delete.each {|i| list.delete_at(i)}.length
116
+ end
117
+ end
118
+
119
+ def lset(key, index, value)
120
+ assert_listy(key)
121
+
122
+ unless list_at?(key)
123
+ raise RuntimeError, "ERR no such key"
124
+ end
125
+
126
+ unless (0...llen(key)).include?(index)
127
+ raise RuntimeError, "ERR index out of range"
128
+ end
129
+
130
+ data[key][index] = value.to_s
131
+ 'OK'
132
+ end
133
+
134
+ def ltrim(key, start, stop)
135
+ with_list_at(key) do |list|
136
+ list.replace(list[start..stop] || []) if list
137
+ 'OK'
138
+ end
139
+ end
140
+
141
+ def rpop(key)
142
+ with_list_at(key) {|list| list.pop if list}
143
+ end
144
+
145
+ def rpoplpush(source, destination)
146
+ value = rpop(source)
147
+ lpush(destination, value)
148
+ value
149
+ end
150
+
151
+ def rpush(key, value)
152
+ with_list_at(key) {|l| l.push(value.to_s)}
153
+ llen(key)
154
+ end
155
+
156
+ def rpushx(key, value)
157
+ assert_listy(key)
158
+ return 0 unless list_at?(key)
159
+ rpush(key, value)
160
+ end
161
+
162
+ private
163
+ def list_at?(key)
164
+ data[key] && listy?(key)
165
+ end
166
+
167
+ def with_list_at(key, &blk)
168
+ with_thing_at(key, :assert_listy, proc {[]}, &blk)
169
+ end
170
+
171
+ def listy?(key)
172
+ data[key].nil? || data[key].kind_of?(Array)
173
+ end
174
+
175
+ def assert_listy(key)
176
+ unless listy?(key)
177
+ # Not the most helpful error, but it's what redis-rb barfs up
178
+ raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
179
+ end
180
+ end
181
+
182
+ def first_nonempty_list(keys)
183
+ keys.find{|k| llen(k) > 0}
184
+ end
185
+
186
+ end
187
+ end
@@ -0,0 +1,86 @@
1
+ require 'mock_redis/undef_redis_methods'
2
+
3
+ class MockRedis
4
+ class MultiDbWrapper
5
+ include UndefRedisMethods
6
+
7
+ def initialize(db)
8
+ @db_index = 0
9
+
10
+ @prototype_db = db.clone
11
+
12
+ @databases = Hash.new {|h,k| h[k] = @prototype_db.clone}
13
+ @databases[@db_index] = db
14
+ end
15
+
16
+ def respond_to?(method, include_private=false)
17
+ super || current_db.respond_to?(method, include_private)
18
+ end
19
+
20
+ def method_missing(method, *args)
21
+ current_db.send(method, *args)
22
+ end
23
+
24
+ def initialize_copy(source)
25
+ super
26
+ @databases = @databases.clone
27
+ @databases.keys.each do |k|
28
+ @databases[k] = @databases[k].clone
29
+ end
30
+ end
31
+
32
+ # Redis commands
33
+ def flushall
34
+ @databases.values.each(&:flushdb)
35
+ 'OK'
36
+ end
37
+
38
+ def move(key, db_index)
39
+ src = current_db
40
+ dest = db(db_index)
41
+
42
+ if !src.exists(key) || dest.exists(key)
43
+ false
44
+ else
45
+ case current_db.type(key)
46
+ when 'hash'
47
+ dest.hmset(key, *(src.hgetall(key).map{|k,v| [k,v]}.flatten))
48
+ when 'list'
49
+ while value = src.rpop(key)
50
+ dest.lpush(key, value)
51
+ end
52
+ when 'set'
53
+ while value = src.spop(key)
54
+ dest.sadd(key, value)
55
+ end
56
+ when 'string'
57
+ dest.set(key, src.get(key))
58
+ when 'zset'
59
+ src.zrange(key, 0, -1, :with_scores => true).each_slice(2) do |(m,s)|
60
+ dest.zadd(key, s, m)
61
+ end
62
+ else
63
+ raise ArgumentError,
64
+ "Can't move a key of type #{current_db.type(key).inspect}"
65
+ end
66
+
67
+ src.del(key)
68
+ true
69
+ end
70
+ end
71
+
72
+ def select(db_index)
73
+ @db_index = db_index.to_i
74
+ 'OK'
75
+ end
76
+
77
+ private
78
+ def current_db
79
+ @databases[@db_index]
80
+ end
81
+
82
+ def db(index)
83
+ @databases[index]
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,125 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+
4
+ class MockRedis
5
+ module SetMethods
6
+ include Assertions
7
+ include UtilityMethods
8
+
9
+ def sadd(key, member)
10
+ with_set_at(key) {|s| !!s.add?(member.to_s)}
11
+ end
12
+
13
+ def scard(key)
14
+ with_set_at(key) {|s| s.length}
15
+ end
16
+
17
+ def sdiff(*keys)
18
+ assert_has_args(keys, 'sdiff')
19
+ with_sets_at(*keys) {|*sets| sets.reduce(&:-)}.to_a
20
+ end
21
+
22
+ def sdiffstore(destination, *keys)
23
+ assert_has_args(keys, 'sdiffstore')
24
+ with_set_at(destination) do |set|
25
+ set.replace(sdiff(*keys))
26
+ end
27
+ scard(destination)
28
+ end
29
+
30
+ def sinter(*keys)
31
+ assert_has_args(keys, 'sinter')
32
+
33
+ with_sets_at(*keys) do |*sets|
34
+ sets.reduce(&:&).to_a
35
+ end
36
+ end
37
+
38
+ def sinterstore(destination, *keys)
39
+ assert_has_args(keys, 'sinterstore')
40
+ with_set_at(destination) do |set|
41
+ set.replace(sinter(*keys))
42
+ end
43
+ scard(destination)
44
+ end
45
+
46
+ def sismember(key, member)
47
+ with_set_at(key) {|s| s.include?(member.to_s)}
48
+ end
49
+
50
+ def smembers(key)
51
+ with_set_at(key, &:to_a)
52
+ end
53
+
54
+ def smove(src, dest, member)
55
+ member = member.to_s
56
+
57
+ with_sets_at(src, dest) do |src_set, dest_set|
58
+ if src_set.delete?(member)
59
+ dest_set.add(member)
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end
65
+ end
66
+
67
+ def spop(key)
68
+ with_set_at(key) do |set|
69
+ member = set.first
70
+ set.delete(member)
71
+ member
72
+ end
73
+ end
74
+
75
+ def srandmember(key)
76
+ with_set_at(key, &:first)
77
+ end
78
+
79
+ def srem(key, member)
80
+ with_set_at(key) {|s| !!s.delete?(member.to_s)}
81
+ end
82
+
83
+ def sunion(*keys)
84
+ assert_has_args(keys, 'sunion')
85
+ with_sets_at(*keys) {|*sets| sets.reduce(&:+).to_a}
86
+ end
87
+
88
+ def sunionstore(destination, *keys)
89
+ assert_has_args(keys, 'sunionstore')
90
+ with_set_at(destination) do |dest_set|
91
+ dest_set.replace(sunion(*keys))
92
+ end
93
+ scard(destination)
94
+ end
95
+
96
+ private
97
+ def with_set_at(key, &blk)
98
+ with_thing_at(key, :assert_sety, proc {Set.new}, &blk)
99
+ end
100
+
101
+ def with_sets_at(*keys, &blk)
102
+ if keys.length == 1
103
+ with_set_at(keys.first, &blk)
104
+ else
105
+ with_set_at(keys.first) do |set|
106
+ with_sets_at(*(keys[1..-1])) do |*sets|
107
+ blk.call(*([set] + sets))
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def sety?(key)
114
+ data[key].nil? || data[key].kind_of?(Set)
115
+ end
116
+
117
+ def assert_sety(key)
118
+ unless sety?(key)
119
+ # Not the most helpful error, but it's what redis-rb barfs up
120
+ raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
121
+ end
122
+ end
123
+
124
+ end
125
+ end