diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
23 files changed, 968 insertions, 655 deletions
diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb index e982f0eb015..83a37655ea9 100644 --- a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb +++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb @@ -18,6 +18,17 @@ RSpec.describe Gitlab::Ci::ArtifactFileReader do expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') end + context 'when FF ci_new_artifact_file_reader is disabled' do + before do + stub_feature_flags(ci_new_artifact_file_reader: false) + end + + it 'returns the content at the path' do + is_expected.to be_present + expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') + end + end + context 'when path does not exist' do let(:path) { 'file/does/not/exist.txt' } let(:expected_error) do diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index ca02eaee0a0..ab760b107f8 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -74,16 +74,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it { is_expected.to be_falsey } end - context 'when config does not contain script' do - let(:name) { :build } - - let(:config) do - { before_script: "cd ${PROJ_DIR} " } - end - - it { is_expected.to be_truthy } - end - context 'when using the default job without script' do let(:name) { :default } let(:config) do @@ -104,14 +94,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it { is_expected.to be_truthy } end - - context 'there are no shared keys between jobs and bridges' do - subject(:shared_values) do - described_class::ALLOWED_KEYS & Gitlab::Ci::Config::Entry::Bridge::ALLOWED_KEYS - end - - it { is_expected.to be_empty } - end end describe 'validations' do diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 8561bd330b7..ac6b589ec6b 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -68,7 +68,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do let(:config) { { rspec: nil } } it 'reports error' do - expect(entry.errors).to include "jobs config should contain valid jobs" + expect(entry.errors).to include 'jobs rspec config should implement a script: or a trigger: keyword' end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 140b3c4f55b..252bda6461d 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -344,9 +344,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do end describe '#errors' do - it 'reports errors about missing script' do + it 'reports errors about missing script or trigger' do expect(root.errors) - .to include "root config contains unknown keys: rspec" + .to include 'jobs rspec config should implement a script: or a trigger: keyword' end end end diff --git a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb index bab604c4504..fbf86927bd9 100644 --- a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb @@ -43,7 +43,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do expect(subject.map(&:attributes)).to match_array( [ { - name: 'test 1/4', + name: 'test: [aws, app1]', instance: 1, parallel: { total: 4 }, variables: { @@ -52,7 +52,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do } }, { - name: 'test 2/4', + name: 'test: [aws, app2]', instance: 2, parallel: { total: 4 }, variables: { @@ -61,7 +61,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do } }, { - name: 'test 3/4', + name: 'test: [ovh, app]', instance: 3, parallel: { total: 4 }, variables: { @@ -70,7 +70,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do } }, { - name: 'test 4/4', + name: 'test: [gcp, app]', instance: 4, parallel: { total: 4 }, variables: { @@ -84,18 +84,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do it 'has parallelized name' do expect(subject.map(&:name)).to match_array( - ['test 1/4', 'test 2/4', 'test 3/4', 'test 4/4'] - ) - end - - it 'has details' do - expect(subject.map(&:name_with_details)).to match_array( - [ - 'test (PROVIDER=aws; STACK=app1)', - 'test (PROVIDER=aws; STACK=app2)', - 'test (PROVIDER=gcp; STACK=app)', - 'test (PROVIDER=ovh; STACK=app)' - ] + ['test: [aws, app1]', 'test: [aws, app2]', 'test: [gcp, app]', 'test: [ovh, app]'] ) end end diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb index 949af8cdc4c..4c19657413c 100644 --- a/spec/lib/gitlab/ci/config/normalizer_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb @@ -178,8 +178,8 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do { matrix: [ { - VAR_1: [1], - VAR_2: [2, 3] + VAR_1: ['A'], + VAR_2: %w[B C] } ] } @@ -187,8 +187,8 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do let(:expanded_job_names) do [ - 'rspec 1/2', - 'rspec 2/2' + 'rspec: [A, B]', + 'rspec: [A, C]' ] end @@ -196,21 +196,17 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do is_expected.not_to include(job_name) end - it 'has parallelized jobs' do - is_expected.to include(*expanded_job_names.map(&:to_sym)) - end - it 'sets job instance in options' do expect(subject.values).to all(include(:instance)) end it 'sets job variables', :aggregate_failures do expect(subject.values[0]).to match( - a_hash_including(variables: { VAR_1: 1, VAR_2: 2, USER_VARIABLE: 'user value' }) + a_hash_including(variables: { VAR_1: 'A', VAR_2: 'B', USER_VARIABLE: 'user value' }) ) expect(subject.values[1]).to match( - a_hash_including(variables: { VAR_1: 1, VAR_2: 3, USER_VARIABLE: 'user value' }) + a_hash_including(variables: { VAR_1: 'A', VAR_2: 'C', USER_VARIABLE: 'user value' }) ) end @@ -226,6 +222,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do expect(configs).to all(match(a_hash_including(original_config))) end + it 'has parallelized jobs' do + is_expected.to include(*expanded_job_names.map(&:to_sym)) + end + it_behaves_like 'parallel dependencies' it_behaves_like 'parallel needs' end @@ -238,5 +238,11 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do is_expected.to match(config) end end + + context 'when jobs config is nil' do + let(:config) { nil } + + it { is_expected.to eq({}) } + end end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 18be9558829..41a45fe4ab7 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -312,7 +312,7 @@ RSpec.describe Gitlab::Ci::Config do HEREDOC end - it 'raises error YamlProcessor validationError' do + it 'raises ConfigError' do expect { config }.to raise_error( described_class::ConfigError, "Included file `invalid` does not have YAML extension!" @@ -329,7 +329,7 @@ RSpec.describe Gitlab::Ci::Config do HEREDOC end - it 'raises error YamlProcessor validationError' do + it 'raises ConfigError' do expect { config }.to raise_error( described_class::ConfigError, 'Include `{"remote":"http://url","local":"/local/file.yml"}` needs to match exactly one accessor!' diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb index a15f3310dab..9b133efad9c 100644 --- a/spec/lib/gitlab/ci/jwt_spec.rb +++ b/spec/lib/gitlab/ci/jwt_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::Ci::Jwt do subject(:payload) { described_class.new(build, ttl: 30).payload } it 'has correct values for the standard JWT attributes' do - Timecop.freeze do + freeze_time do now = Time.now.to_i aggregate_failures do diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb new file mode 100644 index 00000000000..077c0fd3162 --- /dev/null +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Lint do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + let(:lint) { described_class.new(project: project, current_user: user) } + + describe '#validate' do + subject { lint.validate(content, dry_run: dry_run) } + + shared_examples 'content is valid' do + let(:content) do + <<~YAML + build: + stage: build + before_script: + - before_build + script: echo + environment: staging + when: manual + rspec: + stage: test + script: rspec + after_script: + - after_rspec + tags: [docker] + YAML + end + + it 'returns a valid result', :aggregate_failures do + expect(subject).to be_valid + + expect(subject.errors).to be_empty + expect(subject.warnings).to be_empty + expect(subject.jobs).to be_present + + build_job = subject.jobs.first + expect(build_job[:name]).to eq('build') + expect(build_job[:stage]).to eq('build') + expect(build_job[:before_script]).to eq(['before_build']) + expect(build_job[:script]).to eq(['echo']) + expect(build_job.fetch(:after_script)).to eq([]) + expect(build_job[:tag_list]).to eq([]) + expect(build_job[:environment]).to eq('staging') + expect(build_job[:when]).to eq('manual') + expect(build_job[:allow_failure]).to eq(true) + + rspec_job = subject.jobs.last + expect(rspec_job[:name]).to eq('rspec') + expect(rspec_job[:stage]).to eq('test') + expect(rspec_job.fetch(:before_script)).to eq([]) + expect(rspec_job[:script]).to eq(['rspec']) + expect(rspec_job[:after_script]).to eq(['after_rspec']) + expect(rspec_job[:tag_list]).to eq(['docker']) + expect(rspec_job.fetch(:environment)).to be_nil + expect(rspec_job[:when]).to eq('on_success') + expect(rspec_job[:allow_failure]).to eq(false) + end + end + + shared_examples 'content with errors and warnings' do + context 'when content has errors' do + let(:content) do + <<~YAML + build: + invalid: syntax + YAML + end + + it 'returns a result with errors' do + expect(subject).not_to be_valid + expect(subject.errors).to include(/jobs build config should implement a script: or a trigger: keyword/) + end + end + + context 'when content has warnings' do + let(:content) do + <<~YAML + rspec: + script: rspec + rules: + - when: always + YAML + end + + it 'returns a result with warnings' do + expect(subject).to be_valid + expect(subject.warnings).to include(/rspec may allow multiple pipelines to run/) + end + end + + context 'when content has more warnings than max limit' do + # content will result in 2 warnings + let(:content) do + <<~YAML + rspec: + script: rspec + rules: + - when: always + rspec2: + script: rspec + rules: + - when: always + YAML + end + + before do + stub_const('Gitlab::Ci::Warnings::MAX_LIMIT', 1) + end + + it 'returns a result with warnings' do + expect(subject).to be_valid + expect(subject.warnings.size).to eq(1) + end + end + + context 'when content has errors and warnings' do + let(:content) do + <<~YAML + rspec: + script: rspec + rules: + - when: always + karma: + script: karma + unknown: key + YAML + end + + it 'returns a result with errors and warnings' do + expect(subject).not_to be_valid + expect(subject.errors).to include(/karma config contains unknown keys/) + expect(subject.warnings).to include(/rspec may allow multiple pipelines to run/) + end + end + end + + shared_context 'advanced validations' do + let(:content) do + <<~YAML + build: + stage: build + script: echo + rules: + - if: '$CI_MERGE_REQUEST_ID' + test: + stage: test + script: echo + needs: [build] + YAML + end + end + + context 'when user has permissions to write the ref' do + before do + project.add_developer(user) + end + + context 'when using default static mode' do + let(:dry_run) { false } + + it_behaves_like 'content with errors and warnings' + + it_behaves_like 'content is valid' do + it 'includes extra attributes' do + subject.jobs.each do |job| + expect(job[:only]).to eq(refs: %w[branches tags]) + expect(job.fetch(:except)).to be_nil + end + end + end + + include_context 'advanced validations' do + it 'does not catch advanced logical errors' do + expect(subject).to be_valid + expect(subject.errors).to be_empty + end + end + + it 'uses YamlProcessor' do + expect(Gitlab::Ci::YamlProcessor) + .to receive(:new) + .and_call_original + + subject + end + end + + context 'when using dry run mode' do + let(:dry_run) { true } + + it_behaves_like 'content with errors and warnings' + + it_behaves_like 'content is valid' do + it 'does not include extra attributes' do + subject.jobs.each do |job| + expect(job.key?(:only)).to be_falsey + expect(job.key?(:except)).to be_falsey + end + end + end + + include_context 'advanced validations' do + it 'runs advanced logical validations' do + expect(subject).not_to be_valid + expect(subject.errors).to eq(["test: needs 'build'"]) + end + end + + it 'uses Ci::CreatePipelineService' do + expect(::Ci::CreatePipelineService) + .to receive(:new) + .and_call_original + + subject + end + end + end + + context 'when user does not have permissions to write the ref' do + before do + project.add_reporter(user) + end + + context 'when using default static mode' do + let(:dry_run) { false } + + it_behaves_like 'content is valid' + end + + context 'when using dry run mode' do + let(:dry_run) { true } + + let(:content) do + <<~YAML + job: + script: echo + YAML + end + + it 'does not allow validation' do + expect(subject).not_to be_valid + expect(subject.errors).to include('Insufficient permissions to create a new pipeline') + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb index 8b9de16ce5f..11e3f32c7ce 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb @@ -11,7 +11,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do let(:command) do double(:command, - config_processor: double(:processor, + yaml_processor_result: double(:processor, jobs: { echo: double(:job_echo), rspec: double(:job_rspec) }), project: project, chat_data: { command: 'echo' }) @@ -25,7 +25,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do subject - expect(command.config_processor.jobs.keys).to eq([:echo]) + expect(command.yaml_processor_result.jobs.keys).to eq([:echo]) end it 'does not remove any jobs for non chat-pipelines' do @@ -33,7 +33,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do subject - expect(command.config_processor.jobs.keys).to eq([:echo, :rspec]) + expect(command.yaml_processor_result.jobs.keys).to eq([:echo, :rspec]) end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index de580d2e148..e55281f9705 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -31,20 +31,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do CI_YAML end - let(:yaml_processor) do + let(:yaml_processor_result) do ::Gitlab::Ci::YamlProcessor.new( ci_yaml, { project: project, sha: pipeline.sha, user: user } - ) + ).execute end let(:save_incompleted) { true } let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, current_user: user, config_processor: yaml_processor, save_incompleted: save_incompleted + project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted ) end @@ -128,7 +128,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end describe '#validation_service_payload' do - subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.config_processor.stages_attributes) } + subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.yaml_processor_result.stages_attributes) } it 'respects the defined schema' do expect(validation_service_payload).to match_schema('/external_validation') diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb index 6e242faa885..fc5725a4d17 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb @@ -90,24 +90,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexer do end with_them do - context 'when ci_if_parenthesis_enabled is enabled' do - before do - stub_feature_flags(ci_if_parenthesis_enabled: true) - end - - it { is_expected.to eq(tokens) } - end - - context 'when ci_if_parenthesis_enabled is disabled' do - before do - stub_feature_flags(ci_if_parenthesis_enabled: false) - end - - it do - expect { subject } - .to raise_error described_class::SyntaxError - end - end + it { is_expected.to eq(tokens) } end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb index 3394a75ac0a..a02c247925e 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb @@ -3,10 +3,6 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do - before do - stub_feature_flags(ci_if_parenthesis_enabled: true) - end - describe '#tree' do context 'validates simple operators' do using RSpec::Parameterized::TableSyntax @@ -31,36 +27,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do context 'when combining && and OR operators' do subject { described_class.seed('$VAR1 == "a" || $VAR2 == "b" && $VAR3 == "c" || $VAR4 == "d" && $VAR5 == "e"').tree } - context 'when parenthesis engine is enabled' do - before do - stub_feature_flags(ci_if_parenthesis_enabled: true) - end - - it 'returns operations in a correct order' do - expect(subject.inspect) - .to eq('or(or(equals($VAR1, "a"), and(equals($VAR2, "b"), equals($VAR3, "c"))), and(equals($VAR4, "d"), equals($VAR5, "e")))') - end - end - - context 'when parenthesis engine is disabled (legacy)' do - before do - stub_feature_flags(ci_if_parenthesis_enabled: false) - end - - it 'returns operations in a invalid order' do - expect(subject.inspect) - .to eq('or(equals($VAR1, "a"), and(equals($VAR2, "b"), or(equals($VAR3, "c"), and(equals($VAR4, "d"), equals($VAR5, "e")))))') - end + it 'returns operations in a correct order' do + expect(subject.inspect) + .to eq('or(or(equals($VAR1, "a"), and(equals($VAR2, "b"), equals($VAR3, "c"))), and(equals($VAR4, "d"), equals($VAR5, "e")))') end end context 'when using parenthesis' do subject { described_class.seed('(($VAR1 == "a" || $VAR2 == "b") && $VAR3 == "c" || $VAR4 == "d") && $VAR5 == "e"').tree } - before do - stub_feature_flags(ci_if_parenthesis_enabled: true) - end - it 'returns operations in a correct order' do expect(subject.inspect) .to eq('and(or(and(or(equals($VAR1, "a"), equals($VAR2, "b")), equals($VAR3, "c")), equals($VAR4, "d")), equals($VAR5, "e"))') @@ -96,38 +71,21 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do end context 'when parenthesis are unmatched' do - context 'when parenthesis engine is enabled' do - before do - stub_feature_flags(ci_if_parenthesis_enabled: true) - end - - where(:expression) do - [ - '$VAR == (', - '$VAR2 == ("aa"', - '$VAR2 == ("aa"))', - '$VAR2 == "aa")', - '(($VAR2 == "aa")', - '($VAR2 == "aa"))' - ] - end - - with_them do - it 'raises a ParseError' do - expect { described_class.seed(expression).tree } - .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError - end - end + where(:expression) do + [ + '$VAR == (', + '$VAR2 == ("aa"', + '$VAR2 == ("aa"))', + '$VAR2 == "aa")', + '(($VAR2 == "aa")', + '($VAR2 == "aa"))' + ] end - context 'when parenthesis engine is disabled' do - before do - stub_feature_flags(ci_if_parenthesis_enabled: false) - end - - it 'raises an SyntaxError' do - expect { described_class.seed('$VAR == (').tree } - .to raise_error Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError + with_them do + it 'raises a ParseError' do + expect { described_class.seed(expression).tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError end end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 733ab30132d..34df0e86a18 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -931,47 +931,30 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'when using 101 needs' do let(:needs_count) { 101 } - context 'when ci_plan_needs_size_limit is disabled' do + 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 + + context 'when ci_needs_size_limit is set to 100' do before do - stub_feature_flags(ci_plan_needs_size_limit: false) + project.actual_limits.update!(ci_needs_size_limit: 100) end it "returns an error" do expect(subject.errors).to contain_exactly( - "rspec: one job can only need 10 others, but you have listed 101. See needs keyword documentation for more details") + "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details") end end - context 'when ci_plan_needs_size_limit is enabled' do + context 'when ci_needs_size_limit is set to 0' do before do - stub_feature_flags(ci_plan_needs_size_limit: true) + project.actual_limits.update!(ci_needs_size_limit: 0) 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 - - context 'when ci_needs_size_limit is set to 100' do - before do - project.actual_limits.update!(ci_needs_size_limit: 100) - 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") - end - 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 "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 + "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details") end end end diff --git a/spec/lib/gitlab/ci/pipeline_object_hierarchy_spec.rb b/spec/lib/gitlab/ci/pipeline_object_hierarchy_spec.rb new file mode 100644 index 00000000000..89602fe79d1 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline_object_hierarchy_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::PipelineObjectHierarchy do + include Ci::SourcePipelineHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:ancestor) { create(:ci_pipeline, project: project) } + let_it_be(:parent) { create(:ci_pipeline, project: project) } + let_it_be(:child) { create(:ci_pipeline, project: project) } + let_it_be(:cousin_parent) { create(:ci_pipeline, project: project) } + let_it_be(:cousin) { create(:ci_pipeline, project: project) } + let_it_be(:triggered_pipeline) { create(:ci_pipeline) } + + before_all do + create_source_pipeline(ancestor, parent) + create_source_pipeline(ancestor, cousin_parent) + create_source_pipeline(parent, child) + create_source_pipeline(cousin_parent, cousin) + create_source_pipeline(child, triggered_pipeline) + end + + describe '#base_and_ancestors' do + it 'includes the base and its ancestors' do + relation = described_class.new(::Ci::Pipeline.where(id: parent.id), + options: { same_project: true }).base_and_ancestors + + expect(relation).to contain_exactly(ancestor, parent) + end + + it 'can find ancestors upto a certain level' do + relation = described_class.new(::Ci::Pipeline.where(id: child.id), + options: { same_project: true }).base_and_ancestors(upto: ancestor.id) + + expect(relation).to contain_exactly(parent, child) + end + + describe 'hierarchy_order option' do + let(:relation) do + described_class.new(::Ci::Pipeline.where(id: child.id), + options: { same_project: true }).base_and_ancestors(hierarchy_order: hierarchy_order) + end + + context ':asc' do + let(:hierarchy_order) { :asc } + + it 'orders by child to ancestor' do + expect(relation).to eq([child, parent, ancestor]) + end + end + + context ':desc' do + let(:hierarchy_order) { :desc } + + it 'orders by ancestor to child' do + expect(relation).to eq([ancestor, parent, child]) + end + end + end + end + + describe '#base_and_descendants' do + it 'includes the base and its descendants' do + relation = described_class.new(::Ci::Pipeline.where(id: parent.id), + options: { same_project: true }).base_and_descendants + + expect(relation).to contain_exactly(parent, child) + end + + context 'when with_depth is true' do + let(:relation) do + described_class.new(::Ci::Pipeline.where(id: ancestor.id), + options: { same_project: true }).base_and_descendants(with_depth: true) + end + + it 'includes depth in the results' do + object_depths = { + ancestor.id => 1, + parent.id => 2, + cousin_parent.id => 2, + child.id => 3, + cousin.id => 3 + } + + relation.each do |object| + expect(object.depth).to eq(object_depths[object.id]) + end + end + end + end + + describe '#all_objects' do + it 'includes its ancestors and descendants' do + relation = described_class.new(::Ci::Pipeline.where(id: parent.id), + options: { same_project: true }).all_objects + + expect(relation).to contain_exactly(ancestor, parent, child) + end + + it 'returns all family tree' do + relation = described_class.new( + ::Ci::Pipeline.where(id: child.id), + described_class.new(::Ci::Pipeline.where(id: child.id), options: { same_project: true }).base_and_ancestors, + options: { same_project: true } + ).all_objects + + expect(relation).to contain_exactly(ancestor, parent, cousin_parent, child, cousin) + end + end +end diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb index 8882defbd9e..7fb208213c1 100644 --- a/spec/lib/gitlab/ci/reports/test_case_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Reports::TestCase do describe '#initialize' do - let(:test_case) { described_class.new(params)} + let(:test_case) { described_class.new(params) } context 'when both classname and name are given' do context 'when test case is passed' do @@ -62,7 +62,9 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do end context 'when attachment is present' do - let(:attachment_test_case) { build(:test_case, :failed_with_attachment) } + let_it_be(:job) { create(:ci_build) } + + let(:attachment_test_case) { build(:test_case, :failed_with_attachment, job: job) } it "initializes the attachment if present" do expect(attachment_test_case.attachment).to eq("some/path.png") diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb index fbe3473f6b0..15fa78444e5 100644 --- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb @@ -176,6 +176,37 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do end end + describe '#sorted' do + subject { test_suite.sorted } + + context 'when there are multiple failed test cases' do + before do + test_suite.add_test_case(create_test_case_rspec_failed('test_spec_1', 1.11)) + test_suite.add_test_case(create_test_case_rspec_failed('test_spec_2', 4.44)) + end + + it 'returns test cases sorted by execution time desc' do + expect(subject.test_cases['failed'].each_value.first.execution_time).to eq(4.44) + expect(subject.test_cases['failed'].values.second.execution_time).to eq(1.11) + end + end + + context 'when there are multiple test cases' do + let(:status_ordered) { %w(error failed success skipped) } + + before do + test_suite.add_test_case(test_case_success) + test_suite.add_test_case(test_case_failed) + test_suite.add_test_case(test_case_error) + test_suite.add_test_case(test_case_skipped) + end + + it 'returns test cases sorted by status' do + expect(subject.test_cases.keys).to eq(status_ordered) + end + end + end + Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type| describe "##{status_type}" do subject { test_suite.public_send("#{status_type}") } diff --git a/spec/lib/gitlab/ci/status/bridge/common_spec.rb b/spec/lib/gitlab/ci/status/bridge/common_spec.rb new file mode 100644 index 00000000000..92600b21afc --- /dev/null +++ b/spec/lib/gitlab/ci/status/bridge/common_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Status::Bridge::Common do + let_it_be(:user) { create(:user) } + let_it_be(:bridge) { create(:ci_bridge) } + let_it_be(:downstream_pipeline) { create(:ci_pipeline) } + + before_all do + create(:ci_sources_pipeline, + source_pipeline: bridge.pipeline, + source_project: bridge.pipeline.project, + source_job: bridge, + pipeline: downstream_pipeline, + project: downstream_pipeline.project) + end + + subject do + Gitlab::Ci::Status::Core + .new(bridge, user) + .extend(described_class) + end + + describe '#details_path' do + context 'when user has access to read downstream pipeline' do + before do + downstream_pipeline.project.add_developer(user) + end + + it { expect(subject).to have_details } + it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" } + + context 'when ci_bridge_pipeline_details is disabled' do + before do + stub_feature_flags(ci_bridge_pipeline_details: false) + end + + it { expect(subject).not_to have_details } + it { expect(subject.details_path).to be_nil } + end + end + + context 'when user does not have access to read downstream pipeline' do + it { expect(subject).not_to have_details } + it { expect(subject.details_path).to be_nil } + end + end +end diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb index e1dcd05373f..bcfb9f19792 100644 --- a/spec/lib/gitlab/ci/status/composite_spec.rb +++ b/spec/lib/gitlab/ci/status/composite_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::Ci::Status::Composite do shared_examples 'compares status and warnings' do let(:composite_status) do - described_class.new(all_statuses) + described_class.new(all_statuses, dag: dag) end it 'returns status and warnings?' do @@ -30,21 +30,29 @@ RSpec.describe Gitlab::Ci::Status::Composite do end context 'allow_failure: false' do - where(:build_statuses, :result, :has_warnings) do - %i(skipped) | 'skipped' | false - %i(skipped success) | 'success' | false - %i(created) | 'created' | false - %i(preparing) | 'preparing' | false - %i(canceled success skipped) | 'canceled' | false - %i(pending created skipped) | 'pending' | false - %i(pending created skipped success) | 'running' | false - %i(running created skipped success) | 'running' | false - %i(success waiting_for_resource) | 'waiting_for_resource' | false - %i(success manual) | 'manual' | false - %i(success scheduled) | 'scheduled' | false - %i(created preparing) | 'preparing' | false - %i(created success pending) | 'running' | false - %i(skipped success failed) | 'failed' | false + where(:build_statuses, :dag, :result, :has_warnings) do + %i(skipped) | false | 'skipped' | false + %i(skipped success) | false | 'success' | false + %i(skipped success) | true | 'skipped' | false + %i(created) | false | 'created' | false + %i(preparing) | false | 'preparing' | false + %i(canceled success skipped) | false | 'canceled' | false + %i(canceled success skipped) | true | 'skipped' | false + %i(pending created skipped) | false | 'pending' | false + %i(pending created skipped success) | false | 'running' | false + %i(running created skipped success) | false | 'running' | false + %i(pending created skipped) | true | 'skipped' | false + %i(pending created skipped success) | true | 'skipped' | false + %i(running created skipped success) | true | 'skipped' | false + %i(success waiting_for_resource) | false | 'waiting_for_resource' | false + %i(success manual) | false | 'manual' | false + %i(success scheduled) | false | 'scheduled' | false + %i(created preparing) | false | 'preparing' | false + %i(created success pending) | false | 'running' | false + %i(skipped success failed) | false | 'failed' | false + %i(skipped success failed) | true | 'skipped' | false + %i(success manual) | true | 'pending' | false + %i(success failed created) | true | 'pending' | false end with_them do @@ -57,11 +65,12 @@ RSpec.describe Gitlab::Ci::Status::Composite do end context 'allow_failure: true' do - where(:build_statuses, :result, :has_warnings) do - %i(manual) | 'skipped' | false - %i(skipped failed) | 'success' | true - %i(created failed) | 'created' | true - %i(preparing manual) | 'preparing' | false + where(:build_statuses, :dag, :result, :has_warnings) do + %i(manual) | false | 'skipped' | false + %i(skipped failed) | false | 'success' | true + %i(skipped failed) | true | 'skipped' | true + %i(created failed) | false | 'created' | true + %i(preparing manual) | false | 'preparing' | false end with_them do diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index def4d1b3bf6..768256ee6b3 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -3,21 +3,21 @@ require 'spec_helper' RSpec.describe 'CI YML Templates' do - subject { Gitlab::Ci::YamlProcessor.new(content) } + subject { Gitlab::Ci::YamlProcessor.new(content).execute } let(:all_templates) { Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) } - let(:disabled_templates) do - Gitlab::Template::GitlabCiYmlTemplate.disabled_templates.map do |template| - template + Gitlab::Template::GitlabCiYmlTemplate.extension + let(:excluded_templates) do + all_templates.select do |name| + Gitlab::Template::GitlabCiYmlTemplate.excluded_patterns.any? { |pattern| pattern.match?(name) } end end - context 'included in a CI YAML configuration' do + context 'when including available templates in a CI YAML configuration' do using RSpec::Parameterized::TableSyntax where(:template_name) do - all_templates - disabled_templates + all_templates - excluded_templates end with_them do @@ -33,7 +33,7 @@ RSpec.describe 'CI YML Templates' do end it 'is valid' do - expect { subject }.not_to raise_error + expect(subject).to be_valid end it 'require default stages to be included' do @@ -41,4 +41,29 @@ RSpec.describe 'CI YML Templates' do end end end + + context 'when including unavailable templates in a CI YAML configuration' do + using RSpec::Parameterized::TableSyntax + + where(:template_name) do + excluded_templates + end + + with_them do + let(:content) do + <<~EOS + include: + - template: #{template_name} + + concrete_build_implemented_by_a_user: + stage: test + script: do something + EOS + end + + it 'is not valid' do + expect(subject).not_to be_valid + end + end + end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index e28469c9404..d65b6fb41f6 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -151,6 +151,28 @@ RSpec.describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do it_behaves_like 'appends' end + + describe 'metrics' do + let(:metrics) { spy('metrics') } + let(:io) { StringIO.new } + let(:stream) { described_class.new(metrics) { io } } + + it 'increments trace streamed operation' do + stream.append(+'123456', 0) + + expect(metrics) + .to have_received(:increment_trace_operation) + .with(operation: :streamed) + end + + it 'increments trace bytes counter' do + stream.append(+'123456', 0) + + expect(metrics) + .to have_received(:increment_trace_bytes) + .with(6) + end + end end describe '#set' do diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 85edf27d3e7..171877dbaee 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -11,6 +11,29 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do it { expect(trace).to delegate_method(:old_trace).to(:job) } end + context 'when trace is migrated to object storage' do + let!(:job) { create(:ci_build, :trace_artifact) } + let!(:artifact1) { job.job_artifacts_trace } + let!(:artifact2) { job.reload.job_artifacts_trace } + let(:test_data) { "hello world" } + + before do + stub_artifacts_object_storage + + artifact1.file.migrate!(ObjectStorage::Store::REMOTE) + end + + it 'reloads the trace after is it migrated' do + stub_const('Gitlab::HttpIO::BUFFER_SIZE', test_data.length) + + expect_next_instance_of(Gitlab::HttpIO) do |http_io| + expect(http_io).to receive(:get_chunk).and_return(test_data, "") + end + + expect(artifact2.job.trace.raw).to eq(test_data) + end + end + context 'when live trace feature is disabled' do before do stub_feature_flags(ci_enable_live_trace: false) 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 |