Commit a2f63172 authored by Kamil Trzciński's avatar Kamil Trzciński

Ensure that we test all possible combinations

We need to cartesian test legacy and atomic processing
with different feature flags

This introduces a HACK to align behaviour
of atomic processing with legacy processing

Fix some YAML configs for Pipeline Processing

This ensures that all statuses are covered
for Pipeline and Stage
parent cbc27027
...@@ -115,8 +115,11 @@ module Ci ...@@ -115,8 +115,11 @@ module Ci
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition [:created, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running transition [:success, :failed, :canceled] => :running
# this is needed to ensure tests to be covered
transition [:running] => :running
end end
event :request_resource do event :request_resource do
......
...@@ -42,8 +42,7 @@ module Ci ...@@ -42,8 +42,7 @@ module Ci
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition [:created, :waiting_for_resource, :preparing] => :pending transition any - [:pending] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end end
event :request_resource do event :request_resource do
......
...@@ -95,7 +95,7 @@ module Ci ...@@ -95,7 +95,7 @@ module Ci
def processable_status(processable) def processable_status(processable)
if processable.scheduling_type_dag? if processable.scheduling_type_dag?
# Processable uses DAG, get status of all dependent needs # Processable uses DAG, get status of all dependent needs
@collection.status_for_names(processable.aggregated_needs_names.to_a) @collection.status_for_names(processable.aggregated_needs_names.to_a, dag: true)
else else
# Processable uses Stages, get status of prior stage # Processable uses Stages, get status of prior stage
@collection.status_for_prior_stage_position(processable.stage_idx.to_i) @collection.status_for_prior_stage_position(processable.stage_idx.to_i)
......
...@@ -32,14 +32,14 @@ module Ci ...@@ -32,14 +32,14 @@ module Ci
# This methods gets composite status of all processables # This methods gets composite status of all processables
def status_of_all def status_of_all
status_for_array(all_statuses) status_for_array(all_statuses, dag: false)
end end
# This methods gets composite status for processables with given names # This methods gets composite status for processables with given names
def status_for_names(names) def status_for_names(names, dag:)
name_statuses = all_statuses_by_name.slice(*names) name_statuses = all_statuses_by_name.slice(*names)
status_for_array(name_statuses.values) status_for_array(name_statuses.values, dag: dag)
end end
# This methods gets composite status for processables before given stage # This methods gets composite status for processables before given stage
...@@ -48,7 +48,7 @@ module Ci ...@@ -48,7 +48,7 @@ module Ci
stage_statuses = all_statuses_grouped_by_stage_position stage_statuses = all_statuses_grouped_by_stage_position
.select { |stage_position, _| stage_position < position } .select { |stage_position, _| stage_position < position }
status_for_array(stage_statuses.values.flatten) status_for_array(stage_statuses.values.flatten, dag: false)
end end
end end
...@@ -65,7 +65,7 @@ module Ci ...@@ -65,7 +65,7 @@ module Ci
strong_memoize("status_for_stage_position_#{current_position}") do strong_memoize("status_for_stage_position_#{current_position}") do
stage_statuses = all_statuses_grouped_by_stage_position[current_position].to_a stage_statuses = all_statuses_grouped_by_stage_position[current_position].to_a
status_for_array(stage_statuses.flatten) status_for_array(stage_statuses.flatten, dag: false)
end end
end end
...@@ -76,7 +76,14 @@ module Ci ...@@ -76,7 +76,14 @@ module Ci
private private
def status_for_array(statuses) def status_for_array(statuses, dag:)
# TODO: This is hack to support
# the same exact behaviour for Atomic and Legacy processing
# that DAG is blocked from executing if dependent is not "complete"
if dag && statuses.any? { |status| HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) }
return 'pending'
end
result = Gitlab::Ci::Status::Composite result = Gitlab::Ci::Status::Composite
.new(statuses) .new(statuses)
.status .status
......
...@@ -53,6 +53,29 @@ describe Ci::Pipeline, :mailer do ...@@ -53,6 +53,29 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#set_status' do
where(:from_status, :to_status) do
from_status_names = described_class.state_machines[:status].states.map(&:name)
to_status_names = from_status_names - [:created] # we never want to transition into created
from_status_names.product(to_status_names)
end
with_them do
it do
pipeline.status = from_status.to_s
if from_status != to_status
expect(pipeline.set_status(to_status.to_s))
.to eq(true)
else
expect(pipeline.set_status(to_status.to_s))
.to eq(false), "loopback transitions are not allowed"
end
end
end
end
describe '.processables' do describe '.processables' do
before do before do
create(:ci_build, name: 'build', pipeline: pipeline) create(:ci_build, name: 'build', pipeline: pipeline)
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Stage, :models do describe Ci::Stage, :models do
let(:stage) { create(:ci_stage_entity) } let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
it_behaves_like 'having unique enum values' it_behaves_like 'having unique enum values'
...@@ -55,6 +56,29 @@ describe Ci::Stage, :models do ...@@ -55,6 +56,29 @@ describe Ci::Stage, :models do
end end
end end
describe '#set_status' do
where(:from_status, :to_status) do
from_status_names = described_class.state_machines[:status].states.map(&:name)
to_status_names = from_status_names - [:created] # we never want to transition into created
from_status_names.product(to_status_names)
end
with_them do
it do
stage.status = from_status.to_s
if from_status != to_status
expect(stage.set_status(to_status.to_s))
.to eq(true)
else
expect(stage.set_status(to_status.to_s))
.to eq(false), "loopback transitions are not allowed"
end
end
end
end
describe '#update_status' do describe '#update_status' do
context 'when stage objects needs to be updated' do context 'when stage objects needs to be updated' do
before do before do
......
...@@ -18,7 +18,7 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do ...@@ -18,7 +18,7 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
it 'does update existing status of processable' do it 'does update existing status of processable' do
collection.set_processable_status(test_a.id, 'success', 100) collection.set_processable_status(test_a.id, 'success', 100)
expect(collection.status_for_names(['test-a'])).to eq('success') expect(collection.status_for_names(['test-a'], dag: false)).to eq('success')
end end
it 'ignores a missing processable' do it 'ignores a missing processable' do
...@@ -33,15 +33,18 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do ...@@ -33,15 +33,18 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
end end
describe '#status_for_names' do describe '#status_for_names' do
where(:names, :status) do where(:names, :status, :dag) do
%w[build-a] | 'success' %w[build-a] | 'success' | false
%w[build-a build-b] | 'failed' %w[build-a build-b] | 'failed' | false
%w[build-a test-a] | 'running' %w[build-a test-a] | 'running' | false
%w[build-a] | 'success' | true
%w[build-a build-b] | 'failed' | true
%w[build-a test-a] | 'pending' | true
end end
with_them do with_them do
it 'returns composite status of given names' do it 'returns composite status of given names' do
expect(collection.status_for_names(names)).to eq(status) expect(collection.status_for_names(names, dag: dag)).to eq(status)
end end
end end
end end
......
...@@ -2,17 +2,19 @@ ...@@ -2,17 +2,19 @@
require 'spec_helper' require 'spec_helper'
require_relative 'shared_processing_service.rb' require_relative 'shared_processing_service.rb'
# require_relative 'shared_processing_service_tests_with_yaml.rb' require_relative 'shared_processing_service_tests_with_yaml.rb'
describe Ci::PipelineProcessing::AtomicProcessingService do describe Ci::PipelineProcessing::AtomicProcessingService do
before do before do
stub_feature_flags(ci_atomic_processing: true) stub_feature_flags(ci_atomic_processing: true)
# This feature flag is implicit
# Atomic Processing does not process statuses differently
stub_feature_flags(ci_composite_status: true)
end end
it_behaves_like 'Pipeline Processing Service' it_behaves_like 'Pipeline Processing Service'
# TODO: This needs to be enabled. There is a different behavior when using `needs` depending on it_behaves_like 'Pipeline Processing Service Tests With Yaml'
# a `manual` job. More info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29405#note_327520605
# it_behaves_like 'Pipeline Processing Service Tests With Yaml'
private private
......
...@@ -7,11 +7,25 @@ require_relative 'shared_processing_service_tests_with_yaml.rb' ...@@ -7,11 +7,25 @@ require_relative 'shared_processing_service_tests_with_yaml.rb'
describe Ci::PipelineProcessing::LegacyProcessingService do describe Ci::PipelineProcessing::LegacyProcessingService do
before do before do
stub_feature_flags(ci_atomic_processing: false) stub_feature_flags(ci_atomic_processing: false)
stub_feature_flags(ci_composite_status: false)
end end
it_behaves_like 'Pipeline Processing Service' context 'when ci_composite_status is enabled' do
it_behaves_like 'Pipeline Processing Service Tests With Yaml' before do
stub_feature_flags(ci_composite_status: true)
end
it_behaves_like 'Pipeline Processing Service'
it_behaves_like 'Pipeline Processing Service Tests With Yaml'
end
context 'when ci_composite_status is disabled' do
before do
stub_feature_flags(ci_composite_status: false)
end
it_behaves_like 'Pipeline Processing Service'
it_behaves_like 'Pipeline Processing Service Tests With Yaml'
end
private private
......
...@@ -816,10 +816,10 @@ shared_examples 'Pipeline Processing Service' do ...@@ -816,10 +816,10 @@ shared_examples 'Pipeline Processing Service' do
context 'when a needed job is skipped', :sidekiq_inline do context 'when a needed job is skipped', :sidekiq_inline do
let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) } let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) }
let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) } let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) }
let!(:deploy) do let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 2, scheduling_type: :dag) }
create_build('deploy', stage: 'deploy', stage_idx: 2, scheduling_type: :dag, needs: [
create(:ci_build_need, name: 'linux:rspec') before do
]) create(:ci_build_need, build: deploy, name: 'linux:build')
end end
it 'skips the jobs depending on it' do it 'skips the jobs depending on it' do
...@@ -836,6 +836,23 @@ shared_examples 'Pipeline Processing Service' do ...@@ -836,6 +836,23 @@ shared_examples 'Pipeline Processing Service' do
end end
end end
context 'when a needed job is manual', :sidekiq_inline do
let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0, when: 'manual', allow_failure: true) }
let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 1, scheduling_type: :dag) }
before do
create(:ci_build_need, build: deploy, name: 'linux:build')
end
it 'makes deploy DAG to be waiting for optional manual to finish' do
expect(process_pipeline).to be_truthy
expect(stages).to eq(%w(skipped created))
expect(all_builds.manual).to contain_exactly(linux_build)
expect(all_builds.created).to contain_exactly(deploy)
end
end
private private
def all_builds def all_builds
......
...@@ -18,36 +18,40 @@ shared_context 'Pipeline Processing Service Tests With Yaml' do ...@@ -18,36 +18,40 @@ shared_context 'Pipeline Processing Service Tests With Yaml' do
project.add_developer(user) project.add_developer(user)
end end
it 'follows transitions', :sidekiq_inline do it 'follows transitions' do
expect(pipeline).to be_persisted expect(pipeline).to be_persisted
check_expectation(test_file.dig('init', 'expect')) Sidekiq::Worker.drain_all # ensure that all async jobs are executed
check_expectation(test_file.dig('init', 'expect'), "init")
test_file['transitions'].each do |transition| test_file['transitions'].each_with_index do |transition, idx|
event_on_jobs(transition['event'], transition['jobs']) event_on_jobs(transition['event'], transition['jobs'])
check_expectation(transition['expect']) Sidekiq::Worker.drain_all # ensure that all async jobs are executed
check_expectation(transition['expect'], "transition:#{idx}")
end end
end end
private private
def check_expectation(expectation) def check_expectation(expectation, message)
expectation.each do |key, value| expect(current_state.deep_stringify_keys).to eq(expectation), message
case key end
when 'pipeline'
expect(pipeline.reload.status).to eq(value) def current_state
when 'stages' # reload pipeline and all relations
expect(pipeline.stages.pluck(:name, :status).to_h).to eq(value) pipeline.reload
when 'jobs'
expect(pipeline.builds.latest.pluck(:name, :status).to_h).to eq(value) {
end pipeline: pipeline.status,
end stages: pipeline.ordered_stages.pluck(:name, :status).to_h,
jobs: pipeline.statuses.latest.pluck(:name, :status).to_h
}
end end
def event_on_jobs(event, job_names) def event_on_jobs(event, job_names)
builds = pipeline.builds.latest.where(name: job_names).to_a statuses = pipeline.statuses.latest.by_name(job_names).to_a
expect(builds.count).to eq(job_names.count) # ensure that we have the same counts expect(statuses.count).to eq(job_names.count) # ensure that we have the same counts
builds.each { |build| build.public_send("#{event}!") } statuses.each { |status| status.public_send("#{event}!") }
end end
end end
end end
...@@ -24,9 +24,9 @@ transitions: ...@@ -24,9 +24,9 @@ transitions:
- event: enqueue - event: enqueue
jobs: [test] jobs: [test]
expect: expect:
pipeline: manual pipeline: pending
stages: stages:
test: manual test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -26,7 +26,7 @@ transitions: ...@@ -26,7 +26,7 @@ transitions:
expect: expect:
pipeline: pending pipeline: pending
stages: stages:
test: running test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -27,7 +27,7 @@ transitions: ...@@ -27,7 +27,7 @@ transitions:
expect: expect:
pipeline: pending pipeline: pending
stages: stages:
test: running test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -23,9 +23,9 @@ transitions: ...@@ -23,9 +23,9 @@ transitions:
- event: enqueue - event: enqueue
jobs: [test] jobs: [test]
expect: expect:
pipeline: manual pipeline: pending
stages: stages:
test: manual test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -36,7 +36,7 @@ transitions: ...@@ -36,7 +36,7 @@ transitions:
expect: expect:
pipeline: running pipeline: running
stages: stages:
test: running test: pending
deploy: success deploy: success
jobs: jobs:
test: pending test: pending
......
...@@ -26,7 +26,7 @@ transitions: ...@@ -26,7 +26,7 @@ transitions:
expect: expect:
pipeline: pending pipeline: pending
stages: stages:
test: running test: pending
deploy: skipped deploy: skipped
jobs: jobs:
test: pending test: pending
......
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