Merge branch 'feature/success-warning-icons-in-stages-builds' into 'master'
Use a warning icon for a stage with allowed to fail builds Closes #21948 See merge request !8503
This commit is contained in:
commit
7446c2f5cf
24 changed files with 745 additions and 214 deletions
|
@ -15,6 +15,7 @@
|
|||
}
|
||||
|
||||
.ci-status-icon-pending,
|
||||
.ci-status-icon-failed_with_warnings,
|
||||
.ci-status-icon-success_with_warnings {
|
||||
color: $gl-warning;
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
&.ci-failed {
|
||||
&.ci-failed,
|
||||
&.ci-failed_with_warnings {
|
||||
color: $gl-danger;
|
||||
border-color: $gl-danger;
|
||||
|
||||
|
|
|
@ -128,16 +128,21 @@ module Ci
|
|||
end
|
||||
|
||||
def stages
|
||||
# TODO, this needs refactoring, see gitlab-ce#26481.
|
||||
|
||||
stages_query = statuses
|
||||
.group('stage').select(:stage).order('max(stage_idx)')
|
||||
|
||||
status_sql = statuses.latest.where('stage=sg.stage').status_sql
|
||||
|
||||
stages_query = statuses.group('stage').select(:stage)
|
||||
.order('max(stage_idx)')
|
||||
warnings_sql = statuses.latest.select('COUNT(*) > 0')
|
||||
.where('stage=sg.stage').failed_but_allowed.to_sql
|
||||
|
||||
stages_with_statuses = CommitStatus.from(stages_query, :sg).
|
||||
pluck('sg.stage', status_sql)
|
||||
stages_with_statuses = CommitStatus.from(stages_query, :sg)
|
||||
.pluck('sg.stage', status_sql, "(#{warnings_sql})")
|
||||
|
||||
stages_with_statuses.map do |stage|
|
||||
Ci::Stage.new(self, name: stage.first, status: stage.last)
|
||||
Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,10 +8,11 @@ module Ci
|
|||
|
||||
delegate :project, to: :pipeline
|
||||
|
||||
def initialize(pipeline, name:, status: nil)
|
||||
def initialize(pipeline, name:, status: nil, warnings: nil)
|
||||
@pipeline = pipeline
|
||||
@name = name
|
||||
@status = status
|
||||
@warnings = warnings
|
||||
end
|
||||
|
||||
def to_param
|
||||
|
@ -39,5 +40,17 @@ module Ci
|
|||
def builds
|
||||
@builds ||= pipeline.builds.where(stage: name)
|
||||
end
|
||||
|
||||
def success?
|
||||
status.to_s == 'success'
|
||||
end
|
||||
|
||||
def has_warnings?
|
||||
if @warnings.nil?
|
||||
statuses.latest.failed_but_allowed.any?
|
||||
else
|
||||
@warnings
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module HasStatus
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DEFAULT_STATUS = 'created'
|
||||
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
|
||||
STARTED_STATUSES = %w[running success failed skipped]
|
||||
ACTIVE_STATUSES = %w[pending running]
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Use warning icon in mini-graph if stage passed conditionally
|
||||
merge_request: 8503
|
||||
author:
|
|
@ -4,8 +4,11 @@ module Gitlab
|
|||
module Build
|
||||
class Factory < Status::Factory
|
||||
def self.extended_statuses
|
||||
[Status::Build::Stop, Status::Build::Play,
|
||||
Status::Build::Cancelable, Status::Build::Retryable]
|
||||
[[Status::Build::Cancelable,
|
||||
Status::Build::Retryable],
|
||||
[Status::Build::FailedAllowed,
|
||||
Status::Build::Play,
|
||||
Status::Build::Stop]]
|
||||
end
|
||||
|
||||
def self.common_helpers
|
||||
|
|
27
lib/gitlab/ci/status/build/failed_allowed.rb
Normal file
27
lib/gitlab/ci/status/build/failed_allowed.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Gitlab
|
||||
module Ci
|
||||
module Status
|
||||
module Build
|
||||
class FailedAllowed < SimpleDelegator
|
||||
include Status::Extended
|
||||
|
||||
def label
|
||||
'failed (allowed to fail)'
|
||||
end
|
||||
|
||||
def icon
|
||||
'icon_status_warning'
|
||||
end
|
||||
|
||||
def group
|
||||
'failed_with_warnings'
|
||||
end
|
||||
|
||||
def self.matches?(build, user)
|
||||
build.failed? && build.allow_failure?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,16 +5,40 @@ module Gitlab
|
|||
def initialize(subject, user)
|
||||
@subject = subject
|
||||
@user = user
|
||||
@status = subject.status || HasStatus::DEFAULT_STATUS
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
if extended_status
|
||||
extended_status.new(core_status)
|
||||
else
|
||||
if extended_statuses.none?
|
||||
core_status
|
||||
else
|
||||
compound_extended_status
|
||||
end
|
||||
end
|
||||
|
||||
def core_status
|
||||
Gitlab::Ci::Status
|
||||
.const_get(@status.capitalize)
|
||||
.new(@subject, @user)
|
||||
.extend(self.class.common_helpers)
|
||||
end
|
||||
|
||||
def compound_extended_status
|
||||
extended_statuses.inject(core_status) do |status, extended|
|
||||
extended.new(status)
|
||||
end
|
||||
end
|
||||
|
||||
def extended_statuses
|
||||
return @extended_statuses if defined?(@extended_statuses)
|
||||
|
||||
groups = self.class.extended_statuses.map do |group|
|
||||
Array(group).find { |status| status.matches?(@subject, @user) }
|
||||
end
|
||||
|
||||
@extended_statuses = groups.flatten.compact
|
||||
end
|
||||
|
||||
def self.extended_statuses
|
||||
[]
|
||||
end
|
||||
|
@ -22,25 +46,6 @@ module Gitlab
|
|||
def self.common_helpers
|
||||
Module.new
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def simple_status
|
||||
@simple_status ||= @subject.status || :created
|
||||
end
|
||||
|
||||
def core_status
|
||||
Gitlab::Ci::Status
|
||||
.const_get(simple_status.capitalize)
|
||||
.new(@subject, @user)
|
||||
.extend(self.class.common_helpers)
|
||||
end
|
||||
|
||||
def extended_status
|
||||
@extended ||= self.class.extended_statuses.find do |status|
|
||||
status.matches?(@subject, @user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Gitlab
|
|||
module Pipeline
|
||||
class Factory < Status::Factory
|
||||
def self.extended_statuses
|
||||
[Pipeline::SuccessWithWarnings]
|
||||
[Status::SuccessWarning]
|
||||
end
|
||||
|
||||
def self.common_helpers
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
module Gitlab
|
||||
module Ci
|
||||
module Status
|
||||
module Pipeline
|
||||
class SuccessWithWarnings < SimpleDelegator
|
||||
include Status::Extended
|
||||
|
||||
def text
|
||||
'passed'
|
||||
end
|
||||
|
||||
def label
|
||||
'passed with warnings'
|
||||
end
|
||||
|
||||
def icon
|
||||
'icon_status_warning'
|
||||
end
|
||||
|
||||
def group
|
||||
'success_with_warnings'
|
||||
end
|
||||
|
||||
def self.matches?(pipeline, user)
|
||||
pipeline.success? && pipeline.has_warnings?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,10 @@ module Gitlab
|
|||
module Status
|
||||
module Stage
|
||||
class Factory < Status::Factory
|
||||
def self.extended_statuses
|
||||
[Status::SuccessWarning]
|
||||
end
|
||||
|
||||
def self.common_helpers
|
||||
Status::Stage::Common
|
||||
end
|
||||
|
|
33
lib/gitlab/ci/status/success_warning.rb
Normal file
33
lib/gitlab/ci/status/success_warning.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
module Gitlab
|
||||
module Ci
|
||||
module Status
|
||||
##
|
||||
# Extended status used when pipeline or stage passed conditionally.
|
||||
# This means that failed jobs that are allowed to fail were present.
|
||||
#
|
||||
class SuccessWarning < SimpleDelegator
|
||||
include Status::Extended
|
||||
|
||||
def text
|
||||
'passed'
|
||||
end
|
||||
|
||||
def label
|
||||
'passed with warnings'
|
||||
end
|
||||
|
||||
def icon
|
||||
'icon_status_warning'
|
||||
end
|
||||
|
||||
def group
|
||||
'success_with_warnings'
|
||||
end
|
||||
|
||||
def self.matches?(subject, user)
|
||||
subject.success? && subject.has_warnings?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,11 +3,12 @@ FactoryGirl.define do
|
|||
transient do
|
||||
name 'test'
|
||||
status nil
|
||||
warnings nil
|
||||
pipeline factory: :ci_empty_pipeline
|
||||
end
|
||||
|
||||
initialize_with do
|
||||
Ci::Stage.new(pipeline, name: name, status: status)
|
||||
Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,15 +3,23 @@ require 'spec_helper'
|
|||
describe Gitlab::Ci::Status::Build::Factory do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { build.project }
|
||||
|
||||
subject { described_class.new(build, user) }
|
||||
let(:status) { subject.fabricate! }
|
||||
let(:status) { factory.fabricate! }
|
||||
let(:factory) { described_class.new(build, user) }
|
||||
|
||||
before { project.team << [user, :developer] }
|
||||
|
||||
context 'when build is successful' do
|
||||
let(:build) { create(:ci_build, :success) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Retryable]
|
||||
end
|
||||
|
||||
it 'fabricates a retryable build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
|
||||
end
|
||||
|
@ -26,24 +34,72 @@ describe Gitlab::Ci::Status::Build::Factory do
|
|||
end
|
||||
|
||||
context 'when build is failed' do
|
||||
let(:build) { create(:ci_build, :failed) }
|
||||
context 'when build is not allowed to fail' do
|
||||
let(:build) { create(:ci_build, :failed) }
|
||||
|
||||
it 'fabricates a retryable build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Retryable]
|
||||
end
|
||||
|
||||
it 'fabricates a retryable build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
|
||||
end
|
||||
|
||||
it 'fabricates status with correct details' do
|
||||
expect(status.text).to eq 'failed'
|
||||
expect(status.icon).to eq 'icon_status_failed'
|
||||
expect(status.label).to eq 'failed'
|
||||
expect(status).to have_details
|
||||
expect(status).to have_action
|
||||
end
|
||||
end
|
||||
|
||||
it 'fabricates status with correct details' do
|
||||
expect(status.text).to eq 'failed'
|
||||
expect(status.icon).to eq 'icon_status_failed'
|
||||
expect(status.label).to eq 'failed'
|
||||
expect(status).to have_details
|
||||
expect(status).to have_action
|
||||
context 'when build is allowed to fail' do
|
||||
let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Retryable,
|
||||
Gitlab::Ci::Status::Build::FailedAllowed]
|
||||
end
|
||||
|
||||
it 'fabricates a failed but allowed build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::FailedAllowed
|
||||
end
|
||||
|
||||
it 'fabricates status with correct details' do
|
||||
expect(status.text).to eq 'failed'
|
||||
expect(status.icon).to eq 'icon_status_warning'
|
||||
expect(status.label).to eq 'failed (allowed to fail)'
|
||||
expect(status).to have_details
|
||||
expect(status).to have_action
|
||||
expect(status.action_title).to include 'Retry'
|
||||
expect(status.action_path).to include 'retry'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build is a canceled' do
|
||||
let(:build) { create(:ci_build, :canceled) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Canceled
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Retryable]
|
||||
end
|
||||
|
||||
it 'fabricates a retryable build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
|
||||
end
|
||||
|
@ -60,6 +116,15 @@ describe Gitlab::Ci::Status::Build::Factory do
|
|||
context 'when build is running' do
|
||||
let(:build) { create(:ci_build, :running) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Running
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Cancelable]
|
||||
end
|
||||
|
||||
it 'fabricates a canceable build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
|
||||
end
|
||||
|
@ -76,6 +141,15 @@ describe Gitlab::Ci::Status::Build::Factory do
|
|||
context 'when build is pending' do
|
||||
let(:build) { create(:ci_build, :pending) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Pending
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Cancelable]
|
||||
end
|
||||
|
||||
it 'fabricates a cancelable build status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
|
||||
end
|
||||
|
@ -92,6 +166,14 @@ describe Gitlab::Ci::Status::Build::Factory do
|
|||
context 'when build is skipped' do
|
||||
let(:build) { create(:ci_build, :skipped) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
|
||||
end
|
||||
|
||||
it 'does not match extended statuses' do
|
||||
expect(factory.extended_statuses).to be_empty
|
||||
end
|
||||
|
||||
it 'fabricates a core skipped status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Skipped
|
||||
end
|
||||
|
@ -109,6 +191,15 @@ describe Gitlab::Ci::Status::Build::Factory do
|
|||
context 'when build is a play action' do
|
||||
let(:build) { create(:ci_build, :playable) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Play]
|
||||
end
|
||||
|
||||
it 'fabricates a core skipped status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Play
|
||||
end
|
||||
|
@ -119,12 +210,22 @@ describe Gitlab::Ci::Status::Build::Factory do
|
|||
expect(status.label).to eq 'manual play action'
|
||||
expect(status).to have_details
|
||||
expect(status).to have_action
|
||||
expect(status.action_path).to include 'play'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build is an environment stop action' do
|
||||
let(:build) { create(:ci_build, :playable, :teardown_environment) }
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::Build::Stop]
|
||||
end
|
||||
|
||||
it 'fabricates a core skipped status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::Build::Stop
|
||||
end
|
||||
|
|
110
spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
Normal file
110
spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
Normal file
|
@ -0,0 +1,110 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Status::Build::FailedAllowed do
|
||||
let(:status) { double('core status') }
|
||||
let(:user) { double('user') }
|
||||
|
||||
subject do
|
||||
described_class.new(status)
|
||||
end
|
||||
|
||||
describe '#text' do
|
||||
it 'does not override status text' do
|
||||
expect(status).to receive(:text)
|
||||
|
||||
subject.text
|
||||
end
|
||||
end
|
||||
|
||||
describe '#icon' do
|
||||
it 'returns a warning icon' do
|
||||
expect(subject.icon).to eq 'icon_status_warning'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#label' do
|
||||
it 'returns information about failed but allowed to fail status' do
|
||||
expect(subject.label).to eq 'failed (allowed to fail)'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#group' do
|
||||
it 'returns status failed with warnings status group' do
|
||||
expect(subject.group).to eq 'failed_with_warnings'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'action details' do
|
||||
describe '#has_action?' do
|
||||
it 'does not decorate action details' do
|
||||
expect(status).to receive(:has_action?)
|
||||
|
||||
subject.has_action?
|
||||
end
|
||||
end
|
||||
|
||||
describe '#action_path' do
|
||||
it 'does not decorate action path' do
|
||||
expect(status).to receive(:action_path)
|
||||
|
||||
subject.action_path
|
||||
end
|
||||
end
|
||||
|
||||
describe '#action_icon' do
|
||||
it 'does not decorate action icon' do
|
||||
expect(status).to receive(:action_icon)
|
||||
|
||||
subject.action_icon
|
||||
end
|
||||
end
|
||||
|
||||
describe '#action_title' do
|
||||
it 'does not decorate action title' do
|
||||
expect(status).to receive(:action_title)
|
||||
|
||||
subject.action_title
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.matches?' do
|
||||
subject { described_class.matches?(build, user) }
|
||||
|
||||
context 'when build is failed' do
|
||||
context 'when build is allowed to fail' do
|
||||
let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
|
||||
|
||||
it 'is a correct match' do
|
||||
expect(subject).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build is not allowed to fail' do
|
||||
let(:build) { create(:ci_build, :failed) }
|
||||
|
||||
it 'is not a correct match' do
|
||||
expect(subject).not_to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build did not fail' do
|
||||
context 'when build is allowed to fail' do
|
||||
let(:build) { create(:ci_build, :success, :allowed_to_fail) }
|
||||
|
||||
it 'is not a correct match' do
|
||||
expect(subject).not_to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build is not allowed to fail' do
|
||||
let(:build) { create(:ci_build, :success) }
|
||||
|
||||
it 'is not a correct match' do
|
||||
expect(subject).not_to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,24 +1,135 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Status::Factory do
|
||||
subject do
|
||||
described_class.new(resource, user)
|
||||
end
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let(:status) { subject.fabricate! }
|
||||
let(:fabricated_status) { factory.fabricate! }
|
||||
let(:factory) { described_class.new(resource, user) }
|
||||
|
||||
context 'when object has a core status' do
|
||||
HasStatus::AVAILABLE_STATUSES.each do |core_status|
|
||||
context "when core status is #{core_status}" do
|
||||
let(:resource) { double(status: core_status) }
|
||||
HasStatus::AVAILABLE_STATUSES.each do |simple_status|
|
||||
context "when simple core status is #{simple_status}" do
|
||||
let(:resource) { double('resource', status: simple_status) }
|
||||
|
||||
it "fabricates a core status #{core_status}" do
|
||||
expect(status).to be_a(
|
||||
Gitlab::Ci::Status.const_get(core_status.capitalize))
|
||||
let(:expected_status) do
|
||||
Gitlab::Ci::Status.const_get(simple_status.capitalize)
|
||||
end
|
||||
|
||||
it "fabricates a core status #{simple_status}" do
|
||||
expect(fabricated_status).to be_a expected_status
|
||||
end
|
||||
|
||||
it "matches a valid core status for #{simple_status}" do
|
||||
expect(factory.core_status).to be_a expected_status
|
||||
end
|
||||
|
||||
it "does not match any extended statuses for #{simple_status}" do
|
||||
expect(factory.extended_statuses).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when resource supports multiple extended statuses' do
|
||||
let(:resource) { double('resource', status: :success) }
|
||||
|
||||
let(:first_extended_status) do
|
||||
Class.new(SimpleDelegator) do
|
||||
def first_method
|
||||
'first return value'
|
||||
end
|
||||
|
||||
def second_method
|
||||
'second return value'
|
||||
end
|
||||
|
||||
def self.matches?(*)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:second_extended_status) do
|
||||
Class.new(SimpleDelegator) do
|
||||
def first_method
|
||||
'decorated return value'
|
||||
end
|
||||
|
||||
def third_method
|
||||
'third return value'
|
||||
end
|
||||
|
||||
def self.matches?(*)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'compound decorator factory' do
|
||||
it 'fabricates compound decorator' do
|
||||
expect(fabricated_status.first_method).to eq 'decorated return value'
|
||||
expect(fabricated_status.second_method).to eq 'second return value'
|
||||
expect(fabricated_status.third_method).to eq 'third return value'
|
||||
end
|
||||
|
||||
it 'delegates to core status' do
|
||||
expect(fabricated_status.text).to eq 'passed'
|
||||
end
|
||||
|
||||
it 'latest matches status becomes a status name' do
|
||||
expect(fabricated_status.class).to eq second_extended_status
|
||||
end
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [first_extended_status, second_extended_status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exclusive statuses are matches' do
|
||||
before do
|
||||
allow(described_class).to receive(:extended_statuses)
|
||||
.and_return([[first_extended_status, second_extended_status]])
|
||||
end
|
||||
|
||||
it 'does not fabricate compound decorator' do
|
||||
expect(fabricated_status.first_method).to eq 'first return value'
|
||||
expect(fabricated_status.second_method).to eq 'second return value'
|
||||
expect(fabricated_status).not_to respond_to(:third_method)
|
||||
end
|
||||
|
||||
it 'delegates to core status' do
|
||||
expect(fabricated_status.text).to eq 'passed'
|
||||
end
|
||||
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
|
||||
end
|
||||
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses).to eq [first_extended_status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exclusive statuses are not matched' do
|
||||
before do
|
||||
allow(described_class).to receive(:extended_statuses)
|
||||
.and_return([[first_extended_status], [second_extended_status]])
|
||||
end
|
||||
|
||||
it_behaves_like 'compound decorator factory'
|
||||
end
|
||||
|
||||
context 'when using simplified status grouping' do
|
||||
before do
|
||||
allow(described_class).to receive(:extended_statuses)
|
||||
.and_return([first_extended_status, second_extended_status])
|
||||
end
|
||||
|
||||
it_behaves_like 'compound decorator factory'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,29 +3,32 @@ require 'spec_helper'
|
|||
describe Gitlab::Ci::Status::Pipeline::Factory do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { pipeline.project }
|
||||
|
||||
subject do
|
||||
described_class.new(pipeline, user)
|
||||
end
|
||||
|
||||
let(:status) do
|
||||
subject.fabricate!
|
||||
end
|
||||
let(:status) { factory.fabricate! }
|
||||
let(:factory) { described_class.new(pipeline, user) }
|
||||
|
||||
before do
|
||||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
context 'when pipeline has a core status' do
|
||||
HasStatus::AVAILABLE_STATUSES.each do |core_status|
|
||||
context "when core status is #{core_status}" do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, status: core_status)
|
||||
HasStatus::AVAILABLE_STATUSES.each do |simple_status|
|
||||
context "when core status is #{simple_status}" do
|
||||
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
|
||||
|
||||
let(:expected_status) do
|
||||
Gitlab::Ci::Status.const_get(simple_status.capitalize)
|
||||
end
|
||||
|
||||
it "fabricates a core status #{core_status}" do
|
||||
expect(status).to be_a(
|
||||
Gitlab::Ci::Status.const_get(core_status.capitalize))
|
||||
it "matches correct core status for #{simple_status}" do
|
||||
expect(factory.core_status).to be_a expected_status
|
||||
end
|
||||
|
||||
it 'does not matche extended statuses' do
|
||||
expect(factory.extended_statuses).to be_empty
|
||||
end
|
||||
|
||||
it "fabricates a core status #{simple_status}" do
|
||||
expect(status).to be_a expected_status
|
||||
end
|
||||
|
||||
it 'extends core status with common pipeline methods' do
|
||||
|
@ -47,13 +50,22 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
|
|||
create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline)
|
||||
end
|
||||
|
||||
it 'fabricates extended "success with warnings" status' do
|
||||
expect(status)
|
||||
.to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings
|
||||
it 'matches correct core status' do
|
||||
expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
|
||||
end
|
||||
|
||||
it 'extends core status with common pipeline methods' do
|
||||
it 'matches correct extended statuses' do
|
||||
expect(factory.extended_statuses)
|
||||
.to eq [Gitlab::Ci::Status::SuccessWarning]
|
||||
end
|
||||
|
||||
it 'fabricates extended "success with warnings" status' do
|
||||
expect(status).to be_a Gitlab::Ci::Status::SuccessWarning
|
||||
end
|
||||
|
||||
it 'extends core status with common pipeline method' do
|
||||
expect(status).to have_details
|
||||
expect(status.details_path).to include "pipelines/#{pipeline.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
|
||||
subject do
|
||||
described_class.new(double('status'))
|
||||
end
|
||||
|
||||
describe '#test' do
|
||||
it { expect(subject.text).to eq 'passed' }
|
||||
end
|
||||
|
||||
describe '#label' do
|
||||
it { expect(subject.label).to eq 'passed with warnings' }
|
||||
end
|
||||
|
||||
describe '#icon' do
|
||||
it { expect(subject.icon).to eq 'icon_status_warning' }
|
||||
end
|
||||
|
||||
describe '#group' do
|
||||
it { expect(subject.group).to eq 'success_with_warnings' }
|
||||
end
|
||||
|
||||
describe '.matches?' do
|
||||
context 'when pipeline is successful' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, status: :success)
|
||||
end
|
||||
|
||||
context 'when pipeline has warnings' do
|
||||
before do
|
||||
allow(pipeline).to receive(:has_warnings?).and_return(true)
|
||||
end
|
||||
|
||||
it 'is a correct match' do
|
||||
expect(described_class.matches?(pipeline, double)).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline does not have warnings' do
|
||||
it 'does not match' do
|
||||
expect(described_class.matches?(pipeline, double)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline is not successful' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, status: :skipped)
|
||||
end
|
||||
|
||||
context 'when pipeline has warnings' do
|
||||
before do
|
||||
allow(pipeline).to receive(:has_warnings?).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not match' do
|
||||
expect(described_class.matches?(pipeline, double)).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline does not have warnings' do
|
||||
it 'does not match' do
|
||||
expect(described_class.matches?(pipeline, double)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -43,4 +43,25 @@ describe Gitlab::Ci::Status::Stage::Factory do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stage has warnings' do
|
||||
let(:stage) do
|
||||
build(:ci_stage, name: 'test', status: :success, pipeline: pipeline)
|
||||
end
|
||||
|
||||
before do
|
||||
create(:ci_build, :allowed_to_fail, :failed,
|
||||
stage: 'test', pipeline: stage.pipeline)
|
||||
end
|
||||
|
||||
it 'fabricates extended "success with warnings" status' do
|
||||
expect(status)
|
||||
.to be_a Gitlab::Ci::Status::SuccessWarning
|
||||
end
|
||||
|
||||
it 'extends core status with common stage method' do
|
||||
expect(status).to have_details
|
||||
expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
75
spec/lib/gitlab/ci/status/success_warning_spec.rb
Normal file
75
spec/lib/gitlab/ci/status/success_warning_spec.rb
Normal file
|
@ -0,0 +1,75 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Status::SuccessWarning do
|
||||
subject do
|
||||
described_class.new(double('status'))
|
||||
end
|
||||
|
||||
describe '#test' do
|
||||
it { expect(subject.text).to eq 'passed' }
|
||||
end
|
||||
|
||||
describe '#label' do
|
||||
it { expect(subject.label).to eq 'passed with warnings' }
|
||||
end
|
||||
|
||||
describe '#icon' do
|
||||
it { expect(subject.icon).to eq 'icon_status_warning' }
|
||||
end
|
||||
|
||||
describe '#group' do
|
||||
it { expect(subject.group).to eq 'success_with_warnings' }
|
||||
end
|
||||
|
||||
describe '.matches?' do
|
||||
let(:matchable) { double('matchable') }
|
||||
|
||||
context 'when matchable subject is successful' do
|
||||
before do
|
||||
allow(matchable).to receive(:success?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when matchable subject has warnings' do
|
||||
before do
|
||||
allow(matchable).to receive(:has_warnings?).and_return(true)
|
||||
end
|
||||
|
||||
it 'is a correct match' do
|
||||
expect(described_class.matches?(matchable, double)).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when matchable subject does not have warnings' do
|
||||
before do
|
||||
allow(matchable).to receive(:has_warnings?).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not match' do
|
||||
expect(described_class.matches?(matchable, double)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when matchable subject is not successful' do
|
||||
before do
|
||||
allow(matchable).to receive(:success?).and_return(false)
|
||||
end
|
||||
|
||||
context 'when matchable subject has warnings' do
|
||||
before do
|
||||
allow(matchable).to receive(:has_warnings?).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not match' do
|
||||
expect(described_class.matches?(matchable, double)).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when matchable subject does not have warnings' do
|
||||
it 'does not match' do
|
||||
expect(described_class.matches?(matchable, double)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -122,55 +122,80 @@ describe Ci::Pipeline, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#stages' do
|
||||
describe 'pipeline 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')
|
||||
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 }
|
||||
describe '#stages' do
|
||||
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]
|
||||
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 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
|
||||
create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success')
|
||||
context 'stages with statuses' do
|
||||
let(:statuses) do
|
||||
subject.map { |stage| [stage.name, stage.status] }
|
||||
end
|
||||
|
||||
it 'ignores the previous state' do
|
||||
expect(statuses).to eq([['build', 'success'],
|
||||
it 'returns list of stages with correct statuses' do
|
||||
expect(statuses).to eq([['build', 'failed'],
|
||||
['test', 'success'],
|
||||
['deploy', 'running']
|
||||
])
|
||||
['deploy', 'running']])
|
||||
end
|
||||
|
||||
context 'when commit status is retried' do
|
||||
before do
|
||||
create(:commit_status, pipeline: pipeline,
|
||||
stage: 'build',
|
||||
name: 'mac',
|
||||
stage_idx: 0,
|
||||
status: 'success')
|
||||
end
|
||||
|
||||
it 'ignores the previous state' do
|
||||
expect(statuses).to eq([['build', 'success'],
|
||||
['test', 'success'],
|
||||
['deploy', 'running']])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stages_count' do
|
||||
it 'returns a valid number of stages' do
|
||||
expect(pipeline.stages_count).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stages_name' do
|
||||
it 'returns a valid names of stages' do
|
||||
expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,6 +142,78 @@ describe Ci::Stage, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#success?' do
|
||||
context 'when stage is successful' do
|
||||
before do
|
||||
create_job(:ci_build, status: :success)
|
||||
create_job(:generic_commit_status, status: :success)
|
||||
end
|
||||
|
||||
it 'is successful' do
|
||||
expect(stage).to be_success
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stage is not successful' do
|
||||
before do
|
||||
create_job(:ci_build, status: :failed)
|
||||
create_job(:generic_commit_status, status: :success)
|
||||
end
|
||||
|
||||
it 'is not successful' do
|
||||
expect(stage).not_to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_warnings?' do
|
||||
context 'when stage has warnings' do
|
||||
context 'when using memoized warnings flag' do
|
||||
context 'when there are warnings' do
|
||||
let(:stage) { build(:ci_stage, warnings: true) }
|
||||
|
||||
it 'has memoized warnings' do
|
||||
expect(stage).not_to receive(:statuses)
|
||||
expect(stage).to have_warnings
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no warnings' do
|
||||
let(:stage) { build(:ci_stage, warnings: false) }
|
||||
|
||||
it 'has memoized warnings' do
|
||||
expect(stage).not_to receive(:statuses)
|
||||
expect(stage).not_to have_warnings
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calculating warnings from statuses' do
|
||||
before do
|
||||
create(:ci_build, :failed, :allowed_to_fail,
|
||||
stage: stage_name, pipeline: pipeline)
|
||||
end
|
||||
|
||||
it 'has warnings calculated from statuses' do
|
||||
expect(stage).to receive(:statuses).and_call_original
|
||||
expect(stage).to have_warnings
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stage does not have warnings' do
|
||||
before do
|
||||
create(:ci_build, :success, stage: stage_name,
|
||||
pipeline: pipeline)
|
||||
end
|
||||
|
||||
it 'does not have warnings calculated from statuses' do
|
||||
expect(stage).to receive(:statuses).and_call_original
|
||||
expect(stage).not_to have_warnings
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_job(type, status: 'success', stage: stage_name)
|
||||
create(type, pipeline: pipeline, stage: stage, status: status)
|
||||
end
|
||||
|
|
|
@ -219,4 +219,10 @@ describe HasStatus do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '::DEFAULT_STATUS' do
|
||||
it 'is a status created' do
|
||||
expect(described_class::DEFAULT_STATUS).to eq 'created'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue