diff options
Diffstat (limited to 'spec/lib/gitlab/ci/yaml_processor_spec.rb')
-rw-r--r-- | spec/lib/gitlab/ci/yaml_processor_spec.rb | 806 |
1 files changed, 342 insertions, 464 deletions
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 1c81cc83cd1..d596494a987 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -7,10 +7,16 @@ module Gitlab RSpec.describe YamlProcessor do include StubRequests - subject { described_class.new(config, user: nil) } + subject { described_class.new(config, user: nil).execute } + + shared_examples 'returns errors' do |error_message| + it 'adds a message when an error is encountered' do + expect(subject.errors).to include(error_message) + end + end describe '#build_attributes' do - subject { described_class.new(config, user: nil).build_attributes(:rspec) } + subject { described_class.new(config, user: nil).execute.build_attributes(:rspec) } describe 'attributes list' do let(:config) do @@ -92,7 +98,7 @@ module Gitlab config = YAML.dump({ default: { tags: %w[A B] }, rspec: { script: "rspec" } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -139,7 +145,7 @@ module Gitlab config = YAML.dump({ default: { interruptible: true }, rspec: { script: "rspec" } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -345,9 +351,7 @@ module Gitlab EOYML end - it 'parses the workflow:rules configuration' do - expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'workflow config contains unknown keys: variables') - end + it_behaves_like 'returns errors', 'workflow config contains unknown keys: variables' end context 'with rules and variables' do @@ -470,12 +474,11 @@ module Gitlab end it 'is propagated all the way up into the raised exception' do - expect { subject }.to raise_error do |error| - expect(error).to be_a(described_class::ValidationError) - expect(error.message).to eq('jobs:invalid:artifacts config should be a hash') - expect(error.warnings).to contain_exactly(/jobs:rspec may allow multiple pipelines to run/) - end + expect(subject).not_to be_valid + expect(subject.warnings).to contain_exactly(/jobs:rspec may allow multiple pipelines to run/) end + + it_behaves_like 'returns errors', 'jobs:invalid:artifacts config should be a hash' end context 'when error is raised before composing the config' do @@ -489,23 +492,18 @@ module Gitlab EOYML end - it 'raises an exception with empty warnings array' do - expect { subject }.to raise_error do |error| - expect(error).to be_a(described_class::ValidationError) - expect(error.message).to eq('Local file `unknown/file.yml` does not have project!') - expect(error.warnings).to be_empty - end + it 'has empty warnings' do + expect(subject.warnings).to be_empty end + + it_behaves_like 'returns errors', 'Local file `unknown/file.yml` does not have project!' end context 'when error is raised after composing the config with warnings' do shared_examples 'has warnings and expected error' do |error_message| - it 'raises an exception including warnings' do - expect { subject }.to raise_error do |error| - expect(error).to be_a(described_class::ValidationError) - expect(error.message).to match(error_message) - expect(error.warnings).to be_present - end + it 'returns errors and warnings', :aggregate_failures do + expect(subject.errors).to include(error_message) + expect(subject.warnings).to be_present end end @@ -585,72 +583,56 @@ module Gitlab describe 'only / except policies validations' do context 'when `only` has an invalid value' do let(:config) { { rspec: { script: "rspec", type: "test", only: only } } } - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } context 'when it is integer' do let(:only) { 1 } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only has to be either an array of conditions or a hash') - end + it_behaves_like 'returns errors', 'jobs:rspec:only has to be either an array of conditions or a hash' end context 'when it is an array of integers' do let(:only) { [1, 1] } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only config should be an array of strings or regexps') - end + it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regexps' end context 'when it is invalid regex' do let(:only) { ["/*invalid/"] } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only config should be an array of strings or regexps') - end + it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regexps' end end context 'when `except` has an invalid value' do let(:config) { { rspec: { script: "rspec", except: except } } } - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } context 'when it is integer' do let(:except) { 1 } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:except has to be either an array of conditions or a hash') - end + it_behaves_like 'returns errors', 'jobs:rspec:except has to be either an array of conditions or a hash' end context 'when it is an array of integers' do let(:except) { [1, 1] } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:except config should be an array of strings or regexps') - end + it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regexps' end context 'when it is invalid regex' do let(:except) { ["/*invalid/"] } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:except config should be an array of strings or regexps') - end + it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regexps' end end end describe "Scripts handling" do let(:config_data) { YAML.dump(config) } - let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data) } + let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data).execute } subject { config_processor.stage_builds_attributes('test').first } @@ -819,7 +801,7 @@ module Gitlab before_script: ["pwd"], rspec: { script: "rspec" } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -852,7 +834,7 @@ module Gitlab command: ["/usr/local/bin/init", "run"] }, "docker:dind"], script: "rspec" } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -883,7 +865,7 @@ module Gitlab before_script: ["pwd"], rspec: { script: "rspec" } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -910,7 +892,7 @@ module Gitlab before_script: ["pwd"], rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -934,9 +916,9 @@ module Gitlab end describe 'Variables' do - let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } - subject { config_processor.builds.first[:yaml_variables] } + let(:build_variables) { subject.builds.first[:yaml_variables] } context 'when global variables are defined' do let(:variables) do @@ -952,7 +934,7 @@ module Gitlab end it 'returns global variables' do - expect(subject).to contain_exactly( + expect(build_variables).to contain_exactly( { key: 'VAR1', value: 'value1', public: true }, { key: 'VAR2', value: 'value2', public: true } ) @@ -980,7 +962,7 @@ module Gitlab let(:inherit) { } it 'returns all unique variables' do - expect(subject).to contain_exactly( + expect(build_variables).to contain_exactly( { key: 'VAR4', value: 'global4', public: true }, { key: 'VAR3', value: 'global3', public: true }, { key: 'VAR1', value: 'value1', public: true }, @@ -993,7 +975,7 @@ module Gitlab let(:inherit) { { variables: false } } it 'does not inherit variables' do - expect(subject).to contain_exactly( + expect(build_variables).to contain_exactly( { key: 'VAR1', value: 'value1', public: true }, { key: 'VAR2', value: 'value2', public: true } ) @@ -1004,7 +986,7 @@ module Gitlab let(:inherit) { { variables: %w[VAR1 VAR4] } } it 'returns all unique variables and inherits only specified variables' do - expect(subject).to contain_exactly( + expect(build_variables).to contain_exactly( { key: 'VAR4', value: 'global4', public: true }, { key: 'VAR1', value: 'value1', public: true }, { key: 'VAR2', value: 'value2', public: true } @@ -1027,7 +1009,7 @@ module Gitlab end it 'returns job variables' do - expect(subject).to contain_exactly( + expect(build_variables).to contain_exactly( { key: 'VAR1', value: 'value1', public: true }, { key: 'VAR2', value: 'value2', public: true } ) @@ -1040,11 +1022,7 @@ module Gitlab %w(VAR1 value1 VAR2 value2) end - it 'raises error' do - expect { subject } - .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - /jobs:rspec:variables config should be a hash of key value pairs/) - end + it_behaves_like 'returns errors', /jobs:rspec:variables config should be a hash of key value pairs/ end context 'when variables key defined but value not specified' do @@ -1057,8 +1035,8 @@ module Gitlab # When variables config is empty, we assume this is a valid # configuration, see issue #18775 # - expect(subject).to be_an_instance_of(Array) - expect(subject).to be_empty + expect(build_variables).to be_an_instance_of(Array) + expect(build_variables).to be_empty end end end @@ -1073,14 +1051,14 @@ module Gitlab end it 'returns empty array' do - expect(subject).to be_an_instance_of(Array) - expect(subject).to be_empty + expect(build_variables).to be_an_instance_of(Array) + expect(build_variables).to be_empty end end end context 'when using `extends`' do - let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } + let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config).execute } subject { config_processor.builds.first } @@ -1142,31 +1120,25 @@ module Gitlab } end - subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts).execute } context "when validating a ci config file with no project context" do context "when a single string is provided" do let(:include_content) { "/local.gitlab-ci.yml" } - it "returns a validation error" do - expect { subject }.to raise_error /does not have project/ - end + it_behaves_like 'returns errors', /does not have project/ end context "when an array is provided" do let(:include_content) { ["/local.gitlab-ci.yml"] } - it "returns a validation error" do - expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /does not have project/) - end + it_behaves_like 'returns errors', /does not have project/ end context "when an array of wrong keyed object is provided" do let(:include_content) { [{ yolo: "/local.gitlab-ci.yml" }] } - it "returns a validation error" do - expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) - end + it_behaves_like 'returns errors', /needs to match exactly one accessor/ end context "when an array of mixed typed objects is provided" do @@ -1185,17 +1157,13 @@ module Gitlab body: 'prepare: { script: ls -al }') end - it "does not return any error" do - expect { subject }.not_to raise_error - end + it { is_expected.to be_valid } end context "when the include type is incorrect" do let(:include_content) { { name: "/local.gitlab-ci.yml" } } - it "returns an invalid configuration error" do - expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) - end + it_behaves_like 'returns errors', /needs to match exactly one accessor/ end end @@ -1210,18 +1178,11 @@ module Gitlab .and_return(YAML.dump({ job1: { script: 'hello' } })) end - it "does not return an error" do - expect { subject }.not_to raise_error - end + it { is_expected.to be_valid } end context "when the included internal file is not present" do - it "returns an error with missing file details" do - expect { subject }.to raise_error( - Gitlab::Ci::YamlProcessor::ValidationError, - "Local file `#{include_content}` does not exist!" - ) - end + it_behaves_like 'returns errors', "Local file `/local.gitlab-ci.yml` does not exist!" end end end @@ -1233,7 +1194,7 @@ module Gitlab rspec: { script: 'rspec', when: when_state } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute builds = config_processor.stage_builds_attributes("test") expect(builds.size).to eq(1) @@ -1243,13 +1204,14 @@ module Gitlab context 'delayed' do context 'with start_in' do - it 'creates one build and sets when:' do - config = YAML.dump({ + let(:config) do + YAML.dump({ rspec: { script: 'rspec', when: 'delayed', start_in: '1 hour' } }) + end - config_processor = Gitlab::Ci::YamlProcessor.new(config) - builds = config_processor.stage_builds_attributes("test") + it 'creates one build and sets when:' do + builds = subject.stage_builds_attributes("test") expect(builds.size).to eq(1) expect(builds.first[:when]).to eq('delayed') @@ -1258,15 +1220,13 @@ module Gitlab end context 'without start_in' do - it 'raises an error' do - config = YAML.dump({ + let(:config) do + YAML.dump({ rspec: { script: 'rspec', when: 'delayed' } }) - - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(YamlProcessor::ValidationError, /start in should be a duration/) end + + it_behaves_like 'returns errors', /start in should be a duration/ end end end @@ -1278,7 +1238,7 @@ module Gitlab variables: { 'VAR1' => 1 } }) end - let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } + let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config).execute } let(:builds) { config_processor.stage_builds_attributes('test') } context 'when job is parallelized' do @@ -1377,16 +1337,13 @@ module Gitlab describe 'cache' do context 'when cache definition has unknown keys' do - it 'raises relevant validation error' do - config = YAML.dump( + let(:config) do + YAML.dump( { cache: { untracked: true, invalid: 'key' }, rspec: { script: 'rspec' } }) - - expect { Gitlab::Ci::YamlProcessor.new(config) }.to raise_error( - Gitlab::Ci::YamlProcessor::ValidationError, - 'cache config contains unknown keys: invalid' - ) end + + it_behaves_like 'returns errors', 'cache config contains unknown keys: invalid' end it "returns cache when defined globally" do @@ -1397,7 +1354,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq( @@ -1419,7 +1376,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq( @@ -1438,7 +1395,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes('test').size).to eq(1) expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq( @@ -1461,7 +1418,7 @@ module Gitlab } ) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes('test').size).to eq(1) expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq( @@ -1484,7 +1441,7 @@ module Gitlab } ) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes('test').size).to eq(1) expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq( @@ -1504,7 +1461,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq( @@ -1534,7 +1491,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute expect(config_processor.stage_builds_attributes("test").size).to eq(1) expect(config_processor.stage_builds_attributes("test").first).to eq({ @@ -1570,7 +1527,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute builds = config_processor.stage_builds_attributes("test") expect(builds.size).to eq(1) @@ -1586,7 +1543,7 @@ module Gitlab } }) - config_processor = Gitlab::Ci::YamlProcessor.new(config) + config_processor = Gitlab::Ci::YamlProcessor.new(config).execute builds = config_processor.stage_builds_attributes("test") expect(builds.size).to eq(1) @@ -1594,17 +1551,19 @@ module Gitlab end end - it "gracefully handles errors in artifacts type" do - config = <<~YAML - test: - script: - - echo "Hello world" - artifacts: - - paths: - - test/ - YAML + context 'when artifacts syntax is wrong' do + let(:config) do + <<~YAML + test: + script: + - echo "Hello world" + artifacts: + - paths: + - test/ + YAML + end - expect { described_class.new(config) }.to raise_error(described_class::ValidationError) + it_behaves_like 'returns errors', 'jobs:test:artifacts config should be a hash' end it 'populates a build options with complete artifacts configuration' do @@ -1620,14 +1579,14 @@ module Gitlab - my/test/something YAML - attributes = Gitlab::Ci::YamlProcessor.new(config).build_attributes('test') + attributes = Gitlab::Ci::YamlProcessor.new(config).execute.build_attributes('test') expect(attributes.dig(*%i[options artifacts exclude])).to eq(%w[my/test/something]) end end describe "release" do - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } let(:config) do { stages: %w[build test release], @@ -1672,8 +1631,9 @@ module Gitlab } end - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - let(:builds) { processor.stage_builds_attributes('deploy') } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } + + let(:builds) { subject.stage_builds_attributes('deploy') } context 'when a production environment is specified' do let(:environment) { 'production' } @@ -1723,18 +1683,13 @@ module Gitlab context 'is not a string' do let(:environment) { 1 } - it 'raises error' do - expect { builds }.to raise_error( - 'jobs:deploy_to_production:environment config should be a hash or a string') - end + it_behaves_like 'returns errors', 'jobs:deploy_to_production:environment config should be a hash or a string' end context 'is not a valid string' do let(:environment) { 'production:staging' } - it 'raises error' do - expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}") - end + it_behaves_like 'returns errors', "jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}" end context 'when on_stop is specified' do @@ -1753,33 +1708,25 @@ module Gitlab context 'without matching job' do let(:close_review) { nil } - it 'raises error' do - expect { builds }.to raise_error('review job: on_stop job close_review is not defined') - end + it_behaves_like 'returns errors', 'review job: on_stop job close_review is not defined' end context 'with close job without environment' do let(:close_review) { { stage: 'deploy', script: 'test' } } - it 'raises error' do - expect { builds }.to raise_error('review job: on_stop job close_review does not have environment defined') - end + it_behaves_like 'returns errors', 'review job: on_stop job close_review does not have environment defined' end context 'with close job for different environment' do let(:close_review) { { stage: 'deploy', script: 'test', environment: 'production' } } - it 'raises error' do - expect { builds }.to raise_error('review job: on_stop job close_review have different environment name') - end + it_behaves_like 'returns errors', 'review job: on_stop job close_review have different environment name' end context 'with close job without stop action' do let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review' } } } - it 'raises error' do - expect { builds }.to raise_error('review job: on_stop job close_review needs to have action stop defined') - end + it_behaves_like 'returns errors', 'review job: on_stop job close_review needs to have action stop defined' end end end @@ -1794,8 +1741,9 @@ module Gitlab } end - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - let(:builds) { processor.stage_builds_attributes('deploy') } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } + + let(:builds) { subject.stage_builds_attributes('deploy') } context 'when no timeout was provided' do it 'does not include job_timeout' do @@ -1809,9 +1757,7 @@ module Gitlab config[:deploy_to_production][:timeout] = 'not-a-number' end - it 'raises an error for invalid number' do - expect { builds }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:deploy_to_production:timeout config should be a duration') - end + it_behaves_like 'returns errors', 'jobs:deploy_to_production:timeout config should be a duration' end context 'when some valid timeout was provided' do @@ -1837,36 +1783,36 @@ module Gitlab } end - subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } context 'no dependencies' do let(:dependencies) { } - it { expect { subject }.not_to raise_error } + it { is_expected.to be_valid } end context 'dependencies to builds' do let(:dependencies) { %w(build1 build2) } - it { expect { subject }.not_to raise_error } + it { is_expected.to be_valid } end context 'dependencies to builds defined as symbols' do let(:dependencies) { [:build1, :build2] } - it { expect { subject }.not_to raise_error } + it { is_expected.to be_valid } end context 'undefined dependency' do let(:dependencies) { ['undefined'] } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') } + it_behaves_like 'returns errors', 'test1 job: undefined dependency: undefined' end context 'dependencies to deploy' do let(:dependencies) { ['deploy'] } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') } + it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in prior stages' end context 'when a job depends on another job that references a not-yet defined stage' do @@ -1891,7 +1837,7 @@ module Gitlab } end - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /is not defined in prior stages/) } + it_behaves_like 'returns errors', /is not defined in prior stages/ end end @@ -1910,10 +1856,10 @@ module Gitlab } end - subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } context 'no needs' do - it { expect { subject }.not_to raise_error } + it { is_expected.to be_valid } end context 'needs two builds' do @@ -2053,20 +1999,20 @@ module Gitlab context 'undefined need' do let(:needs) { ['undefined'] } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: undefined need: undefined') } + it_behaves_like 'returns errors', 'test1 job: undefined need: undefined' end context 'needs to deploy' do let(:needs) { ['deploy'] } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: need deploy is not defined in prior stages') } + it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in prior stages' end context 'needs and dependencies that are mismatching' do let(:needs) { %w(build1) } let(:dependencies) { %w(build2) } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies the build2 should be part of needs') } + it_behaves_like 'returns errors', 'jobs:test1 dependencies the build2 should be part of needs' end context 'needs with a Hash type and dependencies with a string type that are mismatching' do @@ -2079,33 +2025,33 @@ module Gitlab let(:dependencies) { %w(build3) } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies the build3 should be part of needs') } + it_behaves_like 'returns errors', 'jobs:test1 dependencies the build3 should be part of needs' end context 'needs with an array type and dependency with a string type' do let(:needs) { %w(build1) } let(:dependencies) { 'deploy' } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies should be an array of strings') } + it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings' end context 'needs with a string type and dependency with an array type' do let(:needs) { 'build1' } let(:dependencies) { %w(deploy) } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1:needs config can only be a hash or an array') } + it_behaves_like 'returns errors', 'jobs:test1:needs config can only be a hash or an array' end context 'needs with a Hash type and dependency with a string type' do let(:needs) { { job: 'build1' } } let(:dependencies) { 'deploy' } - it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies should be an array of strings') } + it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings' end end context 'with when/rules conflict' do - subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } let(:config) do { @@ -2121,9 +2067,7 @@ module Gitlab } end - it 'raises no exceptions' do - expect { subject }.not_to raise_error - end + it { is_expected.to be_valid } it 'returns all jobs regardless of their inclusion' do expect(subject.builds.count).to eq(config.keys.count) @@ -2141,9 +2085,7 @@ module Gitlab } end - it 'raises a ValidationError' do - expect { subject }.to raise_error(YamlProcessor::ValidationError, /may not be used with `rules`: when/) - end + it_behaves_like 'returns errors', /may not be used with `rules`: when/ end context 'used with job-level when:delayed' do @@ -2159,14 +2101,12 @@ module Gitlab } end - it 'raises a ValidationError' do - expect { subject }.to raise_error(YamlProcessor::ValidationError, /may not be used with `rules`: when, start_in/) - end + it_behaves_like 'returns errors', /may not be used with `rules`: when, start_in/ end end describe "Hidden jobs" do - let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } + let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config).execute } subject { config_processor.stage_builds_attributes("test") } @@ -2213,7 +2153,7 @@ module Gitlab end describe "YAML Alias/Anchor" do - let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } + let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config).execute } subject { config_processor.stage_builds_attributes("build") } @@ -2310,7 +2250,7 @@ module Gitlab }) end - it { expect { subject }.not_to raise_error } + it { is_expected.to be_valid } end context 'when job is not specified specified while artifact is' do @@ -2323,11 +2263,7 @@ module Gitlab }) end - it do - expect { subject }.to raise_error( - described_class::ValidationError, - /include config must specify the job where to fetch the artifact from/) - end + it_behaves_like 'returns errors', /include config must specify the job where to fetch the artifact from/ end context 'when include is a string' do @@ -2343,376 +2279,323 @@ module Gitlab }) end - it { expect { subject }.not_to raise_error } + it { is_expected.to be_valid } end end describe "Error handling" do - it "fails to parse YAML" do - expect do - Gitlab::Ci::YamlProcessor.new("invalid: yaml: test") - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + subject { described_class.new(config).execute } + + context 'when YAML syntax is invalid' do + let(:config) { 'invalid: yaml: test' } + + it_behaves_like 'returns errors', /mapping values are not allowed/ end - it "indicates that object is invalid" do - expect do - Gitlab::Ci::YamlProcessor.new("invalid_yaml") - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + context 'when object is invalid' do + let(:config) { 'invalid_yaml' } + + it_behaves_like 'returns errors', /Invalid configuration format/ end - it "returns errors if tags parameter is invalid" do - config = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:tags config should be an array of strings") + context 'returns errors if tags parameter is invalid' do + let(:config) { YAML.dump({ rspec: { script: "test", tags: "mysql" } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:tags config should be an array of strings' end - it "returns errors if before_script parameter is invalid" do - config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "before_script config should be an array containing strings and arrays of strings") + context 'returns errors if before_script parameter is invalid' do + let(:config) { YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'before_script config should be an array containing strings and arrays of strings' end - it "returns errors if job before_script parameter is not an array of strings" do - config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array containing strings and arrays of strings") + context 'returns errors if job before_script parameter is not an array of strings' do + let(:config) { YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings' end - it "returns errors if job before_script parameter is multi-level nested array of strings" do - config = YAML.dump({ rspec: { script: "test", before_script: [["ls", ["pwd"]], "test"] } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array containing strings and arrays of strings") + context 'returns errors if job before_script parameter is multi-level nested array of strings' do + let(:config) { YAML.dump({ rspec: { script: "test", before_script: [["ls", ["pwd"]], "test"] } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings' end - it "returns errors if after_script parameter is invalid" do - config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "after_script config should be an array containing strings and arrays of strings") + context 'returns errors if after_script parameter is invalid' do + let(:config) { YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'after_script config should be an array containing strings and arrays of strings' end - it "returns errors if job after_script parameter is not an array of strings" do - config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array containing strings and arrays of strings") + context 'returns errors if job after_script parameter is not an array of strings' do + let(:config) { YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings' end - it "returns errors if job after_script parameter is multi-level nested array of strings" do - config = YAML.dump({ rspec: { script: "test", after_script: [["ls", ["pwd"]], "test"] } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array containing strings and arrays of strings") + context 'returns errors if job after_script parameter is multi-level nested array of strings' do + let(:config) { YAML.dump({ rspec: { script: "test", after_script: [["ls", ["pwd"]], "test"] } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings' end - it "returns errors if image parameter is invalid" do - config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "image config should be a hash or a string") + context 'returns errors if image parameter is invalid' do + let(:config) { YAML.dump({ image: ["test"], rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'image config should be a hash or a string' end - it "returns errors if job name is blank" do - config = YAML.dump({ '' => { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:job name can't be blank") + context 'returns errors if job name is blank' do + let(:config) { YAML.dump({ '' => { script: "test" } }) } + + it_behaves_like 'returns errors', "jobs:job name can't be blank" end - it "returns errors if job name is non-string" do - config = YAML.dump({ 10 => { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:10 name should be a symbol") + context 'returns errors if job name is non-string' do + let(:config) { YAML.dump({ 10 => { script: "test" } }) } + + it_behaves_like 'returns errors', 'jobs:10 name should be a symbol' end - it "returns errors if job image parameter is invalid" do - config = YAML.dump({ rspec: { script: "test", image: ["test"] } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string") + context 'returns errors if job image parameter is invalid' do + let(:config) { YAML.dump({ rspec: { script: "test", image: ["test"] } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:image config should be a hash or a string' end - it "returns errors if services parameter is not an array" do - config = YAML.dump({ services: "test", rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services config should be a array") + context 'returns errors if services parameter is not an array' do + let(:config) { YAML.dump({ services: "test", rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'services config should be a array' end - it "returns errors if services parameter is not an array of strings" do - config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services:service config should be a hash or a string") + context 'returns errors if services parameter is not an array of strings' do + let(:config) { YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'services:service config should be a hash or a string' end - it "returns errors if job services parameter is not an array" do - config = YAML.dump({ rspec: { script: "test", services: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services config should be a array") + context 'returns errors if job services parameter is not an array' do + let(:config) { YAML.dump({ rspec: { script: "test", services: "test" } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:services config should be a array' end - it "returns errors if job services parameter is not an array of strings" do - config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services:service config should be a hash or a string") + context 'returns errors if job services parameter is not an array of strings' do + let(:config) { YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:services:service config should be a hash or a string' end - it "returns error if job configuration is invalid" do - config = YAML.dump({ extra: "bundle update" }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "root config contains unknown keys: extra") + context 'returns error if job configuration is invalid' do + let(:config) { YAML.dump({ extra: "bundle update" }) } + + it_behaves_like 'returns errors', 'jobs extra config should implement a script: or a trigger: keyword' end - it "returns errors if services configuration is not correct" do - config = YAML.dump({ extra: { script: 'rspec', services: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:extra:services config should be a array") + context 'returns errors if services configuration is not correct' do + let(:config) { YAML.dump({ extra: { script: 'rspec', services: "test" } }) } + + it_behaves_like 'returns errors', 'jobs:extra:services config should be a array' end - it "returns errors if there are no jobs defined" do - config = YAML.dump({ before_script: ["bundle update"] }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job") + context 'returns errors if there are no jobs defined' do + let(:config) { YAML.dump({ before_script: ["bundle update"] }) } + + it_behaves_like 'returns errors', 'jobs config should contain at least one visible job' end - it "returns errors if the job script is not defined" do - config = YAML.dump({ rspec: { before_script: "test" } }) + context 'returns errors if the job script is not defined' do + let(:config) { YAML.dump({ rspec: { before_script: "test" } }) } - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec script can't be blank") + it_behaves_like 'returns errors', 'jobs rspec config should implement a script: or a trigger: keyword' end - it "returns errors if there are no visible jobs defined" do - config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job") + context 'returns errors if there are no visible jobs defined' do + let(:config) { YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } }) } + + it_behaves_like 'returns errors', 'jobs config should contain at least one visible job' end - it "returns errors if job allow_failure parameter is not an boolean" do - config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value") + context 'returns errors if job allow_failure parameter is not an boolean' do + let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) } + + it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a boolean value' end - it "returns errors if job stage is not a string" do - config = YAML.dump({ rspec: { script: "test", type: 1 } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:type config should be a string") + context 'returns errors if job stage is not a string' do + let(:config) { YAML.dump({ rspec: { script: "test", type: 1 } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:type config should be a string' end - it "returns errors if job stage is not a pre-defined stage" do - config = YAML.dump({ rspec: { script: "test", type: "acceptance" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post") + context 'returns errors if job stage is not a pre-defined stage' do + let(:config) { YAML.dump({ rspec: { script: "test", type: "acceptance" } }) } + + it_behaves_like 'returns errors', 'rspec job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post' end - it "returns errors if job stage is not a defined stage" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: chosen stage does not exist; available stages are .pre, build, test, .post") + context 'returns errors if job stage is not a defined stage' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } }) } + + it_behaves_like 'returns errors', 'rspec job: chosen stage does not exist; available stages are .pre, build, test, .post' end - it "returns errors if stages is not an array" do - config = YAML.dump({ stages: "test", rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "stages config should be an array of strings") + context 'returns errors if stages is not an array' do + let(:config) { YAML.dump({ stages: "test", rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'stages config should be an array of strings' end - it "returns errors if stages is not an array of strings" do - config = YAML.dump({ stages: [true, "test"], rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "stages config should be an array of strings") + context 'returns errors if stages is not an array of strings' do + let(:config) { YAML.dump({ stages: [true, "test"], rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'stages config should be an array of strings' end - it "returns errors if variables is not a map" do - config = YAML.dump({ variables: "test", rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "variables config should be a hash of key value pairs") + context 'returns errors if variables is not a map' do + let(:config) { YAML.dump({ variables: "test", rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs' end - it "returns errors if variables is not a map of key-value strings" do - config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "variables config should be a hash of key value pairs") + context 'returns errors if variables is not a map of key-value strings' do + let(:config) { YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs' end - it "returns errors if job when is not on_success, on_failure or always" do - config = YAML.dump({ rspec: { script: "test", when: 1 } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be one of: #{Gitlab::Ci::Config::Entry::Job::ALLOWED_WHEN.join(', ')}") + context 'returns errors if job when is not on_success, on_failure or always' do + let(:config) { YAML.dump({ rspec: { script: "test", when: 1 } }) } + + it_behaves_like 'returns errors', "jobs:rspec when should be one of: #{Gitlab::Ci::Config::Entry::Job::ALLOWED_WHEN.join(', ')}" end - it "returns errors if job artifacts:name is not an a string" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string") + context 'returns errors if job artifacts:name is not an a string' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:artifacts name should be a string' end - it "returns errors if job artifacts:when is not an a predefined value" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always") + context 'returns errors if job artifacts:when is not an a predefined value' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:artifacts when should be on_success, on_failure or always' end - it "returns errors if job artifacts:expire_in is not an a string" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration") + context 'returns errors if job artifacts:expire_in is not an a string' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:artifacts expire in should be a duration' end - it "returns errors if job artifacts:expire_in is not an a valid duration" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration") + context 'returns errors if job artifacts:expire_in is not an a valid duration' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:artifacts expire in should be a duration' end - it "returns errors if job artifacts:untracked is not an array of strings" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value") + context 'returns errors if job artifacts:untracked is not an array of strings' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:artifacts untracked should be a boolean value' end - it "returns errors if job artifacts:paths is not an array of strings" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings") + context 'returns errors if job artifacts:paths is not an array of strings' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:artifacts paths should be an array of strings' end - it "returns errors if cache:untracked is not an array of strings" do - config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:untracked config should be a boolean value") + context 'returns errors if cache:untracked is not an array of strings' do + let(:config) { YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'cache:untracked config should be a boolean value' end - it "returns errors if cache:paths is not an array of strings" do - config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:paths config should be an array of strings") + context 'returns errors if cache:paths is not an array of strings' do + let(:config) { YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', 'cache:paths config should be an array of strings' end - it "returns errors if cache:key is not a string" do - config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:key should be a hash, a string or a symbol") + context 'returns errors if cache:key is not a string' do + let(:config) { YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } }) } + + it_behaves_like 'returns errors', "cache:key should be a hash, a string or a symbol" end - it "returns errors if job cache:key is not an a string" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: 1 } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:key should be a hash, a string or a symbol") + context 'returns errors if job cache:key is not an a string' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: 1 } } }) } + + it_behaves_like 'returns errors', "jobs:rspec:cache:key should be a hash, a string or a symbol" end - it 'returns errors if job cache:key:files is not an array of strings' do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { files: [1] } } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key:files config should be an array of strings') + context 'returns errors if job cache:key:files is not an array of strings' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { files: [1] } } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:cache:key:files config should be an array of strings' end - it 'returns errors if job cache:key:files is an empty array' do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { files: [] } } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key:files config requires at least 1 item') + context 'returns errors if job cache:key:files is an empty array' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { files: [] } } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:cache:key:files config requires at least 1 item' end - it 'returns errors if job defines only cache:key:prefix' do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key config missing required keys: files') + context 'returns errors if job defines only cache:key:prefix' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:cache:key config missing required keys: files' end - it 'returns errors if job cache:key:prefix is not an a string' do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key:prefix config should be a string or symbol') + context 'returns errors if job cache:key:prefix is not an a string' do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) } + + it_behaves_like 'returns errors', 'jobs:rspec:cache:key:prefix config should be a string or symbol' end - it "returns errors if job cache:untracked is not an array of strings" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value") + context "returns errors if job cache:untracked is not an array of strings" do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } }) } + + it_behaves_like 'returns errors', "jobs:rspec:cache:untracked config should be a boolean value" end - it "returns errors if job cache:paths is not an array of strings" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { paths: "string" } } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings") + context "returns errors if job cache:paths is not an array of strings" do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { paths: "string" } } }) } + + it_behaves_like 'returns errors', "jobs:rspec:cache:paths config should be an array of strings" end - it "returns errors if job dependencies is not an array of strings" do - config = YAML.dump({ types: %w(build test), rspec: { script: "test", dependencies: "string" } }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings") + context "returns errors if job dependencies is not an array of strings" do + let(:config) { YAML.dump({ types: %w(build test), rspec: { script: "test", dependencies: "string" } }) } + + it_behaves_like 'returns errors', "jobs:rspec dependencies should be an array of strings" end - it 'returns errors if pipeline variables expression policy is invalid' do - config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } }) + context 'returns errors if pipeline variables expression policy is invalid' do + let(:config) { YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } }) } - expect { Gitlab::Ci::YamlProcessor.new(config) } - .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only variables invalid expression syntax') + it_behaves_like 'returns errors', 'jobs:rspec:only variables invalid expression syntax' end - it 'returns errors if pipeline changes policy is invalid' do - config = YAML.dump({ rspec: { script: 'test', only: { changes: [1] } } }) + context 'returns errors if pipeline changes policy is invalid' do + let(:config) { YAML.dump({ rspec: { script: 'test', only: { changes: [1] } } }) } - expect { Gitlab::Ci::YamlProcessor.new(config) } - .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only changes should be an array of strings') + it_behaves_like 'returns errors', 'jobs:rspec:only changes should be an array of strings' end - it 'returns errors if extended hash configuration is invalid' do - config = YAML.dump({ rspec: { extends: 'something', script: 'test' } }) + context 'returns errors if extended hash configuration is invalid' do + let(:config) { YAML.dump({ rspec: { extends: 'something', script: 'test' } }) } - expect { Gitlab::Ci::YamlProcessor.new(config) } - .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'rspec: unknown keys in `extends` (something)') + it_behaves_like 'returns errors', 'rspec: unknown keys in `extends` (something)' end - it 'returns errors if parallel is invalid' do - config = YAML.dump({ rspec: { parallel: 'test', script: 'test' } }) + context 'returns errors if parallel is invalid' do + let(:config) { YAML.dump({ rspec: { parallel: 'test', script: 'test' } }) } - expect { Gitlab::Ci::YamlProcessor.new(config) } - .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:parallel should be an integer or a hash') + it_behaves_like 'returns errors', 'jobs:rspec:parallel should be an integer or a hash' end end @@ -2750,8 +2633,8 @@ module Gitlab end end - describe '.new_with_validation_errors' do - subject { Gitlab::Ci::YamlProcessor.new_with_validation_errors(content) } + describe '#execute' do + subject { Gitlab::Ci::YamlProcessor.new(content).execute } context 'when the YAML could not be parsed' do let(:content) { YAML.dump('invalid: yaml: test') } @@ -2759,7 +2642,6 @@ module Gitlab it 'returns errors and empty configuration' do expect(subject.valid?).to eq(false) expect(subject.errors).to eq(['Invalid configuration format']) - expect(subject.config).to be_blank end end @@ -2769,7 +2651,6 @@ module Gitlab it 'returns errors and empty configuration' do expect(subject.valid?).to eq(false) expect(subject.errors).to eq(['jobs:rspec:tags config should be an array of strings']) - expect(subject.config).to be_blank end end @@ -2781,7 +2662,6 @@ module Gitlab expect(subject.errors).to contain_exactly( 'jobs:rspec config contains unknown keys: bad_tags', 'jobs:rspec rules should be an array of hashes') - expect(subject.config).to be_blank end end @@ -2791,7 +2671,6 @@ module Gitlab it 'returns errors and empty configuration' do expect(subject.valid?).to eq(false) expect(subject.errors).to eq(['Please provide content of .gitlab-ci.yml']) - expect(subject.config).to be_blank end end @@ -2801,7 +2680,6 @@ module Gitlab it 'returns errors and empty configuration' do expect(subject.valid?).to eq(false) expect(subject.errors).to eq(['Unknown alias: bad_alias']) - expect(subject.config).to be_blank end end @@ -2811,7 +2689,7 @@ module Gitlab it 'returns errors and empty configuration' do expect(subject.valid?).to eq(true) expect(subject.errors).to be_empty - expect(subject.config).to be_present + expect(subject.builds).to be_present end end end |