2015-12-31 18:33:35 -05:00
|
|
|
# frozen_string_literal: true
|
2014-01-30 12:22:07 -05:00
|
|
|
|
2019-04-01 12:20:41 -04:00
|
|
|
require "securerandom"
|
|
|
|
require "sidekiq"
|
2013-03-28 00:24:47 -04:00
|
|
|
|
2019-04-01 12:20:41 -04:00
|
|
|
module Sidekiq
|
2013-09-20 17:39:11 -04:00
|
|
|
class Testing
|
|
|
|
class << self
|
|
|
|
attr_accessor :__test_mode
|
|
|
|
|
2014-10-09 09:55:07 -04:00
|
|
|
def __set_test_mode(mode)
|
|
|
|
if block_given?
|
2019-04-01 12:20:41 -04:00
|
|
|
current_mode = __test_mode
|
2013-09-20 17:39:11 -04:00
|
|
|
begin
|
|
|
|
self.__test_mode = mode
|
2014-10-09 09:55:07 -04:00
|
|
|
yield
|
2013-09-20 17:39:11 -04:00
|
|
|
ensure
|
|
|
|
self.__test_mode = current_mode
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self.__test_mode = mode
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def disable!(&block)
|
|
|
|
__set_test_mode(:disable, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def fake!(&block)
|
|
|
|
__set_test_mode(:fake, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def inline!(&block)
|
|
|
|
__set_test_mode(:inline, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def enabled?
|
2019-04-01 12:20:41 -04:00
|
|
|
__test_mode != :disable
|
2013-09-20 17:39:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def disabled?
|
2019-04-01 12:20:41 -04:00
|
|
|
__test_mode == :disable
|
2013-09-20 17:39:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def fake?
|
2019-04-01 12:20:41 -04:00
|
|
|
__test_mode == :fake
|
2013-09-20 17:39:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def inline?
|
2019-04-01 12:20:41 -04:00
|
|
|
__test_mode == :inline
|
2013-09-20 17:39:11 -04:00
|
|
|
end
|
2015-09-04 18:35:33 -04:00
|
|
|
|
|
|
|
def server_middleware
|
|
|
|
@server_chain ||= Middleware::Chain.new
|
|
|
|
yield @server_chain if block_given?
|
|
|
|
@server_chain
|
|
|
|
end
|
2017-05-14 23:58:44 -04:00
|
|
|
|
|
|
|
def constantize(str)
|
2019-04-01 12:20:41 -04:00
|
|
|
names = str.split("::")
|
2017-05-14 23:58:44 -04:00
|
|
|
names.shift if names.empty? || names.first.empty?
|
|
|
|
|
|
|
|
names.inject(Object) do |constant, name|
|
|
|
|
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
|
|
|
end
|
|
|
|
end
|
2013-09-20 17:39:11 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Default to fake testing to keep old behavior
|
|
|
|
Sidekiq::Testing.fake!
|
|
|
|
|
2013-05-30 14:39:01 -04:00
|
|
|
class EmptyQueueError < RuntimeError; end
|
|
|
|
|
2018-05-30 12:57:37 -04:00
|
|
|
module TestingClient
|
2013-10-24 00:47:57 -04:00
|
|
|
def raw_push(payloads)
|
|
|
|
if Sidekiq::Testing.fake?
|
|
|
|
payloads.each do |job|
|
2016-11-26 00:19:16 -05:00
|
|
|
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
2019-04-01 12:20:41 -04:00
|
|
|
job["enqueued_at"] = Time.now.to_f unless job["at"]
|
|
|
|
Queues.push(job["queue"], job["class"], job)
|
2013-10-24 00:47:57 -04:00
|
|
|
end
|
|
|
|
true
|
|
|
|
elsif Sidekiq::Testing.inline?
|
2014-09-10 18:09:57 -04:00
|
|
|
payloads.each do |job|
|
2019-04-01 12:20:41 -04:00
|
|
|
klass = Sidekiq::Testing.constantize(job["class"])
|
|
|
|
job["id"] ||= SecureRandom.hex(12)
|
2015-11-17 13:07:30 -05:00
|
|
|
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
|
|
klass.process_job(job_hash)
|
2013-03-28 00:24:47 -04:00
|
|
|
end
|
2013-10-24 00:47:57 -04:00
|
|
|
true
|
|
|
|
else
|
2018-05-29 14:02:30 -04:00
|
|
|
super
|
2013-03-28 00:24:47 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-30 12:57:37 -04:00
|
|
|
Sidekiq::Client.prepend TestingClient
|
2018-05-29 14:02:30 -04:00
|
|
|
|
2015-11-17 13:07:30 -05:00
|
|
|
module Queues
|
|
|
|
##
|
|
|
|
# The Queues class is only for testing the fake queue implementation.
|
2015-11-25 10:23:27 -05:00
|
|
|
# There are 2 data structures involved in tandem. This is due to the
|
|
|
|
# Rspec syntax of change(QueueWorker.jobs, :size). It keeps a reference
|
|
|
|
# to the array. Because the array was dervied from a filter of the total
|
|
|
|
# jobs enqueued, it appeared as though the array didn't change.
|
|
|
|
#
|
|
|
|
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
|
|
|
# on the queue, and another with keys of the worker names, so the array for
|
|
|
|
# QueueWorker.jobs is a straight reference to a real array.
|
|
|
|
#
|
|
|
|
# Queue-based hash:
|
2015-11-17 13:07:30 -05:00
|
|
|
#
|
|
|
|
# {
|
|
|
|
# "default"=>[
|
|
|
|
# {
|
|
|
|
# "class"=>"TestTesting::QueueWorker",
|
|
|
|
# "args"=>[1, 2],
|
|
|
|
# "retry"=>true,
|
|
|
|
# "queue"=>"default",
|
|
|
|
# "jid"=>"abc5b065c5c4b27fc1102833",
|
|
|
|
# "created_at"=>1447445554.419934
|
|
|
|
# }
|
|
|
|
# ]
|
|
|
|
# }
|
|
|
|
#
|
2015-11-25 10:23:27 -05:00
|
|
|
# Worker-based hash:
|
|
|
|
#
|
|
|
|
# {
|
|
|
|
# "TestTesting::QueueWorker"=>[
|
|
|
|
# {
|
|
|
|
# "class"=>"TestTesting::QueueWorker",
|
|
|
|
# "args"=>[1, 2],
|
|
|
|
# "retry"=>true,
|
|
|
|
# "queue"=>"default",
|
|
|
|
# "jid"=>"abc5b065c5c4b27fc1102833",
|
|
|
|
# "created_at"=>1447445554.419934
|
|
|
|
# }
|
|
|
|
# ]
|
|
|
|
# }
|
|
|
|
#
|
2015-11-17 13:07:30 -05:00
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# require 'sidekiq/testing'
|
|
|
|
#
|
|
|
|
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
|
|
# HardWorker.perform_async(:something)
|
|
|
|
# assert_equal 1, Sidekiq::Queues["default"].size
|
|
|
|
# assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
|
|
|
|
#
|
|
|
|
# You can also clear all workers' jobs:
|
|
|
|
#
|
|
|
|
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
|
|
# HardWorker.perform_async(:something)
|
|
|
|
# Sidekiq::Queues.clear_all
|
|
|
|
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
|
|
#
|
|
|
|
# This can be useful to make sure jobs don't linger between tests:
|
|
|
|
#
|
|
|
|
# RSpec.configure do |config|
|
|
|
|
# config.before(:each) do
|
|
|
|
# Sidekiq::Queues.clear_all
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
class << self
|
|
|
|
def [](queue)
|
2015-11-25 07:15:34 -05:00
|
|
|
jobs_by_queue[queue]
|
2015-11-17 13:07:30 -05:00
|
|
|
end
|
|
|
|
|
2015-11-25 07:15:34 -05:00
|
|
|
def push(queue, klass, job)
|
|
|
|
jobs_by_queue[queue] << job
|
|
|
|
jobs_by_worker[klass] << job
|
|
|
|
end
|
|
|
|
|
|
|
|
def jobs_by_queue
|
|
|
|
@jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
|
|
|
|
end
|
|
|
|
|
|
|
|
def jobs_by_worker
|
|
|
|
@jobs_by_worker ||= Hash.new { |hash, key| hash[key] = [] }
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_for(jid, queue, klass)
|
2016-01-14 09:25:03 -05:00
|
|
|
jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
|
2015-11-25 07:15:34 -05:00
|
|
|
jobs_by_worker[klass].delete_if { |job| job["jid"] == jid }
|
|
|
|
end
|
|
|
|
|
|
|
|
def clear_for(queue, klass)
|
|
|
|
jobs_by_queue[queue].clear
|
|
|
|
jobs_by_worker[klass].clear
|
2015-11-17 13:07:30 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def clear_all
|
2015-11-25 07:15:34 -05:00
|
|
|
jobs_by_queue.clear
|
|
|
|
jobs_by_worker.clear
|
2015-11-17 13:07:30 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-02-10 00:46:44 -05:00
|
|
|
module Worker
|
2012-02-10 23:30:14 -05:00
|
|
|
##
|
2012-02-14 12:00:26 -05:00
|
|
|
# The Sidekiq testing infrastructure overrides perform_async
|
2012-02-10 23:30:14 -05:00
|
|
|
# so that it does not actually touch the network. Instead it
|
2012-02-14 12:00:26 -05:00
|
|
|
# stores the asynchronous jobs in a per-class array so that
|
2012-02-10 23:30:14 -05:00
|
|
|
# their presence/absence can be asserted by your tests.
|
|
|
|
#
|
|
|
|
# This is similar to ActionMailer's :test delivery_method and its
|
|
|
|
# ActionMailer::Base.deliveries array.
|
2012-02-14 12:00:26 -05:00
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# require 'sidekiq/testing'
|
|
|
|
#
|
|
|
|
# assert_equal 0, HardWorker.jobs.size
|
|
|
|
# HardWorker.perform_async(:something)
|
|
|
|
# assert_equal 1, HardWorker.jobs.size
|
|
|
|
# assert_equal :something, HardWorker.jobs[0]['args'][0]
|
|
|
|
#
|
2012-04-05 23:06:47 -04:00
|
|
|
# assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
|
2012-10-19 00:16:28 -04:00
|
|
|
# MyMailer.delay.send_welcome_email('foo@example.com')
|
|
|
|
# assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
|
|
|
|
#
|
|
|
|
# You can also clear and drain all workers' jobs:
|
|
|
|
#
|
|
|
|
# assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
|
|
|
|
# assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
|
|
|
|
#
|
|
|
|
# MyMailer.delay.send_welcome_email('foo@example.com')
|
|
|
|
# MyModel.delay.do_something_hard
|
|
|
|
#
|
2012-04-05 23:06:47 -04:00
|
|
|
# assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
|
2012-10-19 00:16:28 -04:00
|
|
|
# assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
|
|
|
|
#
|
|
|
|
# Sidekiq::Worker.clear_all # or .drain_all
|
|
|
|
#
|
|
|
|
# assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
|
|
|
|
# assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
|
|
|
|
#
|
|
|
|
# This can be useful to make sure jobs don't linger between tests:
|
|
|
|
#
|
|
|
|
# RSpec.configure do |config|
|
|
|
|
# config.before(:each) do
|
|
|
|
# Sidekiq::Worker.clear_all
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# or for acceptance testing, i.e. with cucumber:
|
|
|
|
#
|
|
|
|
# AfterStep do
|
|
|
|
# Sidekiq::Worker.drain_all
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# When I sign up as "foo@example.com"
|
|
|
|
# Then I should receive a welcome email to "foo@example.com"
|
2012-04-05 23:06:47 -04:00
|
|
|
#
|
2012-02-10 00:46:44 -05:00
|
|
|
module ClassMethods
|
2015-11-17 13:07:30 -05:00
|
|
|
# Queue for this worker
|
|
|
|
def queue
|
2019-07-22 22:46:58 -04:00
|
|
|
get_sidekiq_options["queue"]
|
2015-11-17 13:07:30 -05:00
|
|
|
end
|
|
|
|
|
2012-10-19 00:16:28 -04:00
|
|
|
# Jobs queued for this worker
|
2012-02-10 23:30:14 -05:00
|
|
|
def jobs
|
2019-04-01 12:20:41 -04:00
|
|
|
Queues.jobs_by_worker[to_s]
|
2012-02-10 00:46:44 -05:00
|
|
|
end
|
2012-05-02 13:45:05 -04:00
|
|
|
|
2012-10-19 00:16:28 -04:00
|
|
|
# Clear all jobs for this worker
|
|
|
|
def clear
|
2019-04-01 12:20:41 -04:00
|
|
|
Queues.clear_for(queue, to_s)
|
2012-10-19 00:16:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Drain and run all jobs for this worker
|
2012-05-02 13:59:31 -04:00
|
|
|
def drain
|
2015-11-17 13:07:30 -05:00
|
|
|
while jobs.any?
|
|
|
|
next_job = jobs.first
|
2019-04-01 12:20:41 -04:00
|
|
|
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
2015-11-17 13:07:30 -05:00
|
|
|
process_job(next_job)
|
2012-05-02 13:45:05 -04:00
|
|
|
end
|
|
|
|
end
|
2013-05-30 01:06:08 -04:00
|
|
|
|
|
|
|
# Pop out a single job and perform it
|
2013-05-30 12:28:47 -04:00
|
|
|
def perform_one
|
2013-05-31 00:16:56 -04:00
|
|
|
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
2015-11-17 13:07:30 -05:00
|
|
|
next_job = jobs.first
|
2019-04-01 12:20:41 -04:00
|
|
|
Queues.delete_for(next_job["jid"], queue, to_s)
|
2015-11-17 13:07:30 -05:00
|
|
|
process_job(next_job)
|
2015-09-04 18:35:33 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def process_job(job)
|
2013-07-01 17:31:20 -04:00
|
|
|
worker = new
|
2019-04-01 12:20:41 -04:00
|
|
|
worker.jid = job["jid"]
|
|
|
|
worker.bid = job["bid"] if worker.respond_to?(:bid=)
|
|
|
|
Sidekiq::Testing.server_middleware.invoke(worker, job, job["queue"]) do
|
|
|
|
execute_job(worker, job["args"])
|
2015-09-04 18:35:33 -04:00
|
|
|
end
|
2014-09-09 21:57:39 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def execute_job(worker, args)
|
|
|
|
worker.perform(*args)
|
2013-05-30 01:06:08 -04:00
|
|
|
end
|
2012-02-10 00:46:44 -05:00
|
|
|
end
|
2012-10-19 00:16:28 -04:00
|
|
|
|
|
|
|
class << self
|
|
|
|
def jobs # :nodoc:
|
2015-11-25 07:15:34 -05:00
|
|
|
Queues.jobs_by_queue.values.flatten
|
2012-10-19 00:16:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Clear all queued jobs across all workers
|
|
|
|
def clear_all
|
2015-11-17 13:07:30 -05:00
|
|
|
Queues.clear_all
|
2012-10-19 00:16:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Drain all queued jobs across all workers
|
|
|
|
def drain_all
|
2015-11-17 13:07:30 -05:00
|
|
|
while jobs.any?
|
|
|
|
worker_classes = jobs.map { |job| job["class"] }.uniq
|
|
|
|
|
|
|
|
worker_classes.each do |worker_class|
|
2017-05-14 23:58:44 -04:00
|
|
|
Sidekiq::Testing.constantize(worker_class).drain
|
2015-11-17 13:07:30 -05:00
|
|
|
end
|
2012-11-01 10:49:35 -04:00
|
|
|
end
|
2012-10-19 00:16:28 -04:00
|
|
|
end
|
|
|
|
end
|
2012-02-10 00:46:44 -05:00
|
|
|
end
|
|
|
|
end
|
2017-03-13 15:23:32 -04:00
|
|
|
|
2017-03-22 11:11:09 -04:00
|
|
|
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
|
2017-03-13 15:23:32 -04:00
|
|
|
puts("**************************************************")
|
|
|
|
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
|
|
|
puts("**************************************************")
|
|
|
|
end
|