From 4ab0320fb4ad61b65b1350b531e6993f79943803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 15:14:54 -0300 Subject: No need to disable sidikiq tests with 1.9.3 anymore --- activejob/test/helper.rb | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'activejob') diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index ec0c8a8ede..db5265d7b2 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -7,17 +7,6 @@ GlobalID.app = 'aj' @adapter = ENV['AJADAPTER'] || 'inline' -def sidekiq? - @adapter == 'sidekiq' -end - -def ruby_193? - RUBY_VERSION == '1.9.3' && RUBY_ENGINE != 'java' -end - -# Sidekiq doesn't work with MRI 1.9.3 -exit if sidekiq? && ruby_193? - if ENV['AJ_INTEGRATION_TESTS'] require 'support/integration/helper' else -- cgit v1.2.3 From b5e88317cc83436971d4b6d5d56629f9e43476d7 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 7 Jan 2015 23:33:01 -0500 Subject: Add :only option to assert_enqueued_jobs With the option, assert_enqueued_jobs will check the number of times a specific kind of job is enqueued. --- activejob/CHANGELOG.md | 13 +++++++++++ activejob/lib/active_job/test_helper.rb | 30 ++++++++++++++++++------ activejob/test/cases/test_helper_test.rb | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) (limited to 'activejob') diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index afdd42be33..bdeae29a50 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,16 @@ +* Add :only option to assert_enqueued_jobs + + With the option, assert_enqueued_jobs will check the number of times a specific kind of job is enqueued: + + def test_logging_job + assert_enqueued_jobs 1, only: LoggingJob do + LoggingJob.perform_later + HelloJob.perform_later('jeremy') + end + end + + *George Claghorn* + * `ActiveJob::Base.deserialize` delegates to the job class diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 2efcea7f2e..0d895a17db 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -42,16 +42,24 @@ module ActiveJob # HelloJob.perform_later('rafael') # end # end - def assert_enqueued_jobs(number) + # + # The number of times a specific job is enqueued can be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 2, only: LoggingJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + def assert_enqueued_jobs(number, only: nil) if block_given? - original_count = enqueued_jobs.size + original_count = enqueued_jobs_size(only: only) yield - new_count = enqueued_jobs.size - assert_equal original_count + number, new_count, - "#{number} jobs expected, but #{new_count - original_count} were enqueued" + new_count = enqueued_jobs_size(only: only) + 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" + actual_count = enqueued_jobs_size(only: only) + assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" end end @@ -215,6 +223,14 @@ module ActiveJob def clear_performed_jobs performed_jobs.clear end + + def enqueued_jobs_size(only: nil) + if only + enqueued_jobs.select { |job| job[:job] == only }.size + else + enqueued_jobs.size + end + end end end end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 784ede3674..21fe3db148 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -87,6 +87,46 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_match(/0 .* but 1/, error.message) end + def test_assert_enqueued_jobs_with_only_option + assert_nothing_raised do + assert_enqueued_jobs 1, only: HelloJob do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later + end + end + end + + def test_assert_enqueued_jobs_with_only_option_and_none_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 1, only: HelloJob do + LoggingJob.perform_later + end + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_enqueued_jobs_with_only_option_and_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 5, only: HelloJob do + HelloJob.perform_later('jeremy') + 4.times { LoggingJob.perform_later } + end + end + + assert_match(/5 .* but 1/, error.message) + end + + def test_assert_enqueued_jobs_with_only_option_and_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 1, only: HelloJob do + 2.times { HelloJob.perform_later('jeremy') } + end + end + + assert_match(/1 .* but 2/, error.message) + end + def test_assert_enqueued_job assert_enqueued_with(job: LoggingJob, queue: 'default') do LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later -- cgit v1.2.3 From a22a653c293cc5d378d0bfdd595ad60603c5608d Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Thu, 8 Jan 2015 07:40:00 -0200 Subject: Fix Active Job changelog formatting and reword a bit [ci skip] --- activejob/CHANGELOG.md | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) (limited to 'activejob') diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index bdeae29a50..829b1c719b 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,38 +1,36 @@ -* Add :only option to assert_enqueued_jobs - - With the option, assert_enqueued_jobs will check the number of times a specific kind of job is enqueued: - - def test_logging_job - assert_enqueued_jobs 1, only: LoggingJob do - LoggingJob.perform_later - HelloJob.perform_later('jeremy') +* Add `:only` option to `assert_enqueued_jobs`, to check the number of times + a specific kind of job is enqueued: + + def test_logging_job + assert_enqueued_jobs 1, only: LoggingJob do + LoggingJob.perform_later + HelloJob.perform_later('jeremy') + end end - end - - *George Claghorn* -* `ActiveJob::Base.deserialize` delegates to the job class + *George Claghorn* +* `ActiveJob::Base.deserialize` delegates to the job class. Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata when they get serialized and read it back when they get performed. Example: - class DeliverWebhookJob < ActiveJob::Base - def serialize - super.merge('attempt_number' => (@attempt_number || 0) + 1) - end + class DeliverWebhookJob < ActiveJob::Base + def serialize + super.merge('attempt_number' => (@attempt_number || 0) + 1) + end - def deserialize(job_data) - super - @attempt_number = job_data['attempt_number'] - end + def deserialize(job_data) + super + @attempt_number = job_data['attempt_number'] + end - rescue_from(TimeoutError) do |exception| - raise exception if @attempt_number > 5 - retry_job(wait: 10) + rescue_from(TimeoutError) do |exception| + raise exception if @attempt_number > 5 + retry_job(wait: 10) + end end - end *Isaac Seymour* -- cgit v1.2.3 From 91e31e82feaf0ba7609c02b1a3508f0506214404 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 8 Jan 2015 09:39:01 -0500 Subject: Add :only option to assert_no_enqueued_jobs --- activejob/lib/active_job/test_helper.rb | 12 ++++++++++-- activejob/test/cases/test_helper_test.rb | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) (limited to 'activejob') diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 0d895a17db..d18656e398 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -79,11 +79,19 @@ module ActiveJob # end # end # + # It can be asserted that no jobs of a specific kind are enqueued: + # + # def test_no_logging + # assert_no_enqueued_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # # Note: This assertion is simply a shortcut for: # # assert_enqueued_jobs 0, &block - def assert_no_enqueued_jobs(&block) - assert_enqueued_jobs 0, &block + def assert_no_enqueued_jobs(only: nil, &block) + assert_enqueued_jobs 0, only: only, &block end # Asserts that the number of performed jobs matches the given number. diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 21fe3db148..f34638e7d8 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -127,6 +127,25 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_match(/1 .* but 2/, error.message) end + def test_assert_no_enqueued_jobs_with_only_option + assert_nothing_raised do + assert_no_enqueued_jobs only: HelloJob do + LoggingJob.perform_later + end + end + end + + def test_assert_no_enqueued_jobs_with_only_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_jobs only: HelloJob do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later + end + end + + assert_match(/0 .* but 1/, error.message) + end + def test_assert_enqueued_job assert_enqueued_with(job: LoggingJob, queue: 'default') do LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later -- cgit v1.2.3 From a7621d7d530e47a783f4a9652c20dae9eaf70d66 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Sat, 10 Jan 2015 12:17:57 +0100 Subject: formatting pass over CHANGELOGs. [ci skip] --- activejob/CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'activejob') diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 829b1c719b..794d05d1b4 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,5 +1,7 @@ * Add `:only` option to `assert_enqueued_jobs`, to check the number of times - a specific kind of job is enqueued: + a specific kind of job is enqueued. + + Example: def test_logging_job assert_enqueued_jobs 1, only: LoggingJob do @@ -14,7 +16,9 @@ Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata - when they get serialized and read it back when they get performed. Example: + when they get serialized and read it back when they get performed. + + Example: class DeliverWebhookJob < ActiveJob::Base def serialize -- cgit v1.2.3 From 9d3042d05e69cecbba25036289eb6ac39fb182fc Mon Sep 17 00:00:00 2001 From: Richard Manyanza Date: Fri, 23 Jan 2015 14:01:25 +0300 Subject: Fix ActiveJob assertions with a GlobalID object argument --- activejob/lib/active_job/test_helper.rb | 14 ++++++++++-- activejob/test/cases/test_helper_test.rb | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) (limited to 'activejob') diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index d18656e398..c544e8a10f 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -175,9 +175,10 @@ module ActiveJob original_enqueued_jobs = enqueued_jobs.dup clear_enqueued_jobs args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) yield matching_job = enqueued_jobs.any? do |job| - args.all? { |key, value| value == job[key] } + serialized_args.all? { |key, value| value == job[key] } end assert matching_job, "No enqueued job found with #{args}" ensure @@ -195,9 +196,10 @@ module ActiveJob original_performed_jobs = performed_jobs.dup clear_performed_jobs args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) perform_enqueued_jobs { yield } matching_job = performed_jobs.any? do |job| - args.all? { |key, value| value == job[key] } + serialized_args.all? { |key, value| value == job[key] } end assert matching_job, "No performed job found with #{args}" ensure @@ -239,6 +241,14 @@ module ActiveJob enqueued_jobs.size end end + + def serialize_args_for_assertion(args) + serialized_args = args.dup + if job_args = serialized_args.delete(:args) + serialized_args[:args] = ActiveJob::Arguments.serialize(job_args) + end + serialized_args + end end end end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index f34638e7d8..0a23ae33c4 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/date' require 'jobs/hello_job' require 'jobs/logging_job' require 'jobs/nested_job' +require 'models/person' class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs @@ -175,6 +176,25 @@ class EnqueuedJobsTest < ActiveJob::TestCase end end end + + def test_assert_enqueued_job_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 + ricardo = Person.new(9) + wilma = Person.new(11) + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_with(job: HelloJob, args: [wilma]) do + HelloJob.perform_later(ricardo) + end + end + + assert_equal "No enqueued job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message + end end class PerformedJobsTest < ActiveJob::TestCase @@ -282,4 +302,23 @@ class PerformedJobsTest < ActiveJob::TestCase end end end + + def test_assert_performed_job_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 + ricardo = Person.new(9) + wilma = Person.new(11) + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: HelloJob, args: [wilma]) do + HelloJob.perform_later(ricardo) + end + end + + assert_equal "No performed job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message + end end -- cgit v1.2.3 From 31085a5cd47894b6fa397ca22fe8aa552d339ce8 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 30 Jan 2015 13:40:49 -0700 Subject: Allow keyword arguments to work with ActiveJob Unfortunately, the HashWithIndifferent access approach is insufficient for our needs. It's perfectly reasonable to want to use keyword arguments with Active Job, which we will see as a symbol keyed hash. For Ruby to convert this back to keyword arguments, it must deserialize to a symbol keyed hash. There are two primary changes to the serialization behavior. We first treat a HWIA separately, and mark it as such so we can convert it back into a HWIA during deserialization. For normal hashes, we keep a list of all symbol keys, and convert them back to symbol keys after deserialization. Fixes #18741. --- activejob/CHANGELOG.md | 6 +++ activejob/lib/active_job/arguments.rb | 49 ++++++++++++++++++---- .../test/cases/argument_serialization_test.rb | 39 ++++++++++++----- activejob/test/jobs/kwargs_job.rb | 7 ++++ 4 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 activejob/test/jobs/kwargs_job.rb (limited to 'activejob') diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 794d05d1b4..09c8f0800d 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,9 @@ +* Allow keyword arguments to be used with Active Job. + + Fixes #18741. + + *Sean Griffin* + * Add `:only` option to `assert_enqueued_jobs`, to check the number of times a specific kind of job is enqueued. diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 752be6898e..622c37098e 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/hash' module ActiveJob # Raised when an exception is raised during job arguments deserialization. @@ -44,7 +44,9 @@ module ActiveJob private GLOBALID_KEY = '_aj_globalid'.freeze - private_constant :GLOBALID_KEY + SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze + WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze + private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY def serialize_argument(argument) case argument @@ -54,10 +56,15 @@ module ActiveJob { GLOBALID_KEY => argument.to_global_id.to_s } when Array argument.map { |arg| serialize_argument(arg) } + when ActiveSupport::HashWithIndifferentAccess + result = serialize_hash(argument) + result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true) + result when Hash - argument.each_with_object({}) do |(key, value), hash| - hash[serialize_hash_key(key)] = serialize_argument(value) - end + symbol_keys = argument.each_key.grep(Symbol).map(&:to_s) + result = serialize_hash(argument) + result[SYMBOL_KEYS_KEY] = symbol_keys + result else raise SerializationError.new("Unsupported argument type: #{argument.class.name}") end @@ -75,7 +82,7 @@ module ActiveJob if serialized_global_id?(argument) deserialize_global_id argument else - deserialize_hash argument + deserialize_hash(argument) end else raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" @@ -90,13 +97,27 @@ module ActiveJob GlobalID::Locator.locate hash[GLOBALID_KEY] end + def serialize_hash(argument) + argument.each_with_object({}) do |(key, value), hash| + hash[serialize_hash_key(key)] = serialize_argument(value) + end + end + def deserialize_hash(serialized_hash) - serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash| - hash[key] = deserialize_argument(value) + result = serialized_hash.transform_values { |v| deserialize_argument(v) } + if result.delete(WITH_INDIFFERENT_ACCESS_KEY) + result = result.with_indifferent_access + elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY) + result = transform_symbol_keys(result, symbol_keys) end + result end - RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym] + RESERVED_KEYS = [ + GLOBALID_KEY, GLOBALID_KEY.to_sym, + SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym, + WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, + ] private_constant :RESERVED_KEYS def serialize_hash_key(key) @@ -109,5 +130,15 @@ module ActiveJob raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}") end end + + def transform_symbol_keys(hash, symbol_keys) + hash.transform_keys do |key| + if symbol_keys.include?(key) + key.to_sym + else + key + end + end + end end end diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb index dbe36fc572..8b9b62190f 100644 --- a/activejob/test/cases/argument_serialization_test.rb +++ b/activejob/test/cases/argument_serialization_test.rb @@ -2,6 +2,7 @@ require 'helper' require 'active_job/arguments' require 'models/person' require 'active_support/core_ext/hash/indifferent_access' +require 'jobs/kwargs_job' class ArgumentSerializationTest < ActiveSupport::TestCase setup do @@ -31,16 +32,26 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end test 'should convert records to Global IDs' do - assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s] + assert_arguments_roundtrip [@person] end test 'should dive deep into arrays and hashes' do - assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]] - assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access] + assert_arguments_roundtrip [3, [@person]] + assert_arguments_roundtrip [{ 'a' => @person }] end - test 'should stringify symbol hash keys' do - assert_equal [ 'a' => 1 ], ActiveJob::Arguments.serialize([ a: 1 ]) + test 'should maintain string and symbol keys' do + assert_arguments_roundtrip([a: 1, "b" => 2]) + end + + test 'should maintain hash with indifferent access' do + symbol_key = { a: 1 } + string_key = { 'a' => 1 } + indifferent_access = { a: 1 }.with_indifferent_access + + assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([symbol_key]).first + assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([string_key]).first + assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first end test 'should disallow non-string/symbol hash keys' do @@ -71,14 +82,22 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end end + test 'allows for keyword arguments' do + KwargsJob.perform_later(argument: 2) + + assert_equal "Job with argument: 2", JobBuffer.last_value + end + private def assert_arguments_unchanged(*args) - assert_arguments_roundtrip args, args + assert_arguments_roundtrip args + end + + def assert_arguments_roundtrip(args) + assert_equal args, perform_round_trip(args) end - def assert_arguments_roundtrip(args, expected_serialized_args) - serialized = ActiveJob::Arguments.serialize(args) - assert_equal expected_serialized_args, serialized - assert_equal args, ActiveJob::Arguments.deserialize(serialized) + def perform_round_trip(args) + ActiveJob::Arguments.deserialize(ActiveJob::Arguments.serialize(args)) end end diff --git a/activejob/test/jobs/kwargs_job.rb b/activejob/test/jobs/kwargs_job.rb new file mode 100644 index 0000000000..2df17d15ae --- /dev/null +++ b/activejob/test/jobs/kwargs_job.rb @@ -0,0 +1,7 @@ +require_relative '../support/job_buffer' + +class KwargsJob < ActiveJob::Base + def perform(argument: 1) + JobBuffer.add("Job with argument: #{argument}") + end +end -- cgit v1.2.3