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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-03 12:08:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-03 12:08:44 +0300
commitb4c39709e346f437a85829f985f6596cb6209d35 (patch)
tree010b87019e3f523045141f3ce683a1f5d4141499 /spec/models/ci
parentea044b0c4c74b91c5d48435254e7fa60aea064ff (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/pipeline_spec.rb128
-rw-r--r--spec/models/ci/ref_spec.rb168
2 files changed, 241 insertions, 55 deletions
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 9d2c7e876c3..c3d3ccfea44 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -157,6 +157,106 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
end
end
+ describe 'unlocking pipelines based on state transition' do
+ let(:ci_ref) { create(:ci_ref) }
+ let(:unlock_previous_pipelines_worker_spy) { class_spy(::Ci::Refs::UnlockPreviousPipelinesWorker) }
+
+ before do
+ stub_const('Ci::Refs::UnlockPreviousPipelinesWorker', unlock_previous_pipelines_worker_spy)
+ stub_feature_flags(ci_stop_unlock_pipelines: false)
+ end
+
+ shared_examples 'not unlocking pipelines' do |event:|
+ context "on #{event}" do
+ let(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :artifacts_locked) }
+
+ it 'does not unlock previous pipelines' do
+ pipeline.fire_events!(event)
+
+ expect(unlock_previous_pipelines_worker_spy).not_to have_received(:perform_async)
+ end
+ end
+ end
+
+ shared_examples 'unlocking pipelines' do |event:|
+ context "on #{event}" do
+ before do
+ pipeline.fire_events!(event)
+ end
+
+ let(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :artifacts_locked) }
+
+ it 'unlocks previous pipelines' do
+ expect(unlock_previous_pipelines_worker_spy).to have_received(:perform_async).with(ci_ref.id)
+ end
+ end
+ end
+
+ context 'when transitioning to unlockable states' do
+ before do
+ pipeline.run
+ end
+
+ it_behaves_like 'unlocking pipelines', event: :succeed
+ it_behaves_like 'unlocking pipelines', event: :drop
+ it_behaves_like 'unlocking pipelines', event: :skip
+ it_behaves_like 'unlocking pipelines', event: :cancel
+ it_behaves_like 'unlocking pipelines', event: :block
+
+ context 'and ci_stop_unlock_pipelines is enabled' do
+ before do
+ stub_feature_flags(ci_stop_unlock_pipelines: true)
+ end
+
+ it_behaves_like 'not unlocking pipelines', event: :succeed
+ it_behaves_like 'not unlocking pipelines', event: :drop
+ it_behaves_like 'not unlocking pipelines', event: :skip
+ it_behaves_like 'not unlocking pipelines', event: :cancel
+ it_behaves_like 'not unlocking pipelines', event: :block
+ end
+
+ context 'and ci_unlock_non_successful_pipelines is disabled' do
+ before do
+ stub_feature_flags(ci_unlock_non_successful_pipelines: false)
+ end
+
+ it_behaves_like 'unlocking pipelines', event: :succeed
+ it_behaves_like 'not unlocking pipelines', event: :drop
+ it_behaves_like 'not unlocking pipelines', event: :skip
+ it_behaves_like 'not unlocking pipelines', event: :cancel
+ it_behaves_like 'not unlocking pipelines', event: :block
+
+ context 'and ci_stop_unlock_pipelines is enabled' do
+ before do
+ stub_feature_flags(ci_stop_unlock_pipelines: true)
+ end
+
+ it_behaves_like 'not unlocking pipelines', event: :succeed
+ it_behaves_like 'not unlocking pipelines', event: :drop
+ it_behaves_like 'not unlocking pipelines', event: :skip
+ it_behaves_like 'not unlocking pipelines', event: :cancel
+ it_behaves_like 'not unlocking pipelines', event: :block
+ end
+ end
+ end
+
+ context 'when transitioning to a non-unlockable state' do
+ before do
+ pipeline.enqueue
+ end
+
+ it_behaves_like 'not unlocking pipelines', event: :run
+
+ context 'and ci_unlock_non_successful_pipelines is disabled' do
+ before do
+ stub_feature_flags(ci_unlock_non_successful_pipelines: false)
+ end
+
+ it_behaves_like 'not unlocking pipelines', event: :run
+ end
+ end
+ end
+
describe 'pipeline age metric' do
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) }
@@ -220,6 +320,34 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
end
end
+ describe '.with_unlockable_status' do
+ let_it_be(:project) { create(:project) }
+
+ let!(:pipeline) { create(:ci_pipeline, project: project, status: status) }
+
+ subject(:result) { described_class.with_unlockable_status }
+
+ described_class::UNLOCKABLE_STATUSES.map(&:to_s).each do |s|
+ context "when pipeline status is #{s}" do
+ let(:status) { s }
+
+ it 'includes the pipeline in the result' do
+ expect(result).to include(pipeline)
+ end
+ end
+ end
+
+ (Ci::HasStatus::AVAILABLE_STATUSES - described_class::UNLOCKABLE_STATUSES.map(&:to_s)).each do |s|
+ context "when pipeline status is #{s}" do
+ let(:status) { s }
+
+ it 'does excludes the pipeline in the result' do
+ expect(result).not_to include(pipeline)
+ end
+ end
+ end
+ end
+
describe '.processables' do
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) }
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index 75071a17fa9..2727c7701b8 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -7,61 +7,6 @@ RSpec.describe Ci::Ref, feature_category: :continuous_integration do
it { is_expected.to belong_to(:project) }
- describe 'state machine transitions' do
- context 'unlock artifacts transition' do
- let(:ci_ref) { create(:ci_ref) }
- let(:unlock_previous_pipelines_worker_spy) { class_spy(::Ci::Refs::UnlockPreviousPipelinesWorker) }
-
- before do
- stub_const('Ci::Refs::UnlockPreviousPipelinesWorker', unlock_previous_pipelines_worker_spy)
- end
-
- context 'pipeline is locked' do
- let!(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :artifacts_locked) }
-
- where(:initial_state, :action, :count) do
- :unknown | :succeed! | 1
- :unknown | :do_fail! | 0
- :success | :succeed! | 1
- :success | :do_fail! | 0
- :failed | :succeed! | 1
- :failed | :do_fail! | 0
- :fixed | :succeed! | 1
- :fixed | :do_fail! | 0
- :broken | :succeed! | 1
- :broken | :do_fail! | 0
- :still_failing | :succeed | 1
- :still_failing | :do_fail | 0
- end
-
- with_them do
- context "when transitioning states" do
- before do
- status_value = Ci::Ref.state_machines[:status].states[initial_state].value
- ci_ref.update!(status: status_value)
- end
-
- it 'calls pipeline complete unlock artifacts service' do
- ci_ref.send(action)
-
- expect(unlock_previous_pipelines_worker_spy).to have_received(:perform_async).exactly(count).times
- end
- end
- end
- end
-
- context 'pipeline is unlocked' do
- let!(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :unlocked) }
-
- it 'does not unlock pipelines' do
- ci_ref.succeed!
-
- expect(unlock_previous_pipelines_worker_spy).not_to have_received(:perform_async)
- end
- end
- end
- end
-
describe '.ensure_for' do
let_it_be(:project) { create(:project, :repository) }
@@ -241,4 +186,117 @@ RSpec.describe Ci::Ref, feature_category: :continuous_integration do
let!(:model) { create(:ci_ref, project: parent) }
end
end
+
+ describe '#last_successful_ci_source_pipeline' do
+ let_it_be(:ci_ref) { create(:ci_ref) }
+
+ let(:ci_source) { Enums::Ci::Pipeline.sources[:push] }
+ let(:dangling_source) { Enums::Ci::Pipeline.sources[:parent_pipeline] }
+
+ subject(:result) { ci_ref.last_successful_ci_source_pipeline }
+
+ context 'when there are no successful CI source pipelines' do
+ let!(:running_ci_source) { create(:ci_pipeline, :running, ci_ref: ci_ref, source: ci_source) }
+ let!(:successful_dangling_source) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: dangling_source) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when there are successful CI source pipelines' do
+ context 'and the latest pipeline is a successful CI source pipeline' do
+ let!(:failed_ci_source) { create(:ci_pipeline, :failed, ci_ref: ci_ref, source: ci_source) }
+ let!(:successful_dangling_source) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: dangling_source, child_of: failed_ci_source) }
+ let!(:successful_ci_source) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: ci_source) }
+
+ it 'returns the last successful CI source pipeline' do
+ expect(result).to eq(successful_ci_source)
+ end
+ end
+
+ context 'and there is a newer successful dangling source pipeline than the successful CI source pipelines' do
+ let!(:successful_ci_source_1) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: ci_source) }
+ let!(:successful_ci_source_2) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: ci_source) }
+ let!(:failed_ci_source) { create(:ci_pipeline, :failed, ci_ref: ci_ref, source: ci_source) }
+ let!(:successful_dangling_source) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: dangling_source, child_of: failed_ci_source) }
+
+ it 'returns the last successful CI source pipeline' do
+ expect(result).to eq(successful_ci_source_2)
+ end
+
+ context 'and the newer successful dangling source is a child of a successful CI source pipeline' do
+ let!(:parent_ci_source) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: ci_source) }
+ let!(:successful_child_source) { create(:ci_pipeline, :success, ci_ref: ci_ref, source: dangling_source, child_of: parent_ci_source) }
+
+ it 'returns the parent pipeline instead' do
+ expect(result).to eq(parent_ci_source)
+ end
+ end
+ end
+ end
+ end
+
+ describe '#last_unlockable_ci_source_pipeline' do
+ let(:ci_source) { Enums::Ci::Pipeline.sources[:push] }
+ let(:dangling_source) { Enums::Ci::Pipeline.sources[:parent_pipeline] }
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:ci_ref) { create(:ci_ref, project: project) }
+
+ subject(:result) { ci_ref.last_unlockable_ci_source_pipeline }
+
+ context 'when there are unlockable pipelines in the ref' do
+ context 'and the last CI source pipeline in the ref is unlockable' do
+ let!(:unlockable_ci_source_1) { create(:ci_pipeline, :success, project: project, ci_ref: ci_ref, source: ci_source) }
+ let!(:unlockable_ci_source_2) { create(:ci_pipeline, :blocked, project: project, ci_ref: ci_ref, source: ci_source) }
+
+ it 'returns the CI source pipeline' do
+ expect(result).to eq(unlockable_ci_source_2)
+ end
+
+ context 'and it has unlockable child pipelines' do
+ let!(:child) { create(:ci_pipeline, :success, project: project, ci_ref: ci_ref, source: dangling_source, child_of: unlockable_ci_source_2) }
+ let!(:child_2) { create(:ci_pipeline, :success, project: project, ci_ref: ci_ref, source: dangling_source, child_of: unlockable_ci_source_2) }
+
+ it 'returns the parent CI source pipeline' do
+ expect(result).to eq(unlockable_ci_source_2)
+ end
+ end
+
+ context 'and it has a non-unlockable child pipeline' do
+ let!(:child) { create(:ci_pipeline, :running, project: project, ci_ref: ci_ref, source: dangling_source, child_of: unlockable_ci_source_2) }
+
+ it 'returns the parent CI source pipeline' do
+ expect(result).to eq(unlockable_ci_source_2)
+ end
+ end
+ end
+
+ context 'and the last CI source pipeline in the ref is not unlockable' do
+ let!(:unlockable_ci_source) { create(:ci_pipeline, :skipped, project: project, ci_ref: ci_ref, source: ci_source) }
+ let!(:unlockable_dangling_source) { create(:ci_pipeline, :success, project: project, ci_ref: ci_ref, source: dangling_source, child_of: unlockable_ci_source) }
+ let!(:non_unlockable_ci_source) { create(:ci_pipeline, :running, project: project, ci_ref: ci_ref, source: ci_source) }
+ let!(:non_unlockable_ci_source_2) { create(:ci_pipeline, :running, project: project, ci_ref: ci_ref, source: ci_source) }
+
+ it 'returns the last unlockable CI source pipeline before it' do
+ expect(result).to eq(unlockable_ci_source)
+ end
+
+ context 'and it has unlockable child pipelines' do
+ let!(:child) { create(:ci_pipeline, :success, project: project, ci_ref: ci_ref, source: dangling_source, child_of: non_unlockable_ci_source) }
+ let!(:child_2) { create(:ci_pipeline, :success, project: project, ci_ref: ci_ref, source: dangling_source, child_of: non_unlockable_ci_source) }
+
+ it 'returns the last unlockable CI source pipeline before it' do
+ expect(result).to eq(unlockable_ci_source)
+ end
+ end
+ end
+ end
+
+ context 'when there are no unlockable pipelines in the ref' do
+ let!(:non_unlockable_pipeline) { create(:ci_pipeline, :running, project: project, ci_ref: ci_ref, source: ci_source) }
+ let!(:pipeline_from_another_ref) { create(:ci_pipeline, :success, source: ci_source) }
+
+ it { is_expected.to be_nil }
+ end
+ end
end