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>2022-09-20 02:18:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-09-20 02:18:09 +0300
commit6ed4ec3e0b1340f96b7c043ef51d1b33bbe85fde (patch)
treedc4d20fe6064752c0bd323187252c77e0a89144b /spec/models/ci
parent9868dae7fc0655bd7ce4a6887d4e6d487690eeed (diff)
Add latest changes from gitlab-org/gitlab@15-4-stable-eev15.4.0-rc42
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/build_dependencies_spec.rb23
-rw-r--r--spec/models/ci/build_spec.rb441
-rw-r--r--spec/models/ci/freeze_period_status_spec.rb9
-rw-r--r--spec/models/ci/job_artifact_spec.rb132
-rw-r--r--spec/models/ci/namespace_mirror_spec.rb38
-rw-r--r--spec/models/ci/pipeline_artifact_spec.rb13
-rw-r--r--spec/models/ci/pipeline_spec.rb352
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb21
-rw-r--r--spec/models/ci/processable_spec.rb20
-rw-r--r--spec/models/ci/runner_spec.rb188
-rw-r--r--spec/models/ci/stage_spec.rb29
-rw-r--r--spec/models/ci/trigger_spec.rb17
12 files changed, 648 insertions, 635 deletions
diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb
index 737348765d9..1dd0386060d 100644
--- a/spec/models/ci/build_dependencies_spec.rb
+++ b/spec/models/ci/build_dependencies_spec.rb
@@ -13,10 +13,15 @@ RSpec.describe Ci::BuildDependencies do
status: 'success')
end
- let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
- let!(:rspec_test) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
- let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
- let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
+ let(:build_stage) { create(:ci_stage, name: 'build', pipeline: pipeline) }
+ let(:test_stage) { create(:ci_stage, name: 'test', pipeline: pipeline) }
+ let(:deploy_stage) { create(:ci_stage, name: 'deploy', pipeline: pipeline) }
+ let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, ci_stage: build_stage) }
+ let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, ci_stage: test_stage) }
+ let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, ci_stage: deploy_stage) }
+ let!(:rspec_test) do
+ create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage_idx: 1, ci_stage: test_stage)
+ end
context 'for local dependencies' do
subject { described_class.new(job).all }
@@ -63,7 +68,7 @@ RSpec.describe Ci::BuildDependencies do
name: 'dag_job',
scheduling_type: :dag,
stage_idx: 2,
- stage: 'deploy'
+ ci_stage: deploy_stage
)
end
@@ -87,7 +92,7 @@ RSpec.describe Ci::BuildDependencies do
name: 'final',
scheduling_type: scheduling_type,
stage_idx: 3,
- stage: 'deploy',
+ ci_stage: deploy_stage,
options: { dependencies: dependencies }
)
end
@@ -218,12 +223,12 @@ RSpec.describe Ci::BuildDependencies do
cross_pipeline_limit.times do |index|
create(:ci_build, :success,
pipeline: parent_pipeline, name: "dependency-#{index}",
- stage_idx: 1, stage: 'build', user: user
+ stage_idx: 1, ci_stage: build_stage, user: user
)
create(:ci_build, :success,
pipeline: sibling_pipeline, name: "dependency-#{index}",
- stage_idx: 1, stage: 'build', user: user
+ stage_idx: 1, ci_stage: build_stage, user: user
)
end
end
@@ -355,7 +360,7 @@ RSpec.describe Ci::BuildDependencies do
describe '#all' do
let!(:job) do
- create(:ci_build, pipeline: pipeline, name: 'deploy', stage_idx: 3, stage: 'deploy')
+ create(:ci_build, pipeline: pipeline, name: 'deploy', stage_idx: 3, ci_stage: deploy_stage)
end
let(:dependencies) { described_class.new(job) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index b865688d370..7ee381b29ea 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -67,31 +67,6 @@ RSpec.describe Ci::Build do
create(:ci_build)
end
-
- context 'when the execute_build_hooks_inline flag is disabled' do
- before do
- stub_feature_flags(execute_build_hooks_inline: false)
- end
-
- it 'uses the old job hooks worker' do
- expect(::BuildHooksWorker).to receive(:perform_async).with(Ci::Build)
-
- create(:ci_build)
- end
- end
-
- context 'when the execute_build_hooks_inline flag is enabled for a project' do
- before do
- stub_feature_flags(execute_build_hooks_inline: project)
- end
-
- it 'executes hooks inline' do
- expect(::BuildHooksWorker).not_to receive(:perform_async)
- expect_next(described_class).to receive(:execute_hooks)
-
- create(:ci_build, project: project)
- end
- end
end
end
@@ -594,6 +569,51 @@ RSpec.describe Ci::Build do
end
end
+ describe '#prevent_rollback_deployment?' do
+ subject { build.prevent_rollback_deployment? }
+
+ let(:build) { create(:ci_build, :created, :with_deployment, project: project, environment: 'production') }
+
+ context 'when build has no environment' do
+ let(:build) { create(:ci_build, :created, project: project, environment: nil) }
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'when project has forward deployment disabled' do
+ before do
+ project.ci_cd_settings.update!(forward_deployment_enabled: false)
+ end
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'when deployment cannot rollback' do
+ before do
+ expect(build.deployment).to receive(:older_than_last_successful_deployment?).and_return(false)
+ end
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'when prevent_outdated_deployment_jobs FF is disabled' do
+ before do
+ stub_feature_flags(prevent_outdated_deployment_jobs: false)
+ expect(build.deployment).not_to receive(:rollback?)
+ end
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'when build can prevent rollback deployment' do
+ before do
+ expect(build.deployment).to receive(:older_than_last_successful_deployment?).and_return(true)
+ end
+
+ it { expect(subject).to be_truthy }
+ end
+ end
+
describe '#schedulable?' do
subject { build.schedulable? }
@@ -1250,70 +1270,6 @@ RSpec.describe Ci::Build do
end
end
- describe '#has_old_trace?' do
- subject { build.has_old_trace? }
-
- context 'when old trace exists' do
- before do
- build.update_column(:trace, 'old trace')
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when old trace does not exist' do
- it { is_expected.to be_falsy }
- end
- end
-
- describe '#trace=' do
- it "expect to fail trace=" do
- expect { build.trace = "new" }.to raise_error(NotImplementedError)
- end
- end
-
- describe '#old_trace' do
- subject { build.old_trace }
-
- before do
- build.update_column(:trace, 'old trace')
- end
-
- it "expect to receive data from database" do
- is_expected.to eq('old trace')
- end
- end
-
- describe '#erase_old_trace!' do
- subject { build.erase_old_trace! }
-
- context 'when old trace exists' do
- before do
- build.update_column(:trace, 'old trace')
- end
-
- it "erases old trace" do
- subject
-
- expect(build.old_trace).to be_nil
- end
-
- it "executes UPDATE query" do
- recorded = ActiveRecord::QueryRecorder.new { subject }
-
- expect(recorded.log.count { |l| l.match?(/UPDATE.*ci_builds/) }).to eq(1)
- end
- end
-
- context 'when old trace does not exist' do
- it 'does not execute UPDATE query' do
- recorded = ActiveRecord::QueryRecorder.new { subject }
-
- expect(recorded.log.count { |l| l.match?(/UPDATE.*ci_builds/) }).to eq(0)
- end
- end
- end
-
describe '#hide_secrets' do
let(:metrics) { spy('metrics') }
let(:subject) { build.hide_secrets(data) }
@@ -1370,13 +1326,12 @@ RSpec.describe Ci::Build do
subject { build.send(event) }
- where(:ff_enabled, :state, :report_count, :trait) do
- true | :success! | 1 | :sast
- true | :cancel! | 1 | :sast
- true | :drop! | 2 | :multiple_report_artifacts
- true | :success! | 0 | :allowed_to_fail
- true | :skip! | 0 | :pending
- false | :success! | 0 | :sast
+ where(:state, :report_count, :trait) do
+ :success! | 1 | :sast
+ :cancel! | 1 | :sast
+ :drop! | 2 | :multiple_report_artifacts
+ :success! | 0 | :allowed_to_fail
+ :skip! | 0 | :pending
end
with_them do
@@ -1386,7 +1341,6 @@ RSpec.describe Ci::Build do
context "when transitioning to #{params[:state]}" do
before do
allow(Gitlab).to receive(:com?).and_return(true)
- stub_feature_flags(report_artifact_build_completed_metrics_on_build_completion: ff_enabled)
end
it 'increments build_completed_report_type metric' do
@@ -1645,32 +1599,6 @@ RSpec.describe Ci::Build do
end
end
- describe '#count_user_verification?' do
- subject { build.count_user_verification? }
-
- context 'when build is the verify action for the environment' do
- let(:build) do
- create(:ci_build,
- ref: 'master',
- environment: 'staging',
- options: { environment: { action: 'verify' } })
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when build is not the verify action for the environment' do
- let(:build) do
- create(:ci_build,
- ref: 'master',
- environment: 'staging',
- options: { environment: { action: 'start' } })
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
describe '#expanded_environment_name' do
subject { build.expanded_environment_name }
@@ -1873,12 +1801,6 @@ RSpec.describe Ci::Build do
context 'build is not erasable' do
let!(:build) { create(:ci_build) }
- describe '#erase' do
- subject { build.erase }
-
- it { is_expected.to be false }
- end
-
describe '#erasable?' do
subject { build.erasable? }
@@ -1887,71 +1809,9 @@ RSpec.describe Ci::Build do
end
context 'build is erasable' do
- context 'logging erase' do
- let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) }
-
- it 'logs erased artifacts' do
- expect(Gitlab::Ci::Artifacts::Logger)
- .to receive(:log_deleted)
- .with(
- match_array(build.job_artifacts.to_a),
- 'Ci::Build#erase'
- )
-
- build.erase
- end
- end
-
- context 'when project is undergoing stats refresh' do
- let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) }
-
- describe '#erase' do
- before do
- allow(build.project).to receive(:refreshing_build_artifacts_size?).and_return(true)
- end
-
- it 'logs and continues with deleting the artifacts' do
- expect(Gitlab::ProjectStatsRefreshConflictsLogger).to receive(:warn_artifact_deletion_during_stats_refresh).with(
- method: 'Ci::Build#erase',
- project_id: build.project.id
- )
-
- build.erase
-
- expect(build.job_artifacts.count).to eq(0)
- end
- end
- end
-
context 'new artifacts' do
let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) }
- describe '#erase' do
- before do
- build.erase(erased_by: erased_by)
- end
-
- context 'erased by user' do
- let!(:erased_by) { create(:user, username: 'eraser') }
-
- include_examples 'erasable'
-
- it 'records user who erased a build' do
- expect(build.erased_by).to eq erased_by
- end
- end
-
- context 'erased by system' do
- let(:erased_by) { nil }
-
- include_examples 'erasable'
-
- it 'does not set user who erased a build' do
- expect(build.erased_by).to be_nil
- end
- end
- end
-
describe '#erasable?' do
subject { build.erasable? }
@@ -1969,76 +1829,12 @@ RSpec.describe Ci::Build do
context 'job has been erased' do
before do
- build.erase
+ build.update!(erased_at: 1.minute.ago)
end
it { is_expected.to be_truthy }
end
end
-
- context 'metadata and build trace are not available' do
- let!(:build) { create(:ci_build, :success, :artifacts) }
-
- before do
- build.erase_erasable_artifacts!
- end
-
- describe '#erase' do
- it 'does not raise error' do
- expect { build.erase }.not_to raise_error
- end
- end
- end
- end
- end
- end
-
- describe '#erase_erasable_artifacts!' do
- let!(:build) { create(:ci_build, :success) }
-
- subject { build.erase_erasable_artifacts! }
-
- before do
- Ci::JobArtifact.file_types.keys.each do |file_type|
- create(:ci_job_artifact, job: build, file_type: file_type, file_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[file_type.to_sym])
- end
- end
-
- it "erases erasable artifacts and logs them" do
- expect(Gitlab::Ci::Artifacts::Logger)
- .to receive(:log_deleted)
- .with(
- match_array(build.job_artifacts.erasable.to_a),
- 'Ci::Build#erase_erasable_artifacts!'
- )
-
- subject
-
- expect(build.job_artifacts.erasable).to be_empty
- end
-
- it "keeps non erasable artifacts" do
- subject
-
- Ci::JobArtifact::NON_ERASABLE_FILE_TYPES.each do |file_type|
- expect(build.send("job_artifacts_#{file_type}")).not_to be_nil
- end
- end
-
- context 'when the project is undergoing stats refresh' do
- before do
- allow(build.project).to receive(:refreshing_build_artifacts_size?).and_return(true)
- end
-
- it 'logs and continues with deleting the artifacts' do
- expect(Gitlab::ProjectStatsRefreshConflictsLogger).to receive(:warn_artifact_deletion_during_stats_refresh).with(
- method: 'Ci::Build#erase_erasable_artifacts!',
- project_id: build.project.id
- )
-
- subject
-
- expect(build.job_artifacts.erasable).to be_empty
end
end
end
@@ -2689,17 +2485,17 @@ RSpec.describe Ci::Build do
describe '#ref_slug' do
{
- 'master' => 'master',
- '1-foo' => '1-foo',
- 'fix/1-foo' => 'fix-1-foo',
- 'fix-1-foo' => 'fix-1-foo',
- 'a' * 63 => 'a' * 63,
- 'a' * 64 => 'a' * 63,
- 'FOO' => 'foo',
- '-' + 'a' * 61 + '-' => 'a' * 61,
- '-' + 'a' * 62 + '-' => 'a' * 62,
- '-' + 'a' * 63 + '-' => 'a' * 62,
- 'a' * 62 + ' ' => 'a' * 62
+ 'master' => 'master',
+ '1-foo' => '1-foo',
+ 'fix/1-foo' => 'fix-1-foo',
+ 'fix-1-foo' => 'fix-1-foo',
+ 'a' * 63 => 'a' * 63,
+ 'a' * 64 => 'a' * 63,
+ 'FOO' => 'foo',
+ '-' + 'a' * 61 + '-' => 'a' * 61,
+ '-' + 'a' * 62 + '-' => 'a' * 62,
+ '-' + 'a' * 63 + '-' => 'a' * 62,
+ 'a' * 62 + ' ' => 'a' * 62
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
@@ -3634,17 +3430,6 @@ RSpec.describe Ci::Build do
it 'includes deploy token variables' do
is_expected.to include(*deploy_token_variables)
end
-
- context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do
- before do
- stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false)
- end
-
- it 'does not include deploy token variables' do
- expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER' }).to be_nil
- expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD' }).to be_nil
- end
- end
end
end
end
@@ -3921,18 +3706,6 @@ RSpec.describe Ci::Build do
build.enqueue
end
- context 'when the execute_build_hooks_inline flag is disabled' do
- before do
- stub_feature_flags(execute_build_hooks_inline: false)
- end
-
- it 'queues BuildHooksWorker' do
- expect(BuildHooksWorker).to receive(:perform_async).with(build)
-
- build.enqueue
- end
- end
-
it 'executes hooks' do
expect(build).to receive(:execute_hooks)
@@ -4048,10 +3821,6 @@ RSpec.describe Ci::Build do
context 'when artifacts of depended job has been erased' do
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
- before do
- pre_stage_job.erase
- end
-
it { expect(job).not_to have_valid_build_dependencies }
end
end
@@ -4072,10 +3841,6 @@ RSpec.describe Ci::Build do
context 'when artifacts of depended job has been erased' do
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
- before do
- pre_stage_job.erase
- end
-
it { expect(job).to have_valid_build_dependencies }
end
end
@@ -4405,9 +4170,7 @@ RSpec.describe Ci::Build do
end
describe '#collect_test_reports!' do
- subject { build.collect_test_reports!(test_reports) }
-
- let(:test_reports) { Gitlab::Ci::Reports::TestReport.new }
+ subject(:test_reports) { build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new) }
it { expect(test_reports.get_suite(build.name).total_count).to eq(0) }
@@ -4455,56 +4218,6 @@ RSpec.describe Ci::Build do
end
end
end
-
- context 'when build is part of parallel build' do
- let(:build_1) { create(:ci_build, name: 'build 1/2') }
- let(:test_report) { Gitlab::Ci::Reports::TestReport.new }
-
- before do
- build_1.collect_test_reports!(test_report)
- end
-
- it 'uses the group name for test suite name' do
- expect(test_report.test_suites.keys).to contain_exactly('build')
- end
-
- context 'when there are more than one parallel builds' do
- let(:build_2) { create(:ci_build, name: 'build 2/2') }
-
- before do
- build_2.collect_test_reports!(test_report)
- end
-
- it 'merges the test suite from parallel builds' do
- expect(test_report.test_suites.keys).to contain_exactly('build')
- end
- end
- end
-
- context 'when build is part of matrix build' do
- let(:test_report) { Gitlab::Ci::Reports::TestReport.new }
- let(:matrix_build_1) { create(:ci_build, :matrix) }
-
- before do
- matrix_build_1.collect_test_reports!(test_report)
- end
-
- it 'uses the job name for the test suite' do
- expect(test_report.test_suites.keys).to contain_exactly(matrix_build_1.name)
- end
-
- context 'when there are more than one matrix builds' do
- let(:matrix_build_2) { create(:ci_build, :matrix) }
-
- before do
- matrix_build_2.collect_test_reports!(test_report)
- end
-
- it 'keeps separate test suites' do
- expect(test_report.test_suites.keys).to match_array([matrix_build_1.name, matrix_build_2.name])
- end
- end
- end
end
describe '#collect_accessibility_reports!' do
@@ -5620,7 +5333,7 @@ RSpec.describe Ci::Build do
end
end
- describe '#runner_features' do
+ describe '#runtime_runner_features' do
subject do
build.save!
build.cancel_gracefully?
@@ -5701,4 +5414,28 @@ RSpec.describe Ci::Build do
end
end
end
+
+ describe '#test_suite_name' do
+ let(:build) { create(:ci_build, name: 'test') }
+
+ it 'uses the group name for test suite name' do
+ expect(build.test_suite_name).to eq('test')
+ end
+
+ context 'when build is part of parallel build' do
+ let(:build) { create(:ci_build, name: 'build 1/2') }
+
+ it 'uses the group name for test suite name' do
+ expect(build.test_suite_name).to eq('build')
+ end
+ end
+
+ context 'when build is part of matrix build' do
+ let!(:matrix_build) { create(:ci_build, :matrix) }
+
+ it 'uses the job name for the test suite' do
+ expect(matrix_build.test_suite_name).to eq(matrix_build.name)
+ end
+ end
+ end
end
diff --git a/spec/models/ci/freeze_period_status_spec.rb b/spec/models/ci/freeze_period_status_spec.rb
index f51381f7a5f..ecbb7af64f7 100644
--- a/spec/models/ci/freeze_period_status_spec.rb
+++ b/spec/models/ci/freeze_period_status_spec.rb
@@ -59,4 +59,13 @@ RSpec.describe Ci::FreezePeriodStatus do
it_behaves_like 'outside freeze period', Time.utc(2020, 4, 13, 8, 1)
end
+
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/370472
+ context 'when period overlaps with itself' do
+ let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '* * * 8 *', freeze_end: '* * * 10 *') }
+
+ it_behaves_like 'within freeze period', Time.utc(2020, 8, 11, 0, 0)
+
+ it_behaves_like 'outside freeze period', Time.utc(2020, 10, 11, 0, 0)
+ end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index b996bf84529..098f8bd4514 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -8,6 +8,8 @@ RSpec.describe Ci::JobArtifact do
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:job) }
+ it { is_expected.to validate_presence_of(:job) }
+ it { is_expected.to validate_presence_of(:partition_id) }
end
it { is_expected.to respond_to(:file) }
@@ -48,82 +50,86 @@ RSpec.describe Ci::JobArtifact do
end
end
- describe '.test_reports' do
- subject { described_class.test_reports }
+ describe '.of_report_type' do
+ subject { described_class.of_report_type(report_type) }
- context 'when there is a test report' do
- let!(:artifact) { create(:ci_job_artifact, :junit) }
+ describe 'test_reports' do
+ let(:report_type) { :test }
- it { is_expected.to eq([artifact]) }
- end
+ context 'when there is a test report' do
+ let!(:artifact) { create(:ci_job_artifact, :junit) }
- context 'when there are no test reports' do
- let!(:artifact) { create(:ci_job_artifact, :archive) }
+ it { is_expected.to eq([artifact]) }
+ end
- it { is_expected.to be_empty }
+ context 'when there are no test reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
+
+ it { is_expected.to be_empty }
+ end
end
- end
- describe '.accessibility_reports' do
- subject { described_class.accessibility_reports }
+ describe 'accessibility_reports' do
+ let(:report_type) { :accessibility }
- context 'when there is an accessibility report' do
- let(:artifact) { create(:ci_job_artifact, :accessibility) }
+ context 'when there is an accessibility report' do
+ let(:artifact) { create(:ci_job_artifact, :accessibility) }
- it { is_expected.to eq([artifact]) }
- end
+ it { is_expected.to eq([artifact]) }
+ end
- context 'when there are no accessibility report' do
- let(:artifact) { create(:ci_job_artifact, :archive) }
+ context 'when there are no accessibility report' do
+ let(:artifact) { create(:ci_job_artifact, :archive) }
- it { is_expected.to be_empty }
+ it { is_expected.to be_empty }
+ end
end
- end
- describe '.coverage_reports' do
- subject { described_class.coverage_reports }
+ describe 'coverage_reports' do
+ let(:report_type) { :coverage }
- context 'when there is a coverage report' do
- let!(:artifact) { create(:ci_job_artifact, :cobertura) }
+ context 'when there is a coverage report' do
+ let!(:artifact) { create(:ci_job_artifact, :cobertura) }
- it { is_expected.to eq([artifact]) }
- end
+ it { is_expected.to eq([artifact]) }
+ end
- context 'when there are no coverage reports' do
- let!(:artifact) { create(:ci_job_artifact, :archive) }
+ context 'when there are no coverage reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
- it { is_expected.to be_empty }
+ it { is_expected.to be_empty }
+ end
end
- end
- describe '.codequality_reports' do
- subject { described_class.codequality_reports }
+ describe 'codequality_reports' do
+ let(:report_type) { :codequality }
- context 'when there is a codequality report' do
- let!(:artifact) { create(:ci_job_artifact, :codequality) }
+ context 'when there is a codequality report' do
+ let!(:artifact) { create(:ci_job_artifact, :codequality) }
- it { is_expected.to eq([artifact]) }
- end
+ it { is_expected.to eq([artifact]) }
+ end
- context 'when there are no codequality reports' do
- let!(:artifact) { create(:ci_job_artifact, :archive) }
+ context 'when there are no codequality reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
- it { is_expected.to be_empty }
+ it { is_expected.to be_empty }
+ end
end
- end
- describe '.terraform_reports' do
- context 'when there is a terraform report' do
- it 'return the job artifact' do
- artifact = create(:ci_job_artifact, :terraform)
+ describe 'terraform_reports' do
+ let(:report_type) { :terraform }
+
+ context 'when there is a terraform report' do
+ let!(:artifact) { create(:ci_job_artifact, :terraform) }
- expect(described_class.terraform_reports).to eq([artifact])
+ it { is_expected.to eq([artifact]) }
end
- end
- context 'when there are no terraform reports' do
- it 'return the an empty array' do
- expect(described_class.terraform_reports).to eq([])
+ context 'when there are no terraform reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
+
+ it { is_expected.to be_empty }
end
end
end
@@ -135,7 +141,7 @@ RSpec.describe Ci::JobArtifact do
context 'when given an unrecognized report type' do
it 'raises error' do
- expect { described_class.file_types_for_report(:blah) }.to raise_error(KeyError, /blah/)
+ expect { described_class.file_types_for_report(:blah) }.to raise_error(ArgumentError, "Unrecognized report type: blah")
end
end
end
@@ -146,8 +152,8 @@ RSpec.describe Ci::JobArtifact do
subject { Ci::JobArtifact.associated_file_types_for(file_type) }
where(:file_type, :result) do
- 'codequality' | %w(codequality)
- 'quality' | nil
+ 'codequality' | %w(codequality)
+ 'quality' | nil
end
with_them do
@@ -754,4 +760,26 @@ RSpec.describe Ci::JobArtifact do
let!(:model) { create(:ci_job_artifact, project: parent) }
end
end
+
+ describe 'partitioning' do
+ let(:job) { build(:ci_build, partition_id: 123) }
+ let(:artifact) { build(:ci_job_artifact, job: job, partition_id: nil) }
+
+ it 'copies the partition_id from job' do
+ expect { artifact.valid? }.to change(artifact, :partition_id).from(nil).to(123)
+ end
+
+ context 'when the job is missing' do
+ let(:artifact) do
+ build(:ci_job_artifact,
+ project: build_stubbed(:project),
+ job: nil,
+ partition_id: nil)
+ end
+
+ it 'does not change the partition_id value' do
+ expect { artifact.valid? }.not_to change(artifact, :partition_id)
+ end
+ end
+ end
end
diff --git a/spec/models/ci/namespace_mirror_spec.rb b/spec/models/ci/namespace_mirror_spec.rb
index 3e77c349ccb..29447cbc89d 100644
--- a/spec/models/ci/namespace_mirror_spec.rb
+++ b/spec/models/ci/namespace_mirror_spec.rb
@@ -16,7 +16,9 @@ RSpec.describe Ci::NamespaceMirror do
expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id])
expect(group2.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id])
expect(group3.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
- expect(group4.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id, group4.id])
+ expect(group4.reload.ci_namespace_mirror).to have_attributes(
+ traversal_ids: [group1.id, group2.id, group3.id, group4.id]
+ )
end
context 'scopes' do
@@ -103,6 +105,8 @@ RSpec.describe Ci::NamespaceMirror do
describe '.sync!' do
subject(:sync) { described_class.sync!(Namespaces::SyncEvent.last) }
+ let(:expected_traversal_ids) { [group1.id, group2.id, group3.id] }
+
context 'when namespace mirror does not exist in the first place' do
let(:namespace) { group3 }
@@ -114,7 +118,7 @@ RSpec.describe Ci::NamespaceMirror do
it 'creates the mirror' do
expect { sync }.to change { described_class.count }.from(3).to(4)
- expect(namespace.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
+ expect(namespace.reload.ci_namespace_mirror).to have_attributes(traversal_ids: expected_traversal_ids)
end
end
@@ -128,36 +132,8 @@ RSpec.describe Ci::NamespaceMirror do
it 'updates the mirror' do
expect { sync }.not_to change { described_class.count }
- expect(namespace.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
- end
- end
-
- shared_context 'changing the middle namespace' do
- let(:namespace) { group2 }
-
- before do
- group2.update!(parent: nil) # creates a sync event
- end
-
- it 'updates traversal_ids for the base and descendants' do
- expect { sync }.not_to change { described_class.count }
-
- expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id])
- expect(group2.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id])
- expect(group3.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id])
- expect(group4.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id, group4.id])
- end
- end
-
- it_behaves_like 'changing the middle namespace'
-
- context 'when the FFs use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do
- before do
- stub_feature_flags(use_traversal_ids: false,
- use_traversal_ids_for_ancestors: false)
+ expect(namespace.reload.ci_namespace_mirror).to have_attributes(traversal_ids: expected_traversal_ids)
end
-
- it_behaves_like 'changing the middle namespace'
end
end
end
diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb
index b051f646bd4..3038cdc944b 100644
--- a/spec/models/ci/pipeline_artifact_spec.rb
+++ b/spec/models/ci/pipeline_artifact_spec.rb
@@ -227,6 +227,19 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
expect(subject.size).to eq(size)
expect(subject.file_format).to eq(Ci::PipelineArtifact::REPORT_TYPES[file_type].to_s)
expect(subject.expire_at).to eq(Ci::PipelineArtifact::EXPIRATION_DATE.from_now)
+ expect(subject.locked).to eq('unknown')
+ end
+
+ it "creates a new pipeline artifact with pipeline's locked state" do
+ artifact = Ci::PipelineArtifact.create_or_replace_for_pipeline!(
+ pipeline: pipeline,
+ file_type: file_type,
+ file: file,
+ size: size,
+ locked: pipeline.locked
+ )
+
+ expect(artifact.locked).to eq(pipeline.locked)
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 0c28c99c113..ec03030a4b8 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -466,6 +466,48 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '.jobs_count_in_alive_pipelines' do
+ before do
+ ::Ci::HasStatus::ALIVE_STATUSES.each do |status|
+ alive_pipeline = create(:ci_pipeline, status: status, project: project)
+ create(:ci_build, pipeline: alive_pipeline)
+ create(:ci_bridge, pipeline: alive_pipeline)
+ end
+
+ completed_pipeline = create(:ci_pipeline, :success, project: project)
+ create(:ci_build, pipeline: completed_pipeline)
+
+ old_pipeline = create(:ci_pipeline, :running, project: project, created_at: 2.days.ago)
+ create(:ci_build, pipeline: old_pipeline)
+ end
+
+ it 'includes all jobs in alive pipelines created in the last 24 hours' do
+ expect(described_class.jobs_count_in_alive_pipelines)
+ .to eq(::Ci::HasStatus::ALIVE_STATUSES.count * 2)
+ end
+ end
+
+ describe '.builds_count_in_alive_pipelines' do
+ before do
+ ::Ci::HasStatus::ALIVE_STATUSES.each do |status|
+ alive_pipeline = create(:ci_pipeline, status: status, project: project)
+ create(:ci_build, pipeline: alive_pipeline)
+ create(:ci_bridge, pipeline: alive_pipeline)
+ end
+
+ completed_pipeline = create(:ci_pipeline, :success, project: project)
+ create(:ci_build, pipeline: completed_pipeline)
+
+ old_pipeline = create(:ci_pipeline, :running, project: project, created_at: 2.days.ago)
+ create(:ci_build, pipeline: old_pipeline)
+ end
+
+ it 'includes all builds in alive pipelines created in the last 24 hours' do
+ expect(described_class.builds_count_in_alive_pipelines)
+ .to eq(::Ci::HasStatus::ALIVE_STATUSES.count)
+ end
+ end
+
describe '#merge_request?' do
let_it_be(:merge_request) { create(:merge_request) }
let_it_be_with_reload(:pipeline) do
@@ -686,7 +728,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '.with_reports' do
context 'when pipeline has a test report' do
- subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
+ subject { described_class.with_reports(Ci::JobArtifact.of_report_type(:test)) }
let!(:pipeline_with_report) { create(:ci_pipeline, :with_test_reports) }
@@ -696,7 +738,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline has a coverage report' do
- subject { described_class.with_reports(Ci::JobArtifact.coverage_reports) }
+ subject { described_class.with_reports(Ci::JobArtifact.of_report_type(:coverage)) }
let!(:pipeline_with_report) { create(:ci_pipeline, :with_coverage_reports) }
@@ -706,7 +748,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline has an accessibility report' do
- subject { described_class.with_reports(Ci::JobArtifact.accessibility_reports) }
+ subject { described_class.with_reports(Ci::JobArtifact.of_report_type(:accessibility)) }
let(:pipeline_with_report) { create(:ci_pipeline, :with_accessibility_reports) }
@@ -716,7 +758,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline has a codequality report' do
- subject { described_class.with_reports(Ci::JobArtifact.codequality_reports) }
+ subject { described_class.with_reports(Ci::JobArtifact.of_report_type(:codequality)) }
let(:pipeline_with_report) { create(:ci_pipeline, :with_codequality_reports) }
@@ -729,14 +771,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it 'selects the pipeline' do
pipeline_with_report = create(:ci_pipeline, :with_terraform_reports)
- expect(described_class.with_reports(Ci::JobArtifact.terraform_reports)).to eq(
+ expect(described_class.with_reports(Ci::JobArtifact.of_report_type(:terraform))).to eq(
[pipeline_with_report]
)
end
end
context 'when pipeline does not have metrics reports' do
- subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
+ subject { described_class.with_reports(Ci::JobArtifact.of_report_type(:test)) }
let!(:pipeline_without_report) { create(:ci_empty_pipeline) }
@@ -1375,32 +1417,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:pipeline) { build(:ci_empty_pipeline, :created) }
before do
- create(:ci_stage, project: project,
- pipeline: pipeline,
- position: 4,
- name: 'deploy')
-
- create(:ci_build, project: project,
- pipeline: pipeline,
- stage: 'test',
- stage_idx: 3,
- name: 'test')
-
- create(:ci_build, project: project,
- pipeline: pipeline,
- stage: 'build',
- stage_idx: 2,
- name: 'build')
-
- create(:ci_stage, project: project,
- pipeline: pipeline,
- position: 1,
- name: 'sanity')
-
- create(:ci_stage, project: project,
- pipeline: pipeline,
- position: 5,
- name: 'cleanup')
+ create(:ci_stage, project: project, pipeline: pipeline, position: 4, name: 'deploy')
+ create(:ci_build, project: project, pipeline: pipeline, stage: 'test', stage_idx: 3, name: 'test')
+ create(:ci_build, project: project, pipeline: pipeline, stage: 'build', stage_idx: 2, name: 'build')
+ create(:ci_stage, project: project, pipeline: pipeline, position: 1, name: 'sanity')
+ create(:ci_stage, project: project, pipeline: pipeline, position: 5, name: 'cleanup')
end
subject { pipeline.stages }
@@ -1577,6 +1598,42 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe 'track artifact report' do
+ let(:pipeline) { create(:ci_pipeline, :running, :with_test_reports, status: :running, user: create(:user)) }
+
+ context 'when transitioning to completed status' do
+ %i[drop! skip! succeed! cancel!].each do |command|
+ it "performs worker on transition to #{command}" do
+ expect(Ci::JobArtifacts::TrackArtifactReportWorker).to receive(:perform_async).with(pipeline.id)
+ pipeline.send(command)
+ end
+ end
+ end
+
+ context 'when pipeline retried from failed to success', :clean_gitlab_redis_shared_state do
+ let(:test_event_name) { 'i_testing_test_report_uploaded' }
+ let(:start_time) { 1.week.ago }
+ let(:end_time) { 1.week.from_now }
+
+ it 'counts only one report' do
+ expect(Ci::JobArtifacts::TrackArtifactReportWorker).to receive(:perform_async).with(pipeline.id).twice.and_call_original
+
+ Sidekiq::Testing.inline! do
+ pipeline.drop!
+ pipeline.run!
+ pipeline.succeed!
+ end
+
+ unique_pipeline_pass = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: test_event_name,
+ start_date: start_time,
+ end_date: end_time
+ )
+ expect(unique_pipeline_pass).to eq(1)
+ end
+ end
+ end
+
describe 'merge request metrics' do
let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
@@ -1649,9 +1706,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
context 'when auto merge is enabled' do
let_it_be_with_reload(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
let_it_be_with_reload(:pipeline) do
- create(:ci_pipeline, :running, project: merge_request.source_project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha)
+ create(:ci_pipeline, :running,
+ project: merge_request.source_project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha)
end
before_all do
@@ -3615,8 +3671,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- describe '#environments_in_self_and_descendants' do
- subject { pipeline.environments_in_self_and_descendants }
+ describe '#environments_in_self_and_project_descendants' do
+ subject { pipeline.environments_in_self_and_project_descendants }
context 'when pipeline is not child nor parent' do
let_it_be(:pipeline) { create(:ci_pipeline, :created) }
@@ -4022,13 +4078,13 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- describe '#self_and_descendants_complete?' do
+ describe '#self_and_project_descendants_complete?' do
let_it_be(:pipeline) { create(:ci_pipeline, :success) }
let_it_be(:child_pipeline) { create(:ci_pipeline, :success, child_of: pipeline) }
let_it_be_with_reload(:grandchild_pipeline) { create(:ci_pipeline, :success, child_of: child_pipeline) }
context 'when all pipelines in the hierarchy is complete' do
- it { expect(pipeline.self_and_descendants_complete?).to be(true) }
+ it { expect(pipeline.self_and_project_descendants_complete?).to be(true) }
end
context 'when a pipeline in the hierarchy is not complete' do
@@ -4036,12 +4092,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
grandchild_pipeline.update!(status: :running)
end
- it { expect(pipeline.self_and_descendants_complete?).to be(false) }
+ it { expect(pipeline.self_and_project_descendants_complete?).to be(false) }
end
end
- describe '#builds_in_self_and_descendants' do
- subject(:builds) { pipeline.builds_in_self_and_descendants }
+ describe '#builds_in_self_and_project_descendants' do
+ subject(:builds) { pipeline.builds_in_self_and_project_descendants }
let(:pipeline) { create(:ci_pipeline) }
let!(:build) { create(:ci_build, pipeline: pipeline) }
@@ -4073,7 +4129,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- describe '#build_with_artifacts_in_self_and_descendants' do
+ describe '#build_with_artifacts_in_self_and_project_descendants' do
let_it_be(:pipeline) { create(:ci_pipeline) }
let!(:build) { create(:ci_build, name: 'test', pipeline: pipeline) }
@@ -4081,14 +4137,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let!(:child_build) { create(:ci_build, :artifacts, name: 'test', pipeline: child_pipeline) }
it 'returns the build with a given name, having artifacts' do
- expect(pipeline.build_with_artifacts_in_self_and_descendants('test')).to eq(child_build)
+ expect(pipeline.build_with_artifacts_in_self_and_project_descendants('test')).to eq(child_build)
end
context 'when same job name is present in both parent and child pipeline' do
let!(:build) { create(:ci_build, :artifacts, name: 'test', pipeline: pipeline) }
it 'returns the job in the parent pipeline' do
- expect(pipeline.build_with_artifacts_in_self_and_descendants('test')).to eq(build)
+ expect(pipeline.build_with_artifacts_in_self_and_project_descendants('test')).to eq(build)
end
end
end
@@ -4158,7 +4214,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
test_build = create(:ci_build, :test_reports, pipeline: pipeline)
create(:ci_build, :coverage_reports, pipeline: pipeline)
- expect(pipeline.latest_report_builds(Ci::JobArtifact.test_reports)).to contain_exactly(test_build)
+ expect(pipeline.latest_report_builds(Ci::JobArtifact.of_report_type(:test))).to contain_exactly(test_build)
end
it 'only returns not retried builds' do
@@ -4169,7 +4225,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- describe '#latest_report_builds_in_self_and_descendants' do
+ describe '#latest_report_builds_in_self_and_project_descendants' do
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
let_it_be(:grandchild_pipeline) { create(:ci_pipeline, child_of: child_pipeline) }
@@ -4179,26 +4235,57 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
child_build = create(:ci_build, :coverage_reports, pipeline: child_pipeline)
grandchild_build = create(:ci_build, :codequality_reports, pipeline: grandchild_pipeline)
- expect(pipeline.latest_report_builds_in_self_and_descendants).to contain_exactly(parent_build, child_build, grandchild_build)
+ expect(pipeline.latest_report_builds_in_self_and_project_descendants).to contain_exactly(parent_build, child_build, grandchild_build)
end
it 'filters builds by scope' do
create(:ci_build, :test_reports, pipeline: pipeline)
grandchild_build = create(:ci_build, :codequality_reports, pipeline: grandchild_pipeline)
- expect(pipeline.latest_report_builds_in_self_and_descendants(Ci::JobArtifact.codequality_reports)).to contain_exactly(grandchild_build)
+ expect(pipeline.latest_report_builds_in_self_and_project_descendants(Ci::JobArtifact.of_report_type(:codequality))).to contain_exactly(grandchild_build)
end
it 'only returns builds that are not retried' do
create(:ci_build, :codequality_reports, :retried, pipeline: grandchild_pipeline)
grandchild_build = create(:ci_build, :codequality_reports, pipeline: grandchild_pipeline)
- expect(pipeline.latest_report_builds_in_self_and_descendants).to contain_exactly(grandchild_build)
+ expect(pipeline.latest_report_builds_in_self_and_project_descendants).to contain_exactly(grandchild_build)
end
end
describe '#has_reports?' do
- subject { pipeline.has_reports?(Ci::JobArtifact.test_reports) }
+ subject { pipeline.has_reports?(Ci::JobArtifact.of_report_type(:test)) }
+
+ let(:pipeline) { create(:ci_pipeline, :running) }
+
+ context 'when pipeline has builds with test reports' do
+ before do
+ create(:ci_build, :test_reports, pipeline: pipeline)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when pipeline does not have builds with test reports' do
+ before do
+ create(:ci_build, :artifacts, pipeline: pipeline)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when retried build has test reports but latest one has none' do
+ before do
+ create(:ci_build, :retried, :test_reports, pipeline: pipeline)
+ create(:ci_build, :artifacts, pipeline: pipeline)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#complete_and_has_reports?' do
+ subject { pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)) }
context 'when pipeline has builds with test reports' do
before do
@@ -4370,6 +4457,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
create(:ci_job_artifact, :junit_with_ant, job: build_java)
end
+ it 'has a test suite for each job' do
+ expect(subject.test_suites.keys).to contain_exactly('rspec', 'java')
+ end
+
it 'returns test reports with collected data' do
expect(subject.total_count).to be(7)
expect(subject.success_count).to be(5)
@@ -4388,6 +4479,34 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ context 'when the pipeline has parallel builds with test reports' do
+ let!(:parallel_build_1) { create(:ci_build, name: 'build 1/2', pipeline: pipeline) }
+ let!(:parallel_build_2) { create(:ci_build, name: 'build 2/2', pipeline: pipeline) }
+
+ before do
+ create(:ci_job_artifact, :junit, job: parallel_build_1)
+ create(:ci_job_artifact, :junit, job: parallel_build_2)
+ end
+
+ it 'merges the test suite from parallel builds' do
+ expect(subject.test_suites.keys).to contain_exactly('build')
+ end
+ end
+
+ context 'the pipeline has matrix builds with test reports' do
+ let!(:matrix_build_1) { create(:ci_build, :matrix, pipeline: pipeline) }
+ let!(:matrix_build_2) { create(:ci_build, :matrix, pipeline: pipeline) }
+
+ before do
+ create(:ci_job_artifact, :junit, job: matrix_build_1)
+ create(:ci_job_artifact, :junit, job: matrix_build_2)
+ end
+
+ it 'keeps separate test suites for each matrix build' do
+ expect(subject.test_suites.keys).to contain_exactly(matrix_build_1.name, matrix_build_2.name)
+ end
+ end
+
context 'when pipeline does not have any builds with test reports' do
it 'returns empty test reports' do
expect(subject.total_count).to be(0)
@@ -4472,10 +4591,20 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let_it_be(:pipeline) { create(:ci_pipeline) }
context 'when the scheduling type is `dag`' do
- it 'returns true' do
- create(:ci_build, pipeline: pipeline, scheduling_type: :dag)
+ context 'when the processable is a bridge' do
+ it 'returns true' do
+ create(:ci_bridge, pipeline: pipeline, scheduling_type: :dag)
+
+ expect(pipeline.uses_needs?).to eq(true)
+ end
+ end
- expect(pipeline.uses_needs?).to eq(true)
+ context 'when the processable is a build' do
+ it 'returns true' do
+ create(:ci_build, pipeline: pipeline, scheduling_type: :dag)
+
+ expect(pipeline.uses_needs?).to eq(true)
+ end
end
end
@@ -4911,8 +5040,58 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- describe '#self_and_ancestors' do
- subject(:self_and_ancestors) { pipeline.self_and_ancestors }
+ describe '#self_and_downstreams' do
+ subject(:self_and_downstreams) { pipeline.self_and_downstreams }
+
+ let(:pipeline) { create(:ci_pipeline, :created) }
+
+ context 'when pipeline is not child nor parent' do
+ it 'returns just the pipeline itself' do
+ expect(self_and_downstreams).to contain_exactly(pipeline)
+ end
+ end
+
+ context 'when pipeline is child' do
+ let(:parent) { create(:ci_pipeline) }
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent) }
+
+ it 'returns self and no ancestors' do
+ expect(self_and_downstreams).to contain_exactly(pipeline)
+ end
+ end
+
+ context 'when pipeline is parent' do
+ let(:child) { create(:ci_pipeline, child_of: pipeline) }
+
+ it 'returns self and child' do
+ expect(self_and_downstreams).to contain_exactly(pipeline, child)
+ end
+ end
+
+ context 'when pipeline is a grandparent pipeline' do
+ let(:child) { create(:ci_pipeline, child_of: pipeline) }
+ let(:grandchild) { create(:ci_pipeline, child_of: child) }
+
+ it 'returns self, child, and grandchild' do
+ expect(self_and_downstreams).to contain_exactly(pipeline, child, grandchild)
+ end
+ end
+
+ context 'when pipeline is a triggered pipeline from a different project' do
+ let(:downstream) { create(:ci_pipeline) }
+
+ before do
+ create_source_pipeline(pipeline, downstream)
+ end
+
+ it 'returns self and cross-project downstream' do
+ expect(self_and_downstreams).to contain_exactly(pipeline, downstream)
+ end
+ end
+ end
+
+ describe '#self_and_project_ancestors' do
+ subject(:self_and_project_ancestors) { pipeline.self_and_project_ancestors }
context 'when pipeline is child' do
let(:pipeline) { create(:ci_pipeline, :created) }
@@ -4925,7 +5104,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
it 'returns parent and self' do
- expect(self_and_ancestors).to contain_exactly(parent, pipeline)
+ expect(self_and_project_ancestors).to contain_exactly(parent, pipeline)
end
end
@@ -4939,7 +5118,20 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
it 'returns only self' do
- expect(self_and_ancestors).to contain_exactly(pipeline)
+ expect(self_and_project_ancestors).to contain_exactly(pipeline)
+ end
+ end
+ end
+
+ describe '#complete_hierarchy_count' do
+ context 'with a combination of ancestor, descendant and sibling pipelines' do
+ let!(:pipeline) { create(:ci_pipeline) }
+ let!(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
+ let!(:sibling_pipeline) { create(:ci_pipeline, child_of: pipeline) }
+ let!(:grandchild_pipeline) { create(:ci_pipeline, child_of: child_pipeline) }
+
+ it 'counts the whole tree' do
+ expect(sibling_pipeline.complete_hierarchy_count).to eq(4)
end
end
end
@@ -5165,16 +5357,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it { is_expected.to be_falsey }
end
- context 'when the pipeline is still running' do
- let(:pipeline) { create(:ci_pipeline, :running) }
+ context 'when the pipeline is still running and with test reports' do
+ let(:pipeline) { create(:ci_pipeline, :running, :with_test_reports) }
- it { is_expected.to be_falsey }
- end
-
- context 'when the pipeline is completed without test reports' do
- let(:pipeline) { create(:ci_pipeline, :success) }
-
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
end
@@ -5298,4 +5484,36 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
end
+
+ describe 'partitioning' do
+ let(:pipeline) { build(:ci_pipeline) }
+
+ before do
+ allow(described_class).to receive(:current_partition_value) { 123 }
+ end
+
+ it 'sets partition_id to the current partition value' do
+ expect { pipeline.valid? }.to change(pipeline, :partition_id).to(123)
+ end
+
+ context 'when it is already set' do
+ let(:pipeline) { build(:ci_pipeline, partition_id: 125) }
+
+ it 'does not change the partition_id value' do
+ expect { pipeline.valid? }.not_to change(pipeline, :partition_id)
+ end
+ end
+
+ context 'without current partition value' do
+ before do
+ allow(described_class).to receive(:current_partition_value) {}
+ end
+
+ it { is_expected.to validate_presence_of(:partition_id) }
+
+ it 'does not change the partition_id value' do
+ expect { pipeline.valid? }.not_to change(pipeline, :partition_id)
+ end
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index 4e8d49585d0..fdcec0e96af 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -17,4 +17,25 @@ RSpec.describe Ci::PipelineVariable do
it { is_expected.to be_a(Hash) }
it { is_expected.to eq({ key: 'foo', value: 'bar' }) }
end
+
+ describe 'partitioning' do
+ context 'with pipeline' do
+ let(:pipeline) { build(:ci_pipeline, partition_id: 123) }
+ let(:variable) { build(:ci_pipeline_variable, pipeline: pipeline, partition_id: nil) }
+
+ it 'copies the partition_id from pipeline' do
+ expect { variable.valid? }.to change(variable, :partition_id).from(nil).to(123)
+ end
+ end
+
+ context 'without pipeline' do
+ subject(:variable) { build(:ci_pipeline_variable, pipeline: nil, partition_id: nil) }
+
+ it { is_expected.to validate_presence_of(:partition_id) }
+
+ it 'does not change the partition_id value' do
+ expect { variable.valid? }.not_to change(variable, :partition_id)
+ end
+ end
+ end
end
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 127a1417d9e..61e2864a518 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -30,10 +30,8 @@ RSpec.describe Ci::Processable do
let_it_be(:downstream_project) { create(:project, :repository) }
let_it_be_with_refind(:processable) do
- create(
- :ci_bridge, :success, pipeline: pipeline, downstream: downstream_project,
- description: 'a trigger job', stage_id: stage.id
- )
+ create(:ci_bridge, :success,
+ pipeline: pipeline, downstream: downstream_project, description: 'a trigger job', stage_id: stage.id)
end
let(:clone_accessors) { ::Ci::Bridge.clone_accessors }
@@ -57,8 +55,7 @@ RSpec.describe Ci::Processable do
let(:clone_accessors) { ::Ci::Build.clone_accessors.without(::Ci::Build.extra_accessors) }
let(:reject_accessors) do
- %i[id status user token_encrypted coverage trace runner
- artifacts_expire_at
+ %i[id status user token_encrypted coverage runner artifacts_expire_at
created_at updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
@@ -86,7 +83,7 @@ RSpec.describe Ci::Processable do
resource resource_group_id processed security_scans author
pipeline_id report_results pending_state pages_deployments
queuing_entry runtime_metadata trace_metadata
- dast_site_profile dast_scanner_profile].freeze
+ dast_site_profile dast_scanner_profile stage_id].freeze
end
before_all do
@@ -208,10 +205,11 @@ RSpec.describe Ci::Processable do
let(:environment_name) { 'review/$CI_COMMIT_REF_SLUG-$GITLAB_USER_ID' }
let!(:processable) do
- create(:ci_build, :with_deployment, environment: environment_name,
- options: { environment: { name: environment_name } },
- pipeline: pipeline, stage_id: stage.id, project: project,
- user: other_developer)
+ create(:ci_build, :with_deployment,
+ environment: environment_name,
+ options: { environment: { name: environment_name } },
+ pipeline: pipeline, stage_id: stage.id, project: project,
+ user: other_developer)
end
it 're-uses the previous persisted environment' do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index ae8748f8ae3..181351222c1 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -266,13 +266,13 @@ RSpec.describe Ci::Runner do
end
shared_examples '.belonging_to_parent_group_of_project' do
- let!(:group1) { create(:group) }
- let!(:project1) { create(:project, group: group1) }
- let!(:runner1) { create(:ci_runner, :group, groups: [group1]) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:project1) { create(:project, group: group1) }
+ let_it_be(:runner1) { create(:ci_runner, :group, groups: [group1]) }
- let!(:group2) { create(:group) }
- let!(:project2) { create(:project, group: group2) }
- let!(:runner2) { create(:ci_runner, :group, groups: [group2]) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project2) { create(:project, group: group2) }
+ let_it_be(:runner2) { create(:ci_runner, :group, groups: [group2]) }
let(:project_id) { project1.id }
@@ -495,8 +495,8 @@ RSpec.describe Ci::Runner do
describe '.active' do
subject { described_class.active(active_value) }
- let!(:runner1) { create(:ci_runner, :instance, active: false) }
- let!(:runner2) { create(:ci_runner, :instance) }
+ let_it_be(:runner1) { create(:ci_runner, :instance, active: false) }
+ let_it_be(:runner2) { create(:ci_runner, :instance) }
context 'with active_value set to false' do
let(:active_value) { false }
@@ -544,7 +544,7 @@ RSpec.describe Ci::Runner do
end
describe '#stale?', :clean_gitlab_redis_cache do
- let(:runner) { create(:ci_runner, :instance) }
+ let(:runner) { build(:ci_runner, :instance) }
subject { runner.stale? }
@@ -619,7 +619,7 @@ RSpec.describe Ci::Runner do
end
describe '#online?', :clean_gitlab_redis_cache do
- let(:runner) { create(:ci_runner, :instance) }
+ let(:runner) { build(:ci_runner, :instance) }
subject { runner.online? }
@@ -1016,7 +1016,7 @@ RSpec.describe Ci::Runner do
let!(:last_update) { runner.ensure_runner_queue_value }
before do
- Ci::Runners::UpdateRunnerService.new(runner).update(description: 'new runner') # rubocop: disable Rails/SaveBang
+ Ci::Runners::UpdateRunnerService.new(runner).execute(description: 'new runner')
end
it 'sets a new last_update value' do
@@ -1162,13 +1162,13 @@ RSpec.describe Ci::Runner do
end
describe '.assignable_for' do
- let(:project) { create(:project) }
- let(:group) { create(:group) }
- let(:another_project) { create(:project) }
- let!(:unlocked_project_runner) { create(:ci_runner, :project, projects: [project]) }
- let!(:locked_project_runner) { create(:ci_runner, :project, locked: true, projects: [project]) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:instance_runner) { create(:ci_runner, :instance) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:another_project) { create(:project) }
+ let_it_be(:unlocked_project_runner) { create(:ci_runner, :project, projects: [project]) }
+ let_it_be(:locked_project_runner) { create(:ci_runner, :project, locked: true, projects: [project]) }
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+ let_it_be(:instance_runner) { create(:ci_runner, :instance) }
context 'with already assigned project' do
subject { described_class.assignable_for(project) }
@@ -1186,59 +1186,74 @@ RSpec.describe Ci::Runner do
end
end
- describe "belongs_to_one_project?" do
- it "returns false if there are two projects runner assigned to" do
- project1 = create(:project)
- project2 = create(:project)
- runner = create(:ci_runner, :project, projects: [project1, project2])
+ context 'Project-related queries' do
+ let_it_be(:project1) { create(:project) }
+ let_it_be(:project2) { create(:project) }
+
+ describe '#owner_project' do
+ subject(:owner_project) { project_runner.owner_project }
+
+ context 'with project1 as first project associated with runner' do
+ let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+
+ it { is_expected.to eq project1 }
+ end
+
+ context 'with project2 as first project associated with runner' do
+ let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project2, project1]) }
- expect(runner.belongs_to_one_project?).to be_falsey
+ it { is_expected.to eq project2 }
+ end
end
- it "returns true" do
- project = create(:project)
- runner = create(:ci_runner, :project, projects: [project])
+ describe "belongs_to_one_project?" do
+ it "returns false if there are two projects runner is assigned to" do
+ runner = create(:ci_runner, :project, projects: [project1, project2])
+
+ expect(runner.belongs_to_one_project?).to be_falsey
+ end
- expect(runner.belongs_to_one_project?).to be_truthy
+ it "returns true if there is only one project runner is assigned to" do
+ runner = create(:ci_runner, :project, projects: [project1])
+
+ expect(runner.belongs_to_one_project?).to be_truthy
+ end
end
- end
- describe '#belongs_to_more_than_one_project?' do
- context 'project runner' do
- let(:project1) { create(:project) }
- let(:project2) { create(:project) }
+ describe '#belongs_to_more_than_one_project?' do
+ context 'project runner' do
+ context 'two projects assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+
+ it 'returns true' do
+ expect(runner.belongs_to_more_than_one_project?).to be_truthy
+ end
+ end
- context 'two projects assigned to runner' do
- let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+ context 'one project assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1]) }
- it 'returns true' do
- expect(runner.belongs_to_more_than_one_project?).to be_truthy
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
end
end
- context 'one project assigned to runner' do
- let(:runner) { create(:ci_runner, :project, projects: [project1]) }
+ context 'group runner' do
+ let(:group) { create(:group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
- end
-
- context 'group runner' do
- let(:group) { create(:group) }
- let(:runner) { create(:ci_runner, :group, groups: [group]) }
-
- it 'returns false' do
- expect(runner.belongs_to_more_than_one_project?).to be_falsey
- end
- end
- context 'shared runner' do
- let(:runner) { create(:ci_runner, :instance) }
+ context 'shared runner' do
+ let(:runner) { create(:ci_runner, :instance) }
- it 'returns false' do
- expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
end
end
end
@@ -1299,7 +1314,7 @@ RSpec.describe Ci::Runner do
end
describe '.search' do
- let(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') }
+ let_it_be(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') }
it 'returns runners with a matching token' do
expect(described_class.search(runner.token)).to eq([runner])
@@ -1326,57 +1341,10 @@ RSpec.describe Ci::Runner do
end
end
- describe '#assigned_to_group?' do
- subject { runner.assigned_to_group? }
-
- context 'when project runner' do
- let(:runner) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
- let(:project) { create(:project) }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when shared runner' do
- let(:runner) { create(:ci_runner, :instance, description: 'Shared runner') }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when group runner' do
- let(:group) { create(:group) }
- let(:runner) { create(:ci_runner, :group, description: 'Group runner', groups: [group]) }
-
- it { is_expected.to be_truthy }
- end
- end
-
- describe '#assigned_to_project?' do
- subject { runner.assigned_to_project? }
-
- context 'when group runner' do
- let(:runner) { create(:ci_runner, :group, description: 'Group runner', groups: [group]) }
- let(:group) { create(:group) }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when shared runner' do
- let(:runner) { create(:ci_runner, :instance, description: 'Shared runner') }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when project runner' do
- let(:runner) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
- let(:project) { create(:project) }
-
- it { is_expected.to be_truthy }
- end
- end
-
describe '#pick_build!' do
+ let_it_be(:runner) { create(:ci_runner) }
+
let(:build) { create(:ci_build) }
- let(:runner) { create(:ci_runner) }
context 'runner can pick the build' do
it 'calls #tick_runner_queue' do
@@ -1413,26 +1381,26 @@ RSpec.describe Ci::Runner do
end
describe '.order_by' do
+ let_it_be(:runner1) { create(:ci_runner, created_at: 1.year.ago, contacted_at: 1.year.ago) }
+ let_it_be(:runner2) { create(:ci_runner, created_at: 1.month.ago, contacted_at: 1.month.ago) }
+
+ before do
+ runner1.update!(token_expires_at: 1.year.from_now)
+ end
+
it 'supports ordering by the contact date' do
- runner1 = create(:ci_runner, contacted_at: 1.year.ago)
- runner2 = create(:ci_runner, contacted_at: 1.month.ago)
runners = described_class.order_by('contacted_asc')
expect(runners).to eq([runner1, runner2])
end
it 'supports ordering by the creation date' do
- runner1 = create(:ci_runner, created_at: 1.year.ago)
- runner2 = create(:ci_runner, created_at: 1.month.ago)
runners = described_class.order_by('created_asc')
expect(runners).to eq([runner2, runner1])
end
it 'supports ordering by the token expiration' do
- runner1 = create(:ci_runner)
- runner1.update!(token_expires_at: 1.year.from_now)
- runner2 = create(:ci_runner)
runner3 = create(:ci_runner)
runner3.update!(token_expires_at: 1.month.from_now)
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index d55a8509a98..dd9af33a562 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -369,4 +369,33 @@ RSpec.describe Ci::Stage, :models do
let!(:model) { create(:ci_stage, project: parent) }
end
end
+
+ describe 'partitioning' do
+ context 'with pipeline' do
+ let(:pipeline) { build(:ci_pipeline, partition_id: 123) }
+ let(:stage) { build(:ci_stage, pipeline: pipeline) }
+
+ it 'copies the partition_id from pipeline' do
+ expect { stage.valid? }.to change(stage, :partition_id).to(123)
+ end
+
+ context 'when it is already set' do
+ let(:stage) { build(:ci_stage, pipeline: pipeline, partition_id: 125) }
+
+ it 'does not change the partition_id value' do
+ expect { stage.valid? }.not_to change(stage, :partition_id)
+ end
+ end
+ end
+
+ context 'without pipeline' do
+ subject(:stage) { build(:ci_stage, pipeline: nil) }
+
+ it { is_expected.to validate_presence_of(:partition_id) }
+
+ it 'does not change the partition_id value' do
+ expect { stage.valid? }.not_to change(stage, :partition_id)
+ end
+ end
+ end
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 4ac8720780c..8517e583ec7 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Ci::Trigger do
trigger = create(:ci_trigger_without_token, project: project)
expect(trigger.token).not_to be_nil
+ expect(trigger.token).to start_with(Ci::Trigger::TRIGGER_TOKEN_PREFIX)
end
it 'does not set a random token if one provided' do
@@ -30,12 +31,22 @@ RSpec.describe Ci::Trigger do
end
describe '#short_token' do
- let(:trigger) { create(:ci_trigger, token: '12345678') }
+ let(:trigger) { create(:ci_trigger) }
subject { trigger.short_token }
- it 'returns shortened token' do
- is_expected.to eq('1234')
+ it 'returns shortened token without prefix' do
+ is_expected.not_to start_with(Ci::Trigger::TRIGGER_TOKEN_PREFIX)
+ end
+
+ context 'token does not have a prefix' do
+ before do
+ trigger.token = '12345678'
+ end
+
+ it 'returns shortened token' do
+ is_expected.to eq('1234')
+ end
end
end