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