debian-mirror-gitlab/spec/lib/gitlab/ci/config/entry/job_spec.rb

780 lines
22 KiB
Ruby
Raw Normal View History

2019-10-12 21:52:04 +05:30
# frozen_string_literal: true
2018-12-13 13:39:08 +05:30
require 'spec_helper'
2016-09-13 17:45:13 +05:30
2020-07-28 23:09:34 +05:30
RSpec.describe Gitlab::Ci::Config::Entry::Job do
2016-09-13 17:45:13 +05:30
let(:entry) { described_class.new(config, name: :rspec) }
2019-12-26 22:10:19 +05:30
it_behaves_like 'with inheritable CI config' do
2020-04-08 14:13:33 +05:30
let(:config) { { script: 'echo' } }
2019-12-26 22:10:19 +05:30
let(:inheritable_key) { 'default' }
let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default }
# These are entries defined in Default
# that we know that we don't want to inherit
# as they do not have sense in context of Job
let(:ignored_inheritable_columns) do
%i[]
end
2020-04-08 14:13:33 +05:30
before do
allow(entry).to receive_message_chain(:inherit_entry, :default_entry, :inherit?).and_return(true)
end
2019-12-26 22:10:19 +05:30
end
2017-08-17 22:00:37 +05:30
describe '.nodes' do
context 'when filtering all the entry/node names' do
subject { described_class.nodes.keys }
let(:result) do
%i[before_script script stage type after_script cache
2019-12-26 22:10:19 +05:30
image services only except rules needs variables artifacts
2020-04-08 14:13:33 +05:30
environment coverage retry interruptible timeout release tags
2020-10-24 23:57:45 +05:30
inherit parallel]
2017-08-17 22:00:37 +05:30
end
2020-07-28 23:09:34 +05:30
it { is_expected.to include(*result) }
2017-08-17 22:00:37 +05:30
end
end
2019-09-30 21:07:59 +05:30
describe '.matching?' do
subject { described_class.matching?(name, config) }
context 'when config is not a hash' do
let(:name) { :rspec }
let(:config) { 'string' }
it { is_expected.to be_falsey }
end
context 'when config is a regular job' do
let(:name) { :rspec }
let(:config) do
{ script: 'ls -al' }
end
it { is_expected.to be_truthy }
end
context 'when config is a bridge job' do
let(:name) { :rspec }
let(:config) do
{ trigger: 'other-project' }
end
it { is_expected.to be_falsey }
end
context 'when config is a hidden job' do
let(:name) { '.rspec' }
let(:config) do
{ script: 'ls -al' }
end
it { is_expected.to be_falsey }
end
2020-10-24 23:57:45 +05:30
context 'when using the default job without script' do
let(:name) { :default }
let(:config) do
{ before_script: "cd ${PROJ_DIR} " }
end
it { is_expected.to be_falsey }
end
context 'when using the default job with script' do
let(:name) { :default }
let(:config) do
{
before_script: "cd ${PROJ_DIR} ",
script: "ls"
}
end
it { is_expected.to be_truthy }
end
2019-09-30 21:07:59 +05:30
end
2016-09-13 17:45:13 +05:30
describe 'validations' do
2017-09-10 17:25:29 +05:30
before do
entry.compose!
end
2016-09-29 09:46:39 +05:30
2016-09-13 17:45:13 +05:30
context 'when entry config value is correct' do
let(:config) { { script: 'rspec' } }
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
context 'when job name is empty' do
let(:entry) { described_class.new(config, name: ''.to_sym) }
it 'reports error' do
2017-08-17 22:00:37 +05:30
expect(entry.errors).to include "job name can't be blank"
2016-09-13 17:45:13 +05:30
end
end
2018-12-05 23:21:45 +05:30
context 'when delayed job' do
context 'when start_in is specified' do
2020-01-01 13:55:28 +05:30
let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } }
2018-12-05 23:21:45 +05:30
it { expect(entry).to be_valid }
end
end
2019-10-12 21:52:04 +05:30
context 'when has needs' do
let(:config) do
{
stage: 'test',
script: 'echo',
needs: ['another-job']
}
end
it { expect(entry).to be_valid }
2020-03-13 15:44:24 +05:30
it "returns scheduling_type as :dag" do
expect(entry.value[:scheduling_type]).to eq(:dag)
end
2019-10-12 21:52:04 +05:30
context 'when has dependencies' do
let(:config) do
{
stage: 'test',
script: 'echo',
dependencies: ['another-job'],
needs: ['another-job']
}
end
it { expect(entry).to be_valid }
end
2020-03-13 15:44:24 +05:30
context 'when it is a release' do
let(:config) do
{
script: ["make changelog | tee release_changelog.txt"],
release: {
tag_name: "v0.06",
name: "Release $CI_TAG_NAME",
description: "./release_changelog.txt"
}
}
end
it { expect(entry).to be_valid }
end
2019-10-12 21:52:04 +05:30
end
2016-09-13 17:45:13 +05:30
end
context 'when entry value is not correct' do
context 'incorrect config value type' do
let(:config) { ['incorrect'] }
describe '#errors' do
it 'reports error about a config type' do
expect(entry.errors)
.to include 'job config should be a hash'
end
end
end
context 'when config is empty' do
let(:config) { {} }
describe '#valid' do
it 'is invalid' do
expect(entry).not_to be_valid
end
end
end
context 'when unknown keys detected' do
let(:config) { { unknown: true } }
describe '#valid' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
2017-08-17 22:00:37 +05:30
context 'when script is not provided' do
let(:config) { { stage: 'test' } }
it 'returns error about missing script entry' do
expect(entry).not_to be_valid
expect(entry.errors).to include "job script can't be blank"
end
end
2017-09-10 17:25:29 +05:30
2018-11-20 20:47:30 +05:30
context 'when extends key is not a string' do
let(:config) { { extends: 123 } }
it 'returns error about wrong value type' do
expect(entry).not_to be_valid
2019-09-04 21:01:54 +05:30
expect(entry.errors).to include "job extends should be an array of strings or a string"
2018-11-20 20:47:30 +05:30
end
end
2018-12-13 13:39:08 +05:30
context 'when parallel value is not correct' do
2017-09-10 17:25:29 +05:30
context 'when it is not a numeric value' do
2020-10-24 23:57:45 +05:30
let(:config) { { script: 'echo', parallel: true } }
2017-09-10 17:25:29 +05:30
it 'returns error about invalid type' do
expect(entry).not_to be_valid
2020-10-24 23:57:45 +05:30
expect(entry.errors).to include 'parallel should be an integer or a hash'
2017-09-10 17:25:29 +05:30
end
end
2018-12-13 13:39:08 +05:30
context 'when it is lower than two' do
2020-10-24 23:57:45 +05:30
let(:config) { { script: 'echo', parallel: 1 } }
2017-09-10 17:25:29 +05:30
it 'returns error about value too low' do
expect(entry).not_to be_valid
expect(entry.errors)
2020-10-24 23:57:45 +05:30
.to include 'parallel config must be greater than or equal to 2'
2017-09-10 17:25:29 +05:30
end
end
2020-10-24 23:57:45 +05:30
context 'when it is an empty hash' do
let(:config) { { script: 'echo', parallel: {} } }
2017-09-10 17:25:29 +05:30
2020-10-24 23:57:45 +05:30
it 'returns error about missing matrix' do
2017-09-10 17:25:29 +05:30
expect(entry).not_to be_valid
2018-12-13 13:39:08 +05:30
expect(entry.errors)
2020-10-24 23:57:45 +05:30
.to include 'parallel config missing required keys: matrix'
2017-09-10 17:25:29 +05:30
end
end
2020-10-24 23:57:45 +05:30
end
2017-09-10 17:25:29 +05:30
2020-10-24 23:57:45 +05:30
context 'when it uses both "when:" and "rules:"' do
let(:config) do
{
script: 'echo',
when: 'on_failure',
rules: [{ if: '$VARIABLE', when: 'on_success' }]
}
2017-09-10 17:25:29 +05:30
end
2019-12-04 20:38:33 +05:30
2020-10-24 23:57:45 +05:30
it 'returns an error about when: being combined with rules' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job config key may not be used with `rules`: when'
2019-12-04 20:38:33 +05:30
end
2017-09-10 17:25:29 +05:30
end
2018-12-05 23:21:45 +05:30
context 'when delayed job' do
context 'when start_in is specified' do
2020-01-01 13:55:28 +05:30
let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } }
2018-12-05 23:21:45 +05:30
2020-01-01 13:55:28 +05:30
it { expect(entry).to be_valid }
2018-12-05 23:21:45 +05:30
end
context 'when start_in is empty' do
let(:config) { { when: 'delayed', start_in: nil } }
it 'returns error about invalid type' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job start in should be a duration'
end
end
context 'when start_in is not formatted as a duration' do
let(:config) { { when: 'delayed', start_in: 'test' } }
it 'returns error about invalid type' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job start in should be a duration'
end
end
2020-01-01 13:55:28 +05:30
context 'when start_in is longer than one week' do
let(:config) { { when: 'delayed', start_in: '8 days' } }
2018-12-05 23:21:45 +05:30
it 'returns error about exceeding the limit' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job start in should not exceed the limit'
end
end
end
2019-12-04 20:38:33 +05:30
context 'when only: is used with rules:' do
let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
it 'returns error about mixing only: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
end
context 'and only: is blank' do
let(:config) { { only: nil, rules: [{ if: '$THIS' }] } }
it 'returns error about mixing only: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
end
end
context 'and rules: is blank' do
let(:config) { { only: ['merge_requests'], rules: nil } }
it 'returns error about mixing only: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
end
end
end
context 'when except: is used with rules:' do
let(:config) { { except: { refs: %w[master] }, rules: [{ if: '$THIS' }] } }
it 'returns error about mixing except: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
end
context 'and except: is blank' do
let(:config) { { except: nil, rules: [{ if: '$THIS' }] } }
it 'returns error about mixing except: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
end
end
context 'and rules: is blank' do
let(:config) { { except: { refs: %w[master] }, rules: nil } }
it 'returns error about mixing except: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
end
end
end
context 'when only: and except: are both used with rules:' do
let(:config) do
{
only: %w[merge_requests],
except: { refs: %w[master] },
rules: [{ if: '$THIS' }]
}
end
it 'returns errors about mixing both only: and except: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
expect(entry.errors).to include /may not be used with `rules`/
end
context 'when only: and except: as both blank' do
let(:config) do
{ only: nil, except: nil, rules: [{ if: '$THIS' }] }
end
it 'returns errors about mixing both only: and except: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
expect(entry.errors).to include /may not be used with `rules`/
end
end
context 'when rules: is blank' do
let(:config) do
{ only: %w[merge_requests], except: { refs: %w[master] }, rules: nil }
end
it 'returns errors about mixing both only: and except: with rules:' do
expect(entry).not_to be_valid
expect(entry.errors).to include /may not be used with `rules`/
expect(entry.errors).to include /may not be used with `rules`/
end
end
end
2018-12-05 23:21:45 +05:30
context 'when start_in specified without delayed specification' do
let(:config) { { start_in: '1 day' } }
it 'returns error about invalid type' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job start in must be blank'
end
end
2019-10-12 21:52:04 +05:30
context 'when has dependencies' do
context 'that are not a array of strings' do
let(:config) do
{ script: 'echo', dependencies: 'build-job' }
end
it 'returns error about invalid type' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job dependencies should be an array of strings'
end
end
end
context 'when has needs' do
context 'when have dependencies that are not subset of needs' do
let(:config) do
{
stage: 'test',
script: 'echo',
dependencies: ['another-job'],
needs: ['build-job']
}
end
it 'returns error about invalid data' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job dependencies the another-job should be part of needs'
end
end
context 'when stage: is missing' do
let(:config) do
{
script: 'echo',
needs: ['build-job']
}
end
it 'returns error about invalid data' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job config missing required keys: stage'
end
end
end
2019-12-04 20:38:33 +05:30
context 'when timeout value is not correct' do
context 'when it is higher than instance wide timeout' do
2020-01-01 13:55:28 +05:30
let(:config) { { timeout: '3 months', script: 'test' } }
2019-12-04 20:38:33 +05:30
it 'returns error about value too high' do
expect(entry).not_to be_valid
expect(entry.errors)
2020-01-01 13:55:28 +05:30
.to include "timeout config should not exceed the limit"
2019-12-04 20:38:33 +05:30
end
end
context 'when it is not a duration' do
2020-01-01 13:55:28 +05:30
let(:config) { { timeout: 100, script: 'test' } }
2019-12-04 20:38:33 +05:30
it 'returns error about wrong value' do
expect(entry).not_to be_valid
2020-01-01 13:55:28 +05:30
expect(entry.errors).to include 'timeout config should be a duration'
2019-12-04 20:38:33 +05:30
end
end
end
context 'when timeout value is correct' do
let(:config) { { script: 'echo', timeout: '1m 1s' } }
it 'returns correct timeout' do
expect(entry).to be_valid
expect(entry.errors).to be_empty
expect(entry.timeout).to eq('1m 1s')
end
end
2020-03-13 15:44:24 +05:30
context 'when it is a release' do
context 'when `release:description` is missing' do
let(:config) do
{
script: ["make changelog | tee release_changelog.txt"],
release: {
tag_name: "v0.06",
name: "Release $CI_TAG_NAME"
}
}
end
it "returns error" do
expect(entry).not_to be_valid
expect(entry.errors).to include "release description can't be blank"
end
end
end
2016-09-13 17:45:13 +05:30
end
end
2016-09-29 09:46:39 +05:30
describe '#relevant?' do
it 'is a relevant entry' do
2018-11-20 20:47:30 +05:30
entry = described_class.new({ script: 'rspec' }, name: :rspec)
2016-09-29 09:46:39 +05:30
expect(entry).to be_relevant
end
end
describe '#compose!' do
let(:specified) do
double('specified', 'specified?' => true, value: 'specified')
end
2019-09-30 21:07:59 +05:30
let(:unspecified) { double('unspecified', 'specified?' => false) }
let(:default) { double('default', '[]' => unspecified) }
2020-01-01 13:55:28 +05:30
let(:workflow) { double('workflow', 'has_rules?' => false) }
2020-04-08 14:13:33 +05:30
let(:deps) do
double('deps',
'default_entry' => default,
'workflow_entry' => workflow,
'variables_value' => nil)
end
2016-09-29 09:46:39 +05:30
2019-09-30 21:07:59 +05:30
context 'when job config overrides default config' do
2017-09-10 17:25:29 +05:30
before do
entry.compose!(deps)
end
2016-09-29 09:46:39 +05:30
2016-09-13 17:45:13 +05:30
let(:config) do
2017-08-17 22:00:37 +05:30
{ script: 'rspec', image: 'some_image', cache: { key: 'test' } }
2016-09-29 09:46:39 +05:30
end
2019-09-30 21:07:59 +05:30
it 'overrides default config' do
2017-09-10 17:25:29 +05:30
expect(entry[:image].value).to eq(name: 'some_image')
2021-01-03 14:25:43 +05:30
expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
2016-09-29 09:46:39 +05:30
end
end
2019-09-30 21:07:59 +05:30
context 'when job config does not override default config' do
2016-09-29 09:46:39 +05:30
before do
2019-09-30 21:07:59 +05:30
allow(default).to receive('[]').with(:image).and_return(specified)
2016-09-29 09:46:39 +05:30
entry.compose!(deps)
2016-09-13 17:45:13 +05:30
end
2016-09-29 09:46:39 +05:30
let(:config) { { script: 'ls', cache: { key: 'test' } } }
2019-09-30 21:07:59 +05:30
it 'uses config from default entry' do
2016-09-29 09:46:39 +05:30
expect(entry[:image].value).to eq 'specified'
2021-01-03 14:25:43 +05:30
expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
2016-09-13 17:45:13 +05:30
end
end
2020-01-01 13:55:28 +05:30
context 'with workflow rules' do
using RSpec::Parameterized::TableSyntax
where(:name, :has_workflow_rules?, :only, :rules, :result) do
"uses default only" | false | nil | nil | { refs: %w[branches tags] }
"uses user only" | false | %w[branches] | nil | { refs: %w[branches] }
"does not define only" | false | nil | [] | nil
"does not define only" | true | nil | nil | nil
"uses user only" | true | %w[branches] | nil | { refs: %w[branches] }
"does not define only" | true | nil | [] | nil
end
with_them do
let(:config) { { script: 'ls', rules: rules, only: only }.compact }
it "#{name}" do
expect(workflow).to receive(:has_rules?) { has_workflow_rules? }
entry.compose!(deps)
expect(entry.only_value).to eq(result)
end
end
end
context 'when workflow rules is used' do
context 'when rules are used' do
let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } }
it 'does not define only' do
expect(entry).not_to be_only_defined
end
end
context 'when rules are not used' do
let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } }
it 'does not define only' do
expect(entry).not_to be_only_defined
end
end
end
2016-09-13 17:45:13 +05:30
end
2016-09-29 09:46:39 +05:30
context 'when composed' do
2017-09-10 17:25:29 +05:30
before do
entry.compose!
end
2016-09-29 09:46:39 +05:30
describe '#value' do
2017-09-10 17:25:29 +05:30
before do
entry.compose!
end
2016-09-29 09:46:39 +05:30
context 'when entry is correct' do
let(:config) do
{ before_script: %w[ls pwd],
script: 'rspec',
after_script: %w[cleanup] }
end
it 'returns correct value' do
expect(entry.value)
.to eq(name: :rspec,
before_script: %w[ls pwd],
script: %w[rspec],
stage: 'test',
2017-08-17 22:00:37 +05:30
ignore: false,
2019-02-15 15:39:39 +05:30
after_script: %w[cleanup],
2019-09-30 21:07:59 +05:30
only: { refs: %w[branches tags] },
2020-03-13 15:44:24 +05:30
variables: {},
scheduling_type: :stage)
2016-09-29 09:46:39 +05:30
end
end
end
2016-09-13 17:45:13 +05:30
end
2017-08-17 22:00:37 +05:30
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
2018-12-05 23:21:45 +05:30
describe '#delayed?' do
context 'when job is a delayed' do
let(:config) { { script: 'deploy', when: 'delayed' } }
it 'is a delayed' do
expect(entry).to be_delayed
end
end
context 'when job is not a delayed' do
let(:config) { { script: 'deploy' } }
it 'is not a delayed' do
expect(entry).not_to be_delayed
end
end
end
2017-08-17 22:00:37 +05:30
describe '#ignored?' do
2021-02-22 17:27:13 +05:30
before do
entry.compose!
end
2017-08-17 22:00:37 +05:30
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
2021-02-22 17:27:13 +05:30
context 'when job is dynamically allowed to fail' do
let(:config) do
{ script: 'deploy', when: 'manual', allow_failure: { exit_codes: 42 } }
end
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
end
2017-08-17 22:00:37 +05:30
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
2021-02-22 17:27:13 +05:30
it 'does not return allow_failure' do
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
end
2017-08-17 22:00:37 +05:30
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
2021-02-22 17:27:13 +05:30
it 'does not return allow_failure_criteria' do
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
end
2017-08-17 22:00:37 +05:30
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
2021-02-22 17:27:13 +05:30
it 'does not return allow_failure_criteria' do
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
end
end
context 'when job is dynamically allowed to fail' do
let(:config) { { script: 'deploy', allow_failure: { exit_codes: 42 } } }
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
it 'returns allow_failure_criteria' do
expect(entry.value[:allow_failure_criteria]).to match(exit_codes: [42])
end
context 'with ci_allow_failure_with_exit_codes disabled' do
before do
stub_feature_flags(ci_allow_failure_with_exit_codes: false)
end
it 'does not return allow_failure_criteria' do
expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
end
end
2017-08-17 22:00:37 +05:30
end
end
end
2016-09-13 17:45:13 +05:30
end