redis-objects-legacy 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1666 @@
1
+
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ require 'redis/objects'
5
+
6
+ describe Redis::Value do
7
+ before do
8
+ @value = Redis::Value.new('spec/value')
9
+ @value.delete
10
+ end
11
+
12
+ it "should marshal default value" do
13
+ @value = Redis::Value.new('spec/value', :default => {:json => 'data'}, :marshal => true)
14
+ @value.value.should == {:json => 'data'}
15
+ end
16
+
17
+ it "should be able to set the default value to false" do
18
+ @value = Redis::Value.new('spec/value', :default => false, :marshal => true)
19
+ @value.value.should == false
20
+ end
21
+
22
+ it "should handle simple values" do
23
+ @value.should == nil
24
+ @value.value = 'Trevor Hoffman'
25
+ @value.should == 'Trevor Hoffman'
26
+ @value.get.should == 'Trevor Hoffman'
27
+ @value.exists?.should == true
28
+ @value.exists.should == 1
29
+ @value.del.should == 1
30
+ @value.should.be.nil
31
+ @value.exists?.should == false
32
+ @value.exists.should == 0
33
+ @value.value = 42
34
+ @value.value.should == '42'
35
+ end
36
+
37
+ it "should compress non marshaled values" do
38
+ @value = Redis::Value.new('spec/value', compress: true)
39
+ @value.value = 'Trevor Hoffman'
40
+ @value.value.should == 'Trevor Hoffman'
41
+ @value.redis.get(@value.key).should.not == 'Trevor Hoffman'
42
+ @value.value = nil
43
+ @value.value.should == nil
44
+ @value.value = ''
45
+ @value.value.should == ''
46
+ end
47
+
48
+ it "should compress marshaled values" do
49
+ @value = Redis::Value.new('spec/value', marshal: true, compress: true)
50
+ @value.value = 'Trevor Hoffman'
51
+ @value.value.should == 'Trevor Hoffman'
52
+ @value.redis.get(@value.key).should.not == Marshal.dump('Trevor Hoffman')
53
+ @value.value = nil
54
+ @value.value.should == nil
55
+ @value.value = ''
56
+ @value.value.should == ''
57
+ @value.delete
58
+ @value.value.should.be.nil
59
+ end
60
+
61
+ it "should handle complex marshaled values" do
62
+ @value.options[:marshal] = true
63
+ @value.should == nil
64
+ @value.value = {:json => 'data'}
65
+ @value.should == {:json => 'data'}
66
+
67
+ # no marshaling
68
+ @value.options[:marshal] = false
69
+ v = {:json => 'data'}
70
+ @value.value = v
71
+ @value.should == v.to_s
72
+
73
+ @value.options[:marshal] = true
74
+ @value.value = [[1,2], {:t3 => 4}]
75
+ @value.should == [[1,2], {:t3 => 4}]
76
+ @value.get.should == [[1,2], {:t3 => 4}]
77
+ @value.del.should == 1
78
+ @value.should.be.nil
79
+ @value.options[:marshal] = false
80
+ end
81
+
82
+ it "should not erroneously unmarshall a string" do
83
+ json_string = {json: 'value'}
84
+ @value = Redis::Value.new('spec/value', :marshal => true)
85
+ @value.value = json_string
86
+ @value.value.should == json_string
87
+ @value.clear
88
+
89
+ default_json_string = {json: 'default'}
90
+ @value = Redis::Value.new('spec/default', :default => default_json_string, :marshal => true)
91
+ @value.value.should == default_json_string
92
+ @value.clear
93
+
94
+ marshalled_string = Marshal.dump({json: 'marshal'})
95
+ @value = Redis::Value.new('spec/marshal', :default => marshalled_string, :marshal => true)
96
+ @value.value.should == marshalled_string
97
+ @value.delete
98
+ end
99
+
100
+ it "should support renaming values" do
101
+ @value.value = 'Peter Pan'
102
+ @value.key.should == 'spec/value'
103
+ @value.rename('spec/value2') # can't test result; switched from true to "OK"
104
+ @value.key.should == 'spec/value2'
105
+ @value.should == 'Peter Pan'
106
+ old = Redis::Value.new('spec/value')
107
+ old.should.be.nil
108
+ old.value = 'Tuff'
109
+ @value.renamenx('spec/value') # can't test result; switched from true to "OK"
110
+ @value.value.should == 'Peter Pan'
111
+ end
112
+
113
+ it "should provide a readable inspect" do
114
+ @value.value = 'monkey'
115
+ @value.inspect.should == '#<Redis::Value "monkey">'
116
+ @value.value = 1234
117
+ @value.inspect.should == '#<Redis::Value "1234">'
118
+ end
119
+
120
+ it 'should delegate unrecognized methods to the value' do
121
+ @value.value = 'monkey'
122
+ @value.to_sym.should == :monkey
123
+ end
124
+
125
+ it 'should properly pass equality operations on to the value' do
126
+ @value.value = 'monkey'
127
+ @value.should == 'monkey'
128
+ end
129
+
130
+ it 'should properly pass nil? on to the value' do
131
+ @value.delete
132
+ @value.nil?.should == true
133
+ end
134
+
135
+ it 'should equate setting the value to nil to deletion' do
136
+ @value.value = nil
137
+ @value.nil?.should == true
138
+ end
139
+
140
+ describe "with expiration" do
141
+ [:value=, :set].each do |meth|
142
+ it "#{meth} should set time to live in seconds when expiration option assigned" do
143
+ @value = Redis::Value.new('spec/value', :expiration => 10)
144
+ @value.send(meth, 'monkey')
145
+ @value.ttl.should > 0
146
+ @value.ttl.should <= 10
147
+ end
148
+
149
+ it "#{meth} should set expiration when expireat option assigned" do
150
+ @value = Redis::Value.new('spec/value', :expireat => Time.now + 10.seconds)
151
+ @value.send(meth, 'monkey')
152
+ @value.ttl.should > 0
153
+ end
154
+ end
155
+ end
156
+
157
+ after do
158
+ @value.delete
159
+ end
160
+ end
161
+
162
+ describe Redis::List do
163
+ describe "as a bounded list" do
164
+ before do
165
+ @list = Redis::List.new('spec/bounded_list',
166
+ :maxlength => 10)
167
+ 1.upto(10) do |i|
168
+ @list << i
169
+ end
170
+
171
+ # Make sure that adding < maxlength doesn't mess up.
172
+ 1.upto(10) do |i|
173
+ @list.at(i - 1).should == i.to_s
174
+ end
175
+ end
176
+
177
+ it "should push the first element out of the list" do
178
+ @list << '11'
179
+ @list.last.should == '11'
180
+ @list.first.should == '2'
181
+ @list.length.should == 10
182
+ end
183
+
184
+ it "should push the last element out of the list for unshift" do
185
+ @list.unshift('0')
186
+ @list.last.should == '9'
187
+ @list.first.should == '0'
188
+ @list.length.should == 10
189
+ end
190
+
191
+ after do
192
+ @list.clear
193
+ end
194
+ end
195
+
196
+ describe "basic operations" do
197
+ before do
198
+ @list = Redis::List.new('spec/list')
199
+ @list.clear
200
+ end
201
+
202
+ it "should handle lists of simple values" do
203
+ @list.should.be.empty
204
+ @list << 'a'
205
+ @list.should == ['a']
206
+ @list.get.should == ['a']
207
+ @list.unshift 'b'
208
+ @list.to_s.should == 'b, a'
209
+ @list.should == ['b','a']
210
+ @list.get.should == ['b','a']
211
+ @list.push 'c'
212
+ @list.should == ['b','a','c']
213
+ @list.get.should == ['b','a','c']
214
+ @list.first.should == 'b'
215
+ @list.last.should == 'c'
216
+ @list << 'd'
217
+ @list.should == ['b','a','c','d']
218
+ @list[1].should == 'a'
219
+ @list[0].should == 'b'
220
+ @list[2].should == 'c'
221
+ @list[3].should == 'd'
222
+ @list.include?('c').should.be.true
223
+ @list.include?('no').should.be.false
224
+ @list.pop.should == 'd'
225
+ @list[0].should == @list.at(0)
226
+ @list[1].should == @list.at(1)
227
+ @list[2].should == @list.at(2)
228
+ @list.should == ['b','a','c']
229
+ @list.get.should == ['b','a','c']
230
+ @list.shift.should == 'b'
231
+ @list.should == ['a','c']
232
+ @list.get.should == ['a','c']
233
+ @list << 'e' << 'f' << 'e'
234
+ @list.should == ['a','c','e','f','e']
235
+ @list.get.should == ['a','c','e','f','e']
236
+ @list.delete('e').should == 2
237
+ @list.should == ['a','c','f']
238
+ @list.get.should == ['a','c','f']
239
+ @list << 'j'
240
+ @list.should == ['a','c','f','j']
241
+ @list.push 'h'
242
+ @list.push 'i', 'j'
243
+ @list.should == ['a','c','f','j','h','i','j']
244
+ # Test against similar Ruby functionality
245
+ a = @list.values
246
+ @list[0..2].should == a[0..2]
247
+ @list[0...2].should == a[0...2]
248
+ @list.slice(0..2).should == a.slice(0..2)
249
+ @list[0, 2].should == a[0, 2]
250
+ @list.range(0, 2).should == a[0..2] # range for Redis works like .. in Ruby
251
+ @list[0, 1].should == a[0, 1]
252
+ @list.range(0, 1).should == a[0..1] # range for Redis works like .. in Ruby
253
+ @list[1, 3].should == a[1, 3]
254
+ @list.slice(1, 3).should == a.slice(1, 3)
255
+ @list[0, 0].should == []
256
+ @list[0, -1].should == a[0, -1]
257
+ @list.length.should == 7
258
+ @list.should == a
259
+ @list.get.should == a
260
+ @list.pop # lose 'j'
261
+ @list.size.should == 6
262
+
263
+ i = -1
264
+ @list.each do |st|
265
+ st.should == @list[i += 1]
266
+ end
267
+ @list.should == ['a','c','f','j','h','i']
268
+ @list.get.should == ['a','c','f','j','h','i']
269
+
270
+ @list.each_with_index do |st,i|
271
+ st.should == @list[i]
272
+ end
273
+ @list.should == ['a','c','f','j','h','i']
274
+ @list.get.should == ['a','c','f','j','h','i']
275
+
276
+ coll = @list.collect{|st| st}
277
+ coll.should == ['a','c','f','j','h','i']
278
+ @list.should == ['a','c','f','j','h','i']
279
+ @list.get.should == ['a','c','f','j','h','i']
280
+
281
+ @list << 'a'
282
+ coll = @list.select{|st| st == 'a'}
283
+ coll.should == ['a','a']
284
+ @list.should == ['a','c','f','j','h','i','a']
285
+ @list.get.should == ['a','c','f','j','h','i','a']
286
+ end
287
+
288
+ it "should support popping & shifting multiple values" do
289
+ @list.should.be.empty
290
+
291
+ @list << 'a' << 'b' << 'c'
292
+ @list.shift(2).should == ['a', 'b']
293
+ @list.shift(2).should == ['c']
294
+ @list.shift(2).should == []
295
+
296
+ @list << 'a' << 'b' << 'c'
297
+ @list.pop(2).should == ['b', 'c']
298
+ @list.pop(2).should == ['a']
299
+ @list.pop(2).should == []
300
+ end
301
+
302
+ it "should handle rpoplpush" do
303
+ list2 = Redis::List.new("spec/list2")
304
+ list2.clear
305
+
306
+ @list << "a" << "b"
307
+ result = @list.rpoplpush(list2)
308
+ result.should == "b"
309
+ @list.should == ["a"]
310
+ list2.should == ["b"]
311
+ end
312
+
313
+ it "should handle insert" do
314
+ @list << 'b' << 'd'
315
+ @list.insert(:before,'b','a')
316
+ @list.insert(:after,'b','c')
317
+ @list.insert("before",'a','z')
318
+ @list.insert("after",'d','e')
319
+ @list.should == ['z','a','b','c','d','e']
320
+ end
321
+
322
+ it "should handle insert at a specific index" do
323
+ @list << 'b' << 'd'
324
+ @list.should == ['b','d']
325
+ @list[0] = 'a'
326
+ @list.should == ['a', 'd']
327
+ @list[1] = 'b'
328
+ @list.should == ['a', 'b']
329
+ end
330
+
331
+ it "should handle lists of complex data types" do
332
+ @list.options[:marshal] = true
333
+ v1 = {:json => 'data'}
334
+ v2 = {:json2 => 'data2'}
335
+ v3 = [1,2,3]
336
+ @list << v1
337
+ @list << v2
338
+ @list.first.should == v1
339
+ @list[0] = @list[0].tap{|d| d[:json] = 'data_4'}
340
+ @list.first.should == {:json => 'data_4'}
341
+ @list.last.should == v2
342
+ @list << [1,2,3,[4,5],6]
343
+ @list.last.should == [1,2,3,[4,5],6]
344
+ @list.shift.should == {:json => 'data_4'}
345
+ @list.size.should == 2
346
+ @list.delete(v2)
347
+ @list.size.should == 1
348
+ @list.push v1, v2
349
+ @list[1].should == v1
350
+ @list.last.should == v2
351
+ @list.size.should == 3
352
+ @list.unshift v2, v3
353
+ @list.size.should == 5
354
+ @list.first.should == v3
355
+ @list.options[:marshal] = false
356
+ end
357
+
358
+ it "should support renaming lists" do
359
+ @list.should.be.empty
360
+ @list << 'a' << 'b' << 'a' << 3
361
+ @list.should == ['a','b','a','3']
362
+ @list.key.should == 'spec/list'
363
+ @list.rename('spec/list3', false) # can't test result; switched from true to "OK"
364
+ @list.key.should == 'spec/list'
365
+ @list.redis.del('spec/list3')
366
+ @list << 'a' << 'b' << 'a' << 3
367
+ @list.rename('spec/list2') # can't test result; switched from true to "OK"
368
+ @list.key.should == 'spec/list2'
369
+ @list.redis.lrange(@list.key, 0, 3).should == ['a','b','a','3']
370
+ old = Redis::List.new('spec/list')
371
+ old.should.be.empty
372
+ old << 'Tuff'
373
+ old.values.should == ['Tuff']
374
+ @list.renamenx('spec/list').should.be.false
375
+ @list.renamenx(old).should.be.false
376
+ @list.renamenx('spec/foo').should.be.true
377
+ old.values.should == ['Tuff']
378
+ @list.clear
379
+ @list.redis.del('spec/list2')
380
+ end
381
+
382
+ it "responds to #value" do
383
+ @list << 'a'
384
+ @list.value.should == @list.get
385
+ @list.value.should == ['a']
386
+ end
387
+
388
+ it "should support to_json" do
389
+ @list << 'a'
390
+ JSON.parse(@list.to_json)['value'].should == ['a']
391
+ end
392
+
393
+ it "should support as_json" do
394
+ @list << 'a'
395
+ @list.as_json['value'].should == ['a']
396
+ end
397
+
398
+ after do
399
+ @list.clear
400
+ end
401
+ end
402
+
403
+ describe 'with expiration' do
404
+ [:push, :<<, :unshift].each do |meth, args|
405
+ it "#{meth} expiration: option" do
406
+ @list = Redis::List.new('spec/list_exp', :expiration => 10)
407
+ @list.clear
408
+ @list.send(meth, 'val')
409
+ @list.ttl.should > 0
410
+ @list.ttl.should <= 10
411
+ end
412
+
413
+ it "#{meth} expireat: option" do
414
+ @list = Redis::List.new('spec/list_exp', :expireat => Time.now + 10.seconds)
415
+ @list.clear
416
+ @list.send(meth, 'val')
417
+ @list.ttl.should > 0
418
+ @list.ttl.should <= 10
419
+ end
420
+ end
421
+
422
+ it "[]= expiration: option" do
423
+ @list = Redis::List.new('spec/list_exp', :expiration => 10)
424
+ @list.clear
425
+ @list.redis.rpush(@list.key, 'hello')
426
+ @list[0] = 'world'
427
+ @list.ttl.should > 0
428
+ @list.ttl.should <= 10
429
+ end
430
+
431
+ it "[]= expireat: option" do
432
+ @list = Redis::List.new('spec/list_exp', :expireat => Time.now + 10.seconds)
433
+ @list.clear
434
+ @list.redis.rpush(@list.key, 'hello')
435
+ @list[0] = 'world'
436
+ @list.ttl.should > 0
437
+ @list.ttl.should <= 10
438
+ end
439
+
440
+ it "insert expiration: option" do
441
+ @list = Redis::List.new('spec/list_exp', :expiration => 10)
442
+ @list.clear
443
+ @list.redis.rpush(@list.key, 'hello')
444
+ @list.insert 'BEFORE', 'hello', 'world'
445
+ @list.ttl.should > 0
446
+ @list.ttl.should <= 10
447
+ end
448
+
449
+ it "insert expireat: option" do
450
+ @list = Redis::List.new('spec/list_exp', :expireat => Time.now + 10.seconds)
451
+ @list.clear
452
+ @list.redis.rpush(@list.key, 'hello')
453
+ @list.insert 'BEFORE', 'hello', 'world'
454
+ @list.ttl.should > 0
455
+ @list.ttl.should <= 10
456
+ end
457
+ end
458
+ end
459
+
460
+ describe Redis::Counter do
461
+ before do
462
+ @counter = Redis::Counter.new('spec/counter')
463
+ @counter2 = Redis::Counter.new('spec/counter')
464
+ @counter.reset
465
+ end
466
+
467
+ it "should support increment/decrement of counters" do
468
+ @counter.key.should == 'spec/counter'
469
+ @counter.incr(10)
470
+ @counter.should == 10
471
+
472
+ # math proxy ops
473
+ (@counter == 10).should.be.true
474
+ (@counter <= 10).should.be.true
475
+ (@counter < 11).should.be.true
476
+ (@counter > 9).should.be.true
477
+ (@counter >= 10).should.be.true
478
+ "#{@counter}".should == "10"
479
+
480
+ @counter.increment.should == 11
481
+ @counter.increment.should == 12
482
+ @counter2.increment.should == 13
483
+ @counter2.increment(2).should == 15
484
+ @counter.decrement.should == 14
485
+ @counter2.decrement.should == 13
486
+ @counter.decrement.should == 12
487
+ @counter2.decrement(4).should == 8
488
+ @counter.should == 8
489
+ @counter.reset.should.be.true
490
+ @counter.should == 0
491
+ @counter.reset(15).should.be.true
492
+ @counter.should == 15
493
+ @counter.getset(111).should == 15
494
+ @counter.should == 111
495
+ end
496
+
497
+ it "should support increment/decrement by float" do
498
+ @counter = Redis::Counter.new('spec/floater')
499
+ @counter.set 10.5
500
+ @counter.incrbyfloat 1
501
+ @counter.incrbyfloat 0.01
502
+ @counter.to_f.should == 11.51
503
+ @counter.set '5.0e3'
504
+ @counter.decrbyfloat -14.31
505
+ @counter.incrbyfloat 2.0e2
506
+ @counter.to_f.should == 5214.31
507
+ @counter.clear
508
+ @counter.should.be.nil
509
+ end
510
+
511
+ it "should support an atomic block" do
512
+ @counter = Redis::Counter.new("spec/block_counter")
513
+ @counter.should == 0
514
+ @counter.increment(1)
515
+
516
+ # successfully increments
517
+ @updated = @counter.increment(1) { |updated| updated == 2 ? 'yep' : nil }
518
+ @updated.should == 'yep'
519
+ @counter.should == 2
520
+
521
+ # fails to increment
522
+ @updated = @counter.increment(1) { |updated| updated == 2 ? 'yep' : nil }
523
+ @updated.should == nil
524
+ @counter.should == 2
525
+
526
+ # successfully increments by float
527
+ @updated = @counter.incrbyfloat(1.5) { |updated| updated == 3.5 ? 'yep' : nil }
528
+ @updated.should == 'yep'
529
+ @counter.should == 3.5
530
+
531
+ # fails to increment by float
532
+ @updated = @counter.incrbyfloat(2.5) { |updated| updated == 5 ? 'yep' : nil }
533
+ @updated.should == nil
534
+ @counter.should == 3.5
535
+
536
+ # fails to decrement by float
537
+ @updated = @counter.decrbyfloat(0.5) { |updated| updated == 5 ? 'yep' : nil }
538
+ @updated.should == nil
539
+ @counter.should == 3.5
540
+
541
+ # successfully decrements by float
542
+ @updated = @counter.decrbyfloat(0.5) { |updated| updated == 3 ? 'yep' : nil }
543
+ @updated.should == 'yep'
544
+ @counter.should == 3
545
+
546
+ # fails to decrement
547
+ @updated = @counter.decrement(1) { |updated| updated == 3 ? 'yep' : nil }
548
+ @updated.should == nil
549
+ @counter.should == 3
550
+
551
+ # successfully decrements
552
+ @updated = @counter.decrement(1) { |updated| updated == 2 ? 'yep' : nil }
553
+ @updated.should == 'yep'
554
+ @counter.should == 2
555
+
556
+ @counter.value = nil
557
+ @counter.should == 0
558
+ end
559
+
560
+ it "should support #to_json" do
561
+ @counter.increment
562
+ JSON.parse(@counter.to_json)['value'].should == 1
563
+ end
564
+
565
+ it "should support #as_json" do
566
+ @counter.increment
567
+ @counter.as_json['value'].should == 1
568
+ end
569
+
570
+ describe 'with expiration' do
571
+ it 'should set time to live in seconds' do
572
+ @counter = Redis::Counter.new('spec/counter', :expiration => 10)
573
+ @counter.increment
574
+ @counter.ttl.should > 0
575
+ @counter.ttl.should <= 10
576
+ end
577
+
578
+ [:increment, :incr, :incrby, :incrbyfloat,
579
+ :decrement, :decr, :decrby, :decrbyfloat, :reset].each do |meth|
580
+ describe meth do
581
+ it "expiration: option" do
582
+ @counter = Redis::Counter.new('spec/counter_exp', :expiration => 10)
583
+ @counter.send(meth)
584
+ @counter.ttl.should > 0
585
+ @counter.ttl.should <= 10
586
+ end
587
+ it "expireat: option" do
588
+ @counter = Redis::Counter.new('spec/counter_exp', :expireat => Time.now + 10.seconds)
589
+ @counter.send(meth)
590
+ @counter.ttl.should > 0
591
+ @counter.ttl.should <= 10
592
+ end
593
+ after do
594
+ @counter.reset
595
+ end
596
+ end
597
+ end
598
+
599
+ [:set, :value=].each do |meth|
600
+ describe meth do
601
+ it "expiration: option" do
602
+ @counter = Redis::Counter.new('spec/counter_exp', :expireat => Time.now + 10.seconds)
603
+ @counter.send(meth, 99)
604
+ @counter.should == 99
605
+ @counter.ttl.should > 0
606
+ @counter.ttl.should <= 10
607
+ end
608
+ it "expireat: option" do
609
+ @counter = Redis::Counter.new('spec/counter_exp', :expireat => Time.now + 10.seconds)
610
+ @counter.send(meth, 99)
611
+ @counter.should == 99
612
+ @counter.ttl.should > 0
613
+ @counter.ttl.should <= 10
614
+ end
615
+ after do
616
+ @counter.reset
617
+ end
618
+ end
619
+ end
620
+ end
621
+
622
+ after do
623
+ @counter.delete
624
+ end
625
+ end
626
+
627
+ describe Redis::Lock do
628
+ before do
629
+ REDIS_HANDLE.flushall
630
+ end
631
+
632
+ it "should ttl to the expiration" do
633
+ expiry = 15
634
+ lock = Redis::Lock.new(:test_lock, :expiration => expiry)
635
+ lock.lock do
636
+ expiration = REDIS_HANDLE.ttl("test_lock")
637
+
638
+ # The expiration stored in redis should be 15 seconds from when we started
639
+ # or a little more
640
+ expiration.should.be.close(expiration, 2.0)
641
+ end
642
+
643
+ # key should have been cleaned up
644
+ REDIS_HANDLE.get("test_lock").should.be.nil
645
+ end
646
+
647
+ it "should set value to 1 when no expiration is set" do
648
+ lock = Redis::Lock.new(:test_lock)
649
+ lock.lock do
650
+ REDIS_HANDLE.ttl('test_lock').should == 1
651
+ end
652
+
653
+ # key should have been cleaned up
654
+ REDIS_HANDLE.get("test_lock").should.be.nil
655
+ end
656
+
657
+ it "should not let blocks execute if they timeout" do
658
+ expiry = 15
659
+ lock = Redis::Lock.new(:test_lock, :expiration => expiry, :timeout => 0.1)
660
+
661
+ # create a fake lock
662
+ REDIS_HANDLE.set("test_lock", (Time.now + expiry).to_f)
663
+
664
+ gotit = false
665
+ error = nil
666
+ begin
667
+ lock.lock do
668
+ gotit = true
669
+ end
670
+ rescue => error
671
+ end
672
+
673
+ error.should.be.kind_of(Redis::Lock::LockTimeout)
674
+
675
+ # should not have the lock
676
+ gotit.should.not.be.true
677
+
678
+ # lock value should still be set
679
+ REDIS_HANDLE.get("test_lock").should.not.be.nil
680
+ end
681
+
682
+ it "should handle a timeout of 0" do
683
+ expiry = 15
684
+ lock = Redis::Lock.new(:test_lock, :timeout => 0)
685
+
686
+ # create a fake lock
687
+ REDIS_HANDLE.set("test_lock", (Time.now + expiry).to_f)
688
+
689
+ gotit = false
690
+ error = nil
691
+ begin
692
+ lock.lock do
693
+ gotit = true
694
+ end
695
+ rescue => error
696
+ end
697
+
698
+ error.should.be.kind_of(Redis::Lock::LockTimeout)
699
+
700
+ # should not have the lock
701
+ gotit.should.not.be.true
702
+
703
+ # lock value should still be set
704
+ REDIS_HANDLE.get("test_lock").should.not.be.nil
705
+ end
706
+
707
+ it "should properly keep the lock across threads" do
708
+ lock = Redis::Lock.new(:test_lock0)
709
+
710
+ t1 = Thread.new do
711
+ lock.lock do
712
+ lock.exists?.should.be.true if RUNNING_LOCALLY
713
+ REDIS_HANDLE.exists?("test_lock0").should.be.true if RUNNING_LOCALLY
714
+ sleep 1.0 # hang onto the lock across other thread
715
+ end
716
+ end
717
+
718
+ t2 = Thread.new do
719
+ # check for the lock from another thread
720
+ lock.exists?.should.be.true if RUNNING_LOCALLY
721
+ REDIS_HANDLE.exists?("test_lock0").should.be.true if RUNNING_LOCALLY
722
+ end
723
+
724
+ t1.join
725
+ t2.join
726
+
727
+ # lock value should not be set since the lock was held for more than the expiry
728
+ lock.exists?.should.be.false
729
+ REDIS_HANDLE.exists?("test_lock0").should.be.false
730
+ end
731
+
732
+ it "Redis should remove the key if lock is held past expiration" do
733
+ lock = Redis::Lock.new(:test_lock1, :expiration => 0.1)
734
+
735
+ lock.lock do
736
+ lock.exists?.should.be.true
737
+ REDIS_HANDLE.exists?("test_lock1").should.be.true
738
+ sleep 1.0 # hang onto the lock > expiry
739
+ end
740
+
741
+ # lock value should not be set since the lock was held for more than the expiry
742
+ lock.exists?.should.be.false
743
+ REDIS_HANDLE.exists?("test_lock1").should.be.false
744
+ end
745
+
746
+
747
+ it "should not manually delete a key with a 'lock' name if finished after expiration" do
748
+ lock = Redis::Lock.new(:test_lock2, :expiration => 0.1)
749
+
750
+ lock.lock do
751
+ REDIS_HANDLE.exists?("test_lock2").should.be.true
752
+ sleep 1.0 # expired, key is deleted
753
+ REDIS_HANDLE.exists?("test_lock2").should.be.false
754
+ REDIS_HANDLE.set("test_lock2", "foo") # this is no longer a lock key, name is a coincidence
755
+ end
756
+
757
+ REDIS_HANDLE.get("test_lock2").should == "foo"
758
+ end
759
+
760
+ it "should manually delete the key if finished before expiration" do
761
+ lock = Redis::Lock.new(:test_lock3, :expiration => 10.0)
762
+
763
+ lock.lock do
764
+ REDIS_HANDLE.exists?("test_lock3").should.be.true
765
+ sleep 0.1
766
+ REDIS_HANDLE.exists?("test_lock3").should.be.true
767
+ end
768
+
769
+ # should delete the key because the lock block is done, regardless of expiry
770
+ # for some strange reason, I have seen this test fail randomly, which is worrisome.
771
+ REDIS_HANDLE.exists?("test_lock3").should.be.false
772
+ end
773
+
774
+
775
+ it "should respond to #to_json" do
776
+ Redis::Lock.new(:test_lock).to_json.should.be.kind_of(String)
777
+ end
778
+
779
+ it "should respond to #as_json" do
780
+ Redis::Lock.new(:test_lock).as_json.should.be.kind_of(Hash)
781
+ end
782
+
783
+ it "should deal with old lock format" do
784
+ expiry = 15
785
+ lock = Redis::Lock.new(:test_lock, expiration: expiry, timeout: 0.1)
786
+
787
+ # create a fake lock in the past
788
+ REDIS_HANDLE.set("test_lock", (Time.now - expiry).to_f)
789
+
790
+ gotit = false
791
+ lock.lock do
792
+ gotit = true
793
+ end
794
+
795
+ # should have the lock
796
+ gotit.should.be.true
797
+
798
+ # lock value should be unset
799
+ REDIS_HANDLE.get("test_lock").should.be.nil
800
+ end
801
+
802
+ end
803
+
804
+ describe Redis::HashKey do
805
+ describe "With Marshal" do
806
+ before do
807
+ @hash = Redis::HashKey.new('test_hash', {:marshal_keys=>{'created_at'=>true}})
808
+ @hash.clear
809
+ end
810
+
811
+ it "should marshal specified keys" do
812
+ @hash['created_at'] = Time.now
813
+ @hash['created_at'].class.should == Time
814
+ end
815
+
816
+ it "should not marshal unless required" do
817
+ @hash['updated_at'] = Time.now
818
+ @hash['updated_at'].class.should == String
819
+ end
820
+
821
+ it "should marshall appropriate key with bulk set and get" do
822
+ @hash.bulk_set({'created_at'=>Time.now, 'updated_at'=>Time.now})
823
+
824
+ @hash['created_at'].class.should == Time
825
+ @hash['updated_at'].class.should == String
826
+
827
+ h = @hash.bulk_get('created_at', 'updated_at')
828
+ h['created_at'].class.should == Time
829
+ h['updated_at'].class.should == String
830
+
831
+ h = @hash.all
832
+ h['created_at'].class.should == Time
833
+ h['updated_at'].class.should == String
834
+ end
835
+ end
836
+
837
+ before do
838
+ @hash = Redis::HashKey.new('test_hash')
839
+ @hash.clear
840
+ end
841
+
842
+ it "should handle complex marshaled values" do
843
+ @hash.options[:marshal] = true
844
+ @hash['abc'].should == nil
845
+ @hash['abc'] = {:json => 'hash marshal'}
846
+ @hash['abc'].should == {:json => 'hash marshal'}
847
+
848
+ # no marshaling
849
+ @hash.options[:marshal] = false
850
+ v = {:json => 'data'}
851
+ @hash['abc'] = v
852
+ @hash['abc'].should == v.to_s
853
+
854
+ @hash.options[:marshal] = true
855
+ @hash['abc'] = [[1,2], {:t3 => 4}]
856
+ @hash['abc'].should == [[1,2], {:t3 => 4}]
857
+ @hash.fetch('abc').should == [[1,2], {:t3 => 4}]
858
+ @hash.delete('abc').should == 1
859
+ @hash.fetch('abc').should.be.nil
860
+
861
+ @hash.options[:marshal] = true
862
+ @hash.bulk_set('abc' => [[1,2], {:t3 => 4}], 'def' => [[6,8], {:t4 => 8}])
863
+ hsh = @hash.bulk_get('abc', 'def', 'foo')
864
+ hsh['abc'].should == [[1,2], {:t3 => 4}]
865
+ hsh['def'].should == [[6,8], {:t4 => 8}]
866
+ hsh['foo'].should.be.nil
867
+
868
+ hsh = @hash.all
869
+ hsh['abc'].should == [[1,2], {:t3 => 4}]
870
+ hsh['def'].should == [[6,8], {:t4 => 8}]
871
+
872
+ @hash.values.sort.should == [[[1,2], {:t3 => 4}], [[6,8], {:t4 => 8}]].sort
873
+
874
+ @hash.delete('def').should == 1
875
+ @hash.delete('abc').should == 1
876
+
877
+ @hash.options[:marshal] = false
878
+ end
879
+
880
+ it "should marshal nil correctly" do
881
+ @hash.options[:marshal] = true
882
+
883
+ @hash['test'].should.be.nil
884
+ @hash['test'] = nil
885
+ @hash['test'].should.be.nil
886
+ @hash.delete('test').should == 1
887
+ @hash['test'].should.be.nil
888
+
889
+ @hash.options[:marshal] = false
890
+ end
891
+
892
+ it "should get and set values" do
893
+ @hash['foo'] = 'bar'
894
+ @hash['foo'].should == 'bar'
895
+ end
896
+
897
+ it "should know what exists" do
898
+ @hash['foo'] = 'bar'
899
+ @hash.include?('foo').should == true
900
+ end
901
+
902
+ it "should delete values" do
903
+ @hash['abc'] = 'xyz'
904
+ @hash.delete('abc')
905
+ @hash['abc'].should == nil
906
+ end
907
+
908
+ it "should respond to each" do
909
+ @hash['foo'] = 'bar'
910
+ @hash.each do |key, val|
911
+ key.should == 'foo'
912
+ val.should == 'bar'
913
+ end
914
+ end
915
+
916
+ it "should have 1 item" do
917
+ @hash['foo'] = 'bar'
918
+ @hash.size.should == 1
919
+ end
920
+
921
+ it "should respond to each_key" do
922
+ @hash['foo'] = 'bar'
923
+ @hash.each_key do |key|
924
+ key.should == 'foo'
925
+ end
926
+ end
927
+
928
+ it "should handle increment/decrement" do
929
+ @hash['integer'] = 1
930
+ @hash.incrby('integer')
931
+ @hash.incrby('integer', 2)
932
+ @hash.get('integer').to_i.should == 4
933
+
934
+ @hash['integer'] = 9
935
+ @hash.decrby('integer')
936
+ @hash.decrby('integer', 6)
937
+ @hash.get('integer').to_i.should == 2
938
+
939
+ @hash['float'] = 12.34
940
+ @hash.decrbyfloat('float')
941
+ @hash.decrbyfloat('float', 6.3)
942
+ @hash.get('float').to_f.should == 5.04
943
+
944
+ @hash['float'] = '5.0e3'
945
+ @hash.incrbyfloat('float')
946
+ @hash.incrbyfloat('float', '1.23e3')
947
+ @hash.incrbyfloat('float', 45.3).should == 6276.3
948
+ @hash.get('float').to_f.should == 6276.3
949
+ end
950
+
951
+ it "should respond to each_value" do
952
+ @hash['foo'] = 'bar'
953
+ @hash.each_value do |val|
954
+ val.should == 'bar'
955
+ end
956
+ end
957
+
958
+ it "should respond to empty?" do
959
+ @empty = Redis::HashKey.new('test_empty_hash')
960
+ @empty.respond_to?(:empty?).should == true
961
+ end
962
+
963
+ it "should be empty after a clear" do
964
+ @hash['foo'] = 'bar'
965
+ @hash.all.should == {'foo' => 'bar'}
966
+ @hash.clear
967
+ @hash.should.be.empty
968
+ end
969
+
970
+ it "should respond to bulk_set" do
971
+ @hash.bulk_set({'abc' => 'xyz', 'bizz' => 'bazz'})
972
+ @hash['abc'].should == 'xyz'
973
+ @hash['bizz'].should == 'bazz'
974
+
975
+ @hash.bulk_set('abc' => '123', 'bang' => 'michael')
976
+ @hash['abc'].should == '123'
977
+ @hash['bang'].should == 'michael'
978
+
979
+ @hash.bulk_set(:sym1 => 'val1', :sym2 => 'val2')
980
+ @hash['sym1'].should == 'val1'
981
+ @hash['sym2'].should == 'val2'
982
+ end
983
+
984
+ it "should respond to bulk_get" do
985
+ @hash['foo'] = 'bar'
986
+ hsh = @hash.bulk_get('abc','foo')
987
+ hsh['abc'].should == nil
988
+ hsh['foo'].should == 'bar'
989
+ end
990
+
991
+ it "should return multiple items via bulk_values" do
992
+ @hash['taco'] = 42
993
+ @hash['burrito'] = 99
994
+ res = @hash.bulk_values('taco', 'burrito')
995
+ res.should == ['42', '99'] # hashes don't convert
996
+ end
997
+
998
+ it "should increment field" do
999
+ @hash.incr('counter')
1000
+ @hash.incr('counter')
1001
+ @hash['counter'].to_i.should == 2
1002
+ end
1003
+
1004
+ it "should respond to fill" do
1005
+ @hash['foo'] = 'bar'
1006
+
1007
+ @hash.fill('abc' => '123', 'bang' => 'michael')
1008
+ @hash['foo'].should == 'bar'
1009
+ @hash['abc'].should == '123'
1010
+ @hash['bang'].should == 'michael'
1011
+ @hash.keys.sort.should == ['abc', 'bang', 'foo']
1012
+ end
1013
+
1014
+ it "raises an error if a non-Hash is passed to fill" do
1015
+ lambda { @hash.fill([]) }.should.raise(ArgumentError)
1016
+ end
1017
+
1018
+ it "should fetch default values" do
1019
+ @hash['abc'] = "123"
1020
+
1021
+ value = @hash.fetch('missing_key','default_value')
1022
+ block = @hash.fetch("missing_key") {|key| "oops: #{key}" }
1023
+ no_error = @hash.fetch("abc") rescue "error"
1024
+
1025
+ no_error.should == "123"
1026
+ value.should == "default_value"
1027
+ block.should == "oops: missing_key"
1028
+ end
1029
+
1030
+ it "should respond to #value" do
1031
+ @hash['abc'] = "123"
1032
+ @hash.value.should == @hash.all
1033
+ @hash.value.should == { "abc" => "123" }
1034
+ end
1035
+
1036
+ it "should respond to #to_json" do
1037
+ @hash['abc'] = "123"
1038
+ JSON.parse(@hash.to_json)['value'].should == { "abc" => "123" }
1039
+ end
1040
+
1041
+ it "should respond to #as_json" do
1042
+ @hash['abc'] = "123"
1043
+ @hash.as_json['value'].should == { "abc" => "123" }
1044
+ end
1045
+
1046
+ it "should return empty object with bulk_get and bulk_value" do
1047
+ h = @hash.bulk_get
1048
+ h.should == {}
1049
+
1050
+ v = @hash.bulk_values
1051
+ v.should == []
1052
+ end
1053
+
1054
+ describe 'with expiration' do
1055
+ {
1056
+ :incrby => 'somekey',
1057
+ :incr => 'somekey',
1058
+ :incrbyfloat => 'somekey',
1059
+ :decrby => 'somekey',
1060
+ :decr => 'somekey',
1061
+ :decrbyfloat => 'somekey',
1062
+ :store => ['somekey', 'somevalue'],
1063
+ :[]= => ['somekey', 'somevalue'],
1064
+ :bulk_set => [{ 'somekey' => 'somevalue' }],
1065
+ :update => [{ 'somekey' => 'somevalue' }],
1066
+ :fill => [{ 'somekey' => 'somevalue' }]
1067
+ }.each do |meth, args|
1068
+ it "#{meth} expiration: option" do
1069
+ @hash = Redis::HashKey.new('spec/hash_expiration', :expiration => 10)
1070
+ @hash.clear
1071
+ @hash.send(meth, *args)
1072
+ @hash.ttl.should > 0
1073
+ @hash.ttl.should <= 10
1074
+ end
1075
+
1076
+ it "#{meth} expireat: option" do
1077
+ @hash = Redis::HashKey.new('spec/hash_expireat', :expireat => Time.now + 10.seconds)
1078
+ @hash.clear
1079
+ @hash.send(meth, *args)
1080
+ @hash.ttl.should > 0
1081
+ @hash.ttl.should <= 10
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ after do
1087
+ @hash.clear
1088
+ end
1089
+ end
1090
+
1091
+ describe Redis::Set do
1092
+ before do
1093
+ @set = Redis::Set.new('spec/set')
1094
+ @set_1 = Redis::Set.new('spec/set_1')
1095
+ @set_2 = Redis::Set.new('spec/set_2')
1096
+ @set_3 = Redis::Set.new('spec/set_3')
1097
+ @set.clear
1098
+ @set_1.clear
1099
+ @set_2.clear
1100
+ @set_3.clear
1101
+ end
1102
+
1103
+ it "should handle sets of simple values" do
1104
+ @set.should.be.empty
1105
+ @set << 'a'
1106
+ @set.randmember.should == 'a'
1107
+ @set << 'a' << 'a'
1108
+ @set.should == ['a']
1109
+ @set.to_s.should == 'a'
1110
+ @set.get.should == ['a']
1111
+ @set << 'b' << 'b'
1112
+ @set.sort.should == ['a','b']
1113
+ @set.members.sort.should == ['a','b']
1114
+ @set.members.sort.reverse.should == ['b','a'] # common question
1115
+ @set.get.sort.should == ['a','b']
1116
+ @set << 'c'
1117
+ @set.sort.should == ['a','b','c']
1118
+ @set.get.sort.should == ['a','b','c']
1119
+ @set.delete('c')
1120
+ @set.sort.should == ['a','b']
1121
+ @set.get.sort.should == ['a','b']
1122
+ @set.length.should == 2
1123
+ @set.size.should == 2
1124
+ @set.delete('a')
1125
+ @set.pop.should == 'b'
1126
+
1127
+ @set.add('a')
1128
+ @set.add('b')
1129
+
1130
+ i = 0
1131
+ @set.each do |st|
1132
+ i += 1
1133
+ end
1134
+ i.should == @set.length
1135
+
1136
+ coll = @set.sort.collect{|st| st}
1137
+ coll.should == ['a','b']
1138
+ @set.sort.should == ['a','b']
1139
+ @set.get.sort.should == ['a','b']
1140
+
1141
+ @set << 'c'
1142
+ @set.member?('c').should.be.true
1143
+ @set.include?('c').should.be.true
1144
+ @set.member?('no').should.be.false
1145
+ coll = @set.select{|st| st == 'c'}
1146
+ coll.should == ['c']
1147
+ @set.sort.should == ['a','b','c']
1148
+ @set.randmember.should.not == 'd'
1149
+ @set.delete_if{|m| m == 'c'}
1150
+ @set.sort.should == ['a','b']
1151
+
1152
+ @set << nil
1153
+ @set.include?("").should.be.true
1154
+ @set.sort.should == ['','a','b']
1155
+ end
1156
+
1157
+ it "should handle empty array adds" do
1158
+ should.not.raise(Redis::CommandError) { @set.add([]) }
1159
+ @set.should.be.empty
1160
+
1161
+ should.not.raise(Redis::CommandError) { @set << [] }
1162
+ @set.should.be.empty
1163
+ end
1164
+
1165
+ it "should handle set intersections, unions, and diffs" do
1166
+ @set_1 << 'a' << 'b' << 'c' << 'd' << 'e'
1167
+ @set_2 << 'c' << 'd' << 'e' << 'f' << 'g'
1168
+ @set_3 << 'a' << 'd' << 'g' << 'l' << 'm'
1169
+ @set_1.sort.should == %w(a b c d e)
1170
+ @set_2.sort.should == %w(c d e f g)
1171
+ @set_3.sort.should == %w(a d g l m)
1172
+ (@set_1 & @set_2).sort.should == ['c','d','e']
1173
+ @set_1.intersection(@set_2).sort.should == ['c','d','e']
1174
+ @set_1.intersection(@set_2, @set_3).sort.should == ['d']
1175
+ @set_1.intersect(@set_2).sort.should == ['c','d','e']
1176
+ @set_1.inter(@set_2, @set_3).sort.should == ['d']
1177
+ @set_1.interstore(INTERSTORE_KEY, @set_2).should == 3
1178
+ @set_1.redis.smembers(INTERSTORE_KEY).sort.should == ['c','d','e']
1179
+ @set_1.interstore(INTERSTORE_KEY, @set_2, @set_3).should == 1
1180
+ @set_1.redis.smembers(INTERSTORE_KEY).sort.should == ['d']
1181
+
1182
+ (@set_1 | @set_2).sort.should == ['a','b','c','d','e','f','g']
1183
+ (@set_1 + @set_2).sort.should == ['a','b','c','d','e','f','g']
1184
+ @set_1.union(@set_2).sort.should == ['a','b','c','d','e','f','g']
1185
+ @set_1.union(@set_2, @set_3).sort.should == ['a','b','c','d','e','f','g','l','m']
1186
+ @set_1.unionstore(UNIONSTORE_KEY, @set_2).should == 7
1187
+ @set_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g']
1188
+ @set_1.unionstore(UNIONSTORE_KEY, @set_2, @set_3).should == 9
1189
+ @set_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g','l','m']
1190
+
1191
+ (@set_1 ^ @set_2).sort.should == ["a", "b"]
1192
+ (@set_1 - @set_2).sort.should == ["a", "b"]
1193
+ (@set_2 - @set_1).sort.should == ["f", "g"]
1194
+ @set_1.difference(@set_2).sort.should == ["a", "b"]
1195
+ @set_1.diff(@set_2).sort.should == ["a", "b"]
1196
+ @set_1.difference(@set_2, @set_3).sort.should == ['b']
1197
+ @set_1.diffstore(DIFFSTORE_KEY, @set_2).should == 2
1198
+ @set_1.redis.smembers(DIFFSTORE_KEY).sort.should == ['a','b']
1199
+ @set_1.diffstore(DIFFSTORE_KEY, @set_2, @set_3).should == 1
1200
+ @set_1.redis.smembers(DIFFSTORE_KEY).sort.should == ['b']
1201
+ end
1202
+
1203
+ it "should support renaming sets" do
1204
+ @set.should.be.empty
1205
+ @set << 'a' << 'b' << 'a' << 3
1206
+ @set.sort.should == ['3','a','b']
1207
+ @set.key.should == 'spec/set'
1208
+ @set.rename('spec/set2') # can't test result; switched from true to "OK"
1209
+ @set.key.should == 'spec/set2'
1210
+ old = Redis::Set.new('spec/set')
1211
+ old.should.be.empty
1212
+ old << 'Tuff'
1213
+ @set.renamenx('spec/set').should.be.false
1214
+ @set.renamenx(old).should.be.false
1215
+ @set.renamenx('spec/foo').should.be.true
1216
+ @set.clear
1217
+ @set.redis.del('spec/set2')
1218
+ end
1219
+
1220
+ it "should handle variadic sadd operations" do
1221
+ @set.should.be.empty
1222
+ @set << 'a'
1223
+ @set.merge('b', 'c')
1224
+ @set.members.sort.should == ['a', 'b', 'c']
1225
+ @set.merge(['d','c','e'])
1226
+ @set.members.sort.should == ['a', 'b', 'c', 'd', 'e']
1227
+ end
1228
+
1229
+ it "should support sorting" do
1230
+ @set_1 << 'c' << 'b' << 'a' << 'e' << 'd'
1231
+ @set_1.sort.should == %w(a b c d e)
1232
+ @set_1.sort(SORT_ORDER).should == %w(e d c b a)
1233
+
1234
+ @set_2 << 2 << 4 << 3 << 1 << 5
1235
+ @set_2.sort.should == %w(1 2 3 4 5)
1236
+ @set_2.sort(SORT_LIMIT).should == %w(3 4)
1237
+
1238
+ @set_3 << 'm_4' << 'm_5' << 'm_1' << 'm_3' << 'm_2'
1239
+ ### incorrect interpretation of what the :by parameter means
1240
+ ### :by will look up values of keys so it would try to find a value in
1241
+ ### redis of "m_m_1" which doesn't exist at this point, it is not a way to
1242
+ ### alter the value to sort by but rather use a different value for this value
1243
+ ### in the set (Kris Fox)
1244
+ # @set_3.sort(:by => 'm_*').should == %w(m_1 m_2 m_3 m_4 m_5)
1245
+ # below passes just fine
1246
+ @set_3.sort.should == %w(m_1 m_2 m_3 m_4 m_5)
1247
+
1248
+ val1 = Redis::Value.new('spec/3/sorted')
1249
+ val2 = Redis::Value.new('spec/4/sorted')
1250
+
1251
+ val1.set('val3')
1252
+ val2.set('val4')
1253
+
1254
+ @set_2.sort(SORT_GET).should == ['val3', 'val4']
1255
+ @set_2.sort(SORT_STORE).should == 2
1256
+ @set_2.redis.type(SORT_STORE[:store]).should == 'list'
1257
+ @set_2.redis.lrange(SORT_STORE[:store], 0, -1).should == ['val3', 'val4']
1258
+
1259
+ @set_1.redis.del val1.key
1260
+ @set_1.redis.del val2.key
1261
+ @set_1.redis.del SORT_STORE[:store]
1262
+ end
1263
+
1264
+ it "should respond to #value" do
1265
+ @set_1 << 'a'
1266
+ @set_1.value.should == @set_1.members
1267
+ @set_1.value.should == ['a']
1268
+ end
1269
+
1270
+ it "should support moving between sets" do
1271
+ @set_1 << 'X' << 'Y' << 'Z'
1272
+ @set_1.move('X', @set_2)
1273
+ @set_2.should == ['X']
1274
+ end
1275
+
1276
+ it "should respond to #to_json" do
1277
+ @set_1 << 'a'
1278
+ JSON.parse(@set_1.to_json)['value'].should == ['a']
1279
+ end
1280
+
1281
+ it "should respond to #as_json" do
1282
+ @set_1 << 'a'
1283
+ @set_1.as_json['value'].should == ['a']
1284
+ end
1285
+
1286
+ describe "with expiration" do
1287
+ [:<<, :add, :merge].each do |meth|
1288
+ it "should set time to live in seconds when expiration option assigned" do
1289
+ @set = Redis::Set.new('spec/set', :expiration => 10)
1290
+ @set.send(meth, 'val')
1291
+ @set.ttl.should > 0
1292
+ @set.ttl.should <= 10
1293
+ end
1294
+
1295
+ it "should set expiration when expireat option assigned" do
1296
+ @set = Redis::Set.new('spec/set', :expireat => Time.now + 10.seconds)
1297
+ @set.send(meth, 'val')
1298
+ @set.ttl.should > 0
1299
+ @set.ttl.should <= 10
1300
+ end
1301
+ end
1302
+ end
1303
+
1304
+ after do
1305
+ @set.clear
1306
+ @set_1.clear
1307
+ @set_2.clear
1308
+ @set_3.clear
1309
+ end
1310
+ end
1311
+
1312
+ describe Redis::SortedSet do
1313
+ before do
1314
+ @set = Redis::SortedSet.new('spec/zset')
1315
+ @set_1 = Redis::SortedSet.new('spec/zset_1')
1316
+ @set_2 = Redis::SortedSet.new('spec/zset_2')
1317
+ @set_3 = Redis::SortedSet.new('spec/zset_3')
1318
+ @set_4 = Redis::SortedSet.new('spec/zset_3', :marshal => true)
1319
+ @set_5 = Redis::Set.new('spec/zset_4')
1320
+ @set.clear
1321
+ @set_1.clear
1322
+ @set_2.clear
1323
+ @set_3.clear
1324
+ @set_4.clear
1325
+ @set_5.clear
1326
+ end
1327
+
1328
+ it "should handle sorted sets of simple values" do
1329
+ @set.should.be.empty
1330
+ @set['a'] = 11
1331
+ @set['a'] = 21
1332
+ @set.add('a', 5)
1333
+ @set.score('a').should == 5
1334
+ @set['a'].should == 5
1335
+ @set['a'] = 3
1336
+ @set['b'] = 5.6
1337
+ @set['b'].should == 5.6
1338
+ @set['c'] = 4
1339
+
1340
+ a = @set.members
1341
+ @set[0,-1].should == a[0,-1]
1342
+ @set[0..2].should == a[0..2]
1343
+ @set[0...2].should == a[0...2]
1344
+ @set.slice(0..2).should == a.slice(0..2)
1345
+ @set[0, 2].should == a[0,2]
1346
+ @set.slice(0, 2).should == a.slice(0, 2)
1347
+ @set.range(0, 2).should == a[0..2]
1348
+ @set[0, 0].should == []
1349
+ @set.range(0,1,:withscores => true).should == [['a',3],['c',4]]
1350
+ @set.revrange(0,1,:withscores => true).should == [['b',5.6],['c',4]]
1351
+ @set.range(0,-1).should == a[0..-1]
1352
+ @set.revrange(0,-1).should == a[0..-1].reverse
1353
+ @set[0..1].should == a[0..1]
1354
+ @set[1].should == 0 # missing
1355
+ @set.at(1).should == 'c'
1356
+ @set.first.should == 'a'
1357
+ @set.last.should == 'b'
1358
+
1359
+ @set.members.should == ['a','c','b']
1360
+ @set.members.reverse.should == ['b','c','a']
1361
+ @set.members(:withscores => true).should == [['a',3],['c',4],['b',5.6]]
1362
+ @set.members(:with_scores => true).should == [['a',3],['c',4],['b',5.6]]
1363
+ @set.members(:withscores => true).reverse.should == [['b',5.6],['c',4],['a',3]]
1364
+ @set.members(:withscores => true).should == @set.range(0,-1,:withscores => true)
1365
+ @set.members(:withscores => true).reverse.should == @set.revrange(0,-1,:withscores => true)
1366
+
1367
+ @set['b'] = 5
1368
+ @set['b'] = 6
1369
+ @set.score('b').should == 6
1370
+ @set.score('f').should == nil
1371
+ @set.delete('c')
1372
+ @set.to_s.should == 'a, b'
1373
+ @set.should == ['a','b']
1374
+ @set.members.should == ['a','b']
1375
+ @set.member?('a').should == true
1376
+ @set.member?('b').should == true
1377
+ @set.member?('c').should == false
1378
+ @set['d'] = 0
1379
+
1380
+ @set.rangebyscore(0, 4).should == ['d','a']
1381
+ @set.rangebyscore(0, 4, :count => 1).should == ['d']
1382
+ @set.rangebyscore(0, 4, :count => 2).should == ['d','a']
1383
+ @set.rangebyscore(0, 4, :limit => 2).should == ['d','a']
1384
+
1385
+ @set.revrangebyscore(4, 0, :withscores => true).should == [['a', 3], ['d', 0]]
1386
+ @set.revrangebyscore(4, 0).should == ['a', 'd']
1387
+ @set.revrangebyscore(4, 0, :count => 2).should == ['a','d']
1388
+ @set.rank('b').should == 2
1389
+ @set.revrank('b').should == 0
1390
+
1391
+ # shouldn't report a rank for a key that doesn't exist
1392
+ @set.rank('foo').should.not == @set.rank(@set.first)
1393
+ @set.rank('foo').should == nil
1394
+
1395
+ # shouldn't report a rank for a key that doesn't exist
1396
+ @set.revrank('foo').should.not == @set.revrank(@set.first)
1397
+ @set.revrank('foo').should == nil
1398
+
1399
+ @set['f'] = 100
1400
+ @set['g'] = 110
1401
+ @set['h'] = 120
1402
+ @set['j'] = 130
1403
+ @set.incr('h', 20)
1404
+ @set.remrangebyscore(100, 120)
1405
+ @set.members.should == ['d','a','b','j','h']
1406
+
1407
+ # Redis 1.3.5
1408
+ # @set['h'] = 12
1409
+ # @set['j'] = 13
1410
+ # @set.remrangebyrank(4,-1)
1411
+ # @set.members.should == ['d','a','b']
1412
+
1413
+ @set.delete('d')
1414
+ @set['c'] = 200
1415
+ @set.members.should == ['a','b','j','h','c']
1416
+ @set.delete('c')
1417
+ @set.length.should == 4
1418
+ @set.size.should == 4
1419
+ @set.count.should == 4
1420
+
1421
+ @set.range_size(100, 120).should == 0
1422
+ @set.range_size(0, 100).should == 2
1423
+ @set.range_size('-inf', 'inf').should == 4
1424
+
1425
+ @set.delete_if{|m| m == 'b'}
1426
+ @set.size.should == 3
1427
+
1428
+ # this is destructive so must come last
1429
+ res = @set.remrangebyrank(0, 2)
1430
+ res.should == 3
1431
+ end
1432
+
1433
+ it "should handle inserting multiple values at once" do
1434
+ @set.merge({ 'a' => 1, 'b' => 2 })
1435
+ @set.merge([['a', 4], ['c', 5]])
1436
+ @set.merge({d: 0, e: 9 })
1437
+
1438
+ @set.members.should == ["d", "b", "a", "c", "e"]
1439
+
1440
+ @set[:f] = 3
1441
+ @set.members.should == ["d", "b", "f", "a", "c", "e"]
1442
+ end
1443
+
1444
+ it "should support marshaling key names" do
1445
+ @set_4.members.should == []
1446
+
1447
+ @set_4[Object] = 1.20
1448
+ @set_4[Module] = 2.30
1449
+ @set_4[nil] = 3.40
1450
+
1451
+ @set_4.incr(Object, 0.5)
1452
+ @set_4.decr(Module, 0.5)
1453
+ @set_4.incr(nil, 0.5)
1454
+
1455
+ @set_4[Object].round(1).should == 1.7
1456
+ @set_4[Module].round(1).should == 1.8
1457
+ @set_4[nil].round(1).should == 3.9
1458
+
1459
+ @set_4.members.should == [Object, Module, nil]
1460
+ end
1461
+
1462
+ it "should support renaming sorted sets" do
1463
+ @set.should.be.empty
1464
+ @set['zynga'] = 151
1465
+ @set['playfish'] = 202
1466
+ @set.members.should == ['zynga','playfish']
1467
+ @set.key.should == 'spec/zset'
1468
+ @set.rename('spec/zset2') # can't test result; switched from true to "OK"
1469
+ @set.key.should == 'spec/zset2'
1470
+ old = Redis::SortedSet.new('spec/zset')
1471
+ old.should.be.empty
1472
+ old['tuff'] = 54
1473
+ @set.renamenx('spec/zset').should.be.false
1474
+ @set.renamenx(old).should.be.false
1475
+ @set.renamenx('spec/zfoo').should.be.true
1476
+ @set.clear
1477
+ @set.redis.del('spec/zset2')
1478
+ end
1479
+
1480
+ it "should handle unions" do
1481
+ @set_1.add('a', 1)
1482
+ @set_1.add('b', 4)
1483
+ @set_1.add('c', 3)
1484
+
1485
+ @set_2.add('b', 2)
1486
+ @set_2.add('c', 1)
1487
+ @set_2.add('d', 0)
1488
+
1489
+ @set_1.union(@set_2).should == ['d', 'a', 'c', 'b']
1490
+
1491
+ @set_1.unionstore(@set.key, @set_2)
1492
+ # @set is now: [[d, 0], [a, 1], [c, 4], [b, 6]]
1493
+ @set.members.should == ['d', 'a', 'c', 'b']
1494
+
1495
+ @set_2.unionstore(@set, @set_1, :aggregate => :sum)
1496
+ # @set is now: [[d, 0], [a, 1], [c, 4], [b, 6]]
1497
+ @set.members.should == ['d', 'a', 'c', 'b']
1498
+
1499
+ @set_1.unionstore(@set, @set_2, :aggregate => :min)
1500
+ # @set is now: [[d, 0], [a, 1], [c, 1], [b, 2]]
1501
+ @set.members.should == ['d', 'a', 'c', 'b']
1502
+ @set['b'].should == 2
1503
+
1504
+ @set_1.unionstore(@set, @set_2, :aggregate => :max)
1505
+ # @set is now: [[d, 0], [a, 1], [c, 3], [b, 4]]
1506
+ @set.members.should == ['d', 'a', 'c', 'b']
1507
+ @set['b'].should == 4
1508
+
1509
+ @set_1.unionstore(@set, @set_2, :aggregate => :sum, :weights => [0, 1])
1510
+ # @set is now: [[a, 0], [d, 0], [c, 1], [b, 2]]
1511
+ @set.members.should == ['a', 'd', 'c', 'b']
1512
+ @set['b'].should == 2
1513
+
1514
+ @set_5 << ['a', 'e', 'f']
1515
+ @set_1.unionstore(@set, @set_5, :aggregate => :sum)
1516
+ # #set is now: [[e, 1], [f, 1], [a, 1], [c, 3], [b, 4]]
1517
+ @set.members.should == ['e', 'f', 'a', 'c', 'b']
1518
+ @set['e'].should == 1
1519
+ end
1520
+
1521
+ it "should handle intersections" do
1522
+ @set_1.add('a', 1)
1523
+ @set_1.add('b', 4)
1524
+ @set_1.add('c', 3)
1525
+
1526
+ @set_2.add('b', 2)
1527
+ @set_2.add('c', 1)
1528
+ @set_2.add('d', 0)
1529
+
1530
+ @set_1.intersection(@set_2).should == ['c', 'b']
1531
+
1532
+ @set_1.interstore(@set.key, @set_2)
1533
+ # @set is now: [[c, 4], [b, 6]]
1534
+ @set.members.should == ['c', 'b']
1535
+
1536
+ @set_2.interstore(@set, @set_1, :aggregate => :sum)
1537
+ # @set is now: [[c, 4], [b, 6]]
1538
+ @set.members.should == ['c', 'b']
1539
+
1540
+ @set_1.interstore(@set, @set_2, :aggregate => :min)
1541
+ # @set is now: [[c, 1], [b, 2]]
1542
+ @set.members.should == ['c', 'b']
1543
+ @set['b'].should == 2
1544
+
1545
+ @set_5 << ['a', 'e', 'b']
1546
+ @set_1.interstore(@set, @set_5, :aggregate => :max)
1547
+ # @set is now: [[a, 1], [b, 4]]
1548
+ @set.members.should == ['a', 'b']
1549
+ @set['b'].should == 4
1550
+ end
1551
+
1552
+ it 'should set time to live in seconds when expiration option assigned' do
1553
+ @set = Redis::SortedSet.new('spec/zset', :expiration => 10)
1554
+ @set['val'] = 1
1555
+ @set.ttl.should > 0
1556
+ @set.ttl.should <= 10
1557
+ end
1558
+
1559
+ it 'should set expiration when expireat option assigned' do
1560
+ @set = Redis::SortedSet.new('spec/zset', :expireat => Time.now + 10.seconds)
1561
+ @set['val'] = 1
1562
+ @set.ttl.should > 0
1563
+ @set.ttl.should <= 10
1564
+ end
1565
+
1566
+ it "should respond to #value" do
1567
+ @set['val'] = 1
1568
+ @set.value.should == @set.members
1569
+ @set.value.should == ['val']
1570
+ end
1571
+
1572
+ it "should respond to #to_json" do
1573
+ @set['val'] = 1
1574
+ JSON.parse(@set.to_json)['value'].should == ['val']
1575
+ end
1576
+
1577
+ it "should respond to #as_json" do
1578
+ @set['val'] = 1
1579
+ @set.as_json['value'].should == ['val']
1580
+ end
1581
+
1582
+ describe "with expiration" do
1583
+ [:[]=, :add, :increment, :incr, :incrby, :decrement, :decr, :decrby].each do |meth|
1584
+ it "#{meth} expiration: option" do
1585
+ @set = Redis::SortedSet.new('spec/zset_exp', :expiration => 10)
1586
+ @set.clear
1587
+ @set.send(meth, 'somekey', 12)
1588
+ @set.ttl.should > 0
1589
+ @set.ttl.should <= 10
1590
+ end
1591
+ it "#{meth} expireat: option" do
1592
+ @set = Redis::SortedSet.new('spec/zset_exp', :expireat => Time.now + 10.seconds)
1593
+ @set.clear
1594
+ @set.send(meth, 'somekey', 12)
1595
+ @set.ttl.should > 0
1596
+ @set.ttl.should <= 10
1597
+ end
1598
+ end
1599
+
1600
+ [:merge, :add_all].each do |meth|
1601
+ it "#{meth} expiration: option" do
1602
+ @set = Redis::SortedSet.new('spec/zset_exp', :expiration => 10)
1603
+ @set.clear
1604
+ @set.send(meth, 'somekey' => 12)
1605
+ @set.ttl.should > 0
1606
+ @set.ttl.should <= 10
1607
+ end
1608
+ it "#{meth} expireat: option" do
1609
+ @set = Redis::SortedSet.new('spec/zset_exp', :expireat => Time.now + 10.seconds)
1610
+ @set.clear
1611
+ @set.send(meth, 'somekey' => 12)
1612
+ @set.ttl.should > 0
1613
+ @set.ttl.should <= 10
1614
+ end
1615
+ end
1616
+
1617
+ [:unionstore, :interstore].each do |meth|
1618
+ it "#{meth} expiration: option" do
1619
+ @set = Redis::SortedSet.new('spec/zset_exp', :expiration => 10)
1620
+ @set.clear
1621
+ @set.redis.zadd(@set.key, 1, "1")
1622
+ @set.send(meth, 'sets', Redis::SortedSet.new('other'))
1623
+ @set.ttl.should > 0
1624
+ @set.ttl.should <= 10
1625
+ end
1626
+
1627
+ it "#{meth} expireat: option" do
1628
+ @set = Redis::SortedSet.new('spec/zset_exp', :expireat => Time.now + 10.seconds)
1629
+ @set.clear
1630
+ @set.redis.zadd(@set.key, 1, "1")
1631
+ @set.send(meth, 'sets', Redis::SortedSet.new('other'))
1632
+ @set.ttl.should > 0
1633
+ @set.ttl.should <= 10
1634
+ end
1635
+ end
1636
+
1637
+ it "delete expiration: option" do
1638
+ @set = Redis::SortedSet.new('spec/zset_exp', :expiration => 10)
1639
+ @set.clear
1640
+ @set.redis.zadd(@set.key, 1, "1")
1641
+ @set.redis.zadd(@set.key, 2, "2")
1642
+ @set.delete("2")
1643
+ @set.ttl.should > 0
1644
+ @set.ttl.should <= 10
1645
+ end
1646
+
1647
+ it "delete expireat: option" do
1648
+ @set = Redis::SortedSet.new('spec/zset_exp', :expireat => Time.now + 10.seconds)
1649
+ @set.clear
1650
+ @set.redis.zadd(@set.key, 1, "1")
1651
+ @set.redis.zadd(@set.key, 2, "2")
1652
+ @set.delete("2")
1653
+ @set.ttl.should > 0
1654
+ @set.ttl.should <= 10
1655
+ end
1656
+ end
1657
+
1658
+ after do
1659
+ @set.clear
1660
+ @set_1.clear
1661
+ @set_2.clear
1662
+ @set_3.clear
1663
+ @set_4.clear
1664
+ @set_5.clear
1665
+ end
1666
+ end