diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 01:45:44 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 01:45:44 +0000 |
commit | 85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch) | |
tree | 9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/models/ci | |
parent | 15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff) | |
download | gitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz |
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/bridge_spec.rb | 1 | ||||
-rw-r--r-- | spec/models/ci/build_metadata_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 145 | ||||
-rw-r--r-- | spec/models/ci/build_trace_chunk_spec.rb | 123 | ||||
-rw-r--r-- | spec/models/ci/build_trace_chunks/fog_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/ci/instance_variable_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/ci/job_artifact_spec.rb | 40 | ||||
-rw-r--r-- | spec/models/ci/legacy_stage_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/ci/persistent_ref_spec.rb | 8 | ||||
-rw-r--r-- | spec/models/ci/pipeline_artifact_spec.rb | 78 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 473 | ||||
-rw-r--r-- | spec/models/ci/ref_spec.rb | 72 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 6 |
13 files changed, 752 insertions, 216 deletions
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 3a459e5897a..850fc1ec6e6 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -50,6 +50,7 @@ RSpec.describe Ci::Bridge do CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED + CI_COMMIT_TIMESTAMP ] expect(bridge.scoped_variables_hash.keys).to include(*variables) diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index e4d71632957..069864fa765 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Ci::BuildMetadata do context 'when both runner and job timeouts are set' do before do - build.update(runner: runner) + build.update!(runner: runner) end context 'when job timeout is higher than runner timeout' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 069ac23c5a4..1e551d9ee33 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -25,6 +25,7 @@ RSpec.describe Ci::Build do it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:job_variables) } it { is_expected.to have_many(:report_results) } + it { is_expected.to have_many(:pages_deployments) } it { is_expected.to have_one(:deployment) } it { is_expected.to have_one(:runner_session) } @@ -448,7 +449,7 @@ RSpec.describe Ci::Build do end it 'schedules BuildScheduleWorker at the right time' do - Timecop.freeze do + freeze_time do expect(Ci::BuildScheduleWorker) .to receive(:perform_at).with(be_like_time(1.minute.since), build.id) @@ -496,7 +497,7 @@ RSpec.describe Ci::Build do let(:option) { { start_in: '1 day' } } it 'returns date after 1 day' do - Timecop.freeze do + freeze_time do is_expected.to eq(1.day.since) end end @@ -506,7 +507,7 @@ RSpec.describe Ci::Build do let(:option) { { start_in: '1 week' } } it 'returns date after 1 week' do - Timecop.freeze do + freeze_time do is_expected.to eq(1.week.since) end end @@ -566,18 +567,18 @@ RSpec.describe Ci::Build do let(:runner) { create(:ci_runner, :project, projects: [build.project]) } before do - runner.update(contacted_at: 1.second.ago) + runner.update!(contacted_at: 1.second.ago) end it { is_expected.to be_truthy } it 'that is inactive' do - runner.update(active: false) + runner.update!(active: false) is_expected.to be_falsey end it 'that is not online' do - runner.update(contacted_at: nil) + runner.update!(contacted_at: nil) is_expected.to be_falsey end @@ -612,6 +613,46 @@ RSpec.describe Ci::Build do end end + describe '#locked_artifacts?' do + subject(:locked_artifacts) { build.locked_artifacts? } + + context 'when pipeline is artifacts_locked' do + before do + build.pipeline.artifacts_locked! + end + + context 'artifacts archive does not exist' do + let(:build) { create(:ci_build) } + + it { is_expected.to be_falsy } + end + + context 'artifacts archive exists' do + let(:build) { create(:ci_build, :artifacts) } + + it { is_expected.to be_truthy } + end + end + + context 'when pipeline is unlocked' do + before do + build.pipeline.unlocked! + end + + context 'artifacts archive does not exist' do + let(:build) { create(:ci_build) } + + it { is_expected.to be_falsy } + end + + context 'artifacts archive exists' do + let(:build) { create(:ci_build, :artifacts) } + + it { is_expected.to be_falsy } + end + end + end + describe '#available_artifacts?' do let(:build) { create(:ci_build) } @@ -683,7 +724,7 @@ RSpec.describe Ci::Build do context 'is expired' do before do - build.update(artifacts_expire_at: Time.current - 7.days) + build.update!(artifacts_expire_at: Time.current - 7.days) end it { is_expected.to be_truthy } @@ -691,7 +732,7 @@ RSpec.describe Ci::Build do context 'is not expired' do before do - build.update(artifacts_expire_at: Time.current + 7.days) + build.update!(artifacts_expire_at: Time.current + 7.days) end it { is_expected.to be_falsey } @@ -1012,18 +1053,53 @@ RSpec.describe Ci::Build do end describe '#hide_secrets' do + let(:metrics) { spy('metrics') } let(:subject) { build.hide_secrets(data) } context 'hide runners token' do let(:data) { "new #{project.runners_token} data"} it { is_expected.to match(/^new x+ data$/) } + + it 'increments trace mutation metric' do + build.hide_secrets(data, metrics) + + expect(metrics) + .to have_received(:increment_trace_operation) + .with(operation: :mutated) + end end context 'hide build token' do let(:data) { "new #{build.token} data"} it { is_expected.to match(/^new x+ data$/) } + + it 'increments trace mutation metric' do + build.hide_secrets(data, metrics) + + expect(metrics) + .to have_received(:increment_trace_operation) + .with(operation: :mutated) + end + end + + context 'when build does not include secrets' do + let(:data) { 'my build log' } + + it 'does not mutate trace' do + trace = build.hide_secrets(data) + + expect(trace).to eq data + end + + it 'does not increment trace mutation metric' do + build.hide_secrets(data, metrics) + + expect(metrics) + .not_to have_received(:increment_trace_operation) + .with(operation: :mutated) + end end end @@ -1200,7 +1276,7 @@ RSpec.describe Ci::Build do context 'when environment is defined' do before do - build.update(environment: 'review') + build.update!(environment: 'review') end it { is_expected.to be_truthy } @@ -1208,7 +1284,7 @@ RSpec.describe Ci::Build do context 'when environment is not defined' do before do - build.update(environment: nil) + build.update!(environment: nil) end it { is_expected.to be_falsey } @@ -1316,7 +1392,7 @@ RSpec.describe Ci::Build do context 'when environment is defined' do before do - build.update(environment: 'review') + build.update!(environment: 'review') end context 'no action is defined' do @@ -1325,7 +1401,7 @@ RSpec.describe Ci::Build do context 'and start action is defined' do before do - build.update(options: { environment: { action: 'start' } } ) + build.update!(options: { environment: { action: 'start' } } ) end it { is_expected.to be_truthy } @@ -1334,7 +1410,7 @@ RSpec.describe Ci::Build do context 'when environment is not defined' do before do - build.update(environment: nil) + build.update!(environment: nil) end it { is_expected.to be_falsey } @@ -1346,7 +1422,7 @@ RSpec.describe Ci::Build do context 'when environment is defined' do before do - build.update(environment: 'review') + build.update!(environment: 'review') end context 'no action is defined' do @@ -1355,7 +1431,7 @@ RSpec.describe Ci::Build do context 'and stop action is defined' do before do - build.update(options: { environment: { action: 'stop' } } ) + build.update!(options: { environment: { action: 'stop' } } ) end it { is_expected.to be_truthy } @@ -1364,7 +1440,7 @@ RSpec.describe Ci::Build do context 'when environment is not defined' do before do - build.update(environment: nil) + build.update!(environment: nil) end it { is_expected.to be_falsey } @@ -1687,7 +1763,7 @@ RSpec.describe Ci::Build do describe '#action?' do before do - build.update(when: value) + build.update!(when: value) end subject { build.action? } @@ -2232,7 +2308,7 @@ RSpec.describe Ci::Build do describe '#has_expiring_archive_artifacts?' do context 'when artifacts have expiration date set' do before do - build.update(artifacts_expire_at: 1.day.from_now) + build.update!(artifacts_expire_at: 1.day.from_now) end context 'and job artifacts archive record exists' do @@ -2252,7 +2328,7 @@ RSpec.describe Ci::Build do context 'when artifacts do not have expiration date set' do before do - build.update(artifacts_expire_at: nil) + build.update!(artifacts_expire_at: nil) end it 'does not have expiring artifacts' do @@ -2329,6 +2405,7 @@ RSpec.describe Ci::Build do { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }, { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false }, + { key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false }, { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false }, { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false }, @@ -2526,7 +2603,7 @@ RSpec.describe Ci::Build do end before do - build.update(user: user) + build.update!(user: user) end it { user_variables.each { |v| is_expected.to include(v) } } @@ -2562,7 +2639,7 @@ RSpec.describe Ci::Build do end before do - build.update(environment: 'production') + build.update!(environment: 'production') end shared_examples 'containing environment variables' do @@ -2589,7 +2666,7 @@ RSpec.describe Ci::Build do context 'when the URL was set from the job' do before do - build.update(options: { environment: { url: url } }) + build.update!(options: { environment: { url: url } }) end it_behaves_like 'containing environment variables' @@ -2607,7 +2684,7 @@ RSpec.describe Ci::Build do context 'when the URL was not set from the job, but environment' do before do - environment.update(external_url: url) + environment.update!(external_url: url) end it_behaves_like 'containing environment variables' @@ -2643,7 +2720,7 @@ RSpec.describe Ci::Build do context 'when build started manually' do before do - build.update(when: :manual) + build.update!(when: :manual) end let(:manual_variable) do @@ -2669,8 +2746,8 @@ RSpec.describe Ci::Build do end before do - build.update(tag: false) - pipeline.update(tag: false) + build.update!(tag: false) + pipeline.update!(tag: false) end it { is_expected.to include(branch_variable) } @@ -2682,8 +2759,8 @@ RSpec.describe Ci::Build do end before do - build.update(tag: true) - pipeline.update(tag: true) + build.update!(tag: true) + pipeline.update!(tag: true) end it { is_expected.to include(tag_variable) } @@ -2860,7 +2937,7 @@ RSpec.describe Ci::Build do context 'and is disabled for project' do before do - project.update(container_registry_enabled: false) + project.update!(container_registry_enabled: false) end it { is_expected.to include(ci_registry) } @@ -2869,7 +2946,7 @@ RSpec.describe Ci::Build do context 'and is enabled for project' do before do - project.update(container_registry_enabled: true) + project.update!(container_registry_enabled: true) end it { is_expected.to include(ci_registry) } @@ -2881,7 +2958,7 @@ RSpec.describe Ci::Build do let(:runner) { create(:ci_runner, description: 'description', tag_list: %w(docker linux)) } before do - build.update(runner: runner) + build.update!(runner: runner) end it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) } @@ -3685,7 +3762,7 @@ RSpec.describe Ci::Build do subject { described_class.where(id: build).matches_tag_ids(tag_ids) } before do - build.update(tag_list: build_tag_list) + build.update!(tag_list: build_tag_list) end context 'when have different tags' do @@ -3731,7 +3808,7 @@ RSpec.describe Ci::Build do subject { described_class.where(id: build).with_any_tags } before do - build.update(tag_list: tag_list) + build.update!(tag_list: tag_list) end context 'when does have tags' do @@ -4087,7 +4164,7 @@ RSpec.describe Ci::Build do let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' } around do |example| - Timecop.freeze { example.run } + freeze_time { example.run } end before do diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index a6362d46449..fefe5e3bfca 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -21,6 +21,25 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do stub_artifacts_object_storage end + describe 'chunk creation' do + let(:metrics) { spy('metrics') } + + before do + allow(::Gitlab::Ci::Trace::Metrics) + .to receive(:new) + .and_return(metrics) + end + + it 'increments trace operation chunked metric' do + build_trace_chunk.save! + + expect(metrics) + .to have_received(:increment_trace_operation) + .with(operation: :chunked) + .once + end + end + context 'FastDestroyAll' do let(:parent) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: parent) } @@ -32,7 +51,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do expect(external_data_counter).to be > 0 expect(subjects.count).to be > 0 - expect { subjects.first.destroy }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`') + expect { subjects.first.destroy! }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`') expect { subjects.destroy_all }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`') # rubocop: disable Cop/DestroyAll expect(subjects.count).to be > 0 @@ -57,7 +76,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do expect(external_data_counter).to be > 0 expect(subjects.count).to be > 0 - expect { parent.destroy }.not_to raise_error + expect { parent.destroy! }.not_to raise_error expect(subjects.count).to eq(0) expect(external_data_counter).to eq(0) @@ -222,6 +241,8 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do subject build_trace_chunk.reload + + expect(build_trace_chunk.checksum).to be_present expect(build_trace_chunk.fog?).to be_truthy expect(build_trace_chunk.data).to eq(new_data) end @@ -344,6 +365,24 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do it_behaves_like 'Scheduling no sidekiq worker' end end + + describe 'append metrics' do + let(:metrics) { spy('metrics') } + + before do + allow(::Gitlab::Ci::Trace::Metrics) + .to receive(:new) + .and_return(metrics) + end + + it 'increments trace operation appended metric' do + build_trace_chunk.append('123456', 0) + + expect(metrics) + .to have_received(:increment_trace_operation) + .with(operation: :appended) + end + end end describe '#truncate' do @@ -461,6 +500,8 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end describe '#persist_data!' do + let(:build) { create(:ci_build, :running) } + subject { build_trace_chunk.persist_data! } shared_examples_for 'Atomic operation' do @@ -502,6 +543,12 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data) end + it 'calculates CRC32 checksum' do + subject + + expect(build_trace_chunk.reload.checksum).to eq '3398914352' + end + it_behaves_like 'Atomic operation' end @@ -516,6 +563,18 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to be_nil end + + context 'when chunk is a final one' do + before do + create(:ci_build_pending_state, build: build) + end + + it 'persists the data' do + subject + + expect(build_trace_chunk.fog?).to be_truthy + end + end end end @@ -565,6 +624,18 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data) expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to be_nil end + + context 'when chunk is a final one' do + before do + create(:ci_build_pending_state, build: build) + end + + it 'persists the data' do + subject + + expect(build_trace_chunk.fog?).to be_truthy + end + end end end @@ -614,6 +685,54 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end end + describe 'final?' do + let(:build) { create(:ci_build, :running) } + + context 'when build pending state exists' do + before do + create(:ci_build_pending_state, build: build) + end + + context 'when chunks is not the last one' do + before do + create(:ci_build_trace_chunk, chunk_index: 1, build: build) + end + + it 'is not a final chunk' do + expect(build.reload.pending_state).to be_present + expect(build_trace_chunk).not_to be_final + end + end + + context 'when chunks is the last one' do + it 'is a final chunk' do + expect(build.reload.pending_state).to be_present + expect(build_trace_chunk).to be_final + end + end + end + + context 'when build pending state does not exist' do + context 'when chunks is not the last one' do + before do + create(:ci_build_trace_chunk, chunk_index: 1, build: build) + end + + it 'is not a final chunk' do + expect(build.reload.pending_state).not_to be_present + expect(build_trace_chunk).not_to be_final + end + end + + context 'when chunks is the last one' do + it 'is not a final chunk' do + expect(build.reload.pending_state).not_to be_present + expect(build_trace_chunk).not_to be_final + end + end + end + end + describe 'deletes data in redis after a parent record destroyed' do let(:project) { create(:project) } diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb index a44ae58dfd2..b7e9adab04a 100644 --- a/spec/models/ci/build_trace_chunks/fog_spec.rb +++ b/spec/models/ci/build_trace_chunks/fog_spec.rb @@ -46,9 +46,7 @@ RSpec.describe Ci::BuildTraceChunks::Fog do end describe '#set_data' do - subject { data_store.set_data(model, data) } - - let(:data) { 'abc123' } + let(:new_data) { 'abc123' } context 'when data exists' do let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') } @@ -56,9 +54,9 @@ RSpec.describe Ci::BuildTraceChunks::Fog do it 'overwrites data' do expect(data_store.data(model)).to eq('sample data in fog') - subject + data_store.set_data(model, new_data) - expect(data_store.data(model)).to eq('abc123') + expect(data_store.data(model)).to eq new_data end end @@ -68,9 +66,9 @@ RSpec.describe Ci::BuildTraceChunks::Fog do it 'sets new data' do expect(data_store.data(model)).to be_nil - subject + data_store.set_data(model, new_data) - expect(data_store.data(model)).to eq('abc123') + expect(data_store.data(model)).to eq new_data end end end diff --git a/spec/models/ci/instance_variable_spec.rb b/spec/models/ci/instance_variable_spec.rb index 15d0c911bc4..0ef1dfbd55c 100644 --- a/spec/models/ci/instance_variable_spec.rb +++ b/spec/models/ci/instance_variable_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Ci::InstanceVariable do let(:value) { SecureRandom.alphanumeric(10_002) } it 'raises a database level error' do - expect { variable.save }.to raise_error(ActiveRecord::StatementInvalid) + expect { variable.save! }.to raise_error(ActiveRecord::StatementInvalid) end end @@ -36,7 +36,7 @@ RSpec.describe Ci::InstanceVariable do let(:value) { SecureRandom.alphanumeric(10_000) } it 'does not raise database level error' do - expect { variable.save }.not_to raise_error + expect { variable.save! }.not_to raise_error end end end @@ -85,7 +85,7 @@ RSpec.describe Ci::InstanceVariable do it 'resets the cache when records are deleted' do expect(described_class.all_cached).to contain_exactly(protected_variable, unprotected_variable) - protected_variable.destroy + protected_variable.destroy! expect(described_class.all_cached).to contain_exactly(unprotected_variable) end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 91a669aa3f4..779839df670 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -20,7 +20,9 @@ RSpec.describe Ci::JobArtifact do it_behaves_like 'having unique enum values' it_behaves_like 'UpdateProjectStatistics' do - subject { build(:ci_job_artifact, :archive, size: 107464) } + let_it_be(:job, reload: true) { create(:ci_build) } + + subject { build(:ci_job_artifact, :archive, job: job, size: 107464) } end describe '.not_expired' do @@ -343,42 +345,6 @@ RSpec.describe Ci::JobArtifact do end end - describe '#each_blob' do - context 'when file format is gzip' do - context 'when gzip file contains one file' do - let(:artifact) { build(:ci_job_artifact, :junit) } - - it 'iterates blob once' do - expect { |b| artifact.each_blob(&b) }.to yield_control.once - end - end - - context 'when gzip file contains three files' do - let(:artifact) { build(:ci_job_artifact, :junit_with_three_testsuites) } - - it 'iterates blob three times' do - expect { |b| artifact.each_blob(&b) }.to yield_control.exactly(3).times - end - end - end - - context 'when file format is raw' do - let(:artifact) { build(:ci_job_artifact, :codequality, file_format: :raw) } - - it 'iterates blob once' do - expect { |b| artifact.each_blob(&b) }.to yield_control.once - end - end - - context 'when there are no adapters for the file format' do - let(:artifact) { build(:ci_job_artifact, :junit, file_format: :zip) } - - it 'raises an error' do - expect { |b| artifact.each_blob(&b) }.to raise_error(described_class::NotSupportedAdapterError) - end - end - end - describe 'expired?' do subject { artifact.expired? } diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb index c53f6abb037..2487ad85889 100644 --- a/spec/models/ci/legacy_stage_spec.rb +++ b/spec/models/ci/legacy_stage_spec.rb @@ -116,7 +116,7 @@ RSpec.describe Ci::LegacyStage do let!(:new_build) { create_job(:ci_build, status: :success) } before do - stage_build.update(retried: true) + stage_build.update!(retried: true) end it "returns status of latest build" do diff --git a/spec/models/ci/persistent_ref_spec.rb b/spec/models/ci/persistent_ref_spec.rb index 18552317025..e488580ae7b 100644 --- a/spec/models/ci/persistent_ref_spec.rb +++ b/spec/models/ci/persistent_ref_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Ci::PersistentRef do context 'when a persistent ref exists' do before do - pipeline.persistent_ref.create + pipeline.persistent_ref.create # rubocop: disable Rails/SaveBang end it { is_expected.to eq(true) } @@ -32,7 +32,7 @@ RSpec.describe Ci::PersistentRef do end describe '#create' do - subject { pipeline.persistent_ref.create } + subject { pipeline.persistent_ref.create } # rubocop: disable Rails/SaveBang let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } let(:project) { create(:project, :repository) } @@ -58,7 +58,7 @@ RSpec.describe Ci::PersistentRef do context 'when a persistent ref already exists' do before do - pipeline.persistent_ref.create + pipeline.persistent_ref.create # rubocop: disable Rails/SaveBang end it 'overwrites a persistent ref' do @@ -78,7 +78,7 @@ RSpec.describe Ci::PersistentRef do context 'when a persistent ref exists' do before do - pipeline.persistent_ref.create + pipeline.persistent_ref.create # rubocop: disable Rails/SaveBang end it 'deletes the ref' do diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb index 9d63d74a6cc..8cbace845a9 100644 --- a/spec/models/ci/pipeline_artifact_spec.rb +++ b/spec/models/ci/pipeline_artifact_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::PipelineArtifact, type: :model do - let_it_be(:coverage_report) { create(:ci_pipeline_artifact) } + let(:coverage_report) { create(:ci_pipeline_artifact) } describe 'associations' do it { is_expected.to belong_to(:pipeline) } @@ -12,6 +12,12 @@ RSpec.describe Ci::PipelineArtifact, type: :model do it_behaves_like 'having unique enum values' + it_behaves_like 'UpdateProjectStatistics' do + let_it_be(:pipeline, reload: true) { create(:ci_pipeline) } + + subject { build(:ci_pipeline_artifact, pipeline: pipeline) } + end + describe 'validations' do it { is_expected.to validate_presence_of(:pipeline) } it { is_expected.to validate_presence_of(:project) } @@ -44,38 +50,74 @@ RSpec.describe Ci::PipelineArtifact, type: :model do end end - describe '#set_size' do + describe 'file is being stored' do subject { create(:ci_pipeline_artifact) } - context 'when file is being created' do - it 'sets the size' do - expect(subject.size).to eq(85) + context 'when existing object has local store' do + it_behaves_like 'mounted file in local store' + end + + context 'when direct upload is enabled' do + before do + stub_artifacts_object_storage(Ci::PipelineArtifactUploader, direct_upload: true) + end + + context 'when file is stored' do + it_behaves_like 'mounted file in object store' end end - context 'when file is being updated' do - it 'updates the size' do - subject.update!(file: fixture_file_upload('spec/fixtures/dk.png')) + context 'when file contains multi-byte characters' do + let(:coverage_report_multibyte) { create(:ci_pipeline_artifact, :with_multibyte_characters) } - expect(subject.size).to eq(1062) + it 'sets the size in bytesize' do + expect(coverage_report_multibyte.size).to eq(14) end end end - describe 'file is being stored' do - subject { create(:ci_pipeline_artifact) } + describe '.has_code_coverage?' do + subject { Ci::PipelineArtifact.has_code_coverage? } - context 'when existing object has local store' do - it_behaves_like 'mounted file in local store' + context 'when pipeline artifact has a code coverage' do + let!(:pipeline_artifact) { create(:ci_pipeline_artifact) } + + it 'returns true' do + expect(subject).to be_truthy + end end - context 'when direct upload is enabled' do - before do - stub_artifacts_object_storage(Ci::PipelineArtifactUploader, direct_upload: true) + context 'when pipeline artifact does not have a code coverage' do + it 'returns false' do + expect(subject).to be_falsey end + end + end - context 'when file is stored' do - it_behaves_like 'mounted file in object store' + describe '.find_with_code_coverage' do + subject { Ci::PipelineArtifact.find_with_code_coverage } + + context 'when pipeline artifact has a coverage report' do + let!(:coverage_report) { create(:ci_pipeline_artifact) } + + it 'returns a pipeline artifact with a code coverage' do + expect(subject.file_type).to eq('code_coverage') + end + end + + context 'when pipeline artifact does not have a coverage report' do + it 'returns nil' do + expect(subject).to be_nil + end + end + end + + describe '#present' do + subject { coverage_report.present } + + context 'when file_type is code_coverage' do + it 'uses code coverage presenter' do + expect(subject.present).to be_kind_of(Ci::PipelineArtifacts::CodeCoveragePresenter) end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b4e80fa7588..228a1e8f7a2 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2,12 +2,14 @@ require 'spec_helper' -RSpec.describe Ci::Pipeline, :mailer do +RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do include ProjectForksHelper include StubRequests + include Ci::SourcePipelineHelpers - let(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:namespace) { create_default(:namespace) } + let_it_be(:project) { create_default(:project, :repository) } let(:pipeline) do create(:ci_empty_pipeline, status: :created, project: project) @@ -220,6 +222,26 @@ RSpec.describe Ci::Pipeline, :mailer do end end + describe '.ci_sources' do + subject { described_class.ci_sources } + + let!(:push_pipeline) { create(:ci_pipeline, source: :push) } + let!(:web_pipeline) { create(:ci_pipeline, source: :web) } + let!(:api_pipeline) { create(:ci_pipeline, source: :api) } + let!(:webide_pipeline) { create(:ci_pipeline, source: :webide) } + let!(:child_pipeline) { create(:ci_pipeline, source: :parent_pipeline) } + + it 'contains pipelines having CI only sources' do + expect(subject).to contain_exactly(push_pipeline, web_pipeline, api_pipeline) + end + + it 'filters on expected sources' do + expect(::Enums::Ci::Pipeline.ci_sources.keys).to contain_exactly( + *%i[unknown push web trigger schedule api external pipeline chat + merge_request_event external_pull_request_event]) + end + end + describe '.outside_pipeline_family' do subject(:outside_pipeline_family) { described_class.outside_pipeline_family(upstream_pipeline) } @@ -714,6 +736,7 @@ RSpec.describe Ci::Pipeline, :mailer do CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED + CI_COMMIT_TIMESTAMP CI_BUILD_REF CI_BUILD_BEFORE_SHA CI_BUILD_REF_NAME @@ -879,7 +902,7 @@ RSpec.describe Ci::Pipeline, :mailer do context 'when there is auto_canceled_by' do before do - pipeline.update(auto_canceled_by: create(:ci_empty_pipeline)) + pipeline.update!(auto_canceled_by: create(:ci_empty_pipeline)) end it 'is auto canceled' do @@ -1237,14 +1260,42 @@ RSpec.describe Ci::Pipeline, :mailer do end describe 'pipeline caching' do - before do - pipeline.config_source = 'repository_source' + context 'if pipeline is cacheable' do + before do + pipeline.source = 'push' + end + + it 'performs ExpirePipelinesCacheWorker' do + expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id) + + pipeline.cancel + end end - it 'performs ExpirePipelinesCacheWorker' do - expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id) + context 'if pipeline is not cacheable' do + before do + pipeline.source = 'webide' + end - pipeline.cancel + it 'deos not perform ExpirePipelinesCacheWorker' do + expect(ExpirePipelineCacheWorker).not_to receive(:perform_async) + + pipeline.cancel + end + end + end + + describe '#dangling?' do + it 'returns true if pipeline comes from any dangling sources' do + pipeline.source = Enums::Ci::Pipeline.dangling_sources.each_key.first + + expect(pipeline).to be_dangling + end + + it 'returns true if pipeline comes from any CI sources' do + pipeline.source = Enums::Ci::Pipeline.ci_sources.each_key.first + + expect(pipeline).not_to be_dangling end end @@ -1309,34 +1360,126 @@ RSpec.describe Ci::Pipeline, :mailer do end end - context 'when pipeline is bridge triggered' do - before do - pipeline.source_bridge = create(:ci_bridge) - end + def auto_devops_pipelines_completed_total(status) + Gitlab::Metrics.counter(:auto_devops_pipelines_completed_total, 'Number of completed auto devops pipelines').get(status: status) + end + end + + describe 'bridge triggered pipeline' do + shared_examples 'upstream downstream pipeline' do + let!(:source_pipeline) { create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge) } + let!(:job) { downstream_pipeline.builds.first } context 'when source bridge is dependent on pipeline status' do - before do - allow(pipeline.source_bridge).to receive(:dependent?).and_return(true) - end + let!(:bridge) { create(:ci_bridge, :strategy_depend, pipeline: upstream_pipeline) } it 'schedules the pipeline bridge worker' do - expect(::Ci::PipelineBridgeStatusWorker).to receive(:perform_async) + expect(::Ci::PipelineBridgeStatusWorker).to receive(:perform_async).with(downstream_pipeline.id) + + downstream_pipeline.succeed! + end + + context 'when the downstream pipeline first fails then retries and succeeds' do + it 'makes the upstream pipeline successful' do + Sidekiq::Testing.inline! { job.drop! } + + expect(downstream_pipeline.reload).to be_failed + expect(upstream_pipeline.reload).to be_failed + + Sidekiq::Testing.inline! do + new_job = Ci::Build.retry(job, project.users.first) + + expect(downstream_pipeline.reload).to be_running + expect(upstream_pipeline.reload).to be_running + + new_job.success! + end + + expect(downstream_pipeline.reload).to be_success + expect(upstream_pipeline.reload).to be_success + end + end + + context 'when the downstream pipeline first succeeds then retries and fails' do + it 'makes the upstream pipeline failed' do + Sidekiq::Testing.inline! { job.success! } + + expect(downstream_pipeline.reload).to be_success + expect(upstream_pipeline.reload).to be_success + + Sidekiq::Testing.inline! do + new_job = Ci::Build.retry(job, project.users.first) + + expect(downstream_pipeline.reload).to be_running + expect(upstream_pipeline.reload).to be_running + + new_job.drop! + end + + expect(downstream_pipeline.reload).to be_failed + expect(upstream_pipeline.reload).to be_failed + end + end + + context 'when the upstream pipeline has another dependent upstream pipeline' do + let!(:upstream_of_upstream_pipeline) { create(:ci_pipeline) } + + before do + upstream_bridge = create(:ci_bridge, :strategy_depend, pipeline: upstream_of_upstream_pipeline) + create(:ci_sources_pipeline, pipeline: upstream_pipeline, + source_job: upstream_bridge) + end + + context 'when the downstream pipeline first fails then retries and succeeds' do + it 'makes upstream pipelines successful' do + Sidekiq::Testing.inline! { job.drop! } + + expect(downstream_pipeline.reload).to be_failed + expect(upstream_pipeline.reload).to be_failed + expect(upstream_of_upstream_pipeline.reload).to be_failed - pipeline.succeed! + Sidekiq::Testing.inline! do + new_job = Ci::Build.retry(job, project.users.first) + + expect(downstream_pipeline.reload).to be_running + expect(upstream_pipeline.reload).to be_running + expect(upstream_of_upstream_pipeline.reload).to be_running + + new_job.success! + end + + expect(downstream_pipeline.reload).to be_success + expect(upstream_pipeline.reload).to be_success + expect(upstream_of_upstream_pipeline.reload).to be_success + end + end end end context 'when source bridge is not dependent on pipeline status' do + let!(:bridge) { create(:ci_bridge, pipeline: upstream_pipeline) } + it 'does not schedule the pipeline bridge worker' do expect(::Ci::PipelineBridgeStatusWorker).not_to receive(:perform_async) - pipeline.succeed! + downstream_pipeline.succeed! end end end - def auto_devops_pipelines_completed_total(status) - Gitlab::Metrics.counter(:auto_devops_pipelines_completed_total, 'Number of completed auto devops pipelines').get(status: status) + context 'multi-project pipelines' do + let!(:downstream_project) { create(:project, :repository) } + let!(:upstream_pipeline) { create(:ci_pipeline, project: project) } + let!(:downstream_pipeline) { create(:ci_pipeline, :with_job, project: downstream_project) } + + it_behaves_like 'upstream downstream pipeline' + end + + context 'parent-child pipelines' do + let!(:upstream_pipeline) { create(:ci_pipeline, project: project) } + let!(:downstream_pipeline) { create(:ci_pipeline, :with_job, project: project) } + + it_behaves_like 'upstream downstream pipeline' end end @@ -1436,8 +1579,6 @@ RSpec.describe Ci::Pipeline, :mailer do context 'when repository exists' do using RSpec::Parameterized::TableSyntax - let(:project) { create(:project, :repository) } - where(:tag, :ref, :result) do false | 'master' | true false | 'non-existent-branch' | false @@ -1457,6 +1598,7 @@ RSpec.describe Ci::Pipeline, :mailer do end context 'when repository does not exist' do + let(:project) { create(:project) } let(:pipeline) do create(:ci_empty_pipeline, project: project, ref: 'master') end @@ -1468,8 +1610,6 @@ RSpec.describe Ci::Pipeline, :mailer do end context 'with non-empty project' do - let(:project) { create(:project, :repository) } - let(:pipeline) do create(:ci_pipeline, project: project, @@ -1524,7 +1664,7 @@ RSpec.describe Ci::Pipeline, :mailer do it 'looks up a commit for a tag' do expect(project.repository.branch_names).not_to include 'v1.0.0' - pipeline.update(sha: project.commit('v1.0.0').sha, ref: 'v1.0.0', tag: true) + pipeline.update!(sha: project.commit('v1.0.0').sha, ref: 'v1.0.0', tag: true) expect(pipeline).to be_tag expect(pipeline).to be_latest @@ -1533,7 +1673,7 @@ RSpec.describe Ci::Pipeline, :mailer do context 'with not latest sha' do before do - pipeline.update(sha: project.commit("#{project.default_branch}~1").sha) + pipeline.update!(sha: project.commit("#{project.default_branch}~1").sha) end it 'returns false' do @@ -1561,7 +1701,7 @@ RSpec.describe Ci::Pipeline, :mailer do let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') } before do - manual.update(retried: true) + manual.update!(retried: true) end it 'returns latest one' do @@ -1596,10 +1736,8 @@ RSpec.describe Ci::Pipeline, :mailer do describe '#modified_paths' do context 'when old and new revisions are set' do - let(:project) { create(:project, :repository) } - before do - pipeline.update(before_sha: '1234abcd', sha: '2345bcde') + pipeline.update!(before_sha: '1234abcd', sha: '2345bcde') end it 'fetches stats for changes between commits' do @@ -1866,8 +2004,6 @@ RSpec.describe Ci::Pipeline, :mailer do end describe '.latest_pipeline_per_commit' do - let(:project) { create(:project) } - let!(:commit_123_ref_master) do create( :ci_empty_pipeline, @@ -1962,15 +2098,14 @@ RSpec.describe Ci::Pipeline, :mailer do end describe '.last_finished_for_ref_id' do - let(:project) { create(:project, :repository) } let(:branch) { project.default_branch } let(:ref) { project.ci_refs.take } - let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] } + let(:dangling_source) { Enums::Ci::Pipeline.sources[:ondemand_dast_scan] } let!(:pipeline1) { create(:ci_pipeline, :success, project: project, ref: branch) } let!(:pipeline2) { create(:ci_pipeline, :success, project: project, ref: branch) } let!(:pipeline3) { create(:ci_pipeline, :failed, project: project, ref: branch) } let!(:pipeline4) { create(:ci_pipeline, :success, project: project, ref: branch) } - let!(:pipeline5) { create(:ci_pipeline, :success, project: project, ref: branch, config_source: config_source) } + let!(:pipeline5) { create(:ci_pipeline, :success, project: project, ref: branch, source: dangling_source) } it 'returns the expected pipeline' do result = described_class.last_finished_for_ref_id(ref.id) @@ -2452,7 +2587,6 @@ RSpec.describe Ci::Pipeline, :mailer do end describe "#merge_requests_as_head_pipeline" do - let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do @@ -2575,11 +2709,11 @@ RSpec.describe Ci::Pipeline, :mailer do end describe '#same_family_pipeline_ids' do - subject(:same_family_pipeline_ids) { pipeline.same_family_pipeline_ids } + subject { pipeline.same_family_pipeline_ids.map(&:id) } context 'when pipeline is not child nor parent' do it 'returns just the pipeline id' do - expect(same_family_pipeline_ids).to contain_exactly(pipeline.id) + expect(subject).to contain_exactly(pipeline.id) end end @@ -2588,21 +2722,12 @@ RSpec.describe Ci::Pipeline, :mailer do let(:sibling) { create(:ci_pipeline, project: pipeline.project) } before do - create(:ci_sources_pipeline, - source_job: create(:ci_build, pipeline: parent), - source_project: parent.project, - pipeline: pipeline, - project: pipeline.project) - - create(:ci_sources_pipeline, - source_job: create(:ci_build, pipeline: parent), - source_project: parent.project, - pipeline: sibling, - project: sibling.project) + create_source_pipeline(parent, pipeline) + create_source_pipeline(parent, sibling) end it 'returns parent sibling and self ids' do - expect(same_family_pipeline_ids).to contain_exactly(parent.id, pipeline.id, sibling.id) + expect(subject).to contain_exactly(parent.id, pipeline.id, sibling.id) end end @@ -2610,15 +2735,43 @@ RSpec.describe Ci::Pipeline, :mailer do let(:child) { create(:ci_pipeline, project: pipeline.project) } before do - create(:ci_sources_pipeline, - source_job: create(:ci_build, pipeline: pipeline), - source_project: pipeline.project, - pipeline: child, - project: child.project) + create_source_pipeline(pipeline, child) end it 'returns self and child ids' do - expect(same_family_pipeline_ids).to contain_exactly(pipeline.id, child.id) + expect(subject).to contain_exactly(pipeline.id, child.id) + end + end + + context 'when pipeline is a child of a child pipeline' do + let(:ancestor) { create(:ci_pipeline, project: pipeline.project) } + let(:parent) { create(:ci_pipeline, project: pipeline.project) } + let(:cousin_parent) { create(:ci_pipeline, project: pipeline.project) } + let(:cousin) { create(:ci_pipeline, project: pipeline.project) } + + before do + create_source_pipeline(ancestor, parent) + create_source_pipeline(ancestor, cousin_parent) + create_source_pipeline(parent, pipeline) + create_source_pipeline(cousin_parent, cousin) + end + + it 'returns all family ids' do + expect(subject).to contain_exactly( + ancestor.id, parent.id, cousin_parent.id, cousin.id, pipeline.id + ) + end + end + + context 'when pipeline is a triggered pipeline' do + let(:upstream) { create(:ci_pipeline, project: create(:project)) } + + before do + create_source_pipeline(upstream, pipeline) + end + + it 'returns self id' do + expect(subject).to contain_exactly(pipeline.id) end end end @@ -2685,7 +2838,8 @@ RSpec.describe Ci::Pipeline, :mailer do end describe 'notifications when pipeline success or failed' do - let(:project) { create(:project, :repository) } + let(:namespace) { create(:namespace) } + let(:project) { create(:project, :repository, namespace: namespace) } let(:pipeline) do create(:ci_pipeline, @@ -2698,7 +2852,7 @@ RSpec.describe Ci::Pipeline, :mailer do project.add_developer(pipeline.user) pipeline.user.global_notification_setting - .update(level: 'custom', failed_pipeline: true, success_pipeline: true) + .update!(level: 'custom', failed_pipeline: true, success_pipeline: true) perform_enqueued_jobs do pipeline.enqueue @@ -2948,6 +3102,54 @@ RSpec.describe Ci::Pipeline, :mailer do end end + describe '#has_coverage_reports?' do + subject { pipeline.has_coverage_reports? } + + context 'when pipeline has a code coverage artifact' do + let(:pipeline) { create(:ci_pipeline, :with_coverage_report_artifact, :running, project: project) } + + it { expect(subject).to be_truthy } + end + + context 'when pipeline does not have a code coverage artifact' do + let(:pipeline) { create(:ci_pipeline, :success, project: project) } + + it { expect(subject).to be_falsey } + end + end + + describe '#can_generate_coverage_reports?' do + subject { pipeline.can_generate_coverage_reports? } + + context 'when pipeline has builds with coverage reports' do + before do + create(:ci_build, :coverage_reports, pipeline: pipeline, project: project) + end + + context 'when pipeline status is running' do + let(:pipeline) { create(:ci_pipeline, :running, project: project) } + + it { expect(subject).to be_falsey } + end + + context 'when pipeline status is success' do + let(:pipeline) { create(:ci_pipeline, :success, project: project) } + + it { expect(subject).to be_truthy } + end + end + + context 'when pipeline does not have builds with coverage reports' do + before do + create(:ci_build, :artifacts, pipeline: pipeline, project: project) + end + + let(:pipeline) { create(:ci_pipeline, :success, project: project) } + + it { expect(subject).to be_falsey } + end + end + describe '#test_report_summary' do subject { pipeline.test_report_summary } @@ -3228,7 +3430,8 @@ RSpec.describe Ci::Pipeline, :mailer do end describe '#parent_pipeline' do - let(:project) { create(:project) } + let_it_be(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } context 'when pipeline is triggered by a pipeline from the same project' do @@ -3283,7 +3486,7 @@ RSpec.describe Ci::Pipeline, :mailer do end describe '#child_pipelines' do - let(:project) { create(:project) } + let_it_be(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } context 'when pipeline triggered other pipelines on same project' do @@ -3413,4 +3616,152 @@ RSpec.describe Ci::Pipeline, :mailer do it { is_expected.to eq(Gitlab::Git::TAG_REF_PREFIX + pipeline.source_ref.to_s) } end end + + describe "#builds_with_coverage" do + it 'returns builds with coverage only' do + rspec = create(:ci_build, name: 'rspec', coverage: 97.1, pipeline: pipeline) + jest = create(:ci_build, name: 'jest', coverage: 94.1, pipeline: pipeline) + karma = create(:ci_build, name: 'karma', coverage: nil, pipeline: pipeline) + + builds = pipeline.builds_with_coverage + + expect(builds).to include(rspec, jest) + expect(builds).not_to include(karma) + end + end + + describe '#base_and_ancestors' do + let(:same_project) { false } + + subject { pipeline.base_and_ancestors(same_project: same_project) } + + context 'when pipeline is not child nor parent' do + it 'returns just the pipeline itself' do + expect(subject).to contain_exactly(pipeline) + end + end + + context 'when pipeline is child' do + let(:parent) { create(:ci_pipeline, project: pipeline.project) } + let(:sibling) { create(:ci_pipeline, project: pipeline.project) } + + before do + create_source_pipeline(parent, pipeline) + create_source_pipeline(parent, sibling) + end + + it 'returns parent and self' do + expect(subject).to contain_exactly(parent, pipeline) + end + end + + context 'when pipeline is parent' do + let(:child) { create(:ci_pipeline, project: pipeline.project) } + + before do + create_source_pipeline(pipeline, child) + end + + it 'returns self' do + expect(subject).to contain_exactly(pipeline) + end + end + + context 'when pipeline is a child of a child pipeline' do + let(:ancestor) { create(:ci_pipeline, project: pipeline.project) } + let(:parent) { create(:ci_pipeline, project: pipeline.project) } + + before do + create_source_pipeline(ancestor, parent) + create_source_pipeline(parent, pipeline) + end + + it 'returns self, parent and ancestor' do + expect(subject).to contain_exactly(ancestor, parent, pipeline) + end + end + + context 'when pipeline is a triggered pipeline' do + let(:upstream) { create(:ci_pipeline, project: create(:project)) } + + before do + create_source_pipeline(upstream, pipeline) + end + + context 'same_project: false' do + it 'returns upstream and self' do + expect(subject).to contain_exactly(pipeline, upstream) + end + end + + context 'same_project: true' do + let(:same_project) { true } + + it 'returns self' do + expect(subject).to contain_exactly(pipeline) + end + end + end + end + + describe 'reset_ancestor_bridges!' do + context 'when the pipeline is a child pipeline and the bridge is depended' do + let!(:parent_pipeline) { create(:ci_pipeline, project: project) } + let!(:bridge) { create_bridge(parent_pipeline, pipeline, true) } + + it 'marks source bridge as pending' do + pipeline.reset_ancestor_bridges! + + expect(bridge.reload).to be_pending + end + + context 'when the parent pipeline has a dependent upstream pipeline' do + let!(:upstream_bridge) do + create_bridge(create(:ci_pipeline, project: create(:project)), parent_pipeline, true) + end + + it 'marks all source bridges as pending' do + pipeline.reset_ancestor_bridges! + + expect(bridge.reload).to be_pending + expect(upstream_bridge.reload).to be_pending + end + end + end + + context 'when the pipeline is a child pipeline and the bridge is not depended' do + let!(:parent_pipeline) { create(:ci_pipeline, project: project) } + let!(:bridge) { create_bridge(parent_pipeline, pipeline, false) } + + it 'does not touch source bridge' do + pipeline.reset_ancestor_bridges! + + expect(bridge.reload).to be_success + end + + context 'when the parent pipeline has a dependent upstream pipeline' do + let!(:upstream_bridge) do + create_bridge(create(:ci_pipeline, project: create(:project)), parent_pipeline, true) + end + + it 'does not touch any source bridge' do + pipeline.reset_ancestor_bridges! + + expect(bridge.reload).to be_success + expect(upstream_bridge.reload).to be_success + end + end + end + + private + + def create_bridge(upstream, downstream, depend = false) + options = depend ? { trigger: { strategy: 'depend' } } : {} + + bridge = create(:ci_bridge, pipeline: upstream, status: 'success', options: options) + create(:ci_sources_pipeline, pipeline: downstream, source_job: bridge) + + bridge + end + end end diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb index 8bce3c10d8c..cb62646532c 100644 --- a/spec/models/ci/ref_spec.rb +++ b/spec/models/ci/ref_spec.rb @@ -16,51 +16,33 @@ RSpec.describe Ci::Ref do stub_const('Ci::PipelineSuccessUnlockArtifactsWorker', unlock_artifacts_worker_spy) end - context 'when keep latest artifact feature is enabled' do - before do - stub_feature_flags(keep_latest_artifacts_for_ref: true) - end - - where(:initial_state, :action, :count) do - :unknown | :succeed! | 1 - :unknown | :do_fail! | 0 - :success | :succeed! | 1 - :success | :do_fail! | 0 - :failed | :succeed! | 1 - :failed | :do_fail! | 0 - :fixed | :succeed! | 1 - :fixed | :do_fail! | 0 - :broken | :succeed! | 1 - :broken | :do_fail! | 0 - :still_failing | :succeed | 1 - :still_failing | :do_fail | 0 - end - - with_them do - context "when transitioning states" do - before do - status_value = Ci::Ref.state_machines[:status].states[initial_state].value - ci_ref.update!(status: status_value) - end - - it 'calls unlock artifacts service' do - ci_ref.send(action) - - expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times - end - end - end + where(:initial_state, :action, :count) do + :unknown | :succeed! | 1 + :unknown | :do_fail! | 0 + :success | :succeed! | 1 + :success | :do_fail! | 0 + :failed | :succeed! | 1 + :failed | :do_fail! | 0 + :fixed | :succeed! | 1 + :fixed | :do_fail! | 0 + :broken | :succeed! | 1 + :broken | :do_fail! | 0 + :still_failing | :succeed | 1 + :still_failing | :do_fail | 0 end - context 'when keep latest artifact feature is not enabled' do - before do - stub_feature_flags(keep_latest_artifacts_for_ref: false) - end + with_them do + context "when transitioning states" do + before do + status_value = Ci::Ref.state_machines[:status].states[initial_state].value + ci_ref.update!(status: status_value) + end - it 'does not call unlock artifacts service' do - ci_ref.succeed! + it 'calls unlock artifacts service' do + ci_ref.send(action) - expect(unlock_artifacts_worker_spy).not_to have_received(:perform_async) + expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times + end end end end @@ -125,8 +107,8 @@ RSpec.describe Ci::Ref do describe '#last_finished_pipeline_id' do let(:pipeline_status) { :running } - let(:config_source) { Ci::PipelineEnums.config_sources[:repository_source] } - let(:pipeline) { create(:ci_pipeline, pipeline_status, config_source: config_source) } + let(:pipeline_source) { Enums::Ci::Pipeline.sources[:push] } + let(:pipeline) { create(:ci_pipeline, pipeline_status, source: pipeline_source) } let(:ci_ref) { pipeline.ci_ref } context 'when there are no finished pipelines' do @@ -142,8 +124,8 @@ RSpec.describe Ci::Ref do expect(ci_ref.last_finished_pipeline_id).to eq(pipeline.id) end - context 'when the pipeline is not a ci_source' do - let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] } + context 'when the pipeline a dangling pipeline' do + let(:pipeline_source) { Enums::Ci::Pipeline.sources[:ondemand_dast_scan] } it 'returns nil' do expect(ci_ref.last_finished_pipeline_id).to be_nil diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 8247ebf1144..3e5d068d780 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -570,7 +570,7 @@ RSpec.describe Ci::Runner do let!(:last_update) { runner.ensure_runner_queue_value } before do - Ci::UpdateRunnerService.new(runner).update(description: 'new runner') + Ci::UpdateRunnerService.new(runner).update(description: 'new runner') # rubocop: disable Rails/SaveBang end it 'sets a new last_update value' do @@ -660,7 +660,7 @@ RSpec.describe Ci::Runner do before do runner.tick_runner_queue - runner.destroy + runner.destroy! end it 'cleans up the queue' do @@ -878,7 +878,7 @@ RSpec.describe Ci::Runner do it 'can be destroyed' do subject - expect { subject.destroy }.to change { described_class.count }.by(-1) + expect { subject.destroy! }.to change { described_class.count }.by(-1) end end |