Commit 10d4b20a authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'pipeline-stage' into 'master'

Refine pipeline stages

## What does this MR do?

Introduces a concept of `Ci::Stage` to make it easier to have detailed statuses.

## Why was this MR needed?

This is needed to simplify the handling of `Ci::Statuses` and make the `Stage` actual concept in code:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7889

See merge request !7927
parents 30daf78d aba894b9
...@@ -21,8 +21,6 @@ module Ci ...@@ -21,8 +21,6 @@ module Ci
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
delegate :stages, to: :statuses
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition created: :pending transition created: :pending
...@@ -98,17 +96,35 @@ module Ci ...@@ -98,17 +96,35 @@ module Ci
sha[0...8] sha[0...8]
end end
def self.stages
# We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
CommitStatus.where(pipeline: pluck(:id)).stages
end
def self.total_duration def self.total_duration
where.not(duration: nil).sum(:duration) where.not(duration: nil).sum(:duration)
end end
def stages_with_latest_statuses def stages_count
statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage) statuses.select(:stage).distinct.count
end
def stages_name
statuses.order(:stage_idx).distinct.
pluck(:stage, :stage_idx).map(&:first)
end
def stages
status_sql = statuses.latest.where('stage=sg.stage').status_sql
stages_query = statuses.group('stage').select(:stage)
.order('max(stage_idx)')
stages_with_statuses = CommitStatus.from(stages_query, :sg).
pluck('sg.stage', status_sql)
stages_with_statuses.map do |stage|
Ci::Stage.new(self, name: stage.first, status: stage.last)
end
end
def artifacts
builds.latest.with_artifacts_not_expired
end end
def project_id def project_id
......
module Ci
# Currently this is artificial object, constructed dynamically
# We should migrate this object to actual database record in the future
class Stage
include StaticModel
attr_reader :pipeline, :name
delegate :project, to: :pipeline
def initialize(pipeline, name:, status: nil)
@pipeline = pipeline
@name = name
@status = status
end
def to_param
name
end
def status
@status ||= statuses.latest.status
end
def detailed_status
Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
end
def statuses
@statuses ||= pipeline.statuses.where(stage: name)
end
def builds
@builds ||= pipeline.builds.where(stage: name)
end
end
end
...@@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base ...@@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base
end end
scope :exclude_ignored, -> do scope :exclude_ignored, -> do
quoted_when = connection.quote_column_name('when')
# We want to ignore failed_but_allowed jobs # We want to ignore failed_but_allowed jobs
where("allow_failure = ? OR status IN (?)", where("allow_failure = ? OR status IN (?)",
false, all_state_names - [:failed, :canceled]). false, all_state_names - [:failed, :canceled])
# We want to ignore skipped manual jobs
where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped').
# We want to ignore skipped on_failure
where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped')
end end
scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
state_machine :status do state_machine :status do
event :enqueue do event :enqueue do
...@@ -117,20 +112,6 @@ class CommitStatus < ActiveRecord::Base ...@@ -117,20 +112,6 @@ class CommitStatus < ActiveRecord::Base
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end end
def self.stages
# We group by stage name, but order stages by theirs' index
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
end
def self.stages_status
# We execute subquery for each stage to calculate a stage status
statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql)
statuses.inject({}) do |h, k|
h[k.first] = k.last
h
end
end
def failed_but_allowed? def failed_but_allowed?
allow_failure? && (failed? || canceled?) allow_failure? && (failed? || canceled?)
end end
......
...@@ -4,7 +4,7 @@ module HasStatus ...@@ -4,7 +4,7 @@ module HasStatus
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped] STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running] ACTIVE_STATUSES = %w[pending running]
COMPLETED_STATUSES = %w[success failed canceled] COMPLETED_STATUSES = %w[success failed canceled skipped]
ORDERED_STATUSES = %w[failed pending running canceled success skipped] ORDERED_STATUSES = %w[failed pending running canceled success skipped]
class_methods do class_methods do
...@@ -23,9 +23,10 @@ module HasStatus ...@@ -23,9 +23,10 @@ module HasStatus
canceled = scope.canceled.select('count(*)').to_sql canceled = scope.canceled.select('count(*)').to_sql
"(CASE "(CASE
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success' WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created' WHEN (#{builds})=(#{created}) THEN 'created'
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
......
...@@ -44,11 +44,11 @@ module Ci ...@@ -44,11 +44,11 @@ module Ci
def valid_statuses_for_when(value) def valid_statuses_for_when(value)
case value case value
when 'on_success' when 'on_success'
%w[success] %w[success skipped]
when 'on_failure' when 'on_failure'
%w[failed] %w[failed]
when 'always' when 'always'
%w[success failed] %w[success failed skipped]
else else
[] []
end end
......
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
%tr.success-message %tr.success-message
%td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"} %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"}
- build_count = @pipeline.statuses.latest.size - build_count = @pipeline.statuses.latest.size
- stage_count = @pipeline.stages.size - stage_count = @pipeline.stages_count
Pipeline Pipeline
%a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
= "\##{@pipeline.id}" = "\##{@pipeline.id}"
......
...@@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %> ...@@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %>
<% end -%> <% end -%>
<% build_count = @pipeline.statuses.latest.size -%> <% build_count = @pipeline.statuses.latest.size -%>
<% stage_count = @pipeline.stages.size -%> <% stage_count = @pipeline.stages_count -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
......
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
%span.label.label-primary %span.label.label-primary
= tag = tag
- if @build.pipeline.stages.many? - if @build.pipeline.stages_count > 1
.dropdown.build-dropdown .dropdown.build-dropdown
.title Stage .title Stage
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
%ul.dropdown-menu %ul.dropdown-menu
- @build.pipeline.stages.each do |stage| - @build.pipeline.stages.each do |stage|
%li %li
%a.stage-item= stage %a.stage-item= stage.name
.builds-container .builds-container
- HasStatus::ORDERED_STATUSES.each do |build_status| - HasStatus::ORDERED_STATUSES.each do |build_status|
......
...@@ -104,9 +104,9 @@ ...@@ -104,9 +104,9 @@
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred') = icon('remove', class: 'cred')
- elsif allow_retry - elsif allow_retry
- if build.retryable? - if build.playable? && !admin
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
- elsif build.playable? && !admin
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play') = custom_icon('icon_play')
- elsif build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
...@@ -43,15 +43,13 @@ ...@@ -43,15 +43,13 @@
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
- stages_status = pipeline.statuses.latest.stages_status
%td.stage-cell %td.stage-cell
- stages.each do |stage| - pipeline.stages.each do |stage|
- status = stages_status[stage] - if stage.status
- tooltip = "#{stage.titleize}: #{status || 'not found'}" - tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}"
- if status
.stage-container .stage-container
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage.name), class: "has-tooltip ci-status-icon-#{stage.status}", title: tooltip do
= ci_icon_for_status(status) = ci_icon_for_status(stage.status)
%td %td
- if pipeline.duration - if pipeline.duration
......
%tr
%th{colspan: 10}
%strong
%a{name: stage}
- status = statuses.latest.status
%span{class: "ci-status-link ci-status-icon-#{status}"}
= ci_icon_for_status(status)
- if stage
&nbsp;
= stage.titleize
= render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
= render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
%tr
%td{colspan: 10}
&nbsp;
...@@ -24,20 +24,8 @@ ...@@ -24,20 +24,8 @@
in in
= time_interval_in_words pipeline.duration = time_interval_in_words pipeline.duration
.row-content-block.build-content.middle-block.pipeline-graph.hidden .row-content-block.build-content.middle-block.hidden
.pipeline-visualization = render "projects/pipelines/graph", pipeline: pipeline
%ul.stage-column-list
- stages = pipeline.stages_with_latest_statuses
- stages.each do |stage, statuses|
%li.stage-column
.stage-name
%a{name: stage}
- if stage
= stage.titleize
.builds-container
%ul
= render "projects/commit/pipeline_stage", statuses: statuses
- if pipeline.yaml_errors.present? - if pipeline.yaml_errors.present?
.bs-callout.bs-callout-danger .bs-callout.bs-callout-danger
...@@ -62,5 +50,4 @@ ...@@ -62,5 +50,4 @@
- if pipeline.project.build_coverage_enabled? - if pipeline.project.build_coverage_enabled?
%th Coverage %th Coverage
%th %th
- pipeline.statuses.relevant.stages.each do |stage| = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
= render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
- status_groups.each do |group_name, grouped_statuses|
- if grouped_statuses.one?
- status = grouped_statuses.first
- is_playable = status.playable? && can?(current_user, :update_build, @project)
%li.build{ class: ("playable" if is_playable) }
.curve
.build-content
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- else
%li.build
.curve
.dropdown.inline.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
%th Stages %th Stages
%th %th
%th %th
= render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false
- pipeline = local_assigns.fetch(:pipeline)
.pipeline-visualization.pipeline-graph
%ul.stage-column-list
= render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage
...@@ -12,19 +12,8 @@ ...@@ -12,19 +12,8 @@
.tab-content .tab-content
#js-tab-pipeline.tab-pane #js-tab-pipeline.tab-pane
.build-content.middle-block.pipeline-graph .build-content.middle-block
.pipeline-visualization = render "projects/pipelines/graph", pipeline: pipeline
%ul.stage-column-list
- stages = pipeline.stages_with_latest_statuses
- stages.each do |stage, statuses|
%li.stage-column
.stage-name
%a{name: stage}
- if stage
= stage.titleize
.builds-container
%ul
= render "projects/commit/pipeline_stage", statuses: statuses
#js-tab-builds.tab-pane #js-tab-builds.tab-pane
- if pipeline.yaml_errors.present? - if pipeline.yaml_errors.present?
...@@ -50,5 +39,4 @@ ...@@ -50,5 +39,4 @@
- if pipeline.project.build_coverage_enabled? - if pipeline.project.build_coverage_enabled?
%th Coverage %th Coverage
%th %th
- pipeline.statuses.relevant.stages.each do |stage| = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
= render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
...@@ -37,7 +37,6 @@ ...@@ -37,7 +37,6 @@
%span CI Lint %span CI Lint
%div.content-list.pipelines %div.content-list.pipelines
- stages = @pipelines.stages
- if @pipelines.blank? - if @pipelines.blank?
%div %div
.nothing-here-block No pipelines to show .nothing-here-block No pipelines to show
...@@ -51,6 +50,6 @@ ...@@ -51,6 +50,6 @@
%th Stages %th Stages
%th %th
%th.hidden-xs %th.hidden-xs
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = render @pipelines, commit_sha: true, stage: true, allow_retry: true
= paginate @pipelines, theme: 'gitlab' = paginate @pipelines, theme: 'gitlab'
- stage = local_assigns.fetch(:stage)
- statuses = stage.statuses.latest
- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
%li.stage-column
.stage-name
%a{ name: stage.name }
= stage.name.titleize
.builds-container
%ul
- status_groups.each do |group_name, grouped_statuses|
- if grouped_statuses.one?
- status = grouped_statuses.first
- is_playable = status.playable? && can?(current_user, :update_build, @project)
%li.build{ class: ("playable" if is_playable) }
.curve
.build-content
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- else
%li.build
.curve
.dropdown.inline.build-content
= render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses
%tr
%th{colspan: 10}
%strong
%a{ name: stage.name }
%span{class: "ci-status-link ci-status-icon-#{stage.status}"}
= ci_icon_for_status(stage.status)
&nbsp;
= stage.name.titleize
= render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
= render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
%tr
%td{colspan: 10}
&nbsp;
module Gitlab
module Ci
module Status
class Factory
attr_reader :subject
def initialize(subject)
@subject = subject
end
def fabricate!
if extended_status
extended_status.new(core_status)
else
core_status
end
end
private
def subject_status
@subject_status ||= subject.status
end
def core_status
Gitlab::Ci::Status
.const_get(subject_status.capitalize)
.new(subject)
end
def extended_status
@extended ||= extended_statuses.find do |status|
status.matches?(subject)
end
end
def extended_statuses
[]
end
end
end
end
end
...@@ -2,35 +2,15 @@ module Gitlab ...@@ -2,35 +2,15 @@ module Gitlab
module Ci module Ci
module Status module Status
module Pipeline module Pipeline
class Factory class Factory < Status::Factory
EXTENDED_STATUSES = [Pipeline::SuccessWithWarnings]
def initialize(pipeline)
@pipeline = pipeline
@status = pipeline.status || :created
end
def fabricate!
if extended_status
extended_status.new(core_status)
else
core_status
end
end
private private
def core_status def extended_statuses
Gitlab::Ci::Status [Pipeline::SuccessWithWarnings]
.const_get(@status.capitalize)
.new(@pipeline)
.extend(Status::Pipeline::Common)
end end
def extended_status def core_status
@extended ||= EXTENDED_STATUSES.find do |status| super.extend(Status::Pipeline::Common)
status.matches?(@pipeline)
end
end end
end end
end end
......
module Gitlab
module Ci
module Status
module Stage
module Common
def has_details?
true
end
def details_path
namespace_project_pipeline_path(@subject.project.namespace,
@subject.project,
@subject.pipeline,
anchor: @subject.name)
end
def has_action?
false
end
end
end
end
end
end
module Gitlab
module Ci
module Status
module Stage
class Factory < Status::Factory
private
def core_status
super.extend(Status::Stage::Common)
end
end
end
end
end
end
...@@ -22,7 +22,7 @@ module Gitlab ...@@ -22,7 +22,7 @@ module Gitlab
sha: pipeline.sha, sha: pipeline.sha,
before_sha: pipeline.before_sha, before_sha: pipeline.before_sha,
status: pipeline.status, status: pipeline.status,
stages: pipeline.stages, stages: pipeline.stages_name,
created_at: pipeline.created_at, created_at: pipeline.created_at,
finished_at: pipeline.finished_at, finished_at: pipeline.finished_at,
duration: pipeline.duration duration: pipeline.duration
......
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
require 'spec_helper'
describe Gitlab::Ci::Status::Factory do
subject do
described_class.new(object)
end
let(:status) { subject.fabricate! }
context 'when object has a core status' do
HasStatus::AVAILABLE_STATUSES.each do |core_status|
context "when core status is #{core_status}" do
let(:object) { double(status: core_status) }
it "fabricates a core status #{core_status}" do
expect(status).to be_a(
Gitlab::Ci::Status.const_get(core_status.capitalize))
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Common do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
subject do
Class.new(Gitlab::Ci::Status::Core)
.new(stage).extend(described_class)
end
it 'does not have action' do
expect(subject).not_to have_action
end
it 'has details' do
expect(subject).to have_details
end
it 'links to the pipeline details page' do
expect(subject.details_path)
.to include "pipelines/#{pipeline.id}"
expect(subject.details_path)
.to include "##{stage.name}"
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Factory do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
subject do
described_class.new(stage)
end
let(:status) do
subject.fabricate!
end
context 'when stage has a core status' do
HasStatus::AVAILABLE_STATUSES.each do |core_status|
context "when core status is #{core_status}" 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
expect(status).to be_a(
Gitlab::Ci::Status.const_get(core_status.capitalize))
end
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}"
end
end
end
end
end
...@@ -20,8 +20,6 @@ describe Ci::Pipeline, models: true do ...@@ -20,8 +20,6 @@ describe Ci::Pipeline, models: true do
it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha } it { is_expected.to respond_to :short_sha }
it { is_expected.to delegate_method(:stages).to(:statuses) }
describe '#valid_commit_sha' do describe '#valid_commit_sha' do
context 'commit.sha can not start with 00000000' do context 'commit.sha can not start with 00000000' do
before do before do
...@@ -125,16 +123,55 @@ describe Ci::Pipeline, models: true do ...@@ -125,16 +123,55 @@ describe Ci::Pipeline, models: true do
end end
describe '#stages' do describe '#stages' do
let(:pipeline2) { FactoryGirl.create :ci_pipeline, project: project } before do
subject { CommitStatus.where(pipeline: [pipeline, pipeline2]).stages } create(:commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success')
create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed')
create(:commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running')
create(:commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success')
end
subject { pipeline.stages }
context 'stages list' do
it 'returns ordered list of stages' do
expect(subject.map(&:name)).to eq(%w[build test deploy])
end
end
it 'returns a valid number of stages' 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|
[stage.name, stage.status]
end
end
it 'returns list of stages with statuses' do
expect(statuses).to eq([['build', 'failed'],
['test', 'success'],
['deploy', 'running']
])
end
context 'when build is retried' do
before do before do
FactoryGirl.create :ci_build, pipeline: pipeline2, stage: 'test', stage_idx: 1 create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success')
FactoryGirl.create :ci_build, pipeline: pipeline, stage: 'build', stage_idx: 0
end end
it 'return all stages' do it 'ignores the previous state' do
is_expected.to eq(%w(build test)) expect(statuses).to eq([['build', 'success'],
['test', 'success'],
['deploy', 'running']
])
end
end
end end
end end
......
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
...@@ -175,7 +175,7 @@ describe CommitStatus, models: true do ...@@ -175,7 +175,7 @@ describe CommitStatus, models: true do
end end
it 'returns statuses without what we want to ignore' do it 'returns statuses without what we want to ignore' do
is_expected.to eq(statuses.values_at(1, 2, 4, 5, 6, 8, 9)) is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9))
end end
end end
...@@ -200,49 +200,6 @@ describe CommitStatus, models: true do ...@@ -200,49 +200,6 @@ describe CommitStatus, models: true do
end end
end end
describe '#stages' do
before do
create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success'
create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed'
create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running'
create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success'
end
context 'stages list' do
subject { CommitStatus.where(pipeline: pipeline).stages }
it 'returns ordered list of stages' do
is_expected.to eq(%w[build test deploy])
end
end
context 'stages with statuses' do
subject { CommitStatus.where(pipeline: pipeline).latest.stages_status }
it 'returns list of stages with statuses' do
is_expected.to eq({
'build' => 'failed',
'test' => 'success',
'deploy' => 'running'
})
end
context 'when build is retried' do
before do
create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success'
end
it 'ignores a previous state' do
is_expected.to eq({
'build' => 'success',
'test' => 'success',
'deploy' => 'running'
})
end
end
end
end
describe '#commit' do describe '#commit' do
it 'returns commit pipeline has been created for' do it 'returns commit pipeline has been created for' do
expect(commit_status.commit).to eq project.commit expect(commit_status.commit).to eq project.commit
......
...@@ -48,7 +48,7 @@ describe HasStatus do ...@@ -48,7 +48,7 @@ describe HasStatus do
[create(type, status: :failed, allow_failure: true)] [create(type, status: :failed, allow_failure: true)]
end end
it { is_expected.to eq 'success' } it { is_expected.to eq 'skipped' }
end end
context 'success and canceled' do context 'success and canceled' do
......
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