spec/redis_mutex_spec.rb in redis-mutex-1.3.5 vs spec/redis_mutex_spec.rb in redis-mutex-2.0.0
- old
+ new
@@ -1,51 +1,81 @@
-require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+require 'spec_helper'
+SHORT_MUTEX_OPTIONS = { :block => 0.1, :sleep => 0.02 }
+
+class C
+ include Redis::Mutex::Macro
+ auto_mutex :run_singularly, :block => 0, :after_failure => lambda {|id| return "failure: #{id}" }
+
+ def run_singularly(id)
+ sleep 0.1
+ return "success: #{id}"
+ end
+end
+
describe Redis::Mutex do
before do
Redis::Classy.flushdb
- @short_mutex_options = { :block => 0.1, :sleep => 0.02 }
end
- after do
+ after :all do
Redis::Classy.flushdb
+ Redis::Classy.quit
end
- it "should set the value to the expiration" do
+ it 'locks the universe' do
+ mutex1 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+ mutex1.lock.should be_true
+
+ mutex2 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+ mutex2.lock.should be_false
+ end
+
+ it 'fails to lock when the lock is taken' do
+ mutex1 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+
+ mutex2 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+ mutex2.lock.should be_true # mutex2 beats us to it
+
+ mutex1.lock.should be_false # fail
+ end
+
+ it 'unlocks only once' do
+ mutex = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+ mutex.lock.should be_true
+
+ mutex.unlock.should be_true # successfully released the lock
+ mutex.unlock.should be_false # the lock no longer exists
+ end
+
+ it 'prevents accidental unlock from outside' do
+ mutex1 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+ mutex1.lock.should be_true
+
+ mutex2 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
+ mutex2.unlock.should be_false
+ end
+
+ it 'sets expiration' do
start = Time.now
expires_in = 10
mutex = Redis::Mutex.new(:test_lock, :expire => expires_in)
- mutex.lock do
+ mutex.with_lock do
mutex.get.to_f.should be_within(1.0).of((start + expires_in).to_f)
end
- # key should have been cleaned up
- mutex.get.should be_nil
+ mutex.get.should be_nil # key should have been cleaned up
end
- it "should get a lock when existing lock is expired" do
- mutex = Redis::Mutex.new(:test_lock)
- # locked in the far past
+ it 'overwrites a lock when existing lock is expired' do
+ # stale lock from the far past
Redis::Mutex.set(:test_lock, Time.now - 60)
+ mutex = Redis::Mutex.new(:test_lock)
mutex.lock.should be_true
- mutex.get.should_not be_nil
- mutex.unlock
- mutex.get.should be_nil
end
- it "should not get a lock when existing lock is still effective" do
- mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
-
- # someone beats us to it
- mutex2 = Redis::Mutex.new(:test_lock, @short_mutex_options)
- mutex2.lock
-
- mutex.lock.should be_false # should not have the lock
- mutex.get.should_not be_nil # lock value should still be set
- end
-
- it "should not remove the key if lock is held past expiration" do
+ it 'fails to unlock the key if it took too long past expiration' do
mutex = Redis::Mutex.new(:test_lock, :expire => 0.1, :block => 0)
mutex.lock.should be_true
sleep 0.2 # lock expired
# someone overwrites the expired lock
@@ -54,41 +84,70 @@
mutex.unlock
mutex.get.should_not be_nil # lock should still be there
end
- it "should ensure unlock when something goes wrong in the block" do
+ it 'ensures unlocking when something goes wrong in the block' do
mutex = Redis::Mutex.new(:test_lock)
begin
- mutex.lock do
+ mutex.with_lock do
raise "Something went wrong!"
end
- rescue
- mutex.locking.should be_false
+ rescue RuntimeError
+ mutex.get.should be_nil
end
end
- it "should reset locking state on reuse" do
- mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
+ it 'resets locking state on reuse' do
+ mutex = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
mutex.lock.should be_true
mutex.lock.should be_false
end
- describe Redis::Mutex::Macro do
- it "should add auto_mutex" do
+ it 'returns value of block' do
+ Redis::Mutex.with_lock(:test_lock) { :test_result }.should == :test_result
+ end
- class C
- include Redis::Mutex::Macro
- auto_mutex :run_singularly, :block => 0, :after_failure => lambda {|id| return "failure: #{id}" }
+ it 'requires block for #with_lock' do
+ expect { Redis::Mutex.with_lock(:test_lock) }.to raise_error(LocalJumpError) #=> no block given (yield)
+ end
- def run_singularly(id)
- sleep 0.1
- return "success: #{id}"
- end
- end
+ it 'raises LockError if lock not obtained' do
+ expect { Redis::Mutex.lock!(:test_lock, SHORT_MUTEX_OPTIONS) }.to_not raise_error
+ expect { Redis::Mutex.lock!(:test_lock, SHORT_MUTEX_OPTIONS) }.to raise_error(Redis::Mutex::LockError)
+ end
+ it 'raises UnlockError if lock not obtained' do
+ mutex = Redis::Mutex.new(:test_lock)
+ mutex.lock.should be_true
+ mutex.unlock.should be_true
+ expect { mutex.unlock! }.to raise_error(Redis::Mutex::UnlockError)
+ end
+
+ it 'raises AssertionError when block is given to #lock' do
+ # instance method
+ mutex = Redis::Mutex.new(:test_lock)
+ expect { mutex.lock {} }.to raise_error(Redis::Mutex::AssertionError)
+
+ # class method
+ expect { Redis::Mutex.lock(:test_lock) {} }.to raise_error(Redis::Mutex::AssertionError)
+ end
+
+ it 'sweeps expired locks' do
+ Redis::Mutex.set(:past, Time.now.to_f - 60)
+ Redis::Mutex.set(:present, Time.now.to_f)
+ Redis::Mutex.set(:future, Time.now.to_f + 60)
+ Redis::Mutex.keys.size.should == 3
+ Redis::Mutex.sweep.should == 2
+ Redis::Mutex.keys.size.should == 1
+ end
+
+ describe Redis::Mutex::Macro do
+ it 'adds auto_mutex' do
t1 = Thread.new { C.new.run_singularly(1).should == "success: 1" }
- sleep 0.01 # In most cases t1 wins, but make sure to give it a head start, not exceeding the sleep inside the method
+ # In most cases t1 wins, but make sure to give it a head start,
+ # not exceeding the sleep inside the method.
+ sleep 0.01
t2 = Thread.new { C.new.run_singularly(2).should == "failure: 2" }
t1.join
t2.join
end
end