a0101ebf84
Rename column in the database Rename fields related to import/export feature Rename API endpoints Rename documentation links Rename the rest of occurrences in the code Replace the images that contain the words "build succeeds" and docs referencing to them Make sure pipeline is green and nothing is missing. updated doc images renamed only_allow_merge_if_build_succeeds in projects and fixed references more updates fix some spec failures fix rubocop offences fix v3 api spec fix MR specs fixed issues with partials fix MR spec fix alignment add missing v3 to v4 doc wip - refactor v3 endpoints fix specs fix a few typos fix project specs copy entities fully to V3 fix entity error more fixes fix failing specs fixed missing entities in V3 API remove comment updated code based on feedback typo fix spec
390 lines
15 KiB
Ruby
390 lines
15 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Ci::ProcessPipelineService, :services do
|
|
let(:user) { create(:user) }
|
|
let(:project) { create(:empty_project) }
|
|
|
|
let(:pipeline) do
|
|
create(:ci_empty_pipeline, ref: 'master', project: project)
|
|
end
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
describe '#execute' do
|
|
context 'start queuing next builds' do
|
|
before do
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage_idx: 0)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'rspec', stage_idx: 1)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'rubocop', stage_idx: 1)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 2)
|
|
end
|
|
|
|
it 'processes a pipeline' do
|
|
expect(process_pipeline).to be_truthy
|
|
succeed_pending
|
|
expect(builds.success.count).to eq(2)
|
|
|
|
expect(process_pipeline).to be_truthy
|
|
succeed_pending
|
|
expect(builds.success.count).to eq(4)
|
|
|
|
expect(process_pipeline).to be_truthy
|
|
succeed_pending
|
|
expect(builds.success.count).to eq(5)
|
|
|
|
expect(process_pipeline).to be_falsey
|
|
end
|
|
|
|
it 'does not process pipeline if existing stage is running' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pending.count).to eq(2)
|
|
|
|
expect(process_pipeline).to be_falsey
|
|
expect(builds.pending.count).to eq(2)
|
|
end
|
|
end
|
|
|
|
context 'custom stage with first job allowed to fail' do
|
|
before do
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'clean_job', stage_idx: 0, allow_failure: true)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'test_job', stage_idx: 1, allow_failure: true)
|
|
end
|
|
|
|
it 'automatically triggers a next stage when build finishes' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:status)).to contain_exactly('pending')
|
|
|
|
pipeline.builds.running_or_pending.each(&:drop)
|
|
expect(builds.pluck(:status)).to contain_exactly('failed', 'pending')
|
|
end
|
|
end
|
|
|
|
context 'properly creates builds when "when" is defined' do
|
|
before do
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'build', stage_idx: 0)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'test', stage_idx: 1)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'test_failure', stage_idx: 2, when: 'on_failure')
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 3)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'production', stage_idx: 3, when: 'manual')
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'cleanup', stage_idx: 4, when: 'always')
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'clear cache', stage_idx: 4, when: 'manual')
|
|
end
|
|
|
|
context 'when builds are successful' do
|
|
it 'properly creates builds' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:name)).to contain_exactly('build')
|
|
expect(builds.pluck(:status)).to contain_exactly('pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
|
|
pipeline.reload
|
|
expect(pipeline.status).to eq('success')
|
|
end
|
|
end
|
|
|
|
context 'when test job fails' do
|
|
it 'properly creates builds' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:name)).to contain_exactly('build')
|
|
expect(builds.pluck(:status)).to contain_exactly('pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:drop)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
|
|
pipeline.reload
|
|
expect(pipeline.status).to eq('failed')
|
|
end
|
|
end
|
|
|
|
context 'when test and test_failure jobs fail' do
|
|
it 'properly creates builds' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:name)).to contain_exactly('build')
|
|
expect(builds.pluck(:status)).to contain_exactly('pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:drop)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:drop)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
|
|
pipeline.reload
|
|
expect(pipeline.status).to eq('failed')
|
|
end
|
|
end
|
|
|
|
context 'when deploy job fails' do
|
|
it 'properly creates builds' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:name)).to contain_exactly('build')
|
|
expect(builds.pluck(:status)).to contain_exactly('pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:drop)
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
|
|
pipeline.reload
|
|
expect(pipeline.status).to eq('failed')
|
|
end
|
|
end
|
|
|
|
context 'when build is canceled in the second stage' do
|
|
it 'does not schedule builds after build has been canceled' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:name)).to contain_exactly('build')
|
|
expect(builds.pluck(:status)).to contain_exactly('pending')
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.running_or_pending).not_to be_empty
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly('build', 'test')
|
|
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
|
|
pipeline.builds.running_or_pending.each(&:cancel)
|
|
|
|
expect(builds.running_or_pending).to be_empty
|
|
expect(pipeline.reload.status).to eq('canceled')
|
|
end
|
|
end
|
|
|
|
context 'when listing manual actions' do
|
|
it 'returns only for skipped builds' do
|
|
# currently all builds are created
|
|
expect(process_pipeline).to be_truthy
|
|
expect(manual_actions).to be_empty
|
|
|
|
# succeed stage build
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
expect(manual_actions).to be_empty
|
|
|
|
# succeed stage test
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
expect(manual_actions).to be_one # production
|
|
|
|
# succeed stage deploy
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
expect(manual_actions).to be_many # production and clear cache
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when there are manual/on_failure jobs in earlier stages' do
|
|
before do
|
|
builds
|
|
process_pipeline
|
|
builds.each(&:reload)
|
|
end
|
|
|
|
context 'when first stage has only manual jobs' do
|
|
let(:builds) do
|
|
[create_build('build', 0, 'manual'),
|
|
create_build('check', 1),
|
|
create_build('test', 2)]
|
|
end
|
|
|
|
it 'starts from the second stage' do
|
|
expect(builds.map(&:status)).to eq(%w[skipped pending created])
|
|
end
|
|
end
|
|
|
|
context 'when second stage has only manual jobs' do
|
|
let(:builds) do
|
|
[create_build('check', 0),
|
|
create_build('build', 1, 'manual'),
|
|
create_build('test', 2)]
|
|
end
|
|
|
|
it 'skips second stage and continues on third stage' do
|
|
expect(builds.map(&:status)).to eq(%w[pending created created])
|
|
|
|
builds.first.success
|
|
builds.each(&:reload)
|
|
|
|
expect(builds.map(&:status)).to eq(%w[success skipped pending])
|
|
end
|
|
end
|
|
|
|
context 'when second stage has only on_failure jobs' do
|
|
let(:builds) do
|
|
[create_build('check', 0),
|
|
create_build('build', 1, 'on_failure'),
|
|
create_build('test', 2)]
|
|
end
|
|
|
|
it 'skips second stage and continues on third stage' do
|
|
expect(builds.map(&:status)).to eq(%w[pending created created])
|
|
|
|
builds.first.success
|
|
builds.each(&:reload)
|
|
|
|
expect(builds.map(&:status)).to eq(%w[success skipped pending])
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when failed build in the middle stage is retried' do
|
|
context 'when failed build is the only unsuccessful build in the stage' do
|
|
before do
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'build:1', stage_idx: 0)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'build:2', stage_idx: 0)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'test:1', stage_idx: 1)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'test:2', stage_idx: 1)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'deploy:1', stage_idx: 2)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'deploy:2', stage_idx: 2)
|
|
end
|
|
|
|
it 'does trigger builds in the next stage' do
|
|
expect(process_pipeline).to be_truthy
|
|
expect(builds.pluck(:name)).to contain_exactly('build:1', 'build:2')
|
|
|
|
pipeline.builds.running_or_pending.each(&:success)
|
|
|
|
expect(builds.pluck(:name))
|
|
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
|
|
|
|
pipeline.builds.find_by(name: 'test:1').success
|
|
pipeline.builds.find_by(name: 'test:2').drop
|
|
|
|
expect(builds.pluck(:name))
|
|
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
|
|
|
|
Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).success
|
|
|
|
expect(builds.pluck(:name)).to contain_exactly(
|
|
'build:1', 'build:2', 'test:1', 'test:2', 'test:2', 'deploy:1', 'deploy:2')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when there are builds that are not created yet' do
|
|
let(:pipeline) do
|
|
create(:ci_pipeline, config: config)
|
|
end
|
|
|
|
let(:config) do
|
|
{ rspec: { stage: 'test', script: 'rspec' },
|
|
deploy: { stage: 'deploy', script: 'rsync' } }
|
|
end
|
|
|
|
before do
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0)
|
|
create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0)
|
|
end
|
|
|
|
it 'processes the pipeline' do
|
|
# Currently we have five builds with state created
|
|
#
|
|
expect(builds.count).to eq(0)
|
|
expect(all_builds.count).to eq(2)
|
|
|
|
# Process builds service will enqueue builds from the first stage.
|
|
#
|
|
process_pipeline
|
|
|
|
expect(builds.count).to eq(2)
|
|
expect(all_builds.count).to eq(2)
|
|
|
|
# When builds succeed we will enqueue remaining builds.
|
|
#
|
|
# We will have 2 succeeded, 1 pending (from stage test), total 4 (two
|
|
# additional build from `.gitlab-ci.yml`).
|
|
#
|
|
succeed_pending
|
|
process_pipeline
|
|
|
|
expect(builds.success.count).to eq(2)
|
|
expect(builds.pending.count).to eq(1)
|
|
expect(all_builds.count).to eq(4)
|
|
|
|
# When pending merge_when_pipeline_succeeds in stage test, we enqueue deploy stage.
|
|
#
|
|
succeed_pending
|
|
process_pipeline
|
|
|
|
expect(builds.pending.count).to eq(1)
|
|
expect(builds.success.count).to eq(3)
|
|
expect(all_builds.count).to eq(4)
|
|
|
|
# When the last one succeeds we have 4 successful builds.
|
|
#
|
|
succeed_pending
|
|
process_pipeline
|
|
|
|
expect(builds.success.count).to eq(4)
|
|
expect(all_builds.count).to eq(4)
|
|
end
|
|
end
|
|
end
|
|
|
|
def all_builds
|
|
pipeline.builds
|
|
end
|
|
|
|
def builds
|
|
all_builds.where.not(status: [:created, :skipped])
|
|
end
|
|
|
|
def process_pipeline
|
|
described_class.new(pipeline.project, user).execute(pipeline)
|
|
end
|
|
|
|
def succeed_pending
|
|
builds.pending.update_all(status: 'success')
|
|
end
|
|
|
|
delegate :manual_actions, to: :pipeline
|
|
|
|
def create_build(name, stage_idx, when_value = nil)
|
|
create(:ci_build,
|
|
:created,
|
|
pipeline: pipeline,
|
|
name: name,
|
|
stage_idx: stage_idx,
|
|
when: when_value)
|
|
end
|
|
end
|