diff options
Diffstat (limited to 'activejob')
28 files changed, 1303 insertions, 169 deletions
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index a3d13ad162..57a62e963d 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,62 @@ +* `ActionDispatch::IntegrationTest` includes `ActiveJob::TestHelper` module by default. + + *Ricardo Díaz* + +* Added `enqueue_retry.active_job`, `retry_stopped.active_job`, and `discard.active_job` hooks. + + *steves* + +* Allow `assert_performed_with` to be called without a block. + + *bogdanvlviv* + +* Execution of `assert_performed_jobs`, and `assert_no_performed_jobs` + without a block should respect passed `:except`, `:only`, and `:queue` options. + + *bogdanvlviv* + +* Allow `:queue` option to job assertions and helpers. + + *bogdanvlviv* + +* Allow `perform_enqueued_jobs` to be called without a block. + + Performs all of the jobs that have been enqueued up to this point in the test. + + *Kevin Deisz* + +* Move `enqueue`/`enqueue_at` notifications to an around callback. + + Improves timing accuracy over the old after callback by including + time spent writing to the adapter's IO implementation. + + *Zach Kemp* + +* Allow call `assert_enqueued_with` with no block. + + Example: + ``` + def test_assert_enqueued_with + MyJob.perform_later(1,2,3) + assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') + + MyJob.set(wait_until: Date.tomorrow.noon).perform_later + assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) + end + ``` + + *bogdanvlviv* + +* Allow passing multiple exceptions to `retry_on`, and `discard_on`. + + *George Claghorn* + +* Pass the error instance as the second parameter of block executed by `discard_on`. + + Fixes #32853. + + *Yuji Yaginuma* + * Remove support for Qu gem. Reasons are that the Qu gem wasn't compatible since Rails 5.1, diff --git a/activejob/Rakefile b/activejob/Rakefile index b4da75adab..0f88b22e8d 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -38,7 +38,7 @@ namespace :test do t.libs << "test" t.test_files = FileList["test/cases/**/*_test.rb"] t.verbose = true - t.warning = false + t.warning = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end @@ -56,7 +56,7 @@ namespace :test do t.libs << "test" t.test_files = FileList["test/integration/**/*_test.rb"] t.verbose = true - t.warning = false + t.warning = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end end diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 86bb0c5540..b344c44aef 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -24,18 +24,20 @@ module ActiveJob module Arguments extend self # :nodoc: - TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] + PERMITTED_TYPES = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] - # Serializes a set of arguments. Whitelisted types are returned - # as-is. Arrays/Hashes are serialized element by element. - # All other types are serialized using GlobalID. + # Serializes a set of arguments. Intrinsic types that can safely be + # serialized without mutation are returned as-is. Arrays/Hashes are + # serialized element by element. All other types are serialized using + # GlobalID. def serialize(arguments) arguments.map { |argument| serialize_argument(argument) } end - # Deserializes a set of arguments. Whitelisted types are returned - # as-is. Arrays/Hashes are deserialized element by element. - # All other types are deserialized using GlobalID. + # Deserializes a set of arguments. Intrinsic types that can safely be + # deserialized without mutation are returned as-is. Arrays/Hashes are + # deserialized element by element. All other types are deserialized using + # GlobalID. def deserialize(arguments) arguments.map { |argument| deserialize_argument(argument) } rescue @@ -64,7 +66,7 @@ module ActiveJob def serialize_argument(argument) case argument - when *TYPE_WHITELIST + when *PERMITTED_TYPES argument when GlobalID::Identification convert_to_global_id_hash(argument) @@ -88,7 +90,7 @@ module ActiveJob case argument when String GlobalID::Locator.locate(argument) || argument - when *TYPE_WHITELIST + when *PERMITTED_TYPES argument when Array argument.map { |arg| deserialize_argument(arg) } diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index 2b2a59e969..95b1062701 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -60,7 +60,6 @@ module ActiveJob #:nodoc: # * SerializationError - Error class for serialization errors. class Base include Core - include Serializers include QueueAdapter include QueueName include QueuePriority diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index da841ae45b..61d402cfca 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -88,7 +88,7 @@ module ActiveJob "provider_job_id" => provider_job_id, "queue_name" => queue_name, "priority" => priority, - "arguments" => serialize_arguments(arguments), + "arguments" => serialize_arguments_if_needed(arguments), "executions" => executions, "locale" => I18n.locale.to_s, "timezone" => Time.zone.try(:name) @@ -133,19 +133,31 @@ module ActiveJob end private + def serialize_arguments_if_needed(arguments) + if arguments_serialized? + @serialized_arguments + else + serialize_arguments(arguments) + end + end + def deserialize_arguments_if_needed - if defined?(@serialized_arguments) && @serialized_arguments.present? + if arguments_serialized? @arguments = deserialize_arguments(@serialized_arguments) @serialized_arguments = nil end end - def serialize_arguments(serialized_args) - Arguments.serialize(serialized_args) + def serialize_arguments(arguments) + Arguments.serialize(arguments) end def deserialize_arguments(serialized_args) Arguments.deserialize(serialized_args) end + + def arguments_serialized? + defined?(@serialized_arguments) && @serialized_arguments + end end end diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb index ae700848d0..bc9e168971 100644 --- a/activejob/lib/active_job/exceptions.rb +++ b/activejob/lib/active_job/exceptions.rb @@ -9,6 +9,7 @@ module ActiveJob module ClassMethods # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts. + # The number of attempts includes the total executions of a job, not just the retried executions. # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a # holding queue for inspection. @@ -21,7 +22,8 @@ module ActiveJob # as a computing proc that the number of executions so far as an argument, or as a symbol reference of # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt> # (first wait 3s, then 18s, then 83s, etc) - # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts) + # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts), + # attempts here refers to the total number of times the job is executed, not just retried executions # * <tt>:queue</tt> - Re-enqueues the job on a different queue # * <tt>:priority</tt> - Re-enqueues the job with a different priority # @@ -30,8 +32,8 @@ module ActiveJob # class RemoteServiceJob < ActiveJob::Base # retry_on CustomAppException # defaults to 3s wait, 5 attempts # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 } - # retry_on(YetAnotherCustomAppException) do |job, exception| - # ExceptionNotifier.caught(exception) + # retry_on(YetAnotherCustomAppException) do |job, error| + # ExceptionNotifier.caught(error) # end # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10 @@ -42,16 +44,17 @@ module ActiveJob # # Might raise Net::OpenTimeout when the remote service is down # end # end - def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) - rescue_from exception do |error| + def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) + rescue_from(*exceptions) do |error| if executions < attempts - logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}." - retry_job wait: determine_delay(wait), queue: queue, priority: priority + retry_job wait: determine_delay(wait), queue: queue, priority: priority, error: error else if block_given? - yield self, error + instrument :retry_stopped, error: error do + yield self, error + end else - logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}." + instrument :retry_stopped, error: error raise error end end @@ -67,8 +70,8 @@ module ActiveJob # # class SearchIndexingJob < ActiveJob::Base # discard_on ActiveJob::DeserializationError - # discard_on(CustomAppException) do |job, exception| - # ExceptionNotifier.caught(exception) + # discard_on(CustomAppException) do |job, error| + # ExceptionNotifier.caught(error) # end # # def perform(record) @@ -76,12 +79,10 @@ module ActiveJob # # Might raise CustomAppException for something domain specific # end # end - def discard_on(exception) - rescue_from exception do |error| - if block_given? - yield self, exception - else - logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}." + def discard_on(*exceptions) + rescue_from(*exceptions) do |error| + instrument :discard, error: error do + yield self, error if block_given? end end end @@ -109,7 +110,9 @@ module ActiveJob # end # end def retry_job(options = {}) - enqueue options + instrument :enqueue_retry, options.slice(:error, :wait) do + enqueue options + end end private @@ -130,5 +133,11 @@ module ActiveJob raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}" end end + + def instrument(name, error: nil, wait: nil, &block) + payload = { job: self, adapter: self.class.queue_adapter, error: error, wait: wait } + + ActiveSupport::Notifications.instrument("#{name}.active_job", payload, &block) + end end end diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb index d75be376ec..f5a343311f 100644 --- a/activejob/lib/active_job/execution.rb +++ b/activejob/lib/active_job/execution.rb @@ -31,11 +31,11 @@ module ActiveJob # # MyJob.new(*args).perform_now def perform_now + # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters + self.executions = (executions || 0) + 1 + deserialize_arguments_if_needed run_callbacks :perform do - # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters - self.executions = (executions || 0) + 1 - perform(*arguments) end rescue => exception diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb index 3312857ac7..0abee4ed98 100644 --- a/activejob/lib/active_job/logging.rb +++ b/activejob/lib/active_job/logging.rb @@ -27,13 +27,13 @@ module ActiveJob end end - after_enqueue do |job| + around_enqueue do |job, block| if job.scheduled_at - ActiveSupport::Notifications.instrument "enqueue_at.active_job", - adapter: job.class.queue_adapter, job: job + ActiveSupport::Notifications.instrument("enqueue_at.active_job", + adapter: job.class.queue_adapter, job: job, &block) else - ActiveSupport::Notifications.instrument "enqueue.active_job", - adapter: job.class.queue_adapter, job: job + ActiveSupport::Notifications.instrument("enqueue.active_job", + adapter: job.class.queue_adapter, job: job, &block) end end end @@ -88,6 +88,34 @@ module ActiveJob end end + def enqueue_retry(event) + job = event.payload[:job] + ex = event.payload[:error] + wait = event.payload[:wait] + + error do + "Retrying #{job.class} in #{wait.inspect} seconds, due to a #{ex&.class.inspect}. The original exception was #{ex&.cause.inspect}." + end + end + + def retry_stopped(event) + job = event.payload[:job] + ex = event.payload[:error] + + error do + "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts. The original exception was #{ex.cause.inspect}." + end + end + + def discard(event) + job = event.payload[:job] + ex = event.payload[:error] + + error do + "Discarded #{job.class} due to a #{ex.class}. The original exception was #{ex.cause.inspect}." + end + end + private def queue_name(event) event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})" diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index 7854467cc1..00c7b407b1 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -15,7 +15,7 @@ module ActiveJob # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html] # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html] - # * Please Note: We are not accepting pull requests for new adapters. See the README for more details. + # * Please Note: We are not accepting pull requests for new adapters. See the {README}[link:files/activejob/README_md.html] for more details. # # === Backends Features # diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb index 0ba93c6e0b..7dc49310ac 100644 --- a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb @@ -16,12 +16,12 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :backburner class BackburnerAdapter def enqueue(job) #:nodoc: - Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name + Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority) end def enqueue_at(job, timestamp) #:nodoc: delay = timestamp - Time.current.to_f - Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay + Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay) end class JobWrapper #:nodoc: diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb index 3d0b590212..ca04dc943c 100644 --- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb @@ -16,7 +16,7 @@ module ActiveJob end def enqueue_at(*) #:nodoc: - raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html" + raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at https://guides.rubyonrails.org/active_job_basics.html" end end end diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb index 885f9ff01c..f73ad444ba 100644 --- a/activejob/lib/active_job/queue_adapters/test_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -12,7 +12,7 @@ module ActiveJob # # Rails.application.config.active_job.queue_adapter = :test class TestAdapter - attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject) + attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue) attr_writer(:enqueued_jobs, :performed_jobs) # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. @@ -29,14 +29,14 @@ module ActiveJob return if filtered?(job) job_data = job_to_hash(job) - enqueue_or_perform(perform_enqueued_jobs, job, job_data) + perform_or_enqueue(perform_enqueued_jobs, job, job_data) end def enqueue_at(job, timestamp) #:nodoc: return if filtered?(job) job_data = job_to_hash(job, at: timestamp) - enqueue_or_perform(perform_enqueued_at_jobs, job, job_data) + perform_or_enqueue(perform_enqueued_at_jobs, job, job_data) end private @@ -44,7 +44,7 @@ module ActiveJob { job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras) end - def enqueue_or_perform(perform, job, job_data) + def perform_or_enqueue(perform, job, job_data) if perform performed_jobs << job_data Base.execute job.serialize @@ -54,12 +54,20 @@ module ActiveJob end def filtered?(job) + filtered_queue?(job) || filtered_job_class?(job) + end + + def filtered_queue?(job) + if queue + job.queue_name != queue.to_s + end + end + + def filtered_job_class?(job) if filter !Array(filter).include?(job.class) elsif reject Array(reject).include?(job.class) - else - false end end end diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index d0294854d3..ecc0908d5f 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -30,6 +30,10 @@ module ActiveJob send(k, v) if respond_to? k end end + + ActiveSupport.on_load(:action_dispatch_integration_test) do + include ActiveJob::TestHelper + end end initializer "active_job.set_reloader_hook" do |app| diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index df66e66659..a5d90f48b8 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -7,7 +7,6 @@ module ActiveJob # and to add new ones. It also has helpers to serialize/deserialize objects. module Serializers # :nodoc: extend ActiveSupport::Autoload - extend ActiveSupport::Concern autoload :ObjectSerializer autoload :SymbolSerializer diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 1cd2c40c15..9efc8c0c12 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -52,7 +52,7 @@ module ActiveJob queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter } end - # Specifies the queue adapter to use with all active job test helpers. + # Specifies the queue adapter to use with all Active Job test helpers. # # Returns an instance of the queue adapter and defaults to # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>. @@ -117,14 +117,18 @@ module ActiveJob # end def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil) if block_given? - original_count = enqueued_jobs_size(only: only, except: except, queue: queue) + original_count = enqueued_jobs_with(only: only, except: except, queue: queue) + yield - new_count = enqueued_jobs_size(only: only, except: except, queue: queue) - assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" + + new_count = enqueued_jobs_with(only: only, except: except, queue: queue) + + actual_count = new_count - original_count else - actual_count = enqueued_jobs_size(only: only, except: except, queue: queue) - assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" + actual_count = enqueued_jobs_with(only: only, except: except, queue: queue) end + + assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" end # Asserts that no jobs have been enqueued. @@ -159,16 +163,24 @@ module ActiveJob # end # end # + # It can be asserted that no jobs are enqueued to a specific queue: + # + # def test_no_logging + # assert_no_enqueued_jobs queue: 'default' do + # LoggingJob.set(queue: :some_queue).perform_later + # end + # end + # # Note: This assertion is simply a shortcut for: # # assert_enqueued_jobs 0, &block - def assert_no_enqueued_jobs(only: nil, except: nil, &block) - assert_enqueued_jobs 0, only: only, except: except, &block + def assert_no_enqueued_jobs(only: nil, except: nil, queue: nil, &block) + assert_enqueued_jobs 0, only: only, except: except, queue: queue, &block end # Asserts that the number of performed jobs matches the given number. # If no block is passed, <tt>perform_enqueued_jobs</tt> - # must be called around the job call. + # must be called around or after the job call. # # def test_jobs # assert_performed_jobs 0 @@ -178,10 +190,11 @@ module ActiveJob # end # assert_performed_jobs 1 # - # perform_enqueued_jobs do - # HelloJob.perform_later('yves') - # assert_performed_jobs 2 - # end + # HelloJob.perform_later('yves') + # + # perform_enqueued_jobs + # + # assert_performed_jobs 2 # end # # If a block is passed, that block should cause the specified number of @@ -198,7 +211,7 @@ module ActiveJob # end # end # - # The block form supports filtering. If the :only option is specified, + # This method also supports filtering. If the +:only+ option is specified, # then only the listed job(s) will be performed. # # def test_hello_job @@ -208,7 +221,7 @@ module ActiveJob # end # end # - # Also if the :except option is specified, + # Also if the +:except+ option is specified, # then the job(s) except specific class will be performed. # # def test_hello_job @@ -229,17 +242,30 @@ module ActiveJob # end # end # end - def assert_performed_jobs(number, only: nil, except: nil) + # + # If the +:queue+ option is specified, + # then only the job(s) enqueued to a specific queue will be performed. + # + # def test_assert_performed_jobs_with_queue_option + # assert_performed_jobs 1, queue: :some_queue do + # HelloJob.set(queue: :some_queue).perform_later("jeremy") + # HelloJob.set(queue: :other_queue).perform_later("bogdan") + # end + # end + def assert_performed_jobs(number, only: nil, except: nil, queue: nil, &block) if block_given? original_count = performed_jobs.size - perform_enqueued_jobs(only: only, except: except) { yield } + + perform_enqueued_jobs(only: only, except: except, queue: queue, &block) + new_count = performed_jobs.size - assert_equal number, new_count - original_count, - "#{number} jobs expected, but #{new_count - original_count} were performed" + + performed_jobs_size = new_count - original_count else - performed_jobs_size = performed_jobs.size - assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed" + performed_jobs_size = performed_jobs_with(only: only, except: except, queue: queue) end + + assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed" end # Asserts that no jobs have been performed. @@ -261,7 +287,7 @@ module ActiveJob # end # end # - # The block form supports filtering. If the :only option is specified, + # The block form supports filtering. If the +:only+ option is specified, # then only the listed job(s) will not be performed. # # def test_no_logging @@ -270,7 +296,7 @@ module ActiveJob # end # end # - # Also if the :except option is specified, + # Also if the +:except+ option is specified, # then the job(s) except specific class will not be performed. # # def test_no_logging @@ -279,14 +305,34 @@ module ActiveJob # end # end # + # If the +:queue+ option is specified, + # then only the job(s) enqueued to a specific queue will not be performed. + # + # def test_assert_no_performed_jobs_with_queue_option + # assert_no_performed_jobs queue: :some_queue do + # HelloJob.set(queue: :other_queue).perform_later("jeremy") + # end + # end + # # Note: This assertion is simply a shortcut for: # # assert_performed_jobs 0, &block - def assert_no_performed_jobs(only: nil, except: nil, &block) - assert_performed_jobs 0, only: only, except: except, &block + def assert_no_performed_jobs(only: nil, except: nil, queue: nil, &block) + assert_performed_jobs 0, only: only, except: except, queue: queue, &block end - # Asserts that the job passed in the block has been enqueued with the given arguments. + # Asserts that the job has been enqueued with the given arguments. + # + # def test_assert_enqueued_with + # MyJob.perform_later(1,2,3) + # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') + # + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) + # end + # + # If a block is passed, that block should cause the job to be + # enqueued with the given arguments. # # def test_assert_enqueued_with # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do @@ -298,19 +344,47 @@ module ActiveJob # end # end def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil) - original_enqueued_jobs_count = enqueued_jobs.count expected = { job: job, args: args, at: at, queue: queue }.compact - serialized_args = serialize_args_for_assertion(expected) - yield - in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) - matching_job = in_block_jobs.find do |in_block_job| - serialized_args.all? { |key, value| value == in_block_job[key] } + expected_args = prepare_args_for_assertion(expected) + + if block_given? + original_enqueued_jobs_count = enqueued_jobs.count + + yield + + jobs = enqueued_jobs.drop(original_enqueued_jobs_count) + else + jobs = enqueued_jobs end + + matching_job = jobs.find do |enqueued_job| + deserialized_job = deserialize_args_for_assertion(enqueued_job) + expected_args.all? { |key, value| value == deserialized_job[key] } + end + assert matching_job, "No enqueued job found with #{expected}" instantiate_job(matching_job) end - # Asserts that the job passed in the block has been performed with the given arguments. + # Asserts that the job has been performed with the given arguments. + # + # def test_assert_performed_with + # MyJob.perform_later(1,2,3) + # + # perform_enqueued_jobs + # + # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') + # + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # + # perform_enqueued_jobs + # + # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) + # end + # + # If a block is passed, that block performs all of the jobs that were + # enqueued throughout the duration of the block and asserts that + # the job has been performed with the given arguments in the block. # # def test_assert_performed_with # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do @@ -321,20 +395,32 @@ module ActiveJob # MyJob.set(wait_until: Date.tomorrow.noon).perform_later # end # end - def assert_performed_with(job: nil, args: nil, at: nil, queue: nil) - original_performed_jobs_count = performed_jobs.count + def assert_performed_with(job: nil, args: nil, at: nil, queue: nil, &block) expected = { job: job, args: args, at: at, queue: queue }.compact - serialized_args = serialize_args_for_assertion(expected) - perform_enqueued_jobs { yield } - in_block_jobs = performed_jobs.drop(original_performed_jobs_count) - matching_job = in_block_jobs.find do |in_block_job| - serialized_args.all? { |key, value| value == in_block_job[key] } + expected_args = prepare_args_for_assertion(expected) + + if block_given? + original_performed_jobs_count = performed_jobs.count + + perform_enqueued_jobs(&block) + + jobs = performed_jobs.drop(original_performed_jobs_count) + else + jobs = performed_jobs + end + + matching_job = jobs.find do |enqueued_job| + deserialized_job = deserialize_args_for_assertion(enqueued_job) + expected_args.all? { |key, value| value == deserialized_job[key] } end + assert matching_job, "No performed job found with #{expected}" instantiate_job(matching_job) end - # Performs all enqueued jobs in the duration of the block. + # Performs all enqueued jobs. If a block is given, performs all of the jobs + # that were enqueued throughout the duration of the block. If a block is + # not given, performs all of the enqueued jobs up to this point in the test. # # def test_perform_enqueued_jobs # perform_enqueued_jobs do @@ -343,6 +429,14 @@ module ActiveJob # assert_performed_jobs 1 # end # + # def test_perform_enqueued_jobs_without_block + # MyJob.perform_later(1, 2, 3) + # + # perform_enqueued_jobs + # + # assert_performed_jobs 1 + # end + # # This method also supports filtering. If the +:only+ option is specified, # then only the listed job(s) will be performed. # @@ -365,24 +459,42 @@ module ActiveJob # assert_performed_jobs 1 # end # - def perform_enqueued_jobs(only: nil, except: nil) + # If the +:queue+ option is specified, + # then only the job(s) enqueued to a specific queue will be performed. + # + # def test_perform_enqueued_jobs_with_queue + # perform_enqueued_jobs queue: :some_queue do + # MyJob.set(queue: :some_queue).perform_later(1, 2, 3) # will be performed + # HelloJob.set(queue: :other_queue).perform_later(1, 2, 3) # will not be performed + # end + # assert_performed_jobs 1 + # end + # + def perform_enqueued_jobs(only: nil, except: nil, queue: nil) + return flush_enqueued_jobs(only: only, except: except, queue: queue) unless block_given? + validate_option(only: only, except: except) + old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs old_filter = queue_adapter.filter old_reject = queue_adapter.reject + old_queue = queue_adapter.queue begin queue_adapter.perform_enqueued_jobs = true queue_adapter.perform_enqueued_at_jobs = true queue_adapter.filter = only queue_adapter.reject = except + queue_adapter.queue = queue + yield ensure queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs queue_adapter.filter = old_filter queue_adapter.reject = old_reject + queue_adapter.queue = old_queue end end @@ -404,26 +516,53 @@ module ActiveJob performed_jobs.clear end - def enqueued_jobs_size(only: nil, except: nil, queue: nil) + def jobs_with(jobs, only: nil, except: nil, queue: nil) validate_option(only: only, except: except) - enqueued_jobs.count do |job| + + jobs.count do |job| job_class = job.fetch(:job) + if only next false unless Array(only).include?(job_class) elsif except next false if Array(except).include?(job_class) end + if queue next false unless queue.to_s == job.fetch(:queue, job_class.queue_name) end + + yield job if block_given? + true end end - def serialize_args_for_assertion(args) - args.dup.tap do |serialized_args| - serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args] - serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at] + def enqueued_jobs_with(only: nil, except: nil, queue: nil, &block) + jobs_with(enqueued_jobs, only: only, except: except, queue: queue, &block) + end + + def performed_jobs_with(only: nil, except: nil, queue: nil, &block) + jobs_with(performed_jobs, only: only, except: except, queue: queue, &block) + end + + def flush_enqueued_jobs(only: nil, except: nil, queue: nil) + enqueued_jobs_with(only: only, except: except, queue: queue) do |payload| + args = ActiveJob::Arguments.deserialize(payload[:args]) + instantiate_job(payload.merge(args: args)).perform_now + queue_adapter.performed_jobs << payload + end + end + + def prepare_args_for_assertion(args) + args.dup.tap do |arguments| + arguments[:at] = arguments[:at].to_f if arguments[:at] + end + end + + def deserialize_args_for_assertion(job) + job.dup.tap do |new_job| + new_job[:args] = ActiveJob::Arguments.deserialize(new_job[:args]) if new_job[:args] end end diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb index e5f1f087fe..f07529d743 100644 --- a/activejob/test/cases/argument_serialization_test.rb +++ b/activejob/test/cases/argument_serialization_test.rb @@ -121,8 +121,10 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end test "should not allow reserved hash keys" do - ["_aj_globalid", :_aj_globalid, "_aj_symbol_keys", :_aj_symbol_keys, - "_aj_hash_with_indifferent_access", :_aj_hash_with_indifferent_access].each do |key| + ["_aj_globalid", :_aj_globalid, + "_aj_symbol_keys", :_aj_symbol_keys, + "_aj_hash_with_indifferent_access", :_aj_hash_with_indifferent_access, + "_aj_serialized", :_aj_serialized].each do |key| assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [key => 1] end diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb index bc33d79f61..37bb65538a 100644 --- a/activejob/test/cases/exceptions_test.rb +++ b/activejob/test/cases/exceptions_test.rb @@ -2,6 +2,7 @@ require "helper" require "jobs/retry_job" +require "models/person" class ExceptionsTest < ActiveJob::TestCase setup do @@ -61,7 +62,7 @@ class ExceptionsTest < ActiveJob::TestCase test "custom handling of discarded job" do perform_enqueued_jobs do RetryJob.perform_later "CustomDiscardableError", 2 - assert_equal "Dealt with a job that was discarded in a custom way", JobBuffer.last_value + assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value end end @@ -113,4 +114,29 @@ class ExceptionsTest < ActiveJob::TestCase end end end + + test "successfully retry job throwing one of two retryable exceptions" do + perform_enqueued_jobs do + RetryJob.perform_later "SecondRetryableErrorOfTwo", 3 + + assert_equal [ + "Raised SecondRetryableErrorOfTwo for the 1st time", + "Raised SecondRetryableErrorOfTwo for the 2nd time", + "Successfully completed job" ], JobBuffer.values + end + end + + test "discard job throwing one of two discardable exceptions" do + perform_enqueued_jobs do + RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2 + assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values + end + end + + test "successfully retry job throwing DeserializationError" do + perform_enqueued_jobs do + RetryJob.perform_later Person.new(404), 5 + assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values + end + end end diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb index 5c9994508e..86f3651564 100644 --- a/activejob/test/cases/job_serialization_test.rb +++ b/activejob/test/cases/job_serialization_test.rb @@ -23,16 +23,16 @@ class JobSerializationTest < ActiveSupport::TestCase test "serialize and deserialize are symmetric" do # Round trip a job in memory only - h1 = HelloJob.new - h1.deserialize(h1.serialize) + h1 = HelloJob.new("Rafael") + h2 = HelloJob.deserialize(h1.serialize) + assert_equal h1.serialize, h2.serialize # Now verify it's identical to a JSON round trip. # We don't want any non-native JSON elements in the job hash, # like symbols. - payload = JSON.dump(h1.serialize) - h2 = HelloJob.new - h2.deserialize(JSON.load(payload)) - assert_equal h1.serialize, h2.serialize + payload = JSON.dump(h2.serialize) + h3 = HelloJob.deserialize(JSON.load(payload)) + assert_equal h2.serialize, h3.serialize end test "deserialize sets locale" do diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb index 1f8c4a5573..b5bf40c83b 100644 --- a/activejob/test/cases/logging_test.rb +++ b/activejob/test/cases/logging_test.rb @@ -8,9 +8,11 @@ require "jobs/logging_job" require "jobs/overridden_logging_job" require "jobs/nested_job" require "jobs/rescue_job" +require "jobs/retry_job" require "models/person" class LoggingTest < ActiveSupport::TestCase + include ActiveJob::TestHelper include ActiveSupport::LogSubscriber::TestHelper include ActiveSupport::Logger::Severity @@ -45,19 +47,31 @@ class LoggingTest < ActiveSupport::TestCase ActiveJob::Base.logger = logger end + def subscribed + [].tap do |events| + ActiveSupport::Notifications.subscribed(-> (*args) { events << args }, /enqueue.*\.active_job/) do + yield + end + end + end + def test_uses_active_job_as_tag HelloJob.perform_later "Cristian" assert_match(/\[ActiveJob\]/, @logger.messages) end def test_uses_job_name_as_tag - LoggingJob.perform_later "Dummy" - assert_match(/\[LoggingJob\]/, @logger.messages) + perform_enqueued_jobs do + LoggingJob.perform_later "Dummy" + assert_match(/\[LoggingJob\]/, @logger.messages) + end end def test_uses_job_id_as_tag - LoggingJob.perform_later "Dummy" - assert_match(/\[LOGGING-JOB-ID\]/, @logger.messages) + perform_enqueued_jobs do + LoggingJob.perform_later "Dummy" + assert_match(/\[LOGGING-JOB-ID\]/, @logger.messages) + end end def test_logs_correct_queue_name @@ -70,55 +84,72 @@ class LoggingTest < ActiveSupport::TestCase end def test_globalid_parameter_logging - person = Person.new(123) - LoggingJob.perform_later person - assert_match(%r{Enqueued.*gid://aj/Person/123}, @logger.messages) - assert_match(%r{Dummy, here is it: #<Person:.*>}, @logger.messages) - assert_match(%r{Performing.*gid://aj/Person/123}, @logger.messages) + perform_enqueued_jobs do + person = Person.new(123) + LoggingJob.perform_later person + assert_match(%r{Enqueued.*gid://aj/Person/123}, @logger.messages) + assert_match(%r{Dummy, here is it: #<Person:.*>}, @logger.messages) + assert_match(%r{Performing.*gid://aj/Person/123}, @logger.messages) + end end def test_globalid_nested_parameter_logging - person = Person.new(123) - LoggingJob.perform_later(person: person) - assert_match(%r{Enqueued.*gid://aj/Person/123}, @logger.messages) - assert_match(%r{Dummy, here is it: .*#<Person:.*>}, @logger.messages) - assert_match(%r{Performing.*gid://aj/Person/123}, @logger.messages) + perform_enqueued_jobs do + person = Person.new(123) + LoggingJob.perform_later(person: person) + assert_match(%r{Enqueued.*gid://aj/Person/123}, @logger.messages) + assert_match(%r{Dummy, here is it: .*#<Person:.*>}, @logger.messages) + assert_match(%r{Performing.*gid://aj/Person/123}, @logger.messages) + end end def test_enqueue_job_logging - HelloJob.perform_later "Cristian" + events = subscribed { HelloJob.perform_later "Cristian" } assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages) + assert_equal(events.count, 1) + key, * = events.first + assert_equal(key, "enqueue.active_job") end def test_perform_job_logging - LoggingJob.perform_later "Dummy" - assert_match(/Performing LoggingJob \(Job ID: .*?\) from .*? with arguments:.*Dummy/, @logger.messages) - assert_match(/Dummy, here is it: Dummy/, @logger.messages) - assert_match(/Performed LoggingJob \(Job ID: .*?\) from .*? in .*ms/, @logger.messages) + perform_enqueued_jobs do + LoggingJob.perform_later "Dummy" + assert_match(/Performing LoggingJob \(Job ID: .*?\) from .*? with arguments:.*Dummy/, @logger.messages) + assert_match(/Dummy, here is it: Dummy/, @logger.messages) + assert_match(/Performed LoggingJob \(Job ID: .*?\) from .*? in .*ms/, @logger.messages) + end end def test_perform_nested_jobs_logging - NestedJob.perform_later - assert_match(/\[LoggingJob\] \[.*?\]/, @logger.messages) - assert_match(/\[ActiveJob\] Enqueued NestedJob \(Job ID: .*\) to/, @logger.messages) - assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performing NestedJob \(Job ID: .*?\) from/, @logger.messages) - assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Enqueued LoggingJob \(Job ID: .*?\) to .* with arguments: "NestedJob"/, @logger.messages) - assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performing LoggingJob \(Job ID: .*?\) from .* with arguments: "NestedJob"/, @logger.messages) - assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Dummy, here is it: NestedJob/, @logger.messages) - assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performed LoggingJob \(Job ID: .*?\) from .* in/, @logger.messages) - assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performed NestedJob \(Job ID: .*?\) from .* in/, @logger.messages) + perform_enqueued_jobs do + NestedJob.perform_later + assert_match(/\[LoggingJob\] \[.*?\]/, @logger.messages) + assert_match(/\[ActiveJob\] Enqueued NestedJob \(Job ID: .*\) to/, @logger.messages) + assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performing NestedJob \(Job ID: .*?\) from/, @logger.messages) + assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Enqueued LoggingJob \(Job ID: .*?\) to .* with arguments: "NestedJob"/, @logger.messages) + assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performing LoggingJob \(Job ID: .*?\) from .* with arguments: "NestedJob"/, @logger.messages) + assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Dummy, here is it: NestedJob/, @logger.messages) + assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performed LoggingJob \(Job ID: .*?\) from .* in/, @logger.messages) + assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performed NestedJob \(Job ID: .*?\) from .* in/, @logger.messages) + end end def test_enqueue_at_job_logging - HelloJob.set(wait_until: 24.hours.from_now).perform_later "Cristian" + events = subscribed { HelloJob.set(wait_until: 24.hours.from_now).perform_later "Cristian" } assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages) + assert_equal(events.count, 1) + key, * = events.first + assert_equal(key, "enqueue_at.active_job") rescue NotImplementedError skip end def test_enqueue_in_job_logging - HelloJob.set(wait: 2.seconds).perform_later "Cristian" + events = subscribed { HelloJob.set(wait: 2.seconds).perform_later "Cristian" } assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages) + assert_equal(events.count, 1) + key, * = events.first + assert_equal(key, "enqueue_at.active_job") rescue NotImplementedError skip end @@ -129,9 +160,45 @@ class LoggingTest < ActiveSupport::TestCase end def test_job_error_logging - RescueJob.perform_later "other" + perform_enqueued_jobs { RescueJob.perform_later "other" } rescue RescueJob::OtherError assert_match(/Performing RescueJob \(Job ID: .*?\) from .*? with arguments:.*other/, @logger.messages) assert_match(/Error performing RescueJob \(Job ID: .*?\) from .*? in .*ms: RescueJob::OtherError \(Bad hair\):\n.*\brescue_job\.rb:\d+:in `perform'/, @logger.messages) end + + def test_enqueue_retry_logging + perform_enqueued_jobs do + RetryJob.perform_later "DefaultsError", 2 + assert_match(/Retrying RetryJob in \d+ seconds, due to a DefaultsError\. The original exception was nil\./, @logger.messages) + end + end + + def test_enqueue_retry_logging_on_retry_job + perform_enqueued_jobs { RescueJob.perform_later "david" } + assert_match(/Retrying RescueJob in nil seconds, due to a nil\. The original exception was nil\./, @logger.messages) + end + + def test_retry_stopped_logging + perform_enqueued_jobs do + RetryJob.perform_later "CustomCatchError", 6 + assert_match(/Stopped retrying RetryJob due to a CustomCatchError, which reoccurred on \d+ attempts\. The original exception was #<CustomCatchError: CustomCatchError>\./, @logger.messages) + end + end + + def test_retry_stopped_logging_without_block + perform_enqueued_jobs do + begin + RetryJob.perform_later "DefaultsError", 6 + rescue DefaultsError + assert_match(/Stopped retrying RetryJob due to a DefaultsError, which reoccurred on \d+ attempts\. The original exception was #<DefaultsError: DefaultsError>\./, @logger.messages) + end + end + end + + def test_discard_logging + perform_enqueued_jobs do + RetryJob.perform_later "DiscardableError", 2 + assert_match(/Discarded RetryJob due to a DiscardableError\. The original exception was nil\./, @logger.messages) + end + end end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 66bcd8f3a0..018c40c28f 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -8,6 +8,7 @@ require "jobs/logging_job" require "jobs/nested_job" require "jobs/rescue_job" require "jobs/inherited_job" +require "jobs/multiple_kwargs_job" require "models/person" class EnqueuedJobsTest < ActiveJob::TestCase @@ -389,13 +390,91 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_match(/`:only` and `:except`/, error.message) end - def test_assert_enqueued_job + def test_assert_no_enqueued_jobs_with_queue_option + assert_nothing_raised do + assert_no_enqueued_jobs queue: :default do + HelloJob.set(queue: :other_queue).perform_later + LoggingJob.set(queue: :other_queue).perform_later + end + end + end + + def test_assert_no_enqueued_jobs_with_queue_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_jobs queue: :other_queue do + HelloJob.set(queue: :other_queue).perform_later + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_enqueued_jobs_with_only_and_queue_option + assert_nothing_raised do + assert_no_enqueued_jobs only: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later + HelloJob.set(queue: :other_queue).perform_later + LoggingJob.set(queue: :some_queue).perform_later + end + end + end + + def test_assert_no_enqueued_jobs_with_only_and_queue_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_jobs only: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later + HelloJob.set(queue: :some_queue).perform_later + LoggingJob.set(queue: :some_queue).perform_later + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_enqueued_jobs_with_except_and_queue_option + assert_nothing_raised do + assert_no_enqueued_jobs except: LoggingJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later + HelloJob.set(queue: :other_queue).perform_later + LoggingJob.set(queue: :some_queue).perform_later + end + end + end + + def test_assert_no_enqueued_jobs_with_except_and_queue_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_jobs except: LoggingJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later + HelloJob.set(queue: :some_queue).perform_later + LoggingJob.set(queue: :some_queue).perform_later + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_enqueued_jobs_with_only_and_except_and_queue_option + error = assert_raise ArgumentError do + assert_no_enqueued_jobs only: HelloJob, except: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later + end + end + + assert_match(/`:only` and `:except`/, error.message) + end + + def test_assert_enqueued_with assert_enqueued_with(job: LoggingJob, queue: "default") do LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later end end - def test_assert_enqueued_job_returns + def test_assert_enqueued_with_with_no_block + LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later + assert_enqueued_with(job: LoggingJob, queue: "default") + end + + def test_assert_enqueued_with_returns job = assert_enqueued_with(job: LoggingJob) do LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3) end @@ -406,13 +485,28 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_equal [1, 2, 3], job.arguments end - def test_assert_enqueued_job_failure + def test_assert_enqueued_with_with_no_block_returns + LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3) + job = assert_enqueued_with(job: LoggingJob) + + assert_instance_of LoggingJob, job + assert_in_delta 5.minutes.from_now, job.scheduled_at, 1 + assert_equal "default", job.queue_name + assert_equal [1, 2, 3], job.arguments + end + + def test_assert_enqueued_with_failure assert_raise ActiveSupport::TestCase::Assertion do assert_enqueued_with(job: LoggingJob, queue: "default") do NestedJob.perform_later end end + assert_raise ActiveSupport::TestCase::Assertion do + LoggingJob.perform_later + assert_enqueued_with(job: LoggingJob) { } + end + error = assert_raise ActiveSupport::TestCase::Assertion do assert_enqueued_with(job: NestedJob, queue: "low") do NestedJob.perform_later @@ -422,7 +516,21 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_equal 'No enqueued job found with {:job=>NestedJob, :queue=>"low"}', error.message end - def test_assert_enqueued_job_args + def test_assert_enqueued_with_with_no_block_failure + assert_raise ActiveSupport::TestCase::Assertion do + NestedJob.perform_later + assert_enqueued_with(job: LoggingJob, queue: "default") + end + + error = assert_raise ActiveSupport::TestCase::Assertion do + NestedJob.perform_later + assert_enqueued_with(job: NestedJob, queue: "low") + end + + assert_equal 'No enqueued job found with {:job=>NestedJob, :queue=>"low"}', error.message + end + + def test_assert_enqueued_with_args assert_raise ArgumentError do assert_enqueued_with(class: LoggingJob) do NestedJob.set(wait_until: Date.tomorrow.noon).perform_later @@ -430,20 +538,44 @@ class EnqueuedJobsTest < ActiveJob::TestCase end end - def test_assert_enqueued_job_with_at_option + def test_assert_enqueued_with_with_no_block_args + assert_raise ArgumentError do + NestedJob.set(wait_until: Date.tomorrow.noon).perform_later + assert_enqueued_with(class: LoggingJob) + end + end + + def test_assert_enqueued_with_with_at_option assert_enqueued_with(job: HelloJob, at: Date.tomorrow.noon) do HelloJob.set(wait_until: Date.tomorrow.noon).perform_later end end - def test_assert_enqueued_job_with_global_id_args + def test_assert_enqueued_with_with_no_block_with_at_option + HelloJob.set(wait_until: Date.tomorrow.noon).perform_later + assert_enqueued_with(job: HelloJob, at: Date.tomorrow.noon) + end + + def test_assert_enqueued_with_with_hash_arg + assert_enqueued_with(job: MultipleKwargsJob, args: [{ argument1: 1, argument2: { a: 1, b: 2 } }]) do + MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1) + end + end + + def test_assert_enqueued_with_with_global_id_args ricardo = Person.new(9) assert_enqueued_with(job: HelloJob, args: [ricardo]) do HelloJob.perform_later(ricardo) end end - def test_assert_enqueued_job_failure_with_global_id_args + def test_assert_enqueued_with_with_no_block_with_global_id_args + ricardo = Person.new(9) + HelloJob.perform_later(ricardo) + assert_enqueued_with(job: HelloJob, args: [ricardo]) + end + + def test_assert_enqueued_with_failure_with_global_id_args ricardo = Person.new(9) wilma = Person.new(11) error = assert_raise ActiveSupport::TestCase::Assertion do @@ -455,7 +587,18 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_equal "No enqueued job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message end - def test_assert_enqueued_job_does_not_change_jobs_count + def test_assert_enqueued_with_failure_with_no_block_with_global_id_args + ricardo = Person.new(9) + wilma = Person.new(11) + error = assert_raise ActiveSupport::TestCase::Assertion do + HelloJob.perform_later(ricardo) + assert_enqueued_with(job: HelloJob, args: [wilma]) + end + + assert_equal "No enqueued job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message + end + + def test_assert_enqueued_with_does_not_change_jobs_count HelloJob.perform_later assert_enqueued_with(job: HelloJob) do HelloJob.perform_later @@ -463,10 +606,18 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_equal 2, queue_adapter.enqueued_jobs.count end + + def test_assert_enqueued_with_with_no_block_does_not_change_jobs_count + HelloJob.perform_later + HelloJob.perform_later + assert_enqueued_with(job: HelloJob) + + assert_equal 2, queue_adapter.enqueued_jobs.count + end end class PerformedJobsTest < ActiveJob::TestCase - def test_performed_enqueue_jobs_with_only_option_doesnt_leak_outside_the_block + def test_perform_enqueued_jobs_with_only_option_doesnt_leak_outside_the_block assert_nil queue_adapter.filter perform_enqueued_jobs only: HelloJob do assert_equal HelloJob, queue_adapter.filter @@ -474,7 +625,13 @@ class PerformedJobsTest < ActiveJob::TestCase assert_nil queue_adapter.filter end - def test_performed_enqueue_jobs_with_except_option_doesnt_leak_outside_the_block + def test_perform_enqueued_jobs_without_block_with_only_option_doesnt_leak + perform_enqueued_jobs only: HelloJob + + assert_nil queue_adapter.filter + end + + def test_perform_enqueued_jobs_with_except_option_doesnt_leak_outside_the_block assert_nil queue_adapter.reject perform_enqueued_jobs except: HelloJob do assert_equal HelloJob, queue_adapter.reject @@ -482,6 +639,150 @@ class PerformedJobsTest < ActiveJob::TestCase assert_nil queue_adapter.reject end + def test_perform_enqueued_jobs_without_block_with_except_option_doesnt_leak + perform_enqueued_jobs except: HelloJob + + assert_nil queue_adapter.reject + end + + def test_perform_enqueued_jobs_with_queue_option_doesnt_leak_outside_the_block + assert_nil queue_adapter.queue + perform_enqueued_jobs queue: :some_queue do + assert_equal :some_queue, queue_adapter.queue + end + assert_nil queue_adapter.queue + end + + def test_perform_enqueued_jobs_without_block_with_queue_option_doesnt_leak + perform_enqueued_jobs queue: :some_queue + + assert_nil queue_adapter.reject + end + + def test_perform_enqueued_jobs_with_block + perform_enqueued_jobs do + HelloJob.perform_later("kevin") + LoggingJob.perform_later("bogdan") + end + + assert_performed_jobs 2 + end + + def test_perform_enqueued_jobs_without_block + HelloJob.perform_later("kevin") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + assert_performed_jobs 2 + end + + def test_perform_enqueued_jobs_with_block_with_only_option + perform_enqueued_jobs only: LoggingJob do + HelloJob.perform_later("kevin") + LoggingJob.perform_later("bogdan") + end + + assert_performed_jobs 1 + assert_performed_jobs 1, only: LoggingJob + end + + def test_perform_enqueued_jobs_without_block_with_only_option + HelloJob.perform_later("kevin") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs only: LoggingJob + + assert_performed_jobs 1 + assert_performed_jobs 1, only: LoggingJob + end + + def test_perform_enqueued_jobs_with_block_with_except_option + perform_enqueued_jobs except: HelloJob do + HelloJob.perform_later("kevin") + LoggingJob.perform_later("bogdan") + end + + assert_performed_jobs 1 + assert_performed_jobs 1, only: LoggingJob + end + + def test_perform_enqueued_jobs_without_block_with_except_option + HelloJob.perform_later("kevin") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs except: HelloJob + + assert_performed_jobs 1 + assert_performed_jobs 1, only: LoggingJob + end + + def test_perform_enqueued_jobs_with_block_with_queue_option + perform_enqueued_jobs queue: :some_queue do + HelloJob.set(queue: :some_queue).perform_later("kevin") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.perform_later("bogdan") + end + + assert_performed_jobs 1 + assert_performed_jobs 1, only: HelloJob, queue: :some_queue + end + + def test_perform_enqueued_jobs_without_block_with_queue_option + HelloJob.set(queue: :some_queue).perform_later("kevin") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs queue: :some_queue + + assert_performed_jobs 1 + assert_performed_jobs 1, only: HelloJob, queue: :some_queue + end + + def test_perform_enqueued_jobs_with_block_with_only_and_queue_options + perform_enqueued_jobs only: HelloJob, queue: :other_queue do + HelloJob.set(queue: :some_queue).perform_later("kevin") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("bogdan") + end + + assert_performed_jobs 1 + assert_performed_jobs 1, only: HelloJob, queue: :other_queue + end + + def test_perform_enqueued_jobs_without_block_with_only_and_queue_options + HelloJob.set(queue: :some_queue).perform_later("kevin") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("bogdan") + + perform_enqueued_jobs only: HelloJob, queue: :other_queue + + assert_performed_jobs 1 + assert_performed_jobs 1, only: HelloJob, queue: :other_queue + end + + def test_perform_enqueued_jobs_with_block_with_except_and_queue_options + perform_enqueued_jobs except: HelloJob, queue: :other_queue do + HelloJob.set(queue: :other_queue).perform_later("kevin") + LoggingJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("bogdan") + end + + assert_performed_jobs 1 + assert_performed_jobs 1, only: LoggingJob, queue: :other_queue + end + + def test_perform_enqueued_jobs_without_block_with_except_and_queue_options + HelloJob.set(queue: :other_queue).perform_later("kevin") + LoggingJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("bogdan") + + perform_enqueued_jobs except: HelloJob, queue: :other_queue + + assert_performed_jobs 1 + assert_performed_jobs 1, only: LoggingJob, queue: :other_queue + end + def test_assert_performed_jobs assert_nothing_raised do assert_performed_jobs 1 do @@ -587,6 +888,28 @@ class PerformedJobsTest < ActiveJob::TestCase end end + def test_assert_performed_jobs_without_block_with_only_option + HelloJob.perform_later("jeremy") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + assert_performed_jobs 1, only: HelloJob + end + + def test_assert_performed_jobs_without_block_with_only_option_failure + LoggingJob.perform_later("jeremy") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, only: HelloJob + end + + assert_match(/1 .* but 0/, error.message) + end + def test_assert_performed_jobs_with_except_option assert_nothing_raised do assert_performed_jobs 1, except: LoggingJob do @@ -596,6 +919,28 @@ class PerformedJobsTest < ActiveJob::TestCase end end + def test_assert_performed_jobs_without_block_with_except_option + HelloJob.perform_later("jeremy") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + assert_performed_jobs 1, except: HelloJob + end + + def test_assert_performed_jobs_without_block_with_except_option_failure + HelloJob.perform_later("jeremy") + HelloJob.perform_later("bogdan") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, except: HelloJob + end + + assert_match(/1 .* but 0/, error.message) + end + def test_assert_performed_jobs_with_only_and_except_option error = assert_raise ArgumentError do assert_performed_jobs 1, only: HelloJob, except: HelloJob do @@ -607,6 +952,19 @@ class PerformedJobsTest < ActiveJob::TestCase assert_match(/`:only` and `:except`/, error.message) end + def test_assert_performed_jobs_without_block_with_only_and_except_options + error = assert_raise ArgumentError do + HelloJob.perform_later("jeremy") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + assert_performed_jobs 1, only: HelloJob, except: HelloJob + end + + assert_match(/`:only` and `:except`/, error.message) + end + def test_assert_performed_jobs_with_only_option_as_array assert_nothing_raised do assert_performed_jobs 2, only: [HelloJob, LoggingJob] do @@ -732,6 +1090,134 @@ class PerformedJobsTest < ActiveJob::TestCase assert_match(/`:only` and `:except`/, error.message) end + def test_assert_performed_jobs_with_queue_option + assert_performed_jobs 1, queue: :some_queue do + HelloJob.set(queue: :some_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + end + end + + def test_assert_performed_jobs_with_queue_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + end + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_performed_jobs_without_block_with_queue_option + HelloJob.set(queue: :some_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + + perform_enqueued_jobs + + assert_performed_jobs 1, queue: :some_queue + end + + def test_assert_performed_jobs_without_block_with_queue_option_failure + HelloJob.set(queue: :other_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, queue: :some_queue + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_performed_jobs_with_only_and_queue_options + assert_performed_jobs 1, only: HelloJob, queue: :some_queue do + HelloJob.set(queue: :some_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + def test_assert_performed_jobs_with_only_and_queue_options_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, only: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_performed_jobs_without_block_with_only_and_queue_options + HelloJob.set(queue: :some_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + assert_performed_jobs 1, only: HelloJob, queue: :some_queue + end + + def test_assert_performed_jobs_without_block_with_only_and_queue_options_failure + HelloJob.set(queue: :other_queue).perform_later("jeremy") + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, only: HelloJob, queue: :some_queue + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_performed_jobs_with_except_and_queue_options + assert_performed_jobs 1, except: HelloJob, queue: :other_queue do + HelloJob.set(queue: :other_queue).perform_later("jeremy") + LoggingJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("jeremy") + end + end + + def test_assert_performed_jobs_with_except_and_queue_options_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, except: HelloJob, queue: :other_queue do + HelloJob.set(queue: :other_queue).perform_later("jeremy") + LoggingJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_performed_jobs_without_block_with_except_and_queue_options + HelloJob.set(queue: :other_queue).perform_later("jeremy") + LoggingJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("jeremy") + + perform_enqueued_jobs + + assert_performed_jobs 1, except: HelloJob, queue: :other_queue + end + + def test_assert_performed_jobs_without_block_with_except_and_queue_options_failure + HelloJob.set(queue: :other_queue).perform_later("jeremy") + LoggingJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, except: HelloJob, queue: :other_queue + end + + assert_match(/1 .* but 0/, error.message) + end + def test_assert_no_performed_jobs_with_only_option assert_nothing_raised do assert_no_performed_jobs only: HelloJob do @@ -740,6 +1226,26 @@ class PerformedJobsTest < ActiveJob::TestCase end end + def test_assert_no_performed_jobs_without_block_with_only_option + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + assert_no_performed_jobs only: HelloJob + end + + def test_assert_no_performed_jobs_without_block_with_only_option_failure + HelloJob.perform_later("bogdan") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs only: HelloJob + end + + assert_match(/0 .* but 1/, error.message) + end + def test_assert_no_performed_jobs_with_except_option assert_nothing_raised do assert_no_performed_jobs except: LoggingJob do @@ -748,6 +1254,26 @@ class PerformedJobsTest < ActiveJob::TestCase end end + def test_assert_no_performed_jobs_without_block_with_except_option + HelloJob.perform_later("jeremy") + + perform_enqueued_jobs + + assert_no_performed_jobs except: HelloJob + end + + def test_assert_no_performed_jobs_without_block_with_except_option_failure + LoggingJob.perform_later("jeremy") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs except: HelloJob + end + + assert_match(/0 .* but 1/, error.message) + end + def test_assert_no_performed_jobs_with_only_and_except_option error = assert_raise ArgumentError do assert_no_performed_jobs only: HelloJob, except: HelloJob do @@ -758,6 +1284,19 @@ class PerformedJobsTest < ActiveJob::TestCase assert_match(/`:only` and `:except`/, error.message) end + def test_assert_no_performed_jobs_without_block_with_only_and_except_options + error = assert_raise ArgumentError do + HelloJob.perform_later("jeremy") + LoggingJob.perform_later("bogdan") + + perform_enqueued_jobs + + assert_no_performed_jobs only: HelloJob, except: HelloJob + end + + assert_match(/`:only` and `:except`/, error.message) + end + def test_assert_no_performed_jobs_with_only_option_as_array assert_nothing_raised do assert_no_performed_jobs only: [HelloJob, RescueJob] do @@ -818,13 +1357,141 @@ class PerformedJobsTest < ActiveJob::TestCase assert_match(/`:only` and `:except`/, error.message) end - def test_assert_performed_job + def test_assert_no_performed_jobs_with_queue_option + assert_no_performed_jobs queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later("jeremy") + end + end + + def test_assert_no_performed_jobs_with_queue_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs queue: :some_queue do + HelloJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_performed_jobs_without_block_with_queue_option + HelloJob.set(queue: :other_queue).perform_later("jeremy") + + perform_enqueued_jobs + + assert_no_performed_jobs queue: :some_queue + end + + def test_assert_no_performed_jobs_without_block_with_queue_option_failure + HelloJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs queue: :some_queue + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_performed_jobs_with_only_and_queue_options + assert_no_performed_jobs only: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + def test_assert_no_performed_jobs_with_only_and_queue_options_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs only: HelloJob, queue: :some_queue do + HelloJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_performed_jobs_without_block_with_only_and_queue_options + HelloJob.set(queue: :other_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + assert_no_performed_jobs only: HelloJob, queue: :some_queue + end + + def test_assert_no_performed_jobs_without_block_with_only_and_queue_options_failure + HelloJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs only: HelloJob, queue: :some_queue + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_performed_jobs_with_except_and_queue_options + assert_no_performed_jobs except: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later("bogdan") + HelloJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("jeremy") + end + end + + def test_assert_no_performed_jobs_with_except_and_queue_options_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs except: HelloJob, queue: :some_queue do + HelloJob.set(queue: :other_queue).perform_later("bogdan") + HelloJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_no_performed_jobs_without_block_with_except_and_queue_options + HelloJob.set(queue: :other_queue).perform_later("bogdan") + HelloJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :other_queue).perform_later("jeremy") + + perform_enqueued_jobs + + assert_no_performed_jobs except: HelloJob, queue: :some_queue + end + + def test_assert_no_performed_jobs_without_block_with_except_and_queue_options_failure + HelloJob.set(queue: :other_queue).perform_later("bogdan") + HelloJob.set(queue: :some_queue).perform_later("bogdan") + LoggingJob.set(queue: :some_queue).perform_later("jeremy") + + perform_enqueued_jobs + + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs except: HelloJob, queue: :some_queue + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_performed_with assert_performed_with(job: NestedJob, queue: "default") do NestedJob.perform_later end end - def test_assert_performed_job_returns + def test_assert_performed_with_without_block + NestedJob.perform_later + + perform_enqueued_jobs + + assert_performed_with(job: NestedJob, queue: "default") + end + + def test_assert_performed_with_returns job = assert_performed_with(job: NestedJob, queue: "default") do NestedJob.perform_later end @@ -835,7 +1502,20 @@ class PerformedJobsTest < ActiveJob::TestCase assert_equal "default", job.queue_name end - def test_assert_performed_job_failure + def test_assert_performed_with_without_block_returns + NestedJob.perform_later + + perform_enqueued_jobs + + job = assert_performed_with(job: NestedJob, queue: "default") + + assert_instance_of NestedJob, job + assert_nil job.scheduled_at + assert_equal [], job.arguments + assert_equal "default", job.queue_name + end + + def test_assert_performed_with_failure assert_raise ActiveSupport::TestCase::Assertion do assert_performed_with(job: LoggingJob) do HelloJob.perform_later @@ -849,7 +1529,23 @@ class PerformedJobsTest < ActiveJob::TestCase end end - def test_assert_performed_job_with_at_option + def test_assert_performed_with_without_block_failure + HelloJob.perform_later + + perform_enqueued_jobs + + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: LoggingJob) + end + + HelloJob.set(queue: "important").perform_later + + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: HelloJob, queue: "low") + end + end + + def test_assert_performed_with_with_at_option assert_performed_with(job: HelloJob, at: Date.tomorrow.noon) do HelloJob.set(wait_until: Date.tomorrow.noon).perform_later end @@ -861,14 +1557,43 @@ class PerformedJobsTest < ActiveJob::TestCase end end - def test_assert_performed_job_with_global_id_args + def test_assert_performed_with_without_block_with_at_option + HelloJob.set(wait_until: Date.tomorrow.noon).perform_later + + perform_enqueued_jobs + + assert_performed_with(job: HelloJob, at: Date.tomorrow.noon) + + HelloJob.set(wait_until: Date.tomorrow.noon).perform_later + + perform_enqueued_jobs + + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: HelloJob, at: Date.today.noon) + end + end + + def test_assert_performed_with_with_hash_arg + assert_performed_with(job: MultipleKwargsJob, args: [{ argument1: 1, argument2: { a: 1, b: 2 } }]) do + MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1) + end + end + + def test_assert_performed_with_with_global_id_args ricardo = Person.new(9) assert_performed_with(job: HelloJob, args: [ricardo]) do HelloJob.perform_later(ricardo) end end - def test_assert_performed_job_failure_with_global_id_args + def test_assert_performed_with_without_bllock_with_global_id_args + ricardo = Person.new(9) + HelloJob.perform_later(ricardo) + perform_enqueued_jobs + assert_performed_with(job: HelloJob, args: [ricardo]) + end + + def test_assert_performed_with_failure_with_global_id_args ricardo = Person.new(9) wilma = Person.new(11) error = assert_raise ActiveSupport::TestCase::Assertion do @@ -880,7 +1605,19 @@ class PerformedJobsTest < ActiveJob::TestCase assert_equal "No performed job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message end - def test_assert_performed_job_does_not_change_jobs_count + def test_assert_performed_with_without_block_failure_with_global_id_args + ricardo = Person.new(9) + wilma = Person.new(11) + HelloJob.perform_later(ricardo) + perform_enqueued_jobs + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: HelloJob, args: [wilma]) + end + + assert_equal "No performed job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message + end + + def test_assert_performed_with_does_not_change_jobs_count assert_performed_with(job: HelloJob) do HelloJob.perform_later end @@ -891,6 +1628,18 @@ class PerformedJobsTest < ActiveJob::TestCase assert_equal 2, queue_adapter.performed_jobs.count end + + def test_assert_performed_with_without_block_does_not_change_jobs_count + HelloJob.perform_later + perform_enqueued_jobs + assert_performed_with(job: HelloJob) + + perform_enqueued_jobs + HelloJob.perform_later + assert_performed_with(job: HelloJob) + + assert_equal 2, queue_adapter.performed_jobs.count + end end class OverrideQueueAdapterTest < ActiveJob::TestCase diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index 32afb5ca62..96253773c7 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -137,4 +137,16 @@ class QueuingTest < ActiveSupport::TestCase assert job_executed "#{@id}.2" assert job_executed_at("#{@id}.2") < job_executed_at("#{@id}.1") end + + test "should run job with higher priority first in Backburner" do + skip unless adapter_is?(:backburner) + + jobs_manager.tube.pause(3) + TestJob.set(priority: 20).perform_later "#{@id}.1" + TestJob.set(priority: 10).perform_later "#{@id}.2" + wait_for_jobs_to_finish_for(10.seconds) + assert job_executed "#{@id}.1" + assert job_executed "#{@id}.2" + assert job_executed_at("#{@id}.2") < job_executed_at("#{@id}.1") + end end diff --git a/activejob/test/jobs/multiple_kwargs_job.rb b/activejob/test/jobs/multiple_kwargs_job.rb new file mode 100644 index 0000000000..b355c4ce1a --- /dev/null +++ b/activejob/test/jobs/multiple_kwargs_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require_relative "../support/job_buffer" + +class MultipleKwargsJob < ActiveJob::Base + def perform(argument1:, argument2:) + JobBuffer.add("Job with argument1: #{argument1}, argument2: #{argument2}") + end +end diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb index 82b80fe12b..68dc17e16c 100644 --- a/activejob/test/jobs/retry_job.rb +++ b/activejob/test/jobs/retry_job.rb @@ -4,23 +4,31 @@ require_relative "../support/job_buffer" require "active_support/core_ext/integer/inflections" class DefaultsError < StandardError; end +class FirstRetryableErrorOfTwo < StandardError; end +class SecondRetryableErrorOfTwo < StandardError; end class LongWaitError < StandardError; end class ShortWaitTenAttemptsError < StandardError; end class ExponentialWaitTenAttemptsError < StandardError; end class CustomWaitTenAttemptsError < StandardError; end class CustomCatchError < StandardError; end class DiscardableError < StandardError; end +class FirstDiscardableErrorOfTwo < StandardError; end +class SecondDiscardableErrorOfTwo < StandardError; end class CustomDiscardableError < StandardError; end class RetryJob < ActiveJob::Base retry_on DefaultsError + retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo retry_on LongWaitError, wait: 1.hour, attempts: 10 retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10 retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10 retry_on CustomWaitTenAttemptsError, wait: ->(executions) { executions * 2 }, attempts: 10 - retry_on(CustomCatchError) { |job, exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts. Message: #{exception.message}") } + retry_on(CustomCatchError) { |job, error| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts. Message: #{error.message}") } + retry_on(ActiveJob::DeserializationError) { |job, error| JobBuffer.add("Raised #{error.class} for the #{job.executions} time") } + discard_on DiscardableError - discard_on(CustomDiscardableError) { |job, exception| JobBuffer.add("Dealt with a job that was discarded in a custom way") } + discard_on FirstDiscardableErrorOfTwo, SecondDiscardableErrorOfTwo + discard_on(CustomDiscardableError) { |job, error| JobBuffer.add("Dealt with a job that was discarded in a custom way. Message: #{error.message}") } def perform(raising, attempts) if executions < attempts diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb index 2a771b08c7..2e7d327b37 100644 --- a/activejob/test/support/integration/adapters/que.rb +++ b/activejob/test/support/integration/adapters/que.rb @@ -18,8 +18,8 @@ module QueJobsManager user = uri.user || ENV["USER"] pass = uri.password db = uri.path[1..-1] - %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1} - %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1} + %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -X -c 'drop database if exists "#{db}"' -U #{user} -t template1} + %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -X -c 'create database "#{db}"' -U #{user} -t template1} Que.connection = Sequel.connect(que_url) Que.migrate! diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb index 1b0685a971..dbbdc12b9d 100644 --- a/activejob/test/support/integration/adapters/queue_classic.rb +++ b/activejob/test/support/integration/adapters/queue_classic.rb @@ -17,8 +17,8 @@ module QueueClassicJobsManager user = uri.user || ENV["USER"] pass = uri.password db = uri.path[1..-1] - %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1} - %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1} + %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -X -c 'drop database if exists "#{db}"' -U #{user} -t template1} + %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -X -c 'create database "#{db}"' -U #{user} -t template1} QC::Setup.create QC.default_conn_adapter.disconnect diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb index a02d874e2e..c5fa2b136f 100644 --- a/activejob/test/support/integration/helper.rb +++ b/activejob/test/support/integration/helper.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -puts "\n\n*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n" +puts "\n\n*** rake test:integration:#{ENV['AJ_ADAPTER']} ***\n" ENV["RAILS_ENV"] = "test" ActiveJob::Base.queue_name_prefix = nil diff --git a/activejob/test/support/queue_classic/inline.rb b/activejob/test/support/queue_classic/inline.rb index ca3cd4581b..0695a34c27 100644 --- a/activejob/test/support/queue_classic/inline.rb +++ b/activejob/test/support/queue_classic/inline.rb @@ -1,22 +1,23 @@ # frozen_string_literal: true require "queue_classic" +require "active_support/core_ext/module/redefine_method" module QC class Queue - def enqueue(method, *args) + redefine_method(:enqueue) do |method, *args| receiver_str, _, message = method.rpartition(".") receiver = eval(receiver_str) receiver.send(message, *args) end - def enqueue_in(seconds, method, *args) + redefine_method(:enqueue_in) do |seconds, method, *args| receiver_str, _, message = method.rpartition(".") receiver = eval(receiver_str) receiver.send(message, *args) end - def enqueue_at(not_before, method, *args) + redefine_method(:enqueue_at) do |not_before, method, *args| receiver_str, _, message = method.rpartition(".") receiver = eval(receiver_str) receiver.send(message, *args) diff --git a/activejob/test/support/sneakers/inline.rb b/activejob/test/support/sneakers/inline.rb index 92b69ee3bc..e772c68c6e 100644 --- a/activejob/test/support/sneakers/inline.rb +++ b/activejob/test/support/sneakers/inline.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true require "sneakers" +require "active_support/core_ext/module/redefine_method" module Sneakers module Worker module ClassMethods - def enqueue(msg) + redefine_method(:enqueue) do |msg| worker = new(nil, nil, {}) worker.work(*msg) end |