diff options
Diffstat (limited to 'spec/lib/gitlab/ci/pipeline/seed/build_spec.rb')
-rw-r--r-- | spec/lib/gitlab/ci/pipeline/seed/build_spec.rb | 1607 |
1 files changed, 846 insertions, 761 deletions
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 75f6a773c2d..1f7f800e238 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do +RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_authoring do let_it_be_with_reload(:project) { create(:project, :repository) } let_it_be(:head_sha) { project.repository.head_commit.id } @@ -11,861 +11,954 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:seed_context) { Gitlab::Ci::Pipeline::Seed::Context.new(pipeline, root_variables: root_variables) } let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage, when: 'on_success' } } let(:previous_stages) { [] } - let(:current_stage) { double(seeds_names: [attributes[:name]]) } + let(:current_stage) { instance_double(Gitlab::Ci::Pipeline::Seed::Stage, seeds_names: [attributes[:name]]) } + let(:current_ci_stage) { build(:ci_stage, pipeline: pipeline) } - let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage]) } + let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage], current_ci_stage) } - describe '#attributes' do - subject { seed_build.attributes } + shared_examples 'build seed' do + describe '#attributes' do + subject { seed_build.attributes } - it { is_expected.to be_a(Hash) } - it { is_expected.to include(:name, :project, :ref) } + it { is_expected.to be_a(Hash) } + it { is_expected.to include(:name, :project, :ref) } - context 'with job:when' do - let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } } + context 'with job:when' do + let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } } - it { is_expected.to include(when: 'on_failure') } - end - - context 'with job:when:delayed' do - let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', start_in: '3 hours' } } - - it { is_expected.to include(when: 'delayed', start_in: '3 hours') } - end - - context 'with job:rules:[when:]' do - context 'is matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } } - - it { is_expected.to include(when: 'always') } + it { is_expected.to include(when: 'on_failure') } end - context 'is not matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } } - - it { is_expected.to include(when: 'never') } - end - end - - context 'with job:rules:[when:delayed]' do - context 'is matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } } + context 'with job:when:delayed' do + let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', options: { start_in: '3 hours' } } } it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) } end - context 'is not matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } } - - it { is_expected.to include(when: 'never') } - end - end - - context 'with job: rules but no explicit when:' do - let(:base_attributes) { { name: 'rspec', ref: 'master' } } - - context 'with a manual job' do - context 'with a matched rule' do - let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) } + context 'with job:rules:[when:]' do + context 'is matched' do + let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } } - it { is_expected.to include(when: 'manual') } + it { is_expected.to include(when: 'always') } end context 'is not matched' do - let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) } + let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } } it { is_expected.to include(when: 'never') } end end - context 'with an automatic job' do + context 'with job:rules:[when:delayed]' do context 'is matched' do - let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) } + let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } } - it { is_expected.to include(when: 'on_success') } + it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) } end context 'is not matched' do - let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) } + let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } } it { is_expected.to include(when: 'never') } end end - end - context 'with job:rules:[variables:]' do - let(:attributes) do - { name: 'rspec', - ref: 'master', - job_variables: [{ key: 'VAR1', value: 'var 1' }, - { key: 'VAR2', value: 'var 2' }], - rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] } - end + context 'with job: rules but no explicit when:' do + let(:base_attributes) { { name: 'rspec', ref: 'master' } } - it do - is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' }, - { key: 'VAR3', value: 'var 3' }, - { key: 'VAR2', value: 'var 2' }]) - end - end + context 'with a manual job' do + context 'with a matched rule' do + let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) } - context 'with job:tags' do - let(:attributes) do - { - name: 'rspec', - ref: 'master', - job_variables: [{ key: 'VARIABLE', value: 'value' }], - tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE'] - } - end + it { is_expected.to include(when: 'manual') } + end - it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) } - it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) } - end + context 'is not matched' do + let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) } - context 'with cache:key' do - let(:attributes) do - { - name: 'rspec', - ref: 'master', - cache: [{ - key: 'a-value' - }] - } - end + it { is_expected.to include(when: 'never') } + end + end + + context 'with an automatic job' do + context 'is matched' do + let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) } + + it { is_expected.to include(when: 'on_success') } + end - it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) } + context 'is not matched' do + let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) } + + it { is_expected.to include(when: 'never') } + end + end + end - context 'with cache:key:files' do + context 'with job:rules:[variables:]' do let(:attributes) do - { - name: 'rspec', + { name: 'rspec', ref: 'master', - cache: [{ - key: { - files: ['VERSION'] - } - }] - } + job_variables: [{ key: 'VAR1', value: 'var 1' }, + { key: 'VAR2', value: 'var 2' }], + rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] } end - it 'includes cache options' do - cache_options = { - options: { - cache: [a_hash_including(key: 'f155568ad0933d8358f66b846133614f76dd0ca4')] - } - } + it do + is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' }, + { key: 'VAR3', value: 'var 3' }, + { key: 'VAR2', value: 'var 2' }]) + end - is_expected.to include(cache_options) + it 'expects the same results on to_resource' do + expect(seed_build.to_resource.yaml_variables).to include({ key: 'VAR1', value: 'new var 1' }, + { key: 'VAR3', value: 'var 3' }, + { key: 'VAR2', value: 'var 2' }) end end - context 'with cache:key:prefix' do + context 'with job:tags' do let(:attributes) do { name: 'rspec', ref: 'master', - cache: [{ - key: { - prefix: 'something' - } - }] + job_variables: [{ key: 'VARIABLE', value: 'value' }], + tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE'] } end - it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) } + it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) } + it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) } end - context 'with cache:key:files and prefix' do + context 'with cache:key' do let(:attributes) do { name: 'rspec', ref: 'master', cache: [{ - key: { - files: ['VERSION'], - prefix: 'something' - } + key: 'a-value' }] } end - it 'includes cache options' do - cache_options = { - options: { - cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')] + it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) } + + context 'with cache:key:files' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: [{ + key: { + files: ['VERSION'] + } + }] } - } + end - is_expected.to include(cache_options) - end - end - end + it 'includes cache options' do + cache_options = { + options: { + cache: [a_hash_including(key: '0-f155568ad0933d8358f66b846133614f76dd0ca4')] + } + } - context 'with empty cache' do - let(:attributes) do - { - name: 'rspec', - ref: 'master', - cache: {} - } - end + is_expected.to include(cache_options) + end + end - it { is_expected.to include({}) } - end + context 'with cache:key:prefix' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: [{ + key: { + prefix: 'something' + } + }] + } + end - context 'with allow_failure' do - let(:options) do - { allow_failure_criteria: { exit_codes: [42] } } - end + it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) } + end - let(:rules) do - [{ if: '$VAR == null', when: 'always' }] - end + context 'with cache:key:files and prefix' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: [{ + key: { + files: ['VERSION'], + prefix: 'something' + } + }] + } + end - let(:attributes) do - { - name: 'rspec', - ref: 'master', - options: options, - rules: rules - } - end + it 'includes cache options' do + cache_options = { + options: { + cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')] + } + } - context 'when rules does not override allow_failure' do - it { is_expected.to match a_hash_including(options: options) } + is_expected.to include(cache_options) + end + end end - context 'when rules set allow_failure to true' do - let(:rules) do - [{ if: '$VAR == null', when: 'always', allow_failure: true }] + context 'with empty cache' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: {} + } end - it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) } + it { is_expected.to include({}) } end - context 'when rules set allow_failure to false' do - let(:rules) do - [{ if: '$VAR == null', when: 'always', allow_failure: false }] + context 'with allow_failure' do + let(:options) do + { allow_failure_criteria: { exit_codes: [42] } } end - it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) } - end - end - - context 'with workflow:rules:[variables:]' do - let(:attributes) do - { name: 'rspec', - ref: 'master', - yaml_variables: [{ key: 'VAR2', value: 'var 2' }, - { key: 'VAR3', value: 'var 3' }], - job_variables: [{ key: 'VAR2', value: 'var 2' }, - { key: 'VAR3', value: 'var 3' }], - root_variables_inheritance: root_variables_inheritance } - end + let(:rules) do + [{ if: '$VAR == null', when: 'always' }] + end - context 'when the pipeline has variables' do - let(:root_variables) do - [{ key: 'VAR1', value: 'var overridden pipeline 1' }, - { key: 'VAR2', value: 'var pipeline 2' }, - { key: 'VAR3', value: 'var pipeline 3' }, - { key: 'VAR4', value: 'new var pipeline 4' }] + let(:attributes) do + { + name: 'rspec', + ref: 'master', + options: options, + rules: rules + } end - context 'when root_variables_inheritance is true' do - let(:root_variables_inheritance) { true } + context 'when rules does not override allow_failure' do + it { is_expected.to match a_hash_including(options: options) } + end - it 'returns calculated yaml variables' do - expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR1', value: 'var overridden pipeline 1' }, - { key: 'VAR2', value: 'var 2' }, - { key: 'VAR3', value: 'var 3' }, - { key: 'VAR4', value: 'new var pipeline 4' }] - ) + context 'when rules set allow_failure to true' do + let(:rules) do + [{ if: '$VAR == null', when: 'always', allow_failure: true }] end - end - context 'when root_variables_inheritance is false' do - let(:root_variables_inheritance) { false } + it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) } - it 'returns job variables' do - expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR2', value: 'var 2' }, - { key: 'VAR3', value: 'var 3' }] - ) - end - end + context 'when options contain other static values' do + let(:options) do + { image: 'busybox', allow_failure_criteria: { exit_codes: [42] } } + end - context 'when root_variables_inheritance is an array' do - let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) } + it { is_expected.to match a_hash_including(options: { image: 'busybox', allow_failure_criteria: nil }) } - it 'returns calculated yaml variables' do - expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR1', value: 'var overridden pipeline 1' }, - { key: 'VAR2', value: 'var 2' }, - { key: 'VAR3', value: 'var 3' }] - ) + it 'deep merges options when exporting to_resource' do + expect(seed_build.to_resource.options).to match a_hash_including( + image: 'busybox', allow_failure_criteria: nil + ) + end end end - end - context 'when the pipeline has not a variable' do - let(:root_variables_inheritance) { true } + context 'when rules set allow_failure to false' do + let(:rules) do + [{ if: '$VAR == null', when: 'always', allow_failure: false }] + end - it 'returns seed yaml variables' do - expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR2', value: 'var 2' }, - { key: 'VAR3', value: 'var 3' }]) + it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) } end end - end - context 'when the job rule depends on variables' do - let(:attributes) do - { name: 'rspec', - ref: 'master', - yaml_variables: [{ key: 'VAR1', value: 'var 1' }], - job_variables: [{ key: 'VAR1', value: 'var 1' }], - root_variables_inheritance: root_variables_inheritance, - rules: rules } - end + context 'with workflow:rules:[variables:]' do + let(:attributes) do + { name: 'rspec', + ref: 'master', + yaml_variables: [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }], + job_variables: [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }], + root_variables_inheritance: root_variables_inheritance } + end + + context 'when the pipeline has variables' do + let(:root_variables) do + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var pipeline 2' }, + { key: 'VAR3', value: 'var pipeline 3' }, + { key: 'VAR4', value: 'new var pipeline 4' }] + end - let(:root_variables_inheritance) { true } + context 'when root_variables_inheritance is true' do + let(:root_variables_inheritance) { true } - context 'when the rules use job variables' do - let(:rules) do - [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }] + it 'returns calculated yaml variables' do + expect(subject[:yaml_variables]).to match_array( + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }, + { key: 'VAR4', value: 'new var pipeline 4' }] + ) + end + end + + context 'when root_variables_inheritance is false' do + let(:root_variables_inheritance) { false } + + it 'returns job variables' do + expect(subject[:yaml_variables]).to match_array( + [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }] + ) + end + end + + context 'when root_variables_inheritance is an array' do + let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) } + + it 'returns calculated yaml variables' do + expect(subject[:yaml_variables]).to match_array( + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }] + ) + end + end end - it 'recalculates the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, - { key: 'VAR2', value: 'new var 2' }) + context 'when the pipeline has not a variable' do + let(:root_variables_inheritance) { true } + + it 'returns seed yaml variables' do + expect(subject[:yaml_variables]).to match_array( + [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }]) + end end end - context 'when the rules use root variables' do - let(:root_variables) do - [{ key: 'VAR2', value: 'var pipeline 2' }] + context 'when the job rule depends on variables' do + let(:attributes) do + { name: 'rspec', + ref: 'master', + yaml_variables: [{ key: 'VAR1', value: 'var 1' }], + job_variables: [{ key: 'VAR1', value: 'var 1' }], + root_variables_inheritance: root_variables_inheritance, + rules: rules } end - let(:rules) do - [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }] - end + let(:root_variables_inheritance) { true } + + context 'when the rules use job variables' do + let(:rules) do + [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }] + end - it 'recalculates the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, - { key: 'VAR2', value: 'overridden var 2' }) + it 'recalculates the variables' do + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, + { key: 'VAR2', value: 'new var 2' }) + end end - context 'when the root_variables_inheritance is false' do - let(:root_variables_inheritance) { false } + context 'when the rules use root variables' do + let(:root_variables) do + [{ key: 'VAR2', value: 'var pipeline 2' }] + end - it 'does not recalculate the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' }) + let(:rules) do + [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }] + end + + it 'recalculates the variables' do + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, + { key: 'VAR2', value: 'overridden var 2' }) end - end - end - end - end - describe '#bridge?' do - subject { seed_build.bridge? } + context 'when the root_variables_inheritance is false' do + let(:root_variables_inheritance) { false } - context 'when job is a downstream bridge' do - let(:attributes) do - { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } } + it 'does not recalculate the variables' do + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' }) + end + end + end end + end - it { is_expected.to be_truthy } + describe '#bridge?' do + subject { seed_build.bridge? } - context 'when trigger definition is empty' do + context 'when job is a downstream bridge' do let(:attributes) do - { name: 'rspec', ref: 'master', options: { trigger: '' } } + { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } } end - it { is_expected.to be_falsey } - end - end + it { is_expected.to be_truthy } - context 'when job is an upstream bridge' do - let(:attributes) do - { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } } - end + context 'when trigger definition is empty' do + let(:attributes) do + { name: 'rspec', ref: 'master', options: { trigger: '' } } + end - it { is_expected.to be_truthy } + it { is_expected.to be_falsey } + end + end - context 'when upstream definition is empty' do + context 'when job is an upstream bridge' do let(:attributes) do - { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } } + { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } } end - it { is_expected.to be_falsey } - end - end + it { is_expected.to be_truthy } - context 'when job is not a bridge' do - it { is_expected.to be_falsey } - end - end + context 'when upstream definition is empty' do + let(:attributes) do + { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } } + end - describe '#to_resource' do - subject { seed_build.to_resource } + it { is_expected.to be_falsey } + end + end - it 'memoizes a resource object' do - expect(subject.object_id).to eq seed_build.to_resource.object_id + context 'when job is not a bridge' do + it { is_expected.to be_falsey } + end end - it 'can not be persisted without explicit assignment' do - pipeline.save! + describe '#to_resource' do + subject { seed_build.to_resource } - expect(subject).not_to be_persisted - end - end + it 'memoizes a resource object' do + expect(subject.object_id).to eq seed_build.to_resource.object_id + end - describe 'applying job inclusion policies' do - subject { seed_build } + it 'can not be persisted without explicit assignment' do + pipeline.save! - context 'when no branch policy is specified' do - let(:attributes) do - { name: 'rspec' } + expect(subject).not_to be_persisted end - - it { is_expected.to be_included } end - context 'when branch policy does not match' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: ['deploy'] } } - end - - it { is_expected.not_to be_included } - end + describe 'applying job inclusion policies' do + subject { seed_build } - context 'when using except' do + context 'when no branch policy is specified' do let(:attributes) do - { name: 'rspec', except: { refs: ['deploy'] } } + { name: 'rspec' } end it { is_expected.to be_included } end - context 'with both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: %w[deploy] }, - except: { refs: %w[deploy] } - } + context 'when branch policy does not match' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: ['deploy'] } } + end + + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: ['deploy'] } } + end - context 'when branch regexp policy does not match' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: %w[/^deploy$/] } } + it { is_expected.to be_included } end - it { is_expected.not_to be_included } - end + context 'with both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[deploy] }, + except: { refs: %w[deploy] } + } + end - context 'when using except' do - let(:attributes) do - { name: 'rspec', except: { refs: %w[/^deploy$/] } } + it { is_expected.not_to be_included } end - - it { is_expected.to be_included } end - context 'with both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: %w[/^deploy$/] }, - except: { refs: %w[/^deploy$/] } - } + context 'when branch regexp policy does not match' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: %w[/^deploy$/] } } + end + + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: %w[/^deploy$/] } } + end - context 'when branch policy matches' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: %w[deploy master] } } + it { is_expected.to be_included } end - it { is_expected.to be_included } - end + context 'with both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[/^deploy$/] }, + except: { refs: %w[/^deploy$/] } + } + end - context 'when using except' do - let(:attributes) do - { name: 'rspec', except: { refs: %w[deploy master] } } + it { is_expected.not_to be_included } end - - it { is_expected.not_to be_included } end - context 'when using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: %w[deploy master] }, - except: { refs: %w[deploy master] } - } + context 'when branch policy matches' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: %w[deploy master] } } + end + + it { is_expected.to be_included } end - it { is_expected.not_to be_included } - end - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: %w[deploy master] } } + end - context 'when keyword policy matches' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: %w[branches] } } + it { is_expected.not_to be_included } end - it { is_expected.to be_included } - end + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[deploy master] }, + except: { refs: %w[deploy master] } + } + end - context 'when using except' do - let(:attributes) do - { name: 'rspec', except: { refs: %w[branches] } } + it { is_expected.not_to be_included } end - - it { is_expected.not_to be_included } end - context 'when using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: %w[branches] }, - except: { refs: %w[branches] } - } + context 'when keyword policy matches' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: %w[branches] } } + end + + it { is_expected.to be_included } end - it { is_expected.not_to be_included } - end - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: %w[branches] } } + end - context 'when keyword policy does not match' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: %w[tags] } } + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[branches] }, + except: { refs: %w[branches] } + } + end - context 'when using except' do - let(:attributes) do - { name: 'rspec', except: { refs: %w[tags] } } + it { is_expected.not_to be_included } end - - it { is_expected.to be_included } end - context 'when using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: %w[tags] }, - except: { refs: %w[tags] } - } + context 'when keyword policy does not match' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: %w[tags] } } + end + + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: %w[tags] } } + end - context 'with source-keyword policy' do - using RSpec::Parameterized + it { is_expected.to be_included } + end - let(:pipeline) do - build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project) - end + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[tags] }, + except: { refs: %w[tags] } + } + end - context 'matches' do - where(:keyword, :source) do - [ - %w[pushes push], - %w[web web], - %w[triggers trigger], - %w[schedules schedule], - %w[api api], - %w[external external] - ] + it { is_expected.not_to be_included } end + end - with_them do - context 'using an only policy' do - let(:attributes) do - { name: 'rspec', only: { refs: [keyword] } } - end + context 'with source-keyword policy' do + using RSpec::Parameterized - it { is_expected.to be_included } + let(:pipeline) do + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project) + end + + context 'matches' do + where(:keyword, :source) do + [ + %w[pushes push], + %w[web web], + %w[triggers trigger], + %w[schedules schedule], + %w[api api], + %w[external external] + ] end - context 'using an except policy' do - let(:attributes) do - { name: 'rspec', except: { refs: [keyword] } } + with_them do + context 'using an only policy' do + let(:attributes) do + { name: 'rspec', only: { refs: [keyword] } } + end + + it { is_expected.to be_included } end - it { is_expected.not_to be_included } - end + context 'using an except policy' do + let(:attributes) do + { name: 'rspec', except: { refs: [keyword] } } + end - context 'using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: [keyword] }, - except: { refs: [keyword] } - } + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } + context 'using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: [keyword] }, + except: { refs: [keyword] } + } + end + + it { is_expected.not_to be_included } + end end end - end - context 'non-matches' do - where(:keyword, :source) do - %w[web trigger schedule api external].map { |source| ['pushes', source] } + - %w[push trigger schedule api external].map { |source| ['web', source] } + - %w[push web schedule api external].map { |source| ['triggers', source] } + - %w[push web trigger api external].map { |source| ['schedules', source] } + - %w[push web trigger schedule external].map { |source| ['api', source] } + - %w[push web trigger schedule api].map { |source| ['external', source] } - end + context 'non-matches' do + where(:keyword, :source) do + %w[web trigger schedule api external].map { |source| ['pushes', source] } + + %w[push trigger schedule api external].map { |source| ['web', source] } + + %w[push web schedule api external].map { |source| ['triggers', source] } + + %w[push web trigger api external].map { |source| ['schedules', source] } + + %w[push web trigger schedule external].map { |source| ['api', source] } + + %w[push web trigger schedule api].map { |source| ['external', source] } + end - with_them do - context 'using an only policy' do - let(:attributes) do - { name: 'rspec', only: { refs: [keyword] } } + with_them do + context 'using an only policy' do + let(:attributes) do + { name: 'rspec', only: { refs: [keyword] } } + end + + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end + context 'using an except policy' do + let(:attributes) do + { name: 'rspec', except: { refs: [keyword] } } + end - context 'using an except policy' do - let(:attributes) do - { name: 'rspec', except: { refs: [keyword] } } + it { is_expected.to be_included } end - it { is_expected.to be_included } - end + context 'using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: [keyword] }, + except: { refs: [keyword] } + } + end - context 'using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: [keyword] }, - except: { refs: [keyword] } - } + it { is_expected.not_to be_included } end - - it { is_expected.not_to be_included } end end end - end - context 'when repository path matches' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } } + context 'when repository path matches' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } } + end + + it { is_expected.to be_included } end - it { is_expected.to be_included } - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } } + end - context 'when using except' do - let(:attributes) do - { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } } + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: ["branches@#{pipeline.project_full_path}"] }, + except: { refs: ["branches@#{pipeline.project_full_path}"] } + } + end - context 'when using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: ["branches@#{pipeline.project_full_path}"] }, - except: { refs: ["branches@#{pipeline.project_full_path}"] } - } + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end - - context 'when using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { - refs: ["branches@#{pipeline.project_full_path}"] - }, - except: { - refs: ["branches@#{pipeline.project_full_path}"] + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { + refs: ["branches@#{pipeline.project_full_path}"] + }, + except: { + refs: ["branches@#{pipeline.project_full_path}"] + } } - } - end + end - it { is_expected.not_to be_included } + it { is_expected.not_to be_included } + end end - end - context 'when repository path does not match' do - context 'when using only' do - let(:attributes) do - { name: 'rspec', only: { refs: %w[branches@fork] } } + context 'when repository path does not match' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: %w[branches@fork] } } + end + + it { is_expected.not_to be_included } end - it { is_expected.not_to be_included } - end + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: %w[branches@fork] } } + end - context 'when using except' do - let(:attributes) do - { name: 'rspec', except: { refs: %w[branches@fork] } } + it { is_expected.to be_included } end - it { is_expected.to be_included } - end + context 'when using both only and except policies' do + let(:attributes) do + { + name: 'rspec', + only: { refs: %w[branches@fork] }, + except: { refs: %w[branches@fork] } + } + end - context 'when using both only and except policies' do - let(:attributes) do - { - name: 'rspec', - only: { refs: %w[branches@fork] }, - except: { refs: %w[branches@fork] } - } + it { is_expected.not_to be_included } end - - it { is_expected.not_to be_included } end - end - context 'using rules:' do - using RSpec::Parameterized + context 'using rules:' do + using RSpec::Parameterized - let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } } + let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } } - context 'with a matching if: rule' do - context 'with an explicit `when: never`' do - where(:rule_set) do - [ - [[{ if: '$VARIABLE == null', when: 'never' }]], - [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]], - [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]] - ] - end + context 'with a matching if: rule' do + context 'with an explicit `when: never`' do + where(:rule_set) do + [ + [[{ if: '$VARIABLE == null', when: 'never' }]], + [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]], + [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]] + ] + end - with_them do - it { is_expected.not_to be_included } + with_them do + it { is_expected.not_to be_included } - it 'still correctly populates when:' do - expect(seed_build.attributes).to include(when: 'never') + it 'still correctly populates when:' do + expect(seed_build.attributes).to include(when: 'never') + end end end - end - context 'with an explicit `when: always`' do - where(:rule_set) do - [ - [[{ if: '$VARIABLE == null', when: 'always' }]], - [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]], - [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]] - ] + context 'with an explicit `when: always`' do + where(:rule_set) do + [ + [[{ if: '$VARIABLE == null', when: 'always' }]], + [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]], + [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]] + ] + end + + with_them do + it { is_expected.to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'always') + end + end end - with_them do - it { is_expected.to be_included } + context 'with an explicit `when: on_failure`' do + where(:rule_set) do + [ + [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]], + [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]], + [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]] + ] + end - it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'always') + with_them do + it { is_expected.to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'on_failure') + end end end - end - context 'with an explicit `when: on_failure`' do - where(:rule_set) do - [ - [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]], - [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]], - [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]] - ] + context 'with an explicit `when: delayed`' do + where(:rule_set) do + [ + [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]], + [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]], + [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]] + ] + end + + with_them do + it { is_expected.to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'delayed', options: { start_in: '1 day' }) + end + end end - with_them do - it { is_expected.to be_included } + context 'without an explicit when: value' do + where(:rule_set) do + [ + [[{ if: '$VARIABLE == null' }]], + [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]], + [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]] + ] + end - it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'on_failure') + with_them do + it { is_expected.to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'on_success') + end end end end - context 'with an explicit `when: delayed`' do - where(:rule_set) do - [ - [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]], - [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]], - [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]] - ] + context 'with a matching changes: rule' do + let(:pipeline) do + build(:ci_pipeline, project: project).tap do |pipeline| + stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml]) + end end - with_them do - it { is_expected.to be_included } + context 'with an explicit `when: never`' do + where(:rule_set) do + [ + [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]], + [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]], + [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]], + [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]], + [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]], + [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]], + [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]], + [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]] + ] + end - it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'delayed', options: { start_in: '1 day' }) + with_them do + it { is_expected.not_to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'never') + end end end - end - context 'without an explicit when: value' do - where(:rule_set) do - [ - [[{ if: '$VARIABLE == null' }]], - [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]], - [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]] - ] - end + context 'with an explicit `when: always`' do + where(:rule_set) do + [ + [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]], + [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]], + [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]], + [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]], + [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]], + [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]], + [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]], + [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]] + ] + end - with_them do - it { is_expected.to be_included } + with_them do + it { is_expected.to be_included } - it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'on_success') + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'always') + end end end - end - end - context 'with a matching changes: rule' do - let(:pipeline) do - build(:ci_pipeline, project: project).tap do |pipeline| - stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml]) + context 'without an explicit when: value' do + where(:rule_set) do + [ + [[{ changes: { paths: %w[*/**/*.rb] } }]], + [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]], + [[{ changes: { paths: %w[spec/**/*.rb] } }]], + [[{ changes: { paths: %w[*.yml] } }]], + [[{ changes: { paths: %w[.*.yml] } }]], + [[{ changes: { paths: %w[**/*] } }]], + [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]], + [[{ changes: { paths: %w[.*.yml **/*] } }]] + ] + end + + with_them do + it { is_expected.to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'on_success') + end + end end end - context 'with an explicit `when: never`' do + context 'with no matching rule' do where(:rule_set) do [ - [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]], - [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]], - [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]], - [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]], - [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]], - [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]], - [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]], - [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]] + [[{ if: '$VARIABLE != null', when: 'never' }]], + [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]], + [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]], + [[{ if: '$VARIABLE != null', when: 'always' }]], + [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]], + [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]], + [[{ if: '$VARIABLE != null' }]], + [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]], + [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]] ] end @@ -878,257 +971,249 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end end - context 'with an explicit `when: always`' do - where(:rule_set) do - [ - [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]], - [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]], - [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]], - [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]], - [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]], - [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]], - [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]], - [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]] - ] + context 'with a rule using CI_ENVIRONMENT_NAME variable' do + let(:rule_set) do + [{ if: '$CI_ENVIRONMENT_NAME == "test"' }] end - with_them do + context 'when environment:name satisfies the rule' do + let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'test', when: 'on_success' } } + it { is_expected.to be_included } it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'always') + expect(seed_build.attributes).to include(when: 'on_success') end end - end - context 'without an explicit when: value' do - where(:rule_set) do - [ - [[{ changes: { paths: %w[*/**/*.rb] } }]], - [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]], - [[{ changes: { paths: %w[spec/**/*.rb] } }]], - [[{ changes: { paths: %w[*.yml] } }]], - [[{ changes: { paths: %w[.*.yml] } }]], - [[{ changes: { paths: %w[**/*] } }]], - [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]], - [[{ changes: { paths: %w[.*.yml **/*] } }]] - ] + context 'when environment:name does not satisfy rule' do + let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'dev', when: 'on_success' } } + + it { is_expected.not_to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'never') + end end - with_them do - it { is_expected.to be_included } + context 'when environment:name is not set' do + it { is_expected.not_to be_included } it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'on_success') + expect(seed_build.attributes).to include(when: 'never') end end end - end - context 'with no matching rule' do - where(:rule_set) do - [ - [[{ if: '$VARIABLE != null', when: 'never' }]], - [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]], - [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]], - [[{ if: '$VARIABLE != null', when: 'always' }]], - [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]], - [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]], - [[{ if: '$VARIABLE != null' }]], - [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]], - [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]] - ] + context 'with no rules' do + let(:rule_set) { [] } + + it { is_expected.not_to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'never') + end end - with_them do + context 'with invalid rules raising error' do + let(:rule_set) do + [ + { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' } + ] + end + it { is_expected.not_to be_included } it 'correctly populates when:' do expect(seed_build.attributes).to include(when: 'never') end + + it 'returns an error' do + expect(seed_build.errors).to contain_exactly( + 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref' + ) + end end end + end - context 'with no rules' do - let(:rule_set) { [] } + describe 'applying needs: dependency' do + subject { seed_build } - it { is_expected.not_to be_included } + let(:needs_count) { 1 } - it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'never') - end + let(:needs_attributes) do + Array.new(needs_count, name: 'build') end - context 'with invalid rules raising error' do - let(:rule_set) do - [ - { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' } - ] - end + let(:attributes) do + { + name: 'rspec', + needs_attributes: needs_attributes + } + end - it { is_expected.not_to be_included } + context 'when build job is not present in prior stages' do + it "is included" do + is_expected.to be_included + end - it 'correctly populates when:' do - expect(seed_build.attributes).to include(when: 'never') + it "returns an error" do + expect(subject.errors).to contain_exactly( + "'rspec' job needs 'build' job, but 'build' is not in any previous stage") end - it 'returns an error' do - expect(seed_build.errors).to contain_exactly( - 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref' - ) + context 'when the needed job is optional' do + let(:needs_attributes) { [{ name: 'build', optional: true }] } + + it "does not return an error" do + expect(subject.errors).to be_empty + end end end - end - end - describe 'applying needs: dependency' do - subject { seed_build } + context 'when build job is part of prior stages' do + let(:stage_attributes) do + { + name: 'build', + index: 0, + builds: [{ name: 'build' }] + } + end - let(:needs_count) { 1 } + let(:stage_seed) do + Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, []) + end - let(:needs_attributes) do - Array.new(needs_count, name: 'build') - end + let(:previous_stages) { [stage_seed] } - let(:attributes) do - { - name: 'rspec', - needs_attributes: needs_attributes - } - end + it "is included" do + is_expected.to be_included + end - context 'when build job is not present in prior stages' do - it "is included" do - is_expected.to be_included + it "does not have errors" do + expect(subject.errors).to be_empty + end end - it "returns an error" do - expect(subject.errors).to contain_exactly( - "'rspec' job needs 'build' job, but 'build' is not in any previous stage") - end + context 'when build job is part of the same stage' do + let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) } - context 'when the needed job is optional' do - let(:needs_attributes) { [{ name: 'build', optional: true }] } + it 'is included' do + is_expected.to be_included + end - it "does not return an error" do + it 'does not have errors' do expect(subject.errors).to be_empty end end - end - - context 'when build job is part of prior stages' do - let(:stage_attributes) do - { - name: 'build', - index: 0, - builds: [{ name: 'build' }] - } - end - - let(:stage_seed) do - Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, []) - end - let(:previous_stages) { [stage_seed] } + context 'when using 101 needs' do + let(:needs_count) { 101 } - it "is included" do - is_expected.to be_included - end + it "returns an error" do + expect(subject.errors).to contain_exactly( + "rspec: one job can only need 50 others, but you have listed 101. See needs keyword documentation for more details") + end - it "does not have errors" do - expect(subject.errors).to be_empty - end - end + context 'when ci_needs_size_limit is set to 100' do + before do + project.actual_limits.update!(ci_needs_size_limit: 100) + end - context 'when build job is part of the same stage' do - let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) } + it "returns an error" do + expect(subject.errors).to contain_exactly( + "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details") + end + end - it 'is included' do - is_expected.to be_included - end + context 'when ci_needs_size_limit is set to 0' do + before do + project.actual_limits.update!(ci_needs_size_limit: 0) + end - it 'does not have errors' do - expect(subject.errors).to be_empty + it "returns an error" do + expect(subject.errors).to contain_exactly( + "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details") + end + end end end - context 'when using 101 needs' do - let(:needs_count) { 101 } + describe 'applying pipeline variables' do + subject { seed_build } - it "returns an error" do - expect(subject.errors).to contain_exactly( - "rspec: one job can only need 50 others, but you have listed 101. See needs keyword documentation for more details") + let(:pipeline_variables) { [] } + let(:pipeline) do + build(:ci_empty_pipeline, project: project, sha: head_sha, variables: pipeline_variables) end - context 'when ci_needs_size_limit is set to 100' do - before do - project.actual_limits.update!(ci_needs_size_limit: 100) + context 'containing variable references' do + let(:pipeline_variables) do + [ + build(:ci_pipeline_variable, key: 'A', value: '$B'), + build(:ci_pipeline_variable, key: 'B', value: '$C') + ] end - it "returns an error" do - expect(subject.errors).to contain_exactly( - "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details") + it "does not have errors" do + expect(subject.errors).to be_empty end end - context 'when ci_needs_size_limit is set to 0' do - before do - project.actual_limits.update!(ci_needs_size_limit: 0) + context 'containing cyclic reference' do + let(:pipeline_variables) do + [ + build(:ci_pipeline_variable, key: 'A', value: '$B'), + build(:ci_pipeline_variable, key: 'B', value: '$C'), + build(:ci_pipeline_variable, key: 'C', value: '$A') + ] end it "returns an error" do expect(subject.errors).to contain_exactly( - "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details") + 'rspec: circular variable reference detected: ["A", "B", "C"]') + end + + context 'with job:rules:[if:]' do + let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } } + + it "included? does not raise" do + expect { subject.included? }.not_to raise_error + end + + it "included? returns true" do + expect(subject.included?).to eq(true) + end end end end end - describe 'applying pipeline variables' do - subject { seed_build } - - let(:pipeline_variables) { [] } - let(:pipeline) do - build(:ci_empty_pipeline, project: project, sha: head_sha, variables: pipeline_variables) + describe 'feature flag ci_reuse_build_in_seed_context' do + let(:attributes) do + { name: 'rspec', rules: [{ if: '$VARIABLE == null' }], when: 'on_success' } end - context 'containing variable references' do - let(:pipeline_variables) do - [ - build(:ci_pipeline_variable, key: 'A', value: '$B'), - build(:ci_pipeline_variable, key: 'B', value: '$C') - ] - end + context 'when enabled' do + it_behaves_like 'build seed' - it "does not have errors" do - expect(subject.errors).to be_empty + it 'initializes the build once' do + expect(Ci::Build).to receive(:new).once.and_call_original + seed_build.to_resource end end - context 'containing cyclic reference' do - let(:pipeline_variables) do - [ - build(:ci_pipeline_variable, key: 'A', value: '$B'), - build(:ci_pipeline_variable, key: 'B', value: '$C'), - build(:ci_pipeline_variable, key: 'C', value: '$A') - ] - end - - it "returns an error" do - expect(subject.errors).to contain_exactly( - 'rspec: circular variable reference detected: ["A", "B", "C"]') + context 'when disabled' do + before do + stub_feature_flags(ci_reuse_build_in_seed_context: false) end - context 'with job:rules:[if:]' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } } - - it "included? does not raise" do - expect { subject.included? }.not_to raise_error - end + it_behaves_like 'build seed' - it "included? returns true" do - expect(subject.included?).to eq(true) - end + it 'initializes the build twice' do + expect(Ci::Build).to receive(:new).twice.and_call_original + seed_build.to_resource end end end |