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,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -c -d
@@ -0,0 +1,20 @@
1
+ ### 0.2.0
2
+ * Support passing a block to `#multi`.
3
+
4
+ ### 0.1.2
5
+ * Fixes for 1.9.2; no functionality changes.
6
+
7
+ ### 0.1.1
8
+ * Fix handling of -inf, +inf, and exclusive endpoints (e.g. "(3") in
9
+ zrangebyscore, zrevrangebyscore, and zremrangebyscore. ("Fix" here
10
+ means "write", as it's something that was completely forgotten the
11
+ first time around.)
12
+
13
+ ### 0.1.0
14
+ * Support `move(key, db)` to move keys between databases.
15
+
16
+ ### 0.0.2
17
+ * Fix gem homepage.
18
+
19
+ ### 0.0.1
20
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mock_redis.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "ruby-debug", :platforms => :mri_18
8
+ gem "ruby-debug19", :platforms => :mri_19
9
+ end
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Philotic, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,94 @@
1
+ # MockRedis
2
+
3
+ A mock redis gem.
4
+
5
+ MockRedis provides the same interface as redis-rb, but it stores its
6
+ data in memory instead of talking to a Redis server. It is intended
7
+ for use in tests.
8
+
9
+ ## Getting Started
10
+
11
+ It's as easy as `require 'mock_redis'; mr = MockRedis.new`. Then you can
12
+ call the same methods on it as you can call on a real `Redis` object.
13
+
14
+ For example:
15
+
16
+ >> require 'mock_redis'
17
+ >> mr = MockRedis.new
18
+ >> mr.set('some key', 'some value')
19
+ => "OK"
20
+ >> mr.get('some key')
21
+ => "some value"
22
+
23
+ ## Supported Features
24
+
25
+ mock_redis supports most of the methods that redis-rb does. Examples
26
+ of supported methods:
27
+
28
+ * String methods: `get`, `set`, `append`, `incr`, etc.
29
+ * List methods: `lpush`, `lpop`, `lrange`, `rpoplpush`, etc.
30
+ * Set methods: `sadd`, `sinter`, `sismember`, etc.
31
+ * Hash methods: `hset`, `hget`, `hgetall`, `hmget`, etc.
32
+ * Sorted set methods: `zadd`, `zrank`, `zunionstore`, etc.
33
+ * Expirations: `expire`, `ttl`, etc.
34
+ * Transactions: `multi`, `exec`, `discard`
35
+
36
+ ## Mostly-Supported Commands
37
+
38
+ A MockRedis object can't do everything that a real Redis client can
39
+ since it's an in-memory object confined to a single process. MockRedis
40
+ makes every attempt to be Redis-compatible, but there are some
41
+ necessary exceptions.
42
+
43
+ * Blocking list commands (`#blpop`, `#brpop`, and `#brpoplpush`) work
44
+ as expected if there is data for them to retrieve. If you use one of
45
+ these commands with a nonzero timeout and there is no data for it to
46
+ retrieve, then the command returns immediately. However, if you ask
47
+ one of these commands for data with a 0 timeout (means "wait
48
+ forever") and there is no data available, then a
49
+ `MockRedis::WouldBlock` exception is raised. It's not what a real
50
+ Redis client would do, but it beats hanging your test run forever.
51
+
52
+ * `#info` just returns canned values; they don't update over time.
53
+
54
+ ## Unsupported Commands
55
+
56
+ Some stuff, we just can't do with a single Ruby object in a single
57
+ Ruby process.
58
+
59
+ * Debugging commands (`#debug('object', key)` and
60
+ `#debug('segfault')`) aren't available.
61
+
62
+ * `#object` isn't available since we don't have any Redis internals to
63
+ poke at.
64
+
65
+ * `#monitor` isn't available; there's no place for requests to come
66
+ from, so there's nothing to receive.
67
+
68
+ * Pubsub commands (`#psubscribe`, `#publish`, `#punsubscribe`) aren't
69
+ available.
70
+
71
+ * `#slowlog` isn't available.
72
+
73
+ ## Remaining Work
74
+
75
+ There are some things we want to have in here, but that we just
76
+ haven't gotten to doing yet. If you're interested in helping out,
77
+ please submit a pull request with your (tested!) implementation.
78
+
79
+ * `#config(:get|:set|:resetstat)` isn't done. They can just return
80
+ canned values.
81
+
82
+ * `#sort` isn't done. We just haven't gotten to it yet. The input
83
+ validation will be a pain in the butt, but other than that, it
84
+ shouldn't be too bad.
85
+
86
+ ## Running the Tests
87
+
88
+ If you want to work on this, you'll probably want to run the
89
+ tests. (Just kidding! There's no probably about it.) These tests were
90
+ written with Redis 2.2.11 running on localhost without any passwords
91
+ required. If you're using a different version of Redis, you may see
92
+ failures due to error message text being different. If you're running
93
+ a really old version of Redis, you'll definitely see failures due to
94
+ stuff that doesn't work!
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = %w(-fs --color)
7
+ t.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,32 @@
1
+ require 'set'
2
+
3
+ require 'mock_redis/assertions'
4
+ require 'mock_redis/database'
5
+ require 'mock_redis/expire_wrapper'
6
+ require 'mock_redis/multi_db_wrapper'
7
+ require 'mock_redis/transaction_wrapper'
8
+ require 'mock_redis/undef_redis_methods'
9
+
10
+ class MockRedis
11
+ include UndefRedisMethods
12
+
13
+ def initialize(*args)
14
+ @db = TransactionWrapper.new(
15
+ ExpireWrapper.new(
16
+ MultiDbWrapper.new(
17
+ Database.new(*args))))
18
+ end
19
+
20
+ def respond_to?(method, include_private=false)
21
+ super || @db.respond_to?(method, include_private)
22
+ end
23
+
24
+ def method_missing(method, *args, &block)
25
+ @db.send(method, *args, &block)
26
+ end
27
+
28
+ def initialize_copy(source)
29
+ super
30
+ @db = @db.clone
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ class MockRedis
2
+ module Assertions
3
+ private
4
+
5
+ def assert_has_args(args, command)
6
+ unless args.any?
7
+ raise RuntimeError,
8
+ "ERR wrong number of arguments for '#{command}' command"
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,432 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/exceptions'
3
+ require 'mock_redis/hash_methods'
4
+ require 'mock_redis/list_methods'
5
+ require 'mock_redis/set_methods'
6
+ require 'mock_redis/string_methods'
7
+ require 'mock_redis/zset_methods'
8
+
9
+ class MockRedis
10
+ class Database
11
+ include HashMethods
12
+ include ListMethods
13
+ include SetMethods
14
+ include StringMethods
15
+ include ZsetMethods
16
+
17
+ attr_reader :data, :expire_times
18
+
19
+ def initialize(*args)
20
+ @data = {}
21
+ @expire_times = []
22
+ end
23
+
24
+ def initialize_copy(source)
25
+ @data = @data.clone
26
+ @data.keys.each {|k| @data[k] = @data[k].clone}
27
+ @expire_times = @expire_times.map{|x| x.clone}
28
+ end
29
+
30
+ # Redis commands go below this line and above 'private'
31
+
32
+ def auth(_) 'OK' end
33
+
34
+ def bgrewriteaof() "Background append only file rewriting started" end
35
+
36
+ def bgsave() "Background saving started" end
37
+
38
+ def dbsize
39
+ data.keys.length
40
+ end
41
+
42
+ def del(*keys)
43
+ keys.
44
+ find_all{|key| data[key]}.
45
+ each {|k| persist(k)}.
46
+ each {|k| data.delete(k)}.
47
+ length
48
+ end
49
+
50
+ def echo(msg)
51
+ msg.to_s
52
+ end
53
+
54
+ def expire(key, seconds)
55
+ expireat(key, Time.now.to_i + seconds.to_i)
56
+ end
57
+
58
+ def expireat(key, timestamp)
59
+ unless looks_like_integer?(timestamp.to_s)
60
+ raise RuntimeError, "ERR value is not an integer or out of range"
61
+ end
62
+
63
+ if exists(key)
64
+ set_expiration(key, Time.at(timestamp.to_i))
65
+ true
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ def exists(key)
72
+ data.has_key?(key)
73
+ end
74
+
75
+ def flushdb
76
+ data.keys.each {|k| del(k)}
77
+ 'OK'
78
+ end
79
+
80
+ def info
81
+ astats = [
82
+ ["2", "2699"],
83
+ ["6", "1"],
84
+ ["7", "1"],
85
+ ["8", "17197"],
86
+ ["9", "109875"],
87
+ ["10", "94348"],
88
+ ["11", "32580"],
89
+ ["12", "52347"],
90
+ ["13", "86475"],
91
+ ["14", "58175"],
92
+ ["15", "53408"],
93
+ ["16", "876949"],
94
+ ["17", "71157"],
95
+ ["18", "5104"],
96
+ ["19", "2705"],
97
+ ["20", "2903"],
98
+ ["21", "1024"],
99
+ ["22", "2546"],
100
+ ["23", "952"],
101
+ ["24", "186080"],
102
+ ["25", "611"],
103
+ ["26", "40936"],
104
+ ["27", "960"],
105
+ ["28", "1323"],
106
+ ["29", "14216"],
107
+ ["30", "52412"],
108
+ ["31", "21130"],
109
+ ["32", "47959"],
110
+ ["33", "6891"],
111
+ ["34", "9712"],
112
+ ["35", "3366"],
113
+ ["36", "5737"],
114
+ ["37", "11274"],
115
+ ["38", "8057"],
116
+ ["39", "2957"],
117
+ ["40", "51200"],
118
+ ["42", "8220"],
119
+ ["43", "8278"],
120
+ ["44", "6539"],
121
+ ["45", "764"],
122
+ ["47", "1018"],
123
+ ["48", "19250"],
124
+ ["49", "713"],
125
+ ["51", "51"],
126
+ ["53", "2"],
127
+ ["55", "3922"],
128
+ ["56", "153"],
129
+ ["57", "614"],
130
+ ["58", "1"],
131
+ ["59", "1775"],
132
+ ["61", "32865"],
133
+ ["63", "2530"],
134
+ ["64", "565"],
135
+ ["65", "1322"],
136
+ ["67", "1572"],
137
+ ["69", "1421"],
138
+ ["71", "1220"],
139
+ ["72", "241"],
140
+ ["73", "5432"],
141
+ ["74", "1122"],
142
+ ["75", "2555"],
143
+ ["77", "1539"],
144
+ ["78", "612"],
145
+ ["79", "902"],
146
+ ["81", "1678"],
147
+ ["83", "51"],
148
+ ["84", "612"],
149
+ ["85", "706"],
150
+ ["87", "410"],
151
+ ["88", "5435"],
152
+ ["89", "813"],
153
+ ["90", "612"],
154
+ ["93", "153"],
155
+ ["94", "612"],
156
+ ["96", "159"],
157
+ ["97", "306"],
158
+ ["99", "153"],
159
+ ["101", "456"],
160
+ ["103", "741"],
161
+ ["105", "447"],
162
+ ["107", "754"],
163
+ ["109", "414"],
164
+ ["111", "475"],
165
+ ["113", "757"],
166
+ ["115", "287"],
167
+ ["117", "420"],
168
+ ["118", "765"],
169
+ ["119", "642"],
170
+ ["120", "159"],
171
+ ["121", "926"],
172
+ ["122", "612"],
173
+ ["123", "251"],
174
+ ["125", "390"],
175
+ ["127", "354"],
176
+ ["128", "617"],
177
+ ["129", "528"],
178
+ ["131", "298"],
179
+ ["132", "612"],
180
+ ["133", "809"],
181
+ ["135", "244"],
182
+ ["136", "306"],
183
+ ["137", "504"],
184
+ ["139", "201"],
185
+ ["141", "1124"],
186
+ ["143", "139"],
187
+ ["144", "159"],
188
+ ["145", "1322"],
189
+ ["147", "410"],
190
+ ["149", "253"],
191
+ ["151", "304"],
192
+ ["153", "312"],
193
+ ["155", "249"],
194
+ ["157", "306"],
195
+ ["159", "348"],
196
+ ["161", "255"],
197
+ ["163", "458"],
198
+ ["165", "5"],
199
+ ["167", "306"],
200
+ ["168", "47"],
201
+ ["169", "214"],
202
+ ["171", "250"],
203
+ ["173", "5"],
204
+ ["177", "10"],
205
+ ["179", "158"],
206
+ ["181", "5"],
207
+ ["183", "10"],
208
+ ["185", "51"],
209
+ ["187", "49"],
210
+ ["191", "5"],
211
+ ["192", "47"],
212
+ ["193", "51"],
213
+ ["197", "112"],
214
+ ["199", "5"],
215
+ ["201", "5"],
216
+ ["203", "5"],
217
+ ["209", "5"],
218
+ ["213", "51"],
219
+ ["217", "102"],
220
+ ["225", "357"],
221
+ ["229", "51"],
222
+ ["233", "204"],
223
+ ["237", "51"],
224
+ ["239", "1"],
225
+ ["247", "46"],
226
+ ["255", "102"],
227
+ [">=256", "6201"],
228
+ ]
229
+
230
+ {
231
+ "allocation_stats" => astats.map {|(a,b)| "#{a}=#{b}"}.join(','),
232
+ "aof_enabled" => "0",
233
+ "arch_bits" => "64",
234
+ "bgrewriteaof_in_progress" => "0",
235
+ "bgsave_in_progress" => "0",
236
+ "blocked_clients" => "0",
237
+ "changes_since_last_save" => "0",
238
+ "client_biggest_input_buf" => "0",
239
+ "client_longest_output_list" => "0",
240
+ "connected_clients" => "1",
241
+ "connected_slaves" => "0",
242
+ "db0" => "keys=8,expires=0",
243
+ "evicted_keys" => "0",
244
+ "expired_keys" => "0",
245
+ "hash_max_zipmap_entries" => "512",
246
+ "hash_max_zipmap_value" => "64",
247
+ "keyspace_hits" => "62645",
248
+ "keyspace_misses" => "29757",
249
+ "last_save_time" => "1310596333",
250
+ "loading" => "0",
251
+ "lru_clock" => "1036434",
252
+ "mem_fragmentation_ratio" => "2.04",
253
+ "multiplexing_api" => "kqueue",
254
+ "process_id" => "14508",
255
+ "pubsub_channels" => "0",
256
+ "pubsub_patterns" => "0",
257
+ "redis_git_dirty" => "0",
258
+ "redis_git_sha1" => "00000000",
259
+ "redis_version" => "2.2.11",
260
+ "role" => "master",
261
+ "total_commands_processed" => "196800",
262
+ "total_connections_received" => "4359",
263
+ "uptime_in_days" => "0",
264
+ "uptime_in_seconds" => "84215",
265
+ "use_tcmalloc" => "0",
266
+ "used_cpu_sys" => "5.54",
267
+ "used_cpu_sys_childrens" => "0.00",
268
+ "used_cpu_user" => "7.65",
269
+ "used_cpu_user_childrens" => "0.02",
270
+ "used_memory" => "931456",
271
+ "used_memory_human" => "909.62K",
272
+ "used_memory_rss" => "1904640",
273
+ "vm_enabled" => "0",
274
+ }
275
+ end
276
+
277
+ def keys(format)
278
+ data.keys.grep(redis_pattern_to_ruby_regex(format))
279
+ end
280
+
281
+ def lastsave
282
+ Time.now.to_i
283
+ end
284
+
285
+ def persist(key)
286
+ if exists(key) && has_expiration?(key)
287
+ remove_expiration(key)
288
+ true
289
+ else
290
+ false
291
+ end
292
+ end
293
+
294
+ def ping
295
+ 'PONG'
296
+ end
297
+
298
+ def quit
299
+ 'OK'
300
+ end
301
+
302
+ def randomkey
303
+ data.keys[rand(data.length)]
304
+ end
305
+
306
+ def rename(key, newkey)
307
+ if key == newkey
308
+ raise RuntimeError, "ERR source and destination objects are the same"
309
+ end
310
+ data[newkey] = data.delete(key)
311
+ 'OK'
312
+ end
313
+
314
+ def renamenx(key, newkey)
315
+ if key == newkey
316
+ raise RuntimeError, "ERR source and destination objects are the same"
317
+ end
318
+ if exists(newkey)
319
+ false
320
+ else
321
+ rename(key, newkey)
322
+ true
323
+ end
324
+ end
325
+
326
+ def save
327
+ 'OK'
328
+ end
329
+
330
+ def ttl(key)
331
+ if has_expiration?(key)
332
+ (expiration(key) - Time.now).to_i
333
+ else
334
+ -1
335
+ end
336
+ end
337
+
338
+ def type(key)
339
+ if !exists(key)
340
+ 'none'
341
+ elsif hashy?(key)
342
+ 'hash'
343
+ elsif stringy?(key)
344
+ 'string'
345
+ elsif listy?(key)
346
+ 'list'
347
+ elsif sety?(key)
348
+ 'set'
349
+ elsif zsety?(key)
350
+ 'zset'
351
+ else
352
+ raise ArgumentError, "Not sure how #{data[key].inspect} got in here"
353
+ end
354
+ end
355
+
356
+ private
357
+
358
+ def assert_valid_timeout(timeout)
359
+ if !looks_like_integer?(timeout.to_s)
360
+ raise RuntimeError, "ERR timeout is not an integer or out of range"
361
+ elsif timeout < 0
362
+ raise RuntimeError, "ERR timeout is negative"
363
+ end
364
+ timeout
365
+ end
366
+
367
+ def can_incr?(value)
368
+ value.nil? || looks_like_integer?(value)
369
+ end
370
+
371
+ def extract_timeout(arglist)
372
+ timeout = assert_valid_timeout(arglist.last)
373
+ [arglist[0..-2], arglist.last]
374
+ end
375
+
376
+ def expiration(key)
377
+ expire_times.find {|(_,k)| k == key}.first
378
+ end
379
+
380
+ def has_expiration?(key)
381
+ expire_times.any? {|(_,k)| k == key}
382
+ end
383
+
384
+ def looks_like_integer?(str)
385
+ str =~ /^-?\d+$/
386
+ end
387
+
388
+ def redis_pattern_to_ruby_regex(pattern)
389
+ Regexp.new(
390
+ "^#{pattern}$".
391
+ gsub(/([^\\])\?/, "\\1.").
392
+ gsub(/([^\\])\*/, "\\1.+"))
393
+ end
394
+
395
+ def remove_expiration(key)
396
+ expire_times.delete_if do |(t, k)|
397
+ key == k
398
+ end
399
+ end
400
+
401
+ def set_expiration(key, time)
402
+ remove_expiration(key)
403
+
404
+ expire_times << [time, key]
405
+ expire_times.sort! do |a, b|
406
+ a.first <=> b.first
407
+ end
408
+ end
409
+
410
+ def zero_pad(string, desired_length)
411
+ padding = "\000" * [(desired_length - string.length), 0].max
412
+ string + padding
413
+ end
414
+
415
+ public
416
+ # This method isn't private, but it also isn't a Redis command, so
417
+ # it doesn't belong up above with all the Redis commands.
418
+ def expire_keys
419
+ now = Time.now
420
+
421
+ to_delete = expire_times.take_while do |(time, key)|
422
+ time <= now
423
+ end
424
+
425
+ to_delete.each do |(time, key)|
426
+ del(key)
427
+ end
428
+
429
+ expire_times.slice!(0, to_delete.length)
430
+ end
431
+ end
432
+ end