spec/job_spec.rb in rufus-scheduler-2.0.24 vs spec/job_spec.rb in rufus-scheduler-3.0.0

- old
+ new

@@ -1,242 +1,514 @@ # # Specifying rufus-scheduler # -# Wed Apr 27 00:51:07 JST 2011 +# Wed Apr 17 06:00:59 JST 2013 # -require 'spec_base' +require 'spec_helper' -describe 'job classes' do +describe Rufus::Scheduler::Job do - before(:each) do - @s = start_scheduler + # specify behaviours common to all job classes + + before :each do + + @taoe = Thread.abort_on_exception + Thread.abort_on_exception = false + + @ose = $stderr + $stderr = StringIO.new + + @scheduler = Rufus::Scheduler.new end - after(:each) do - stop_scheduler(@s) + + after :each do + + @scheduler.shutdown + + Thread.abort_on_exception = @taoe + + $stderr = @ose end - describe Rufus::Scheduler::Job do + describe '#last_time' do - describe '#running' do + it 'returns nil if the job never fired' do - it 'returns false when the job is inactive' do + job = @scheduler.schedule_in '10d' do; end - job = @s.in '2d' do - end + job.last_time.should == nil + end - job.running.should == false - end + it 'returns the last time the job fired' do - it 'returns true when the job is active' do + job = @scheduler.schedule_in '0s' do; end - job = @s.in 0 do - sleep(100) - end + sleep 0.4 - wait_next_tick + job.last_time.should_not == nil + end + end - job.running.should == true - end + describe '#threads' do - it 'returns false when the job hits some error' do + it 'returns an empty list when the job is not running' do - $exception = nil + job = @scheduler.in('1d', :job => true) {} - def @s.handle_exception(j, e) - #p e - $exception = e - end + job.threads.size.should == 0 + end - job = @s.in 0 do - raise "nada" + it 'returns an empty list after the job terminated' do + + job = @scheduler.in('0s', :job => true) {} + + sleep 0.8 + + job.threads.size.should == 0 + end + + it 'lists the threads the job currently runs in' do + + job = + @scheduler.schedule_in('0s') do + sleep(1) end - wait_next_tick + sleep 0.4 - $exception.should_not == nil - job.running.should == false - end + job.threads.size.should == 1 + + t = job.threads.first + t[:rufus_scheduler_job].should == job end + end - describe '#running?' do + describe '#kill' do - it 'is an alias for #running' do + it 'has no effect if the job is not running' do - job = @s.in 0 do - sleep(100) + job = @scheduler.schedule_in '10d' do; end + + tls = Thread.list.size + + job.kill + + Thread.list.size.should == tls + end + + it 'makes the threads vacant' do + + counter = 0 + + job = + @scheduler.schedule_in '0s' do + sleep 2 + counter = counter + 1 end - wait_next_tick + sleep 1 - job.running?.should == true - end + v0 = @scheduler.work_threads(:vacant).size + a0 = @scheduler.work_threads(:active).size + + job.kill + + sleep 2 + + v1 = @scheduler.work_threads(:vacant).size + a1 = @scheduler.work_threads(:active).size + + counter.should == 0 + + v0.should == 0 + a0.should == 1 + + v1.should == 1 + a1.should == 0 end end - describe Rufus::Scheduler::AtJob do + describe '#running?' do - describe '#unschedule' do + it 'returns false when the job is not running in any thread' do - it 'removes the job from the scheduler' do + job = @scheduler.in('1d', :job => true) {} - job = @s.at Time.now + 3 * 3600 do - end + job.running?.should == false + end - wait_next_tick + it 'returns true when the job is running in at least one thread' do - job.unschedule + job = @scheduler.in('0s', :job => true) { sleep(1) } - @s.jobs.size.should == 0 - end + sleep 0.4 + + job.running?.should == true end + end - describe '#next_time' do + describe '#scheduled?' do - it 'returns the time when the job will trigger' do + it 'returns true when the job is scheduled' do - t = Time.now + 3 * 3600 + job = @scheduler.schedule_in('1d') {} - job = @s.at Time.now + 3 * 3600 do - end + job.scheduled?.should == true + end - job.next_time.class.should == Time - job.next_time.to_i.should == t.to_i - end + it 'returns false when the job is not scheduled' do + + job = @scheduler.schedule_in('0.1s') {} + + sleep 0.4 + + job.scheduled?.should == false end + + it 'returns true for repeat jobs that are running' do + + job = @scheduler.schedule_interval('0.4s') { sleep(10) } + + sleep 1 + + job.running?.should == true + job.scheduled?.should == true + end end - describe Rufus::Scheduler::InJob do + context 'job-local variables' do - describe '#unschedule' do + describe '#[]=' do - it 'removes the job from the scheduler' do + it 'sets a job-local variable' do - job = @s.in '2d' do - end + job = + @scheduler.schedule_every '1s' do |job| + job[:counter] ||= 0 + job[:counter] += 1 + end - wait_next_tick + sleep 3 - job.unschedule + job[:counter].should > 1 + end + end - @s.jobs.size.should == 0 + describe '#[]' do + + it 'returns nil if there is no such entry' do + + job = @scheduler.schedule_in '1s' do; end + + job[:nada].should == nil end + + it 'returns the value of a job-local variable' do + + job = @scheduler.schedule_in '1s' do; end + job[:x] = :y + + job[:x].should == :y + end end - describe '#next_time' do + describe '#key?' do - it 'returns the time when the job will trigger' do + it 'returns true if there is an entry with the given key' do - t = Time.now + 3 * 3600 + job = @scheduler.schedule_in '1s' do; end + job[:x] = :y - job = @s.in '3h' do - end + job.key?(:x).should == true + end + end - job.next_time.class.should == Time - job.next_time.to_i.should == t.to_i + describe '#keys' do + + it 'returns the array of keys of the job-local variables' do + + job = @scheduler.schedule_in '1s' do; end + job[:x] = :y + job['hello'] = :z + job[123] = {} + + job.keys.sort_by { |k| k.to_s }.should == [ 123, 'hello', :x ] end end end - describe Rufus::Scheduler::EveryJob do + context ':tag / :tags => [ t0, t1 ]' do - describe '#next_time' do + it 'accepts one tag' do - it 'returns the time when the job will trigger' do + job = @scheduler.in '10d', :job => true, :tag => 't0' do; end - t = Time.now + 3 * 3600 + job.tags.should == %w[ t0 ] + end - job = @s.every '3h' do + it 'accepts an array of tags' do + + job = @scheduler.in '10d', :job => true, :tag => %w[ t0 t1 ] do; end + + job.tags.should == %w[ t0 t1 ] + end + + it 'turns tags into strings' do + + job = @scheduler.in '10d', :job => true, :tags => [ 1, 2 ] do; end + + job.tags.should == %w[ 1 2 ] + end + end + + context ':blocking => true' do + + it 'runs the job in the same thread as the scheduler thread' do + + job = + @scheduler.in('0s', :job => true, :blocking => true) do + sleep(1) end - job.next_time.class.should == Time - job.next_time.to_i.should == t.to_i - end + sleep 0.4 + + job.threads.first.should == @scheduler.thread + + sleep 1.4 + + job.threads.size.should == 0 end + end - describe '#paused?' do + context 'default one thread per job behaviour' do - it 'returns false initially' do + it 'runs the job in a dedicated thread' do - job = @s.every '3h' do; end + job = + @scheduler.in('0s', :job => true) do + sleep(1) + end - job.paused?.should == false - end + sleep 0.4 + + job.threads.first.should_not == @scheduler.thread + + sleep 1.4 + + job.threads.size.should == 0 end + end - describe '#pause' do + context ':allow_overlapping / :allow_overlap / :overlap' do - it 'pauses the job' do + context 'default (:overlap => true)' do - job = @s.every '3h' do; end + it 'lets a job overlap itself' do - job.pause + job = + @scheduler.every('0.3', :job => true) do + sleep(5) + end - job.paused?.should == true + sleep 3 + + job.threads.size.should > 1 end end - describe '#resume' do + context 'when :overlap => false' do - it 'resumes the job' do + it 'prevents a job from overlapping itself' do - job = @s.every '3h' do; end + job = + @scheduler.every('0.3', :job => true, :overlap => false) do + sleep(5) + end - job.resume + sleep 3 - job.paused?.should == false + job.threads.size.should == 1 end end end - describe Rufus::Scheduler::CronJob do + context ':mutex' do - describe '#next_time' do + context ':mutex => "mutex_name"' do - it 'returns the time when the job will trigger' do + it 'prevents concurrent executions' do - job = @s.cron '* * * * *' do + j0 = + @scheduler.in('0s', :job => true, :mutex => 'vladivostok') do + sleep(3) + end + j1 = + @scheduler.in('0s', :job => true, :mutex => 'vladivostok') do + sleep(3) + end + + sleep 0.7 + + if j0.threads.any? + j0.threads.size.should == 1 + j1.threads.size.should == 0 + else + j0.threads.size.should == 0 + j1.threads.size.should == 1 end - job.next_time.class.should == Time - (job.next_time.to_i - Time.now.to_i).should satisfy { |v| v < 60 } + @scheduler.mutexes.keys.should == %w[ vladivostok ] end end - describe '#paused?' do + context ':mutex => mutex_instance' do - it 'returns false initially' do + it 'prevents concurrent executions' do - job = @s.cron '* * * * *' do; end + m = Mutex.new - job.paused?.should == false + j0 = @scheduler.in('0s', :job => true, :mutex => m) { sleep(3) } + j1 = @scheduler.in('0s', :job => true, :mutex => m) { sleep(3) } + + sleep 0.7 + + if j0.threads.any? + j0.threads.size.should == 1 + j1.threads.size.should == 0 + else + j0.threads.size.should == 0 + j1.threads.size.should == 1 + end + + @scheduler.mutexes.keys.should == [] end end - describe '#pause' do + context ':mutex => [ array_of_mutex_names_or_instances ]' do - it 'pauses the job' do + it 'prevents concurrent executions' do - job = @s.cron '* * * * *' do; end + j0 = + @scheduler.in('0s', :job => true, :mutex => %w[ a b ]) do + sleep(3) + end + j1 = + @scheduler.in('0s', :job => true, :mutex => %w[ a b ]) do + sleep(3) + end - job.pause + sleep 0.7 - job.paused?.should == true + if j0.threads.any? + j0.threads.size.should == 1 + j1.threads.size.should == 0 + else + j0.threads.size.should == 0 + j1.threads.size.should == 1 + end + + @scheduler.mutexes.keys.sort.should == %w[ a b ] end end + end - describe '#resume' do + context ':timeout => duration_or_point_in_time' do - it 'resumes the job' do + it 'interrupts the job it is stashed to (duration)' do - job = @s.cron '* * * * *' do; end + counter = 0 + toe = nil - job.resume + job = + @scheduler.schedule_in '0s', :timeout => '1s' do + begin + counter = counter + 1 + sleep 1.5 + counter = counter + 1 + rescue Rufus::Scheduler::TimeoutError => e + toe = e + end + end - job.paused?.should == false + sleep(3) + + counter.should == 1 + toe.class.should == Rufus::Scheduler::TimeoutError + end + + it 'interrupts the job it is stashed to (point in time)' do + + counter = 0 + + job = + @scheduler.schedule_in '0s', :timeout => Time.now + 1 do + begin + counter = counter + 1 + sleep 1.5 + counter = counter + 1 + rescue Rufus::Scheduler::TimeoutError => e + end + end + + sleep(3) + + counter.should == 1 + end + + it 'starts timing when the job enters successfully all its mutexes' do + + t0, t1, t2 = nil + + @scheduler.schedule_in '0s', :mutex => 'a' do + sleep 1 + t0 = Time.now end + + job = + @scheduler.schedule_in '0.5s', :mutex => 'a', :timeout => '1s' do + begin + t1 = Time.now + sleep 2 + rescue Rufus::Scheduler::TimeoutError => e + t2 = Time.now + end + end + + sleep 3 + + t0.should <= t1 + + d = t2 - t1 + d.should >= 1.0 + d.should < 1.5 + end + + it 'emits the timeout information to $stderr (default #on_error)' do + + @scheduler.every('1s', :timeout => '0.5s') do + sleep 0.9 + end + + sleep 2 + + $stderr.string.should match(/Rufus::Scheduler::TimeoutError/) + end + + it 'does not prevent a repeat job from recurring' do + + counter = 0 + + @scheduler.every('1s', :timeout => '0.5s') do + counter = counter + 1 + sleep 0.9 + end + + sleep 3 + + counter.should > 1 end end end