redis 0.0.1 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../../tasks/redis.tasks"
@@ -0,0 +1,524 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'redis/raketasks'
3
+ require 'logger'
4
+
5
+ class Foo
6
+ attr_accessor :bar
7
+ def initialize(bar)
8
+ @bar = bar
9
+ end
10
+
11
+ def ==(other)
12
+ @bar == other.bar
13
+ end
14
+ end
15
+
16
+ describe "redis" do
17
+ before(:all) do
18
+ result = RedisRunner.start_detached
19
+ raise("Could not start redis-server, aborting") unless result
20
+
21
+ # yea, this sucks, but it seems like sometimes we try to connect too quickly w/o it
22
+ sleep 1
23
+
24
+ # use database 15 for testing so we dont accidentally step on you real data
25
+ @r = Redis.new :db => 15
26
+ end
27
+
28
+ before(:each) do
29
+ @r['foo'] = 'bar'
30
+ end
31
+
32
+ after(:each) do
33
+ @r.keys('*').each {|k| @r.del k}
34
+ end
35
+
36
+ after(:all) do
37
+ begin
38
+ @r.quit
39
+ ensure
40
+ RedisRunner.stop
41
+ end
42
+ end
43
+
44
+ it "should be able connect without a timeout" do
45
+ lambda { Redis.new :timeout => 0 }.should_not raise_error
46
+ end
47
+
48
+ it "should be able to provide a logger" do
49
+ log = StringIO.new
50
+ r = Redis.new :db => 15, :logger => Logger.new(log)
51
+ r.ping
52
+ log.string.should include("ping")
53
+ end
54
+
55
+ it "should be able to PING" do
56
+ @r.ping.should == 'PONG'
57
+ end
58
+
59
+ it "should be able to GET a key" do
60
+ @r['foo'].should == 'bar'
61
+ end
62
+
63
+ it "should be able to SET a key" do
64
+ @r['foo'] = 'nik'
65
+ @r['foo'].should == 'nik'
66
+ end
67
+
68
+ it "should properly handle trailing newline characters" do
69
+ @r['foo'] = "bar\n"
70
+ @r['foo'].should == "bar\n"
71
+ end
72
+
73
+ it "should store and retrieve all possible characters at the beginning and the end of a string" do
74
+ (0..255).each do |char_idx|
75
+ string = "#{char_idx.chr}---#{char_idx.chr}"
76
+ @r['foo'] = string
77
+ @r['foo'].should == string
78
+ end
79
+ end
80
+
81
+ it "should be able to SET a key with an expiry" do
82
+ @r.set('foo', 'bar', 1)
83
+ @r['foo'].should == 'bar'
84
+ sleep 2
85
+ @r['foo'].should == nil
86
+ end
87
+
88
+ it "should be able to return a TTL for a key" do
89
+ @r.set('foo', 'bar', 1)
90
+ @r.ttl('foo').should == 1
91
+ end
92
+
93
+ it "should be able to SETNX" do
94
+ @r['foo'] = 'nik'
95
+ @r['foo'].should == 'nik'
96
+ @r.setnx 'foo', 'bar'
97
+ @r['foo'].should == 'nik'
98
+ end
99
+ #
100
+ it "should be able to GETSET" do
101
+ @r.getset('foo', 'baz').should == 'bar'
102
+ @r['foo'].should == 'baz'
103
+ end
104
+ #
105
+ it "should be able to INCR a key" do
106
+ @r.del('counter')
107
+ @r.incr('counter').should == 1
108
+ @r.incr('counter').should == 2
109
+ @r.incr('counter').should == 3
110
+ end
111
+ #
112
+ it "should be able to INCRBY a key" do
113
+ @r.del('counter')
114
+ @r.incrby('counter', 1).should == 1
115
+ @r.incrby('counter', 2).should == 3
116
+ @r.incrby('counter', 3).should == 6
117
+ end
118
+ #
119
+ it "should be able to DECR a key" do
120
+ @r.del('counter')
121
+ @r.incr('counter').should == 1
122
+ @r.incr('counter').should == 2
123
+ @r.incr('counter').should == 3
124
+ @r.decr('counter').should == 2
125
+ @r.decr('counter', 2).should == 0
126
+ end
127
+ #
128
+ it "should be able to RANDKEY" do
129
+ @r.randkey.should_not be_nil
130
+ end
131
+ #
132
+ it "should be able to RENAME a key" do
133
+ @r.del 'foo'
134
+ @r.del'bar'
135
+ @r['foo'] = 'hi'
136
+ @r.rename 'foo', 'bar'
137
+ @r['bar'].should == 'hi'
138
+ end
139
+ #
140
+ it "should be able to RENAMENX a key" do
141
+ @r.del 'foo'
142
+ @r.del 'bar'
143
+ @r['foo'] = 'hi'
144
+ @r['bar'] = 'ohai'
145
+ @r.renamenx 'foo', 'bar'
146
+ @r['bar'].should == 'ohai'
147
+ end
148
+ #
149
+ it "should be able to get DBSIZE of the database" do
150
+ @r.delete 'foo'
151
+ dbsize_without_foo = @r.dbsize
152
+ @r['foo'] = 0
153
+ dbsize_with_foo = @r.dbsize
154
+
155
+ dbsize_with_foo.should == dbsize_without_foo + 1
156
+ end
157
+ #
158
+ it "should be able to EXPIRE a key" do
159
+ @r['foo'] = 'bar'
160
+ @r.expire 'foo', 1
161
+ @r['foo'].should == "bar"
162
+ sleep 2
163
+ @r['foo'].should == nil
164
+ end
165
+ #
166
+ it "should be able to EXISTS" do
167
+ @r['foo'] = 'nik'
168
+ @r.exists('foo').should be_true
169
+ @r.del 'foo'
170
+ @r.exists('foo').should be_false
171
+ end
172
+ #
173
+ it "should be able to KEYS" do
174
+ @r.keys("f*").each { |key| @r.del key }
175
+ @r['f'] = 'nik'
176
+ @r['fo'] = 'nak'
177
+ @r['foo'] = 'qux'
178
+ @r.keys("f*").sort.should == ['f','fo', 'foo'].sort
179
+ end
180
+ #
181
+ it "should be able to return a random key (RANDOMKEY)" do
182
+ 3.times { @r.exists(@r.randomkey).should be_true }
183
+ end
184
+ #
185
+ it "should be able to check the TYPE of a key" do
186
+ @r['foo'] = 'nik'
187
+ @r.type('foo').should == "string"
188
+ @r.del 'foo'
189
+ @r.type('foo').should == "none"
190
+ end
191
+ #
192
+ it "should be able to push to the head of a list (LPUSH)" do
193
+ @r.lpush "list", 'hello'
194
+ @r.lpush "list", 42
195
+ @r.type('list').should == "list"
196
+ @r.llen('list').should == 2
197
+ @r.lpop('list').should == '42'
198
+ end
199
+ #
200
+ it "should be able to push to the tail of a list (RPUSH)" do
201
+ @r.rpush "list", 'hello'
202
+ @r.type('list').should == "list"
203
+ @r.llen('list').should == 1
204
+ end
205
+ #
206
+ it "should be able to pop the tail of a list (RPOP)" do
207
+ @r.rpush "list", 'hello'
208
+ @r.rpush"list", 'goodbye'
209
+ @r.type('list').should == "list"
210
+ @r.llen('list').should == 2
211
+ @r.rpop('list').should == 'goodbye'
212
+ end
213
+ #
214
+ it "should be able to pop the head of a list (LPOP)" do
215
+ @r.rpush "list", 'hello'
216
+ @r.rpush "list", 'goodbye'
217
+ @r.type('list').should == "list"
218
+ @r.llen('list').should == 2
219
+ @r.lpop('list').should == 'hello'
220
+ end
221
+ #
222
+ it "should be able to get the length of a list (LLEN)" do
223
+ @r.rpush "list", 'hello'
224
+ @r.rpush "list", 'goodbye'
225
+ @r.type('list').should == "list"
226
+ @r.llen('list').should == 2
227
+ end
228
+ #
229
+ it "should be able to get a range of values from a list (LRANGE)" do
230
+ @r.rpush "list", 'hello'
231
+ @r.rpush "list", 'goodbye'
232
+ @r.rpush "list", '1'
233
+ @r.rpush "list", '2'
234
+ @r.rpush "list", '3'
235
+ @r.type('list').should == "list"
236
+ @r.llen('list').should == 5
237
+ @r.lrange('list', 2, -1).should == ['1', '2', '3']
238
+ end
239
+ #
240
+ it "should be able to trim a list (LTRIM)" do
241
+ @r.rpush "list", 'hello'
242
+ @r.rpush "list", 'goodbye'
243
+ @r.rpush "list", '1'
244
+ @r.rpush "list", '2'
245
+ @r.rpush "list", '3'
246
+ @r.type('list').should == "list"
247
+ @r.llen('list').should == 5
248
+ @r.ltrim 'list', 0, 1
249
+ @r.llen('list').should == 2
250
+ @r.lrange('list', 0, -1).should == ['hello', 'goodbye']
251
+ end
252
+ #
253
+ it "should be able to get a value by indexing into a list (LINDEX)" do
254
+ @r.rpush "list", 'hello'
255
+ @r.rpush "list", 'goodbye'
256
+ @r.type('list').should == "list"
257
+ @r.llen('list').should == 2
258
+ @r.lindex('list', 1).should == 'goodbye'
259
+ end
260
+ #
261
+ it "should be able to set a value by indexing into a list (LSET)" do
262
+ @r.rpush "list", 'hello'
263
+ @r.rpush "list", 'hello'
264
+ @r.type('list').should == "list"
265
+ @r.llen('list').should == 2
266
+ @r.lset('list', 1, 'goodbye').should == 'OK'
267
+ @r.lindex('list', 1).should == 'goodbye'
268
+ end
269
+ #
270
+ it "should be able to remove values from a list (LREM)" do
271
+ @r.rpush "list", 'hello'
272
+ @r.rpush "list", 'goodbye'
273
+ @r.type('list').should == "list"
274
+ @r.llen('list').should == 2
275
+ @r.lrem('list', 1, 'hello').should == 1
276
+ @r.lrange('list', 0, -1).should == ['goodbye']
277
+ end
278
+ #
279
+ it "should be able add members to a set (SADD)" do
280
+ @r.sadd "set", 'key1'
281
+ @r.sadd "set", 'key2'
282
+ @r.type('set').should == "set"
283
+ @r.scard('set').should == 2
284
+ @r.smembers('set').sort.should == ['key1', 'key2'].sort
285
+ end
286
+ #
287
+ it "should be able delete members to a set (SREM)" do
288
+ @r.sadd "set", 'key1'
289
+ @r.sadd "set", 'key2'
290
+ @r.type('set').should == "set"
291
+ @r.scard('set').should == 2
292
+ @r.smembers('set').sort.should == ['key1', 'key2'].sort
293
+ @r.srem('set', 'key1')
294
+ @r.scard('set').should == 1
295
+ @r.smembers('set').should == ['key2']
296
+ end
297
+ #
298
+ it "should be able to return and remove random key from set (SPOP)" do
299
+ @r.sadd "set_pop", "key1"
300
+ @r.sadd "set_pop", "key2"
301
+ @r.spop("set_pop").should_not be_nil
302
+ @r.scard("set_pop").should == 1
303
+ end
304
+ #
305
+ it "should be able to return random key without delete the key from a set (SRANDMEMBER)" do
306
+ @r.sadd "set_srandmember", "key1"
307
+ @r.sadd "set_srandmember", "key2"
308
+ @r.srandmember("set_srandmember").should_not be_nil
309
+ @r.scard("set_srandmember").should == 2
310
+ end
311
+ #
312
+ it "should be able count the members of a set (SCARD)" do
313
+ @r.sadd "set", 'key1'
314
+ @r.sadd "set", 'key2'
315
+ @r.type('set').should == "set"
316
+ @r.scard('set').should == 2
317
+ end
318
+ #
319
+ it "should be able test for set membership (SISMEMBER)" do
320
+ @r.sadd "set", 'key1'
321
+ @r.sadd "set", 'key2'
322
+ @r.type('set').should == "set"
323
+ @r.scard('set').should == 2
324
+ @r.sismember('set', 'key1').should be_true
325
+ @r.sismember('set', 'key2').should be_true
326
+ @r.sismember('set', 'notthere').should be_false
327
+ end
328
+ #
329
+ it "should be able to do set intersection (SINTER)" do
330
+ @r.sadd "set", 'key1'
331
+ @r.sadd "set", 'key2'
332
+ @r.sadd "set2", 'key2'
333
+ @r.sinter('set', 'set2').should == ['key2']
334
+ end
335
+ #
336
+ it "should be able to do set intersection and store the results in a key (SINTERSTORE)" do
337
+ @r.sadd "set", 'key1'
338
+ @r.sadd "set", 'key2'
339
+ @r.sadd "set2", 'key2'
340
+ @r.sinterstore('newone', 'set', 'set2').should == 1
341
+ @r.smembers('newone').should == ['key2']
342
+ end
343
+ #
344
+ it "should be able to do set union (SUNION)" do
345
+ @r.sadd "set", 'key1'
346
+ @r.sadd "set", 'key2'
347
+ @r.sadd "set2", 'key2'
348
+ @r.sadd "set2", 'key3'
349
+ @r.sunion('set', 'set2').sort.should == ['key1','key2','key3'].sort
350
+ end
351
+ #
352
+ it "should be able to do set union and store the results in a key (SUNIONSTORE)" do
353
+ @r.sadd "set", 'key1'
354
+ @r.sadd "set", 'key2'
355
+ @r.sadd "set2", 'key2'
356
+ @r.sadd "set2", 'key3'
357
+ @r.sunionstore('newone', 'set', 'set2').should == 3
358
+ @r.smembers('newone').sort.should == ['key1','key2','key3'].sort
359
+ end
360
+ #
361
+ it "should be able to do set difference (SDIFF)" do
362
+ @r.sadd "set", 'a'
363
+ @r.sadd "set", 'b'
364
+ @r.sadd "set2", 'b'
365
+ @r.sadd "set2", 'c'
366
+ @r.sdiff('set', 'set2').should == ['a']
367
+ end
368
+ #
369
+ it "should be able to do set difference and store the results in a key (SDIFFSTORE)" do
370
+ @r.sadd "set", 'a'
371
+ @r.sadd "set", 'b'
372
+ @r.sadd "set2", 'b'
373
+ @r.sadd "set2", 'c'
374
+ @r.sdiffstore('newone', 'set', 'set2')
375
+ @r.smembers('newone').should == ['a']
376
+ end
377
+ #
378
+ it "should be able move elements from one set to another (SMOVE)" do
379
+ @r.sadd 'set1', 'a'
380
+ @r.sadd 'set1', 'b'
381
+ @r.sadd 'set2', 'x'
382
+ @r.smove('set1', 'set2', 'a').should be_true
383
+ @r.sismember('set2', 'a').should be_true
384
+ @r.delete('set1')
385
+ end
386
+ #
387
+ it "should be able to do crazy SORT queries" do
388
+ # The 'Dogs' is capitialized on purpose
389
+ @r['dog_1'] = 'louie'
390
+ @r.rpush 'Dogs', 1
391
+ @r['dog_2'] = 'lucy'
392
+ @r.rpush 'Dogs', 2
393
+ @r['dog_3'] = 'max'
394
+ @r.rpush 'Dogs', 3
395
+ @r['dog_4'] = 'taj'
396
+ @r.rpush 'Dogs', 4
397
+ @r.sort('Dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
398
+ @r.sort('Dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
399
+ end
400
+
401
+ it "should be able to handle array of :get using SORT" do
402
+ @r['dog:1:name'] = 'louie'
403
+ @r['dog:1:breed'] = 'mutt'
404
+ @r.rpush 'dogs', 1
405
+ @r['dog:2:name'] = 'lucy'
406
+ @r['dog:2:breed'] = 'poodle'
407
+ @r.rpush 'dogs', 2
408
+ @r['dog:3:name'] = 'max'
409
+ @r['dog:3:breed'] = 'hound'
410
+ @r.rpush 'dogs', 3
411
+ @r['dog:4:name'] = 'taj'
412
+ @r['dog:4:breed'] = 'terrier'
413
+ @r.rpush 'dogs', 4
414
+ @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt']
415
+ @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier']
416
+ end
417
+ #
418
+ it "should provide info (INFO)" do
419
+ [:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
420
+ @r.info.keys.should include(x)
421
+ end
422
+ end
423
+ #
424
+ it "should be able to flush the database (FLUSHDB)" do
425
+ @r['key1'] = 'keyone'
426
+ @r['key2'] = 'keytwo'
427
+ @r.keys('*').sort.should == ['foo', 'key1', 'key2'].sort #foo from before
428
+ @r.flushdb
429
+ @r.keys('*').should == []
430
+ end
431
+ #
432
+ it "should raise exception when manually try to change the database" do
433
+ lambda { @r.select(0) }.should raise_error
434
+ end
435
+ #
436
+ it "should be able to provide the last save time (LASTSAVE)" do
437
+ savetime = @r.lastsave
438
+ Time.at(savetime).class.should == Time
439
+ Time.at(savetime).should <= Time.now
440
+ end
441
+
442
+ it "should be able to MGET keys" do
443
+ @r['foo'] = 1000
444
+ @r['bar'] = 2000
445
+ @r.mget('foo', 'bar').should == ['1000', '2000']
446
+ @r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
447
+ end
448
+
449
+ it "should be able to mapped MGET keys" do
450
+ @r['foo'] = 1000
451
+ @r['bar'] = 2000
452
+ @r.mapped_mget('foo', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
453
+ @r.mapped_mget('foo', 'baz', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
454
+ end
455
+
456
+ it "should be able to MSET values" do
457
+ @r.mset :key1 => "value1", :key2 => "value2"
458
+ @r['key1'].should == "value1"
459
+ @r['key2'].should == "value2"
460
+ end
461
+
462
+ it "should be able to MSETNX values" do
463
+ @r.msetnx :keynx1 => "valuenx1", :keynx2 => "valuenx2"
464
+ @r.mget('keynx1', 'keynx2').should == ["valuenx1", "valuenx2"]
465
+
466
+ @r["keynx1"] = "value1"
467
+ @r["keynx2"] = "value2"
468
+ @r.msetnx :keynx1 => "valuenx1", :keynx2 => "valuenx2"
469
+ @r.mget('keynx1', 'keynx2').should == ["value1", "value2"]
470
+ end
471
+
472
+ it "should bgsave" do
473
+ @r.bgsave.should == 'OK'
474
+ end
475
+
476
+ it "should be able to ECHO" do
477
+ @r.echo("message in a bottle\n").should == "message in a bottle\n"
478
+ end
479
+
480
+ it "should raise error when invoke MONITOR" do
481
+ lambda { @r.monitor }.should raise_error
482
+ end
483
+
484
+ it "should raise error when invoke SYNC" do
485
+ lambda { @r.sync }.should raise_error
486
+ end
487
+
488
+ it "should handle multiple servers" do
489
+ require 'dist_redis'
490
+ @r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15)
491
+
492
+ 100.times do |idx|
493
+ @r[idx] = "foo#{idx}"
494
+ end
495
+
496
+ 100.times do |idx|
497
+ @r[idx].should == "foo#{idx}"
498
+ end
499
+ end
500
+
501
+ it "should be able to pipeline writes" do
502
+ @r.pipelined do |pipeline|
503
+ pipeline.lpush 'list', "hello"
504
+ pipeline.lpush 'list', 42
505
+ end
506
+
507
+ @r.type('list').should == "list"
508
+ @r.llen('list').should == 2
509
+ @r.lpop('list').should == '42'
510
+ end
511
+
512
+ it "should do nothing when pipelining no commands" do
513
+ @r.pipelined do |pipeline|
514
+ end
515
+ end
516
+
517
+ it "should AUTH when connecting with a password" do
518
+ r = Redis.new(:password => 'secret')
519
+ r.stub!(:connect_to)
520
+ r.should_receive(:call_command).with(['auth', 'secret'])
521
+ r.connect_to_server
522
+ end
523
+
524
+ end