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-06-09 21:09:32 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-09 21:09:32 +0300
commit1268cfeaf73a73c5593f13639530513716826e2b (patch)
tree2508ffb2fddaa666d99b4e5984d3340eb9194782 /spec/models
parentbe769d5fcced48143e92f83c997b7d5ba14873e1 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models')
-rw-r--r--spec/models/ci/pipeline_spec.rb329
-rw-r--r--spec/models/preloaders/projects/notes_preloader_spec.rb61
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