diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 16:18:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 16:18:24 +0300 |
commit | 0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch) | |
tree | 4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /spec/lib/gitlab/ci | |
parent | 744144d28e3e7fddc117924fef88de5d9674fe4c (diff) |
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci')
21 files changed, 704 insertions, 171 deletions
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 5b47d3a3922..0bb26babfc0 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -169,6 +169,22 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it { expect(entry).to be_valid } end end + + context 'when rules are used' do + let(:config) { { script: 'ls', cache: { key: 'test' }, rules: rules } } + + let(:rules) do + [ + { if: '$CI_PIPELINE_SOURCE == "schedule"', when: 'never' }, + [ + { if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' }, + { if: '$CI_PIPELINE_SOURCE == "merge_request_event"' } + ] + ] + end + + it { expect(entry).to be_valid } + end end context 'when entry value is not correct' do @@ -485,6 +501,70 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do end end end + + context 'when invalid rules are used' do + let(:config) { { script: 'ls', cache: { key: 'test' }, rules: rules } } + + context 'with rules nested more than max allowed levels' do + let(:sample_rule) { { if: '$THIS == "other"', when: 'always' } } + + let(:rules) do + [ + { if: '$THIS == "that"', when: 'always' }, + [ + { if: '$SKIP', when: 'never' }, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [ + sample_rule, + [sample_rule] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + end + + it { expect(entry).not_to be_valid } + end + + context 'with rules with invalid keys' do + let(:rules) do + [ + { invalid_key: 'invalid' }, + [ + { if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' }, + { if: '$CI_PIPELINE_SOURCE == "merge_request_event"' } + ] + ] + end + + it { expect(entry).not_to be_valid } + end + end end end @@ -618,6 +698,29 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do end end end + + context 'when job is using tags' do + context 'when limit is reached' do + let(:tags) { Array.new(100) { |i| "tag-#{i}" } } + let(:config) { { tags: tags, script: 'test' } } + + it 'returns error', :aggregate_failures do + expect(entry).not_to be_valid + expect(entry.errors) + .to include "tags config must be less than the limit of #{Gitlab::Ci::Config::Entry::Tags::TAGS_LIMIT} tags" + end + end + + context 'when limit is not reached' do + let(:config) { { tags: %w[tag1 tag2], script: 'test' } } + + it 'returns a valid entry', :aggregate_failures do + expect(entry).to be_valid + expect(entry.errors).to be_empty + expect(entry.tags).to eq(%w[tag1 tag2]) + end + end + end end describe '#manual_action?' do diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb index 91252378541..cfec33003e4 100644 --- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do let(:config) do [ { if: '$THIS == "that"', when: 'always' }, - [{ if: '$SKIP', when: 'never' }] + [{ if: '$SKIP', when: 'never' }, { if: '$THIS == "other"', when: 'always' }] ] end @@ -64,11 +64,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do let(:config) do [ { if: '$THIS == "that"', when: 'always' }, - [{ if: '$SKIP', when: 'never' }, [{ if: '$THIS == "other"', when: 'aways' }]] + [{ if: '$SKIP', when: 'never' }, [{ if: '$THIS == "other"', when: 'always' }]] ] end - it { is_expected.not_to be_valid } + it { is_expected.to be_valid } end end @@ -119,7 +119,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do context 'with rules nested more than one level' do let(:first_rule) { { if: '$THIS == "that"', when: 'always' } } let(:second_rule) { { if: '$SKIP', when: 'never' } } - let(:third_rule) { { if: '$THIS == "other"', when: 'aways' } } + let(:third_rule) { { if: '$THIS == "other"', when: 'always' } } let(:config) do [ diff --git a/spec/lib/gitlab/ci/config/entry/tags_spec.rb b/spec/lib/gitlab/ci/config/entry/tags_spec.rb new file mode 100644 index 00000000000..79317de373b --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/tags_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::Tags do + let(:entry) { described_class.new(config) } + + describe 'validation' do + context 'when tags config value is correct' do + let(:config) { %w[tag1 tag2] } + + describe '#value' do + it 'returns tags configuration' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + describe '#errors' do + context 'when tags config is not an array of strings' do + let(:config) { [1, 2] } + + it 'reports error' do + expect(entry.errors) + .to include 'tags config should be an array of strings' + end + end + + context 'when tags limit is reached' do + let(:config) { Array.new(50) {|i| "tag-#{i}" } } + + context 'when ci_build_tags_limit is enabled' do + before do + stub_feature_flags(ci_build_tags_limit: true) + end + + it 'reports error' do + expect(entry.errors) + .to include "tags config must be less than the limit of #{described_class::TAGS_LIMIT} tags" + end + end + + context 'when ci_build_tags_limit is disabled' do + before do + stub_feature_flags(ci_build_tags_limit: false) + end + + it 'does not report an error' do + expect(entry.errors).to be_empty + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 15293429354..4017accb462 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -297,4 +297,65 @@ RSpec.describe Gitlab::Ci::CronParser do it { is_expected.to eq(true) } end end + + describe '.parse_natural', :aggregate_failures do + let(:cron_line) { described_class.parse_natural_with_timestamp(time, { unit: 'day', duration: 1 }) } + let(:time) { Time.parse('Mon, 30 Aug 2021 06:29:44.067132000 UTC +00:00') } + let(:hours) { Fugit::Cron.parse(cron_line).hours } + let(:minutes) { Fugit::Cron.parse(cron_line).minutes } + let(:weekdays) { Fugit::Cron.parse(cron_line).weekdays.first } + let(:months) { Fugit::Cron.parse(cron_line).months } + + context 'when repeat cycle is day' do + it 'generates daily cron expression', :aggregate_failures do + expect(hours).to include time.hour + expect(minutes).to include time.min + end + end + + context 'when repeat cycle is week' do + let(:cron_line) { described_class.parse_natural_with_timestamp(time, { unit: 'week', duration: 1 }) } + + it 'generates weekly cron expression', :aggregate_failures do + expect(hours).to include time.hour + expect(minutes).to include time.min + expect(weekdays).to include time.wday + end + end + + context 'when repeat cycle is month' do + let(:cron_line) { described_class.parse_natural_with_timestamp(time, { unit: 'month', duration: 3 }) } + + it 'generates monthly cron expression', :aggregate_failures do + expect(minutes).to include time.min + expect(months).to include time.month + end + + context 'when an unsupported duration is specified' do + subject { described_class.parse_natural_with_timestamp(time, { unit: 'month', duration: 7 }) } + + it 'raises an exception' do + expect { subject }.to raise_error(NotImplementedError, 'The cadence {:unit=>"month", :duration=>7} is not supported') + end + end + end + + context 'when repeat cycle is year' do + let(:cron_line) { described_class.parse_natural_with_timestamp(time, { unit: 'year', duration: 1 }) } + + it 'generates yearly cron expression', :aggregate_failures do + expect(hours).to include time.hour + expect(minutes).to include time.min + expect(months).to include time.month + end + end + + context 'when the repeat cycle is not implemented' do + subject { described_class.parse_natural_with_timestamp(time, { unit: 'quarterly', duration: 1 }) } + + it 'raises an exception' do + expect { subject }.to raise_error(NotImplementedError, 'The cadence unit quarterly is not implemented') + end + end + end end diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb index c6387bf615b..c49673f5a4a 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# TODO remove duplication from spec/lib/gitlab/ci/parsers/security/common_spec.rb and spec/lib/gitlab/ci/parsers/security/common_spec.rb -# See https://gitlab.com/gitlab-org/gitlab/-/issues/336589 require 'spec_helper' RSpec.describe Gitlab::Ci::Parsers::Security::Common do @@ -15,11 +13,18 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do # The path 'yarn.lock' was initially used by DependencyScanning, it is okay for SAST locations to use it, but this could be made better let(:location) { ::Gitlab::Ci::Reports::Security::Locations::Sast.new(file_path: 'yarn.lock', start_line: 1, end_line: 1) } let(:tracking_data) { nil } + let(:vulnerability_flags_data) do + [ + ::Gitlab::Ci::Reports::Security::Flag.new(type: 'flagged-as-likely-false-positive', origin: 'post analyzer X', description: 'static string to sink'), + ::Gitlab::Ci::Reports::Security::Flag.new(type: 'flagged-as-likely-false-positive', origin: 'post analyzer Y', description: 'integer to sink') + ] + end before do allow_next_instance_of(described_class) do |parser| allow(parser).to receive(:create_location).and_return(location) allow(parser).to receive(:tracking_data).and_return(tracking_data) + allow(parser).to receive(:create_flags).and_return(vulnerability_flags_data) end artifact.each_blob { |blob| described_class.parse!(blob, report, vulnerability_finding_signatures_enabled) } @@ -233,6 +238,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do end end + describe 'parsing flags' do + it 'returns flags object for each finding' do + flags = report.findings.first.flags + + expect(flags).to contain_exactly( + have_attributes(type: 'flagged-as-likely-false-positive', origin: 'post analyzer X', description: 'static string to sink'), + have_attributes(type: 'flagged-as-likely-false-positive', origin: 'post analyzer Y', description: 'integer to sink') + ) + end + end + describe 'parsing links' do it 'returns links object for each finding', :aggregate_failures do links = report.findings.flat_map(&:links) diff --git a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb index f434ffd12bf..951e0576a58 100644 --- a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb @@ -6,7 +6,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do using RSpec::Parameterized::TableSyntax where(:report_type, :expected_errors, :valid_data) do - :sast | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] } + 'sast' | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] } + :sast | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] } :secret_detection | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] } end diff --git a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb index 5fa414f5bd1..32c92724f62 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb @@ -3,10 +3,16 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations do - let(:project) { create(:project, :repository) } - let(:user) { create(:user, developer_projects: [project]) } + let_it_be_with_reload(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user, developer_projects: [project]) } + let(:pipeline) { Ci::Pipeline.new } - let(:step) { described_class.new(pipeline, command) } + let(:bridge) { nil } + + let(:variables_attributes) do + [{ key: 'first', secret_value: 'world' }, + { key: 'second', secret_value: 'second_world' }] + end let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( @@ -20,7 +26,26 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations do merge_request: nil, project: project, current_user: user, - bridge: bridge) + bridge: bridge, + variables_attributes: variables_attributes) + end + + let(:step) { described_class.new(pipeline, command) } + + shared_examples 'breaks the chain' do + it 'returns true' do + step.perform! + + expect(step.break?).to be true + end + end + + shared_examples 'does not break the chain' do + it 'returns false' do + step.perform! + + expect(step.break?).to be false + end end context 'when a bridge is passed in to the pipeline creation' do @@ -37,26 +62,83 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations do ) end - it 'never breaks the chain' do - step.perform! - - expect(step.break?).to eq(false) - end + it_behaves_like 'does not break the chain' end context 'when a bridge is not passed in to the pipeline creation' do - let(:bridge) { nil } - it 'leaves the source pipeline empty' do step.perform! expect(pipeline.source_pipeline).to be_nil end - it 'never breaks the chain' do + it_behaves_like 'does not break the chain' + end + + it 'sets pipeline variables' do + step.perform! + + expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) }) + .to eq variables_attributes.map(&:with_indifferent_access) + end + + context 'when project setting restrict_user_defined_variables is enabled' do + before do + project.update!(restrict_user_defined_variables: true) + end + + context 'when user is developer' do + it_behaves_like 'breaks the chain' + + it 'returns an error on variables_attributes', :aggregate_failures do + step.perform! + + expect(pipeline.errors.full_messages).to eq(['Insufficient permissions to set pipeline variables']) + expect(pipeline.variables).to be_empty + end + + context 'when variables_attributes is not specified' do + let(:variables_attributes) { nil } + + it_behaves_like 'does not break the chain' + + it 'assigns empty variables' do + step.perform! + + expect(pipeline.variables).to be_empty + end + end + end + + context 'when user is maintainer' do + before do + project.add_maintainer(user) + end + + it_behaves_like 'does not break the chain' + + it 'assigns variables_attributes' do + step.perform! + + expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) }) + .to eq variables_attributes.map(&:with_indifferent_access) + end + end + end + + context 'with duplicate pipeline variables' do + let(:variables_attributes) do + [{ key: 'first', secret_value: 'world' }, + { key: 'first', secret_value: 'second_world' }] + end + + it_behaves_like 'breaks the chain' + + it 'returns an error for variables_attributes' do step.perform! - expect(step.break?).to eq(false) + expect(pipeline.errors.full_messages).to eq(['Duplicate variable name: first']) + expect(pipeline.variables).to be_empty end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index 7771289abe6..dca2204f544 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -8,11 +8,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do let(:pipeline) { Ci::Pipeline.new } - let(:variables_attributes) do - [{ key: 'first', secret_value: 'world' }, - { key: 'second', secret_value: 'second_world' }] - end - let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( source: :push, @@ -24,100 +19,26 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do schedule: nil, merge_request: nil, project: project, - current_user: user, - variables_attributes: variables_attributes) + current_user: user) end let(:step) { described_class.new(pipeline, command) } - shared_examples 'builds pipeline' do - it 'builds a pipeline with the expected attributes' do - step.perform! - - expect(pipeline.sha).not_to be_empty - expect(pipeline.sha).to eq project.commit.id - expect(pipeline.ref).to eq 'master' - expect(pipeline.tag).to be false - expect(pipeline.user).to eq user - expect(pipeline.project).to eq project - end - end - - shared_examples 'breaks the chain' do - it 'returns true' do - step.perform! - - expect(step.break?).to be true - end - end - - shared_examples 'does not break the chain' do - it 'returns false' do - step.perform! - - expect(step.break?).to be false - end - end - - before do - stub_ci_pipeline_yaml_file(gitlab_ci_yaml) - end - - it_behaves_like 'does not break the chain' - it_behaves_like 'builds pipeline' - - it 'sets pipeline variables' do + it 'does not break the chain' do step.perform! - expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) }) - .to eq variables_attributes.map(&:with_indifferent_access) + expect(step.break?).to be false end - context 'when project setting restrict_user_defined_variables is enabled' do - before do - project.update!(restrict_user_defined_variables: true) - end - - context 'when user is developer' do - it_behaves_like 'breaks the chain' - it_behaves_like 'builds pipeline' - - it 'returns an error on variables_attributes', :aggregate_failures do - step.perform! - - expect(pipeline.errors.full_messages).to eq(['Insufficient permissions to set pipeline variables']) - expect(pipeline.variables).to be_empty - end - - context 'when variables_attributes is not specified' do - let(:variables_attributes) { nil } - - it_behaves_like 'does not break the chain' - it_behaves_like 'builds pipeline' - - it 'assigns empty variables' do - step.perform! - - expect(pipeline.variables).to be_empty - end - end - end - - context 'when user is maintainer' do - before do - project.add_maintainer(user) - end - - it_behaves_like 'does not break the chain' - it_behaves_like 'builds pipeline' - - it 'assigns variables_attributes' do - step.perform! + it 'builds a pipeline with the expected attributes' do + step.perform! - expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) }) - .to eq variables_attributes.map(&:with_indifferent_access) - end - end + expect(pipeline.sha).not_to be_empty + expect(pipeline.sha).to eq project.commit.id + expect(pipeline.ref).to eq 'master' + expect(pipeline.tag).to be false + expect(pipeline.user).to eq user + expect(pipeline.project).to eq project end it 'returns a valid pipeline' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb index 2727f2603cd..27a5abf988c 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb @@ -44,6 +44,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines do expect(build_statuses(pipeline)).to contain_exactly('pending') end + it 'cancels the builds with 2 queries to avoid query timeout' do + second_query_regex = /WHERE "ci_pipelines"\."id" = \d+ AND \(NOT EXISTS/ + recorder = ActiveRecord::QueryRecorder.new { perform } + second_query = recorder.occurrences.keys.filter { |occ| occ =~ second_query_regex } + + expect(second_query).to be_one + end + context 'when the previous pipeline has a child pipeline' do let(:child_pipeline) { create(:ci_pipeline, child_of: prev_pipeline) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index c22a0e23794..0d78ce3440a 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -341,4 +341,40 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do end end end + + describe '#observe_step_duration' do + context 'when ci_pipeline_creation_step_duration_tracking is enabled' do + it 'adds the duration to the step duration histogram' do + histogram = double(:histogram) + duration = 1.hour + + expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_creation_step_duration_histogram) + .and_return(histogram) + expect(histogram).to receive(:observe) + .with({ step: 'Gitlab::Ci::Pipeline::Chain::Build' }, duration.seconds) + + described_class.new.observe_step_duration( + Gitlab::Ci::Pipeline::Chain::Build, + duration + ) + end + end + + context 'when ci_pipeline_creation_step_duration_tracking is disabled' do + before do + stub_feature_flags(ci_pipeline_creation_step_duration_tracking: false) + end + + it 'does nothing' do + duration = 1.hour + + expect(::Gitlab::Ci::Pipeline::Metrics).not_to receive(:pipeline_creation_step_duration_histogram) + + described_class.new.observe_step_duration( + Gitlab::Ci::Pipeline::Chain::Build, + duration + ) + end + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb index 42ec9ab6f5d..e0d656f456e 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb @@ -92,6 +92,27 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) end + + context 'when path specifies a refname' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo:refname' } + let(:config_content_result) do + <<~EOY + --- + include: + - project: another-group/another-repo + file: path/to/.gitlab-ci.yml + ref: refname + EOY + end + + it 'builds root config including the path and refname to another repository' do + subject.perform! + + expect(pipeline.config_source).to eq 'external_project_source' + expect(pipeline.pipeline_config.content).to eq(config_content_result) + expect(command.config_content).to eq(config_content_result) + end + end end context 'when config is defined in the default .gitlab-ci.yml' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb index 83d47ae6819..e8eb3333b88 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb @@ -8,8 +8,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do let(:pipeline) { build_stubbed(:ci_pipeline) } let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project) } - let(:first_step) { spy('first step') } - let(:second_step) { spy('second step') } + let(:first_step) { spy('first step', name: 'FirstStep') } + let(:second_step) { spy('second step', name: 'SecondStep') } let(:sequence) { [first_step, second_step] } let(:histogram) { spy('prometheus metric') } @@ -61,6 +61,17 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do expect(histogram).to have_received(:observe) end + it 'adds step sequence duration to duration histogram' do + expect(command.metrics) + .to receive(:pipeline_creation_step_duration_histogram) + .twice + .and_return(histogram) + expect(histogram).to receive(:observe).with({ step: 'FirstStep' }, any_args).ordered + expect(histogram).to receive(:observe).with({ step: 'SecondStep' }, any_args).ordered + + subject.build! + end + it 'records pipeline size by pipeline source in a histogram' do allow(command.metrics) .to receive(:pipeline_size_histogram) diff --git a/spec/lib/gitlab/ci/pipeline/metrics_spec.rb b/spec/lib/gitlab/ci/pipeline/metrics_spec.rb new file mode 100644 index 00000000000..83b969ff3c4 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/metrics_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Gitlab::Ci::Pipeline::Metrics do + describe '.pipeline_creation_step_duration_histogram' do + around do |example| + described_class.clear_memoization(:pipeline_creation_step_histogram) + + example.run + + described_class.clear_memoization(:pipeline_creation_step_histogram) + end + + it 'adds the step to the step duration histogram' do + expect(::Gitlab::Metrics).to receive(:histogram) + .with( + :gitlab_ci_pipeline_creation_step_duration_seconds, + 'Duration of each pipeline creation step', + { step: nil }, + [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 15.0, 20.0, 50.0, 240.0] + ) + + described_class.pipeline_creation_step_duration_histogram + 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 58938251ca1..0c28515b574 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -490,12 +490,21 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end context 'when job belongs to a resource group' do - let(:attributes) { { name: 'rspec', ref: 'master', resource_group_key: 'iOS' } } + let(:resource_group) { 'iOS' } + let(:attributes) { { name: 'rspec', ref: 'master', resource_group_key: resource_group, environment: 'production' }} it 'returns a job with resource group' do expect(subject.resource_group).not_to be_nil expect(subject.resource_group.key).to eq('iOS') end + + context 'when resource group has $CI_ENVIRONMENT_NAME in it' do + let(:resource_group) { 'test/$CI_ENVIRONMENT_NAME' } + + it 'expands environment name' do + expect(subject.resource_group.key).to eq('test/production') + end + end end end @@ -1140,16 +1149,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'does not have errors' do expect(subject.errors).to be_empty end - - context 'when ci_same_stage_job_needs FF is disabled' do - before do - stub_feature_flags(ci_same_stage_job_needs: false) - end - - it 'has errors' do - expect(subject.errors).to contain_exactly("'rspec' job needs 'build' job, but 'build' is not in any previous stage") - end - end end context 'when using 101 needs' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb index 3424e7d03a3..5d8a9358e10 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb @@ -34,10 +34,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do described_class.new(seed_context, stages_attributes) end - before do - stub_feature_flags(ci_same_stage_job_needs: false) - end - describe '#stages' do it 'returns the stage resources' do stages = seed.stages diff --git a/spec/lib/gitlab/ci/reports/security/flag_spec.rb b/spec/lib/gitlab/ci/reports/security/flag_spec.rb new file mode 100644 index 00000000000..27f83694ac2 --- /dev/null +++ b/spec/lib/gitlab/ci/reports/security/flag_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::Security::Flag do + subject(:security_flag) { described_class.new(type: 'flagged-as-likely-false-positive', origin: 'post analyzer X', description: 'static string to sink') } + + describe '#initialize' do + context 'when all params are given' do + it 'initializes an instance' do + expect { subject }.not_to raise_error + + expect(subject).to have_attributes( + type: 'flagged-as-likely-false-positive', + origin: 'post analyzer X', + description: 'static string to sink' + ) + end + end + + describe '#to_hash' do + it 'returns expected hash' do + expect(security_flag.to_hash).to eq( + { + flag_type: :false_positive, + origin: 'post analyzer X', + description: 'static string to sink' + } + ) + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace/backoff_spec.rb b/spec/lib/gitlab/ci/trace/backoff_spec.rb new file mode 100644 index 00000000000..0fb7e81c6c5 --- /dev/null +++ b/spec/lib/gitlab/ci/trace/backoff_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Trace::Backoff do + using RSpec::Parameterized::TableSyntax + + subject(:backoff) { described_class.new(archival_attempts) } + + it 'keeps the MAX_ATTEMPTS limit in sync' do + expect(Ci::BuildTraceMetadata::MAX_ATTEMPTS).to eq(5) + end + + it 'keeps the Redis TTL limit in sync' do + expect(Ci::BuildTraceChunks::RedisBase::CHUNK_REDIS_TTL).to eq(7.days) + end + + describe '#value' do + where(:archival_attempts, :result) do + 1 | 9.6 + 2 | 19.2 + 3 | 28.8 + 4 | 38.4 + 5 | 48.0 + end + + with_them do + subject { backoff.value } + + it { is_expected.to eq(result.hours) } + end + end + + describe '#value_with_jitter' do + where(:archival_attempts, :min_value, :max_value) do + 1 | 9.6 | 13.6 + 2 | 19.2 | 23.2 + 3 | 28.8 | 32.8 + 4 | 38.4 | 42.4 + 5 | 48.0 | 52.0 + end + + with_them do + subject { backoff.value_with_jitter } + + it { is_expected.to be_in(min_value.hours..max_value.hours) } + end + end + + it 'all retries are happening under the 7 days limit' do + backoff_total = 1.upto(Ci::BuildTraceMetadata::MAX_ATTEMPTS).sum do |attempt| + backoff = described_class.new(attempt) + expect(backoff).to receive(:rand) + .with(described_class::MAX_JITTER_VALUE) + .and_return(described_class::MAX_JITTER_VALUE) + + backoff.value_with_jitter + end + + expect(backoff_total).to be < Ci::BuildTraceChunks::RedisBase::CHUNK_REDIS_TTL + end +end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 69f56871740..1a31b2dad56 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -130,4 +130,18 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_defa end end end + + describe '#can_attempt_archival_now?' do + it 'creates the record and returns true' do + expect(trace.can_attempt_archival_now?).to be_truthy + end + end + + describe '#increment_archival_attempts!' do + it 'creates the record and increments its value' do + expect { trace.increment_archival_attempts! } + .to change { build.reload.trace_metadata&.archival_attempts }.from(nil).to(1) + .and change { build.reload.trace_metadata&.last_archival_attempt_at } + end + end end diff --git a/spec/lib/gitlab/ci/variables/collection/sort_spec.rb b/spec/lib/gitlab/ci/variables/collection/sort_spec.rb index 01eef673c35..7e4e9602a92 100644 --- a/spec/lib/gitlab/ci/variables/collection/sort_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/sort_spec.rb @@ -5,20 +5,10 @@ require 'rspec-parameterized' RSpec.describe Gitlab::Ci::Variables::Collection::Sort do describe '#initialize with non-Collection value' do - context 'when FF :variable_inside_variable is disabled' do - subject { Gitlab::Ci::Variables::Collection::Sort.new([]) } + subject { Gitlab::Ci::Variables::Collection::Sort.new([]) } - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError, /Collection object was expected/) - end - end - - context 'when FF :variable_inside_variable is enabled' do - subject { Gitlab::Ci::Variables::Collection::Sort.new([]) } - - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError, /Collection object was expected/) - end + it 'raises ArgumentError' do + expect { subject }.to raise_error(ArgumentError, /Collection object was expected/) end end @@ -182,5 +172,33 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do expect { subject }.to raise_error(TSort::Cyclic) end end + + context 'with overridden variables' do + let(:variables) do + [ + { key: 'PROJECT_VAR', value: '$SUBGROUP_VAR' }, + { key: 'SUBGROUP_VAR', value: '$TOP_LEVEL_GROUP_NAME' }, + { key: 'SUBGROUP_VAR', value: '$SUB_GROUP_NAME' }, + { key: 'TOP_LEVEL_GROUP_NAME', value: 'top-level-group' }, + { key: 'SUB_GROUP_NAME', value: 'vars-in-vars-subgroup' } + ] + end + + let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) } + + subject do + Gitlab::Ci::Variables::Collection::Sort.new(collection).tsort.map { |v| { v[:key] => v.value } } + end + + it 'preserves relative order of overridden variables' do + is_expected.to eq([ + { 'TOP_LEVEL_GROUP_NAME' => 'top-level-group' }, + { 'SUBGROUP_VAR' => '$TOP_LEVEL_GROUP_NAME' }, + { 'SUB_GROUP_NAME' => 'vars-in-vars-subgroup' }, + { 'SUBGROUP_VAR' => '$SUB_GROUP_NAME' }, + { 'PROJECT_VAR' => '$SUBGROUP_VAR' } + ]) + end + end end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index abda27f0d6e..7ba98380986 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -123,17 +123,102 @@ RSpec.describe Gitlab::Ci::Variables::Collection do end describe '#[]' do - variable = { key: 'VAR', value: 'value', public: true, masked: false } + subject { Gitlab::Ci::Variables::Collection.new(variables)[var_name] } - collection = described_class.new([variable]) + shared_examples 'an array access operator' do + context 'for a non-existent variable name' do + let(:var_name) { 'UNKNOWN_VAR' } - it 'returns nil for a non-existent variable name' do - expect(collection['UNKNOWN_VAR']).to be_nil + it 'returns nil' do + is_expected.to be_nil + end + end + + context 'for an existent variable name' do + let(:var_name) { 'VAR' } + + it 'returns the last Item' do + is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection::Item) + expect(subject.to_runner_variable).to eq(variables.last) + end + end + end + + context 'with variable key with single entry' do + let(:variables) do + [ + { key: 'VAR', value: 'value', public: true, masked: false } + ] + end + + it_behaves_like 'an array access operator' + end + + context 'with variable key with multiple entries' do + let(:variables) do + [ + { key: 'VAR', value: 'value', public: true, masked: false }, + { key: 'VAR', value: 'override value', public: true, masked: false } + ] + end + + it_behaves_like 'an array access operator' end + end + + describe '#all' do + subject { described_class.new(variables).all(var_name) } - it 'returns Item for an existent variable name' do - expect(collection['VAR']).to be_an_instance_of(Gitlab::Ci::Variables::Collection::Item) - expect(collection['VAR'].to_runner_variable).to eq(variable) + shared_examples 'a method returning all known variables or nil' do + context 'for a non-existent variable name' do + let(:var_name) { 'UNKNOWN_VAR' } + + it 'returns nil' do + is_expected.to be_nil + end + end + + context 'for an existing variable name' do + let(:var_name) { 'VAR' } + + it 'returns all expected Items' do + is_expected.to eq(expected_variables.map { |v| Gitlab::Ci::Variables::Collection::Item.fabricate(v) }) + end + end + end + + context 'with variable key with single entry' do + let(:variables) do + [ + { key: 'VAR', value: 'value', public: true, masked: false } + ] + end + + it_behaves_like 'a method returning all known variables or nil' do + let(:expected_variables) do + [ + { key: 'VAR', value: 'value', public: true, masked: false } + ] + end + end + end + + context 'with variable key with multiple entries' do + let(:variables) do + [ + { key: 'VAR', value: 'value', public: true, masked: false }, + { key: 'VAR', value: 'override value', public: true, masked: false } + ] + end + + it_behaves_like 'a method returning all known variables or nil' do + let(:expected_variables) do + [ + { key: 'VAR', value: 'value', public: true, masked: false }, + { key: 'VAR', value: 'override value', public: true, masked: false } + ] + end + end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 49a470f9e01..1591c2e6b60 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -590,14 +590,6 @@ module Gitlab end it_behaves_like 'has warnings and expected error', /build job: need test is not defined in current or prior stages/ - - context 'with ci_same_stage_job_needs FF disabled' do - before do - stub_feature_flags(ci_same_stage_job_needs: false) - end - - it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/ - end end end end @@ -1809,14 +1801,6 @@ module Gitlab let(:dependencies) { ['deploy'] } it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in current or prior stages' - - context 'with ci_same_stage_job_needs FF disabled' do - before do - stub_feature_flags(ci_same_stage_job_needs: false) - end - - it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in prior stages' - end end context 'when a job depends on another job that references a not-yet defined stage' do @@ -2053,14 +2037,6 @@ module Gitlab let(:needs) { ['deploy'] } it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in current or prior stages' - - context 'with ci_same_stage_job_needs FF disabled' do - before do - stub_feature_flags(ci_same_stage_job_needs: false) - end - - it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in prior stages' - end end context 'needs and dependencies that are mismatching' do |