diff options
Diffstat (limited to 'spec/models/ci/build_spec.rb')
-rw-r--r-- | spec/models/ci/build_spec.rb | 383 |
1 files changed, 322 insertions, 61 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 66d2f5f4ee9..62dec522161 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -111,10 +111,6 @@ RSpec.describe Ci::Build do describe '.with_downloadable_artifacts' do subject { described_class.with_downloadable_artifacts } - before do - stub_feature_flags(drop_license_management_artifact: false) - end - context 'when job does not have a downloadable artifact' do let!(:job) { create(:ci_build) } @@ -320,11 +316,23 @@ RSpec.describe Ci::Build do end end + describe '#stick_build_if_status_changed' do + it 'sticks the build if the status changed' do + job = create(:ci_build, :pending) + + allow(Gitlab::Database::LoadBalancing).to receive(:enable?) + .and_return(true) + + expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:stick) + .with(:build, job.id) + + job.update!(status: :running) + end + end + describe '#enqueue' do let(:build) { create(:ci_build, :created) } - subject { build.enqueue } - before do allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites) allow(Ci::PrepareBuildService).to receive(:perform_async) @@ -334,28 +342,74 @@ RSpec.describe Ci::Build do let(:has_prerequisites) { true } it 'transitions to preparing' do - subject + build.enqueue expect(build).to be_preparing end + + it 'does not push build to the queue' do + build.enqueue + + expect(build.queuing_entry).not_to be_present + end end context 'build has no prerequisites' do let(:has_prerequisites) { false } it 'transitions to pending' do - subject + build.enqueue expect(build).to be_pending end + + it 'pushes build to a queue' do + build.enqueue + + expect(build.queuing_entry).to be_present + end + + context 'when build status transition fails' do + before do + ::Ci::Build.find(build.id).update_column(:lock_version, 100) + end + + it 'does not push build to a queue' do + expect { build.enqueue! } + .to raise_error(ActiveRecord::StaleObjectError) + + expect(build.queuing_entry).not_to be_present + end + end + + context 'when there is a queuing entry already present' do + before do + ::Ci::PendingBuild.create!(build: build, project: build.project) + end + + it 'does not raise an error' do + expect { build.enqueue! }.not_to raise_error + expect(build.reload.queuing_entry).to be_present + end + end + + context 'when both failure scenario happen at the same time' do + before do + ::Ci::Build.find(build.id).update_column(:lock_version, 100) + ::Ci::PendingBuild.create!(build: build, project: build.project) + end + + it 'raises stale object error exception' do + expect { build.enqueue! } + .to raise_error(ActiveRecord::StaleObjectError) + end + end end end describe '#enqueue_preparing' do let(:build) { create(:ci_build, :preparing) } - subject { build.enqueue_preparing } - before do allow(build).to receive(:any_unmet_prerequisites?).and_return(has_unmet_prerequisites) end @@ -364,9 +418,10 @@ RSpec.describe Ci::Build do let(:has_unmet_prerequisites) { false } it 'transitions to pending' do - subject + build.enqueue_preparing expect(build).to be_pending + expect(build.queuing_entry).to be_present end end @@ -374,9 +429,10 @@ RSpec.describe Ci::Build do let(:has_unmet_prerequisites) { true } it 'remains in preparing' do - subject + build.enqueue_preparing expect(build).to be_preparing + expect(build.queuing_entry).not_to be_present end end end @@ -405,6 +461,64 @@ RSpec.describe Ci::Build do end end + describe '#run' do + context 'when build has been just created' do + let(:build) { create(:ci_build, :created) } + + it 'creates queuing entry and then removes it' do + build.enqueue! + expect(build.queuing_entry).to be_present + + build.run! + expect(build.reload.queuing_entry).not_to be_present + end + end + + context 'when build status transition fails' do + let(:build) { create(:ci_build, :pending) } + + before do + ::Ci::PendingBuild.create!(build: build, project: build.project) + ::Ci::Build.find(build.id).update_column(:lock_version, 100) + end + + it 'does not remove build from a queue' do + expect { build.run! } + .to raise_error(ActiveRecord::StaleObjectError) + + expect(build.queuing_entry).to be_present + end + end + + context 'when build has been picked by a shared runner' do + let(:build) { create(:ci_build, :pending) } + + it 'creates runtime metadata entry' do + build.runner = create(:ci_runner, :instance_type) + + build.run! + + expect(build.reload.runtime_metadata).to be_present + end + end + end + + describe '#drop' do + context 'when has a runtime tracking entry' do + let(:build) { create(:ci_build, :pending) } + + it 'removes runtime tracking entry' do + build.runner = create(:ci_runner, :instance_type) + + build.run! + expect(build.reload.runtime_metadata).to be_present + + build.drop! + expect(build.reload.runtime_metadata).not_to be_present + end + end + end + describe '#schedulable?' do subject { build.schedulable? } @@ -586,28 +700,10 @@ RSpec.describe Ci::Build do end end - context 'with runners_cached_states feature flag enabled' do - before do - stub_feature_flags(runners_cached_states: true) - end - - it 'caches the result in Redis' do - expect(Rails.cache).to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute) - - build.any_runners_online? - end - end - - context 'with runners_cached_states feature flag disabled' do - before do - stub_feature_flags(runners_cached_states: false) - end - - it 'does not cache' do - expect(Rails.cache).not_to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute) + it 'caches the result in Redis' do + expect(Rails.cache).to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute) - build.any_runners_online? - end + build.any_runners_online? end end @@ -624,28 +720,10 @@ RSpec.describe Ci::Build do it { is_expected.to be_truthy } end - context 'with runners_cached_states feature flag enabled' do - before do - stub_feature_flags(runners_cached_states: true) - end - - it 'caches the result in Redis' do - expect(Rails.cache).to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute) - - build.any_runners_available? - end - end - - context 'with runners_cached_states feature flag disabled' do - before do - stub_feature_flags(runners_cached_states: false) - end - - it 'does not cache' do - expect(Rails.cache).not_to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute) + it 'caches the result in Redis' do + expect(Rails.cache).to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute) - build.any_runners_available? - end + build.any_runners_available? end end @@ -1650,8 +1728,6 @@ RSpec.describe Ci::Build do subject { build.erase_erasable_artifacts! } before do - stub_feature_flags(drop_license_management_artifact: false) - 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 @@ -1840,6 +1916,26 @@ RSpec.describe Ci::Build do it { is_expected.not_to be_retryable } end + + context 'when a canceled build has been retried already' do + before do + project.add_developer(user) + build.cancel! + described_class.retry(build, user) + end + + context 'when prevent_retry_of_retried_jobs feature flag is enabled' do + it { is_expected.not_to be_retryable } + end + + context 'when prevent_retry_of_retried_jobs feature flag is disabled' do + before do + stub_feature_flags(prevent_retry_of_retried_jobs: false) + end + + it { is_expected.to be_retryable } + end + end end end @@ -2525,7 +2621,6 @@ RSpec.describe Ci::Build do { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false }, { key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false }, { key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false }, - { key: 'CI_PROJECT_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false }, { key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false }, { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false }, { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false }, @@ -2566,6 +2661,17 @@ RSpec.describe Ci::Build do it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.to_runner_variables).to eq(predefined_variables) } + it 'excludes variables that require an environment or user' do + environment_based_variables_collection = subject.filter do |variable| + %w[ + YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG + CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL + ].include?(variable[:key]) + end + + expect(environment_based_variables_collection).to be_empty + end + context 'when ci_job_jwt feature flag is disabled' do before do stub_feature_flags(ci_job_jwt: false) @@ -2635,7 +2741,7 @@ RSpec.describe Ci::Build do let(:expected_variables) do predefined_variables.map { |variable| variable.fetch(:key) } + %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG - CI_ENVIRONMENT_URL] + CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL] end before do @@ -2653,6 +2759,50 @@ RSpec.describe Ci::Build do expect(received_variables).to eq expected_variables end + + describe 'CI_ENVIRONMENT_ACTION' do + let(:enviroment_action_variable) { subject.find { |variable| variable[:key] == 'CI_ENVIRONMENT_ACTION' } } + + shared_examples 'defaults value' do + it 'value matches start' do + expect(enviroment_action_variable[:value]).to eq('start') + end + end + + it_behaves_like 'defaults value' + + context 'when options is set' do + before do + build.update!(options: options) + end + + context 'when options is empty' do + let(:options) { {} } + + it_behaves_like 'defaults value' + end + + context 'when options is nil' do + let(:options) { nil } + + it_behaves_like 'defaults value' + end + + context 'when options environment is specified' do + let(:options) { { environment: {} } } + + it_behaves_like 'defaults value' + end + + context 'when options environment action specified' do + let(:options) { { environment: { action: 'stop' } } } + + it 'matches the specified action' do + expect(enviroment_action_variable[:value]).to eq('stop') + end + end + end + end end end end @@ -2691,7 +2841,8 @@ RSpec.describe Ci::Build do let(:environment_variables) do [ { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false }, - { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false } + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false }, + { key: 'CI_ENVIRONMENT_TIER', value: 'production', public: true, masked: false } ] end @@ -2700,6 +2851,7 @@ RSpec.describe Ci::Build do project: build.project, name: 'production', slug: 'prod-slug', + tier: 'production', external_url: '') end @@ -4693,7 +4845,7 @@ RSpec.describe Ci::Build do context 'with project services' do before do - create(:service, active: true, job_events: true, project: project) + create(:integration, active: true, job_events: true, project: project) end it 'executes services' do @@ -4707,7 +4859,7 @@ RSpec.describe Ci::Build do context 'without relevant project services' do before do - create(:service, active: true, job_events: false, project: project) + create(:integration, active: true, job_events: false, project: project) end it 'does not execute services' do @@ -4987,4 +5139,113 @@ RSpec.describe Ci::Build do it { is_expected.to be_truthy } end end + + describe '.build_matchers' do + let_it_be(:pipeline) { create(:ci_pipeline, :protected) } + + subject(:matchers) { pipeline.builds.build_matchers(pipeline.project) } + + context 'when the pipeline is empty' do + it 'does not throw errors' do + is_expected.to eq([]) + end + end + + context 'when the pipeline has builds' do + let_it_be(:build_without_tags) do + create(:ci_build, pipeline: pipeline) + end + + let_it_be(:build_with_tags) do + create(:ci_build, pipeline: pipeline, tag_list: %w[tag1 tag2]) + end + + let_it_be(:other_build_with_tags) do + create(:ci_build, pipeline: pipeline, tag_list: %w[tag2 tag1]) + end + + it { expect(matchers.size).to eq(2) } + + it 'groups build ids' do + expect(matchers.map(&:build_ids)).to match_array([ + [build_without_tags.id], + match_array([build_with_tags.id, other_build_with_tags.id]) + ]) + end + + it { expect(matchers.map(&:tag_list)).to match_array([[], %w[tag1 tag2]]) } + + it { expect(matchers.map(&:protected?)).to all be_falsey } + + context 'when the builds are protected' do + before do + pipeline.builds.update_all(protected: true) + end + + it { expect(matchers).to all be_protected } + end + end + end + + describe '#build_matcher' do + let_it_be(:build) do + build_stubbed(:ci_build, tag_list: %w[tag1 tag2]) + end + + subject(:matcher) { build.build_matcher } + + it { expect(matcher.build_ids).to eq([build.id]) } + + it { expect(matcher.tag_list).to match_array(%w[tag1 tag2]) } + + it { expect(matcher.protected?).to eq(build.protected?) } + + it { expect(matcher.project).to eq(build.project) } + end + + describe '#shared_runner_build?' do + context 'when build does not have a runner assigned' do + it 'is not a shared runner build' do + expect(build.runner).to be_nil + + expect(build).not_to be_shared_runner_build + end + end + + context 'when build has a project runner assigned' do + before do + build.runner = create(:ci_runner, :project) + end + + it 'is not a shared runner build' do + expect(build).not_to be_shared_runner_build + end + end + + context 'when build has an instance runner assigned' do + before do + build.runner = create(:ci_runner, :instance_type) + end + + it 'is a shared runner build' do + expect(build).to be_shared_runner_build + end + end + end + + describe '.without_coverage' do + let!(:build_with_coverage) { create(:ci_build, pipeline: pipeline, coverage: 100.0) } + + it 'returns builds without coverage values' do + expect(described_class.without_coverage).to eq([build]) + end + end + + describe '.with_coverage_regex' do + let!(:build_with_coverage_regex) { create(:ci_build, pipeline: pipeline, coverage_regex: '\d') } + + it 'returns builds with coverage regex values' do + expect(described_class.with_coverage_regex).to eq([build_with_coverage_regex]) + end + end end |