summaryrefslogtreecommitdiff
path: root/spec/services/ci/reset_skipped_jobs_service_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/services/ci/reset_skipped_jobs_service_spec.rb')
-rw-r--r--spec/services/ci/reset_skipped_jobs_service_spec.rb487
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