Commit 79b8f02b authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'pipeline-blocking-actions' into 'master'

Make manual actions blocking

Closes #26360 and #22628

See merge request !9585
parents b696cbc5 3b35d1f8
...@@ -63,6 +63,10 @@ module Ci ...@@ -63,6 +63,10 @@ module Ci
end end
state_machine :status do state_machine :status do
event :actionize do
transition created: :manual
end
after_transition any => [:pending] do |build| after_transition any => [:pending] do |build|
build.run_after_commit do build.run_after_commit do
BuildQueueWorker.perform_async(id) BuildQueueWorker.perform_async(id)
...@@ -94,16 +98,21 @@ module Ci ...@@ -94,16 +98,21 @@ module Ci
.fabricate! .fabricate!
end end
def manual?
self.when == 'manual'
end
def other_actions def other_actions
pipeline.manual_actions.where.not(name: name) pipeline.manual_actions.where.not(name: name)
end end
def playable? def playable?
project.builds_enabled? && commands.present? && manual? && skipped? project.builds_enabled? && has_commands? &&
action? && manual?
end
def action?
self.when == 'manual'
end
def has_commands?
commands.present?
end end
def play(current_user) def play(current_user)
...@@ -122,7 +131,7 @@ module Ci ...@@ -122,7 +131,7 @@ module Ci
end end
def retryable? def retryable?
project.builds_enabled? && commands.present? && project.builds_enabled? && has_commands? &&
(success? || failed? || canceled?) (success? || failed? || canceled?)
end end
...@@ -552,7 +561,7 @@ module Ci ...@@ -552,7 +561,7 @@ module Ci
] ]
variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag? variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual? variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if action?
variables variables
end end
......
...@@ -49,6 +49,10 @@ module Ci ...@@ -49,6 +49,10 @@ module Ci
transition any - [:canceled] => :canceled transition any - [:canceled] => :canceled
end end
event :block do
transition any - [:manual] => :manual
end
# IMPORTANT # IMPORTANT
# Do not add any operations to this state_machine # Do not add any operations to this state_machine
# Create a separate worker for each new operation # Create a separate worker for each new operation
...@@ -321,6 +325,7 @@ module Ci ...@@ -321,6 +325,7 @@ module Ci
when 'failed' then drop when 'failed' then drop
when 'canceled' then cancel when 'canceled' then cancel
when 'skipped' then skip when 'skipped' then skip
when 'manual' then block
end end
end end
end end
......
...@@ -29,9 +29,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -29,9 +29,11 @@ class CommitStatus < ActiveRecord::Base
end end
scope :exclude_ignored, -> do scope :exclude_ignored, -> do
# We want to ignore failed_but_allowed jobs # We want to ignore failed but allowed to fail jobs.
#
# TODO, we also skip ignored optional manual actions.
where("allow_failure = ? OR status IN (?)", where("allow_failure = ? OR status IN (?)",
false, all_state_names - [:failed, :canceled]) false, all_state_names - [:failed, :canceled, :manual])
end end
scope :retried, -> { where.not(id: latest) } scope :retried, -> { where.not(id: latest) }
...@@ -42,11 +44,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -42,11 +44,11 @@ class CommitStatus < ActiveRecord::Base
state_machine :status do state_machine :status do
event :enqueue do event :enqueue do
transition [:created, :skipped] => :pending transition [:created, :skipped, :manual] => :pending
end end
event :process do event :process do
transition skipped: :created transition [:skipped, :manual] => :created
end end
event :run do event :run do
...@@ -66,7 +68,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -66,7 +68,7 @@ class CommitStatus < ActiveRecord::Base
end end
event :cancel do event :cancel do
transition [:created, :pending, :running] => :canceled transition [:created, :pending, :running, :manual] => :canceled
end end
before_transition created: [:pending, :running] do |commit_status| before_transition created: [:pending, :running] do |commit_status|
...@@ -86,7 +88,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -86,7 +88,7 @@ class CommitStatus < ActiveRecord::Base
commit_status.run_after_commit do commit_status.run_after_commit do
pipeline.try do |pipeline| pipeline.try do |pipeline|
if complete? if complete? || manual?
PipelineProcessWorker.perform_async(pipeline.id) PipelineProcessWorker.perform_async(pipeline.id)
else else
PipelineUpdateWorker.perform_async(pipeline.id) PipelineUpdateWorker.perform_async(pipeline.id)
......
...@@ -2,22 +2,21 @@ module HasStatus ...@@ -2,22 +2,21 @@ module HasStatus
extend ActiveSupport::Concern extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'.freeze DEFAULT_STATUS = 'created'.freeze
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze BLOCKED_STATUS = 'manual'.freeze
STARTED_STATUSES = %w[running success failed skipped].freeze AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped manual].freeze
STARTED_STATUSES = %w[running success failed skipped manual].freeze
ACTIVE_STATUSES = %w[pending running].freeze ACTIVE_STATUSES = %w[pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[failed pending running canceled success skipped].freeze ORDERED_STATUSES = %w[manual failed pending running canceled success skipped].freeze
class_methods do class_methods do
def status_sql def status_sql
scope = if respond_to?(:exclude_ignored) scope = respond_to?(:exclude_ignored) ? exclude_ignored : all
exclude_ignored
else
all
end
builds = scope.select('count(*)').to_sql builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql
manual = scope.manual.select('count(*)').to_sql
pending = scope.pending.select('count(*)').to_sql pending = scope.pending.select('count(*)').to_sql
running = scope.running.select('count(*)').to_sql running = scope.running.select('count(*)').to_sql
skipped = scope.skipped.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql
...@@ -30,7 +29,8 @@ module HasStatus ...@@ -30,7 +29,8 @@ module HasStatus
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' 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})>0 THEN 'running'
WHEN (#{manual})>0 THEN 'manual'
ELSE 'failed' ELSE 'failed'
END)" END)"
end end
...@@ -63,6 +63,7 @@ module HasStatus ...@@ -63,6 +63,7 @@ module HasStatus
state :success, value: 'success' state :success, value: 'success'
state :canceled, value: 'canceled' state :canceled, value: 'canceled'
state :skipped, value: 'skipped' state :skipped, value: 'skipped'
state :manual, value: 'manual'
end end
scope :created, -> { where(status: 'created') } scope :created, -> { where(status: 'created') }
...@@ -73,12 +74,13 @@ module HasStatus ...@@ -73,12 +74,13 @@ module HasStatus
scope :failed, -> { where(status: 'failed') } scope :failed, -> { where(status: 'failed') }
scope :canceled, -> { where(status: 'canceled') } scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') } scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do scope :cancelable, -> do
where(status: [:running, :pending, :created]) where(status: [:running, :pending, :created, :manual])
end end
end end
...@@ -94,6 +96,10 @@ module HasStatus ...@@ -94,6 +96,10 @@ module HasStatus
COMPLETED_STATUSES.include?(status) COMPLETED_STATUSES.include?(status)
end end
def blocked?
BLOCKED_STATUS == status
end
private private
def calculate_duration def calculate_duration
......
...@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity ...@@ -12,7 +12,7 @@ class BuildEntity < Grape::Entity
path_to(:retry_namespace_project_build, build) path_to(:retry_namespace_project_build, build)
end end
expose :play_path, if: ->(build, _) { build.manual? } do |build| expose :play_path, if: ->(build, _) { build.playable? } do |build|
path_to(:play_namespace_project_build, build) path_to(:play_namespace_project_build, build)
end end
......
...@@ -22,6 +22,8 @@ module Ci ...@@ -22,6 +22,8 @@ module Ci
def process_stage(index) def process_stage(index)
current_status = status_for_prior_stages(index) current_status = status_for_prior_stages(index)
return if HasStatus::BLOCKED_STATUS == current_status
if HasStatus::COMPLETED_STATUSES.include?(current_status) if HasStatus::COMPLETED_STATUSES.include?(current_status)
created_builds_in_stage(index).select do |build| created_builds_in_stage(index).select do |build|
Gitlab::OptimisticLocking.retry_lock(build) do |subject| Gitlab::OptimisticLocking.retry_lock(build) do |subject|
...@@ -33,7 +35,7 @@ module Ci ...@@ -33,7 +35,7 @@ module Ci
def process_build(build, current_status) def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status) if valid_statuses_for_when(build.when).include?(current_status)
build.enqueue build.action? ? build.actionize : build.enqueue
true true
else else
build.skip build.skip
...@@ -49,6 +51,8 @@ module Ci ...@@ -49,6 +51,8 @@ module Ci
%w[failed] %w[failed]
when 'always' when 'always'
%w[success failed skipped] %w[success failed skipped]
when 'manual'
%w[success]
else else
[] []
end end
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
%span.label.label-info triggered %span.label.label-info triggered
- if build.try(:allow_failure) - if build.try(:allow_failure)
%span.label.label-danger allowed to fail %span.label.label-danger allowed to fail
- if build.manual? - if build.action?
%span.label.label-info manual %span.label.label-info manual
- if pipeline_link - if pipeline_link
......
---
title: Make it possible to configure blocking manual actions
merge_request: 9585
author:
class MigrateLegacyManualActions < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
execute <<-EOS
UPDATE ci_builds SET status = 'manual', allow_failure = true
WHERE ci_builds.when = 'manual' AND ci_builds.status = 'skipped';
EOS
end
def down
execute <<-EOS
UPDATE ci_builds SET status = 'skipped', allow_failure = false
WHERE ci_builds.when = 'manual' AND ci_builds.status = 'manual';
EOS
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170305203726) do ActiveRecord::Schema.define(version: 20170306170512) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -545,13 +545,30 @@ The above script will: ...@@ -545,13 +545,30 @@ The above script will:
Manual actions are a special type of job that are not executed automatically; Manual actions are a special type of job that are not executed automatically;
they need to be explicitly started by a user. Manual actions can be started they need to be explicitly started by a user. Manual actions can be started
from pipeline, build, environment, and deployment views. You can execute the from pipeline, build, environment, and deployment views.
same manual action multiple times.
An example usage of manual actions is deployment to production. An example usage of manual actions is deployment to production.
Read more at the [environments documentation][env-manual]. Read more at the [environments documentation][env-manual].
Manual actions can be either optional or blocking. Blocking manual action will
block execution of the pipeline at stage this action is defined in. It is
possible to resume execution of the pipeline when someone executes a blocking
manual actions by clicking a _play_ button.
When pipeline is blocked it will not be merged if Merge When Pipeline Succeeds
is set. Blocked pipelines also do have a special status, called _manual_.
Manual actions are non-blocking by default. If you want to make manual action
blocking, it is necessary to add `allow_failure: false` to the job's definition
in `.gitlab-ci.yml`.
Optional manual actions have `allow_failure: true` set by default.
**Statuses of optional actions do not contribute to overall pipeline status.**
> Blocking manual actions were introduced in GitLab 9.0
### environment ### environment
> >
......
...@@ -58,7 +58,7 @@ module Ci ...@@ -58,7 +58,7 @@ module Ci
commands: job[:commands], commands: job[:commands],
tag_list: job[:tags] || [], tag_list: job[:tags] || [],
name: job[:name].to_s, name: job[:name].to_s,
allow_failure: job[:allow_failure] || false, allow_failure: job[:ignore],
when: job[:when] || 'on_success', when: job[:when] || 'on_success',
environment: job[:environment_name], environment: job[:environment_name],
coverage_regex: job[:coverage], coverage_regex: job[:coverage],
......
...@@ -104,6 +104,14 @@ module Gitlab ...@@ -104,6 +104,14 @@ module Gitlab
(before_script_value.to_a + script_value.to_a).join("\n") (before_script_value.to_a + script_value.to_a).join("\n")
end end
def manual_action?
self.when == 'manual'
end
def ignored?
allow_failure.nil? ? manual_action? : allow_failure
end
private private
def inherit!(deps) def inherit!(deps)
...@@ -135,7 +143,8 @@ module Gitlab ...@@ -135,7 +143,8 @@ module Gitlab
environment_name: environment_defined? ? environment_value[:name] : nil, environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil, coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value, artifacts: artifacts_value,
after_script: after_script_value } after_script: after_script_value,
ignore: ignored? }
end end
end end
end end
......
...@@ -5,22 +5,10 @@ module Gitlab ...@@ -5,22 +5,10 @@ module Gitlab
class Play < SimpleDelegator class Play < SimpleDelegator
include Status::Extended include Status::Extended
def text
'manual'
end
def label def label
'manual play action' 'manual play action'
end end
def icon
'icon_status_manual'
end
def group
'manual'
end
def has_action? def has_action?
can?(user, :update_build, subject) can?(user, :update_build, subject)
end end
......
...@@ -5,22 +5,10 @@ module Gitlab ...@@ -5,22 +5,10 @@ module Gitlab
class Stop < SimpleDelegator class Stop < SimpleDelegator
include Status::Extended include Status::Extended
def text
'manual'
end
def label def label
'manual stop action' 'manual stop action'
end end
def icon
'icon_status_manual'
end
def group
'manual'
end
def has_action? def has_action?
can?(user, :update_build, subject) can?(user, :update_build, subject)
end end
......
module Gitlab
module Ci
module Status
class Manual < Status::Core
def text
'manual'
end
def label
'manual action'
end
def icon
'icon_status_manual'
end
end
end
end
end
...@@ -39,7 +39,7 @@ module Gitlab ...@@ -39,7 +39,7 @@ module Gitlab
started_at: build.started_at, started_at: build.started_at,
finished_at: build.finished_at, finished_at: build.finished_at,
when: build.when, when: build.when,
manual: build.manual?, manual: build.action?,
user: build.user.try(:hook_attrs), user: build.user.try(:hook_attrs),
runner: build.runner && runner_hook_attrs(build.runner), runner: build.runner && runner_hook_attrs(build.runner),
artifacts_file: { artifacts_file: {
......
...@@ -57,7 +57,7 @@ FactoryGirl.define do ...@@ -57,7 +57,7 @@ FactoryGirl.define do
end end
trait :manual do trait :manual do
status 'skipped' status 'manual'
self.when 'manual' self.when 'manual'
end end
...@@ -71,8 +71,11 @@ FactoryGirl.define do ...@@ -71,8 +71,11 @@ FactoryGirl.define do
allow_failure true allow_failure true
end end
trait :ignored do
allowed_to_fail
end
trait :playable do trait :playable do
skipped
manual manual
end end
......
...@@ -35,6 +35,10 @@ FactoryGirl.define do ...@@ -35,6 +35,10 @@ FactoryGirl.define do
status 'created' status 'created'
end end
trait :manual do
status 'manual'
end
after(:build) do |build, evaluator| after(:build) do |build, evaluator|
build.project = build.pipeline.project build.project = build.pipeline.project
end end
......
...@@ -13,7 +13,7 @@ feature 'Environment', :feature do ...@@ -13,7 +13,7 @@ feature 'Environment', :feature do
feature 'environment details page' do feature 'environment details page' do
given!(:environment) { create(:environment, project: project) } given!(:environment) { create(:environment, project: project) }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { } given!(:action) { }
before do before do
visit_environment(environment) visit_environment(environment)
...@@ -69,17 +69,23 @@ feature 'Environment', :feature do ...@@ -69,17 +69,23 @@ feature 'Environment', :feature do
end end
context 'with manual action' do context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } given(:action) do
create(:ci_build, :manual, pipeline: pipeline,
name: 'deploy to production')
end
scenario 'does show a play button' do scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize) expect(page).to have_link(action.name.humanize)
end end
scenario 'does allow to play manual action' do scenario 'does allow to play manual action' do
expect(manual).to be_skipped expect(action).to be_manual
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name) expect { click_link(action.name.humanize) }
expect(manual.reload).to be_pending .not_to change { Ci::Pipeline.count }
expect(page).to have_content(action.name)
expect(action.reload).to be_pending
end end
context 'with external_url' do context 'with external_url' do
...@@ -130,8 +136,16 @@ feature 'Environment', :feature do ...@@ -130,8 +136,16 @@ feature 'Environment', :feature do
context 'when environment is available' do context 'when environment is available' do
context 'with stop action' do context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:action) do
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } create(:ci_build, :manual, pipeline: pipeline,
name: 'close_app')
end
given(:deployment) do
create(:deployment, environment: environment,
deployable: build,
on_stop: 'close_app')
end
scenario 'does show stop button' do scenario 'does show stop button' do
expect(page).to have_link('Stop') expect(page).to have_link('Stop')
......
...@@ -12,7 +12,7 @@ feature 'Environments page', :feature, :js do ...@@ -12,7 +12,7 @@ feature 'Environments page', :feature, :js do
given!(:environment) { } given!(:environment) { }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { } given!(:action) { }
before do before do
visit_environments(project) visit_environments(project)
...@@ -90,7 +90,7 @@ feature 'Environments page', :feature, :js do ...@@ -90,7 +90,7 @@ feature 'Environments page', :feature, :js do
given(:pipeline) { create(:ci_pipeline, project: project) } given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) } given(:build) { create(:ci_build, pipeline: pipeline) }
given(:manual) do given(:action) do
create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production')
end end
...@@ -102,19 +102,19 @@ feature 'Environments page', :feature, :js do ...@@ -102,19 +102,19 @@ feature 'Environments page', :feature, :js do
scenario 'does show a play button' do scenario 'does show a play button' do
find('.js-dropdown-play-icon-container').click find('.js-dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize) expect(page).to have_content(action.name.humanize)
end end
scenario 'does allow to play manual action', js: true do scenario 'does allow to play manual action', js: true do
expect(manual).to be_skipped expect(action).to be_manual
find('.js-dropdown-play-icon-container').click find('.js-dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize) expect(page).to have_content(action.name.humanize)
expect { click_link(manual.name.humanize) } expect { click_link(action.name.humanize) }
.not_to change { Ci::Pipeline.count } .not_to change { Ci::Pipeline.count }
expect(manual.reload).to be_pending expect(action.reload).to be_pending
end end
scenario 'does show build name and id' do scenario 'does show build name and id' do
...@@ -144,8 +144,15 @@ feature 'Environments page', :feature, :js do ...@@ -144,8 +144,15 @@ feature 'Environments page', :feature, :js do
end end
context 'with stop action' do context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:action) do
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } create(:ci_build, :manual, pipeline: pipeline, name: 'close_app')
end
given(:deployment) do
create(:deployment, environment: environment,
deployable: build,
on_stop: 'close_app')
end
scenario 'does show stop button' do scenario 'does show stop button' do
expect(page).to have_selector('.stop-env-link') expect(page).to have_selector('.stop-env-link')
......
...@@ -15,9 +15,9 @@ module Ci ...@@ -15,9 +15,9 @@ module Ci
end end
describe '#build_attributes' do describe '#build_attributes' do
describe 'coverage entry' do subject { described_class.new(config, path).build_attributes(:rspec) }
subject { described_class.new(config, path).build_attributes(:rspec) }
describe 'coverage entry' do
describe 'code coverage regexp' do describe 'code coverage regexp' do
let(:config) do let(:config) do
YAML.dump(rspec: { script: 'rspec', YAML.dump(rspec: { script: 'rspec',
...@@ -30,6 +30,56 @@ module Ci ...@@ -30,6 +30,56 @@ module Ci
end end
end end
end end
describe 'allow failure entry' do
context 'when job is a manual action' do
context 'when allow_failure is defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec',
when: 'manual',
allow_failure: false })
end
it 'is not allowed to fail' do
expect(subject[:allow_failure]).to be false
end
end
context 'when allow_failure is not defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec',
when: 'manual' })
end
it 'is allowed to fail' do
expect(subject[:allow_failure]).to be true
end
end
end
context 'when job is not a manual action' do
context 'when allow_failure is defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec',
allow_failure: false })
end
it 'is not allowed to fail' do
expect(subject[:allow_failure]).to be false
end
end
context 'when allow_failure is not defined' do
let(:config) do
YAML.dump(rspec: { script: 'rspec' })
end
it 'is not allowed to fail' do
expect(subject[:allow_failure]).to be false
end
end
end
end
end end
describe "#builds_for_ref" do describe "#builds_for_ref" do
......
...@@ -155,6 +155,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -155,6 +155,7 @@ describe Gitlab::Ci::Config::Entry::Global do
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { VAR: 'value' }, variables: { VAR: 'value' },
ignore: false,
after_script: ['make clean'] }, after_script: ['make clean'] },
spinach: { name: :spinach, spinach: { name: :spinach,
before_script: [], before_script: [],
...@@ -165,6 +166,7 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -165,6 +166,7 @@ describe Gitlab::Ci::Config::Entry::Global do
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {}, variables: {},
ignore: false,
after_script: ['make clean'] }, after_script: ['make clean'] },
) )
end end
......
...@@ -144,6 +144,7 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -144,6 +144,7 @@ describe Gitlab::Ci::Config::Entry::Job do
script: %w[rspec], script: %w[rspec],
commands: "ls\npwd\nrspec", commands: "ls\npwd\nrspec",
stage: 'test', stage: 'test',
ignore: false,
after_script: %w[cleanup]) after_script: %w[cleanup])
end end
end end
...@@ -159,4 +160,82 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -159,4 +160,82 @@ describe Gitlab::Ci::Config::Entry::Job do
end end
end end
end end
describe '#manual_action?' do
context 'when job is a manual action' do
let(:config) { { script: 'deploy', when: 'manual' } }
it 'is a manual action' do
expect(entry).to be_manual_action
end
end
context 'when job is not a manual action' do
let(:config) { { script: 'deploy' } }
it 'is not a manual action' do
expect(entry).not_to be_manual_action
end
end
end
describe '#ignored?' do
context 'when job is a manual action' do
context 'when it is not specified if job is allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual' }
end
it 'is an ignored job' do
expect(entry).to be_ignored
end
end
context 'when job is allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual', allow_failure: true }
end
it 'is an ignored job' do
expect(entry).to be_ignored
end
end
context 'when job is not allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual', allow_failure: false }
end
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
end
context 'when job is not a manual action' do
context 'when it is not specified if job is allowed to fail' do
let(:config) { { script: 'deploy' } }
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
context 'when job is allowed to fail' do
let(:config) { { script: 'deploy', allow_failure: true } }
it 'is an ignored job' do
expect(entry).to be_ignored
end
end
context 'when job is not allowed to fail' do
let(:config) { { script: 'deploy', allow_failure: false } }
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
end
end
end end
...@@ -62,10 +62,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do ...@@ -62,10 +62,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do
rspec: { name: :rspec, rspec: { name: :rspec,
script: %w[rspec], script: %w[rspec],
commands: 'rspec', commands: 'rspec',
ignore: false,
stage: 'test' }, stage: 'test' },
spinach: { name: :spinach, spinach: { name: :spinach,
script: %w[spinach], script: %w[spinach],
commands: 'spinach', commands: 'spinach',
ignore: false,
stage: 'test' }) stage: 'test' })
end end
end end
......
...@@ -192,7 +192,7 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -192,7 +192,7 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:build) { create(:ci_build, :playable) } let(:build) { create(:ci_build, :playable) }
it 'matches correct core status' do it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual
end end
it 'matches correct extended statuses' do it 'matches correct extended statuses' do
...@@ -200,12 +200,13 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -200,12 +200,13 @@ describe Gitlab::Ci::Status::Build::Factory do
.to eq [Gitlab::Ci::Status::Build::Play] .to eq [Gitlab::Ci::Status::Build::Play]
end end
it 'fabricates a core skipped status' do it 'fabricates a play detailed status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Play expect(status).to be_a Gitlab::Ci::Status::Build::Play
end end
it 'fabricates status with correct details' do it 'fabricates status with correct details' do
expect(status.text).to eq 'manual' expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual' expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual play action' expect(status.label).to eq 'manual play action'
expect(status).to have_details expect(status).to have_details
...@@ -218,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -218,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:build) { create(:ci_build, :playable, :teardown_environment) } let(:build) { create(:ci_build, :playable, :teardown_environment) }
it 'matches correct core status' do it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual
end end
it 'matches correct extended statuses' do it 'matches correct extended statuses' do
...@@ -226,12 +227,13 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -226,12 +227,13 @@ describe Gitlab::Ci::Status::Build::Factory do
.to eq [Gitlab::Ci::Status::Build::Stop] .to eq [Gitlab::Ci::Status::Build::Stop]
end end
it 'fabricates a core skipped status' do it 'fabricates a stop detailed status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Stop expect(status).to be_a Gitlab::Ci::Status::Build::Stop
end end
it 'fabricates status with correct details' do it 'fabricates status with correct details' do
expect(status.text).to eq 'manual' expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual' expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual stop action' expect(status.label).to eq 'manual stop action'
expect(status).to have_details expect(status).to have_details
......
...@@ -6,22 +6,10 @@ describe Gitlab::Ci::Status::Build::Play do ...@@ -6,22 +6,10 @@ describe Gitlab::Ci::Status::Build::Play do
subject { described_class.new(status) } subject { described_class.new(status) }
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do describe '#label' do
it { expect(subject.label).to eq 'manual play action' } it { expect(subject.label).to eq 'manual play action' }
end end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe '#group' do
it { expect(subject.group).to eq 'manual' }
end
describe 'action details' do describe 'action details' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
......
...@@ -8,22 +8,10 @@ describe Gitlab::Ci::Status::Build::Stop do ...@@ -8,22 +8,10 @@ describe Gitlab::Ci::Status::Build::Stop do
described_class.new(status) described_class.new(status)
end end
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do describe '#label' do
it { expect(subject.label).to eq 'manual stop action' } it { expect(subject.label).to eq 'manual stop action' }
end end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe '#group' do
it { expect(subject.group).to eq 'manual' }
end
describe 'action details' do describe 'action details' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Canceled do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Canceled do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'canceled' } it { expect(subject.text).to eq 'canceled' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Created do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Created do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'created' } it { expect(subject.text).to eq 'created' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Failed do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Failed do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'failed' } it { expect(subject.text).to eq 'failed' }
end end
describe '#label' do describe '#label' do
......
require 'spec_helper'
describe Gitlab::Ci::Status::Manual do
subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do
it { expect(subject.label).to eq 'manual action' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe '#group' do
it { expect(subject.group).to eq 'manual' }
end
end
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Pending do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Pending do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'pending' } it { expect(subject.text).to eq 'pending' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Running do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Running do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'running' } it { expect(subject.text).to eq 'running' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Skipped do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Skipped do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'skipped' } it { expect(subject.text).to eq 'skipped' }
end end
describe '#label' do describe '#label' do
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Success do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Success do
end end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'passed' } it { expect(subject.text).to eq 'passed' }
end end
describe '#label' do describe '#label' do
......
...@@ -20,6 +20,30 @@ describe Ci::Build, :models do ...@@ -20,6 +20,30 @@ describe Ci::Build, :models do
it { is_expected.to validate_presence_of :ref } it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :trace_html } it { is_expected.to respond_to :trace_html }
describe '#actionize' do
context 'when build is a created' do
before do
build.update_column(:status, :created)
end
it 'makes build a manual action' do
expect(build.actionize).to be true
expect(build.reload).to be_manual
end
end
context 'when build is not created' do
before do
build.update_column(:status, :pending)
end
it 'does not change build status' do
expect(build.actionize).to be false
expect(build.reload).to be_pending
end
end
end
describe '#any_runners_online?' do describe '#any_runners_online?' do
subject { build.any_runners_online? } subject { build.any_runners_online? }
...@@ -587,13 +611,21 @@ describe Ci::Build, :models do ...@@ -587,13 +611,21 @@ describe Ci::Build, :models do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'and build.status is failed' do context 'and build status is failed' do
before do before do
build.status = 'failed' build.status = 'failed'
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
context 'when build is a manual action' do
before do
build.status = 'manual'
end
it { is_expected.to be_falsey }
end
end end
end end
...@@ -682,12 +714,12 @@ describe Ci::Build, :models do ...@@ -682,12 +714,12 @@ describe Ci::Build, :models do
end end
end end
describe '#manual?' do describe '#action?' do
before do before do
build.update(when: value) build.update(when: value)
end end
subject { build.manual? } subject { build.action? }
context 'when is set to manual' do context 'when is set to manual' do
let(:value) { 'manual' } let(:value) { 'manual' }
...@@ -703,14 +735,50 @@ describe Ci::Build, :models do ...@@ -703,14 +735,50 @@ describe Ci::Build, :models do
end end
end end
describe '#has_commands?' do
context 'when build has commands' do
let(:build) do
create(:ci_build, commands: 'rspec')
end
it 'has commands' do
expect(build).to have_commands
end
end
context 'when does not have commands' do
context 'when commands are an empty string' do
let(:build) do
create(:ci_build, commands: '')
end
it 'has no commands' do
expect(build).not_to have_commands
end
end
context 'when commands are not set at all' do
let(:build) do
create(:ci_build, commands: nil)
end
it 'has no commands' do
expect(build).not_to have_commands
end
end
end
end
describe '#has_tags?' do describe '#has_tags?' do
context 'when build has tags' do context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) } subject { create(:ci_build, tag_list: ['tag']) }
it { is_expected.to have_tags } it { is_expected.to have_tags }
end end
context 'when build does not have tags' do context 'when build does not have tags' do
subject { create(:ci_build, tag_list: []) } subject { create(:ci_build, tag_list: []) }
it { is_expected.not_to have_tags } it { is_expected.not_to have_tags }
end end
end end
......
...@@ -24,6 +24,14 @@ describe Ci::Pipeline, models: true do ...@@ -24,6 +24,14 @@ 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 }
describe '#block' do
it 'changes pipeline status to manual' do
expect(pipeline.block).to be true
expect(pipeline.reload).to be_manual
expect(pipeline.reload).to be_blocked
end
end
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
...@@ -635,6 +643,14 @@ describe Ci::Pipeline, models: true do ...@@ -635,6 +643,14 @@ describe Ci::Pipeline, models: true do
end end
end end
context 'when pipeline is blocked' do
let(:pipeline) { create(:ci_pipeline, status: :manual) }
it 'returns detailed status for blocked pipeline' do
expect(subject.text).to eq 'manual'
end
end
context 'when pipeline is successful but with warnings' do context 'when pipeline is successful but with warnings' do
let(:pipeline) { create(:ci_pipeline, status: :success) } let(:pipeline) { create(:ci_pipeline, status: :success) }
......
...@@ -158,7 +158,7 @@ describe CommitStatus, :models do ...@@ -158,7 +158,7 @@ describe CommitStatus, :models do
end end
end end
describe '.exclude_ignored' do describe '.after_stage' do
subject { described_class.after_stage(0) } subject { described_class.after_stage(0) }
let(:statuses) do let(:statuses) do
...@@ -185,11 +185,32 @@ describe CommitStatus, :models do ...@@ -185,11 +185,32 @@ describe CommitStatus, :models do
create_status(allow_failure: true, status: 'success'), create_status(allow_failure: true, status: 'success'),
create_status(allow_failure: true, status: 'failed'), create_status(allow_failure: true, status: 'failed'),
create_status(allow_failure: false, status: 'success'), create_status(allow_failure: false, status: 'success'),
create_status(allow_failure: false, status: 'failed')] create_status(allow_failure: false, status: 'failed'),
create_status(allow_failure: true, status: 'manual'),
create_status(allow_failure: false, status: 'manual')]
end
it 'returns statuses without what we want to ignore' do
is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9, 11))
end
end
describe '.failed_but_allowed' do
subject { described_class.failed_but_allowed.order(:id) }
let(:statuses) do
[create_status(allow_failure: true, status: 'success'),
create_status(allow_failure: true, status: 'failed'),
create_status(allow_failure: false, status: 'success'),
create_status(allow_failure: false, status: 'failed'),
create_status(allow_failure: true, status: 'canceled'),
create_status(allow_failure: false, status: 'canceled'),
create_status(allow_failure: true, status: 'manual'),
create_status(allow_failure: false, status: 'manual')]
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(0, 1, 2, 3, 4, 5, 6, 8, 9)) is_expected.to eq(statuses.values_at(1, 4))
end end
end end
......
...@@ -109,6 +109,24 @@ describe HasStatus do ...@@ -109,6 +109,24 @@ describe HasStatus do
it { is_expected.to eq 'running' } it { is_expected.to eq 'running' }
end end
context 'when one status is a blocking manual action' do
let!(:statuses) do
[create(type, status: :failed),
create(type, status: :manual, allow_failure: false)]
end
it { is_expected.to eq 'manual' }
end
context 'when one status is a non-blocking manual action' do
let!(:statuses) do
[create(type, status: :failed),
create(type, status: :manual, allow_failure: true)]
end
it { is_expected.to eq 'failed' }
end
end end
context 'ci build statuses' do context 'ci build statuses' do
...@@ -218,6 +236,18 @@ describe HasStatus do ...@@ -218,6 +236,18 @@ describe HasStatus do
it_behaves_like 'not containing the job', status it_behaves_like 'not containing the job', status
end end
end end
describe '.manual' do
subject { CommitStatus.manual }
%i[manual].each do |status|
it_behaves_like 'containing the job', status
end
%i[failed success skipped canceled].each do |status|
it_behaves_like 'not containing the job', status
end
end
end end
describe '::DEFAULT_STATUS' do describe '::DEFAULT_STATUS' do
...@@ -225,4 +255,10 @@ describe HasStatus do ...@@ -225,4 +255,10 @@ describe HasStatus do
expect(described_class::DEFAULT_STATUS).to eq 'created' expect(described_class::DEFAULT_STATUS).to eq 'created'
end end
end end
describe '::BLOCKED_STATUS' do
it 'is a status manual' do
expect(described_class::BLOCKED_STATUS).to eq 'manual'
end
end
end end
require 'spec_helper' require 'spec_helper'
describe Ci::ProcessPipelineService, :services do describe Ci::ProcessPipelineService, '#execute', :services do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
...@@ -12,379 +12,518 @@ describe Ci::ProcessPipelineService, :services do ...@@ -12,379 +12,518 @@ describe Ci::ProcessPipelineService, :services do
project.add_developer(user) project.add_developer(user)
end end
describe '#execute' do context 'when simple pipeline is defined' do
context 'start queuing next builds' do before do
before do create_build('linux', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0) create_build('mac', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage_idx: 0) create_build('rspec', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'rspec', stage_idx: 1) create_build('rubocop', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'rubocop', stage_idx: 1) create_build('deploy', stage_idx: 2)
create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 2) end
end
it 'processes a pipeline' do it 'processes a pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
succeed_pending
expect(builds.success.count).to eq(2)
expect(process_pipeline).to be_truthy succeed_pending
succeed_pending
expect(builds.success.count).to eq(4) 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_build('clean_job', stage_idx: 0, allow_failure: true)
create_build('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_statuses).to eq ['pending']
fail_running_or_pending
expect(builds_statuses).to eq %w(failed pending)
end
end
context 'when optional manual actions are defined' do
before do
create_build('build', stage_idx: 0)
create_build('test', stage_idx: 1)
create_build('test_failure', stage_idx: 2, when: 'on_failure')
create_build('deploy', stage_idx: 3)
create_build('production', stage_idx: 3, when: 'manual', allow_failure: true)
create_build('cleanup', stage_idx: 4, when: 'always')
create_build('clear:cache', stage_idx: 4, when: 'manual', allow_failure: true)
end
context 'when builds are successful' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
succeed_pending expect(builds_names).to eq ['build']
expect(builds.success.count).to eq(5) expect(builds_statuses).to eq ['pending']
succeed_running_or_pending
expect(builds_names).to eq %w(build test)
expect(builds_statuses).to eq %w(success pending)
expect(process_pipeline).to be_falsey succeed_running_or_pending
expect(builds_names).to eq %w(build test deploy production)
expect(builds_statuses).to eq %w(success success pending manual)
succeed_running_or_pending
expect(builds_names).to eq %w(build test deploy production cleanup clear:cache)
expect(builds_statuses).to eq %w(success success success manual pending manual)
succeed_running_or_pending
expect(builds_statuses).to eq %w(success success success manual success manual)
expect(pipeline.reload.status).to eq 'success'
end end
end
it 'does not process pipeline if existing stage is running' do context 'when test job fails' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds.pending.count).to eq(2) expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending']
succeed_running_or_pending
expect(builds_names).to eq %w(build test)
expect(builds_statuses).to eq %w(success pending)
expect(process_pipeline).to be_falsey fail_running_or_pending
expect(builds.pending.count).to eq(2)
expect(builds_names).to eq %w(build test test_failure)
expect(builds_statuses).to eq %w(success failed pending)
succeed_running_or_pending
expect(builds_names).to eq %w(build test test_failure cleanup)
expect(builds_statuses).to eq %w(success failed success pending)
succeed_running_or_pending
expect(builds_statuses).to eq %w(success failed success success)
expect(pipeline.reload.status).to eq 'failed'
end end
end end
context 'custom stage with first job allowed to fail' do context 'when test and test_failure jobs fail' do
before do it 'properly processes the pipeline' do
create(:ci_build, :created, pipeline: pipeline, name: 'clean_job', stage_idx: 0, allow_failure: true) expect(process_pipeline).to be_truthy
create(:ci_build, :created, pipeline: pipeline, name: 'test_job', stage_idx: 1, allow_failure: true) expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending']
succeed_running_or_pending
expect(builds_names).to eq %w(build test)
expect(builds_statuses).to eq %w(success pending)
fail_running_or_pending
expect(builds_names).to eq %w(build test test_failure)
expect(builds_statuses).to eq %w(success failed pending)
fail_running_or_pending
expect(builds_names).to eq %w(build test test_failure cleanup)
expect(builds_statuses).to eq %w(success failed failed pending)
succeed_running_or_pending
expect(builds_names).to eq %w(build test test_failure cleanup)
expect(builds_statuses).to eq %w(success failed failed success)
expect(pipeline.reload.status).to eq('failed')
end end
end
it 'automatically triggers a next stage when build finishes' do context 'when deploy job fails' do
it 'properly processes the pipeline' do
expect(process_pipeline).to be_truthy expect(process_pipeline).to be_truthy
expect(builds.pluck(:status)).to contain_exactly('pending') expect(builds_names).to eq ['build']
expect(builds_statuses).to eq ['pending']
succeed_running_or_pending
pipeline.builds.running_or_pending.each(&:drop) expect(builds_names).to eq %w(build test)
expect(builds.pluck(:status)).to contain_exactly('failed', 'pending') expect(builds_statuses).to eq %w(success pending)
succeed_running_or_pending
expect(builds_names).to eq %w(build test deploy production)
expect(builds_statuses).to eq %w(success success pending manual)
fail_running_or_pending
expect(builds_names).to eq %w(build test deploy production cleanup)
expect(builds_statuses).to eq %w(success success failed manual pending)
succeed_running_or_pending
expect(builds_statuses).to eq %w(success success failed manual success)
expect(pipeline.reload).to be_failed
end end
end end
context 'properly creates builds when "when" is defined' do context 'when build is canceled in the second stage' do
before do it 'does not schedule builds after build has been canceled' do
create(:ci_build, :created, pipeline: pipeline, name: 'build', stage_idx: 0) expect(process_pipeline).to be_truthy
create(:ci_build, :created, pipeline: pipeline, name: 'test', stage_idx: 1) expect(builds_names).to eq ['build']
create(:ci_build, :created, pipeline: pipeline, name: 'test_failure', stage_idx: 2, when: 'on_failure') expect(builds_statuses).to eq ['pending']
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 succeed_running_or_pending
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 expect(builds.running_or_pending).not_to be_empty
it 'properly creates builds' do expect(builds_names).to eq %w(build test)
expect(process_pipeline).to be_truthy expect(builds_statuses).to eq %w(success pending)
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 cancel_running_or_pending
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 expect(builds.running_or_pending).to be_empty
it 'properly creates builds' do expect(builds_names).to eq %w[build test]
expect(process_pipeline).to be_truthy expect(builds_statuses).to eq %w[success canceled]
expect(builds.pluck(:name)).to contain_exactly('build') expect(pipeline.reload).to be_canceled
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 end
end
context 'when build is canceled in the second stage' do context 'when listing optional manual actions' do
it 'does not schedule builds after build has been canceled' do it 'returns only for skipped builds' do
expect(process_pipeline).to be_truthy # currently all builds are created
expect(builds.pluck(:name)).to contain_exactly('build') expect(process_pipeline).to be_truthy
expect(builds.pluck(:status)).to contain_exactly('pending') expect(manual_actions).to be_empty
pipeline.builds.running_or_pending.each(&:success)
expect(builds.running_or_pending).not_to be_empty # succeed stage build
succeed_running_or_pending
expect(builds.pluck(:name)).to contain_exactly('build', 'test') expect(manual_actions).to be_empty
expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
pipeline.builds.running_or_pending.each(&:cancel)
expect(builds.running_or_pending).to be_empty # succeed stage test
expect(pipeline.reload.status).to eq('canceled') succeed_running_or_pending
end
end expect(manual_actions).to be_one # production
# succeed stage deploy
succeed_running_or_pending
context 'when listing manual actions' do expect(manual_actions).to be_many # production and clear cache
it 'returns only for skipped builds' do end
# currently all builds are created end
expect(process_pipeline).to be_truthy end
expect(manual_actions).to be_empty
# succeed stage build context 'when there are manual action in earlier stages' do
pipeline.builds.running_or_pending.each(&:success) context 'when first stage has only optional manual actions' do
expect(manual_actions).to be_empty before do
create_build('build', stage_idx: 0, when: 'manual', allow_failure: true)
create_build('check', stage_idx: 1)
create_build('test', stage_idx: 2)
# succeed stage test process_pipeline
pipeline.builds.running_or_pending.each(&:success) end
expect(manual_actions).to be_one # production
# succeed stage deploy it 'starts from the second stage' do
pipeline.builds.running_or_pending.each(&:success) expect(all_builds_statuses).to eq %w[manual pending created]
expect(manual_actions).to be_many # production and clear cache
end
end end
end end
context 'when there are manual/on_failure jobs in earlier stages' do context 'when second stage has only optional manual actions' do
before do before do
builds create_build('check', stage_idx: 0)
create_build('build', stage_idx: 1, when: 'manual', allow_failure: true)
create_build('test', stage_idx: 2)
process_pipeline process_pipeline
builds.each(&:reload)
end end
context 'when first stage has only manual jobs' do it 'skips second stage and continues on third stage' do
let(:builds) do expect(all_builds_statuses).to eq(%w[pending created created])
[create_build('build', 0, 'manual'),
create_build('check', 1),
create_build('test', 2)]
end
it 'starts from the second stage' do builds.first.success
expect(builds.map(&:status)).to eq(%w[skipped pending created])
end expect(all_builds_statuses).to eq(%w[success manual pending])
end end
end
end
context 'when blocking manual actions are defined' do
before do
create_build('code:test', stage_idx: 0)
create_build('staging:deploy', stage_idx: 1, when: 'manual')
create_build('staging:test', stage_idx: 2, when: 'on_success')
create_build('production:deploy', stage_idx: 3, when: 'manual')
create_build('production:test', stage_idx: 4, when: 'always')
end
context 'when second stage has only manual jobs' do context 'when first stage succeeds' do
let(:builds) do it 'blocks pipeline on stage with first manual action' do
[create_build('check', 0), process_pipeline
create_build('build', 1, 'manual'),
create_build('test', 2)]
end
it 'skips second stage and continues on third stage' do expect(builds_names).to eq %w[code:test]
expect(builds.map(&:status)).to eq(%w[pending created created]) expect(builds_statuses).to eq %w[pending]
expect(pipeline.reload.status).to eq 'pending'
builds.first.success succeed_running_or_pending
builds.each(&:reload)
expect(builds.map(&:status)).to eq(%w[success skipped pending]) expect(builds_names).to eq %w[code:test staging:deploy]
end expect(builds_statuses).to eq %w[success manual]
expect(pipeline.reload).to be_manual
end end
end
context 'when first stage fails' do
it 'does not take blocking action into account' do
process_pipeline
expect(builds_names).to eq %w[code:test]
expect(builds_statuses).to eq %w[pending]
expect(pipeline.reload.status).to eq 'pending'
context 'when second stage has only on_failure jobs' do fail_running_or_pending
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_names).to eq %w[code:test production:test]
expect(builds.map(&:status)).to eq(%w[pending created created]) expect(builds_statuses).to eq %w[failed pending]
builds.first.success succeed_running_or_pending
builds.each(&:reload)
expect(builds.map(&:status)).to eq(%w[success skipped pending]) expect(builds_statuses).to eq %w[failed success]
end expect(pipeline.reload).to be_failed
end end
end end
context 'when failed build in the middle stage is retried' do context 'when pipeline is promoted sequentially up to the end' do
context 'when failed build is the only unsuccessful build in the stage' do it 'properly processes entire pipeline' do
before do process_pipeline
create(:ci_build, :created, pipeline: pipeline, name: 'build:1', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'build:2', stage_idx: 0) expect(builds_names).to eq %w[code:test]
create(:ci_build, :created, pipeline: pipeline, name: 'test:1', stage_idx: 1) expect(builds_statuses).to eq %w[pending]
create(:ci_build, :created, pipeline: pipeline, name: 'test:2', stage_idx: 1)
create(:ci_build, :created, pipeline: pipeline, name: 'deploy:1', stage_idx: 2) succeed_running_or_pending
create(:ci_build, :created, pipeline: pipeline, name: 'deploy:2', stage_idx: 2)
end expect(builds_names).to eq %w[code:test staging:deploy]
expect(builds_statuses).to eq %w[success manual]
expect(pipeline.reload).to be_manual
play_manual_action('staging:deploy')
expect(builds_statuses).to eq %w[success pending]
succeed_running_or_pending
expect(builds_names).to eq %w[code:test staging:deploy staging:test]
expect(builds_statuses).to eq %w[success success pending]
succeed_running_or_pending
it 'does trigger builds in the next stage' do expect(builds_names).to eq %w[code:test staging:deploy staging:test
expect(process_pipeline).to be_truthy production:deploy]
expect(builds.pluck(:name)).to contain_exactly('build:1', 'build:2') expect(builds_statuses).to eq %w[success success success manual]
pipeline.builds.running_or_pending.each(&:success) expect(pipeline.reload).to be_manual
expect(pipeline.reload).to be_blocked
expect(pipeline.reload).not_to be_active
expect(pipeline.reload).not_to be_complete
expect(builds.pluck(:name)) play_manual_action('production:deploy')
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
pipeline.builds.find_by(name: 'test:1').success expect(builds_statuses).to eq %w[success success success pending]
pipeline.builds.find_by(name: 'test:2').drop expect(pipeline.reload).to be_running
expect(builds.pluck(:name)) succeed_running_or_pending
.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_names).to eq %w[code:test staging:deploy staging:test
production:deploy production:test]
expect(builds_statuses).to eq %w[success success success success pending]
expect(pipeline.reload).to be_running
expect(builds.pluck(:name)).to contain_exactly( succeed_running_or_pending
'build:1', 'build:2', 'test:1', 'test:2', 'test:2', 'deploy:1', 'deploy:2')
end expect(builds_names).to eq %w[code:test staging:deploy staging:test
production:deploy production:test]
expect(builds_statuses).to eq %w[success success success success success]
expect(pipeline.reload).to be_success
end end
end end
end
context 'when there are builds that are not created yet' do context 'when second stage has only on_failure jobs' do
let(:pipeline) do before do
create(:ci_pipeline, config: config) create_build('check', stage_idx: 0)
end create_build('build', stage_idx: 1, when: 'on_failure')
create_build('test', stage_idx: 2)
let(:config) do process_pipeline
{ rspec: { stage: 'test', script: 'rspec' }, end
deploy: { stage: 'deploy', script: 'rsync' } }
end it 'skips second stage and continues on third stage' do
expect(all_builds_statuses).to eq(%w[pending created created])
builds.first.success
expect(all_builds_statuses).to eq(%w[success skipped pending])
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 before do
create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0) create_build('build:1', stage_idx: 0)
create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0) create_build('build:2', stage_idx: 0)
create_build('test:1', stage_idx: 1)
create_build('test:2', stage_idx: 1)
create_build('deploy:1', stage_idx: 2)
create_build('deploy:2', stage_idx: 2)
end end
it 'processes the pipeline' do it 'does trigger builds in the next stage' do
# Currently we have five builds with state created expect(process_pipeline).to be_truthy
# expect(builds_names).to eq ['build:1', 'build:2']
expect(builds.count).to eq(0)
expect(all_builds.count).to eq(2)
# Process builds service will enqueue builds from the first stage. succeed_running_or_pending
#
process_pipeline
expect(builds.count).to eq(2) expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2']
expect(all_builds.count).to eq(2)
# When builds succeed we will enqueue remaining builds. pipeline.builds.find_by(name: 'test:1').success
# pipeline.builds.find_by(name: 'test:2').drop
# 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_names).to eq ['build:1', 'build:2', 'test:1', 'test: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. Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).success
#
succeed_pending
process_pipeline
expect(builds.pending.count).to eq(1) expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2',
expect(builds.success.count).to eq(3) 'test:2', 'deploy:1', 'deploy:2']
expect(all_builds.count).to eq(4) end
end
end
# When the last one succeeds we have 4 successful builds. context 'when there are builds that are not created yet' do
# let(:pipeline) do
succeed_pending create(:ci_pipeline, config: config)
process_pipeline end
expect(builds.success.count).to eq(4) let(:config) do
expect(all_builds.count).to eq(4) { rspec: { stage: 'test', script: 'rspec' },
end deploy: { stage: 'deploy', script: 'rsync' } }
end
before do
create_build('linux', stage: 'build', stage_idx: 0)
create_build('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 end
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
def all_builds def all_builds
pipeline.builds pipeline.builds.order(:stage_idx, :id)
end end
def builds def builds
all_builds.where.not(status: [:created, :skipped]) all_builds.where.not(status: [:created, :skipped])
end end
def process_pipeline def builds_names
described_class.new(pipeline.project, user).execute(pipeline) builds.pluck(:name)
end
def builds_statuses
builds.pluck(:status)
end
def all_builds_statuses
all_builds.pluck(:status)
end end
def succeed_pending def succeed_pending
builds.pending.update_all(status: 'success') builds.pending.update_all(status: 'success')
end end
def succeed_running_or_pending
pipeline.builds.running_or_pending.each(&:success)
end
def fail_running_or_pending
pipeline.builds.running_or_pending.each(&:drop)
end
def cancel_running_or_pending
pipeline.builds.running_or_pending.each(&:cancel)
end
def play_manual_action(name)
builds.find_by(name: name).play(user)
end
delegate :manual_actions, to: :pipeline delegate :manual_actions, to: :pipeline
def create_build(name, stage_idx, when_value = nil) def create_build(name, **opts)
create(:ci_build, create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
:created,
pipeline: pipeline,
name: name,
stage_idx: stage_idx,
when: when_value)
end end
end end
...@@ -89,35 +89,74 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -89,35 +89,74 @@ describe Ci::RetryPipelineService, '#execute', :services do
end end
context 'when pipeline contains manual actions' do context 'when pipeline contains manual actions' do
context 'when there is a canceled manual action in first stage' do context 'when there are optional manual actions only' do
before do context 'when there is a canceled manual action in first stage' do
create_build('rspec 1', :failed, 0) before do
create_build('staging', :canceled, 0, :manual) create_build('rspec 1', :failed, 0)
create_build('rspec 2', :canceled, 1) create_build('staging', :canceled, 0, when: :manual, allow_failure: true)
create_build('rspec 2', :canceled, 1)
end
it 'retries failed builds and marks subsequent for processing' do
service.execute(pipeline)
expect(build('rspec 1')).to be_pending
expect(build('staging')).to be_manual
expect(build('rspec 2')).to be_created
expect(pipeline.reload).to be_running
end
end end
end
it 'retries builds failed builds and marks subsequent for processing' do context 'when pipeline has blocking manual actions defined' do
service.execute(pipeline) context 'when pipeline retry should enqueue builds' do
before do
create_build('test', :failed, 0)
create_build('deploy', :canceled, 0, when: :manual, allow_failure: false)
create_build('verify', :canceled, 1)
end
it 'retries failed builds' do
service.execute(pipeline)
expect(build('test')).to be_pending
expect(build('deploy')).to be_manual
expect(build('verify')).to be_created
expect(pipeline.reload).to be_running
end
end
expect(build('rspec 1')).to be_pending context 'when pipeline retry should block pipeline immediately' do
expect(build('staging')).to be_skipped before do
expect(build('rspec 2')).to be_created create_build('test', :success, 0)
expect(pipeline.reload).to be_running create_build('deploy:1', :success, 1, when: :manual, allow_failure: false)
create_build('deploy:2', :failed, 1, when: :manual, allow_failure: false)
create_build('verify', :canceled, 2)
end
it 'reprocesses blocking manual action and blocks pipeline' do
service.execute(pipeline)
expect(build('deploy:1')).to be_success
expect(build('deploy:2')).to be_manual
expect(build('verify')).to be_created
expect(pipeline.reload).to be_blocked
end
end end
end end
context 'when there is a skipped manual action in last stage' do context 'when there is a skipped manual action in last stage' do
before do before do
create_build('rspec 1', :canceled, 0) create_build('rspec 1', :canceled, 0)
create_build('rspec 2', :skipped, 0, :manual) create_build('rspec 2', :skipped, 0, when: :manual, allow_failure: true)
create_build('staging', :skipped, 1, :manual) create_build('staging', :skipped, 1, when: :manual, allow_failure: true)
end end
it 'retries canceled job and reprocesses manual actions' do it 'retries canceled job and reprocesses manual actions' do
service.execute(pipeline) service.execute(pipeline)
expect(build('rspec 1')).to be_pending expect(build('rspec 1')).to be_pending
expect(build('rspec 2')).to be_skipped expect(build('rspec 2')).to be_manual
expect(build('staging')).to be_created expect(build('staging')).to be_created
expect(pipeline.reload).to be_running expect(pipeline.reload).to be_running
end end
...@@ -126,7 +165,7 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -126,7 +165,7 @@ describe Ci::RetryPipelineService, '#execute', :services do
context 'when there is a created manual action in the last stage' do context 'when there is a created manual action in the last stage' do
before do before do
create_build('rspec 1', :canceled, 0) create_build('rspec 1', :canceled, 0)
create_build('staging', :created, 1, :manual) create_build('staging', :created, 1, when: :manual, allow_failure: true)
end end
it 'retries canceled job and does not update the manual action' do it 'retries canceled job and does not update the manual action' do
...@@ -141,14 +180,14 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -141,14 +180,14 @@ describe Ci::RetryPipelineService, '#execute', :services do
context 'when there is a created manual action in the first stage' do context 'when there is a created manual action in the first stage' do
before do before do
create_build('rspec 1', :canceled, 0) create_build('rspec 1', :canceled, 0)
create_build('staging', :created, 0, :manual) create_build('staging', :created, 0, when: :manual, allow_failure: true)
end end
it 'retries canceled job and skipps the manual action' do it 'retries canceled job and processes the manual action' do
service.execute(pipeline) service.execute(pipeline)
expect(build('rspec 1')).to be_pending expect(build('rspec 1')).to be_pending
expect(build('staging')).to be_skipped expect(build('staging')).to be_manual
expect(pipeline.reload).to be_running expect(pipeline.reload).to be_running
end end
end end
...@@ -183,13 +222,12 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -183,13 +222,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
statuses.latest.find_by(name: name) statuses.latest.find_by(name: name)
end end
def create_build(name, status, stage_num, on = 'on_success') def create_build(name, status, stage_num, **opts)
create(:ci_build, name: name, create(:ci_build, name: name,
status: status, status: status,
stage: "stage_#{stage_num}", stage: "stage_#{stage_num}",
stage_idx: stage_num, stage_idx: stage_num,
when: on, pipeline: pipeline, **opts) do |build|
pipeline: pipeline) do |build|
pipeline.update_status pipeline.update_status
end end
end end
......
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