Reputation: 974
Starting using rspec I have difficulties trying to test threaded code.
Here is a simplicfication of a code founded, and I made it cause i need a Queue with Timeout capabilities
require "thread"
class TimeoutQueue
def initialize
@lock = Mutex.new
@items = []
@new_item = ConditionVariable.new
end
def push(obj)
@lock.synchronize do
@items.push(obj)
@new_item.signal
end
end
def pop(timeout = :never)
timeout += Time.now unless timeout == :never
@lock.synchronize do
loop do
time_left = timeout == :never ? nil : timeout - Time.now
if @items.empty? and time_left.to_f >= 0
@new_item.wait(@lock, time_left)
end
return @items.shift unless @items.empty?
next if timeout == :never or timeout > Time.now
return nil
end
end
end
alias_method :<<, :push
end
But I can't find a way to test it using rspec. Is there any effective documentation on testing threaded code? Any gem that can helps me? I'm a bit blocked, thanks in advance
Upvotes: 2
Views: 5045
Reputation: 37409
When unit-testing we don't want any non-deterministic behavior to affect our tests, so when testing threading we should not run anything in parallel.
Instead, we should isolate our code, and simulate the cases we want to test, by stubbing @lock
, @new_item
, and perhaps even Time.now
(to be more readable I've taken the liberty to imagine you also have attr_reader :lock, :new_item
):
it 'should signal after push' do
allow(subject.lock).to receive(:synchronize).and_yield
expect(subject.new_item).to receive(:signal)
subject.push('object')
expect(subject.items).to include('object')
end
it 'should time out if taken to long to enter synchronize loop' do
@now = Time.now
allow(Time).to receive(:now).and_return(@now, @now + 10.seconds)
allow(subject.items).to receive(:empty?).and_return true
allow(subject.lock).to receive(:synchronize).and_yield
expect(subject.new_item).to_not receive(:wait)
expect(subject.pop(5.seconds)).to be_nil
end
etc...
Upvotes: 3