2019-03-30 07:23:56 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-07-04 13:31:15 +00:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-24 18:09:03 +00:00
|
|
|
RSpec.describe Ci::Stage, :models do
|
2020-05-08 12:09:37 +00:00
|
|
|
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
|
2021-04-06 18:09:02 +00:00
|
|
|
|
2020-05-08 12:09:37 +00:00
|
|
|
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
|
2018-12-03 12:47:26 +00:00
|
|
|
|
2018-12-04 10:24:21 +00:00
|
|
|
it_behaves_like 'having unique enum values'
|
|
|
|
|
2017-07-20 11:30:05 +00:00
|
|
|
describe 'associations' do
|
2017-07-20 11:05:26 +00:00
|
|
|
before do
|
|
|
|
create(:ci_build, stage_id: stage.id)
|
|
|
|
create(:commit_status, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
2017-08-16 11:30:49 +00:00
|
|
|
describe '#statuses' do
|
2017-07-20 11:05:26 +00:00
|
|
|
it 'returns all commit statuses' do
|
2017-08-16 11:30:49 +00:00
|
|
|
expect(stage.statuses.count).to be 2
|
2017-07-20 11:05:26 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#builds' do
|
|
|
|
it 'returns only builds' do
|
|
|
|
expect(stage.builds).to be_one
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-03-23 09:09:17 +00:00
|
|
|
describe '.by_name' do
|
|
|
|
it 'finds stages by name' do
|
|
|
|
a = create(:ci_stage_entity, name: 'a')
|
|
|
|
b = create(:ci_stage_entity, name: 'b')
|
|
|
|
c = create(:ci_stage_entity, name: 'c')
|
|
|
|
|
|
|
|
expect(described_class.by_name('a')).to contain_exactly(a)
|
|
|
|
expect(described_class.by_name('b')).to contain_exactly(b)
|
|
|
|
expect(described_class.by_name(%w[a c])).to contain_exactly(a, c)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-04 13:31:15 +00:00
|
|
|
describe '#status' do
|
|
|
|
context 'when stage is pending' do
|
|
|
|
let(:stage) { create(:ci_stage_entity, status: 'pending') }
|
|
|
|
|
|
|
|
it 'has a correct status value' do
|
|
|
|
expect(stage.status).to eq 'pending'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when stage is success' do
|
|
|
|
let(:stage) { create(:ci_stage_entity, status: 'success') }
|
|
|
|
|
|
|
|
it 'has a correct status value' do
|
|
|
|
expect(stage.status).to eq 'success'
|
|
|
|
end
|
|
|
|
end
|
2017-08-22 12:04:34 +00:00
|
|
|
|
|
|
|
context 'when stage status is not defined' do
|
|
|
|
before do
|
|
|
|
stage.update_column(:status, nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets the default value' do
|
|
|
|
expect(described_class.find(stage.id).status)
|
|
|
|
.to eq 'created'
|
|
|
|
end
|
|
|
|
end
|
2017-07-04 13:31:15 +00:00
|
|
|
end
|
2017-07-20 11:30:05 +00:00
|
|
|
|
2020-05-08 12:09:37 +00:00
|
|
|
describe '#set_status' do
|
|
|
|
where(:from_status, :to_status) do
|
|
|
|
from_status_names = described_class.state_machines[:status].states.map(&:name)
|
|
|
|
to_status_names = from_status_names - [:created] # we never want to transition into created
|
|
|
|
|
|
|
|
from_status_names.product(to_status_names)
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
it do
|
|
|
|
stage.status = from_status.to_s
|
|
|
|
|
|
|
|
if from_status != to_status
|
|
|
|
expect(stage.set_status(to_status.to_s))
|
|
|
|
.to eq(true)
|
|
|
|
else
|
|
|
|
expect(stage.set_status(to_status.to_s))
|
|
|
|
.to eq(false), "loopback transitions are not allowed"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-18 12:56:08 +00:00
|
|
|
describe '#update_status' do
|
2017-07-20 11:30:05 +00:00
|
|
|
context 'when stage objects needs to be updated' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :success, stage_id: stage.id)
|
|
|
|
create(:ci_build, :running, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates stage status correctly' do
|
2020-01-17 15:08:37 +00:00
|
|
|
expect { stage.update_legacy_status }
|
2017-07-20 11:30:05 +00:00
|
|
|
.to change { stage.reload.status }
|
2019-12-12 12:07:33 +00:00
|
|
|
.to eq 'running'
|
2017-07-20 11:30:05 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-25 10:43:27 +00:00
|
|
|
context 'when stage has only created builds' do
|
|
|
|
let(:stage) { create(:ci_stage_entity, status: :created) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
create(:ci_build, :created, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates status to skipped' do
|
|
|
|
expect(stage.reload.status).to eq 'created'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when stage is skipped because of skipped builds' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :skipped, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates status to skipped' do
|
2020-01-17 15:08:37 +00:00
|
|
|
expect { stage.update_legacy_status }
|
2018-05-25 10:43:27 +00:00
|
|
|
.to change { stage.reload.status }
|
2019-12-12 12:07:33 +00:00
|
|
|
.to eq 'skipped'
|
2018-05-25 10:43:27 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-26 11:21:36 +00:00
|
|
|
context 'when stage is scheduled because of scheduled builds' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :scheduled, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates status to scheduled' do
|
2020-01-17 15:08:37 +00:00
|
|
|
expect { stage.update_legacy_status }
|
2018-09-26 11:21:36 +00:00
|
|
|
.to change { stage.reload.status }
|
|
|
|
.to 'scheduled'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-24 12:08:01 +00:00
|
|
|
context 'when build is waiting for resource' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :waiting_for_resource, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates status to waiting for resource' do
|
2020-01-17 15:08:37 +00:00
|
|
|
expect { stage.update_legacy_status }
|
2019-12-24 12:08:01 +00:00
|
|
|
.to change { stage.reload.status }
|
|
|
|
.to 'waiting_for_resource'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-25 10:43:27 +00:00
|
|
|
context 'when stage is skipped because is empty' do
|
2017-07-24 09:22:01 +00:00
|
|
|
it 'updates status to skipped' do
|
2020-01-17 15:08:37 +00:00
|
|
|
expect { stage.update_legacy_status }
|
2017-07-24 09:22:01 +00:00
|
|
|
.to change { stage.reload.status }
|
2019-12-12 12:07:33 +00:00
|
|
|
.to eq('skipped')
|
2017-07-24 09:22:01 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-20 11:30:05 +00:00
|
|
|
context 'when stage object is locked' do
|
2017-07-24 09:22:01 +00:00
|
|
|
before do
|
|
|
|
create(:ci_build, :failed, stage_id: stage.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'retries a lock to update a stage status' do
|
|
|
|
stage.lock_version = 100
|
|
|
|
|
2020-01-17 15:08:37 +00:00
|
|
|
stage.update_legacy_status
|
2017-07-24 09:22:01 +00:00
|
|
|
|
|
|
|
expect(stage.reload).to be_failed
|
|
|
|
end
|
2017-07-20 11:30:05 +00:00
|
|
|
end
|
2018-06-04 09:29:39 +00:00
|
|
|
|
|
|
|
context 'when statuses status was not recognized' do
|
|
|
|
before do
|
|
|
|
allow(stage)
|
2019-10-01 12:05:59 +00:00
|
|
|
.to receive(:latest_stage_status)
|
2018-06-04 09:29:39 +00:00
|
|
|
.and_return(:unknown)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an exception' do
|
2020-01-17 15:08:37 +00:00
|
|
|
expect { stage.update_legacy_status }
|
2020-06-22 15:09:27 +00:00
|
|
|
.to raise_error(Ci::HasStatus::UnknownStatusError)
|
2018-06-04 09:29:39 +00:00
|
|
|
end
|
|
|
|
end
|
2017-07-20 11:30:05 +00:00
|
|
|
end
|
2018-04-18 12:56:08 +00:00
|
|
|
|
2018-05-25 10:47:56 +00:00
|
|
|
describe '#detailed_status' do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
let(:user) { create(:user) }
|
|
|
|
let(:stage) { create(:ci_stage_entity, status: :created) }
|
2019-12-19 00:08:01 +00:00
|
|
|
|
2018-05-25 10:47:56 +00:00
|
|
|
subject { stage.detailed_status(user) }
|
|
|
|
|
|
|
|
where(:statuses, :label) do
|
|
|
|
%w[created] | :created
|
|
|
|
%w[success] | :passed
|
|
|
|
%w[pending] | :pending
|
|
|
|
%w[skipped] | :skipped
|
|
|
|
%w[canceled] | :canceled
|
|
|
|
%w[success failed] | :failed
|
|
|
|
%w[running pending] | :running
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
before do
|
|
|
|
statuses.each do |status|
|
|
|
|
create(:commit_status, project: stage.project,
|
2018-05-25 11:47:22 +00:00
|
|
|
pipeline: stage.pipeline,
|
|
|
|
stage_id: stage.id,
|
|
|
|
status: status)
|
2018-05-25 10:47:56 +00:00
|
|
|
|
2020-01-17 15:08:37 +00:00
|
|
|
stage.update_legacy_status
|
2018-05-25 10:47:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has a correct label' do
|
2019-12-12 12:07:33 +00:00
|
|
|
expect(subject.label).to eq(label.to_s)
|
2018-05-25 10:47:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when stage has warnings' do
|
|
|
|
before do
|
2018-05-25 11:47:22 +00:00
|
|
|
create(:ci_build, project: stage.project,
|
|
|
|
pipeline: stage.pipeline,
|
|
|
|
stage_id: stage.id,
|
|
|
|
status: :failed,
|
|
|
|
allow_failure: true)
|
2018-05-25 10:47:56 +00:00
|
|
|
|
2020-01-17 15:08:37 +00:00
|
|
|
stage.update_legacy_status
|
2018-05-25 10:47:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'is passed with warnings' do
|
2019-12-12 12:07:33 +00:00
|
|
|
expect(subject.label).to eq s_('CiStatusLabel|passed with warnings')
|
2018-05-25 10:47:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#groups' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, stage_id: stage.id, name: 'rspec 0 1')
|
|
|
|
create(:ci_build, stage_id: stage.id, name: 'rspec 0 2')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'groups stage builds by name' do
|
|
|
|
expect(stage.groups).to be_one
|
|
|
|
expect(stage.groups.first.name).to eq 'rspec'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-10-04 14:15:28 +00:00
|
|
|
describe '#delay' do
|
|
|
|
subject { stage.delay }
|
2018-09-26 11:21:36 +00:00
|
|
|
|
|
|
|
let(:stage) { create(:ci_stage_entity, status: :created) }
|
|
|
|
|
|
|
|
it 'updates stage status' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(stage).to be_scheduled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-25 10:47:56 +00:00
|
|
|
describe '#position' do
|
2018-05-01 12:30:44 +00:00
|
|
|
context 'when stage has been imported and does not have position index set' do
|
2018-04-18 12:56:08 +00:00
|
|
|
before do
|
2018-05-01 12:30:44 +00:00
|
|
|
stage.update_column(:position, nil)
|
2018-04-24 09:18:12 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'when stage has statuses' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :running, stage_id: stage.id, stage_idx: 10)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'recalculates index before updating status' do
|
2018-05-01 12:30:44 +00:00
|
|
|
expect(stage.reload.position).to be_nil
|
2018-04-19 07:26:17 +00:00
|
|
|
|
2020-01-17 15:08:37 +00:00
|
|
|
stage.update_legacy_status
|
2018-04-24 09:18:12 +00:00
|
|
|
|
2018-05-01 12:30:44 +00:00
|
|
|
expect(stage.reload.position).to eq 10
|
2018-04-24 09:18:12 +00:00
|
|
|
end
|
2018-04-18 12:56:08 +00:00
|
|
|
end
|
|
|
|
|
2021-04-26 12:09:44 +00:00
|
|
|
context 'when stage has statuses with nil idx' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :running, stage_id: stage.id, stage_idx: nil)
|
|
|
|
create(:ci_build, :running, stage_id: stage.id, stage_idx: 10)
|
|
|
|
create(:ci_build, :running, stage_id: stage.id, stage_idx: nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets index to a non-empty value' do
|
|
|
|
expect { stage.update_legacy_status }.to change { stage.reload.position }.from(nil).to(10)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-24 09:18:12 +00:00
|
|
|
context 'when stage does not have statuses' do
|
|
|
|
it 'fallbacks to zero' do
|
2018-05-01 12:30:44 +00:00
|
|
|
expect(stage.reload.position).to be_nil
|
2018-04-18 12:56:08 +00:00
|
|
|
|
2020-01-17 15:08:37 +00:00
|
|
|
stage.update_legacy_status
|
2018-04-18 12:56:08 +00:00
|
|
|
|
2018-05-01 12:30:44 +00:00
|
|
|
expect(stage.reload.position).to eq 0
|
2018-04-24 09:18:12 +00:00
|
|
|
end
|
2018-04-18 12:56:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-05-25 10:47:56 +00:00
|
|
|
|
|
|
|
context 'when stage has warnings' do
|
|
|
|
before do
|
|
|
|
create(:ci_build, :failed, :allowed_to_fail, stage_id: stage.id)
|
2021-01-27 09:09:01 +00:00
|
|
|
create(:ci_bridge, :failed, :allowed_to_fail, stage_id: stage.id)
|
2018-05-25 10:47:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
describe '#has_warnings?' do
|
|
|
|
it 'returns true' do
|
|
|
|
expect(stage).to have_warnings
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#number_of_warnings' do
|
|
|
|
it 'returns a lazy stage warnings counter' do
|
|
|
|
lazy_queries = ActiveRecord::QueryRecorder.new do
|
|
|
|
stage.number_of_warnings
|
|
|
|
end
|
|
|
|
|
|
|
|
synced_queries = ActiveRecord::QueryRecorder.new do
|
|
|
|
stage.number_of_warnings.to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(lazy_queries.count).to eq 0
|
|
|
|
expect(synced_queries.count).to eq 1
|
|
|
|
|
|
|
|
expect(stage.number_of_warnings.inspect).to include 'BatchLoader'
|
2021-01-27 09:09:01 +00:00
|
|
|
expect(stage.number_of_warnings).to eq 2
|
2018-05-25 10:47:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when stage does not have warnings' do
|
|
|
|
describe '#has_warnings?' do
|
|
|
|
it 'returns false' do
|
|
|
|
expect(stage).not_to have_warnings
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-05-02 18:27:35 +00:00
|
|
|
|
|
|
|
it_behaves_like 'manual playable stage', :ci_stage_entity
|
2017-07-04 13:31:15 +00:00
|
|
|
end
|