redis-objects-legacy 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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