diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
commit | 6ed4ec3e0b1340f96b7c043ef51d1b33bbe85fde (patch) | |
tree | dc4d20fe6064752c0bd323187252c77e0a89144b /spec/models/ci | |
parent | 9868dae7fc0655bd7ce4a6887d4e6d487690eeed (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.rb | 23 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 441 | ||||
-rw-r--r-- | spec/models/ci/freeze_period_status_spec.rb | 9 | ||||
-rw-r--r-- | spec/models/ci/job_artifact_spec.rb | 132 | ||||
-rw-r--r-- | spec/models/ci/namespace_mirror_spec.rb | 38 | ||||
-rw-r--r-- | spec/models/ci/pipeline_artifact_spec.rb | 13 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 352 | ||||
-rw-r--r-- | spec/models/ci/pipeline_variable_spec.rb | 21 | ||||
-rw-r--r-- | spec/models/ci/processable_spec.rb | 20 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 188 | ||||
-rw-r--r-- | spec/models/ci/stage_spec.rb | 29 | ||||
-rw-r--r-- | spec/models/ci/trigger_spec.rb | 17 |
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 |