Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/background_task_spec.rb')
-rw-r--r--spec/lib/gitlab/background_task_spec.rb209
1 files changed, 209 insertions, 0 deletions
diff --git a/spec/lib/gitlab/background_task_spec.rb b/spec/lib/gitlab/background_task_spec.rb
new file mode 100644
index 00000000000..102556b6b2f
--- /dev/null
+++ b/spec/lib/gitlab/background_task_spec.rb
@@ -0,0 +1,209 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+# We need to capture task state from a closure, which requires instance variables.
+# rubocop: disable RSpec/InstanceVariable
+RSpec.describe Gitlab::BackgroundTask do
+ let(:options) { {} }
+ let(:task) do
+ proc do
+ @task_run = true
+ @task_thread = Thread.current
+ end
+ end
+
+ subject(:background_task) { described_class.new(task, **options) }
+
+ def expect_condition
+ Timeout.timeout(3) do
+ sleep 0.1 until yield
+ end
+ end
+
+ context 'when stopped' do
+ it 'is not running' do
+ expect(background_task).not_to be_running
+ end
+
+ describe '#start' do
+ it 'runs the given task on a background thread' do
+ test_thread = Thread.current
+
+ background_task.start
+
+ expect_condition { @task_run == true }
+ expect_condition { @task_thread != test_thread }
+ expect(background_task).to be_running
+ end
+
+ it 'returns self' do
+ expect(background_task.start).to be(background_task)
+ end
+
+ context 'when installing exit handler' do
+ it 'stops a running background task' do
+ expect(background_task).to receive(:at_exit).and_yield
+
+ background_task.start
+
+ expect(background_task).not_to be_running
+ end
+ end
+
+ context 'when task responds to start' do
+ let(:task_class) do
+ Struct.new(:started, :start_retval, :run) do
+ def start
+ self.started = true
+ self.start_retval
+ end
+
+ def call
+ self.run = true
+ end
+ end
+ end
+
+ let(:task) { task_class.new }
+
+ it 'calls start' do
+ background_task.start
+
+ expect_condition { task.started == true }
+ end
+
+ context 'when start returns true' do
+ it 'runs the task' do
+ task.start_retval = true
+
+ background_task.start
+
+ expect_condition { task.run == true }
+ end
+ end
+
+ context 'when start returns false' do
+ it 'does not run the task' do
+ task.start_retval = false
+
+ background_task.start
+
+ expect_condition { task.run.nil? }
+ end
+ end
+ end
+
+ context 'when synchronous is set to true' do
+ let(:options) { { synchronous: true } }
+
+ it 'calls join on the thread' do
+ # Thread has to be run in a block, expect_next_instance_of does not support this.
+ allow_any_instance_of(Thread).to receive(:join) # rubocop:disable RSpec/AnyInstanceOf
+
+ background_task.start
+
+ expect_condition { @task_run == true }
+ expect(@task_thread).to have_received(:join)
+ end
+ end
+ end
+
+ describe '#stop' do
+ it 'is a no-op' do
+ expect { background_task.stop }.not_to change { subject.running? }
+ expect_condition { @task_run.nil? }
+ end
+ end
+ end
+
+ context 'when running' do
+ before do
+ background_task.start
+ end
+
+ describe '#start' do
+ it 'raises an error' do
+ expect { background_task.start }.to raise_error(described_class::AlreadyStartedError)
+ end
+ end
+
+ describe '#stop' do
+ it 'stops running' do
+ expect { background_task.stop }.to change { subject.running? }.from(true).to(false)
+ end
+
+ context 'when task responds to stop' do
+ let(:task_class) do
+ Struct.new(:stopped, :call) do
+ def stop
+ self.stopped = true
+ end
+ end
+ end
+
+ let(:task) { task_class.new }
+
+ it 'calls stop' do
+ background_task.stop
+
+ expect_condition { task.stopped == true }
+ end
+ end
+
+ context 'when task stop raises an error' do
+ let(:error) { RuntimeError.new('task error') }
+ let(:options) { { name: 'test_background_task' } }
+
+ let(:task_class) do
+ Struct.new(:call, :error, keyword_init: true) do
+ def stop
+ raise error
+ end
+ end
+ end
+
+ let(:task) { task_class.new(error: error) }
+
+ it 'stops gracefully' do
+ expect { background_task.stop }.not_to raise_error
+ expect(background_task).not_to be_running
+ end
+
+ it 'reports the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ error, { extra: { reported_by: 'test_background_task' } }
+ )
+
+ background_task.stop
+ end
+ end
+ end
+
+ context 'when task run raises exception' do
+ let(:error) { RuntimeError.new('task error') }
+ let(:options) { { name: 'test_background_task' } }
+ let(:task) do
+ proc do
+ @task_run = true
+ raise error
+ end
+ end
+
+ it 'stops gracefully' do
+ expect_condition { @task_run == true }
+ expect { background_task.stop }.not_to raise_error
+ expect(background_task).not_to be_running
+ end
+
+ it 'reports the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ error, { extra: { reported_by: 'test_background_task' } }
+ )
+
+ background_task.stop
+ end
+ end
+ end
+end
+# rubocop: enable RSpec/InstanceVariable