mock_redis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE +19 -0
  5. data/README.md +88 -0
  6. data/Rakefile +10 -0
  7. data/lib/mock_redis/assertions.rb +13 -0
  8. data/lib/mock_redis/database.rb +432 -0
  9. data/lib/mock_redis/exceptions.rb +3 -0
  10. data/lib/mock_redis/expire_wrapper.rb +25 -0
  11. data/lib/mock_redis/hash_methods.rb +102 -0
  12. data/lib/mock_redis/list_methods.rb +187 -0
  13. data/lib/mock_redis/multi_db_wrapper.rb +48 -0
  14. data/lib/mock_redis/set_methods.rb +125 -0
  15. data/lib/mock_redis/string_methods.rb +195 -0
  16. data/lib/mock_redis/transaction_wrapper.rb +75 -0
  17. data/lib/mock_redis/undef_redis_methods.rb +11 -0
  18. data/lib/mock_redis/utility_methods.rb +22 -0
  19. data/lib/mock_redis/version.rb +3 -0
  20. data/lib/mock_redis/zset.rb +80 -0
  21. data/lib/mock_redis/zset_methods.rb +210 -0
  22. data/lib/mock_redis.rb +32 -0
  23. data/mock_redis.gemspec +24 -0
  24. data/spec/cloning_spec.rb +96 -0
  25. data/spec/commands/append_spec.rb +24 -0
  26. data/spec/commands/auth_spec.rb +7 -0
  27. data/spec/commands/bgrewriteaof_spec.rb +7 -0
  28. data/spec/commands/bgsave_spec.rb +7 -0
  29. data/spec/commands/blpop_spec.rb +55 -0
  30. data/spec/commands/brpop_spec.rb +54 -0
  31. data/spec/commands/brpoplpush_spec.rb +53 -0
  32. data/spec/commands/dbsize_spec.rb +18 -0
  33. data/spec/commands/decr_spec.rb +34 -0
  34. data/spec/commands/decrby_spec.rb +34 -0
  35. data/spec/commands/del_spec.rb +20 -0
  36. data/spec/commands/echo_spec.rb +11 -0
  37. data/spec/commands/exists_spec.rb +14 -0
  38. data/spec/commands/expire_spec.rb +83 -0
  39. data/spec/commands/expireat_spec.rb +48 -0
  40. data/spec/commands/flushall_spec.rb +38 -0
  41. data/spec/commands/flushdb_spec.rb +38 -0
  42. data/spec/commands/get_spec.rb +23 -0
  43. data/spec/commands/getbit_spec.rb +34 -0
  44. data/spec/commands/getrange_spec.rb +22 -0
  45. data/spec/commands/getset_spec.rb +23 -0
  46. data/spec/commands/hdel_spec.rb +35 -0
  47. data/spec/commands/hexists_spec.rb +22 -0
  48. data/spec/commands/hget_spec.rb +23 -0
  49. data/spec/commands/hgetall_spec.rb +22 -0
  50. data/spec/commands/hincrby_spec.rb +52 -0
  51. data/spec/commands/hkeys_spec.rb +19 -0
  52. data/spec/commands/hlen_spec.rb +19 -0
  53. data/spec/commands/hmget_spec.rb +30 -0
  54. data/spec/commands/hmset_spec.rb +43 -0
  55. data/spec/commands/hset_spec.rb +23 -0
  56. data/spec/commands/hsetnx_spec.rb +39 -0
  57. data/spec/commands/hvals_spec.rb +19 -0
  58. data/spec/commands/incr_spec.rb +34 -0
  59. data/spec/commands/incrby_spec.rb +44 -0
  60. data/spec/commands/info_spec.rb +13 -0
  61. data/spec/commands/keys_spec.rb +87 -0
  62. data/spec/commands/lastsave_spec.rb +8 -0
  63. data/spec/commands/lindex_spec.rb +39 -0
  64. data/spec/commands/linsert_spec.rb +68 -0
  65. data/spec/commands/llen_spec.rb +16 -0
  66. data/spec/commands/lpop_spec.rb +34 -0
  67. data/spec/commands/lpush_spec.rb +30 -0
  68. data/spec/commands/lpushx_spec.rb +33 -0
  69. data/spec/commands/lrange_spec.rb +35 -0
  70. data/spec/commands/lrem_spec.rb +79 -0
  71. data/spec/commands/lset_spec.rb +38 -0
  72. data/spec/commands/ltrim_spec.rb +35 -0
  73. data/spec/commands/mget_spec.rb +34 -0
  74. data/spec/commands/mset_spec.rb +29 -0
  75. data/spec/commands/msetnx_spec.rb +40 -0
  76. data/spec/commands/persist_spec.rb +49 -0
  77. data/spec/commands/ping_spec.rb +7 -0
  78. data/spec/commands/quit_spec.rb +7 -0
  79. data/spec/commands/randomkey_spec.rb +20 -0
  80. data/spec/commands/rename_spec.rb +31 -0
  81. data/spec/commands/renamenx_spec.rb +36 -0
  82. data/spec/commands/rpop_spec.rb +34 -0
  83. data/spec/commands/rpoplpush_spec.rb +45 -0
  84. data/spec/commands/rpush_spec.rb +30 -0
  85. data/spec/commands/rpushx_spec.rb +33 -0
  86. data/spec/commands/sadd_spec.rb +22 -0
  87. data/spec/commands/save_spec.rb +7 -0
  88. data/spec/commands/scard_spec.rb +18 -0
  89. data/spec/commands/sdiff_spec.rb +47 -0
  90. data/spec/commands/sdiffstore_spec.rb +51 -0
  91. data/spec/commands/select_spec.rb +53 -0
  92. data/spec/commands/set_spec.rb +7 -0
  93. data/spec/commands/setbit_spec.rb +46 -0
  94. data/spec/commands/setex_spec.rb +22 -0
  95. data/spec/commands/setnx_spec.rb +25 -0
  96. data/spec/commands/setrange_spec.rb +30 -0
  97. data/spec/commands/sinter_spec.rb +41 -0
  98. data/spec/commands/sinterstore_spec.rb +46 -0
  99. data/spec/commands/sismember_spec.rb +29 -0
  100. data/spec/commands/smembers_spec.rb +18 -0
  101. data/spec/commands/smove_spec.rb +41 -0
  102. data/spec/commands/spop_spec.rb +25 -0
  103. data/spec/commands/srandmember_spec.rb +25 -0
  104. data/spec/commands/srem_spec.rb +35 -0
  105. data/spec/commands/strlen_spec.rb +19 -0
  106. data/spec/commands/sunion_spec.rb +40 -0
  107. data/spec/commands/sunionstore_spec.rb +46 -0
  108. data/spec/commands/ttl_spec.rb +36 -0
  109. data/spec/commands/type_spec.rb +36 -0
  110. data/spec/commands/unwatch_spec.rb +7 -0
  111. data/spec/commands/watch_spec.rb +7 -0
  112. data/spec/commands/zadd_spec.rb +29 -0
  113. data/spec/commands/zcard_spec.rb +19 -0
  114. data/spec/commands/zcount_spec.rb +23 -0
  115. data/spec/commands/zincrby_spec.rb +24 -0
  116. data/spec/commands/zinterstore_spec.rb +88 -0
  117. data/spec/commands/zrange_spec.rb +31 -0
  118. data/spec/commands/zrangebyscore_spec.rb +42 -0
  119. data/spec/commands/zrank_spec.rb +23 -0
  120. data/spec/commands/zrem_spec.rb +25 -0
  121. data/spec/commands/zremrangebyrank_spec.rb +22 -0
  122. data/spec/commands/zremrangebyscore_spec.rb +21 -0
  123. data/spec/commands/zrevrange_spec.rb +31 -0
  124. data/spec/commands/zrevrangebyscore_spec.rb +42 -0
  125. data/spec/commands/zrevrank_spec.rb +23 -0
  126. data/spec/commands/zscore_spec.rb +16 -0
  127. data/spec/commands/zunionstore_spec.rb +96 -0
  128. data/spec/spec_helper.rb +44 -0
  129. data/spec/support/redis_multiplexer.rb +91 -0
  130. data/spec/support/shared_examples/only_operates_on_hashes.rb +13 -0
  131. data/spec/support/shared_examples/only_operates_on_lists.rb +13 -0
  132. data/spec/support/shared_examples/only_operates_on_sets.rb +13 -0
  133. data/spec/support/shared_examples/only_operates_on_strings.rb +13 -0
  134. data/spec/support/shared_examples/only_operates_on_zsets.rb +57 -0
  135. data/spec/transactions_spec.rb +73 -0
  136. metadata +358 -0
@@ -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,48 @@
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 select(db_index)
39
+ @db_index = db_index.to_i
40
+ 'OK'
41
+ end
42
+
43
+ private
44
+ def current_db
45
+ @databases[@db_index]
46
+ end
47
+ end
48
+ 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.merge(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.merge(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.merge(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
@@ -0,0 +1,195 @@
1
+ require 'mock_redis/assertions'
2
+
3
+ class MockRedis
4
+ module StringMethods
5
+ include Assertions
6
+
7
+ def append(key, value)
8
+ assert_stringy(key)
9
+ data[key] ||= ""
10
+ data[key] << value
11
+ data[key].length
12
+ end
13
+
14
+ def decr(key)
15
+ decrby(key, 1)
16
+ end
17
+
18
+ def decrby(key, n)
19
+ incrby(key, -n)
20
+ end
21
+
22
+ def get(key)
23
+ assert_stringy(key)
24
+ data[key]
25
+ end
26
+
27
+ def getbit(key, offset)
28
+ assert_stringy(key)
29
+
30
+ offset_of_byte = offset / 8
31
+ offset_within_byte = offset % 8
32
+
33
+ # String#getbyte would be lovely, but it's not in 1.8.7.
34
+ byte = (data[key] || "").each_byte.drop(offset_of_byte).first
35
+
36
+ if byte
37
+ (byte & (2**7 >> offset_within_byte)) > 0 ? 1 : 0
38
+ else
39
+ 0
40
+ end
41
+ end
42
+
43
+ def getrange(key, start, stop)
44
+ assert_stringy(key)
45
+ (data[key] || "")[start..stop]
46
+ end
47
+
48
+ def getset(key, value)
49
+ retval = get(key)
50
+ set(key, value)
51
+ retval
52
+ end
53
+
54
+ def incr(key)
55
+ incrby(key, 1)
56
+ end
57
+
58
+ def incrby(key, n)
59
+ assert_stringy(key)
60
+ unless can_incr?(data[key])
61
+ raise RuntimeError, "ERR value is not an integer or out of range"
62
+ end
63
+
64
+ unless looks_like_integer?(n.to_s)
65
+ raise RuntimeError, "ERR value is not an integer or out of range"
66
+ end
67
+
68
+ new_value = data[key].to_i + n.to_i
69
+ data[key] = new_value.to_s
70
+ # for some reason, redis-rb doesn't return this as a string.
71
+ new_value
72
+ end
73
+
74
+ def mget(*keys)
75
+ assert_has_args(keys, 'mget')
76
+
77
+ keys.map do |key|
78
+ get(key) if stringy?(key)
79
+ end
80
+ end
81
+
82
+ def mset(*kvpairs)
83
+ assert_has_args(kvpairs, 'mset')
84
+ if kvpairs.length.odd?
85
+ raise RuntimeError, "ERR wrong number of arguments for MSET"
86
+ end
87
+
88
+ kvpairs.each_slice(2) do |(k,v)|
89
+ set(k,v)
90
+ end
91
+
92
+ "OK"
93
+ end
94
+
95
+ def msetnx(*kvpairs)
96
+ assert_has_args(kvpairs, 'msetnx')
97
+
98
+ if kvpairs.each_slice(2).any? {|(k,v)| exists(k)}
99
+ 0
100
+ else
101
+ mset(*kvpairs)
102
+ 1
103
+ end
104
+ end
105
+
106
+ def set(key, value)
107
+ data[key] = value.to_s
108
+ 'OK'
109
+ end
110
+
111
+ def setbit(key, offset, value)
112
+ assert_stringy(key, "ERR bit is not an integer or out of range")
113
+ retval = getbit(key, offset)
114
+
115
+ str = data[key] || ""
116
+
117
+ offset_of_byte = offset / 8
118
+ offset_within_byte = offset % 8
119
+
120
+ if offset_of_byte >= str.bytesize
121
+ str = zero_pad(str, offset_of_byte+1)
122
+ end
123
+
124
+ char_index = byte_index = offset_within_char = 0
125
+ str.each_char do |c|
126
+ if byte_index < offset_of_byte
127
+ char_index += 1
128
+ byte_index += c.bytesize
129
+ else
130
+ offset_within_char = byte_index - offset_of_byte
131
+ break
132
+ end
133
+ end
134
+
135
+ char = str[char_index]
136
+ char = char.chr if char.respond_to?(:chr) # ruby 1.8 vs 1.9
137
+ char_as_number = char.each_byte.reduce(0) do |a, byte|
138
+ (a << 8) + byte
139
+ end
140
+ char_as_number |=
141
+ (2**((char.bytesize * 8)-1) >>
142
+ (offset_within_char * 8 + offset_within_byte))
143
+ str[char_index] = char_as_number.chr
144
+
145
+ data[key] = str
146
+ retval
147
+ end
148
+
149
+ def setex(key, seconds, value)
150
+ set(key, value)
151
+ expire(key, seconds)
152
+ 'OK'
153
+ end
154
+
155
+ def setnx(key, value)
156
+ if exists(key)
157
+ false
158
+ else
159
+ set(key, value)
160
+ true
161
+ end
162
+ end
163
+
164
+ def setrange(key, offset, value)
165
+ assert_stringy(key)
166
+ value = value.to_s
167
+ old_value = (data[key] || "")
168
+
169
+ prefix = zero_pad(old_value[0...offset], offset)
170
+ data[key] = prefix + value + (old_value[(offset + value.length)..-1] || "")
171
+ data[key].length
172
+ end
173
+
174
+ def strlen(key)
175
+ assert_stringy(key)
176
+ (data[key] || "").bytesize
177
+ end
178
+
179
+
180
+
181
+
182
+ private
183
+ def stringy?(key)
184
+ data[key].nil? || data[key].kind_of?(String)
185
+ end
186
+
187
+ def assert_stringy(key,
188
+ message="ERR Operation against a key holding the wrong kind of value")
189
+ unless stringy?(key)
190
+ raise RuntimeError, message
191
+ end
192
+ end
193
+
194
+ end
195
+ end