diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-09 21:09:32 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-09 21:09:32 +0300 |
commit | 1268cfeaf73a73c5593f13639530513716826e2b (patch) | |
tree | 2508ffb2fddaa666d99b4e5984d3340eb9194782 /spec/models | |
parent | be769d5fcced48143e92f83c997b7d5ba14873e1 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models')
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 329 | ||||
-rw-r--r-- | spec/models/preloaders/projects/notes_preloader_spec.rb | 61 |
2 files changed, 61 insertions, 329 deletions
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 46d050c0625..b9e331affb1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -3029,335 +3029,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end - describe '#cancel_running' do - let(:latest_status) { pipeline.statuses.pluck(:status) } - - let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } - - it 'logs the event' do - allow(Gitlab::AppJsonLogger).to receive(:info) - - pipeline.cancel_running - - expect(Gitlab::AppJsonLogger) - .to have_received(:info) - .with( - a_hash_including( - event: 'pipeline_cancel_running', - pipeline_id: pipeline.id, - auto_canceled_by_pipeline_id: nil, - cascade_to_children: true, - execute_async: true - ) - ) - end - - context 'when there is a running external job and a regular job' do - before do - create(:ci_build, :running, pipeline: pipeline) - create(:generic_commit_status, :running, pipeline: pipeline) - - pipeline.cancel_running - end - - it 'cancels both jobs' do - expect(latest_status).to contain_exactly('canceled', 'canceled') - end - end - - context 'when jobs are in different stages' do - before do - create(:ci_build, :running, stage_idx: 0, pipeline: pipeline) - create(:ci_build, :running, stage_idx: 1, pipeline: pipeline) - - pipeline.cancel_running - end - - it 'cancels both jobs' do - expect(latest_status).to contain_exactly('canceled', 'canceled') - end - end - - context 'when there are created builds present in the pipeline' do - before do - create(:ci_build, :running, stage_idx: 0, pipeline: pipeline) - create(:ci_build, :created, stage_idx: 1, pipeline: pipeline) - - pipeline.cancel_running - end - - it 'cancels created builds' do - expect(latest_status).to eq %w(canceled canceled) - end - end - - context 'with bridge jobs' do - before do - create(:ci_bridge, :created, pipeline: pipeline) - - pipeline.cancel_running - end - - it 'bridges are canceled' do - expect(pipeline.bridges.first.status).to eq 'canceled' - end - end - - context 'when pipeline is not cancelable' do - before do - create(:ci_build, :canceled, stage_idx: 0, pipeline: pipeline) - - pipeline.cancel_running - end - - it 'does not send cancel signal to cancel self' do - expect(pipeline).not_to receive(:cancel_self_only) - - pipeline.cancel_running - end - end - - context 'preloading relations' do - let(:pipeline1) { create(:ci_empty_pipeline, :created) } - let(:pipeline2) { create(:ci_empty_pipeline, :created) } - - before do - create(:ci_build, :pending, pipeline: pipeline1) - create(:generic_commit_status, :pending, pipeline: pipeline1) - - create(:ci_build, :pending, pipeline: pipeline2) - create(:ci_build, :pending, pipeline: pipeline2) - create(:generic_commit_status, :pending, pipeline: pipeline2) - create(:generic_commit_status, :pending, pipeline: pipeline2) - create(:generic_commit_status, :pending, pipeline: pipeline2) - end - - it 'preloads relations for each build to avoid N+1 queries' do - control1 = ActiveRecord::QueryRecorder.new do - pipeline1.cancel_running - end - - control2 = ActiveRecord::QueryRecorder.new do - pipeline2.cancel_running - end - - extra_update_queries = 4 # transition ... => :canceled, queue pop - extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types - - expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries) - end - end - - shared_examples 'retries' do - context 'when the first try cannot get an exclusive lock' do - let(:retries) { 1 } - - subject { pipeline.cancel_running(retries: retries) } - - before do - create(:ci_build, :running, pipeline: pipeline) - - stub_first_cancel_call_fails - end - - it 'retries again and cancels the build' do - subject - - expect(latest_status).to contain_exactly('canceled') - end - - context 'when the retries parameter is 0' do - let(:retries) { 0 } - - it 'raises error' do - expect { subject }.to raise_error(ActiveRecord::StaleObjectError) - end - end - end - - def stub_first_cancel_call_fails - call_count = 0 - - allow_next_found_instance_of(Ci::Build) do |build| - allow(build).to receive(:cancel).and_wrap_original do |original, *args| # rubocop:disable RSpec/AnyInstanceOf - call_count >= retries ? raise(ActiveRecord::StaleObjectError) : original.call(*args) - - call_count += 1 - end - end - end - end - - it_behaves_like 'retries' - - context 'when auto canceled' do - let!(:canceled_by) { create(:ci_empty_pipeline) } - - before do - create(:ci_build, :running, pipeline: pipeline) - - pipeline.cancel_running(auto_canceled_by_pipeline_id: canceled_by.id) - end - - it 'sets auto cancel' do - jobs_canceled_by = pipeline.statuses.map { |s| s.auto_canceled_by.id } - - expect(jobs_canceled_by).to contain_exactly(canceled_by.id) - expect(pipeline.auto_canceled_by.id).to eq(canceled_by.id) - end - end - - context 'when there are child pipelines', :sidekiq_inline do - let_it_be(:child_pipeline) { create(:ci_empty_pipeline, :created, child_of: pipeline) } - - before do - project.clear_memoization(:cascade_cancel_pipelines_enabled) - - pipeline.reload - end - - context 'when cascade_to_children is true' do - let(:cascade_to_children) { true } - let(:canceled_by) { nil } - let(:execute_async) { true } - - let(:params) do - { - cascade_to_children: cascade_to_children, - execute_async: execute_async - }.tap do |p| - p.merge!(auto_canceled_by_pipeline_id: canceled_by.id) if canceled_by - end - end - - subject(:cancel_running) { pipeline.cancel_running(**params) } - - context 'when cancelable child pipeline builds' do - before do - create(:ci_build, :created, pipeline: child_pipeline) - create(:ci_build, :running, pipeline: child_pipeline) - end - - it 'cancels child builds' do - cancel_running - - latest_status_for_child = child_pipeline.statuses.pluck(:status) - expect(latest_status_for_child).to eq %w(canceled canceled) - expect(latest_status).to eq %w(canceled) - end - - it 'cancels bridges' do - create(:ci_bridge, :created, pipeline: pipeline) - create(:ci_bridge, :created, pipeline: child_pipeline) - - cancel_running - - expect(pipeline.bridges.reload.first.status).to eq 'canceled' - expect(child_pipeline.bridges.reload.first.status).to eq 'canceled' - end - - context 'with nested child pipelines' do - let!(:nested_child_pipeline) { create(:ci_empty_pipeline, :created, child_of: child_pipeline) } - let!(:nested_child_pipeline_build) { create(:ci_build, :created, pipeline: nested_child_pipeline) } - - it 'cancels them' do - cancel_running - - expect(nested_child_pipeline.reload.status).to eq 'canceled' - expect(nested_child_pipeline_build.reload.status).to eq 'canceled' - end - end - - context 'when auto canceled' do - let(:canceled_by) { create(:ci_empty_pipeline) } - - it 'sets auto cancel' do - cancel_running - - pipeline.reload - - jobs_canceled_by_ids = pipeline.statuses.map(&:auto_canceled_by_id) - child_pipelines_canceled_by_ids = pipeline.child_pipelines.map(&:auto_canceled_by_id) - child_pipelines_jobs_canceled_by_ids = pipeline.child_pipelines.map(&:statuses).flatten.map(&:auto_canceled_by_id) - - expect(jobs_canceled_by_ids).to contain_exactly(canceled_by.id) - expect(pipeline.auto_canceled_by_id).to eq(canceled_by.id) - expect(child_pipelines_canceled_by_ids).to contain_exactly(canceled_by.id) - expect(child_pipelines_jobs_canceled_by_ids).to contain_exactly(canceled_by.id, canceled_by.id) - end - end - - context 'when execute_async is false' do - let(:execute_async) { false } - - it 'runs sync' do - expect(::Ci::CancelPipelineWorker).not_to receive(:perform_async) - - cancel_running - end - - it 'cancels children' do - cancel_running - - latest_status_for_child = child_pipeline.statuses.pluck(:status) - expect(latest_status_for_child).to eq %w(canceled canceled) - expect(latest_status).to eq %w(canceled) - end - - context 'with nested child pipelines' do - let!(:nested_child_pipeline) { create(:ci_empty_pipeline, :created, child_of: child_pipeline) } - let!(:nested_child_pipeline_build) { create(:ci_build, :created, pipeline: nested_child_pipeline) } - - it 'cancels them' do - cancel_running - - expect(nested_child_pipeline.reload.status).to eq 'canceled' - expect(nested_child_pipeline_build.reload.status).to eq 'canceled' - end - end - end - end - - it 'does not cancel uncancelable child pipeline builds' do - create(:ci_build, :failed, pipeline: child_pipeline) - - cancel_running - - latest_status_for_child = child_pipeline.statuses.pluck(:status) - expect(latest_status_for_child).to eq %w(failed) - expect(latest_status).to eq %w(canceled) - end - end - - context 'when cascade_to_children is false' do - let(:cascade_to_children) { false } - - subject(:cancel_running) { pipeline.cancel_running(cascade_to_children: cascade_to_children) } - - it 'does not cancel cancelable child pipeline builds' do - create(:ci_build, :created, pipeline: child_pipeline) - create(:ci_build, :running, pipeline: child_pipeline) - - cancel_running - - latest_status_for_child = child_pipeline.statuses.order_id_desc.pluck(:status) - expect(latest_status_for_child).to eq %w(running created) - expect(latest_status).to eq %w(canceled) - end - - it 'does not cancel uncancelable child pipeline builds' do - create(:ci_build, :failed, pipeline: child_pipeline) - - cancel_running - - latest_status_for_child = child_pipeline.statuses.pluck(:status) - expect(latest_status_for_child).to eq %w(failed) - expect(latest_status).to eq %w(canceled) - end - end - end - end - describe '.cancelable' do subject { described_class.cancelable } diff --git a/spec/models/preloaders/projects/notes_preloader_spec.rb b/spec/models/preloaders/projects/notes_preloader_spec.rb new file mode 100644 index 00000000000..a5ec99241ec --- /dev/null +++ b/spec/models/preloaders/projects/notes_preloader_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Preloaders::Projects::NotesPreloader, :request_store, feature_category: :team_planning do + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:issue) { create(:issue, project: project) } + + it 'preloads author access level and contributor status' do + developer1 = create(:user).tap { |u| project.add_developer(u) } + developer2 = create(:user).tap { |u| project.add_developer(u) } + contributor1 = create(:user) + contributor2 = create(:user) + contributor3 = create(:user) + + create_merge_request_for(contributor1) + create_note_for(developer1) + create_note_for(contributor1) + + notes = issue.notes.preload(:author, :project).to_a + + control = ActiveRecord::QueryRecorder.new do + preload_and_fetch_attributes(notes, developer1) + end + + create_merge_request_for(contributor2) + create_merge_request_for(contributor3) + create_note_for(developer2) + create_note_for(contributor2) + create_note_for(developer1) + create_note_for(contributor3) + issue.reload + + notes = issue.notes.preload(:author, :project).to_a + + expect do + preload_and_fetch_attributes(notes, developer1) + end.not_to exceed_query_limit(control) + end + + def create_note_for(user) + create(:note, project: project, noteable: issue, author: user) + end + + def create_merge_request_for(user) + create( + :merge_request, + :merged, + :simple, + source_project: project, + author: user, + target_branch: project.default_branch.to_s + ) + end + + def preload_and_fetch_attributes(notes, user) + described_class.new(project, user).call(notes) + + notes.each { |n| n.contributor? && n.human_max_access } + end +end |