summaryrefslogtreecommitdiff
path: root/spec/models/ci/pipeline_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci/pipeline_spec.rb')
-rw-r--r--spec/models/ci/pipeline_spec.rb575
1 files changed, 476 insertions, 99 deletions
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 1ca370dc950..f5e824bb066 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -222,6 +222,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '.for_branch' do
+ subject { described_class.for_branch(branch) }
+
+ let(:branch) { 'master' }
+ let!(:pipeline) { create(:ci_pipeline, ref: 'master') }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+
+ context 'with tag pipeline' do
+ let(:branch) { 'v1.0' }
+ let!(:pipeline) { create(:ci_pipeline, ref: 'v1.0', tag: true) }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '.ci_sources' do
subject { described_class.ci_sources }
@@ -242,6 +262,27 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '.ci_branch_sources' do
+ subject { described_class.ci_branch_sources }
+
+ let_it_be(:push_pipeline) { create(:ci_pipeline, source: :push) }
+ let_it_be(:web_pipeline) { create(:ci_pipeline, source: :web) }
+ let_it_be(:api_pipeline) { create(:ci_pipeline, source: :api) }
+ let_it_be(:webide_pipeline) { create(:ci_pipeline, source: :webide) }
+ let_it_be(:child_pipeline) { create(:ci_pipeline, source: :parent_pipeline) }
+ let_it_be(:merge_request_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline) }
+
+ it 'contains pipelines having CI only sources' do
+ expect(subject).to contain_exactly(push_pipeline, web_pipeline, api_pipeline)
+ end
+
+ it 'filters on expected sources' do
+ expect(::Enums::Ci::Pipeline.ci_branch_sources.keys).to contain_exactly(
+ *%i[unknown push web trigger schedule api external pipeline chat
+ external_pull_request_event])
+ end
+ end
+
describe '.outside_pipeline_family' do
subject(:outside_pipeline_family) { described_class.outside_pipeline_family(upstream_pipeline) }
@@ -269,7 +310,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let!(:older_other_pipeline) { create(:ci_pipeline, project: project) }
let!(:upstream_pipeline) { create(:ci_pipeline, project: project) }
- let!(:child_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:child_pipeline) { create(:ci_pipeline, child_of: upstream_pipeline) }
let!(:other_pipeline) { create(:ci_pipeline, project: project) }
@@ -498,6 +539,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ context 'when pipeline has a codequality report' do
+ subject { described_class.with_reports(Ci::JobArtifact.codequality_reports) }
+
+ let(:pipeline_with_report) { create(:ci_pipeline, :with_codequality_reports) }
+
+ it 'selects the pipeline' do
+ is_expected.to eq([pipeline_with_report])
+ end
+ end
+
context 'when pipeline has a terraform report' do
it 'selects the pipeline' do
pipeline_with_report = create(:ci_pipeline, :with_terraform_reports)
@@ -744,11 +795,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
]
end
- context 'when pipeline is merge request' do
- let(:pipeline) do
- create(:ci_pipeline, merge_request: merge_request)
- end
-
+ context 'when merge request is present' do
let(:merge_request) do
create(:merge_request,
source_project: project,
@@ -764,64 +811,142 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:milestone) { create(:milestone, project: project) }
let(:labels) { create_list(:label, 2) }
- it 'exposes merge request pipeline variables' do
- expect(subject.to_hash)
- .to include(
- 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
- 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
- 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
- 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
- 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
- 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
- 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
- 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => pipeline.target_sha.to_s,
- 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
- 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
- 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
- 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
- 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s,
- 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
- 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
- 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
- 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
- 'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s)
- end
-
- context 'when source project does not exist' do
+ context 'when pipeline for merge request is created' do
+ let(:pipeline) do
+ create(:ci_pipeline, :detached_merge_request_pipeline,
+ ci_ref_presence: false,
+ user: user,
+ merge_request: merge_request)
+ end
+
before do
- merge_request.update_column(:source_project_id, nil)
+ project.add_developer(user)
end
- it 'does not expose source project related variables' do
- expect(subject.to_hash.keys).not_to include(
- %w[CI_MERGE_REQUEST_SOURCE_PROJECT_ID
- CI_MERGE_REQUEST_SOURCE_PROJECT_PATH
- CI_MERGE_REQUEST_SOURCE_PROJECT_URL
- CI_MERGE_REQUEST_SOURCE_BRANCH_NAME])
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => '',
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => '',
+ 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => 'detached',
+ 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
+ end
+
+ it 'exposes diff variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s,
+ 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha)
+ end
+
+ context 'without assignee' do
+ let(:assignees) { [] }
+
+ it 'does not expose assignee variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES')
+ end
end
- end
- context 'without assignee' do
- let(:assignees) { [] }
+ context 'without milestone' do
+ let(:milestone) { nil }
- it 'does not expose assignee variable' do
- expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES')
+ it 'does not expose milestone variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE')
+ end
+ end
+
+ context 'without labels' do
+ let(:labels) { [] }
+
+ it 'does not expose labels variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
+ end
end
end
- context 'without milestone' do
- let(:milestone) { nil }
+ context 'when pipeline on branch is created' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, user: user, ref: 'feature')
+ end
+
+ context 'when a merge request is created' do
+ before do
+ merge_request
+ end
- it 'does not expose milestone variable' do
- expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE')
+ context 'when user has access to project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'merge request references are returned matching the pipeline' do
+ expect(subject.to_hash).to include(
+ 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
+ end
+ end
+
+ context 'when user does not have access to project' do
+ it 'CI_OPEN_MERGE_REQUESTS is not returned' do
+ expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
+ end
+ end
+ end
+
+ context 'when no a merge request is created' do
+ it 'CI_OPEN_MERGE_REQUESTS is not returned' do
+ expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
+ end
end
end
- context 'without labels' do
- let(:labels) { [] }
+ context 'with merged results' do
+ let(:pipeline) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request)
+ end
+
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => merge_request.target_branch_sha,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => merge_request.source_branch_sha,
+ 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => 'merged_result')
+ end
- it 'does not expose labels variable' do
- expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
+ it 'exposes diff variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s,
+ 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha)
end
end
end
@@ -1126,6 +1251,40 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe 'synching status to Jira' do
+ let(:worker) { ::JiraConnect::SyncBuildsWorker }
+
+ %i[prepare! run! skip! drop! succeed! cancel! block! delay!].each do |event|
+ context "when we call pipeline.#{event}" do
+ it 'triggers a Jira synch worker' do
+ expect(worker).to receive(:perform_async).with(pipeline.id, Integer)
+
+ pipeline.send(event)
+ end
+
+ context 'the feature is disabled' do
+ it 'does not trigger a worker' do
+ stub_feature_flags(jira_sync_builds: false)
+
+ expect(worker).not_to receive(:perform_async)
+
+ pipeline.send(event)
+ end
+ end
+
+ context 'the feature is enabled for this project' do
+ it 'does trigger a worker' do
+ stub_feature_flags(jira_sync_builds: pipeline.project)
+
+ expect(worker).to receive(:perform_async)
+
+ pipeline.send(event)
+ end
+ end
+ end
+ end
+ end
+
describe '#duration', :sidekiq_inline do
context 'when multiple builds are finished' do
before do
@@ -2539,6 +2698,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it 'receives a pending event once' do
expect(WebMock).to have_requested_pipeline_hook('pending').once
end
+
+ it 'builds hook data once' do
+ create(:pipelines_email_service, project: project)
+
+ expect(Gitlab::DataBuilder::Pipeline).to receive(:build).once.and_call_original
+
+ pipeline.execute_hooks
+ end
end
context 'when build is run' do
@@ -2600,6 +2767,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it 'did not execute pipeline_hook after touched' do
expect(WebMock).not_to have_requested(:post, hook.url)
end
+
+ it 'does not build hook data' do
+ expect(Gitlab::DataBuilder::Pipeline).not_to receive(:build)
+
+ pipeline.execute_hooks
+ end
end
def create_build(name, stage_idx)
@@ -2734,6 +2907,93 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#related_merge_requests' do
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
+ let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'stable') }
+ let(:branch_pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+ let(:merge_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, merge_request: merge_request) }
+
+ context 'for a branch pipeline' do
+ subject { branch_pipeline.related_merge_requests }
+
+ it 'when no merge request is created' do
+ is_expected.to be_empty
+ end
+
+ it 'when another merge requests are created' do
+ merge_request
+ other_merge_request
+
+ is_expected.to contain_exactly(merge_request, other_merge_request)
+ end
+ end
+
+ context 'for a merge pipeline' do
+ subject { merge_pipeline.related_merge_requests }
+
+ it 'when only merge pipeline is created' do
+ merge_pipeline
+
+ is_expected.to contain_exactly(merge_request)
+ end
+
+ it 'when a merge request is created' do
+ merge_pipeline
+ other_merge_request
+
+ is_expected.to contain_exactly(merge_request, other_merge_request)
+ end
+ end
+ end
+
+ describe '#open_merge_requests_refs' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let!(:pipeline) { create(:ci_pipeline, user: user, project: project, ref: 'feature') }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
+
+ subject { pipeline.open_merge_requests_refs }
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns open merge requests' do
+ is_expected.to eq([merge_request.to_reference(full: true)])
+ end
+
+ it 'does not return closed merge requests' do
+ merge_request.close!
+
+ is_expected.to be_empty
+ end
+
+ context 'limits amount of returned merge requests' do
+ let!(:other_merge_requests) do
+ Array.new(4) do |idx|
+ create(:merge_request, source_project: project, source_branch: 'feature', target_branch: "master-#{idx}")
+ end
+ end
+
+ let(:other_merge_requests_refs) do
+ other_merge_requests.map { |mr| mr.to_reference(full: true) }
+ end
+
+ it 'returns only last 4 in a reverse order' do
+ is_expected.to eq(other_merge_requests_refs.reverse)
+ end
+ end
+ end
+
+ context 'when user does not have permissions' do
+ it 'does not return any merge requests' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '#same_family_pipeline_ids' do
subject { pipeline.same_family_pipeline_ids.map(&:id) }
@@ -2744,13 +3004,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is child' do
- let(:parent) { create(:ci_pipeline, project: pipeline.project) }
- let(:sibling) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create_source_pipeline(parent, pipeline)
- create_source_pipeline(parent, sibling)
- end
+ let(:parent) { create(:ci_pipeline, project: project) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent) }
+ let!(:sibling) { create(:ci_pipeline, child_of: parent) }
it 'returns parent sibling and self ids' do
expect(subject).to contain_exactly(parent.id, pipeline.id, sibling.id)
@@ -2758,11 +3014,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is parent' do
- let(:child) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create_source_pipeline(pipeline, child)
- end
+ let!(:child) { create(:ci_pipeline, child_of: pipeline) }
it 'returns self and child ids' do
expect(subject).to contain_exactly(pipeline.id, child.id)
@@ -2770,17 +3022,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is a child of a child pipeline' do
- let(:ancestor) { create(:ci_pipeline, project: pipeline.project) }
- let(:parent) { create(:ci_pipeline, project: pipeline.project) }
- let(:cousin_parent) { create(:ci_pipeline, project: pipeline.project) }
- let(:cousin) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create_source_pipeline(ancestor, parent)
- create_source_pipeline(ancestor, cousin_parent)
- create_source_pipeline(parent, pipeline)
- create_source_pipeline(cousin_parent, cousin)
- end
+ let(:ancestor) { create(:ci_pipeline, project: project) }
+ let!(:parent) { create(:ci_pipeline, child_of: ancestor) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent) }
+ let!(:cousin_parent) { create(:ci_pipeline, child_of: ancestor) }
+ let!(:cousin) { create(:ci_pipeline, child_of: cousin_parent) }
it 'returns all family ids' do
expect(subject).to contain_exactly(
@@ -2790,11 +3036,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is a triggered pipeline' do
- let(:upstream) { create(:ci_pipeline, project: create(:project)) }
-
- before do
- create_source_pipeline(upstream, pipeline)
- end
+ let!(:upstream) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline)}
it 'returns self id' do
expect(subject).to contain_exactly(pipeline.id)
@@ -2802,6 +3044,46 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#root_ancestor' do
+ subject { pipeline.root_ancestor }
+
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when pipeline is child of child pipeline' do
+ let!(:root_ancestor) { create(:ci_pipeline, project: project) }
+ let!(:parent_pipeline) { create(:ci_pipeline, child_of: root_ancestor) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+
+ it 'returns the root ancestor' do
+ expect(subject).to eq(root_ancestor)
+ end
+ end
+
+ context 'when pipeline is root ancestor' do
+ let!(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
+
+ it 'returns itself' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+
+ context 'when pipeline is standalone' do
+ it 'returns itself' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+
+ context 'when pipeline is multi-project downstream pipeline' do
+ let!(:upstream_pipeline) do
+ create(:ci_pipeline, project: create(:project), upstream_of: pipeline)
+ end
+
+ it 'ignores cross project ancestors' do
+ expect(subject).to eq(pipeline)
+ end
+ end
+ end
+
describe '#stuck?' do
before do
create(:ci_build, :pending, pipeline: pipeline)
@@ -2838,7 +3120,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
stub_feature_flags(ci_store_pipeline_messages: false)
end
- it ' does not add pipeline error message' do
+ it 'does not add pipeline error message' do
pipeline.add_error_message('The error message')
expect(pipeline.messages).to be_empty
@@ -3343,6 +3625,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
])
end
+ it 'does not execute N+1 queries' do
+ single_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
+ single_rspec = create(:ci_build, :success, name: 'rspec', pipeline: single_build_pipeline, project: project)
+ create(:ci_job_artifact, :cobertura, job: single_rspec, project: project)
+
+ control = ActiveRecord::QueryRecorder.new { single_build_pipeline.coverage_reports }
+
+ expect { subject }.not_to exceed_query_limit(control)
+ end
+
context 'when builds are retried' do
let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) }
let!(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) }
@@ -3360,6 +3652,39 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#codequality_reports' do
+ subject(:codequality_reports) { pipeline.codequality_reports }
+
+ context 'when pipeline has multiple builds with codequality reports' do
+ let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) }
+ let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline, project: project) }
+
+ before do
+ create(:ci_job_artifact, :codequality, job: build_rspec, project: project)
+ create(:ci_job_artifact, :codequality_without_errors, job: build_golang, project: project)
+ end
+
+ it 'returns codequality report with collected data' do
+ expect(codequality_reports.degradations_count).to eq(3)
+ end
+
+ context 'when builds are retried' do
+ let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) }
+ let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) }
+
+ it 'returns a codequality reports without degradations' do
+ expect(codequality_reports.degradations).to be_empty
+ end
+ end
+ end
+
+ context 'when pipeline does not have any builds with codequality reports' do
+ it 'returns codequality reports without degradations' do
+ expect(codequality_reports.degradations).to be_empty
+ end
+ end
+ end
+
describe '#total_size' do
let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
@@ -3509,18 +3834,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#parent_pipeline' do
let_it_be(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
-
context 'when pipeline is triggered by a pipeline from the same project' do
- let(:upstream_pipeline) { create(:ci_pipeline, project: pipeline.project) }
-
- before do
- create(:ci_sources_pipeline,
- source_pipeline: upstream_pipeline,
- source_project: project,
- pipeline: pipeline,
- project: project)
- end
+ let_it_be(:upstream_pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, child_of: upstream_pipeline) }
it 'returns the parent pipeline' do
expect(pipeline.parent_pipeline).to eq(upstream_pipeline)
@@ -3532,15 +3848,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is triggered by a pipeline from another project' do
- let(:upstream_pipeline) { create(:ci_pipeline) }
-
- before do
- create(:ci_sources_pipeline,
- source_pipeline: upstream_pipeline,
- source_project: upstream_pipeline.project,
- pipeline: pipeline,
- project: project)
- end
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:upstream_pipeline) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline) }
it 'returns nil' do
expect(pipeline.parent_pipeline).to be_nil
@@ -3552,6 +3861,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is not triggered by a pipeline' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
it 'returns nil' do
expect(pipeline.parent_pipeline).to be_nil
end
@@ -3851,4 +4162,70 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
bridge
end
end
+
+ describe 'test failure history processing' do
+ it 'performs the service asynchronously when the pipeline is completed' do
+ service = double
+
+ expect(Ci::TestFailureHistoryService).to receive(:new).with(pipeline).and_return(service)
+ expect(service).to receive_message_chain(:async, :perform_if_needed)
+
+ pipeline.succeed!
+ end
+ end
+
+ describe '#latest_test_report_builds' do
+ it 'returns pipeline builds with test report artifacts' do
+ test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :artifacts, pipeline: pipeline, project: project)
+
+ expect(pipeline.latest_test_report_builds).to contain_exactly(test_build)
+ end
+
+ it 'preloads project on each build to avoid N+1 queries' do
+ create(:ci_build, :test_reports, pipeline: pipeline, project: project)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ pipeline.latest_test_report_builds.map(&:project).map(&:full_path)
+ end
+
+ multi_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
+ create(:ci_build, :test_reports, pipeline: multi_build_pipeline, project: project)
+ create(:ci_build, :test_reports, pipeline: multi_build_pipeline, project: project)
+
+ expect { multi_build_pipeline.latest_test_report_builds.map(&:project).map(&:full_path) }
+ .not_to exceed_query_limit(control_count)
+ end
+ end
+
+ describe '#builds_with_failed_tests' do
+ it 'returns pipeline builds with test report artifacts' do
+ failed_build = create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :success, :test_reports, pipeline: pipeline, project: project)
+
+ expect(pipeline.builds_with_failed_tests).to contain_exactly(failed_build)
+ end
+
+ it 'supports limiting the number of builds to fetch' do
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+
+ expect(pipeline.builds_with_failed_tests(limit: 1).count).to eq(1)
+ end
+
+ it 'preloads project on each build to avoid N+1 queries' do
+ create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ pipeline.builds_with_failed_tests.map(&:project).map(&:full_path)
+ end
+
+ multi_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
+ create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline, project: project)
+ create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline, project: project)
+
+ expect { multi_build_pipeline.builds_with_failed_tests.map(&:project).map(&:full_path) }
+ .not_to exceed_query_limit(control_count)
+ end
+ end
end