diff options
Diffstat (limited to 'spec/services/ci/reset_skipped_jobs_service_spec.rb')
-rw-r--r-- | spec/services/ci/reset_skipped_jobs_service_spec.rb | 487 |
1 files changed, 444 insertions, 43 deletions
diff --git a/spec/services/ci/reset_skipped_jobs_service_spec.rb b/spec/services/ci/reset_skipped_jobs_service_spec.rb index 712a21e665b..ba6a4a4e822 100644 --- a/spec/services/ci/reset_skipped_jobs_service_spec.rb +++ b/spec/services/ci/reset_skipped_jobs_service_spec.rb @@ -6,13 +6,22 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:user) { project.first_owner } + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a1) { find_job('a1') } + let(:a2) { find_job('a2') } + let(:b1) { find_job('b1') } + let(:input_processables) { a1 } # This is the input used when running service.execute() + before_all do project.repository.create_file(user, 'init', 'init', message: 'init', branch_name: 'master') end subject(:service) { described_class.new(project, user) } - context 'with a stage-dag mixed pipeline' do + shared_examples 'with a stage-dag mixed pipeline' do let(:config) do <<-YAML stages: [a, b, c] @@ -52,13 +61,6 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : YAML end - let(:pipeline) do - Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload - end - - let(:a1) { find_job('a1') } - let(:b1) { find_job('b1') } - before do stub_ci_pipeline_yaml_file(config) check_jobs_statuses( @@ -107,7 +109,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end it 'marks subsequent skipped jobs as processable' do - execute_after_requeue_service(a1) + service.execute(input_processables) check_jobs_statuses( a1: 'pending', @@ -135,7 +137,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : { 'name' => 'c2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => [] } ) - execute_after_requeue_service(a1) + service.execute(input_processables) expect(jobs_name_status_owner_needs).to contain_exactly( { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, @@ -150,7 +152,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end end - context 'with stage-dag mixed pipeline with some same-stage needs' do + shared_examples 'with stage-dag mixed pipeline with some same-stage needs' do let(:config) do <<-YAML stages: [a, b, c] @@ -184,12 +186,6 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : YAML end - let(:pipeline) do - Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload - end - - let(:a1) { find_job('a1') } - before do stub_ci_pipeline_yaml_file(config) check_jobs_statuses( @@ -224,7 +220,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end it 'marks subsequent skipped jobs as processable' do - execute_after_requeue_service(a1) + service.execute(input_processables) check_jobs_statuses( a1: 'pending', @@ -237,61 +233,465 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end end - context 'with same-stage needs' do + shared_examples 'with same-stage needs' do let(:config) do <<-YAML - a: + a1: script: exit $(($RANDOM % 2)) - b: + b1: script: exit 0 - needs: [a] + needs: [a1] - c: + c1: script: exit 0 - needs: [b] + needs: [b1] YAML end - let(:pipeline) do - Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a1: 'pending', + b1: 'created', + c1: 'created' + ) + + a1.drop! + check_jobs_statuses( + a1: 'failed', + b1: 'skipped', + c1: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + check_jobs_statuses( + a1: 'pending', + b1: 'skipped', + c1: 'skipped' + ) end - let(:a) { find_job('a') } + it 'marks subsequent skipped jobs as processable' do + service.execute(input_processables) + + check_jobs_statuses( + a1: 'pending', + b1: 'created', + c1: 'created' + ) + end + end + + context 'with same-stage needs where the parent jobs do not share the same descendants' do + let(:config) do + <<-YAML + a1: + script: exit $(($RANDOM % 2)) + + a2: + script: exit $(($RANDOM % 2)) + + b1: + script: exit 0 + needs: [a1] + + b2: + script: exit 0 + needs: [a2] + + c1: + script: exit 0 + needs: [b1] + + c2: + script: exit 0 + needs: [b2] + YAML + end before do stub_ci_pipeline_yaml_file(config) check_jobs_statuses( - a: 'pending', - b: 'created', - c: 'created' + a1: 'pending', + a2: 'pending', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' ) - a.drop! + a1.drop! + a2.drop! + check_jobs_statuses( - a: 'failed', - b: 'skipped', - c: 'skipped' + a1: 'failed', + a2: 'failed', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + + check_jobs_statuses( + a1: 'pending', + a2: 'failed', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' ) - new_a = Ci::RetryJobService.new(project, user).clone!(a) - new_a.enqueue! + new_a2 = Ci::RetryJobService.new(project, user).clone!(a2) + new_a2.enqueue! + check_jobs_statuses( - a: 'pending', - b: 'skipped', - c: 'skipped' + a1: 'pending', + a2: 'pending', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' ) end + # This demonstrates that when only a1 is inputted, only the *1 subsequent jobs are reset. + # This is in contrast to the following example when both a1 and a2 are inputted. it 'marks subsequent skipped jobs as processable' do - execute_after_requeue_service(a) + service.execute(input_processables) check_jobs_statuses( - a: 'pending', - b: 'created', - c: 'created' + a1: 'pending', + a2: 'pending', + b1: 'created', + b2: 'skipped', + c1: 'created', + c2: 'skipped' ) end + + context 'when multiple processables are inputted' do + # When both a1 and a2 are inputted, all subsequent jobs are reset. + it 'marks subsequent skipped jobs as processable' do + input_processables = [a1, a2] + service.execute(input_processables) + + check_jobs_statuses( + a1: 'pending', + a2: 'pending', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' + ) + end + end + end + + context 'when a single processable is inputted' do + it_behaves_like 'with a stage-dag mixed pipeline' + it_behaves_like 'with stage-dag mixed pipeline with some same-stage needs' + it_behaves_like 'with same-stage needs' + end + + context 'when multiple processables are inputted' do + let(:input_processables) { [a1, b1] } + + it_behaves_like 'with a stage-dag mixed pipeline' + it_behaves_like 'with stage-dag mixed pipeline with some same-stage needs' + it_behaves_like 'with same-stage needs' + end + + context 'when FF is `ci_support_reset_skipped_jobs_for_multiple_jobs` disabled' do + before do + stub_feature_flags(ci_support_reset_skipped_jobs_for_multiple_jobs: false) + end + + context 'with a stage-dag mixed pipeline' do + let(:config) do + <<-YAML + stages: [a, b, c] + + a1: + stage: a + script: exit $(($RANDOM % 2)) + + a2: + stage: a + script: exit 0 + needs: [a1] + + a3: + stage: a + script: exit 0 + needs: [a2] + + b1: + stage: b + script: exit 0 + needs: [] + + b2: + stage: b + script: exit 0 + needs: [a2] + + c1: + stage: c + script: exit 0 + needs: [b2] + + c2: + stage: c + script: exit 0 + YAML + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a1) { find_job('a1') } + let(:b1) { find_job('b1') } + + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a1: 'pending', + a2: 'created', + a3: 'created', + b1: 'pending', + b2: 'created', + c1: 'created', + c2: 'created' + ) + + b1.success! + check_jobs_statuses( + a1: 'pending', + a2: 'created', + a3: 'created', + b1: 'success', + b2: 'created', + c1: 'created', + c2: 'created' + ) + + a1.drop! + check_jobs_statuses( + a1: 'failed', + a2: 'skipped', + a3: 'skipped', + b1: 'success', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + check_jobs_statuses( + a1: 'pending', + a2: 'skipped', + a3: 'skipped', + b1: 'success', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + end + + it 'marks subsequent skipped jobs as processable' do + execute_after_requeue_service(a1) + + check_jobs_statuses( + a1: 'pending', + a2: 'created', + a3: 'created', + b1: 'success', + b2: 'created', + c1: 'created', + c2: 'created' + ) + end + + context 'when executed by a different user than the original owner' do + let(:retryer) { create(:user).tap { |u| project.add_maintainer(u) } } + let(:service) { described_class.new(project, retryer) } + + it 'reassigns jobs with updated statuses to the retryer' do + expect(jobs_name_status_owner_needs).to contain_exactly( + { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'a2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a1'] }, + { 'name' => 'a3', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'b2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'c1', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['b2'] }, + { 'name' => 'c2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => [] } + ) + + execute_after_requeue_service(a1) + + expect(jobs_name_status_owner_needs).to contain_exactly( + { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'a2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a1'] }, + { 'name' => 'a3', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] }, + { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'b2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] }, + { 'name' => 'c1', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['b2'] }, + { 'name' => 'c2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => [] } + ) + end + end + end + + context 'with stage-dag mixed pipeline with some same-stage needs' do + let(:config) do + <<-YAML + stages: [a, b, c] + + a1: + stage: a + script: exit $(($RANDOM % 2)) + + a2: + stage: a + script: exit 0 + needs: [a1] + + b1: + stage: b + script: exit 0 + needs: [b2] + + b2: + stage: b + script: exit 0 + + c1: + stage: c + script: exit 0 + needs: [b2] + + c2: + stage: c + script: exit 0 + YAML + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a1) { find_job('a1') } + + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a1: 'pending', + a2: 'created', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' + ) + + a1.drop! + check_jobs_statuses( + a1: 'failed', + a2: 'skipped', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + check_jobs_statuses( + a1: 'pending', + a2: 'skipped', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + end + + it 'marks subsequent skipped jobs as processable' do + execute_after_requeue_service(a1) + + check_jobs_statuses( + a1: 'pending', + a2: 'created', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' + ) + end + end + + context 'with same-stage needs' do + let(:config) do + <<-YAML + a: + script: exit $(($RANDOM % 2)) + + b: + script: exit 0 + needs: [a] + + c: + script: exit 0 + needs: [b] + YAML + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a) { find_job('a') } + + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a: 'pending', + b: 'created', + c: 'created' + ) + + a.drop! + check_jobs_statuses( + a: 'failed', + b: 'skipped', + c: 'skipped' + ) + + new_a = Ci::RetryJobService.new(project, user).clone!(a) + new_a.enqueue! + check_jobs_statuses( + a: 'pending', + b: 'skipped', + c: 'skipped' + ) + end + + it 'marks subsequent skipped jobs as processable' do + execute_after_requeue_service(a) + + check_jobs_statuses( + a: 'pending', + b: 'created', + c: 'created' + ) + end + end end private @@ -314,6 +714,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end end + # Remove this method when FF is `ci_support_reset_skipped_jobs_for_multiple_jobs` is removed def execute_after_requeue_service(processable) service.execute(processable) end |