Commit 325e66d7 authored by Shinya Maeda's avatar Shinya Maeda Committed by Alessio Caiazza
Browse files

Add Spec for ProcessPipelineService

parent c349dd49d456
......@@ -80,7 +80,7 @@ class Pipeline < ActiveRecord::Base
state_machine :status, initial: :created do
event :enqueue do
transition [:created, :skipped] => :pending
transition [:created, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running
end
......
......@@ -2,13 +2,38 @@
module Ci
class ProcessBuildService < BaseService
def execute(build)
if build.schedulable?
build.schedule!
elsif build.action?
build.actionize
def execute(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status)
if build.schedulable?
build.schedule!
elsif build.action?
build.actionize
else
build.enqueue
end
true
else
build.enqueue
build.skip
false
end
end
private
def valid_statuses_for_when(value)
case value
when 'on_success'
%w[success skipped]
when 'on_failure'
%w[failed]
when 'always'
%w[success failed skipped]
when 'manual'
%w[success skipped]
when 'delayed'
%w[success skipped]
else
[]
end
end
end
......
......@@ -29,39 +29,13 @@ def process_stage(index)
if HasStatus::COMPLETED_STATUSES.include?(current_status)
created_builds_in_stage(index).select do |build|
Gitlab::OptimisticLocking.retry_lock(build) do |subject|
process_build(subject, current_status)
Ci::ProcessBuildService.new(project, @user)
.execute(build, current_status)
end
end
end
end
def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status)
Ci::ProcessBuildService.new(project, @user).execute(build)
true
else
build.skip
false
end
end
def valid_statuses_for_when(value)
case value
when 'on_success'
%w[success skipped]
when 'on_failure'
%w[failed]
when 'always'
%w[success failed skipped]
when 'manual'
%w[success skipped]
when 'delayed'
%w[success skipped]
else
[]
end
end
# rubocop: disable CodeReuse/ActiveRecord
def status_for_prior_stages(index)
pipeline.builds.where('stage_idx < ?', index).latest.status || 'success'
......
......@@ -242,6 +242,187 @@
end
end
context 'when delayed jobs are defined' do
context 'when the scene is timed incremental rollout' do
before do
create_build('build', stage_idx: 0)
create_build('rollout10%', **delayed_options, stage_idx: 1)
create_build('rollout100%', **delayed_options, stage_idx: 2)
create_build('cleanup', stage_idx: 3)
allow(Ci::BuildScheduleWorker).to receive(:perform_at)
end
context 'when builds are successful' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'build': 'pending' })
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
enqueue_scheduled('rollout10%')
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'scheduled' })
enqueue_scheduled('rollout100%')
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'success', 'cleanup': 'pending' })
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'success', 'cleanup': 'success' })
expect(pipeline.reload.status).to eq 'success'
end
end
context 'when build job fails' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'build': 'pending' })
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'build': 'failed' })
expect(pipeline.reload.status).to eq 'failed'
end
end
context 'when rollout 10% is unscheduled' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'build': 'pending' })
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
unschedule
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'manual' })
expect(pipeline.reload.status).to eq 'manual'
end
context 'when user plays rollout 10%' do
it 'schedules rollout100%' do
process_pipeline
succeed_pending
unschedule
play_manual_action('rollout10%')
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'scheduled' })
expect(pipeline.reload.status).to eq 'scheduled'
end
end
end
context 'when rollout 10% fails' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'build': 'pending' })
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
enqueue_scheduled('rollout10%')
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'failed' })
expect(pipeline.reload.status).to eq 'failed'
end
context 'when user retries rollout 10%' do
it 'does not schedule rollout10% again' do
process_pipeline
succeed_pending
enqueue_scheduled('rollout10%')
fail_running_or_pending
retry_build('rollout10%')
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'pending' })
expect(pipeline.reload.status).to eq 'running'
end
end
end
context 'when rollout 10% is played immidiately' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'build': 'pending' })
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
play_manual_action('rollout10%')
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'pending' })
expect(pipeline.reload.status).to eq 'running'
end
end
end
context 'when only one scheduled job exists in a pipeline' do
before do
create_build('delayed', **delayed_options, stage_idx: 0)
allow(Ci::BuildScheduleWorker).to receive(:perform_at)
end
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed': 'scheduled' })
expect(pipeline.reload.status).to eq 'scheduled'
end
end
context 'when there are two delayed jobs in a stage' do
before do
create_build('delayed1', **delayed_options, stage_idx: 0)
create_build('delayed2', **delayed_options, stage_idx: 0)
create_build('job', stage_idx: 1)
allow(Ci::BuildScheduleWorker).to receive(:perform_at)
end
it 'blocks the stage until all scheduled jobs finished' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed1': 'scheduled', 'delayed2': 'scheduled' })
enqueue_scheduled('delayed1')
expect(builds_names_and_statuses).to eq({ 'delayed1': 'pending', 'delayed2': 'scheduled' })
expect(pipeline.reload.status).to eq 'scheduled'
end
end
context 'when a delayed job is allowed to fail' do
before do
create_build('delayed', **delayed_options, allow_failure: true, stage_idx: 0)
create_build('job', stage_idx: 1)
allow(Ci::BuildScheduleWorker).to receive(:perform_at)
end
it 'blocks the stage and continues after it failed' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed': 'scheduled' })
enqueue_scheduled('delayed')
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'delayed': 'failed', 'job': 'pending' })
expect(pipeline.reload.status).to eq 'pending'
end
end
end
context 'when there are manual action in earlier stages' do
context 'when first stage has only optional manual actions' do
before do
......@@ -536,6 +717,10 @@ def builds_names
builds.pluck(:name)
end
def builds_names_and_statuses
builds.inject({}) { |h, b| h[b.name.to_sym] = b.status; h }
end
def all_builds_names
all_builds.pluck(:name)
end
......@@ -549,7 +734,7 @@ def all_builds_statuses
end
def succeed_pending
builds.pending.update_all(status: 'success')
builds.pending.map(&:success)
end
def succeed_running_or_pending
......@@ -568,6 +753,14 @@ def play_manual_action(name)
builds.find_by(name: name).play(user)
end
def enqueue_scheduled(name)
builds.scheduled.find_by(name: name).enqueue
end
def retry_build(name)
Ci::Build.retry(builds.find_by(name: name), user)
end
def manual_actions
pipeline.manual_actions(true)
end
......@@ -575,4 +768,12 @@ def manual_actions
def create_build(name, **opts)
create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
end
def delayed_options
{ when: 'delayed', options: { start_in: '1 minute' } }
end
def unschedule
pipeline.builds.scheduled.map(&:unschedule)
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment