diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-03 12:08:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-03 12:08:44 +0300 |
commit | b4c39709e346f437a85829f985f6596cb6209d35 (patch) | |
tree | 010b87019e3f523045141f3ce683a1f5d4141499 /spec/models/ci | |
parent | ea044b0c4c74b91c5d48435254e7fa60aea064ff (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 128 | ||||
-rw-r--r-- | spec/models/ci/ref_spec.rb | 168 |
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 |