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

- old
+ new

@@ -1,248 +1,955 @@ # # Specifying rufus-scheduler # -# Sat Mar 21 17:43:23 JST 2009 +# Wed Apr 17 06:00:59 JST 2013 # -require 'spec_base' +require 'spec_helper' -describe SCHEDULER_CLASS do +describe Rufus::Scheduler do - it 'stops' do + describe '#initialize' do - var = nil + it 'starts the scheduler thread' do - s = start_scheduler - s.in('3s') { var = true } + scheduler = Rufus::Scheduler.new - stop_scheduler(s) + t = Thread.list.find { |t| + t[:name] == "rufus_scheduler_#{scheduler.object_id}_scheduler" + } - var.should == nil - sleep 4 - var.should == nil + t[:rufus_scheduler].should == scheduler + end + + it 'sets a :rufus_scheduler thread local var' do + + scheduler = Rufus::Scheduler.new + end + + it 'accepts a :frequency => integer option' do + + scheduler = Rufus::Scheduler.new(:frequency => 2) + + scheduler.frequency.should == 2 + end + + it 'accepts a :frequency => "2h1m" option' do + + scheduler = Rufus::Scheduler.new(:frequency => '2h1m') + + scheduler.frequency.should == 3600 * 2 + 60 + end + + it 'accepts a :thread_name option' do + + scheduler = Rufus::Scheduler.new(:thread_name => 'oliphant') + + t = Thread.list.find { |t| t[:name] == 'oliphant' } + + t[:rufus_scheduler].should == scheduler + end + + #it 'accepts a :min_work_threads option' do + # scheduler = Rufus::Scheduler.new(:min_work_threads => 9) + # scheduler.min_work_threads.should == 9 + #end + + it 'accepts a :max_work_threads option' do + + scheduler = Rufus::Scheduler.new(:max_work_threads => 9) + + scheduler.max_work_threads.should == 9 + end end - unless SCHEDULER_CLASS == Rufus::Scheduler::EmScheduler + before :each do + @scheduler = Rufus::Scheduler.new + end + after :each do + @scheduler.shutdown + end - it 'sets a default scheduler thread name' do + describe 'a schedule method' do - s = start_scheduler + it 'passes the job to its block when it triggers' do - s.instance_variable_get(:@thread)['name'].should match( - /Rufus::Scheduler::.*Scheduler - \d+\.\d+\.\d+/) + j = nil + job = @scheduler.schedule_in('0s') { |jj| j = jj } - stop_scheduler(s) + sleep 0.4 + + j.should == job end - it 'sets the scheduler thread name' do + it 'passes the trigger time as second block argument' do - s = start_scheduler(:thread_name => 'nada') - s.instance_variable_get(:@thread)['name'].should == 'nada' + t = nil + @scheduler.schedule_in('0s') { |jj, tt| t = tt } - stop_scheduler(s) + sleep 0.4 + + t.class.should == Time end + + class MyHandler + attr_reader :counter + def initialize + @counter = 0 + end + def call(job, time) + @counter = @counter + 1 + end + end + + it 'accepts a callable object instead of a block' do + + mh = MyHandler.new + + @scheduler.schedule_in('0s', mh) + + sleep 0.4 + + mh.counter.should == 1 + end + + class MyOtherHandler + attr_reader :counter + def initialize + @counter = 0 + end + def call + @counter = @counter + 1 + end + end + + it 'accepts a callable obj instead of a block (#call with no args)' do + + job = @scheduler.schedule_in('0s', MyOtherHandler.new) + + sleep 0.4 + + job.handler.counter.should == 1 + end + + it 'accepts a class as callable' do + + job = + @scheduler.schedule_in('0s', Class.new do + attr_reader :value + def call + @value = 7 + end + end) + + sleep 0.4 + + job.handler.value.should == 7 + end + + it 'raises if the scheduler is shutting down' do + + @scheduler.shutdown + + lambda { + @scheduler.in('0s') { puts 'hhhhhhhhhhhello!!' } + }.should raise_error(RuntimeError) + end end - it 'accepts a custom frequency' do + describe '#in / #at' do - var = nil + # scheduler.in(2.hours.from_now) { ... } - s = start_scheduler(:frequency => 3.0) + it 'accepts point in time and duration indifferently (#in)' do - s.in('1s') { var = true } + seen = false - sleep 1 - var.should == nil + t = Time.now + 1 - sleep 1 - var.should == nil + @scheduler.in(t) { seen = true } - sleep 2 - var.should == true + sleep 0.1 while seen != true + end - stop_scheduler(s) + it 'accepts point in time and duration indifferently (#at)' do + + seen = false + + t = 1 + + @scheduler.at(t) { seen = true } + + sleep 0.1 while seen != true + end end - context 'pause/resume' do + describe '#schedule' do - before(:each) do - @s = start_scheduler + it 'accepts a duration and schedules an InJob' do + + j = @scheduler.schedule '1s' do; end + + j.class.should == Rufus::Scheduler::InJob + j.original.should == '1s' end - after(:each) do - stop_scheduler(@s) + + it 'accepts a point in time and schedules an AtJob' do + + j = @scheduler.schedule '2070/12/24 23:00' do; end + + j.class.should == Rufus::Scheduler::AtJob + j.next_time.strftime('%Y %m %d').should == '2070 12 24' end - describe '#pause' do + it 'accepts a cron string and schedules a CronJob' do - it 'pauses a job (every)' do + j = @scheduler.schedule '* * * * *' do; end - $count = 0 + j.class.should == Rufus::Scheduler::CronJob + end + end - j = @s.every '1s' do - $count = $count + 1 + describe '#repeat' do + + it 'accepts a duration and schedules an EveryJob' do + + j = @scheduler.repeat '1s' do; end + + j.class.should == Rufus::Scheduler::EveryJob + end + + it 'accepts a cron string and schedules a CronJob' do + + j = @scheduler.repeat '* * * * *' do; end + + j.class.should == Rufus::Scheduler::CronJob + end + end + + describe '#unschedule(job_or_work_id)' do + + it 'accepts job ids' do + + job = @scheduler.schedule_in '10d' do; end + + job.unscheduled_at.should == nil + + @scheduler.unschedule(job.id) + + job.unscheduled_at.should_not == nil + end + + it 'accepts jobs' do + + job = @scheduler.schedule_in '10d' do; end + + job.unscheduled_at.should == nil + + @scheduler.unschedule(job) + + job.unscheduled_at.should_not == nil + end + + it 'carefully unschedules repeat jobs' do + + counter = 0 + + job = + @scheduler.schedule_every '0.5s' do + counter = counter + 1 end - @s.pause(j.job_id) + sleep 1.5 + c = counter - sleep 2.5 + @scheduler.unschedule(job) - j.paused?.should == true - $count.should == 0 + sleep 1.5 + counter.should == c + end + end + + describe '#uptime' do + + it 'returns the uptime as a float' do + + @scheduler.uptime.should >= 0.0 + end + end + + describe '#uptime_s' do + + it 'returns the uptime as a human readable string' do + + sleep 1 + + @scheduler.uptime_s.should match(/^[12]s\d+$/) + end + end + + describe '#join' do + + it 'joins the scheduler thread' do + + t = Thread.new { @scheduler.join; Thread.current['a'] = 'over' } + + t['a'].should == nil + + @scheduler.shutdown + + sleep(1) + + t['a'].should == 'over' + end + end + + describe '#job(job_id)' do + + it 'returns nil if there is no corresponding Job instance' do + + @scheduler.job('nada').should == nil + end + + it 'returns the corresponding Job instance' do + + job_id = @scheduler.in '10d' do; end + + sleep(1) # give it some time to get scheduled + + @scheduler.job(job_id).job_id.should == job_id + end + end + +# describe '#find_by_tag(t)' do +# +# it 'returns an empty list when there are no jobs with the given tag' do +# +# @scheduler.find_by_tag('nada').should == [] +# end +# +# it 'returns all the jobs with the given tag' do +# +# @scheduler.in '10d', :tag => 't0' do; end +# @scheduler.every '2h', :tag => %w[ t0 t1 ] do; end +# @scheduler.every '3h' do; end +# +# @scheduler.find_by_tag('t0').map(&:original).should == +# %w[ 2h 10d ] +# @scheduler.find_by_tag('t1').map(&:original).should == +# %w[ 2h ] +# @scheduler.find_by_tag('t1', 't0').map(&:original).sort.should == +# %w[ 2h ] +# end +# end + + describe '#threads' do + + it 'just lists the main thread (scheduler thread) when no job is scheduled' do + + @scheduler.threads.should == [ @scheduler.thread ] + end + + it 'lists all the threads a scheduler uses' do + + @scheduler.in '0s' do + sleep(2) end - it 'pauses a job (cron)' do + sleep 0.4 - $count = 0 + @scheduler.threads.size.should == 2 + end + end - j = @s.cron '* * * * * *' do - $count = $count + 1 - end + describe '#work_threads(:all)' do - @s.pause(j.job_id) + it 'returns an empty array when the scheduler has not yet done anything' do - sleep 2.5 + @scheduler.work_threads.should == [] + end - j.paused?.should == true - $count.should == 0 + it 'lists all the work threads in the pool' do + + @scheduler.in '0s' do + # nada end + @scheduler.in '0s' do + sleep(2) + end + + sleep 0.6 + + @scheduler.work_threads.size.should == 2 end + end - describe '#resume' do + describe '#work_threads(:vacant)' do - it 'resumes a job (every)' do + it 'returns an empty array when the scheduler has not yet done anything' do - $count = 0 + @scheduler.work_threads(:vacant).should == [] + end - j = @s.every '1s' do - $count = $count + 1 + it 'lists all the work threads in the pool' do + + @scheduler.in '0s' do + # nada + end + @scheduler.in '0s' do + sleep(2) + end + + sleep 0.4 + + @scheduler.work_threads(:vacant).size.should == 1 + end + end + + describe '#work_threads(:active)' do + + it 'returns [] when there are no jobs running' do + + @scheduler.work_threads(:active).should == [] + end + + it 'returns the list of threads of the running jobs' do + + job = + @scheduler.schedule_in('0s') do + sleep 1 end - @s.pause(j.job_id) + sleep 0.4 - sleep 2.5 + @scheduler.work_threads(:active).size.should == 1 - c = $count + t = @scheduler.work_threads(:active).first - @s.resume(j.job_id) + t.class.should == Thread + t[@scheduler.thread_key].should == true + t[:rufus_scheduler_job].should == job + t[:rufus_scheduler_time].should_not == nil + end - sleep 1.5 + it 'does not return threads from other schedulers' do - j.paused?.should == false - ($count > c).should == true - end + scheduler = Rufus::Scheduler.new - it 'pauses a job (cron)' do + job = + @scheduler.schedule_in('0s') do + sleep(1) + end - $count = 0 + sleep 0.4 - j = @s.cron '* * * * * *' do - $count = $count + 1 + scheduler.work_threads(:active).should == [] + + scheduler.shutdown + end + end + + #describe '#min_work_threads' do + # it 'returns the min job thread count' do + # @scheduler.min_work_threads.should == 7 + # end + #end + #describe '#min_work_threads=' do + # it 'sets the min job thread count' do + # @scheduler.min_work_threads = 1 + # @scheduler.min_work_threads.should == 1 + # end + #end + + describe '#max_work_threads' do + + it 'returns the max job thread count' do + + @scheduler.max_work_threads.should == 35 + end + end + + describe '#max_work_threads=' do + + it 'sets the max job thread count' do + + @scheduler.max_work_threads = 14 + + @scheduler.max_work_threads.should == 14 + end + end + + #describe '#kill_all_work_threads' do + # + # it 'kills all the work threads' do + # + # @scheduler.in '0s' do; sleep(5); end + # @scheduler.in '0s' do; sleep(5); end + # @scheduler.in '0s' do; sleep(5); end + # + # sleep 0.5 + # + # @scheduler.work_threads.size.should == 3 + # + # @scheduler.send(:kill_all_work_threads) + # + # sleep 0.5 + # + # @scheduler.work_threads.size.should == 0 + # end + #end + + describe '#running_jobs' do + + it 'returns [] when there are no running jobs' do + + @scheduler.running_jobs.should == [] + end + + it 'returns a list of running Job instances' do + + job = + @scheduler.schedule_in('0s') do + sleep(1) end - @s.pause(j.job_id) + sleep 0.4 - sleep 2.5 + job.running?.should == true + @scheduler.running_jobs.should == [ job ] + end - c = $count + it 'does not return twice the same job' do - @s.resume(j.job_id) + job = + @scheduler.schedule_every('0.3s') do + sleep(5) + end - sleep 1.5 + sleep 1.5 - j.paused?.should == false - ($count > c).should == true + job.running?.should == true + @scheduler.running_jobs.should == [ job ] + end + end + + describe '#running_jobs(:tag/:tags => x)' do + + it 'returns a list of running jobs filtered by tag' do + + @scheduler.in '0.1s', :tag => 't0' do + sleep 3 end + @scheduler.in '0.2s', :tag => 't1' do + sleep 3 + end + + sleep 0.4 + + @scheduler.running_jobs(:tag => 't0').map(&:original).should == + %w[ 0.1s ] + @scheduler.running_jobs(:tag => 't1').map(&:original).should == + %w[ 0.2s ] + @scheduler.running_jobs(:tags => %w[ t0 t1 ]).map(&:original).should == + [] end end - context 'trigger threads' do + #-- + # management methods + #++ - before(:each) do - @s = start_scheduler + describe '#shutdown' do + + it 'blanks the uptime' do + + @scheduler.shutdown + + @scheduler.uptime.should == nil end - after(:each) do - stop_scheduler(@s) + + it 'shuts the scheduler down' do + + @scheduler.shutdown + + sleep 0.100 + sleep 0.400 if RUBY_VERSION < '1.9.0' + + t = Thread.list.find { |t| + t[:name] == "rufus_scheduler_#{@scheduler.object_id}" + } + + t.should == nil end - describe '#trigger_threads' do + it 'has a #stop alias' do - it 'returns an empty list when no jobs are running' do + @scheduler.stop - @s.trigger_threads.should == [] - end + @scheduler.uptime.should == nil + end - it 'returns a list of the threads of the running jobs' do + #it 'has a #close alias' + end - @s.in('100') { sleep 10 } + describe '#shutdown(:wait)' do - sleep 0.5 + it 'shuts down and blocks until all the jobs ended their current runs' do - @s.trigger_threads.collect { |e| e.class }.should == [ Thread ] + counter = 0 + + @scheduler.in '0s' do + sleep 1 + counter = counter + 1 end + + sleep 0.4 + + @scheduler.shutdown(:wait) + + counter.should == 1 + @scheduler.uptime.should == nil + @scheduler.running_jobs.should == [] + @scheduler.threads.should == [] end + end - describe '#running_jobs' do + describe '#shutdown(:kill)' do - it 'returns an empty list when no jobs are running' do + it 'kills all the jobs and then shuts down' do - @s.running_jobs.should == [] + counter = 0 + + @scheduler.in '0s' do + sleep 1 + counter = counter + 1 end + @scheduler.at Time.now + 0.3 do + sleep 1 + counter = counter + 1 + end - it 'returns a list of the currently running jobs' do + sleep 0.4 - job = @s.in('100') { sleep 10 } + @scheduler.shutdown(:kill) - sleep 0.5 + sleep 1.4 - @s.running_jobs.should == [ job ] + counter.should == 0 + @scheduler.uptime.should == nil + @scheduler.running_jobs.should == [] + @scheduler.threads.should == [] + end + end + + describe '#pause' do + + it 'pauses the scheduler' do + + job = @scheduler.schedule_in '1s' do; end + + @scheduler.pause + + sleep(3) + + job.last_time.should == nil + end + end + + describe '#resume' do + + it 'works' do + + job = @scheduler.schedule_in '2s' do; end + + @scheduler.pause + sleep(1) + @scheduler.resume + sleep(2) + + job.last_time.should_not == nil + end + end + + describe '#paused?' do + + it 'returns true if the scheduler is paused' do + + @scheduler.pause + @scheduler.paused?.should == true + end + + it 'returns false if the scheduler is not paused' do + + @scheduler.paused?.should == false + + @scheduler.pause + @scheduler.resume + + @scheduler.paused?.should == false + end + end + + #-- + # job methods + #++ + + describe '#jobs' do + + it 'is empty at the beginning' do + + @scheduler.jobs.should == [] + end + + it 'returns the list of scheduled jobs' do + + @scheduler.in '10d' do; end + @scheduler.in '1w' do; end + + sleep(1) + + jobs = @scheduler.jobs + + jobs.collect { |j| j.original }.sort.should == %w[ 10d 1w ] + end + + it 'returns all the jobs (even those pending reschedule)' do + + @scheduler.in '0s', :blocking => true do + sleep 2 end + + sleep 0.4 + + @scheduler.jobs.size.should == 1 end + + it 'does not return unscheduled jobs' do + + job = + @scheduler.schedule_in '0s', :blocking => true do + sleep 2 + end + + sleep 0.4 + + job.unschedule + + @scheduler.jobs.size.should == 0 + end end - context 'termination' do + describe '#jobs(:tag / :tags => x)' do - describe '#stop(true)' do + it 'returns [] when there are no jobs with the corresponding tag' do - it 'terminates the scheduler, blocking until all the jobs are unscheduled' do + @scheduler.jobs(:tag => 'nada').should == [] + @scheduler.jobs(:tags => %w[ nada hello ]).should == [] + end - $every = nil - $cron = nil + it 'returns the jobs with the corresponding tag' do - s = start_scheduler - s.every '1s' do - $every = :in - sleep 0.5 - $every = :out + @scheduler.in '10d', :tag => 't0' do; end + @scheduler.every '2h', :tag => %w[ t0 t1 ] do; end + @scheduler.every '3h' do; end + + @scheduler.jobs(:tags => 't0').map(&:original).sort.should == + %w[ 10d 2h ] + @scheduler.jobs(:tags => 't1').map(&:original).sort.should == + %w[ 2h ] + @scheduler.jobs(:tags => [ 't1', 't0' ]).map(&:original).sort.should == + %w[ 2h ] + end + end + + describe '#every_jobs' do + + it 'returns EveryJob instances' do + + @scheduler.at '2030/12/12 12:10:00' do; end + @scheduler.in '10d' do; end + @scheduler.every '5m' do; end + + jobs = @scheduler.every_jobs + + jobs.collect { |j| j.original }.sort.should == %w[ 5m ] + end + end + + describe '#at_jobs' do + + it 'returns AtJob instances' do + + @scheduler.at '2030/12/12 12:10:00' do; end + @scheduler.in '10d' do; end + @scheduler.every '5m' do; end + + jobs = @scheduler.at_jobs + + jobs.collect { |j| j.original }.sort.should == [ '2030/12/12 12:10:00' ] + end + end + + describe '#in_jobs' do + + it 'returns InJob instances' do + + @scheduler.at '2030/12/12 12:10:00' do; end + @scheduler.in '10d' do; end + @scheduler.every '5m' do; end + + jobs = @scheduler.in_jobs + + jobs.collect { |j| j.original }.sort.should == %w[ 10d ] + end + end + + describe '#cron_jobs' do + + it 'returns CronJob instances' do + + @scheduler.at '2030/12/12 12:10:00' do; end + @scheduler.in '10d' do; end + @scheduler.every '5m' do; end + @scheduler.cron '* * * * *' do; end + + jobs = @scheduler.cron_jobs + + jobs.collect { |j| j.original }.sort.should == [ '* * * * *' ] + end + end + + describe '#interval_jobs' do + + it 'returns IntervalJob instances' do + + @scheduler.at '2030/12/12 12:10:00' do; end + @scheduler.in '10d' do; end + @scheduler.every '5m' do; end + @scheduler.cron '* * * * *' do; end + @scheduler.interval '7m' do; end + + jobs = @scheduler.interval_jobs + + jobs.collect { |j| j.original }.sort.should == %w[ 7m ] + end + end + + #-- + # callbacks + #++ + + describe '#on_pre_trigger' do + + it 'is called right before a job triggers' do + + $out = [] + + def @scheduler.on_pre_trigger(job) + $out << "pre #{job.id}" + end + + job_id = + @scheduler.in '0.5s' do |job| + $out << job.id end - s.cron '* * * * * *' do - $cron = :in - sleep 0.5 - $cron = :out + + sleep 0.7 + + $out.should == [ "pre #{job_id}", job_id ] + end + + it 'accepts the job and the triggerTime as argument' do + + $tt = nil + + def @scheduler.on_pre_trigger(job, trigger_time) + $tt = trigger_time + end + + start = Time.now + + @scheduler.in '0.5s' do; end + + sleep 0.7 + + $tt.class.should == Time + $tt.should > start + $tt.should < Time.now + end + + context 'when it returns false' do + + it 'prevents the job from triggering' do + + $out = [] + + def @scheduler.on_pre_trigger(job) + $out << "pre #{job.id}" + false end - sleep 2 + job_id = + @scheduler.in '0.5s' do |job| + $out << job.id + end - s.stop(:terminate => true) + sleep 0.7 - s.jobs.size.should == 0 - $every.should == :out - $cron.should == :out + $out.should == [ "pre #{job_id}" ] end end end -end -describe 'Rufus::Scheduler#start_new' do + describe '#on_post_trigger' do - it 'piggybacks EM if present and running' do + it 'is called right after a job triggers' do - s = Rufus::Scheduler.start_new + $out = [] - s.class.should == SCHEDULER_CLASS + def @scheduler.on_post_trigger(job) + $out << "post #{job.id}" + end - stop_scheduler(s) + job_id = + @scheduler.in '0.5s' do |job| + $out << job.id + end + + sleep 0.7 + + $out.should == [ job_id, "post #{job_id}" ] + end + end + + #-- + # misc + #++ + + describe '.singleton / .s' do + + before(:each) do + + Rufus::Scheduler.class_eval { @singleton = nil } # ;-) + end + + it 'returns a singleton instance of the scheduler' do + + s0 = Rufus::Scheduler.singleton + s1 = Rufus::Scheduler.s + + s0.class.should == Rufus::Scheduler + s1.object_id.should == s0.object_id + end + + it 'accepts initialization parameters' do + + s = Rufus::Scheduler.singleton(:max_work_threads => 77) + s = Rufus::Scheduler.singleton(:max_work_threads => 42) + + s.max_work_threads.should == 77 + end end end