From 719af5b43306860998513843406cb25e918360ec Mon Sep 17 00:00:00 2001 From: "Ben Sheldon [he/him]" Date: Thu, 30 Sep 2021 10:10:03 -0700 Subject: [PATCH] Cleanup thread tests, introduce "Slow" ExampleJob type, refactor ExampleJob types, run cron in GoodJob Development harness (#390) --- spec/app/jobs/example_job_spec.rb | 27 +++++--- spec/lib/good_job/current_thread_spec.rb | 69 +++++++------------ spec/test_app/app/jobs/example_job.rb | 23 ++++--- spec/test_app/config/environments/demo.rb | 2 +- .../config/environments/development.rb | 20 ++++++ spec/test_app/config/initializers/postgres.rb | 13 ++++ 6 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 spec/test_app/config/initializers/postgres.rb diff --git a/spec/app/jobs/example_job_spec.rb b/spec/app/jobs/example_job_spec.rb index c28ba449a..c8d1e22da 100644 --- a/spec/app/jobs/example_job_spec.rb +++ b/spec/app/jobs/example_job_spec.rb @@ -8,39 +8,50 @@ end describe "#perform" do - describe ":success" do + describe "SUCCESS_TYPE" do it 'completes successfully' do - active_job = described_class.perform_later('success') + active_job = described_class.perform_later(described_class::SUCCESS_TYPE) execution = GoodJob::Execution.find(active_job.provider_job_id) expect(execution.error).to be_nil end end - describe ":error_once" do + describe "ERROR_ONCE_TYPE" do it 'errors once then succeeds' do - active_job = described_class.perform_later('error_once') + active_job = described_class.perform_later(described_class::ERROR_ONCE_TYPE) executions = GoodJob::Execution.where(active_job_id: active_job.job_id).order(created_at: :asc) expect(executions.size).to eq 2 expect(executions.last.error).to be_nil end end - describe ":error_five_times" do + describe "ERROR_FIVE_TIMES_TYPE" do it 'errors five times then succeeds' do - active_job = described_class.perform_later('error_five_times') + active_job = described_class.perform_later(described_class::ERROR_FIVE_TIMES_TYPE) executions = GoodJob::Execution.where(active_job_id: active_job.job_id).order(created_at: :asc) expect(executions.size).to eq 6 expect(executions.last.error).to be_nil end end - describe ":dead" do + describe "DEAD_TYPE" do it 'errors but does not retry' do - active_job = described_class.perform_later('dead') + active_job = described_class.perform_later(described_class::DEAD_TYPE) executions = GoodJob::Execution.where(active_job_id: active_job.job_id).order(created_at: :asc) expect(executions.size).to eq 3 expect(executions.last.error).to be_present end end + + describe "SLOW_TYPE" do + it 'sleeps for period' do + expect_any_instance_of(Object).to receive(:sleep) + + active_job = described_class.perform_later(described_class::SLOW_TYPE) + + execution = GoodJob::Execution.find(active_job.provider_job_id) + expect(execution.error).to be_nil + end + end end end diff --git a/spec/lib/good_job/current_thread_spec.rb b/spec/lib/good_job/current_thread_spec.rb index 1e43ebd17..ca52b415a 100644 --- a/spec/lib/good_job/current_thread_spec.rb +++ b/spec/lib/good_job/current_thread_spec.rb @@ -2,55 +2,36 @@ require 'rails_helper' RSpec.describe GoodJob::CurrentThread do - describe '.error_on_discard' do - it 'maintains value across threads' do - described_class.error_on_discard = 'apple' - - Thread.new do - described_class.error_on_discard = 'bear' - end.join - - expect(described_class.error_on_discard).to eq 'apple' - end - - it 'maintains value across Rails execution wrapper' do - Rails.application.executor.wrap do - described_class.error_on_discard = 'apple' + [ + :cron_key, + :execution, + :error_on_discard, + :error_on_retry, + ].each do |accessor| + describe ".#{accessor}" do + it 'maintains value across threads' do + described_class.send "#{accessor}=", 'apple' + + Thread.new do + described_class.send "#{accessor}=", 'bear' + end.join + + expect(described_class.send(accessor)).to eq 'apple' end - expect(described_class.error_on_discard).to eq 'apple' - end - - it 'is resettable' do - described_class.error_on_discard = 'apple' - described_class.reset - expect(described_class.error_on_discard).to eq nil - end - end + it 'maintains value across Rails reloader wrapper' do + Rails.application.reloader.wrap do + described_class.send "#{accessor}=", 'apple' + end - describe '.error_on_retry' do - it 'maintains value across threads' do - described_class.error_on_retry = 'apple' - - Thread.new do - described_class.error_on_retry = 'bear' - end.join - - expect(described_class.error_on_retry).to eq 'apple' - end - - it 'maintains value across Rails execution wrapper' do - Rails.application.executor.wrap do - described_class.error_on_retry = 'apple' + expect(described_class.send(accessor)).to eq 'apple' end - expect(described_class.error_on_retry).to eq 'apple' - end - - it 'is resettable' do - described_class.error_on_retry = 'apple' - described_class.reset - expect(described_class.error_on_retry).to eq nil + it 'is resettable' do + described_class.send "#{accessor}=", 'apple' + described_class.reset + expect(described_class.send(accessor)).to eq nil + end end end diff --git a/spec/test_app/app/jobs/example_job.rb b/spec/test_app/app/jobs/example_job.rb index c5c56e503..c3f522146 100644 --- a/spec/test_app/app/jobs/example_job.rb +++ b/spec/test_app/app/jobs/example_job.rb @@ -2,20 +2,27 @@ class ExampleJob < ApplicationJob ExpectedError = Class.new(StandardError) DeadError = Class.new(StandardError) + TYPES = [ + SUCCESS_TYPE = 'success', + ERROR_ONCE_TYPE = 'error_once', + ERROR_FIVE_TIMES_TYPE = 'error_five_times', + DEAD_TYPE = 'dead', + SLOW_TYPE = 'slow', + ] + retry_on DeadError, attempts: 3 - def perform(type = :success) - return unless type.respond_to?(:to_sym) - type = type.to_sym - - if type == :success + def perform(type = SUCCESS_TYPE) + if type == SUCCESS_TYPE true - elsif type == :error_once + elsif type == ERROR_ONCE_TYPE raise(ExpectedError, "Executed #{executions} #{"time".pluralize(executions)}.") if executions < 2 - elsif type == :error_five_times + elsif type == ERROR_FIVE_TIMES_TYPE raise(ExpectedError, "Executed #{executions} #{"time".pluralize(executions)}.") if executions < 6 - elsif type == :dead + elsif type == DEAD_TYPE raise DeadError + elsif type == SLOW_TYPE + sleep 5 end end end diff --git a/spec/test_app/config/environments/demo.rb b/spec/test_app/config/environments/demo.rb index 01ad5399e..e2f7e5be9 100644 --- a/spec/test_app/config/environments/demo.rb +++ b/spec/test_app/config/environments/demo.rb @@ -15,7 +15,7 @@ cron: "* * * * * *", class: "ExampleJob", args: (lambda do - type = [:success, :error_once, :error_five_times, :dead].sample + type = ExampleJob::TYPES.sample [type.to_s] end), set: (lambda do diff --git a/spec/test_app/config/environments/development.rb b/spec/test_app/config/environments/development.rb index 07675cf57..bdf765020 100644 --- a/spec/test_app/config/environments/development.rb +++ b/spec/test_app/config/environments/development.rb @@ -62,4 +62,24 @@ config.active_job.queue_adapter = :good_job GoodJob.preserve_job_records = true + + config.good_job.enable_cron = true + config.good_job.cron = { + frequent_example: { + description: "Enqueue an ExampleJob", + cron: "*/5 * * * * *", + class: "ExampleJob", + args: (lambda do + type = ExampleJob::SLOW_TYPE # ExampleJob::TYPES.sample + [type.to_s] + end), + set: (lambda do + queue = [:default, :elephants, :mice].sample + delay = [0, (0..60).to_a.sample].sample + priority = [-10, 0, 10].sample + + { wait: delay, queue: queue, priority: priority } + end), + }, + } end diff --git a/spec/test_app/config/initializers/postgres.rb b/spec/test_app/config/initializers/postgres.rb new file mode 100644 index 000000000..87c0a862f --- /dev/null +++ b/spec/test_app/config/initializers/postgres.rb @@ -0,0 +1,13 @@ +# Output Postgres notifications through Rails.logger instead of stderr +if Rails.env.development? + ActiveSupport.on_load :active_record do + ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :before, lambda { |conn| + raw_connection = conn.raw_connection + next unless raw_connection.respond_to? :set_notice_receiver + + raw_connection.set_notice_receiver do |result| + Rails.logger.info("Postgres notice: #{result.error_message}\n#{caller.join("\n")}") + end + } + end +end