diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index aa23b5cf97c142be111110b8ff974cb4aac09378..d14825c082ac8ba33ddd57fb02ac56e6e3a9c6b1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -104,6 +104,10 @@ module Ci statuses.select(:stage).distinct.count end + def stages_name + statuses.order(:stage_idx).distinct.pluck(:stage) + end + def stages status_sql = statuses.latest.where('stage=sg.stage').status_sql diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb index e0284d55da8d9ff05a68f0fb496ea2b7e95b3c6c..e50e54b6e99f89154ea1e880326d7db49cd8373b 100644 --- a/lib/gitlab/data_builder/pipeline.rb +++ b/lib/gitlab/data_builder/pipeline.rb @@ -22,7 +22,7 @@ module Gitlab sha: pipeline.sha, before_sha: pipeline.before_sha, status: pipeline.status, - stages: pipeline.stages.map(&:name), + stages: pipeline.stages_name, created_at: pipeline.created_at, finished_at: pipeline.finished_at, duration: pipeline.duration diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb new file mode 100644 index 0000000000000000000000000000000000000000..ee3b17b8bf1fe0b485b215b4a79b5ae41d5977e2 --- /dev/null +++ b/spec/factories/ci/stages.rb @@ -0,0 +1,13 @@ +FactoryGirl.define do + factory :ci_stage, class: Ci::Stage do + transient do + name 'test' + status nil + pipeline factory: :ci_empty_pipeline + end + + initialize_with do + Ci::Stage.new(pipeline, name: name, status: status) + end + end +end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index d6243940f2e5eaf37bc389c268d3b6aba3db811d..939ad2edf04d9527b057a30a9f934380952bd5b0 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do Gitlab::Ci::Status.const_get(core_status.capitalize)) end - it 'extends core status with common pipeline methods' do + it 'extends core status with common stage methods' do expect(status).to have_details expect(status).not_to have_action expect(status.details_path) @@ -45,7 +45,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do .to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings end - it 'extends core status with common pipeline methods' do + it 'extends core status with common stage methods' do expect(status).to have_details end end diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb index f15d604787882e8270ecdd7440a5a7ff3cc34dd7..f3259c6f23e0738cbba6c14f70a179065b1f0dd5 100644 --- a/spec/lib/gitlab/ci/status/stage/common_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Common do - let(:pipeline) { create(:ci_pipeline) } - let(:stage) { Ci::Stage.new(pipeline, name: 'test') } + let(:pipeline) { create(:ci_empty_pipeline) } + let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } subject do Class.new(Gitlab::Ci::Status::Core) diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 2d22bd1e2a05626dc9821721f7f77df458417925..17929665c836f11f924ba4dc906df0d04a1697c0 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Factory do - let(:pipeline) { create(:ci_pipeline) } - let(:stage) { Ci::Stage.new(pipeline, name: 'test') } + let(:pipeline) { create(:ci_empty_pipeline) } + let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } subject do described_class.new(stage) @@ -15,8 +15,10 @@ describe Gitlab::Ci::Status::Stage::Factory do context 'when stage has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do - let!(:build) do + before do create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) + create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status) + create(:ci_build, pipeline: pipeline, stage: 'build', status: :failed) end it "fabricates a core status #{core_status}" do @@ -24,7 +26,7 @@ describe Gitlab::Ci::Status::Stage::Factory do Gitlab::Ci::Status.const_get(core_status.capitalize)) end - it 'extends core status with common pipeline methods' do + it 'extends core status with common stage methods' do expect(status).to have_details expect(status.details_path).to include "pipelines/#{pipeline.id}" expect(status.details_path).to include "##{stage.name}" diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index cdc858c13b4be84d759cf9f1bb28e059b008814e..8158e71dd55aa92572efbafc75f0e77fe53701fa 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -142,6 +142,10 @@ describe Ci::Pipeline, models: true do expect(pipeline.stages_count).to eq(3) end + it 'returns a valid names of stages' do + expect(pipeline.stages_name).to eq(['build', 'test', 'deploy']) + end + context 'stages with statuses' do let(:statuses) do subject.map do |stage| diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f232761dba282b636ff4f8c75d900a9c02cb00f7 --- /dev/null +++ b/spec/models/ci/stage_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe Ci::Stage, models: true do + let(:stage) { build(:ci_stage) } + let(:pipeline) { stage.pipeline } + let(:stage_name) { stage.name } + + describe '#expectations' do + subject { stage } + + it { is_expected.to include_module(StaticModel) } + + it { is_expected.to respond_to(:pipeline) } + it { is_expected.to respond_to(:name) } + + it { is_expected.to delegate_method(:project).to(:pipeline) } + end + + describe '#statuses' do + let!(:stage_build) { create_job(:ci_build) } + let!(:commit_status) { create_job(:commit_status) } + let!(:other_build) { create_job(:ci_build, stage: 'other stage') } + + subject { stage.statuses } + + it "returns only matching statuses" do + is_expected.to contain_exactly(stage_build, commit_status) + end + end + + describe '#builds' do + let!(:stage_build) { create_job(:ci_build) } + let!(:commit_status) { create_job(:commit_status) } + + subject { stage.builds } + + it "returns only builds" do + is_expected.to contain_exactly(stage_build) + end + end + + describe '#status' do + subject { stage.status } + + context 'if status is already defined' do + let(:stage) { build(:ci_stage, status: 'success') } + + it "returns defined status" do + is_expected.to eq('success') + end + end + + context 'if status has to be calculated' do + let!(:stage_build) { create_job(:ci_build, status: :failed) } + + it "returns status of a build" do + is_expected.to eq('failed') + end + + context 'and builds are retried' do + let!(:new_build) { create_job(:ci_build, status: :success) } + + it "returns status of latest build" do + is_expected.to eq('success') + end + end + end + end + + describe '#detailed_status' do + subject { stage.detailed_status } + + context 'when build is created' do + let!(:stage_build) { create_job(:ci_build, status: :created) } + + it 'returns detailed status for created stage' do + expect(subject.text).to eq 'created' + end + end + + context 'when build is pending' do + let!(:stage_build) { create_job(:ci_build, status: :pending) } + + it 'returns detailed status for pending stage' do + expect(subject.text).to eq 'pending' + end + end + + context 'when build is running' do + let!(:stage_build) { create_job(:ci_build, status: :running) } + + it 'returns detailed status for running stage' do + expect(subject.text).to eq 'running' + end + end + + context 'when build is successful' do + let!(:stage_build) { create_job(:ci_build, status: :success) } + + it 'returns detailed status for successful stage' do + expect(subject.text).to eq 'passed' + end + end + + context 'when build is failed' do + let!(:stage_build) { create_job(:ci_build, status: :failed) } + + it 'returns detailed status for failed stage' do + expect(subject.text).to eq 'failed' + end + end + + context 'when build is canceled' do + let!(:stage_build) { create_job(:ci_build, status: :canceled) } + + it 'returns detailed status for canceled stage' do + expect(subject.text).to eq 'canceled' + end + end + + context 'when build is skipped' do + let!(:stage_build) { create_job(:ci_build, status: :skipped) } + + it 'returns detailed status for skipped stage' do + expect(subject.text).to eq 'skipped' + end + end + end + + def create_job(type, status: 'success', stage: stage_name) + create(type, pipeline: pipeline, stage: stage, status: status) + end +end