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/artifacts/decompressed_artifact_size_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/components/instance_path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb131
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules_spec.rb71
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb137
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config/external/file/component_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/external/rules_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/config/header/input_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/access_spec.rb (renamed from spec/lib/gitlab/ci/interpolation/access_spec.rb)11
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/block_spec.rb112
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/config_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/context_spec.rb (renamed from spec/lib/gitlab/ci/interpolation/context_spec.rb)16
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb137
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb (renamed from spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb)56
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/template_spec.rb (renamed from spec/lib/gitlab/ci/interpolation/template_spec.rb)6
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb105
-rw-r--r--spec/lib/gitlab/ci/config/yaml/loader_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/yaml/result_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/yaml_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/input/arguments/base_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/input/arguments/default_spec.rb53
-rw-r--r--spec/lib/gitlab/ci/input/arguments/options_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/input/arguments/required_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/input/arguments/unknown_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/input/inputs_spec.rb126
-rw-r--r--spec/lib/gitlab/ci/interpolation/block_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/interpolation/config_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/jwt_v2_spec.rb78
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/project_config/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/project_config_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/queue/metrics_spec.rb71
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/component_spec.rb148
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/play_manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/tags/bulk_insert_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/variables/downstream/generator_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb36
55 files changed, 1714 insertions, 668 deletions
diff --git a/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb b/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb
index ef39a431d63..47d91e2478e 100644
--- a/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb
+++ b/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Artifacts::DecompressedArtifactSizeValidator, feature
let(:gzip_valid?) { true }
let(:validator) { instance_double(::Gitlab::Ci::DecompressedGzipSizeValidator, valid?: gzip_valid?) }
- before(:all) do
+ before_all do
Zlib::GzipWriter.open(file_path) do |gz|
gz.write('Hello World!')
end
diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb
index 511036efd37..f4bc706f9b4 100644
--- a/spec/lib/gitlab/ci/components/instance_path_spec.rb
+++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
create(:release, project: existing_project, sha: 'sha-1', released_at: Time.zone.now)
end
- before(:all) do
+ before_all do
# Previous release
create(:release, project: existing_project, sha: 'sha-2', released_at: Time.zone.now - 1.day)
end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
index 10c1d92e209..dd15b049b9b 100644
--- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
@@ -1,117 +1,132 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper' # Change this to fast spec helper when FF `ci_refactor_external_rules` is removed
require_dependency 'active_model'
RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule, feature_category: :pipeline_composition do
let(:factory) do
- Gitlab::Config::Entry::Factory.new(described_class)
- .value(config)
+ Gitlab::Config::Entry::Factory.new(described_class).value(config)
end
subject(:entry) { factory.create! }
- describe '.new' do
- shared_examples 'an invalid config' do |error_message|
- it { is_expected.not_to be_valid }
+ before do
+ entry.compose!
+ end
+
+ shared_examples 'a valid config' do
+ it { is_expected.to be_valid }
+
+ it 'returns the expected value' do
+ expect(entry.value).to eq(config.compact)
+ end
- it 'has errors' do
- expect(entry.errors).to include(error_message)
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
end
+
+ it 'returns the expected value' do
+ expect(entry.value).to eq(config)
+ end
+ end
+ end
+
+ shared_examples 'an invalid config' do |error_message|
+ it { is_expected.not_to be_valid }
+
+ it 'has errors' do
+ expect(entry.errors).to include(error_message)
end
+ end
- context 'when specifying an if: clause' do
- let(:config) { { if: '$THIS || $THAT' } }
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT' } }
- it { is_expected.to be_valid }
+ it_behaves_like 'a valid config'
- context 'with when:' do
- let(:config) { { if: '$THIS || $THAT', when: 'never' } }
+ context 'with when:' do
+ let(:config) { { if: '$THIS || $THAT', when: 'never' } }
- it { is_expected.to be_valid }
- end
+ it_behaves_like 'a valid config'
end
- context 'when specifying an exists: clause' do
- let(:config) { { exists: './this.md' } }
+ context 'with when: <invalid string>' do
+ let(:config) { { if: '$THIS || $THAT', when: 'on_success' } }
- it { is_expected.to be_valid }
+ it_behaves_like 'an invalid config', /when unknown value: on_success/
end
- context 'using a list of multiple expressions' do
- let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
+ context 'with when: null' do
+ let(:config) { { if: '$THIS || $THAT', when: nil } }
- it_behaves_like 'an invalid config', /invalid expression syntax/
+ it_behaves_like 'a valid config'
end
- context 'when specifying an invalid if: clause expression' do
- let(:config) { { if: ['$MY_VAR =='] } }
+ context 'when if: clause is invalid' do
+ let(:config) { { if: '$MY_VAR ==' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when specifying an if: clause expression with an invalid token' do
- let(:config) { { if: ['$MY_VAR == 123'] } }
+ context 'when if: clause has an integer operand' do
+ let(:config) { { if: '$MY_VAR == 123' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when using invalid regex in an if: clause' do
- let(:config) { { if: ['$MY_VAR =~ /some ( thing/'] } }
+ context 'when if: clause has invalid regex' do
+ let(:config) { { if: '$MY_VAR =~ /some ( thing/' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when using an if: clause with lookahead regex character "?"' do
+ context 'when if: clause has lookahead regex character "?"' do
let(:config) { { if: '$CI_COMMIT_REF =~ /^(?!master).+/' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when specifying unknown policy' do
- let(:config) { { invalid: :something } }
+ context 'when if: clause has array of expressions' do
+ let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
- it_behaves_like 'an invalid config', /unknown keys: invalid/
+ it_behaves_like 'an invalid config', /invalid expression syntax/
end
+ end
+
+ context 'when specifying an exists: clause' do
+ let(:config) { { exists: './this.md' } }
- context 'when clause is empty' do
- let(:config) { {} }
+ it_behaves_like 'a valid config'
- it_behaves_like 'an invalid config', /can't be blank/
+ context 'when array' do
+ let(:config) { { exists: ['./this.md', './that.md'] } }
+
+ it_behaves_like 'a valid config'
end
- context 'when policy strategy does not match' do
- let(:config) { 'string strategy' }
+ context 'when null' do
+ let(:config) { { exists: nil } }
- it_behaves_like 'an invalid config', /should be a hash/
+ it_behaves_like 'a valid config'
end
end
- describe '#value' do
- subject(:value) { entry.value }
-
- context 'when specifying an if: clause' do
- let(:config) { { if: '$THIS || $THAT' } }
+ context 'when specifying an unknown keyword' do
+ let(:config) { { invalid: :something } }
- it 'returns the config' do
- expect(subject).to eq(if: '$THIS || $THAT')
- end
+ it_behaves_like 'an invalid config', /unknown keys: invalid/
+ end
- context 'with when:' do
- let(:config) { { if: '$THIS || $THAT', when: 'never' } }
+ context 'when config is blank' do
+ let(:config) { {} }
- it 'returns the config' do
- expect(subject).to eq(if: '$THIS || $THAT', when: 'never')
- end
- end
- end
+ it_behaves_like 'an invalid config', /can't be blank/
+ end
- context 'when specifying an exists: clause' do
- let(:config) { { exists: './test.md' } }
+ context 'when config type is invalid' do
+ let(:config) { 'invalid' }
- it 'returns the config' do
- expect(subject).to eq(exists: './test.md')
- end
- end
+ it_behaves_like 'an invalid config', /should be a hash/
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
index d5988dbbb58..05db81abfc1 100644
--- a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper' # Change this to fast spec helper when FF `ci_refactor_external_rules` is removed
require_dependency 'active_model'
-RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do
+RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules, feature_category: :pipeline_composition do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
.value(config)
@@ -77,23 +77,68 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do
describe '#value' do
subject(:value) { entry.value }
- context 'with an "if"' do
- let(:config) do
- [{ if: '$THIS == "that"' }]
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ end
+
+ it { is_expected.to eq([]) }
+
+ context 'when composed' do
+ before do
+ entry.compose!
end
- it { is_expected.to eq(config) }
+ it 'returns the composed entries value' do
+ expect(entry).to be_valid
+ is_expected.to eq(
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ )
+ end
+
+ context 'when invalid' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP', invalid: 'invalid' }
+ ]
+ end
+
+ it 'returns the invalid config' do
+ expect(entry).not_to be_valid
+ is_expected.to eq(config)
+ end
+ end
end
- context 'with a list of two rules' do
- let(:config) do
- [
- { if: '$THIS == "that"' },
- { if: '$SKIP' }
- ]
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
+ end
+
+ context 'with an "if"' do
+ let(:config) do
+ [{ if: '$THIS == "that"' }]
+ end
+
+ it { is_expected.to eq(config) }
end
- it { is_expected.to eq(config) }
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP' }
+ ]
+ end
+
+ it { is_expected.to eq(config) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb
index ab2e8d4db78..eba9411560e 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Need, feature_category: :pipeline_composition do
subject(:need) { described_class.new(config) }
shared_examples 'job type' do
@@ -219,6 +219,81 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
it_behaves_like 'job type'
end
+
+ context 'when parallel:matrix has a value' do
+ before do
+ need.compose!
+ end
+
+ context 'and it is a string value' do
+ let(:config) do
+ { job: 'job_name', parallel: { matrix: [{ platform: 'p1', stack: 's1' }] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(
+ name: 'job_name',
+ artifacts: true,
+ optional: false,
+ parallel: { matrix: [{ "platform" => ['p1'], "stack" => ['s1'] }] }
+ )
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+
+ context 'and it is an array value' do
+ let(:config) do
+ { job: 'job_name', parallel: { matrix: [{ platform: %w[p1 p2], stack: %w[s1 s2] }] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(
+ name: 'job_name',
+ artifacts: true,
+ optional: false,
+ parallel: { matrix: [{ 'platform' => %w[p1 p2], 'stack' => %w[s1 s2] }] }
+ )
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+
+ context 'and it is a both an array and string value' do
+ let(:config) do
+ { job: 'job_name', parallel: { matrix: [{ platform: %w[p1 p2], stack: 's1' }] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(
+ name: 'job_name',
+ artifacts: true,
+ optional: false,
+ parallel: { matrix: [{ 'platform' => %w[p1 p2], 'stack' => ['s1'] }] }
+ )
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+ end
end
context 'with cross pipeline artifacts needs' do
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index 489fbac68b2..d1a8a74ac06 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Needs, feature_category: :pipeline_composition do
subject(:needs) { described_class.new(config) }
before do
@@ -67,6 +67,141 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
end
end
+ context 'when needs value is a hash' do
+ context 'with a job value' do
+ let(:config) do
+ { job: 'job_name' }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a parallel value that is a numeric value' do
+ let(:config) do
+ { job: 'job_name', parallel: 2 }
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns errors about number values being invalid for needs:parallel' do
+ expect(needs.errors).to match_array(["needs config cannot use \"parallel: <number>\"."])
+ end
+ end
+ end
+ end
+
+ context 'when needs:parallel value is incorrect' do
+ context 'with a keyword that is not "matrix"' do
+ let(:config) do
+ [
+ { job: 'job_name', parallel: { not_matrix: [{ one: 'aaa', two: 'bbb' }] } }
+ ]
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns errors about incorrect matrix keyword' do
+ expect(needs.errors).to match_array([
+ 'need:parallel config contains unknown keys: not_matrix',
+ 'need:parallel config missing required keys: matrix'
+ ])
+ end
+ end
+ end
+
+ context 'with a number value' do
+ let(:config) { [{ job: 'job_name', parallel: 2 }] }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns errors about number values being invalid for needs:parallel' do
+ expect(needs.errors).to match_array(["needs config cannot use \"parallel: <number>\"."])
+ end
+ end
+ end
+ end
+
+ context 'when needs:parallel:matrix value is empty' do
+ let(:config) { [{ job: 'job_name', parallel: { matrix: {} } }] }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about incorrect type' do
+ expect(needs.errors).to contain_exactly(
+ 'need:parallel:matrix config should be an array of hashes')
+ end
+ end
+ end
+
+ context 'when needs:parallel:matrix value is incorrect' do
+ let(:config) { [{ job: 'job_name', parallel: { matrix: 'aaa' } }] }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about incorrect type' do
+ expect(needs.errors).to contain_exactly(
+ 'need:parallel:matrix config should be an array of hashes')
+ end
+ end
+ end
+
+ context 'when needs:parallel:matrix value is correct' do
+ context 'with a simple config' do
+ let(:config) do
+ [
+ { job: 'job_name', parallel: { matrix: [{ A: 'a1', B: 'b1' }] } }
+ ]
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a complex config' do
+ let(:config) do
+ [
+ {
+ job: 'job_name1',
+ artifacts: true,
+ parallel: { matrix: [{ A: %w[a1 a2], B: %w[b1 b2 b3], C: %w[c1 c2] }] }
+ },
+ {
+ job: 'job_name2',
+ parallel: {
+ matrix: [
+ { A: %w[a1 a2], D: %w[d1 d2] },
+ { E: %w[e1 e2], F: ['f1'] },
+ { C: %w[c1 c2 c3], G: %w[g1 g2], H: ['h1'] }
+ ]
+ }
+ }
+ ]
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+ end
+ end
+
context 'with too many cross pipeline dependencies' do
let(:limit) { described_class::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT }
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 73bf2d422b7..d610c3ce2f6 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports, feature_category: :pipeline_c
:terraform | 'tfplan.json'
:accessibility | 'gl-accessibility.json'
:cyclonedx | 'gl-sbom.cdx.zip'
+ :annotations | 'gl-annotations.json'
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index d917924f257..d8bd578be94 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -57,6 +57,24 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
end
end
end
+
+ describe 'max_total_yaml_size_bytes' do
+ context 'when application setting `max_total_yaml_size_bytes` is requsted and was never updated by the admin' do
+ it 'returns the default value `max_total_yaml_size_bytes`' do
+ expect(subject.max_total_yaml_size_bytes).to eq(157286400)
+ end
+ end
+
+ context 'when `max_total_yaml_size_bytes` was adjusted by the admin' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 200000000)
+ end
+
+ it 'returns the updated value of application setting `max_total_yaml_size_bytes`' do
+ expect(subject.max_total_yaml_size_bytes).to eq(200000000)
+ end
+ end
+ end
end
describe '#set_deadline' do
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index d6dd75f4b10..1415dbeb532 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -254,7 +254,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
describe '#load_and_validate_expanded_hash!' do
let(:location) { 'some/file/config.yml' }
let(:logger) { instance_double(::Gitlab::Ci::Pipeline::Logger, :instrument) }
- let(:context_params) { { sha: 'HEAD', variables: variables, project: project, logger: logger } }
+ let(:context_params) { { sha: 'HEAD', variables: variables, project: project, logger: logger, user: user } }
+ let(:user) { instance_double(User, id: 'test-user-id') }
+
+ before do
+ allow(logger).to receive(:instrument).and_yield
+ end
it 'includes instrumentation for loading and expanding the content' do
expect(logger).to receive(:instrument).once.ordered.with(:config_file_fetch_content_hash).and_yield
@@ -262,5 +267,26 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
file.load_and_validate_expanded_hash!
end
+
+ context 'when the content is interpolated' do
+ let(:content) { "spec:\n inputs:\n website:\n---\nkey: value" }
+
+ subject(:file) { test_class.new({ inputs: { website: 'test' }, location: location, content: content }, ctx) }
+
+ it 'increments the ci_interpolation_users usage counter' do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ .with('ci_interpolation_users', values: 'test-user-id')
+
+ file.load_and_validate_expanded_hash!
+ end
+ end
+
+ context 'when the content is not interpolated' do
+ it 'does not increment the ci_interpolation_users usage counter' do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ file.load_and_validate_expanded_hash!
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/component_spec.rb b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
index 7e3406413d0..487690296b5 100644
--- a/spec/lib/gitlab/ci/config/external/file/component_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
@@ -41,14 +41,6 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
let(:params) { { component: 'some-value' } }
it { is_expected.to be_truthy }
-
- context 'when feature flag ci_include_components is disabled' do
- before do
- stub_feature_flags(ci_include_components: false)
- end
-
- it { is_expected.to be_falsey }
- end
end
context 'when component is not specified' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
index 719c75dca80..cea65faccd7 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
@@ -18,54 +18,26 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
describe '#process' do
subject(:process) { matcher.process(locations) }
- context 'with ci_include_components FF disabled' do
- before do
- stub_feature_flags(ci_include_components: false)
- end
-
- let(:locations) do
- [
- { local: 'file.yml' },
- { file: 'file.yml', project: 'namespace/project' },
- { remote: 'https://example.com/.gitlab-ci.yml' },
- { template: 'file.yml' },
- { artifact: 'generated.yml', job: 'test' }
- ]
- end
-
- it 'returns an array of file objects' do
- is_expected.to contain_exactly(
- an_instance_of(Gitlab::Ci::Config::External::File::Local),
- an_instance_of(Gitlab::Ci::Config::External::File::Project),
- an_instance_of(Gitlab::Ci::Config::External::File::Remote),
- an_instance_of(Gitlab::Ci::Config::External::File::Template),
- an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
- )
- end
+ let(:locations) do
+ [
+ { local: 'file.yml' },
+ { file: 'file.yml', project: 'namespace/project' },
+ { component: 'gitlab.com/org/component@1.0' },
+ { remote: 'https://example.com/.gitlab-ci.yml' },
+ { template: 'file.yml' },
+ { artifact: 'generated.yml', job: 'test' }
+ ]
end
- context 'with ci_include_components FF enabled' do
- let(:locations) do
- [
- { local: 'file.yml' },
- { file: 'file.yml', project: 'namespace/project' },
- { component: 'gitlab.com/org/component@1.0' },
- { remote: 'https://example.com/.gitlab-ci.yml' },
- { template: 'file.yml' },
- { artifact: 'generated.yml', job: 'test' }
- ]
- end
-
- it 'returns an array of file objects' do
- is_expected.to contain_exactly(
- an_instance_of(Gitlab::Ci::Config::External::File::Local),
- an_instance_of(Gitlab::Ci::Config::External::File::Project),
- an_instance_of(Gitlab::Ci::Config::External::File::Component),
- an_instance_of(Gitlab::Ci::Config::External::File::Remote),
- an_instance_of(Gitlab::Ci::Config::External::File::Template),
- an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
- )
- end
+ it 'returns an array of file objects' do
+ is_expected.to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Local),
+ an_instance_of(Gitlab::Ci::Config::External::File::Project),
+ an_instance_of(Gitlab::Ci::Config::External::File::Component),
+ an_instance_of(Gitlab::Ci::Config::External::File::Remote),
+ an_instance_of(Gitlab::Ci::Config::External::File::Template),
+ an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
+ )
end
context 'when a location is not valid' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
index e7dd5bd5079..69b0524be9e 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
@@ -364,5 +364,77 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
end
end
end
+
+ describe '#verify_max_total_pipeline_size' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context)
+ ]
+ end
+
+ let(:project_files) do
+ {
+ 'myfolder/file1.yml' => <<~YAML,
+ build:
+ script: echo Hello World
+ YAML
+ 'myfolder/file2.yml' => <<~YAML
+ include:
+ - local: myfolder/file1.yml
+ build:
+ script: echo Hello from the other file
+ YAML
+ }
+ end
+
+ context 'when pipeline tree size is within the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 10000)
+ end
+
+ it 'passes the verification' do
+ expect(process.all?(&:valid?)).to be_truthy
+ end
+ end
+
+ context 'when pipeline tree size is larger then the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 50)
+ end
+
+ let(:expected_error_class) { Gitlab::Ci::Config::External::Mapper::TooMuchDataInPipelineTreeError }
+
+ it 'raises a limit error' do
+ expect { process }.to raise_error(expected_error_class)
+ end
+ end
+
+ context 'when introduce_ci_max_total_yaml_size_bytes is disabled' do
+ before do
+ stub_feature_flags(introduce_ci_max_total_yaml_size_bytes: false)
+ end
+
+ context 'when pipeline tree size is within the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 10000)
+ end
+
+ it 'passes the verification' do
+ expect(process.all?(&:valid?)).to be_truthy
+ end
+ end
+
+ context 'when pipeline tree size is larger then the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 100)
+ end
+
+ it 'passes the verification' do
+ expect(process.all?(&:valid?)).to be_truthy
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 935b6989dd7..19113ce6a4e 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -425,17 +425,6 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
output = processor.perform
expect(output.keys).to match_array([:image, :component_x_job])
end
-
- context 'when feature flag ci_include_components is disabled' do
- before do
- stub_feature_flags(ci_include_components: false)
- end
-
- it 'returns an error' do
- expect { processor.perform }
- .to raise_error(described_class::IncludeError, /does not have a valid subkey for include./)
- end
- end
end
context 'when a valid project file is defined' do
@@ -572,7 +561,17 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
end
it 'raises IncludeError' do
- expect { subject }.to raise_error(described_class::IncludeError, /invalid include rule/)
+ expect { subject }.to raise_error(described_class::IncludeError, /contains unknown keys: changes/)
+ end
+
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
+ end
+
+ it 'raises IncludeError' do
+ expect { subject }.to raise_error(described_class::IncludeError, /invalid include rule/)
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb
index 25b7998ef5e..8674af7ab65 100644
--- a/spec/lib/gitlab/ci/config/external/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb
@@ -76,8 +76,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
it 'raises an error' do
- expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}')
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
end
end
@@ -105,8 +104,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_
let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] }
it 'raises an error' do
- expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:exists=>"Dockerfile", :when=>"on_success"}')
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
end
end
@@ -121,8 +119,94 @@ RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_
let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
it 'raises an error' do
- expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:changes=>["$MY_VAR"]}')
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /contains unknown keys: changes/)
+ end
+ end
+
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
+ end
+
+ context 'when there is no rule' do
+ let(:rule_hashes) {}
+
+ it { is_expected.to eq(true) }
+ end
+
+ it_behaves_like 'when there is a rule with if'
+
+ context 'when there is a rule with exists' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile' }] }
+
+ it_behaves_like 'when there is a rule with exists'
+ end
+
+ context 'when there is a rule with if and when' do
+ context 'with when: never' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'never' }] }
+
+ it_behaves_like 'when there is a rule with if', false, false
+ end
+
+ context 'with when: always' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'always' }] }
+
+ it_behaves_like 'when there is a rule with if'
+ end
+
+ context 'with when: <invalid string>' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
+
+ it 'raises an error' do
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
+ 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}')
+ end
+ end
+
+ context 'with when: null' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: nil }] }
+
+ it_behaves_like 'when there is a rule with if'
+ end
+ end
+
+ context 'when there is a rule with exists and when' do
+ context 'with when: never' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'never' }] }
+
+ it_behaves_like 'when there is a rule with exists', false, false
+ end
+
+ context 'with when: always' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'always' }] }
+
+ it_behaves_like 'when there is a rule with exists'
+ end
+
+ context 'with when: <invalid string>' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] }
+
+ it 'raises an error' do
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
+ 'invalid include rule: {:exists=>"Dockerfile", :when=>"on_success"}')
+ end
+ end
+
+ context 'with when: null' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: nil }] }
+
+ it_behaves_like 'when there is a rule with exists'
+ end
+ end
+
+ context 'when there is a rule with changes' do
+ let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
+
+ it 'raises an error' do
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
+ 'invalid include rule: {:changes=>["$MY_VAR"]}')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/header/input_spec.rb b/spec/lib/gitlab/ci/config/header/input_spec.rb
index 73b5b8f9497..b5155dff6e8 100644
--- a/spec/lib/gitlab/ci/config/header/input_spec.rb
+++ b/spec/lib/gitlab/ci/config/header/input_spec.rb
@@ -46,12 +46,29 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co
it_behaves_like 'a valid input'
end
- context 'when is a required required input' do
+ context 'when is a required input' do
let(:input_hash) { nil }
it_behaves_like 'a valid input'
end
+ context 'when given a valid type' do
+ where(:input_type) { ::Gitlab::Ci::Config::Interpolation::Inputs.input_types }
+
+ with_them do
+ let(:input_hash) { { type: input_type } }
+
+ it_behaves_like 'a valid input'
+ end
+ end
+
+ context 'when given an invalid type' do
+ let(:input_hash) { { type: 'datetime' } }
+ let(:expected_errors) { ['foo input type unknown value: datetime'] }
+
+ it_behaves_like 'an invalid input'
+ end
+
context 'when contains unknown keywords' do
let(:input_hash) { { test: 123 } }
let(:expected_errors) { ['foo config contains unknown keys: test'] }
diff --git a/spec/lib/gitlab/ci/interpolation/access_spec.rb b/spec/lib/gitlab/ci/config/interpolation/access_spec.rb
index f327377b7e3..ee414c209f7 100644
--- a/spec/lib/gitlab/ci/interpolation/access_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/access_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Interpolation::Access, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Access, feature_category: :pipeline_composition do
subject { described_class.new(access, ctx) }
let(:access) do
@@ -46,4 +46,13 @@ RSpec.describe Gitlab::Ci::Interpolation::Access, feature_category: :pipeline_co
.to eq 'invalid interpolation access pattern'
end
end
+
+ context 'when a non-existent key is accessed' do
+ let(:access) { 'inputs.nonexistent' }
+
+ it 'returns an error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('unknown interpolation key: `nonexistent`')
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/interpolation/block_spec.rb b/spec/lib/gitlab/ci/config/interpolation/block_spec.rb
new file mode 100644
index 00000000000..bfaa4eb3e05
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/block_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Block, feature_category: :pipeline_composition do
+ subject { described_class.new(block, data, ctx) }
+
+ let(:data) do
+ 'inputs.data'
+ end
+
+ let(:block) do
+ "$[[ #{data} ]]"
+ end
+
+ let(:ctx) do
+ { inputs: { data: 'abcdef' }, env: { 'ENV' => 'dev' } }
+ end
+
+ it 'knows its content' do
+ expect(subject.content).to eq 'inputs.data'
+ end
+
+ it 'properly evaluates the access pattern' do
+ expect(subject.value).to eq 'abcdef'
+ end
+
+ describe '.match' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 ]] $[[ access2 ]]', &b) }
+ .to yield_successive_args(['$[[ access1 ]]', 'access1'], ['$[[ access2 ]]', 'access2'])
+ end
+
+ it 'matches an empty block' do
+ expect { |b| described_class.match('$[[]]', &b) }
+ .to yield_with_args('$[[]]', '')
+ end
+
+ context 'when functions are specified in the block' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 | func1 ]] $[[ access2 | func1 | func2(0,1) ]]', &b) }
+ .to yield_successive_args(['$[[ access1 | func1 ]]', 'access1 | func1'],
+ ['$[[ access2 | func1 | func2(0,1) ]]', 'access2 | func1 | func2(0,1)'])
+ end
+ end
+ end
+
+ describe 'when functions are specified in the block' do
+ let(:function_string1) { 'truncate(1,5)' }
+ let(:data) { "inputs.data | #{function_string1}" }
+ let(:access_value) { 'abcdef' }
+
+ it 'returns the modified value' do
+ expect(subject).to be_valid
+ expect(subject.value).to eq('bcdef')
+ end
+
+ context 'when there is an access error' do
+ let(:data) { "inputs.undefined | #{function_string1}" }
+
+ it 'returns the access error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('unknown interpolation key: `undefined`')
+ end
+ end
+
+ context 'when there is a function error' do
+ let(:data) { 'inputs.data | undefined' }
+
+ it 'returns the function error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `undefined`/)
+ end
+ end
+
+ context 'when multiple functions are specified' do
+ let(:function_string2) { 'truncate(2,2)' }
+ let(:data) { "inputs.data | #{function_string1} | #{function_string2}" }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+
+ context 'when the data has inconsistent spacing' do
+ let(:data) { "inputs.data|#{function_string1} | #{function_string2} " }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+ end
+
+ context 'when a stack of functions errors in the middle' do
+ let(:function_string2) { 'truncate(2)' }
+
+ it 'does not modify the value' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `truncate\(2\)`/)
+ expect(subject.instance_variable_get(:@value)).to be_nil
+ end
+ end
+
+ context 'when too many functions are specified' do
+ it 'returns error' do
+ stub_const('Gitlab::Ci::Config::Interpolation::Block::MAX_FUNCTIONS', 1)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('too many functions in interpolation block')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/config_spec.rb b/spec/lib/gitlab/ci/config/interpolation/config_spec.rb
new file mode 100644
index 00000000000..1731e954906
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/config_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Config, feature_category: :pipeline_composition do
+ subject { described_class.new(YAML.safe_load(config)) }
+
+ let(:config) do
+ <<~CFG
+ test:
+ spec:
+ env: $[[ inputs.env ]]
+
+ $[[ inputs.key ]]:
+ name: $[[ inputs.key ]]
+ script: my-value
+ CFG
+ end
+
+ describe '.fabricate' do
+ subject { described_class.fabricate(config) }
+
+ context 'when given an Interpolation::Config' do
+ let(:config) { described_class.new(YAML.safe_load('yaml:')) }
+
+ it 'returns the given config' do
+ is_expected.to be(config)
+ end
+ end
+
+ context 'when given an unknown object' do
+ let(:config) { [] }
+
+ it 'raises an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError, 'unknown interpolation config')
+ end
+ end
+ end
+
+ describe '#replace!' do
+ it 'replaces each of the nodes with a block return value' do
+ result = subject.replace! { |node| "abc#{node}cde" }
+
+ expect(result).to eq({
+ 'abctestcde' => { 'abcspeccde' => { 'abcenvcde' => 'abc$[[ inputs.env ]]cde' } },
+ 'abc$[[ inputs.key ]]cde' => {
+ 'abcnamecde' => 'abc$[[ inputs.key ]]cde',
+ 'abcscriptcde' => 'abcmy-valuecde'
+ }
+ })
+ expect(subject.to_h).to eq({
+ '$[[ inputs.key ]]' => { 'name' => '$[[ inputs.key ]]', 'script' => 'my-value' },
+ 'test' => { 'spec' => { 'env' => '$[[ inputs.env ]]' } }
+ })
+ end
+
+ context 'when config size is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_NODES", 7)
+ end
+
+ it 'returns a config size error' do
+ replaced = 0
+
+ subject.replace! { replaced += 1 }
+
+ expect(replaced).to eq 4
+ expect(subject.errors.size).to eq 1
+ expect(subject.errors.first).to eq 'config too large'
+ end
+ end
+
+ context 'when node size is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_NODE_SIZE", 1)
+ end
+
+ it 'returns a config size error' do
+ subject.replace! { |node| "abc#{node}cde" }
+
+ expect(subject.errors.size).to eq 1
+ expect(subject.errors.first).to eq 'config node too large'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/context_spec.rb b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
index 2b126f4a8b3..c90866c986a 100644
--- a/spec/lib/gitlab/ci/interpolation/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
@@ -2,13 +2,27 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Interpolation::Context, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Context, feature_category: :pipeline_composition do
subject { described_class.new(ctx) }
let(:ctx) do
{ inputs: { key: 'abc' } }
end
+ describe '.fabricate' do
+ context 'when given an unexpected object' do
+ it 'raises an ArgumentError' do
+ expect { described_class.fabricate([]) }.to raise_error(ArgumentError, 'unknown interpolation context')
+ end
+ end
+ end
+
+ describe '#to_h' do
+ it 'returns the context hash' do
+ expect(subject.to_h).to eq(ctx)
+ end
+ end
+
describe '#depth' do
it 'returns a max depth of the hash' do
expect(subject.depth).to eq 2
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
new file mode 100644
index 00000000000..c193e88dbe2
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Base, feature_category: :pipeline_composition do
+ let(:custom_function_klass) do
+ Class.new(described_class) do
+ def self.function_expression_pattern
+ /.*/
+ end
+
+ def self.name
+ 'test_function'
+ end
+ end
+ end
+
+ it 'defines an expected interface for child classes' do
+ expect { described_class.function_expression_pattern }.to raise_error(NotImplementedError)
+ expect { described_class.name }.to raise_error(NotImplementedError)
+ expect { custom_function_klass.new('test').execute('input') }.to raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
new file mode 100644
index 00000000000..c521eff9811
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Truncate, feature_category: :pipeline_composition do
+ it 'matches exactly the truncate function with 2 numeric arguments' do
+ expect(described_class.matches?('truncate(1,2)')).to be_truthy
+ expect(described_class.matches?('truncate( 11 , 222 )')).to be_truthy
+ expect(described_class.matches?('truncate( string , 222 )')).to be_falsey
+ expect(described_class.matches?('truncate(222)')).to be_falsey
+ expect(described_class.matches?('unknown(1,2)')).to be_falsey
+ end
+
+ it 'truncates the given input' do
+ function = described_class.new('truncate(1,2)')
+
+ output = function.execute('test')
+
+ expect(function).to be_valid
+ expect(output).to eq('es')
+ end
+
+ context 'when given a non-string input' do
+ it 'returns an error' do
+ function = described_class.new('truncate(1,2)')
+
+ function.execute(100)
+
+ expect(function).not_to be_valid
+ expect(function.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
new file mode 100644
index 00000000000..881f092c440
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::FunctionsStack, feature_category: :pipeline_composition do
+ let(:functions) { ['truncate(0,4)', 'truncate(1,2)'] }
+ let(:input_value) { 'test_input_value' }
+
+ subject { described_class.new(functions).evaluate(input_value) }
+
+ it 'modifies the given input value according to the function expressions' do
+ expect(subject).to be_success
+ expect(subject.value).to eq('es')
+ end
+
+ context 'when applying a function fails' do
+ let(:input_value) { 666 }
+
+ it 'returns the error given by the failure' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+
+ context 'when function expressions do not match any function' do
+ let(:functions) { ['truncate(0)', 'unknown'] }
+
+ it 'returns an error' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'no function matching `truncate(0)`: check that the function name, arguments, and types are correct',
+ 'no function matching `unknown`: check that the function name, arguments, and types are correct'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
new file mode 100644
index 00000000000..30036ee68ed
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs::BaseInput, feature_category: :pipeline_composition do
+ describe '.matches?' do
+ it 'is not implemented' do
+ expect { described_class.matches?(double) }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '.type_name' do
+ it 'is not implemented' do
+ expect { described_class.type_name }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#valid_value?' do
+ it 'is not implemented' do
+ expect do
+ described_class.new(
+ name: 'website', spec: { website: nil }, value: { website: 'example.com' }
+ ).valid_value?('test')
+ end.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
new file mode 100644
index 00000000000..ea06f181fa4
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pipeline_composition do
+ let(:inputs) { described_class.new(specs, args) }
+ let(:specs) { { foo: { default: 'bar' } } }
+ let(:args) { {} }
+
+ context 'when inputs are valid' do
+ where(:specs, :args, :merged) do
+ [
+ [
+ { foo: { default: 'bar' } }, {},
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { default: 'bar' } }, { foo: 'test' },
+ { foo: 'test' }
+ ],
+ [
+ { foo: nil }, { foo: 'bar' },
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { type: 'string' } }, { foo: 'bar' },
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { type: 'string', default: 'bar' } }, { foo: 'test' },
+ { foo: 'test' }
+ ],
+ [
+ { foo: { type: 'string', default: 'bar' } }, {},
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { default: 'bar' }, baz: nil }, { baz: 'test' },
+ { foo: 'bar', baz: 'test' }
+ ],
+ [
+ { number_input: { type: 'number' } },
+ { number_input: 8 },
+ { number_input: 8 }
+ ],
+ [
+ { default_number_input: { default: 9, type: 'number' } },
+ {},
+ { default_number_input: 9 }
+ ],
+ [
+ { true_input: { type: 'boolean' }, false_input: { type: 'boolean' } },
+ { true_input: true, false_input: false },
+ { true_input: true, false_input: false }
+ ],
+ [
+ { default_boolean_input: { default: true, type: 'boolean' } },
+ {},
+ { default_boolean_input: true }
+ ]
+ ]
+ end
+
+ with_them do
+ it 'contains the merged inputs' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(merged)
+ end
+ end
+ end
+
+ context 'when inputs are invalid' do
+ where(:specs, :args, :errors) do
+ [
+ [
+ { foo: nil }, { foo: 'bar', test: 'bar' },
+ ['unknown input arguments: test']
+ ],
+ [
+ { foo: nil }, { test: 'bar', gitlab: '1' },
+ ['unknown input arguments: test, gitlab', '`foo` input: required value has not been provided']
+ ],
+ [
+ { foo: 123 }, {},
+ ['unknown input specification for `foo` (valid types: boolean, number, string)']
+ ],
+ [
+ { a: nil, foo: 123 }, { a: '123' },
+ ['unknown input specification for `foo` (valid types: boolean, number, string)']
+ ],
+ [
+ { foo: nil }, {},
+ ['`foo` input: required value has not been provided']
+ ],
+ [
+ { foo: { default: 123 } }, { foo: 'test' },
+ ['`foo` input: default value is not a string']
+ ],
+ [
+ { foo: { default: 'test' } }, { foo: 123 },
+ ['`foo` input: provided value is not a string']
+ ],
+ [
+ { foo: nil }, { foo: 123 },
+ ['`foo` input: provided value is not a string']
+ ],
+ [
+ { number_input: { type: 'number' } },
+ { number_input: 'NaN' },
+ ['`number_input` input: provided value is not a number']
+ ],
+ [
+ { default_number_input: { default: 'NaN', type: 'number' } },
+ {},
+ ['`default_number_input` input: default value is not a number']
+ ],
+ [
+ { boolean_input: { type: 'boolean' } },
+ { boolean_input: 'string' },
+ ['`boolean_input` input: provided value is not a boolean']
+ ],
+ [
+ { default_boolean_input: { default: 'string', type: 'boolean' } },
+ {},
+ ['`default_boolean_input` input: default value is not a boolean']
+ ]
+ ]
+ end
+
+ with_them do
+ it 'contains the merged inputs', :aggregate_failures do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(*errors)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
index 888756a3eb1..7bb09d35064 100644
--- a/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
@@ -2,13 +2,12 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Interpolator, feature_category: :pipeline_composition do
let_it_be(:project) { create(:project) }
- let(:current_user) { build(:user, id: 1234) }
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) }
- subject { described_class.new(result, arguments, current_user: current_user) }
+ subject { described_class.new(result, arguments) }
context 'when input data is valid' do
let(:header) do
@@ -26,16 +25,10 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
it 'correctly interpolates the config' do
subject.interpolate!
+ expect(subject).to be_interpolated
expect(subject).to be_valid
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
-
- it 'tracks the event' do
- expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
- .with('ci_interpolation_users', { values: 1234 })
-
- subject.interpolate!
- end
end
context 'when config has a syntax error' do
@@ -54,6 +47,20 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
end
end
+ context 'when spec header is missing but inputs are specified' do
+ let(:header) { nil }
+ let(:content) { { test: 'echo' } }
+ let(:arguments) { { foo: 'bar' } }
+
+ it 'surfaces an error about invalid inputs' do
+ subject.interpolate!
+
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to eq subject.errors.first
+ expect(subject.errors).to include('unknown input arguments')
+ end
+ end
+
context 'when spec header is invalid' do
let(:header) do
{ spec: { arguments: { website: nil } } }
@@ -76,47 +83,47 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
end
end
- context 'when interpolation block is invalid' do
+ context 'when provided interpolation argument is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
- { test: 'deploy $[[ inputs.abc ]]' }
+ { test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
- { website: 'gitlab.com' }
+ { website: ['gitlab.com'] }
end
- it 'correctly interpolates the config' do
+ it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
- expect(subject.errors).to include 'unknown interpolation key: `abc`'
- expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
+ expect(subject.error_message).to eq subject.errors.first
+ expect(subject.errors).to include '`website` input: provided value is not a string'
end
end
- context 'when provided interpolation argument is invalid' do
+ context 'when interpolation block is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
- { test: 'deploy $[[ inputs.website ]]' }
+ { test: 'deploy $[[ inputs.abc ]]' }
end
let(:arguments) do
- { website: ['gitlab.com'] }
+ { website: 'gitlab.com' }
end
- it 'correctly interpolates the config' do
+ it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
- expect(subject.error_message).to eq subject.errors.first
- expect(subject.errors).to include 'unsupported value in input argument `website`'
+ expect(subject.errors).to include 'unknown interpolation key: `abc`'
+ expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
end
end
@@ -133,11 +140,12 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
{ website: 'gitlab.com' }
end
- it 'correctly interpolates the config' do
+ it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
- expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
+ expect(subject.error_message)
+ .to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
end
end
diff --git a/spec/lib/gitlab/ci/interpolation/template_spec.rb b/spec/lib/gitlab/ci/config/interpolation/template_spec.rb
index a3ef1bb4445..c7d88822558 100644
--- a/spec/lib/gitlab/ci/interpolation/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/template_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Template, feature_category: :pipeline_composition do
subject { described_class.new(YAML.safe_load(config), ctx) }
let(:config) do
@@ -67,7 +67,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_
context 'when template contains symbols that need interpolation' do
subject do
- described_class.new({ '$[[ inputs.key ]]'.to_sym => 'cde' }, ctx)
+ described_class.new({ '$[[ inputs.key ]]': 'cde' }, ctx)
end
it 'performs a valid interpolation' do
@@ -78,7 +78,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_
context 'when template is too large' do
before do
- stub_const('Gitlab::Ci::Interpolation::Config::MAX_NODES', 1)
+ stub_const('Gitlab::Ci::Config::Interpolation::Config::MAX_NODES', 1)
end
it 'returns an error' do
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index 96ca5d98a6e..cc549b38dc3 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
@@ -103,6 +103,34 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
end
end
+ shared_examples 'needs:parallel:matrix' do
+ let(:expanded_needs_parallel_job_attributes) do
+ expanded_needs_parallel_job_names.map do |job_name|
+ { name: job_name }
+ end
+ end
+
+ context 'when job has needs:parallel:matrix on parallelized jobs' do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: {
+ script: 'echo 1',
+ needs: {
+ job: [
+ { name: job_name.to_s, parallel: needs_parallel_config }
+ ]
+ }
+ }
+ }
+ end
+
+ it 'parallelizes and only keeps needs specified by needs:parallel:matrix' do
+ expect(subject.dig(:other_job, :needs, :job)).to eq(expanded_needs_parallel_job_attributes)
+ end
+ end
+ end
+
context 'with parallel config as integer' do
let(:variables_config) { {} }
let(:parallel_config) { 5 }
@@ -167,7 +195,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it_behaves_like 'parallel needs'
end
- context 'with parallel matrix config' do
+ context 'with a simple parallel matrix config' do
let(:variables_config) do
{
USER_VARIABLE: 'user value'
@@ -192,6 +220,19 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
]
end
+ let(:needs_parallel_config) do
+ {
+ matrix: [
+ {
+ VAR_1: ['A'],
+ VAR_2: ['C']
+ }
+ ]
+ }
+ end
+
+ let(:expanded_needs_parallel_job_names) { ['rspec: [A, C]'] }
+
it 'does not have original job' do
is_expected.not_to include(job_name)
end
@@ -228,6 +269,66 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it_behaves_like 'parallel dependencies'
it_behaves_like 'parallel needs'
+ it_behaves_like 'needs:parallel:matrix'
+ end
+
+ context 'with a complex parallel matrix config' do
+ let(:variables_config) { {} }
+ let(:parallel_config) do
+ {
+ matrix: [
+ {
+ PLATFORM: ['centos'],
+ STACK: %w[ruby python java],
+ DB: %w[postgresql mysql]
+ },
+ {
+ PLATFORM: ['ubuntu'],
+ PROVIDER: %w[aws gcp]
+ }
+ ]
+ }
+ end
+
+ let(:needs_parallel_config) do
+ {
+ matrix: [
+ {
+ PLATFORM: ['centos'],
+ STACK: %w[ruby python],
+ DB: ['postgresql']
+ },
+ {
+ PLATFORM: ['ubuntu'],
+ PROVIDER: ['aws']
+ }
+ ]
+ }
+ end
+
+ let(:expanded_needs_parallel_job_names) do
+ [
+ 'rspec: [centos, ruby, postgresql]',
+ 'rspec: [centos, python, postgresql]',
+ 'rspec: [ubuntu, aws]'
+ ]
+ end
+
+ let(:expanded_job_names) do
+ [
+ 'rspec: [centos, ruby, postgresql]',
+ 'rspec: [centos, ruby, mysql]',
+ 'rspec: [centos, python, postgresql]',
+ 'rspec: [centos, python, mysql]',
+ 'rspec: [centos, java, postgresql]',
+ 'rspec: [centos, java, mysql]',
+ 'rspec: [ubuntu, aws]',
+ 'rspec: [ubuntu, gcp]'
+ ]
+ end
+
+ it_behaves_like 'parallel needs'
+ it_behaves_like 'needs:parallel:matrix'
end
context 'when parallel config does not matches a factory' do
diff --git a/spec/lib/gitlab/ci/config/yaml/loader_spec.rb b/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
index 4e6151677e6..57a9a47d699 100644
--- a/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
@@ -21,12 +21,13 @@ RSpec.describe ::Gitlab::Ci::Config::Yaml::Loader, feature_category: :pipeline_c
YAML
end
- subject(:result) { described_class.new(yaml, inputs: inputs, current_user: project.creator).load }
+ subject(:result) { described_class.new(yaml, inputs: inputs).load }
it 'loads and interpolates CI config YAML' do
expected_config = { test_job: { script: ['echo "hello test"'] } }
expect(result).to be_valid
+ expect(result).to be_interpolated
expect(result.content).to eq(expected_config)
end
diff --git a/spec/lib/gitlab/ci/config/yaml/result_spec.rb b/spec/lib/gitlab/ci/config/yaml/result_spec.rb
index d17e0609ef6..a66c630dfc9 100644
--- a/spec/lib/gitlab/ci/config/yaml/result_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml/result_spec.rb
@@ -51,4 +51,14 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Result, feature_category: :pipeline_com
expect(result).not_to be_valid
expect(result.error).to be_a ArgumentError
end
+
+ describe '#interpolated?' do
+ it 'defaults to false' do
+ expect(described_class.new).not_to be_interpolated
+ end
+
+ it 'returns the value passed to the initializer' do
+ expect(described_class.new(interpolated: true)).to be_interpolated
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/yaml_spec.rb b/spec/lib/gitlab/ci/config/yaml_spec.rb
index 27d93d555f1..e30ddbb8033 100644
--- a/spec/lib/gitlab/ci/config/yaml_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml_spec.rb
@@ -36,17 +36,5 @@ RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_composition
.to raise_error ::Gitlab::Config::Loader::FormatError, /mapping values are not allowed in this context/
end
end
-
- context 'when given a user' do
- let(:user) { instance_double(User) }
-
- subject(:config) { described_class.load!(yaml, current_user: user) }
-
- it 'passes it to Loader' do
- expect(::Gitlab::Ci::Config::Yaml::Loader).to receive(:new).with(yaml, current_user: user).and_call_original
-
- config
- end
- end
end
end
diff --git a/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb b/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb
index dad5bd2548b..f1b10648f51 100644
--- a/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb
+++ b/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::DecompressedGzipSizeValidator, feature_category: :importers do
let_it_be(:filepath) { File.join(Dir.tmpdir, 'decompressed_gzip_size_validator_spec.gz') }
- before(:all) do
+ before_all do
create_compressed_file
end
diff --git a/spec/lib/gitlab/ci/input/arguments/base_spec.rb b/spec/lib/gitlab/ci/input/arguments/base_spec.rb
deleted file mode 100644
index ed8e99b7257..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/base_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Base, feature_category: :pipeline_composition do
- subject do
- Class.new(described_class) do
- def validate!; end
- def to_value; end
- end
- end
-
- it 'fabricates an invalid input argument if unknown value is provided' do
- argument = subject.new(:something, { spec: 123 }, [:a, :b])
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq 'unsupported value in input argument `something`'
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/default_spec.rb b/spec/lib/gitlab/ci/input/arguments/default_spec.rb
deleted file mode 100644
index bc0cee6ac4e..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/default_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Default, feature_category: :pipeline_composition do
- it 'returns a user-provided value if it is present' do
- argument = described_class.new(:website, { default: 'https://gitlab.com' }, 'https://example.gitlab.com')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'https://example.gitlab.com'
- expect(argument.to_hash).to eq({ website: 'https://example.gitlab.com' })
- end
-
- it 'returns an empty value if user-provider input is empty' do
- argument = described_class.new(:website, { default: 'https://gitlab.com' }, '')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq ''
- expect(argument.to_hash).to eq({ website: '' })
- end
-
- it 'returns a default value if user-provider one is unknown' do
- argument = described_class.new(:website, { default: 'https://gitlab.com' }, nil)
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'https://gitlab.com'
- expect(argument.to_hash).to eq({ website: 'https://gitlab.com' })
- end
-
- it 'returns an error if the default argument has not been recognized' do
- argument = described_class.new(:website, { default: ['gitlab.com'] }, 'abc')
-
- expect(argument).not_to be_valid
- end
-
- it 'returns an error if the argument has not been fabricated correctly' do
- argument = described_class.new(:website, { required: 'https://gitlab.com' }, 'https://example.gitlab.com')
-
- expect(argument).not_to be_valid
- end
-
- describe '.matches?' do
- it 'matches specs with default configuration' do
- expect(described_class.matches?({ default: 'abc' })).to be true
- end
-
- it 'does not match specs different configuration keyword' do
- expect(described_class.matches?({ options: %w[a b] })).to be false
- expect(described_class.matches?('a b c')).to be false
- expect(described_class.matches?(%w[default a])).to be false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/options_spec.rb b/spec/lib/gitlab/ci/input/arguments/options_spec.rb
deleted file mode 100644
index 17e3469b294..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/options_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Options, feature_category: :pipeline_composition do
- it 'returns a user-provided value if it is an allowed one' do
- argument = described_class.new(:run, { options: %w[opt1 opt2] }, 'opt1')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'opt1'
- expect(argument.to_hash).to eq({ run: 'opt1' })
- end
-
- it 'returns an error if user-provided value is not allowlisted' do
- argument = described_class.new(:run, { options: %w[opt1 opt2] }, 'opt3')
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`run` input: argument value opt3 not allowlisted'
- end
-
- it 'returns an error if specification is not correct' do
- argument = described_class.new(:website, { options: nil }, 'opt1')
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`website` input: argument specification invalid'
- end
-
- it 'returns an error if specification is using a hash' do
- argument = described_class.new(:website, { options: { a: 1 } }, 'opt1')
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`website` input: argument specification invalid'
- end
-
- it 'returns an empty value if it is allowlisted' do
- argument = described_class.new(:run, { options: ['opt1', ''] }, '')
-
- expect(argument).to be_valid
- expect(argument.to_value).to be_empty
- expect(argument.to_hash).to eq({ run: '' })
- end
-
- describe '.matches?' do
- it 'matches specs with options configuration' do
- expect(described_class.matches?({ options: %w[a b] })).to be true
- end
-
- it 'does not match specs different configuration keyword' do
- expect(described_class.matches?({ default: 'abc' })).to be false
- expect(described_class.matches?(['options'])).to be false
- expect(described_class.matches?('options')).to be false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/required_spec.rb b/spec/lib/gitlab/ci/input/arguments/required_spec.rb
deleted file mode 100644
index 847272998c2..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/required_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Required, feature_category: :pipeline_composition do
- it 'returns a user-provided value if it is present' do
- argument = described_class.new(:website, nil, 'https://example.gitlab.com')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'https://example.gitlab.com'
- expect(argument.to_hash).to eq({ website: 'https://example.gitlab.com' })
- end
-
- it 'returns an empty value if user-provider value is empty' do
- argument = described_class.new(:website, nil, '')
-
- expect(argument).to be_valid
- expect(argument.to_hash).to eq(website: '')
- end
-
- it 'returns an error if user-provided value is unspecified' do
- argument = described_class.new(:website, nil, nil)
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`website` input: required value has not been provided'
- end
-
- describe '.matches?' do
- it 'matches specs without configuration' do
- expect(described_class.matches?(nil)).to be true
- end
-
- it 'matches specs with empty configuration' do
- expect(described_class.matches?('')).to be true
- end
-
- it 'matches specs with an empty hash configuration' do
- expect(described_class.matches?({})).to be true
- end
-
- it 'does not match specs with configuration' do
- expect(described_class.matches?({ options: %w[a b] })).to be false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/unknown_spec.rb b/spec/lib/gitlab/ci/input/arguments/unknown_spec.rb
deleted file mode 100644
index 1270423ac72..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/unknown_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Unknown, feature_category: :pipeline_composition do
- it 'raises an error when someone tries to evaluate the value' do
- argument = described_class.new(:website, nil, 'https://example.gitlab.com')
-
- expect(argument).not_to be_valid
- expect { argument.to_value }.to raise_error ArgumentError
- end
-
- describe '.matches?' do
- it 'always matches' do
- expect(described_class.matches?('abc')).to be true
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/inputs_spec.rb b/spec/lib/gitlab/ci/input/inputs_spec.rb
deleted file mode 100644
index 5d2d5192299..00000000000
--- a/spec/lib/gitlab/ci/input/inputs_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Inputs, feature_category: :pipeline_composition do
- describe '#valid?' do
- let(:spec) { { website: nil } }
-
- it 'describes user-provided inputs' do
- inputs = described_class.new(spec, { website: 'http://example.gitlab.com' })
-
- expect(inputs).to be_valid
- end
- end
-
- context 'when proper specification has been provided' do
- let(:spec) do
- {
- website: nil,
- env: { default: 'development' },
- run: { options: %w[tests spec e2e] }
- }
- end
-
- let(:args) { { website: 'https://gitlab.com', run: 'tests' } }
-
- it 'fabricates desired input arguments' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).to be_valid
- expect(inputs.count).to eq 3
- expect(inputs.to_hash).to eq(args.merge(env: 'development'))
- end
- end
-
- context 'when inputs and args are empty' do
- it 'is a valid use-case' do
- inputs = described_class.new({}, {})
-
- expect(inputs).to be_valid
- expect(inputs.to_hash).to be_empty
- end
- end
-
- context 'when there are arguments recoincilation errors present' do
- context 'when required argument is missing' do
- let(:spec) { { website: nil } }
-
- it 'returns an error' do
- inputs = described_class.new(spec, {})
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq '`website` input: required value has not been provided'
- end
- end
-
- context 'when argument is not present but configured as allowlist' do
- let(:spec) do
- { run: { options: %w[opt1 opt2] } }
- end
-
- it 'returns an error' do
- inputs = described_class.new(spec, {})
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq '`run` input: argument not provided'
- end
- end
- end
-
- context 'when unknown specification argument has been used' do
- let(:spec) do
- {
- website: nil,
- env: { default: 'development' },
- run: { options: %w[tests spec e2e] },
- test: { unknown: 'something' }
- }
- end
-
- let(:args) { { website: 'https://gitlab.com', run: 'tests' } }
-
- it 'fabricates an unknown argument entry and returns an error' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).not_to be_valid
- expect(inputs.count).to eq 4
- expect(inputs.errors.first).to eq '`test` input: unrecognized input argument specification: `unknown`'
- end
- end
-
- context 'when unknown arguments are being passed by a user' do
- let(:spec) do
- { env: { default: 'development' } }
- end
-
- let(:args) { { website: 'https://gitlab.com', run: 'tests' } }
-
- it 'returns an error with a list of unknown arguments' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq 'unknown input arguments: [:website, :run]'
- end
- end
-
- context 'when composite specification is being used' do
- let(:spec) do
- {
- env: {
- default: 'dev',
- options: %w[test dev prod]
- }
- }
- end
-
- let(:args) { { env: 'dev' } }
-
- it 'returns an error describing an unknown specification' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq '`env` input: unrecognized input argument definition'
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/interpolation/block_spec.rb b/spec/lib/gitlab/ci/interpolation/block_spec.rb
deleted file mode 100644
index 4a8709df3dc..00000000000
--- a/spec/lib/gitlab/ci/interpolation/block_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_composition do
- subject { described_class.new(block, data, ctx) }
-
- let(:data) do
- 'inputs.data'
- end
-
- let(:block) do
- "$[[ #{data} ]]"
- end
-
- let(:ctx) do
- { inputs: { data: 'abc' }, env: { 'ENV' => 'dev' } }
- end
-
- it 'knows its content' do
- expect(subject.content).to eq 'inputs.data'
- end
-
- it 'properly evaluates the access pattern' do
- expect(subject.value).to eq 'abc'
- end
-
- describe '.match' do
- it 'matches each block in a string' do
- expect { |b| described_class.match('$[[ access1 ]] $[[ access2 ]]', &b) }
- .to yield_successive_args(['$[[ access1 ]]', 'access1'], ['$[[ access2 ]]', 'access2'])
- end
-
- it 'matches an empty block' do
- expect { |b| described_class.match('$[[]]', &b) }
- .to yield_with_args('$[[]]', '')
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/interpolation/config_spec.rb b/spec/lib/gitlab/ci/interpolation/config_spec.rb
deleted file mode 100644
index e745269d8c0..00000000000
--- a/spec/lib/gitlab/ci/interpolation/config_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Interpolation::Config, feature_category: :pipeline_composition do
- subject { described_class.new(YAML.safe_load(config)) }
-
- let(:config) do
- <<~CFG
- test:
- spec:
- env: $[[ inputs.env ]]
-
- $[[ inputs.key ]]:
- name: $[[ inputs.key ]]
- script: my-value
- CFG
- end
-
- describe '#replace!' do
- it 'replaces each od the nodes with a block return value' do
- result = subject.replace! { |node| "abc#{node}cde" }
-
- expect(result).to eq({
- 'abctestcde' => { 'abcspeccde' => { 'abcenvcde' => 'abc$[[ inputs.env ]]cde' } },
- 'abc$[[ inputs.key ]]cde' => {
- 'abcnamecde' => 'abc$[[ inputs.key ]]cde',
- 'abcscriptcde' => 'abcmy-valuecde'
- }
- })
- end
- end
-
- context 'when config size is exceeded' do
- before do
- stub_const("#{described_class}::MAX_NODES", 7)
- end
-
- it 'returns a config size error' do
- replaced = 0
-
- subject.replace! { replaced += 1 }
-
- expect(replaced).to eq 4
- expect(subject.errors.size).to eq 1
- expect(subject.errors.first).to eq 'config too large'
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb b/spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb
new file mode 100644
index 00000000000..0dd0d2fcf0d
--- /dev/null
+++ b/spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::JwtV2::ClaimMapper::Repository, feature_category: :continuous_integration do
+ let_it_be(:sha) { '35fa264414ee3ed7d0b8a6f5da40751c8600a772' }
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline, ref: 'test-branch-for-claim-mapper', sha: sha) }
+
+ let(:url) { 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml' }
+ let(:project_config) { instance_double(Gitlab::Ci::ProjectConfig, url: url) }
+
+ subject(:mapper) { described_class.new(project_config, pipeline) }
+
+ describe '#to_h' do
+ it 'returns expected claims' do
+ expect(mapper.to_h).to eq({
+ ci_config_ref_uri: 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml@refs/heads/test-branch-for-claim-mapper',
+ ci_config_sha: sha
+ })
+ end
+
+ context 'when ref is a tag' do
+ let_it_be(:tag) { 'test-tag-for-claim-mapper' }
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline, tag: tag, ref: tag, sha: sha) }
+
+ it 'returns expected claims' do
+ expect(mapper.to_h).to eq({
+ ci_config_ref_uri: 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml@refs/tags/test-tag-for-claim-mapper',
+ ci_config_sha: sha
+ })
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb b/spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb
new file mode 100644
index 00000000000..b7a73c938a3
--- /dev/null
+++ b/spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::JwtV2::ClaimMapper, feature_category: :continuous_integration do
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline) }
+
+ let(:source) { :unknown_source }
+ let(:url) { 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml' }
+ let(:project_config) { instance_double(Gitlab::Ci::ProjectConfig, url: url, source: source) }
+
+ subject(:mapper) { described_class.new(project_config, pipeline) }
+
+ describe '#to_h' do
+ it 'returns an empty hash when source is not implemented' do
+ expect(mapper.to_h).to eq({})
+ end
+
+ context 'when mapper for source is implemented' do
+ where(:source) { described_class::MAPPER_FOR_CONFIG_SOURCE.keys }
+ let(:result) do
+ {
+ ci_config_ref_uri: 'ci_config_ref_uri',
+ ci_config_sha: 'ci_config_sha'
+ }
+ end
+
+ with_them do
+ it 'uses mapper' do
+ mapper_class = described_class::MAPPER_FOR_CONFIG_SOURCE[source]
+ expect_next_instance_of(mapper_class, project_config, pipeline) do |instance|
+ expect(instance).to receive(:to_h).and_return(result)
+ end
+
+ expect(mapper.to_h).to eq(result)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb
index 575f174f737..d45d8cacb88 100644
--- a/spec/lib/gitlab/ci/jwt_v2_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb
@@ -129,75 +129,39 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :continuous_integration do
end
end
- describe 'ci_config_ref_uri' do
- it 'joins project_config.url and pipeline.source_ref_path with @' do
- expect(payload[:ci_config_ref_uri]).to eq('gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml' \
- '@refs/heads/auto-deploy-2020-03-19')
- end
-
- context 'when project config is nil' do
- before do
- allow(Gitlab::Ci::ProjectConfig).to receive(:new).and_return(nil)
- end
-
- it 'is nil' do
- expect(payload[:ci_config_ref_uri]).to be_nil
- end
- end
-
- context 'when ProjectConfig#url raises an error' do
- before do
- allow(project_config).to receive(:url).and_raise(RuntimeError)
- end
+ describe 'claims delegated to mapper' do
+ let(:ci_config_ref_uri) { 'ci_config_ref_uri' }
+ let(:ci_config_sha) { 'ci_config_sha' }
- it 'raises the same error' do
- expect { payload }.to raise_error(RuntimeError)
+ it 'delegates claims to Gitlab::Ci::JwtV2::ClaimMapper' do
+ expect_next_instance_of(Gitlab::Ci::JwtV2::ClaimMapper, project_config, pipeline) do |mapper|
+ expect(mapper).to receive(:to_h).and_return({
+ ci_config_ref_uri: ci_config_ref_uri,
+ ci_config_sha: ci_config_sha
+ })
end
- context 'in production' do
- before do
- stub_rails_env('production')
- end
-
- it 'is nil' do
- expect(payload[:ci_config_ref_uri]).to be_nil
- end
- end
- end
-
- context 'when config source is not repository' do
- before do
- allow(project_config).to receive(:source).and_return(:auto_devops_source)
- end
-
- it 'is nil' do
- expect(payload[:ci_config_ref_uri]).to be_nil
- end
+ expect(payload[:ci_config_ref_uri]).to eq(ci_config_ref_uri)
+ expect(payload[:ci_config_sha]).to eq(ci_config_sha)
end
end
- describe 'ci_config_sha' do
- it 'is the SHA of the pipeline' do
- expect(payload[:ci_config_sha]).to eq(pipeline.sha)
- end
+ describe 'project_visibility' do
+ using RSpec::Parameterized::TableSyntax
- context 'when project config is nil' do
- before do
- allow(Gitlab::Ci::ProjectConfig).to receive(:new).and_return(nil)
- end
-
- it 'is nil' do
- expect(payload[:ci_config_sha]).to be_nil
- end
+ where(:visibility_level, :visibility_level_string) do
+ Project::PUBLIC | 'public'
+ Project::INTERNAL | 'internal'
+ Project::PRIVATE | 'private'
end
- context 'when config source is not repository' do
+ with_them do
before do
- allow(project_config).to receive(:source).and_return(:auto_devops_source)
+ project.visibility_level = visibility_level
end
- it 'is nil' do
- expect(payload[:ci_config_sha]).to be_nil
+ it 'is a string representation of the project visibility_level' do
+ expect(payload[:project_visibility]).to eq(visibility_level_string)
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 9c268d9039e..66e4b987ac1 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -42,9 +42,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
before do
expect(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(pipeline.sha, ci_config_path)
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'builds root config including the local custom file' do
@@ -132,9 +132,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
before do
expect(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(pipeline.sha, '.gitlab-ci.yml')
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'builds root config including the canonical CI config file' do
diff --git a/spec/lib/gitlab/ci/project_config/repository_spec.rb b/spec/lib/gitlab/ci/project_config/repository_spec.rb
index e8a997a7e43..bd95eefe821 100644
--- a/spec/lib/gitlab/ci/project_config/repository_spec.rb
+++ b/spec/lib/gitlab/ci/project_config/repository_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Repository, feature_category: :continu
context 'when Gitaly raises error' do
before do
- allow(project.repository).to receive(:gitlab_ci_yml_for).and_raise(GRPC::Internal)
+ allow(project.repository).to receive(:blob_at).and_raise(GRPC::Internal)
end
it { is_expected.to be_nil }
diff --git a/spec/lib/gitlab/ci/project_config_spec.rb b/spec/lib/gitlab/ci/project_config_spec.rb
index 13ef0939ddd..6a4af3c61bf 100644
--- a/spec/lib/gitlab/ci/project_config_spec.rb
+++ b/spec/lib/gitlab/ci/project_config_spec.rb
@@ -45,9 +45,9 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
before do
allow(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(sha, ci_config_path)
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'returns root config including the local custom file' do
@@ -122,9 +122,9 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
before do
allow(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(sha, '.gitlab-ci.yml')
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'returns root config including the canonical CI config file' do
diff --git a/spec/lib/gitlab/ci/queue/metrics_spec.rb b/spec/lib/gitlab/ci/queue/metrics_spec.rb
new file mode 100644
index 00000000000..2fb4226ba5a
--- /dev/null
+++ b/spec/lib/gitlab/ci/queue/metrics_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Queue::Metrics, feature_category: :continuous_integration do
+ let(:metrics) { described_class.new(build(:ci_runner)) }
+
+ describe '#observe_queue_depth' do
+ subject { metrics.observe_queue_depth(:found, 1) }
+
+ it { is_expected.not_to be_nil }
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#observe_queue_size' do
+ subject { metrics.observe_queue_size(-> { 0 }, :some_runner_type) }
+
+ it { is_expected.not_to be_nil }
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#observe_queue_time' do
+ subject { metrics.observe_queue_time(:process, :some_runner_type) { 1 } }
+
+ specify do
+ expect(described_class).to receive(:queue_iteration_duration_seconds).and_call_original
+
+ subject
+ end
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ specify do
+ expect(described_class).not_to receive(:queue_iteration_duration_seconds)
+
+ subject
+ end
+ end
+
+ describe '.observe_active_runners' do
+ subject { described_class.observe_active_runners(-> { 0 }) }
+
+ it { is_expected.not_to be_nil }
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
index 5dbcc1991d4..d62d25aeefe 100644
--- a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
@@ -27,6 +27,154 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen
)
end
+ describe '#name' do
+ subject { component.name }
+
+ it { is_expected.to eq(name) }
+
+ context 'with namespace' do
+ let(:purl) do
+ 'pkg:maven/org.NameSpace/Name@v0.0.1'
+ end
+
+ it { is_expected.to eq('org.NameSpace/Name') }
+
+ context 'when needing normalization' do
+ let(:purl) do
+ 'pkg:pypi/org.NameSpace/Name@v0.0.1'
+ end
+
+ it { is_expected.to eq('org.namespace/name') }
+ end
+ end
+ end
+
+ describe '#<=>' do
+ where do
+ {
+ 'equal' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: 0
+ },
+ 'name lesser' => {
+ a_name: 'component-a',
+ b_name: 'component-b',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-b@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: -1
+ },
+ 'name greater' => {
+ a_name: 'component-b',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-b@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: 1
+ },
+ 'purl type lesser' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:composer/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: -1
+ },
+ 'purl type greater' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:composer/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: 1
+ },
+ 'purl type nulls first' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: nil,
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: -1
+ },
+ 'version lesser' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '2.0.0',
+ expected: -1
+ },
+ 'version greater' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '2.0.0',
+ b_version: '1.0.0',
+ expected: 1
+ },
+ 'version nulls first' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: nil,
+ b_version: '1.0.0',
+ expected: -1
+ }
+ }
+ end
+
+ with_them do
+ specify do
+ a = described_class.new(
+ name: a_name,
+ type: a_type,
+ purl: a_purl,
+ version: a_version
+ )
+
+ b = described_class.new(
+ name: b_name,
+ type: b_type,
+ purl: b_purl,
+ version: b_version
+ )
+
+ expect(a <=> b).to eq(expected)
+ end
+ end
+ end
+
describe '#ingestible?' do
subject { component.ingestible? }
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 702341a7ea7..34e430202c9 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory, feature_category: :continuous
end
context 'when stage has manual builds' do
- Ci::HasStatus::BLOCKED_STATUS.each do |core_status|
+ (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
context "when status is #{core_status}" do
let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
index e23645c106b..fc52b7bf9d4 100644
--- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual, feature_category: :continu
context 'when stage is skipped' do
let(:stage) { create(:ci_stage, status: :skipped) }
- it { is_expected.to be_falsy }
+ it { is_expected.to be_truthy }
end
context 'when stage is manual' do
diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
index b72a818c16c..460ecbb05d0 100644
--- a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
+++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
subject(:service) { described_class.new(statuses) }
describe 'gem version' do
- let(:acceptable_version) { '9.0.0' }
+ let(:acceptable_version) { '9.0.1' }
let(:error_message) do
<<~MESSAGE
diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
index e5324560944..0880c556523 100644
--- a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
CI_PIPELINE_IID
CI_PIPELINE_SOURCE
CI_PIPELINE_CREATED_AT
+ CI_PIPELINE_NAME
CI_COMMIT_SHA
CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA
@@ -43,6 +44,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
CI_PIPELINE_IID
CI_PIPELINE_SOURCE
CI_PIPELINE_CREATED_AT
+ CI_PIPELINE_NAME
CI_COMMIT_SHA
CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index 28c9bdc4c4b..3411426fcdb 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -111,6 +111,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache, featur
value: pipeline.source },
{ key: 'CI_PIPELINE_CREATED_AT',
value: pipeline.created_at.iso8601 },
+ { key: 'CI_PIPELINE_NAME',
+ value: pipeline.name },
{ key: 'CI_COMMIT_SHA',
value: job.sha },
{ key: 'CI_COMMIT_SHORT_SHA',
diff --git a/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
index 5b33527e06c..95d0f089f6d 100644
--- a/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
@@ -7,13 +7,19 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::ExpandableVariableGenerator, f
Gitlab::Ci::Variables::Collection.fabricate(
[
{ key: 'REF1', value: 'ref 1' },
- { key: 'REF2', value: 'ref 2' }
+ { key: 'REF2', value: 'ref 2' },
+ { key: 'NESTED_REF1', value: 'nested $REF1' }
]
)
end
+ let(:expand_file_refs) { false }
+
let(:context) do
- Gitlab::Ci::Variables::Downstream::Generator::Context.new(all_bridge_variables: all_bridge_variables)
+ Gitlab::Ci::Variables::Downstream::Generator::Context.new(
+ all_bridge_variables: all_bridge_variables,
+ expand_file_refs: expand_file_refs
+ )
end
subject(:generator) { described_class.new(context) }
@@ -34,5 +40,54 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::ExpandableVariableGenerator, f
expect(generator.for(var)).to match_array([{ key: 'VAR1', value: 'ref 1 ref 2 ' }])
end
end
+
+ context 'when given a variable with nested interpolation' do
+ it 'returns an array containing the expanded variables' do
+ var = Gitlab::Ci::Variables::Collection::Item.fabricate({ key: 'VAR1', value: '$REF1 $REF2 $NESTED_REF1' })
+
+ expect(generator.for(var)).to match_array([{ key: 'VAR1', value: 'ref 1 ref 2 nested $REF1' }])
+ end
+ end
+
+ context 'when given a variable with expansion on a file variable' do
+ let(:all_bridge_variables) do
+ Gitlab::Ci::Variables::Collection.fabricate(
+ [
+ { key: 'REF1', value: 'ref 1' },
+ { key: 'FILE_REF2', value: 'ref 2', file: true },
+ { key: 'NESTED_REF3', value: 'ref 3 $REF1 and $FILE_REF2', file: true }
+ ]
+ )
+ end
+
+ context 'when expand_file_refs is false' do
+ let(:expand_file_refs) { false }
+
+ it 'returns an array containing the unexpanded variable and the file variable dependency' do
+ var = { key: 'VAR1', value: '$REF1 $FILE_REF2 $FILE_REF3 $NESTED_REF3' }
+ var = Gitlab::Ci::Variables::Collection::Item.fabricate(var)
+
+ expected = [
+ { key: 'VAR1', value: 'ref 1 $FILE_REF2 $NESTED_REF3' },
+ { key: 'FILE_REF2', value: 'ref 2', variable_type: :file },
+ { key: 'NESTED_REF3', value: 'ref 3 $REF1 and $FILE_REF2', variable_type: :file }
+ ]
+
+ expect(generator.for(var)).to match_array(expected)
+ end
+ end
+
+ context 'when expand_file_refs is true' do
+ let(:expand_file_refs) { true }
+
+ it 'returns an array containing the expanded variables' do
+ var = { key: 'VAR1', value: '$REF1 $FILE_REF2 $FILE_REF3 $NESTED_REF3' }
+ var = Gitlab::Ci::Variables::Collection::Item.fabricate(var)
+
+ expected = { key: 'VAR1', value: 'ref 1 ref 2 ref 3 $REF1 and $FILE_REF2' }
+ expect(generator.for(var)).to contain_exactly(expected)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
index 61e8b9a8c4a..cd68b0cdf2b 100644
--- a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
@@ -45,6 +45,7 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
variables: bridge_variables,
forward_yaml_variables?: true,
forward_pipeline_variables?: true,
+ expand_file_refs?: false,
yaml_variables: yaml_variables,
pipeline_variables: pipeline_variables,
pipeline_schedule_variables: pipeline_schedule_variables
@@ -81,5 +82,61 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
expect(generator.calculate).to be_empty
end
+
+ context 'with file variable interpolation' do
+ let(:bridge_variables) do
+ Gitlab::Ci::Variables::Collection.fabricate(
+ [
+ { key: 'REF1', value: 'ref 1' },
+ { key: 'FILE_REF3', value: 'ref 3', file: true }
+ ]
+ )
+ end
+
+ let(:yaml_variables) do
+ [{ key: 'INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
+ let(:pipeline_variables) do
+ [{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
+ let(:pipeline_schedule_variables) do
+ [{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
+ context 'when expand_file_refs is true' do
+ before do
+ allow(bridge).to receive(:expand_file_refs?).and_return(true)
+ end
+
+ it 'expands file variables' do
+ expected = [
+ { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
+ { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
+ { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }
+ ]
+
+ expect(generator.calculate).to contain_exactly(*expected)
+ end
+ end
+
+ context 'when expand_file_refs is false' do
+ before do
+ allow(bridge).to receive(:expand_file_refs?).and_return(false)
+ end
+
+ it 'does not expand file variables and adds file variables' do
+ expected = [
+ { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'FILE_REF3', value: 'ref 3', variable_type: :file }
+ ]
+
+ expect(generator.calculate).to contain_exactly(*expected)
+ end
+ 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 c4e27d0e420..f8f1d71e773 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -2675,6 +2675,42 @@ module Gitlab
it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings'
end
+
+ context 'needs with parallel:matrix' do
+ let(:config) do
+ {
+ build1: {
+ stage: 'build',
+ script: 'build',
+ parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': %w[monitoring app1 app2] }] }
+ },
+ test1: {
+ stage: 'test',
+ script: 'test',
+ needs: [{ job: 'build1', parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': ['app1'] }] } }]
+ }
+ }
+ end
+
+ it "does create jobs with valid specification" do
+ expect(subject.builds.size).to eq(4)
+ expect(subject.builds[3]).to eq(
+ stage: "test",
+ stage_idx: 2,
+ name: "test1",
+ only: { refs: %w[branches tags] },
+ options: { script: ["test"] },
+ needs_attributes: [
+ { name: "build1: [aws, app1]", artifacts: true, optional: false }
+ ],
+ when: "on_success",
+ allow_failure: false,
+ job_variables: [],
+ root_variables_inheritance: true,
+ scheduling_type: :dag
+ )
+ end
+ end
end
context 'with when/rules' do