activejob 5.2.4.4 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -50
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +18 -13
  5. data/lib/active_job.rb +2 -1
  6. data/lib/active_job/arguments.rb +80 -30
  7. data/lib/active_job/base.rb +6 -1
  8. data/lib/active_job/callbacks.rb +46 -3
  9. data/lib/active_job/configured_job.rb +2 -0
  10. data/lib/active_job/core.rb +40 -21
  11. data/lib/active_job/enqueuing.rb +20 -7
  12. data/lib/active_job/exceptions.rb +60 -28
  13. data/lib/active_job/execution.rb +11 -2
  14. data/lib/active_job/gem_version.rb +4 -4
  15. data/lib/active_job/instrumentation.rb +40 -0
  16. data/lib/active_job/log_subscriber.rb +140 -0
  17. data/lib/active_job/logging.rb +3 -101
  18. data/lib/active_job/queue_adapter.rb +5 -0
  19. data/lib/active_job/queue_adapters.rb +13 -11
  20. data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
  21. data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
  22. data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
  23. data/lib/active_job/queue_adapters/que_adapter.rb +2 -2
  24. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +2 -2
  25. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +1 -1
  26. data/lib/active_job/queue_adapters/test_adapter.rb +32 -14
  27. data/lib/active_job/queue_name.rb +23 -3
  28. data/lib/active_job/railtie.rb +20 -1
  29. data/lib/active_job/serializers.rb +66 -0
  30. data/lib/active_job/serializers/date_serializer.rb +20 -0
  31. data/lib/active_job/serializers/date_time_serializer.rb +16 -0
  32. data/lib/active_job/serializers/duration_serializer.rb +23 -0
  33. data/lib/active_job/serializers/module_serializer.rb +20 -0
  34. data/lib/active_job/serializers/object_serializer.rb +53 -0
  35. data/lib/active_job/serializers/symbol_serializer.rb +20 -0
  36. data/lib/active_job/serializers/time_object_serializer.rb +13 -0
  37. data/lib/active_job/serializers/time_serializer.rb +16 -0
  38. data/lib/active_job/serializers/time_with_zone_serializer.rb +16 -0
  39. data/lib/active_job/test_helper.rb +316 -68
  40. data/lib/active_job/timezones.rb +13 -0
  41. data/lib/active_job/translation.rb +1 -1
  42. data/lib/rails/generators/job/job_generator.rb +4 -0
  43. metadata +29 -14
  44. data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeObjectSerializer < ObjectSerializer # :nodoc:
6
+ NANO_PRECISION = 9
7
+
8
+ def serialize(time)
9
+ super("value" => time.iso8601(NANO_PRECISION))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeSerializer < TimeObjectSerializer # :nodoc:
6
+ def deserialize(hash)
7
+ Time.iso8601(hash["value"])
8
+ end
9
+
10
+ private
11
+ def klass
12
+ Time
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeWithZoneSerializer < TimeObjectSerializer # :nodoc:
6
+ def deserialize(hash)
7
+ Time.iso8601(hash["value"]).in_time_zone
8
+ end
9
+
10
+ private
11
+ def klass
12
+ ActiveSupport::TimeWithZone
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/class/subclasses"
4
- require "active_support/core_ext/hash/keys"
5
4
 
6
5
  module ActiveJob
7
6
  # Provides helper methods for testing Active Job
@@ -10,6 +9,8 @@ module ActiveJob
10
9
  :performed_jobs, :performed_jobs=,
11
10
  to: :queue_adapter
12
11
 
12
+ include ActiveSupport::Testing::Assertions
13
+
13
14
  module TestQueueAdapter
14
15
  extend ActiveSupport::Concern
15
16
 
@@ -52,7 +53,7 @@ module ActiveJob
52
53
  queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter }
53
54
  end
54
55
 
55
- # Specifies the queue adapter to use with all active job test helpers.
56
+ # Specifies the queue adapter to use with all Active Job test helpers.
56
57
  #
57
58
  # Returns an instance of the queue adapter and defaults to
58
59
  # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>.
@@ -75,7 +76,7 @@ module ActiveJob
75
76
  # assert_enqueued_jobs 2
76
77
  # end
77
78
  #
78
- # If a block is passed, that block will cause the specified number of
79
+ # If a block is passed, asserts that the block will cause the specified number of
79
80
  # jobs to be enqueued.
80
81
  #
81
82
  # def test_jobs_again
@@ -89,7 +90,7 @@ module ActiveJob
89
90
  # end
90
91
  # end
91
92
  #
92
- # The number of times a specific job was enqueued can be asserted.
93
+ # Asserts the number of times a specific job was enqueued by passing +:only+ option.
93
94
  #
94
95
  # def test_logging_job
95
96
  # assert_enqueued_jobs 1, only: LoggingJob do
@@ -98,7 +99,7 @@ module ActiveJob
98
99
  # end
99
100
  # end
100
101
  #
101
- # The number of times a job except specific class was enqueued can be asserted.
102
+ # Asserts the number of times a job except specific class was enqueued by passing +:except+ option.
102
103
  #
103
104
  # def test_logging_job
104
105
  # assert_enqueued_jobs 1, except: HelloJob do
@@ -107,7 +108,10 @@ module ActiveJob
107
108
  # end
108
109
  # end
109
110
  #
110
- # The number of times a job is enqueued to a specific queue can also be asserted.
111
+ # +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
112
+ # a hash containing the job's class and it's argument are passed as argument.
113
+ #
114
+ # Asserts the number of times a job is enqueued to a specific queue by passing +:queue+ option.
111
115
  #
112
116
  # def test_logging_job
113
117
  # assert_enqueued_jobs 2, queue: 'default' do
@@ -115,16 +119,20 @@ module ActiveJob
115
119
  # HelloJob.perform_later('elfassy')
116
120
  # end
117
121
  # end
118
- def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil)
122
+ def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil, &block)
119
123
  if block_given?
120
- original_count = enqueued_jobs_size(only: only, except: except, queue: queue)
121
- yield
122
- new_count = enqueued_jobs_size(only: only, except: except, queue: queue)
123
- assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
124
+ original_jobs = enqueued_jobs_with(only: only, except: except, queue: queue)
125
+
126
+ assert_nothing_raised(&block)
127
+
128
+ new_jobs = enqueued_jobs_with(only: only, except: except, queue: queue)
129
+
130
+ actual_count = (new_jobs - original_jobs).count
124
131
  else
125
- actual_count = enqueued_jobs_size(only: only, except: except, queue: queue)
126
- assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
132
+ actual_count = enqueued_jobs_with(only: only, except: except, queue: queue).count
127
133
  end
134
+
135
+ assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
128
136
  end
129
137
 
130
138
  # Asserts that no jobs have been enqueued.
@@ -135,7 +143,7 @@ module ActiveJob
135
143
  # assert_enqueued_jobs 1
136
144
  # end
137
145
  #
138
- # If a block is passed, that block should not cause any job to be enqueued.
146
+ # If a block is passed, asserts that the block will not cause any job to be enqueued.
139
147
  #
140
148
  # def test_jobs_again
141
149
  # assert_no_enqueued_jobs do
@@ -143,7 +151,7 @@ module ActiveJob
143
151
  # end
144
152
  # end
145
153
  #
146
- # It can be asserted that no jobs of a specific kind are enqueued:
154
+ # Asserts that no jobs of a specific kind are enqueued by passing +:only+ option.
147
155
  #
148
156
  # def test_no_logging
149
157
  # assert_no_enqueued_jobs only: LoggingJob do
@@ -151,7 +159,7 @@ module ActiveJob
151
159
  # end
152
160
  # end
153
161
  #
154
- # It can be asserted that no jobs except specific class are enqueued:
162
+ # Asserts that no jobs except specific class are enqueued by passing +:except+ option.
155
163
  #
156
164
  # def test_no_logging
157
165
  # assert_no_enqueued_jobs except: HelloJob do
@@ -159,16 +167,27 @@ module ActiveJob
159
167
  # end
160
168
  # end
161
169
  #
170
+ # +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
171
+ # a hash containing the job's class and it's argument are passed as argument.
172
+ #
173
+ # Asserts that no jobs are enqueued to a specific queue by passing +:queue+ option
174
+ #
175
+ # def test_no_logging
176
+ # assert_no_enqueued_jobs queue: 'default' do
177
+ # LoggingJob.set(queue: :some_queue).perform_later
178
+ # end
179
+ # end
180
+ #
162
181
  # Note: This assertion is simply a shortcut for:
163
182
  #
164
183
  # assert_enqueued_jobs 0, &block
165
- def assert_no_enqueued_jobs(only: nil, except: nil, &block)
166
- assert_enqueued_jobs 0, only: only, except: except, &block
184
+ def assert_no_enqueued_jobs(only: nil, except: nil, queue: nil, &block)
185
+ assert_enqueued_jobs 0, only: only, except: except, queue: queue, &block
167
186
  end
168
187
 
169
188
  # Asserts that the number of performed jobs matches the given number.
170
189
  # If no block is passed, <tt>perform_enqueued_jobs</tt>
171
- # must be called around the job call.
190
+ # must be called around or after the job call.
172
191
  #
173
192
  # def test_jobs
174
193
  # assert_performed_jobs 0
@@ -178,13 +197,14 @@ module ActiveJob
178
197
  # end
179
198
  # assert_performed_jobs 1
180
199
  #
181
- # perform_enqueued_jobs do
182
- # HelloJob.perform_later('yves')
183
- # assert_performed_jobs 2
184
- # end
200
+ # HelloJob.perform_later('yves')
201
+ #
202
+ # perform_enqueued_jobs
203
+ #
204
+ # assert_performed_jobs 2
185
205
  # end
186
206
  #
187
- # If a block is passed, that block should cause the specified number of
207
+ # If a block is passed, asserts that the block will cause the specified number of
188
208
  # jobs to be performed.
189
209
  #
190
210
  # def test_jobs_again
@@ -198,7 +218,7 @@ module ActiveJob
198
218
  # end
199
219
  # end
200
220
  #
201
- # The block form supports filtering. If the :only option is specified,
221
+ # This method also supports filtering. If the +:only+ option is specified,
202
222
  # then only the listed job(s) will be performed.
203
223
  #
204
224
  # def test_hello_job
@@ -208,7 +228,7 @@ module ActiveJob
208
228
  # end
209
229
  # end
210
230
  #
211
- # Also if the :except option is specified,
231
+ # Also if the +:except+ option is specified,
212
232
  # then the job(s) except specific class will be performed.
213
233
  #
214
234
  # def test_hello_job
@@ -229,17 +249,42 @@ module ActiveJob
229
249
  # end
230
250
  # end
231
251
  # end
232
- def assert_performed_jobs(number, only: nil, except: nil)
252
+ #
253
+ # A proc may also be specified. When passed a Proc, the job's instance will be passed as argument.
254
+ #
255
+ # def test_hello_and_logging_jobs
256
+ # assert_nothing_raised do
257
+ # assert_performed_jobs(1, only: ->(job) { job.is_a?(HelloJob) }) do
258
+ # HelloJob.perform_later('jeremy')
259
+ # LoggingJob.perform_later('stewie')
260
+ # RescueJob.perform_later('david')
261
+ # end
262
+ # end
263
+ # end
264
+ #
265
+ # If the +:queue+ option is specified,
266
+ # then only the job(s) enqueued to a specific queue will be performed.
267
+ #
268
+ # def test_assert_performed_jobs_with_queue_option
269
+ # assert_performed_jobs 1, queue: :some_queue do
270
+ # HelloJob.set(queue: :some_queue).perform_later("jeremy")
271
+ # HelloJob.set(queue: :other_queue).perform_later("bogdan")
272
+ # end
273
+ # end
274
+ def assert_performed_jobs(number, only: nil, except: nil, queue: nil, &block)
233
275
  if block_given?
234
276
  original_count = performed_jobs.size
235
- perform_enqueued_jobs(only: only, except: except) { yield }
277
+
278
+ perform_enqueued_jobs(only: only, except: except, queue: queue, &block)
279
+
236
280
  new_count = performed_jobs.size
237
- assert_equal number, new_count - original_count,
238
- "#{number} jobs expected, but #{new_count - original_count} were performed"
281
+
282
+ performed_jobs_size = new_count - original_count
239
283
  else
240
- performed_jobs_size = performed_jobs.size
241
- assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
284
+ performed_jobs_size = performed_jobs_with(only: only, except: except, queue: queue).count
242
285
  end
286
+
287
+ assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
243
288
  end
244
289
 
245
290
  # Asserts that no jobs have been performed.
@@ -253,7 +298,7 @@ module ActiveJob
253
298
  # end
254
299
  # end
255
300
  #
256
- # If a block is passed, that block should not cause any job to be performed.
301
+ # If a block is passed, asserts that the block will not cause any job to be performed.
257
302
  #
258
303
  # def test_jobs_again
259
304
  # assert_no_performed_jobs do
@@ -261,7 +306,7 @@ module ActiveJob
261
306
  # end
262
307
  # end
263
308
  #
264
- # The block form supports filtering. If the :only option is specified,
309
+ # The block form supports filtering. If the +:only+ option is specified,
265
310
  # then only the listed job(s) will not be performed.
266
311
  #
267
312
  # def test_no_logging
@@ -270,7 +315,7 @@ module ActiveJob
270
315
  # end
271
316
  # end
272
317
  #
273
- # Also if the :except option is specified,
318
+ # Also if the +:except+ option is specified,
274
319
  # then the job(s) except specific class will not be performed.
275
320
  #
276
321
  # def test_no_logging
@@ -279,17 +324,63 @@ module ActiveJob
279
324
  # end
280
325
  # end
281
326
  #
327
+ # +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
328
+ # an instance of the job will be passed as argument.
329
+ #
330
+ # If the +:queue+ option is specified,
331
+ # then only the job(s) enqueued to a specific queue will not be performed.
332
+ #
333
+ # def test_assert_no_performed_jobs_with_queue_option
334
+ # assert_no_performed_jobs queue: :some_queue do
335
+ # HelloJob.set(queue: :other_queue).perform_later("jeremy")
336
+ # end
337
+ # end
338
+ #
282
339
  # Note: This assertion is simply a shortcut for:
283
340
  #
284
341
  # assert_performed_jobs 0, &block
285
- def assert_no_performed_jobs(only: nil, except: nil, &block)
286
- assert_performed_jobs 0, only: only, except: except, &block
342
+ def assert_no_performed_jobs(only: nil, except: nil, queue: nil, &block)
343
+ assert_performed_jobs 0, only: only, except: except, queue: queue, &block
287
344
  end
288
345
 
289
- # Asserts that the job passed in the block has been enqueued with the given arguments.
346
+ # Asserts that the job has been enqueued with the given arguments.
347
+ #
348
+ # def test_assert_enqueued_with
349
+ # MyJob.perform_later(1,2,3)
350
+ # assert_enqueued_with(job: MyJob, args: [1,2,3])
351
+ #
352
+ # MyJob.set(wait_until: Date.tomorrow.noon, queue: "my_queue").perform_later
353
+ # assert_enqueued_with(at: Date.tomorrow.noon, queue: "my_queue")
354
+ # end
355
+ #
356
+ # The given arguments may also be specified as matcher procs that return a
357
+ # boolean value indicating whether a job's attribute meets certain criteria.
358
+ #
359
+ # For example, a proc can be used to match a range of times:
360
+ #
361
+ # def test_assert_enqueued_with
362
+ # at_matcher = ->(job_at) { (Date.yesterday..Date.tomorrow).cover?(job_at) }
363
+ #
364
+ # MyJob.set(wait_until: Date.today.noon).perform_later
365
+ #
366
+ # assert_enqueued_with(job: MyJob, at: at_matcher)
367
+ # end
368
+ #
369
+ # A proc can also be used to match a subset of a job's args:
290
370
  #
291
371
  # def test_assert_enqueued_with
292
- # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
372
+ # args_matcher = ->(job_args) { job_args[0].key?(:foo) }
373
+ #
374
+ # MyJob.perform_later(foo: "bar", other_arg: "No need to check in the test")
375
+ #
376
+ # assert_enqueued_with(job: MyJob, args: args_matcher)
377
+ # end
378
+ #
379
+ # If a block is passed, asserts that the block will cause the job to be
380
+ # enqueued with the given arguments.
381
+ #
382
+ # def test_assert_enqueued_with
383
+ # assert_enqueued_with(job: MyJob, args: [1,2,3]) do
293
384
  # MyJob.perform_later(1,2,3)
294
385
  # end
295
386
  #
@@ -297,24 +388,89 @@ module ActiveJob
297
388
  # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
298
389
  # end
299
390
  # end
300
- def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil)
301
- original_enqueued_jobs_count = enqueued_jobs.count
391
+ def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil, &block)
302
392
  expected = { job: job, args: args, at: at, queue: queue }.compact
303
393
  expected_args = prepare_args_for_assertion(expected)
304
- yield
305
- in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
306
- matching_job = in_block_jobs.find do |in_block_job|
307
- deserialized_job = deserialize_args_for_assertion(in_block_job)
308
- expected_args.all? { |key, value| value == deserialized_job[key] }
394
+ potential_matches = []
395
+
396
+ if block_given?
397
+ original_enqueued_jobs = enqueued_jobs.dup
398
+
399
+ assert_nothing_raised(&block)
400
+
401
+ jobs = enqueued_jobs - original_enqueued_jobs
402
+ else
403
+ jobs = enqueued_jobs
404
+ end
405
+
406
+ matching_job = jobs.find do |enqueued_job|
407
+ deserialized_job = deserialize_args_for_assertion(enqueued_job)
408
+ potential_matches << deserialized_job
409
+
410
+ expected_args.all? do |key, value|
411
+ if value.respond_to?(:call)
412
+ value.call(deserialized_job[key])
413
+ else
414
+ value == deserialized_job[key]
415
+ end
416
+ end
309
417
  end
310
- assert matching_job, "No enqueued job found with #{expected}"
418
+
419
+ message = +"No enqueued job found with #{expected}"
420
+ message << "\n\nPotential matches: #{potential_matches.join("\n")}" if potential_matches.present?
421
+ assert matching_job, message
311
422
  instantiate_job(matching_job)
312
423
  end
313
424
 
314
- # Asserts that the job passed in the block has been performed with the given arguments.
425
+ # Asserts that the job has been performed with the given arguments.
426
+ #
427
+ # def test_assert_performed_with
428
+ # MyJob.perform_later(1,2,3)
429
+ #
430
+ # perform_enqueued_jobs
431
+ #
432
+ # assert_performed_with(job: MyJob, args: [1,2,3])
433
+ #
434
+ # MyJob.set(wait_until: Date.tomorrow.noon, queue: "my_queue").perform_later
435
+ #
436
+ # perform_enqueued_jobs
437
+ #
438
+ # assert_performed_with(at: Date.tomorrow.noon, queue: "my_queue")
439
+ # end
440
+ #
441
+ # The given arguments may also be specified as matcher procs that return a
442
+ # boolean value indicating whether a job's attribute meets certain criteria.
443
+ #
444
+ # For example, a proc can be used to match a range of times:
315
445
  #
316
446
  # def test_assert_performed_with
317
- # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
447
+ # at_matcher = ->(job_at) { (Date.yesterday..Date.tomorrow).cover?(job_at) }
448
+ #
449
+ # MyJob.set(wait_until: Date.today.noon).perform_later
450
+ #
451
+ # perform_enqueued_jobs
452
+ #
453
+ # assert_performed_with(job: MyJob, at: at_matcher)
454
+ # end
455
+ #
456
+ # A proc can also be used to match a subset of a job's args:
457
+ #
458
+ # def test_assert_performed_with
459
+ # args_matcher = ->(job_args) { job_args[0].key?(:foo) }
460
+ #
461
+ # MyJob.perform_later(foo: "bar", other_arg: "No need to check in the test")
462
+ #
463
+ # perform_enqueued_jobs
464
+ #
465
+ # assert_performed_with(job: MyJob, args: args_matcher)
466
+ # end
467
+ #
468
+ # If a block is passed, that block performs all of the jobs that were
469
+ # enqueued throughout the duration of the block and asserts that
470
+ # the job has been performed with the given arguments in the block.
471
+ #
472
+ # def test_assert_performed_with
473
+ # assert_performed_with(job: MyJob, args: [1,2,3]) do
318
474
  # MyJob.perform_later(1,2,3)
319
475
  # end
320
476
  #
@@ -322,21 +478,44 @@ module ActiveJob
322
478
  # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
323
479
  # end
324
480
  # end
325
- def assert_performed_with(job: nil, args: nil, at: nil, queue: nil)
326
- original_performed_jobs_count = performed_jobs.count
481
+ def assert_performed_with(job: nil, args: nil, at: nil, queue: nil, &block)
327
482
  expected = { job: job, args: args, at: at, queue: queue }.compact
328
483
  expected_args = prepare_args_for_assertion(expected)
329
- perform_enqueued_jobs { yield }
330
- in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
331
- matching_job = in_block_jobs.find do |in_block_job|
332
- deserialized_job = deserialize_args_for_assertion(in_block_job)
333
- expected_args.all? { |key, value| value == deserialized_job[key] }
484
+ potential_matches = []
485
+
486
+ if block_given?
487
+ original_performed_jobs_count = performed_jobs.count
488
+
489
+ perform_enqueued_jobs(&block)
490
+
491
+ jobs = performed_jobs.drop(original_performed_jobs_count)
492
+ else
493
+ jobs = performed_jobs
334
494
  end
335
- assert matching_job, "No performed job found with #{expected}"
495
+
496
+ matching_job = jobs.find do |enqueued_job|
497
+ deserialized_job = deserialize_args_for_assertion(enqueued_job)
498
+ potential_matches << deserialized_job
499
+
500
+ expected_args.all? do |key, value|
501
+ if value.respond_to?(:call)
502
+ value.call(deserialized_job[key])
503
+ else
504
+ value == deserialized_job[key]
505
+ end
506
+ end
507
+ end
508
+
509
+ message = +"No performed job found with #{expected}"
510
+ message << "\n\nPotential matches: #{potential_matches.join("\n")}" if potential_matches.present?
511
+ assert matching_job, message
512
+
336
513
  instantiate_job(matching_job)
337
514
  end
338
515
 
339
- # Performs all enqueued jobs in the duration of the block.
516
+ # Performs all enqueued jobs. If a block is given, performs all of the jobs
517
+ # that were enqueued throughout the duration of the block. If a block is
518
+ # not given, performs all of the enqueued jobs up to this point in the test.
340
519
  #
341
520
  # def test_perform_enqueued_jobs
342
521
  # perform_enqueued_jobs do
@@ -345,6 +524,14 @@ module ActiveJob
345
524
  # assert_performed_jobs 1
346
525
  # end
347
526
  #
527
+ # def test_perform_enqueued_jobs_without_block
528
+ # MyJob.perform_later(1, 2, 3)
529
+ #
530
+ # perform_enqueued_jobs
531
+ #
532
+ # assert_performed_jobs 1
533
+ # end
534
+ #
348
535
  # This method also supports filtering. If the +:only+ option is specified,
349
536
  # then only the listed job(s) will be performed.
350
537
  #
@@ -367,24 +554,50 @@ module ActiveJob
367
554
  # assert_performed_jobs 1
368
555
  # end
369
556
  #
370
- def perform_enqueued_jobs(only: nil, except: nil)
557
+ # +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
558
+ # an instance of the job will be passed as argument.
559
+ #
560
+ # If the +:queue+ option is specified,
561
+ # then only the job(s) enqueued to a specific queue will be performed.
562
+ #
563
+ # def test_perform_enqueued_jobs_with_queue
564
+ # perform_enqueued_jobs queue: :some_queue do
565
+ # MyJob.set(queue: :some_queue).perform_later(1, 2, 3) # will be performed
566
+ # HelloJob.set(queue: :other_queue).perform_later(1, 2, 3) # will not be performed
567
+ # end
568
+ # assert_performed_jobs 1
569
+ # end
570
+ #
571
+ # If the +:at+ option is specified, then only run jobs enqueued to run
572
+ # immediately or before the given time
573
+ def perform_enqueued_jobs(only: nil, except: nil, queue: nil, at: nil, &block)
574
+ return flush_enqueued_jobs(only: only, except: except, queue: queue, at: at) unless block_given?
575
+
371
576
  validate_option(only: only, except: except)
577
+
372
578
  old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
373
579
  old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
374
580
  old_filter = queue_adapter.filter
375
581
  old_reject = queue_adapter.reject
582
+ old_queue = queue_adapter.queue
583
+ old_at = queue_adapter.at
376
584
 
377
585
  begin
378
586
  queue_adapter.perform_enqueued_jobs = true
379
587
  queue_adapter.perform_enqueued_at_jobs = true
380
588
  queue_adapter.filter = only
381
589
  queue_adapter.reject = except
382
- yield
590
+ queue_adapter.queue = queue
591
+ queue_adapter.at = at
592
+
593
+ assert_nothing_raised(&block)
383
594
  ensure
384
595
  queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
385
596
  queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
386
597
  queue_adapter.filter = old_filter
387
598
  queue_adapter.reject = old_reject
599
+ queue_adapter.queue = old_queue
600
+ queue_adapter.at = old_at
388
601
  end
389
602
  end
390
603
 
@@ -406,39 +619,74 @@ module ActiveJob
406
619
  performed_jobs.clear
407
620
  end
408
621
 
409
- def enqueued_jobs_size(only: nil, except: nil, queue: nil)
622
+ def jobs_with(jobs, only: nil, except: nil, queue: nil, at: nil)
410
623
  validate_option(only: only, except: except)
411
- enqueued_jobs.count do |job|
624
+
625
+ jobs.dup.select do |job|
412
626
  job_class = job.fetch(:job)
627
+
413
628
  if only
414
- next false unless Array(only).include?(job_class)
629
+ next false unless filter_as_proc(only).call(job)
415
630
  elsif except
416
- next false if Array(except).include?(job_class)
631
+ next false if filter_as_proc(except).call(job)
417
632
  end
633
+
418
634
  if queue
419
635
  next false unless queue.to_s == job.fetch(:queue, job_class.queue_name)
420
636
  end
637
+
638
+ if at && job[:at]
639
+ next false if job[:at] > at.to_f
640
+ end
641
+
642
+ yield job if block_given?
643
+
421
644
  true
422
645
  end
423
646
  end
424
647
 
648
+ def filter_as_proc(filter)
649
+ return filter if filter.is_a?(Proc)
650
+
651
+ ->(job) { Array(filter).include?(job.fetch(:job)) }
652
+ end
653
+
654
+ def enqueued_jobs_with(only: nil, except: nil, queue: nil, at: nil, &block)
655
+ jobs_with(enqueued_jobs, only: only, except: except, queue: queue, at: at, &block)
656
+ end
657
+
658
+ def performed_jobs_with(only: nil, except: nil, queue: nil, &block)
659
+ jobs_with(performed_jobs, only: only, except: except, queue: queue, &block)
660
+ end
661
+
662
+ def flush_enqueued_jobs(only: nil, except: nil, queue: nil, at: nil)
663
+ enqueued_jobs_with(only: only, except: except, queue: queue, at: at) do |payload|
664
+ queue_adapter.enqueued_jobs.delete(payload)
665
+ queue_adapter.performed_jobs << payload
666
+ instantiate_job(payload).perform_now
667
+ end.count
668
+ end
669
+
425
670
  def prepare_args_for_assertion(args)
426
671
  args.dup.tap do |arguments|
427
- arguments[:at] = arguments[:at].to_f if arguments[:at]
672
+ if arguments[:at].acts_like?(:time)
673
+ at_range = arguments[:at] - 1..arguments[:at] + 1
674
+ arguments[:at] = ->(at) { at_range.cover?(at) }
675
+ end
428
676
  end
429
677
  end
430
678
 
431
679
  def deserialize_args_for_assertion(job)
432
680
  job.dup.tap do |new_job|
681
+ new_job[:at] = Time.at(new_job[:at]) if new_job[:at]
433
682
  new_job[:args] = ActiveJob::Arguments.deserialize(new_job[:args]) if new_job[:args]
434
683
  end
435
684
  end
436
685
 
437
686
  def instantiate_job(payload)
438
- args = ActiveJob::Arguments.deserialize(payload[:args])
439
- job = payload[:job].new(*args)
687
+ job = payload[:job].deserialize(payload)
440
688
  job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
441
- job.queue_name = payload[:queue]
689
+ job.send(:deserialize_arguments_if_needed)
442
690
  job
443
691
  end
444
692