test: cover RecordingJob and log
This commit is contained in:
1 parent
c26e4c77dc
commit
10cabe922d
1 file changed
+160
@@ -0,0 +1,160 @@
|
||||
require 'minitest/autorun'
|
||||
require 'prism'
|
||||
require 'tmpdir'
|
||||
require 'fileutils'
|
||||
|
||||
class RecordingJob
|
||||
def self.log
|
||||
@log ||= []
|
||||
end
|
||||
|
||||
def perform(value)
|
||||
self.class.log << value
|
||||
end
|
||||
end
|
||||
|
||||
class FlakyJob
|
||||
def self.attempts
|
||||
@attempts ||= 0
|
||||
end
|
||||
|
||||
def self.reset!
|
||||
@attempts = 0
|
||||
end
|
||||
|
||||
def perform
|
||||
self.class.instance_variable_set(:@attempts, self.class.attempts + 1)
|
||||
raise "not yet (attempt #{self.class.attempts})" if self.class.attempts < 3
|
||||
end
|
||||
end
|
||||
|
||||
class AlwaysFailsJob
|
||||
def perform
|
||||
raise 'nope'
|
||||
end
|
||||
end
|
||||
|
||||
class WorkerTest < Minitest::Test
|
||||
def setup
|
||||
@dir = Dir.mktmpdir('prism-jobs-worker')
|
||||
@queue = Prism::Jobs::Queue.new(File.join(@dir, 'jobs.pstore'))
|
||||
RecordingJob.log.clear
|
||||
FlakyJob.reset!
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.remove_entry(@dir)
|
||||
end
|
||||
|
||||
def test_work_off_performs_every_ready_job
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['a'])
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['b'])
|
||||
|
||||
processed = Prism::Jobs::Worker.new(@queue).work_off
|
||||
|
||||
assert_equal 2, processed
|
||||
assert_equal %w[a b], RecordingJob.log.sort
|
||||
assert_equal 0, @queue.size
|
||||
end
|
||||
|
||||
def test_work_off_does_not_touch_jobs_scheduled_in_the_future
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['now'])
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['later'], run_at: Time.now + 3600)
|
||||
|
||||
processed = Prism::Jobs::Worker.new(@queue).work_off
|
||||
|
||||
assert_equal 1, processed
|
||||
assert_equal ['now'], RecordingJob.log
|
||||
assert_equal 1, @queue.size
|
||||
end
|
||||
|
||||
def test_failing_job_is_retried_and_eventually_succeeds
|
||||
@queue.enqueue(job_class: 'FlakyJob', args: [])
|
||||
worker = Prism::Jobs::Worker.new(@queue)
|
||||
|
||||
worker.work_off
|
||||
assert_equal 0, worker.work_off, 'retry is backed off into the future, not immediately ready'
|
||||
force_all_due
|
||||
|
||||
worker.work_off
|
||||
force_all_due
|
||||
worker.work_off
|
||||
|
||||
assert_equal 0, @queue.size
|
||||
assert_equal 3, FlakyJob.attempts
|
||||
end
|
||||
|
||||
def test_job_that_always_fails_is_dead_lettered_after_max_attempts
|
||||
@queue.enqueue(job_class: 'AlwaysFailsJob', args: [], max_attempts: 2)
|
||||
worker = Prism::Jobs::Worker.new(@queue)
|
||||
|
||||
worker.work_off
|
||||
force_all_due
|
||||
worker.work_off
|
||||
|
||||
assert_equal 0, @queue.size
|
||||
assert_equal 1, @queue.dead_letter_size
|
||||
assert_match(/nope/, @queue.dead_jobs.first[:last_error])
|
||||
end
|
||||
|
||||
def test_error_handler_is_invoked_with_the_exception_and_job
|
||||
seen = []
|
||||
@queue.enqueue(job_class: 'AlwaysFailsJob', args: [], max_attempts: 5)
|
||||
worker = Prism::Jobs::Worker.new(@queue, error_handler: ->(error, job) { seen << [error.message, job[:job_class]] })
|
||||
|
||||
worker.work_off
|
||||
|
||||
assert_equal [['nope', 'AlwaysFailsJob']], seen
|
||||
end
|
||||
|
||||
def test_work_off_can_be_scoped_to_a_single_queue_name
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['default-queue'])
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['mailers-queue'], queue_name: 'mailers')
|
||||
|
||||
processed = Prism::Jobs::Worker.new(@queue, queue_name: 'mailers').work_off
|
||||
|
||||
assert_equal 1, processed
|
||||
assert_equal ['mailers-queue'], RecordingJob.log
|
||||
assert_equal 1, @queue.size
|
||||
end
|
||||
|
||||
def test_process_publishes_perform_job_instrumentation_event
|
||||
@queue.enqueue(job_class: 'RecordingJob', args: ['c'], queue_name: 'mailers')
|
||||
events = []
|
||||
subscriber = Prism::Instrumentation.subscribe('perform_job.prism') { |event| events << event }
|
||||
|
||||
Prism::Jobs::Worker.new(@queue, queue_name: 'mailers').work_off
|
||||
|
||||
assert_equal 1, events.size
|
||||
assert_equal 'RecordingJob', events.first.payload[:job_class]
|
||||
assert_equal 'mailers', events.first.payload[:queue_name]
|
||||
assert_equal 'completed', events.first.payload[:status]
|
||||
assert_kind_of Float, events.first.duration
|
||||
ensure
|
||||
Prism::Instrumentation.unsubscribe(subscriber)
|
||||
end
|
||||
|
||||
def test_process_publishes_instrumentation_event_with_exception_on_failure
|
||||
@queue.enqueue(job_class: 'AlwaysFailsJob', args: [], max_attempts: 5)
|
||||
events = []
|
||||
subscriber = Prism::Instrumentation.subscribe('perform_job.prism') { |event| events << event }
|
||||
|
||||
Prism::Jobs::Worker.new(@queue).work_off
|
||||
|
||||
assert_equal 1, events.size
|
||||
assert_nil events.first.payload[:status]
|
||||
assert_equal 'nope', events.first.payload[:exception][1]
|
||||
ensure
|
||||
Prism::Instrumentation.unsubscribe(subscriber)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Real retries are scheduled with backoff into the future; tests that
|
||||
# need to observe a second/third attempt without sleeping reach into the
|
||||
# store to pull a job's run_at back into the past instead.
|
||||
def force_all_due
|
||||
pstore = @queue.instance_variable_get(:@pstore)
|
||||
pstore.transaction { pstore[:jobs].each_value { |job| job[:run_at] = Time.now - 1 } }
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user