diff options
Diffstat (limited to 'activejob')
-rw-r--r-- | activejob/lib/active_job.rb | 2 | ||||
-rw-r--r-- | activejob/lib/active_job/arguments.rb | 10 | ||||
-rw-r--r-- | activejob/lib/active_job/base.rb | 3 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapter.rb | 32 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapters.rb | 1 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb | 19 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapters/test_adapter.rb | 76 | ||||
-rw-r--r-- | activejob/lib/active_job/test_case.rb | 7 | ||||
-rw-r--r-- | activejob/lib/active_job/test_helper.rb | 196 | ||||
-rw-r--r-- | activejob/test/cases/rescue_test.rb | 5 | ||||
-rw-r--r-- | activejob/test/cases/test_case_test.rb | 14 | ||||
-rw-r--r-- | activejob/test/cases/test_helper_test.rb | 214 | ||||
-rw-r--r-- | activejob/test/support/queue_classic/inline.rb | 12 |
13 files changed, 569 insertions, 22 deletions
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 29123170b8..93d9d1b2d8 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -31,4 +31,6 @@ module ActiveJob autoload :Base autoload :QueueAdapters + autoload :TestCase + autoload :TestHelper end diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index e54f4afbc7..9d4490b0fc 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -5,7 +5,7 @@ module ActiveJob class DeserializationError < StandardError attr_reader :original_exception - def initialize(e) + def initialize(e) #:nodoc: super ("Error while trying to deserialize arguments: #{e.message}") @original_exception = e set_backtrace e.backtrace @@ -30,6 +30,8 @@ module ActiveJob def deserialize(arguments) arguments.map { |argument| deserialize_argument(argument) } + rescue => e + raise DeserializationError.new(e) end private @@ -40,7 +42,7 @@ module ActiveJob when *TYPE_WHITELIST argument when Array - serialize(argument) + argument.map { |arg| serialize_argument(arg) } when Hash Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ] else @@ -51,14 +53,12 @@ module ActiveJob def deserialize_argument(argument) case argument when Array - deserialize(argument) + argument.map { |arg| deserialize_argument(arg) } when Hash Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access else GlobalID::Locator.locate(argument) || argument end - rescue => e - raise DeserializationError.new(e) end def serialize_hash_key(key) diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index d5ba253016..1b54786303 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -8,8 +8,7 @@ require 'active_job/logging' module ActiveJob class Base - extend QueueAdapter - + include QueueAdapter include QueueName include Enqueuing include Execution diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 13c23abce4..fb54aec75e 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -3,21 +3,27 @@ require 'active_support/core_ext/string/inflections' module ActiveJob module QueueAdapter - mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter } + extend ActiveSupport::Concern - def queue_adapter=(name_or_adapter) - @@queue_adapter = \ - case name_or_adapter - when Symbol, String - load_adapter(name_or_adapter) - when Class - name_or_adapter - end - end + module ClassMethods + mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter } - private - def load_adapter(name) - "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize + def queue_adapter=(name_or_adapter) + @@queue_adapter = \ + case name_or_adapter + when :test + ActiveJob::QueueAdapters::TestAdapter.new + when Symbol, String + load_adapter(name_or_adapter) + when Class + name_or_adapter + end end + + private + def load_adapter(name) + "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize + end + end end end
\ No newline at end of file diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index 007068ff0a..345b01ef00 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -12,5 +12,6 @@ module ActiveJob autoload :SidekiqAdapter autoload :SneakersAdapter autoload :SuckerPunchAdapter + autoload :TestAdapter end end diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb index d74f8cf90e..914390a958 100644 --- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb @@ -5,11 +5,26 @@ module ActiveJob class QueueClassicAdapter class << self def enqueue(job, *args) - QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args) + build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args) end def enqueue_at(job, timestamp, *args) - raise NotImplementedError + queue = build_queue(job.queue_name) + unless queue.respond_to?(:enqueue_at) + raise NotImplementedError, 'To be able to schedule jobs with Queue Classic ' \ + 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' + 'You can implement this yourself or you can use the queue_classic-later gem.' + end + queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.name, *args) + end + + # Builds a <tt>QC::Queue</tt> object to schedule jobs on. + # + # If you have a custom <tt>QC::Queue</tt> subclass you'll need to suclass + # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the + # <tt>build_queue</tt> method. + def build_queue(queue_name) + QC::Queue.new(queue_name) end end diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb new file mode 100644 index 0000000000..185d6fc7e6 --- /dev/null +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -0,0 +1,76 @@ +module ActiveJob + module QueueAdapters + class TestAdapter + attr_accessor(:perform_enqueued_jobs) { false } + attr_accessor(:perform_enqueued_at_jobs) { false } + delegate :name, to: :class + + # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. + def enqueued_jobs + @enqueued_jobs ||= [] + end + + # Allows you to overwrite the default enqueued jobs store from an array to some + # other object. If you just want to clear the store, + # call ActiveJob::QueueAdapters::TestAdapter.enqueued_jobs.clear. + # + # If you place another object here, please make sure it responds to: + # + # * << (message) + # * clear + # * length + # * size + # * and other common Array methods + def enqueued_jobs=(val) + @enqueued_jobs = val + end + + # Provides a store of all the performed jobs with the TestAdapter so you can check them. + def performed_jobs + @performed_jobs ||= [] + end + + # Allows you to overwrite the default performed jobs store from an array to some + # other object. If you just want to clear the store, + # call ActiveJob::QueueAdapters::TestAdapter.performed_jobs.clear. + # + # If you place another object here, please make sure it responds to: + # + # * << (message) + # * clear + # * length + # * size + # * and other common Array methods + def performed_jobs=(val) + @performed_jobs = val + end + + def enqueue(job, *args) + if perform_enqueued_jobs? + performed_jobs << {job: job, args: args, queue: job.queue_name} + job.new.execute(*args) + else + enqueued_jobs << {job: job, args: args, queue: job.queue_name} + end + end + + def enqueue_at(job, timestamp, *args) + if perform_enqueued_at_jobs? + performed_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp} + job.new.execute(*args) + else + enqueued_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp} + end + end + + private + def perform_enqueued_jobs? + perform_enqueued_jobs + end + + def perform_enqueued_at_jobs? + perform_enqueued_at_jobs + end + end + end +end diff --git a/activejob/lib/active_job/test_case.rb b/activejob/lib/active_job/test_case.rb new file mode 100644 index 0000000000..d894a7b5cd --- /dev/null +++ b/activejob/lib/active_job/test_case.rb @@ -0,0 +1,7 @@ +require 'active_support/test_case' + +module ActiveJob + class TestCase < ActiveSupport::TestCase + include ActiveJob::TestHelper + end +end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb new file mode 100644 index 0000000000..767619097c --- /dev/null +++ b/activejob/lib/active_job/test_helper.rb @@ -0,0 +1,196 @@ +module ActiveJob + # Provides helper methods for testing Active Job + module TestHelper + extend ActiveSupport::Concern + + included do + def before_setup + @old_queue_adapter = queue_adapter + ActiveJob::Base.queue_adapter = :test + clear_enqueued_jobs + clear_performed_jobs + super + end + + def after_teardown + super + ActiveJob::Base.queue_adapter = @old_queue_adapter + end + + # Asserts that the number of enqueued jobs matches the given number. + # + # def test_jobs + # assert_enqueued_jobs 0 + # HelloJob.enqueue('david') + # assert_enqueued_jobs 1 + # HelloJob.enqueue('abdelkader') + # assert_enqueued_jobs 2 + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be enqueued. + # + # def test_jobs_again + # assert_enqueued_jobs 1 do + # HelloJob.enqueue('cristian') + # end + # + # assert_enqueued_jobs 2 do + # HelloJob.enqueue('aaron') + # HelloJob.enqueue('rafael') + # end + # end + def assert_enqueued_jobs(number) + if block_given? + original_count = enqueued_jobs.size + yield + new_count = enqueued_jobs.size + assert_equal original_count + number, new_count, + "#{number} jobs expected, but #{new_count - original_count} were enqueued" + else + enqueued_jobs_size = enqueued_jobs.size + assert_equal number, enqueued_jobs_size, "#{number} jobs expected, but #{enqueued_jobs_size} were enqueued" + end + end + + # Assert that no job have been enqueued. + # + # def test_jobs + # assert_no_enqueued_jobs + # HelloJob.enqueue('jeremy') + # assert_enqueued_jobs 1 + # end + # + # If a block is passed, that block should not cause any job to be enqueued. + # + # def test_jobs_again + # assert_no_enqueued_jobs do + # # No job should be enqueued from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_enqueued_jobs 0 + def assert_no_enqueued_jobs(&block) + assert_enqueued_jobs 0, &block + end + + # Asserts that the number of performed jobs matches the given number. + # + # def test_jobs + # assert_performed_jobs 0 + # HelloJob.enqueue('xavier') + # assert_performed_jobs 1 + # HelloJob.enqueue('yves') + # assert_performed_jobs 2 + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be performed. + # + # def test_jobs_again + # assert_performed_jobs 1 do + # HelloJob.enqueue('robin') + # end + # + # assert_performed_jobs 2 do + # HelloJob.enqueue('carlos') + # HelloJob.enqueue('sean') + # end + # end + def assert_performed_jobs(number) + if block_given? + original_count = performed_jobs.size + yield + new_count = performed_jobs.size + assert_equal original_count + number, new_count, + "#{number} jobs expected, but #{new_count - original_count} were performed" + else + performed_jobs_size = performed_jobs.size + assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed" + end + end + + # Asserts that no jobs have been performed. + # + # def test_jobs + # assert_no_performed_jobs + # HelloJob.enqueue('matthew') + # assert_performed_jobs 1 + # end + # + # If a block is passed, that block should not cause any job to be performed. + # + # def test_jobs_again + # assert_no_performed_jobs do + # # No job should be performed from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_performed_jobs 0 + def assert_no_performed_jobs(&block) + assert_performed_jobs 0, &block + end + + # Asserts that the job passed in the block has been enqueued with the given arguments. + # + # def assert_enqueued_job + # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do + # MyJob.enqueue(1,2,3) + # end + # end + def assert_enqueued_with(args = {}, &_block) + original_enqueued_jobs = enqueued_jobs.dup + clear_enqueued_jobs + args.assert_valid_keys(:job, :args, :at, :queue) + yield + matching_job = enqueued_jobs.any? do |job| + args.all? { |key, value| value == job[key] } + end + assert matching_job + ensure + queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs + end + + # Asserts that the job passed in the block has been performed with the given arguments. + # + # def test_assert_performed_with + # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do + # MyJob.enqueue(1,2,3) + # end + # end + def assert_performed_with(args = {}, &_block) + original_performed_jobs = performed_jobs.dup + clear_performed_jobs + args.assert_valid_keys(:job, :args, :at, :queue) + yield + matching_job = performed_jobs.any? do |job| + args.all? { |key, value| value == job[key] } + end + assert matching_job, "No performed job found with #{args}" + ensure + queue_adapter.performed_jobs = original_performed_jobs + performed_jobs + end + + def queue_adapter + ActiveJob::Base.queue_adapter + end + + delegate :enqueued_jobs, :enqueued_jobs=, + :performed_jobs, :performed_jobs=, + to: :queue_adapter + + private + def clear_enqueued_jobs + enqueued_jobs.clear + end + + def clear_performed_jobs + performed_jobs.clear + end + end + end +end diff --git a/activejob/test/cases/rescue_test.rb b/activejob/test/cases/rescue_test.rb index d9ea3c91d7..3af147383e 100644 --- a/activejob/test/cases/rescue_test.rb +++ b/activejob/test/cases/rescue_test.rb @@ -28,4 +28,9 @@ class RescueTest < ActiveSupport::TestCase assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound' assert_not_includes JobBuffer.values, 'performed beautifully' end + + test "should not wrap DeserializationError in DeserializationError" do + RescueJob.enqueue [Person.new(404)] + assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound' + end end diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb new file mode 100644 index 0000000000..1d0fdbd22d --- /dev/null +++ b/activejob/test/cases/test_case_test.rb @@ -0,0 +1,14 @@ +require 'helper' +require 'jobs/hello_job' +require 'jobs/logging_job' +require 'jobs/nested_job' + +class ActiveJobTestCaseTest < ActiveJob::TestCase + def test_include_helper + assert_includes self.class.ancestors, ActiveJob::TestHelper + end + + def test_set_test_adapter + assert_instance_of ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter + end +end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb new file mode 100644 index 0000000000..240aa23ce3 --- /dev/null +++ b/activejob/test/cases/test_helper_test.rb @@ -0,0 +1,214 @@ +require 'helper' +require 'active_support/core_ext/time' +require 'active_support/core_ext/date' +require 'jobs/hello_job' +require 'jobs/logging_job' +require 'jobs/nested_job' + +class EnqueuedJobsTest < ActiveJob::TestCase + setup { queue_adapter.perform_enqueued_at_jobs = true } + + def test_assert_enqueued_jobs + assert_nothing_raised do + assert_enqueued_jobs 1 do + HelloJob.enqueue('david') + end + end + end + + def test_repeated_enqueued_jobs_calls + assert_nothing_raised do + assert_enqueued_jobs 1 do + HelloJob.enqueue('abdelkader') + end + end + + assert_nothing_raised do + assert_enqueued_jobs 2 do + HelloJob.enqueue('sean') + HelloJob.enqueue('yves') + end + end + end + + def test_assert_enqueued_jobs_with_no_block + assert_nothing_raised do + HelloJob.enqueue('rafael') + assert_enqueued_jobs 1 + end + + assert_nothing_raised do + HelloJob.enqueue('aaron') + HelloJob.enqueue('matthew') + assert_enqueued_jobs 3 + end + end + + def test_assert_no_enqueued_jobs + assert_nothing_raised do + assert_no_enqueued_jobs do + # Scheduled jobs are being performed in this context + HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey') + end + end + end + + def test_assert_enqueued_jobs_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 2 do + HelloJob.enqueue('xavier') + end + end + + assert_match(/2 .* but 1/, error.message) + end + + def test_assert_enqueued_jobs_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 1 do + HelloJob.enqueue('cristian') + HelloJob.enqueue('guillermo') + end + end + + assert_match(/1 .* but 2/, error.message) + end + + def test_assert_no_enqueued_jobs_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_jobs do + HelloJob.enqueue('jeremy') + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_enqueued_job + assert_enqueued_with(job: LoggingJob, queue: 'default') do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + + def test_assert_enqueued_job_failure + assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_with(job: LoggingJob, queue: 'default') do + NestedJob.enqueue + end + end + + assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_with(job: NestedJob, queue: 'low') do + NestedJob.enqueue + end + end + end + + def test_assert_enqueued_job_args + assert_raise ArgumentError do + assert_enqueued_with(class: LoggingJob) do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + end +end + +class PerformedJobsTest < ActiveJob::TestCase + setup { queue_adapter.perform_enqueued_jobs = true } + + def test_assert_performed_jobs + assert_nothing_raised do + assert_performed_jobs 1 do + HelloJob.enqueue('david') + end + end + end + + def test_repeated_performed_jobs_calls + assert_nothing_raised do + assert_performed_jobs 1 do + HelloJob.enqueue('abdelkader') + end + end + + assert_nothing_raised do + assert_performed_jobs 2 do + HelloJob.enqueue('sean') + HelloJob.enqueue('yves') + end + end + end + + def test_assert_performed_jobs_with_no_block + assert_nothing_raised do + HelloJob.enqueue('rafael') + assert_performed_jobs 1 + end + + assert_nothing_raised do + HelloJob.enqueue('aaron') + HelloJob.enqueue('matthew') + assert_performed_jobs 3 + end + end + + def test_assert_no_performed_jobs + assert_nothing_raised do + assert_no_performed_jobs do + # Scheduled jobs are being enqueued in this context + HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey') + end + end + end + + def test_assert_performed_jobs_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 2 do + HelloJob.enqueue('xavier') + end + end + + assert_match(/2 .* but 1/, error.message) + end + + def test_assert_performed_jobs_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1 do + HelloJob.enqueue('cristian') + HelloJob.enqueue('guillermo') + end + end + + assert_match(/1 .* but 2/, error.message) + end + + def test_assert_no_performed_jobs_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs do + HelloJob.enqueue('jeremy') + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_performed_job + assert_performed_with(job: NestedJob, queue: 'default') do + NestedJob.enqueue + end + end + + def test_assert_performed_job_failure + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: LoggingJob, queue: 'default') do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: NestedJob, queue: 'low') do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + end +end diff --git a/activejob/test/support/queue_classic/inline.rb b/activejob/test/support/queue_classic/inline.rb index 5e9c295e01..5743d5bbb5 100644 --- a/activejob/test/support/queue_classic/inline.rb +++ b/activejob/test/support/queue_classic/inline.rb @@ -7,5 +7,17 @@ module QC receiver = eval(receiver_str) receiver.send(message, *args) end + + def enqueue_in(seconds, method, *args) + receiver_str, _, message = method.rpartition('.') + receiver = eval(receiver_str) + receiver.send(message, *args) + end + + def enqueue_at(not_before, method, *args) + receiver_str, _, message = method.rpartition('.') + receiver = eval(receiver_str) + receiver.send(message, *args) + end end end |