Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb103
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/tags_spec.rb63
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb61
-rw-r--r--spec/lib/gitlab/ci/parsers/security/common_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb108
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb101
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb36
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/pipeline/metrics_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/reports/security/flag_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/trace/backoff_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/variables/collection/sort_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb99
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb24
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