2019-07-02 11:14:31 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-24 09:08:32 +00:00
|
|
|
RSpec.describe Gitlab::BatchPopQueueing do
|
2019-07-02 11:14:31 +00:00
|
|
|
include ExclusiveLeaseHelpers
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
describe '#initialize' do
|
|
|
|
where(:namespace, :queue_id, :expect_error, :error_type) do
|
|
|
|
'feature' | '1' | false | nil
|
|
|
|
:feature | '1' | false | nil
|
|
|
|
nil | '1' | true | NoMethodError
|
|
|
|
'feature' | nil | true | NoMethodError
|
|
|
|
'' | '1' | true | ArgumentError
|
|
|
|
'feature' | '' | true | ArgumentError
|
|
|
|
'feature' | 1 | true | NoMethodError
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
it do
|
|
|
|
if expect_error
|
|
|
|
expect { described_class.new(namespace, queue_id) }.to raise_error(error_type)
|
|
|
|
else
|
|
|
|
expect { described_class.new(namespace, queue_id) }.not_to raise_error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#safe_execute', :clean_gitlab_redis_queues do
|
|
|
|
subject { queue.safe_execute(new_items, lock_timeout: lock_timeout) }
|
|
|
|
|
|
|
|
let(:queue) { described_class.new(namespace, queue_id) }
|
|
|
|
let(:namespace) { 'feature' }
|
|
|
|
let(:queue_id) { '1' }
|
|
|
|
let(:lock_timeout) { 10.minutes }
|
|
|
|
let(:new_items) { %w[A B] }
|
|
|
|
let(:lock_key) { queue.send(:lock_key) }
|
|
|
|
let(:queue_key) { queue.send(:queue_key) }
|
|
|
|
|
|
|
|
it 'enqueues new items always' do
|
|
|
|
Gitlab::Redis::Queues.with do |redis|
|
|
|
|
expect(redis).to receive(:sadd).with(queue_key, new_items)
|
|
|
|
expect(redis).to receive(:expire).with(queue_key, (lock_timeout + described_class::EXTRA_QUEUE_EXPIRE_WINDOW).to_i)
|
|
|
|
end
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'yields the new items with exclusive lease' do
|
|
|
|
uuid = 'test'
|
|
|
|
expect_to_obtain_exclusive_lease(lock_key, uuid, timeout: lock_timeout)
|
|
|
|
expect_to_cancel_exclusive_lease(lock_key, uuid)
|
|
|
|
|
|
|
|
expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
|
|
|
|
.to yield_with_args(match_array(new_items))
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the result and no items in the queue' do
|
|
|
|
expect(subject[:status]).to eq(:finished)
|
|
|
|
expect(subject[:new_items]).to be_empty
|
|
|
|
|
|
|
|
Gitlab::Redis::Queues.with do |redis|
|
|
|
|
expect(redis.llen(queue_key)).to be(0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when new items are enqueued during the process' do
|
|
|
|
it 'returns the result with newly added items' do
|
|
|
|
result = queue.safe_execute(new_items) do
|
|
|
|
queue.safe_execute(['C'])
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(result[:status]).to eq(:finished)
|
|
|
|
expect(result[:new_items]).to eq(['C'])
|
|
|
|
|
|
|
|
Gitlab::Redis::Queues.with do |redis|
|
|
|
|
expect(redis.scard(queue_key)).to be(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when interger items are enqueued' do
|
|
|
|
let(:new_items) { [1, 2, 3] }
|
|
|
|
|
|
|
|
it 'yields as String values' do
|
|
|
|
expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
|
|
|
|
.to yield_with_args(%w[1 2 3])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the queue key does not exist in Redis' do
|
|
|
|
before do
|
|
|
|
allow(queue).to receive(:enqueue) { }
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'yields empty array' do
|
|
|
|
expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
|
|
|
|
.to yield_with_args([])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the other process has already been working on the queue' do
|
|
|
|
before do
|
|
|
|
stub_exclusive_lease_taken(lock_key, timeout: lock_timeout)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not yield the block' do
|
|
|
|
expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
|
|
|
|
.not_to yield_control
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the result' do
|
|
|
|
expect(subject[:status]).to eq(:enqueued)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a duplicate item is enqueued' do
|
|
|
|
it 'returns the poped items to the queue and raise an error' do
|
|
|
|
expect { |b| queue.safe_execute(%w[1 1 2 2], &b) }
|
|
|
|
.to yield_with_args(match_array(%w[1 2]))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there are two queues' do
|
|
|
|
it 'enqueues items to each queue' do
|
|
|
|
queue_1 = described_class.new(namespace, '1')
|
|
|
|
queue_2 = described_class.new(namespace, '2')
|
|
|
|
|
|
|
|
result_2 = nil
|
|
|
|
|
|
|
|
result_1 = queue_1.safe_execute(['A']) do |_|
|
|
|
|
result_2 = queue_2.safe_execute(['B']) do |_|
|
|
|
|
queue_1.safe_execute(['C'])
|
|
|
|
queue_2.safe_execute(['D'])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(result_1[:status]).to eq(:finished)
|
|
|
|
expect(result_1[:new_items]).to eq(['C'])
|
|
|
|
expect(result_2[:status]).to eq(:finished)
|
|
|
|
expect(result_2[:new_items]).to eq(['D'])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|