292 lines
8.9 KiB
Ruby
292 lines
8.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "spec_helper"
|
|
|
|
RSpec.describe Gitlab::Database::Migrations::SidekiqHelpers do
|
|
let(:model) do
|
|
ActiveRecord::Migration.new.extend(described_class)
|
|
end
|
|
|
|
describe "sidekiq migration helpers", :redis do
|
|
let(:worker) do
|
|
Class.new do
|
|
include Sidekiq::Worker
|
|
|
|
sidekiq_options queue: "test"
|
|
|
|
def self.name
|
|
"WorkerClass"
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:worker_two) do
|
|
Class.new do
|
|
include Sidekiq::Worker
|
|
|
|
sidekiq_options queue: "test_two"
|
|
|
|
def self.name
|
|
"WorkerTwoClass"
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:same_queue_different_worker) do
|
|
Class.new do
|
|
include Sidekiq::Worker
|
|
|
|
sidekiq_options queue: "test"
|
|
|
|
def self.name
|
|
"SameQueueDifferentWorkerClass"
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:unrelated_worker) do
|
|
Class.new do
|
|
include Sidekiq::Worker
|
|
|
|
sidekiq_options queue: "unrelated"
|
|
|
|
def self.name
|
|
"UnrelatedWorkerClass"
|
|
end
|
|
end
|
|
end
|
|
|
|
before do
|
|
stub_const(worker.name, worker)
|
|
stub_const(worker_two.name, worker_two)
|
|
stub_const(unrelated_worker.name, unrelated_worker)
|
|
stub_const(same_queue_different_worker.name, same_queue_different_worker)
|
|
end
|
|
|
|
describe "#sidekiq_remove_jobs", :clean_gitlab_redis_queues do
|
|
def clear_queues
|
|
Sidekiq::Queue.new("test").clear
|
|
Sidekiq::Queue.new("test_two").clear
|
|
Sidekiq::Queue.new("unrelated").clear
|
|
Sidekiq::RetrySet.new.clear
|
|
Sidekiq::ScheduledSet.new.clear
|
|
end
|
|
|
|
around do |example|
|
|
clear_queues
|
|
Sidekiq::Testing.disable!(&example)
|
|
clear_queues
|
|
end
|
|
|
|
context 'when inside a transaction' do
|
|
it 'raises RuntimeError' do
|
|
expect(model).to receive(:transaction_open?).and_return(true)
|
|
|
|
expect { model.sidekiq_remove_jobs(job_klasses: [worker.name]) }
|
|
.to raise_error(RuntimeError)
|
|
end
|
|
end
|
|
|
|
context 'when outside a transaction' do
|
|
before do
|
|
allow(model).to receive(:transaction_open?).and_return(false)
|
|
allow(model).to receive(:disable_statement_timeout).and_call_original
|
|
end
|
|
|
|
context "when the constant is not defined" do
|
|
it "doesn't try to delete it" do
|
|
my_non_constant = +"SomeThingThatIsNotAConstant"
|
|
|
|
expect(Sidekiq::Queue).not_to receive(:new).with(any_args)
|
|
model.sidekiq_remove_jobs(job_klasses: [my_non_constant])
|
|
end
|
|
end
|
|
|
|
context "when the constant is defined" do
|
|
it "will use it find job instances to delete" do
|
|
my_constant = worker.name
|
|
expect(Sidekiq::Queue)
|
|
.to receive(:new)
|
|
.with(worker.queue)
|
|
.and_call_original
|
|
model.sidekiq_remove_jobs(job_klasses: [my_constant])
|
|
end
|
|
end
|
|
|
|
it "removes all related job instances from the job classes' queues" do
|
|
worker.perform_async
|
|
worker_two.perform_async
|
|
same_queue_different_worker.perform_async
|
|
unrelated_worker.perform_async
|
|
|
|
worker_queue = Sidekiq::Queue.new(worker.queue)
|
|
worker_two_queue = Sidekiq::Queue.new(worker_two.queue)
|
|
unrelated_queue = Sidekiq::Queue.new(unrelated_worker.queue)
|
|
|
|
expect(worker_queue.size).to eq(2)
|
|
expect(worker_two_queue.size).to eq(1)
|
|
expect(unrelated_queue.size).to eq(1)
|
|
|
|
model.sidekiq_remove_jobs(job_klasses: [worker.name, worker_two.name])
|
|
|
|
expect(worker_queue.size).to eq(1)
|
|
expect(worker_two_queue.size).to eq(0)
|
|
expect(worker_queue.map(&:klass)).not_to include(worker.name)
|
|
expect(worker_queue.map(&:klass)).to include(
|
|
same_queue_different_worker.name
|
|
)
|
|
expect(worker_two_queue.map(&:klass)).not_to include(worker_two.name)
|
|
expect(unrelated_queue.size).to eq(1)
|
|
end
|
|
|
|
context "when job instances are in the scheduled set" do
|
|
it "removes all related job instances from the scheduled set" do
|
|
worker.perform_in(1.hour)
|
|
worker_two.perform_in(1.hour)
|
|
unrelated_worker.perform_in(1.hour)
|
|
|
|
scheduled = Sidekiq::ScheduledSet.new
|
|
|
|
expect(scheduled.size).to eq(3)
|
|
expect(scheduled.map(&:klass)).to include(
|
|
worker.name,
|
|
worker_two.name,
|
|
unrelated_worker.name
|
|
)
|
|
|
|
model.sidekiq_remove_jobs(job_klasses: [worker.name, worker_two.name])
|
|
|
|
expect(scheduled.size).to eq(1)
|
|
expect(scheduled.map(&:klass)).not_to include(worker.name)
|
|
expect(scheduled.map(&:klass)).not_to include(worker_two.name)
|
|
expect(scheduled.map(&:klass)).to include(unrelated_worker.name)
|
|
end
|
|
end
|
|
|
|
context "when job instances are in the retry set" do
|
|
include_context "when handling retried jobs"
|
|
|
|
it "removes all related job instances from the retry set" do
|
|
retry_in(worker, 1.hour)
|
|
retry_in(worker, 2.hours)
|
|
retry_in(worker, 3.hours)
|
|
retry_in(worker_two, 4.hours)
|
|
retry_in(unrelated_worker, 5.hours)
|
|
|
|
retries = Sidekiq::RetrySet.new
|
|
|
|
expect(retries.size).to eq(5)
|
|
expect(retries.map(&:klass)).to include(
|
|
worker.name,
|
|
worker_two.name,
|
|
unrelated_worker.name
|
|
)
|
|
|
|
model.sidekiq_remove_jobs(job_klasses: [worker.name, worker_two.name])
|
|
|
|
expect(retries.size).to eq(1)
|
|
expect(retries.map(&:klass)).not_to include(worker.name)
|
|
expect(retries.map(&:klass)).not_to include(worker_two.name)
|
|
expect(retries.map(&:klass)).to include(unrelated_worker.name)
|
|
end
|
|
end
|
|
|
|
# Imitate job deletion returning zero and then non zero.
|
|
context "when job fails to be deleted" do
|
|
let(:job_double) do
|
|
instance_double(
|
|
"Sidekiq::JobRecord",
|
|
klass: worker.name
|
|
)
|
|
end
|
|
|
|
context "and does not work enough times in a row before max attempts" do
|
|
it "tries the max attempts without succeeding" do
|
|
worker.perform_async
|
|
|
|
allow(job_double).to receive(:delete).and_return(true)
|
|
|
|
# Scheduled set runs last so only need to stub out its values.
|
|
allow(Sidekiq::ScheduledSet)
|
|
.to receive(:new)
|
|
.and_return([job_double])
|
|
|
|
expect(model.sidekiq_remove_jobs(job_klasses: [worker.name]))
|
|
.to eq(
|
|
{
|
|
attempts: 5,
|
|
success: false
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
context "and then it works enough times in a row before max attempts" do
|
|
it "succeeds" do
|
|
worker.perform_async
|
|
|
|
# attempt 1: false will increment the streak once to 1
|
|
# attempt 2: true resets it back to 0
|
|
# attempt 3: false will increment the streak once to 1
|
|
# attempt 4: false will increment the streak once to 2, loop breaks
|
|
allow(job_double).to receive(:delete).and_return(false, true, false)
|
|
|
|
worker.perform_async
|
|
|
|
# Scheduled set runs last so only need to stub out its values.
|
|
allow(Sidekiq::ScheduledSet)
|
|
.to receive(:new)
|
|
.and_return([job_double])
|
|
|
|
expect(model.sidekiq_remove_jobs(job_klasses: [worker.name]))
|
|
.to eq(
|
|
{
|
|
attempts: 4,
|
|
success: true
|
|
}
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#sidekiq_queue_length" do
|
|
context "when queue is empty" do
|
|
it "returns zero" do
|
|
Sidekiq::Testing.disable! do
|
|
expect(model.sidekiq_queue_length("test")).to eq 0
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when queue contains jobs" do
|
|
it "returns correct size of the queue" do
|
|
Sidekiq::Testing.disable! do
|
|
worker.perform_async("Something", [1])
|
|
worker.perform_async("Something", [2])
|
|
|
|
expect(model.sidekiq_queue_length("test")).to eq 2
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#sidekiq_queue_migrate" do
|
|
it "migrates jobs from one sidekiq queue to another" do
|
|
Sidekiq::Testing.disable! do
|
|
worker.perform_async("Something", [1])
|
|
worker.perform_async("Something", [2])
|
|
|
|
expect(model.sidekiq_queue_length("test")).to eq 2
|
|
expect(model.sidekiq_queue_length("new_test")).to eq 0
|
|
|
|
model.sidekiq_queue_migrate("test", to: "new_test")
|
|
|
|
expect(model.sidekiq_queue_length("test")).to eq 0
|
|
expect(model.sidekiq_queue_length("new_test")).to eq 2
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|