diff options
Diffstat (limited to 'spec/services/ci/retry_job_service_spec.rb')
-rw-r--r-- | spec/services/ci/retry_job_service_spec.rb | 284 |
1 files changed, 217 insertions, 67 deletions
diff --git a/spec/services/ci/retry_job_service_spec.rb b/spec/services/ci/retry_job_service_spec.rb index 69f19c5acf2..540e700efa6 100644 --- a/spec/services/ci/retry_job_service_spec.rb +++ b/spec/services/ci/retry_job_service_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe Ci::RetryJobService do + using RSpec::Parameterized::TableSyntax let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:project) { create(:project, :repository) } @@ -11,11 +12,11 @@ RSpec.describe Ci::RetryJobService do end let_it_be(:stage) do - create(:ci_stage, project: project, - pipeline: pipeline, - name: 'test') + create(:ci_stage, pipeline: pipeline, name: 'test') end + let_it_be(:deploy_stage) { create(:ci_stage, pipeline: pipeline, name: 'deploy', position: stage.position + 1) } + let(:job_variables_attributes) { [{ key: 'MANUAL_VAR', value: 'manual test var' }] } let(:user) { developer } @@ -26,24 +27,11 @@ RSpec.describe Ci::RetryJobService do project.add_reporter(reporter) end - shared_context 'retryable bridge' do - let_it_be(:downstream_project) { create(:project, :repository) } - - let_it_be_with_refind(:job) do - create(:ci_bridge, :success, - pipeline: pipeline, downstream: downstream_project, description: 'a trigger job', ci_stage: stage - ) - end - - let_it_be(:job_to_clone) { job } - - before do - job.update!(retried: false) + shared_context 'retryable build' do + let_it_be_with_reload(:job) do + create(:ci_build, :success, pipeline: pipeline, ci_stage: stage) end - end - shared_context 'retryable build' do - let_it_be_with_refind(:job) { create(:ci_build, :success, pipeline: pipeline, ci_stage: stage) } let_it_be(:another_pipeline) { create(:ci_empty_pipeline, project: project) } let_it_be(:job_to_clone) do @@ -60,6 +48,12 @@ RSpec.describe Ci::RetryJobService do end end + shared_context 'with ci_retry_job_fix disabled' do + before do + stub_feature_flags(ci_retry_job_fix: false) + end + end + shared_examples_for 'clones the job' do let(:job) { job_to_clone } @@ -87,8 +81,7 @@ RSpec.describe Ci::RetryJobService do context 'when the job has needs' do before do - create(:ci_build_need, build: job, name: 'build1') - create(:ci_build_need, build: job, name: 'build2') + create_list(:ci_build_need, 2, build: job) end it 'bulk inserts all the needs' do @@ -123,16 +116,12 @@ RSpec.describe Ci::RetryJobService do end context 'when there are subsequent processables that are skipped' do - let_it_be(:stage) { create(:ci_stage, pipeline: pipeline, name: 'deploy') } - let!(:subsequent_build) do - create(:ci_build, :skipped, stage_idx: 2, - pipeline: pipeline, - ci_stage: stage) + create(:ci_build, :skipped, pipeline: pipeline, ci_stage: deploy_stage) end let!(:subsequent_bridge) do - create(:ci_bridge, :skipped, stage_idx: 2, pipeline: pipeline, ci_stage: stage) + create(:ci_bridge, :skipped, pipeline: pipeline, ci_stage: deploy_stage) end it 'resumes pipeline processing in the subsequent stage' do @@ -152,10 +141,9 @@ RSpec.describe Ci::RetryJobService do end context 'when the pipeline has other jobs' do - let!(:stage2) { create(:ci_stage, project: project, pipeline: pipeline, name: 'deploy') } - let!(:build2) { create(:ci_build, pipeline: pipeline, ci_stage: stage ) } - let!(:deploy) { create(:ci_build, pipeline: pipeline, ci_stage: stage2) } - let!(:deploy_needs_build2) { create(:ci_build_need, build: deploy, name: build2.name) } + let!(:other_test_build) { create(:ci_build, pipeline: pipeline, ci_stage: stage) } + let!(:deploy) { create(:ci_build, pipeline: pipeline, ci_stage: deploy_stage) } + let!(:deploy_needs_build2) { create(:ci_build_need, build: deploy, name: other_test_build.name) } context 'when job has a nil scheduling_type' do before do @@ -166,7 +154,7 @@ RSpec.describe Ci::RetryJobService do it 'populates scheduling_type of processables' do expect(new_job.scheduling_type).to eq('stage') expect(job.reload.scheduling_type).to eq('stage') - expect(build2.reload.scheduling_type).to eq('stage') + expect(other_test_build.reload.scheduling_type).to eq('stage') expect(deploy.reload.scheduling_type).to eq('dag') end end @@ -193,6 +181,13 @@ RSpec.describe Ci::RetryJobService do end end + shared_examples_for 'checks enqueue_immediately?' do + it "returns enqueue_immediately" do + subject + expect(new_job.enqueue_immediately?).to eq enqueue_immediately + end + end + describe '#clone!' do let(:new_job) { service.clone!(job) } @@ -200,20 +195,6 @@ RSpec.describe Ci::RetryJobService do expect { service.clone!(create(:ci_build).present) }.to raise_error(TypeError) end - context 'when the job to be cloned is a bridge' do - include_context 'retryable bridge' - - it_behaves_like 'clones the job' - - context 'when given variables' do - let(:new_job) { service.clone!(job, variables: job_variables_attributes) } - - it 'does not give variables to the new bridge' do - expect { new_job }.not_to raise_error - end - end - end - context 'when the job to be cloned is a build' do include_context 'retryable build' @@ -224,7 +205,7 @@ RSpec.describe Ci::RetryJobService do context 'when a build with a deployment is retried' do let!(:job) do create(:ci_build, :with_deployment, :deploy_to_production, - pipeline: pipeline, ci_stage: stage, project: project) + pipeline: pipeline, ci_stage: stage) end it 'creates a new deployment' do @@ -247,7 +228,6 @@ RSpec.describe Ci::RetryJobService do options: { environment: { name: environment_name } }, pipeline: pipeline, ci_stage: stage, - project: project, user: other_developer) end @@ -282,24 +262,44 @@ RSpec.describe Ci::RetryJobService do end end end - end - describe '#execute' do - let(:new_job) { service.execute(job)[:job] } + context 'when enqueue_if_actionable is provided' do + let!(:job) do + create(:ci_build, *[trait].compact, :failed, pipeline: pipeline, ci_stage: stage) + end - context 'when the job to be retried is a bridge' do - include_context 'retryable bridge' + let(:new_job) { subject } - it_behaves_like 'retries the job' + subject { service.clone!(job, enqueue_if_actionable: enqueue_if_actionable) } - context 'when given variables' do - let(:new_job) { service.clone!(job, variables: job_variables_attributes) } + where(:enqueue_if_actionable, :trait, :enqueue_immediately) do + true | nil | false + true | :manual | true + true | :expired_scheduled | true + + false | nil | false + false | :manual | false + false | :expired_scheduled | false + end + + with_them do + it_behaves_like 'checks enqueue_immediately?' - it 'does not give variables to the new bridge' do - expect { new_job }.not_to raise_error + context 'when feature flag is disabled' do + include_context 'with ci_retry_job_fix disabled' + + it_behaves_like 'checks enqueue_immediately?' do + let(:enqueue_immediately) { false } + end end end end + end + + describe '#execute' do + let(:new_job) { subject[:job] } + + subject { service.execute(job) } context 'when the job to be retried is a build' do include_context 'retryable build' @@ -307,24 +307,18 @@ RSpec.describe Ci::RetryJobService do it_behaves_like 'retries the job' context 'when there are subsequent jobs that are skipped' do - let_it_be(:stage) { create(:ci_stage, pipeline: pipeline, name: 'deploy') } - let!(:subsequent_build) do - create(:ci_build, :skipped, stage_idx: 2, - pipeline: pipeline, - stage_id: stage.id) + create(:ci_build, :skipped, pipeline: pipeline, ci_stage: deploy_stage) end let!(:subsequent_bridge) do - create(:ci_bridge, :skipped, stage_idx: 2, - pipeline: pipeline, - stage_id: stage.id) + create(:ci_bridge, :skipped, pipeline: pipeline, ci_stage: deploy_stage) end it 'does not cause an N+1 when updating the job ownership' do control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { service.execute(job) }.count - create_list(:ci_build, 2, :skipped, stage_idx: job.stage_idx + 1, pipeline: pipeline, stage_id: stage.id) + create_list(:ci_build, 2, :skipped, pipeline: pipeline, ci_stage: deploy_stage) expect { service.execute(job) }.not_to exceed_all_query_limit(control_count) end @@ -352,5 +346,161 @@ RSpec.describe Ci::RetryJobService do end end end + + context 'when job being retried has jobs in previous stages' do + let!(:job) do + create( + :ci_build, + :failed, + name: 'deploy_a', + pipeline: pipeline, + ci_stage: deploy_stage + ) + end + + before do + create( + :ci_build, + previous_stage_job_status, + name: 'test_a', + pipeline: pipeline, + ci_stage: stage + ) + end + + where(:previous_stage_job_status, :after_status) do + :created | 'created' + :pending | 'created' + :running | 'created' + :manual | 'created' + :scheduled | 'created' + :success | 'pending' + :failed | 'skipped' + :skipped | 'pending' + end + + with_them do + it 'updates the new job status to after_status' do + expect(subject).to be_success + expect(new_job.status).to eq after_status + end + + context 'when feature flag is disabled' do + include_context 'with ci_retry_job_fix disabled' + + it 'enqueues the new job' do + expect(subject).to be_success + expect(new_job).to be_pending + end + end + end + end + + context 'when job being retried has DAG dependencies' do + let!(:job) do + create( + :ci_build, + :failed, + :dependent, + name: 'deploy_a', + pipeline: pipeline, + ci_stage: deploy_stage, + needed: dependency + ) + end + + let(:dependency) do + create( + :ci_build, + dag_dependency_status, + name: 'test_a', + pipeline: pipeline, + ci_stage: stage + ) + end + + where(:dag_dependency_status, :after_status) do + :created | 'created' + :pending | 'created' + :running | 'created' + :manual | 'created' + :scheduled | 'created' + :success | 'pending' + :failed | 'skipped' + :skipped | 'skipped' + end + + with_them do + it 'updates the new job status to after_status' do + expect(subject).to be_success + expect(new_job.status).to eq after_status + end + + context 'when feature flag is disabled' do + include_context 'with ci_retry_job_fix disabled' + + it 'enqueues the new job' do + expect(subject).to be_success + expect(new_job).to be_pending + end + end + end + end + + context 'when there are other manual/scheduled jobs' do + let_it_be(:test_manual_build) do + create(:ci_build, :manual, pipeline: pipeline, ci_stage: stage) + end + + let_it_be(:subsequent_manual_build) do + create(:ci_build, :manual, pipeline: pipeline, ci_stage: deploy_stage) + end + + let_it_be(:test_scheduled_build) do + create(:ci_build, :scheduled, pipeline: pipeline, ci_stage: stage) + end + + let_it_be(:subsequent_scheduled_build) do + create(:ci_build, :scheduled, pipeline: pipeline, ci_stage: deploy_stage) + end + + let!(:job) do + create(:ci_build, *[trait].compact, :failed, pipeline: pipeline, ci_stage: stage) + end + + where(:trait, :enqueue_immediately) do + nil | false + :manual | true + :expired_scheduled | true + end + + with_them do + it 'retries the given job but not the other manual/scheduled jobs' do + expect { subject } + .to change { Ci::Build.count }.by(1) + .and not_change { test_manual_build.reload.status } + .and not_change { subsequent_manual_build.reload.status } + .and not_change { test_scheduled_build.reload.status } + .and not_change { subsequent_scheduled_build.reload.status } + + expect(new_job).to be_pending + end + + it_behaves_like 'checks enqueue_immediately?' + + context 'when feature flag is disabled' do + include_context 'with ci_retry_job_fix disabled' + + it 'enqueues the new job' do + expect(subject).to be_success + expect(new_job).to be_pending + end + + it_behaves_like 'checks enqueue_immediately?' do + let(:enqueue_immediately) { false } + end + end + end + end end end |