2021-04-29 21:17:54 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: :continuous_integration do
|
2022-05-07 20:08:51 +05:30
|
|
|
let_it_be(:project) { create(:project, :empty_repo) }
|
2022-03-02 08:16:31 +05:30
|
|
|
let_it_be(:user) { project.first_owner }
|
2021-04-29 21:17:54 +05:30
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
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()
|
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
before_all do
|
|
|
|
project.repository.create_file(user, 'init', 'init', message: 'init', branch_name: 'master')
|
|
|
|
end
|
2021-04-29 21:17:54 +05:30
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
subject(:service) { described_class.new(project, user) }
|
2022-01-26 12:08:38 +05:30
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
shared_examples 'with a stage-dag mixed pipeline' do
|
2022-05-07 20:08:51 +05:30
|
|
|
let(:config) do
|
2023-03-04 22:38:38 +05:30
|
|
|
<<-YAML
|
2022-05-07 20:08:51 +05:30
|
|
|
stages: [a, b, c]
|
2022-01-26 12:08:38 +05:30
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
a1:
|
|
|
|
stage: a
|
|
|
|
script: exit $(($RANDOM % 2))
|
|
|
|
|
|
|
|
a2:
|
|
|
|
stage: a
|
|
|
|
script: exit 0
|
|
|
|
needs: [a1]
|
2022-01-26 12:08:38 +05:30
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
a3:
|
|
|
|
stage: a
|
|
|
|
script: exit 0
|
|
|
|
needs: [a2]
|
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
b1:
|
|
|
|
stage: b
|
|
|
|
script: exit 0
|
|
|
|
needs: []
|
2022-01-26 12:08:38 +05:30
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
b2:
|
|
|
|
stage: b
|
|
|
|
script: exit 0
|
|
|
|
needs: [a2]
|
|
|
|
|
|
|
|
c1:
|
|
|
|
stage: c
|
|
|
|
script: exit 0
|
|
|
|
needs: [b2]
|
|
|
|
|
|
|
|
c2:
|
|
|
|
stage: c
|
|
|
|
script: exit 0
|
2023-03-04 22:38:38 +05:30
|
|
|
YAML
|
2022-01-26 12:08:38 +05:30
|
|
|
end
|
2021-04-29 21:17:54 +05:30
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
before do
|
|
|
|
stub_ci_pipeline_yaml_file(config)
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'pending',
|
|
|
|
a2: 'created',
|
2022-07-23 23:45:48 +05:30
|
|
|
a3: 'created',
|
2022-05-07 20:08:51 +05:30
|
|
|
b1: 'pending',
|
|
|
|
b2: 'created',
|
|
|
|
c1: 'created',
|
|
|
|
c2: 'created'
|
|
|
|
)
|
|
|
|
|
|
|
|
b1.success!
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'pending',
|
|
|
|
a2: 'created',
|
2022-07-23 23:45:48 +05:30
|
|
|
a3: 'created',
|
2022-05-07 20:08:51 +05:30
|
|
|
b1: 'success',
|
|
|
|
b2: 'created',
|
|
|
|
c1: 'created',
|
|
|
|
c2: 'created'
|
|
|
|
)
|
|
|
|
|
|
|
|
a1.drop!
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'failed',
|
|
|
|
a2: 'skipped',
|
2022-07-23 23:45:48 +05:30
|
|
|
a3: 'skipped',
|
2022-05-07 20:08:51 +05:30
|
|
|
b1: 'success',
|
|
|
|
b2: 'skipped',
|
|
|
|
c1: 'skipped',
|
|
|
|
c2: 'skipped'
|
|
|
|
)
|
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
new_a1 = Ci::RetryJobService.new(project, user).clone!(a1)
|
2022-05-07 20:08:51 +05:30
|
|
|
new_a1.enqueue!
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'pending',
|
|
|
|
a2: 'skipped',
|
2022-07-23 23:45:48 +05:30
|
|
|
a3: 'skipped',
|
2022-05-07 20:08:51 +05:30
|
|
|
b1: 'success',
|
|
|
|
b2: 'skipped',
|
|
|
|
c1: 'skipped',
|
|
|
|
c2: 'skipped'
|
|
|
|
)
|
2021-09-30 23:02:18 +05:30
|
|
|
end
|
2022-01-26 12:08:38 +05:30
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
it 'marks subsequent skipped jobs as processable' do
|
2023-07-09 08:55:56 +05:30
|
|
|
service.execute(input_processables)
|
2022-05-07 20:08:51 +05:30
|
|
|
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'pending',
|
|
|
|
a2: 'created',
|
2022-10-11 01:57:18 +05:30
|
|
|
a3: 'created',
|
2022-05-07 20:08:51 +05:30
|
|
|
b1: 'success',
|
|
|
|
b2: 'created',
|
|
|
|
c1: 'created',
|
|
|
|
c2: 'created'
|
|
|
|
)
|
|
|
|
end
|
2022-07-23 23:45:48 +05:30
|
|
|
|
|
|
|
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' => [] }
|
|
|
|
)
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
service.execute(input_processables)
|
2022-07-23 23:45:48 +05:30
|
|
|
|
|
|
|
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'] },
|
2022-10-11 01:57:18 +05:30
|
|
|
{ 'name' => 'a3', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] },
|
2022-07-23 23:45:48 +05:30
|
|
|
{ '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
|
2021-09-30 23:02:18 +05:30
|
|
|
end
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
shared_examples 'with stage-dag mixed pipeline with some same-stage needs' do
|
2022-05-07 20:08:51 +05:30
|
|
|
let(:config) do
|
2023-03-04 22:38:38 +05:30
|
|
|
<<-YAML
|
2022-05-07 20:08:51 +05:30
|
|
|
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
|
2023-03-04 22:38:38 +05:30
|
|
|
YAML
|
2022-05-07 20:08:51 +05:30
|
|
|
end
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
before do
|
2022-05-07 20:08:51 +05:30
|
|
|
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'
|
|
|
|
)
|
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
new_a1 = Ci::RetryJobService.new(project, user).clone!(a1)
|
2022-05-07 20:08:51 +05:30
|
|
|
new_a1.enqueue!
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'pending',
|
|
|
|
a2: 'skipped',
|
|
|
|
b1: 'skipped',
|
|
|
|
b2: 'skipped',
|
|
|
|
c1: 'skipped',
|
|
|
|
c2: 'skipped'
|
|
|
|
)
|
2021-04-29 21:17:54 +05:30
|
|
|
end
|
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
it 'marks subsequent skipped jobs as processable' do
|
2023-07-09 08:55:56 +05:30
|
|
|
service.execute(input_processables)
|
2022-05-07 20:08:51 +05:30
|
|
|
|
|
|
|
check_jobs_statuses(
|
|
|
|
a1: 'pending',
|
|
|
|
a2: 'created',
|
|
|
|
b1: 'created',
|
|
|
|
b2: 'created',
|
|
|
|
c1: 'created',
|
|
|
|
c2: 'created'
|
|
|
|
)
|
|
|
|
end
|
2021-04-29 21:17:54 +05:30
|
|
|
end
|
2022-05-07 20:08:51 +05:30
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
shared_examples 'with same-stage needs' do
|
2022-10-11 01:57:18 +05:30
|
|
|
let(:config) do
|
2023-03-04 22:38:38 +05:30
|
|
|
<<-YAML
|
2023-07-09 08:55:56 +05:30
|
|
|
a1:
|
2022-10-11 01:57:18 +05:30
|
|
|
script: exit $(($RANDOM % 2))
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
b1:
|
2022-10-11 01:57:18 +05:30
|
|
|
script: exit 0
|
2023-07-09 08:55:56 +05:30
|
|
|
needs: [a1]
|
2022-10-11 01:57:18 +05:30
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
c1:
|
2022-10-11 01:57:18 +05:30
|
|
|
script: exit 0
|
2023-07-09 08:55:56 +05:30
|
|
|
needs: [b1]
|
2023-03-04 22:38:38 +05:30
|
|
|
YAML
|
2022-10-11 01:57:18 +05:30
|
|
|
end
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
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'
|
|
|
|
)
|
2022-10-11 01:57:18 +05:30
|
|
|
end
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
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
|
2022-10-11 01:57:18 +05:30
|
|
|
|
|
|
|
before do
|
|
|
|
stub_ci_pipeline_yaml_file(config)
|
|
|
|
check_jobs_statuses(
|
2023-07-09 08:55:56 +05:30
|
|
|
a1: 'pending',
|
|
|
|
a2: 'pending',
|
|
|
|
b1: 'created',
|
|
|
|
b2: 'created',
|
|
|
|
c1: 'created',
|
|
|
|
c2: 'created'
|
2022-10-11 01:57:18 +05:30
|
|
|
)
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
a1.drop!
|
|
|
|
a2.drop!
|
|
|
|
|
2022-10-11 01:57:18 +05:30
|
|
|
check_jobs_statuses(
|
2023-07-09 08:55:56 +05:30
|
|
|
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'
|
2022-10-11 01:57:18 +05:30
|
|
|
)
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
new_a2 = Ci::RetryJobService.new(project, user).clone!(a2)
|
|
|
|
new_a2.enqueue!
|
|
|
|
|
2022-10-11 01:57:18 +05:30
|
|
|
check_jobs_statuses(
|
2023-07-09 08:55:56 +05:30
|
|
|
a1: 'pending',
|
|
|
|
a2: 'pending',
|
|
|
|
b1: 'skipped',
|
|
|
|
b2: 'skipped',
|
|
|
|
c1: 'skipped',
|
|
|
|
c2: 'skipped'
|
2022-10-11 01:57:18 +05:30
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
# 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.
|
2022-10-11 01:57:18 +05:30
|
|
|
it 'marks subsequent skipped jobs as processable' do
|
2023-07-09 08:55:56 +05:30
|
|
|
service.execute(input_processables)
|
2022-10-11 01:57:18 +05:30
|
|
|
|
|
|
|
check_jobs_statuses(
|
2023-07-09 08:55:56 +05:30
|
|
|
a1: 'pending',
|
|
|
|
a2: 'pending',
|
|
|
|
b1: 'created',
|
|
|
|
b2: 'skipped',
|
|
|
|
c1: 'created',
|
|
|
|
c2: 'skipped'
|
2022-10-11 01:57:18 +05:30
|
|
|
)
|
|
|
|
end
|
2023-07-09 08:55:56 +05:30
|
|
|
|
|
|
|
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
|
2022-10-11 01:57:18 +05:30
|
|
|
end
|
|
|
|
|
2022-05-07 20:08:51 +05:30
|
|
|
private
|
|
|
|
|
|
|
|
def find_job(name)
|
|
|
|
processables.find_by!(name: name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_jobs_statuses(statuses)
|
|
|
|
expect(processables.order(:name).pluck(:name, :status)).to contain_exactly(*statuses.stringify_keys.to_a)
|
|
|
|
end
|
|
|
|
|
|
|
|
def processables
|
|
|
|
pipeline.processables.latest
|
|
|
|
end
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
def jobs_name_status_owner_needs
|
|
|
|
processables.reload.map do |job|
|
|
|
|
job.attributes.slice('name', 'status', 'user_id').merge('needs' => job.needs.map(&:name))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
# Remove this method when FF is `ci_support_reset_skipped_jobs_for_multiple_jobs` is removed
|
2022-05-07 20:08:51 +05:30
|
|
|
def execute_after_requeue_service(processable)
|
|
|
|
service.execute(processable)
|
|
|
|
end
|
2021-04-29 21:17:54 +05:30
|
|
|
end
|